286 lines
6.3 KiB
Python
286 lines
6.3 KiB
Python
"""
|
|
====T Block====
|
|
|
|
#
|
|
###
|
|
|
|
0 1 0
|
|
1 1 1
|
|
|
|
1 0
|
|
1 1
|
|
1 0
|
|
|
|
1 1 1
|
|
0 1 0
|
|
|
|
0 1
|
|
1 1
|
|
0 1
|
|
|
|
====I Block====
|
|
|
|
####
|
|
|
|
1 1 1 1
|
|
|
|
1
|
|
1
|
|
1
|
|
1
|
|
|
|
====O Block====
|
|
|
|
##
|
|
##
|
|
|
|
1 1
|
|
1 1
|
|
|
|
====S Block=====
|
|
|
|
##
|
|
##
|
|
|
|
0 1 1
|
|
1 1 0
|
|
|
|
1 0
|
|
1 1
|
|
0 1
|
|
|
|
====L Block=====
|
|
|
|
#
|
|
#
|
|
##
|
|
|
|
1 0
|
|
1 0
|
|
1 1
|
|
"""
|
|
|
|
import math
|
|
import random
|
|
import os
|
|
|
|
BEST_SCORE_FILE_NAME = "best_score"
|
|
|
|
block_shapes = [
|
|
# T Block
|
|
[[0, 1, 0],
|
|
[1, 1, 1]],
|
|
# L Block
|
|
[[1, 0],
|
|
[1, 0],
|
|
[1, 1]],
|
|
# S Block
|
|
[[0, 1, 1],
|
|
[1, 1, 0]],
|
|
# O Block
|
|
[[1, 1],
|
|
[1, 1]],
|
|
# I Block
|
|
[[1], [1], [1], [1]]
|
|
]
|
|
|
|
|
|
class Board:
|
|
"""Board representation"""
|
|
|
|
def __init__(self, height, width):
|
|
self.height = height
|
|
self.width = width
|
|
self.board = self._get_new_board()
|
|
|
|
self.current_block_pos = None
|
|
self.current_block = None
|
|
self.next_block = None
|
|
|
|
self.game_over = False
|
|
self.score = None
|
|
self.lines = None
|
|
self.best_score = None
|
|
self.level = None
|
|
|
|
def start(self):
|
|
"""Start game"""
|
|
|
|
self.board = self._get_new_board()
|
|
|
|
self.current_block_pos = None
|
|
self.current_block = None
|
|
self.next_block = None
|
|
|
|
self.game_over = False
|
|
self.score = 0
|
|
self.lines = 0
|
|
self.level = 1
|
|
self.best_score = self._read_best_score()
|
|
|
|
self._place_new_block()
|
|
|
|
def is_game_over(self):
|
|
"""Is game over"""
|
|
|
|
return self.game_over
|
|
|
|
def rotate_block(self):
|
|
rotated_shape = list(map(list, zip(*self.current_block.shape[::-1])))
|
|
|
|
if self._can_move(self.current_block_pos, rotated_shape):
|
|
self.current_block.shape = rotated_shape
|
|
|
|
def move_block(self, direction):
|
|
"""Try to move block"""
|
|
|
|
pos = self.current_block_pos
|
|
if direction == "left":
|
|
new_pos = [pos[0], pos[1] - 1]
|
|
elif direction == "right":
|
|
new_pos = [pos[0], pos[1] + 1]
|
|
elif direction == "down":
|
|
new_pos = [pos[0] + 1, pos[1]]
|
|
else:
|
|
raise ValueError("wrong directions")
|
|
|
|
if self._can_move(new_pos, self.current_block.shape):
|
|
self.current_block_pos = new_pos
|
|
elif direction == "down":
|
|
self._land_block()
|
|
self._burn()
|
|
self._place_new_block()
|
|
|
|
def drop(self):
|
|
"""Move to very very bottom"""
|
|
|
|
i = 1
|
|
while self._can_move((self.current_block_pos[0] + 1, self.current_block_pos[1]), self.current_block.shape):
|
|
i += 1
|
|
self.move_block("down")
|
|
|
|
self._land_block()
|
|
self._burn()
|
|
self._place_new_block()
|
|
|
|
def _get_new_board(self):
|
|
"""Create new empty board"""
|
|
|
|
return [[0 for _ in range(self.width)] for _ in range(self.height)]
|
|
|
|
def _place_new_block(self):
|
|
"""Place new block and generate the next one"""
|
|
|
|
if self.next_block is None:
|
|
self.current_block = self._get_new_block()
|
|
self.next_block = self._get_new_block()
|
|
else:
|
|
self.current_block = self.next_block
|
|
self.next_block = self._get_new_block()
|
|
|
|
size = Block.get_size(self.current_block.shape)
|
|
col_pos = math.floor((self.width - size[1]) / 2)
|
|
self.current_block_pos = [0, col_pos]
|
|
|
|
if self._check_overlapping(self.current_block_pos, self.current_block.shape):
|
|
self.game_over = True
|
|
self._save_best_score()
|
|
else:
|
|
self.score += 5
|
|
|
|
def _land_block(self):
|
|
"""Put block to the board and generate a new one"""
|
|
|
|
size = Block.get_size(self.current_block.shape)
|
|
for row in range(size[0]):
|
|
for col in range(size[1]):
|
|
if self.current_block.shape[row][col] == 1:
|
|
self.board[self.current_block_pos[0] + row][self.current_block_pos[1] + col] = 1
|
|
|
|
def _burn(self):
|
|
"""Remove matched lines"""
|
|
|
|
for row in range(self.height):
|
|
if all(col != 0 for col in self.board[row]):
|
|
for r in range(row, 0, -1):
|
|
self.board[r] = self.board[r - 1]
|
|
self.board[0] = [0 for _ in range(self.width)]
|
|
self.score += 100
|
|
self.lines += 1
|
|
if self.lines % 10 == 0:
|
|
self.level += 1
|
|
|
|
def _check_overlapping(self, pos, shape):
|
|
"""If current block overlaps any other on the board"""
|
|
|
|
size = Block.get_size(shape)
|
|
for row in range(size[0]):
|
|
for col in range(size[1]):
|
|
if shape[row][col] == 1:
|
|
if self.board[pos[0] + row][pos[1] + col] == 1:
|
|
return True
|
|
return False
|
|
|
|
def _can_move(self, pos, shape):
|
|
"""Check if move is possible"""
|
|
|
|
size = Block.get_size(shape)
|
|
if pos[1] < 0 or pos[1] + size[1] > self.width \
|
|
or pos[0] + size[0] > self.height:
|
|
return False
|
|
|
|
return not self._check_overlapping(pos, shape)
|
|
|
|
def _save_best_score(self):
|
|
"""Save best score to file"""
|
|
|
|
if self.best_score < self.score:
|
|
with open(BEST_SCORE_FILE_NAME, "w") as file:
|
|
file.write(str(self.score))
|
|
|
|
@staticmethod
|
|
def _read_best_score():
|
|
"""Read best score from file"""
|
|
|
|
if os.path.exists(f"./{BEST_SCORE_FILE_NAME}"):
|
|
with open(BEST_SCORE_FILE_NAME) as file:
|
|
return int(file.read())
|
|
return 0
|
|
|
|
@staticmethod
|
|
def _get_new_block():
|
|
"""Get random block"""
|
|
|
|
block = Block(random.randint(0, len(block_shapes) - 1))
|
|
|
|
# flip it randomly
|
|
if random.getrandbits(1):
|
|
block.flip()
|
|
|
|
return block
|
|
|
|
|
|
class Block:
|
|
"""Block representation"""
|
|
|
|
def __init__(self, block_type):
|
|
self.shape = block_shapes[block_type]
|
|
self.color = block_type + 1
|
|
|
|
def flip(self):
|
|
self.shape = list(map(list, self.shape[::-1]))
|
|
|
|
def _get_rotated(self):
|
|
return list(map(list, zip(*self.shape[::-1])))
|
|
|
|
def size(self):
|
|
"""Get size of the block"""
|
|
|
|
return self.get_size(self.shape)
|
|
|
|
@staticmethod
|
|
def get_size(shape):
|
|
"""Get size of a shape"""
|
|
|
|
return [len(shape), len(shape[0])]
|