Continue work
This commit is contained in:
@@ -43,7 +43,7 @@ def mirror(mat: list[list]) -> list[list]:
|
|||||||
|
|
||||||
class Tetromino(NamedTuple):
|
class Tetromino(NamedTuple):
|
||||||
color: str
|
color: str
|
||||||
shape: list[list[str]]
|
shape: list[list]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def width(self):
|
def width(self):
|
||||||
@@ -73,6 +73,12 @@ class Tetromino(NamedTuple):
|
|||||||
out.append("\n")
|
out.append("\n")
|
||||||
return "".join(out)
|
return "".join(out)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return id(self) == id(other)
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return id(self)
|
||||||
|
|
||||||
|
|
||||||
class Action(NamedTuple):
|
class Action(NamedTuple):
|
||||||
key: int
|
key: int
|
||||||
@@ -716,13 +722,10 @@ class Game:
|
|||||||
)
|
)
|
||||||
SMALL_FONT = FONT.scaled(4, 2, 2)
|
SMALL_FONT = FONT.scaled(4, 2, 2)
|
||||||
TINY_FONT = FONT.scaled(2, 1, 1)
|
TINY_FONT = FONT.scaled(2, 1, 1)
|
||||||
SCORE_DISPLAY_BORDER = 5
|
CONTROL_DISPLAY_BORDER = 5
|
||||||
|
NEXT_HELD_BOX_SIZE = 5
|
||||||
CONTROLS_START_X = (BOARD_SIZE.width + 2) * CELL_SIZE.width
|
CONTROLS_START_X = (BOARD_SIZE.width + 2) * CELL_SIZE.width
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def random_tetromino():
|
|
||||||
return random.choice(Game.TETROMINOS)
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._game_over = False
|
self._game_over = False
|
||||||
|
|
||||||
@@ -740,15 +743,23 @@ class Game:
|
|||||||
else:
|
else:
|
||||||
self.action_map = Game.GAME_ACTION_MAP
|
self.action_map = Game.GAME_ACTION_MAP
|
||||||
|
|
||||||
def draw_block(self, x: int, y: int, color):
|
def random_tetromino(self):
|
||||||
|
if not self.cur_random_peice_seq:
|
||||||
|
self.cur_random_peice_seq = list(Game.TETROMINOS)
|
||||||
|
random.shuffle(self.cur_random_peice_seq)
|
||||||
|
return self.cur_random_peice_seq.pop(0)
|
||||||
|
|
||||||
|
def draw_block(self, x: int, y: int, color, target=None):
|
||||||
"""Draw a block at (X, Y). Coordinates are for the top left corner."""
|
"""Draw a block at (X, Y). Coordinates are for the top left corner."""
|
||||||
|
if target is None:
|
||||||
|
target = self.screen
|
||||||
draw.rect(
|
draw.rect(
|
||||||
self.screen,
|
target,
|
||||||
Game.PALETTE[color].norm,
|
Game.PALETTE[color].norm,
|
||||||
(x, y, Game.CELL_SIZE.width, Game.CELL_SIZE.height),
|
(x, y, Game.CELL_SIZE.width, Game.CELL_SIZE.height),
|
||||||
)
|
)
|
||||||
draw.polygon(
|
draw.polygon(
|
||||||
self.screen,
|
target,
|
||||||
Game.PALETTE[color].light,
|
Game.PALETTE[color].light,
|
||||||
[
|
[
|
||||||
(x, y),
|
(x, y),
|
||||||
@@ -767,7 +778,7 @@ class Game:
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
draw.polygon(
|
draw.polygon(
|
||||||
self.screen,
|
target,
|
||||||
Game.PALETTE[color].dark,
|
Game.PALETTE[color].dark,
|
||||||
[
|
[
|
||||||
(x + Game.CELL_SIZE.width, y),
|
(x + Game.CELL_SIZE.width, y),
|
||||||
@@ -861,10 +872,16 @@ class Game:
|
|||||||
self.handle_key_event(event.type, event.key)
|
self.handle_key_event(event.type, event.key)
|
||||||
|
|
||||||
def swap_with_hold(self):
|
def swap_with_hold(self):
|
||||||
to_swap = self.held_piece or Game.random_tetromino()
|
to_swap = self.held_piece
|
||||||
|
used_next = False
|
||||||
|
if not to_swap:
|
||||||
|
to_swap = self.next_piece
|
||||||
|
used_next = True
|
||||||
if not self.intersects_board(*self.current_block_pos, to_swap):
|
if not self.intersects_board(*self.current_block_pos, to_swap):
|
||||||
self.held_piece = self.current_block
|
self.held_piece = self.current_block
|
||||||
self.current_block = to_swap
|
self.current_block = to_swap
|
||||||
|
if used_next:
|
||||||
|
self.next_piece = self.random_tetromino()
|
||||||
|
|
||||||
def intersects_board(self, x: int, y: int, piece: Tetromino):
|
def intersects_board(self, x: int, y: int, piece: Tetromino):
|
||||||
pw, ph = piece.width, piece.height
|
pw, ph = piece.width, piece.height
|
||||||
@@ -970,7 +987,8 @@ class Game:
|
|||||||
break
|
break
|
||||||
|
|
||||||
def drop_rows_above_for_clear(y: int):
|
def drop_rows_above_for_clear(y: int):
|
||||||
self.board[: y + 1] = self.board[:y]
|
self.board.pop(y)
|
||||||
|
# this also restores indices for future clears
|
||||||
self.board.insert(0, [None] * Game.BOARD_SIZE.width)
|
self.board.insert(0, [None] * Game.BOARD_SIZE.width)
|
||||||
|
|
||||||
if not self.clearing_rows:
|
if not self.clearing_rows:
|
||||||
@@ -979,7 +997,9 @@ class Game:
|
|||||||
self.score += self.CLEAR_POINTS
|
self.score += self.CLEAR_POINTS
|
||||||
self.clearing_rows.add(y)
|
self.clearing_rows.add(y)
|
||||||
elif not any(self.board[next(iter(self.clearing_rows))]):
|
elif not any(self.board[next(iter(self.clearing_rows))]):
|
||||||
for y in self.clearing_rows:
|
# Order matters here
|
||||||
|
for y in sorted(self.clearing_rows):
|
||||||
|
print(y)
|
||||||
drop_rows_above_for_clear(y)
|
drop_rows_above_for_clear(y)
|
||||||
self.clearing_rows = set()
|
self.clearing_rows = set()
|
||||||
self.clearing_frames = self.CLEAR_SPEED
|
self.clearing_frames = self.CLEAR_SPEED
|
||||||
@@ -994,10 +1014,93 @@ class Game:
|
|||||||
def draw_score(self):
|
def draw_score(self):
|
||||||
text = f"Score: {self.score:05}"
|
text = f"Score: {self.score:05}"
|
||||||
exts = self.TINY_FONT.compute_extents(text)
|
exts = self.TINY_FONT.compute_extents(text)
|
||||||
x = Game.CONTROLS_START_X + Game.SCORE_DISPLAY_BORDER
|
x = Game.CONTROLS_START_X + Game.CONTROL_DISPLAY_BORDER
|
||||||
y = self.screen.get_size()[1] - Game.SCORE_DISPLAY_BORDER - exts.height
|
y = (
|
||||||
|
self.screen.get_size()[1]
|
||||||
|
- Game.CONTROL_DISPLAY_BORDER
|
||||||
|
- exts.height
|
||||||
|
)
|
||||||
self.TINY_FONT.render_text(text, self.screen, x, y, "white")
|
self.TINY_FONT.render_text(text, self.screen, x, y, "white")
|
||||||
|
|
||||||
|
@cache
|
||||||
|
def get_scaled_piece(
|
||||||
|
self, piece: Tetromino, scale: float
|
||||||
|
) -> pygame.surface.Surface:
|
||||||
|
ow = piece.width * Game.CELL_SIZE.width
|
||||||
|
oh = piece.height * Game.CELL_SIZE.height
|
||||||
|
os_surf = pygame.surface.Surface((ow, oh), pygame.SRCALPHA)
|
||||||
|
for y, row in enumerate(piece.shape):
|
||||||
|
for x, cell in enumerate(row):
|
||||||
|
if cell:
|
||||||
|
self.draw_block(
|
||||||
|
x * Game.CELL_SIZE.width,
|
||||||
|
y * Game.CELL_SIZE.height,
|
||||||
|
piece.color,
|
||||||
|
os_surf,
|
||||||
|
)
|
||||||
|
sw, sh = int(ow * scale), int(oh * scale)
|
||||||
|
return pygame.transform.scale(os_surf, (sw, sh))
|
||||||
|
|
||||||
|
def draw_boxed_piece(
|
||||||
|
self,
|
||||||
|
text: str,
|
||||||
|
piece: Tetromino | None,
|
||||||
|
x: int,
|
||||||
|
y: int,
|
||||||
|
box_size: int,
|
||||||
|
) -> Size:
|
||||||
|
"""Return the overall bounding box."""
|
||||||
|
text_ext = Game.TINY_FONT.compute_extents(text)
|
||||||
|
Game.TINY_FONT.center_text(
|
||||||
|
text,
|
||||||
|
self.screen,
|
||||||
|
x + box_size // 2,
|
||||||
|
y + text_ext.height // 2,
|
||||||
|
"white",
|
||||||
|
)
|
||||||
|
pygame.draw.rect(
|
||||||
|
self.screen,
|
||||||
|
Game.PALETTE["gray"].norm,
|
||||||
|
(
|
||||||
|
x,
|
||||||
|
y + text_ext.height + Game.TINY_FONT.row_kern,
|
||||||
|
box_size,
|
||||||
|
box_size,
|
||||||
|
),
|
||||||
|
width=Game.NEXT_HELD_BOX_SIZE,
|
||||||
|
)
|
||||||
|
if piece:
|
||||||
|
piece_surf = self.get_scaled_piece(piece, 0.75)
|
||||||
|
pw, ph = piece_surf.get_size()
|
||||||
|
self.screen.blit(
|
||||||
|
piece_surf,
|
||||||
|
(
|
||||||
|
x + box_size // 2 - pw // 2,
|
||||||
|
y
|
||||||
|
+ text_ext.height
|
||||||
|
+ Game.TINY_FONT.row_kern
|
||||||
|
+ box_size // 2
|
||||||
|
- ph // 2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return Size(
|
||||||
|
max(box_size, text_ext.width),
|
||||||
|
text_ext.height + box_size + Game.TINY_FONT.row_kern,
|
||||||
|
)
|
||||||
|
|
||||||
|
def draw_held_and_next_piece(self):
|
||||||
|
width = int(Game.CONTROLS_WIDTH * 0.8)
|
||||||
|
y = Game.CONTROL_DISPLAY_BORDER
|
||||||
|
x = Game.CONTROLS_START_X + Game.CONTROLS_WIDTH // 2 - width // 2
|
||||||
|
exts = self.draw_boxed_piece("Hold", self.held_piece, x, y, width)
|
||||||
|
self.draw_boxed_piece(
|
||||||
|
"Next",
|
||||||
|
self.next_piece,
|
||||||
|
x,
|
||||||
|
y + exts.height + 2 * Game.CONTROL_DISPLAY_BORDER,
|
||||||
|
width,
|
||||||
|
)
|
||||||
|
|
||||||
def game_loop(self):
|
def game_loop(self):
|
||||||
self.doing_drop = False
|
self.doing_drop = False
|
||||||
|
|
||||||
@@ -1010,19 +1113,21 @@ class Game:
|
|||||||
self.pending_actions = []
|
self.pending_actions = []
|
||||||
|
|
||||||
if not self.current_block:
|
if not self.current_block:
|
||||||
self.generate_new_block()
|
self.swap_in_next_piece()
|
||||||
|
|
||||||
self.screen.fill("black")
|
self.screen.fill("black")
|
||||||
if not self.clearing_rows:
|
if not self.clearing_rows:
|
||||||
self.draw_current_block()
|
self.draw_current_block()
|
||||||
self.draw_board_content()
|
self.draw_board_content()
|
||||||
self.draw_board_border()
|
self.draw_board_border()
|
||||||
|
self.draw_held_and_next_piece()
|
||||||
|
|
||||||
self.draw_score()
|
self.draw_score()
|
||||||
|
|
||||||
def start_new_game(self):
|
def start_new_game(self):
|
||||||
self.clear_board()
|
self.clear_board()
|
||||||
self.generate_new_block()
|
self.next_piece = None
|
||||||
|
self.swap_in_next_piece(True)
|
||||||
self.held_piece = None
|
self.held_piece = None
|
||||||
self.subcell_move = 0
|
self.subcell_move = 0
|
||||||
self.subcell_drop = 0
|
self.subcell_drop = 0
|
||||||
@@ -1055,6 +1160,7 @@ class Game:
|
|||||||
self.draw_board_content()
|
self.draw_board_content()
|
||||||
self.draw_current_block()
|
self.draw_current_block()
|
||||||
self.draw_board_border()
|
self.draw_board_border()
|
||||||
|
self.draw_held_and_next_piece()
|
||||||
|
|
||||||
self.draw_score()
|
self.draw_score()
|
||||||
|
|
||||||
@@ -1094,8 +1200,14 @@ class Game:
|
|||||||
pygame.display.flip()
|
pygame.display.flip()
|
||||||
self.clock.tick(Game.FRAMERATE)
|
self.clock.tick(Game.FRAMERATE)
|
||||||
|
|
||||||
def generate_new_block(self):
|
def swap_in_next_piece(self, force: bool = False):
|
||||||
self.current_block = Game.random_tetromino()
|
self.cur_random_peice_seq = None
|
||||||
|
if not self.next_piece or force:
|
||||||
|
self.next_piece = self.random_tetromino()
|
||||||
|
self.current_block = self.random_tetromino()
|
||||||
|
else:
|
||||||
|
self.current_block = self.next_piece
|
||||||
|
self.next_piece = self.random_tetromino()
|
||||||
# top left corner
|
# top left corner
|
||||||
self.current_block_pos = Cell(
|
self.current_block_pos = Cell(
|
||||||
self.BOARD_SIZE.width // 2
|
self.BOARD_SIZE.width // 2
|
||||||
@@ -1108,7 +1220,9 @@ class Game:
|
|||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
pygame.init()
|
pygame.init()
|
||||||
self.screen = pygame.display.set_mode(Game.WINDOW_SIZE, pygame.SCALED)
|
self.screen = pygame.display.set_mode(
|
||||||
|
Game.WINDOW_SIZE, pygame.SCALED | pygame.HWACCEL
|
||||||
|
)
|
||||||
pygame.display.set_caption("Tetris")
|
pygame.display.set_caption("Tetris")
|
||||||
self.clock = pygame.time.Clock()
|
self.clock = pygame.time.Clock()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user