TetrisTerminalExportWindows/board.py

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])]