From c6c77e9091580e75cf99f28e83d712d3c081e920 Mon Sep 17 00:00:00 2001 From: Alexander Rosenberg Date: Fri, 1 May 2026 18:39:06 -0700 Subject: [PATCH] Update scoring and handling --- tetris.py | 122 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 93 insertions(+), 29 deletions(-) diff --git a/tetris.py b/tetris.py index 9194446..24ba840 100644 --- a/tetris.py +++ b/tetris.py @@ -300,14 +300,26 @@ class LazyVariable: class Game: - NORMAL_PLACE_POINTS = 10 - HARD_PLACE_POINTS = 20 - CLEAR_POINTS = 100 + # Points + DROP_POINTS = 1 + HARD_DROP_POINTS = 2 + POINTS_PER_LINE = [100, 300, 500, 800] + + LINES_PER_LEVEL = 10 + MOVE_SPEED = 20 # cells per second + MOVE_INITIAL_SLOWDOWN = 3.0 # cells + + DROP_LEVEL_SCALE = 0.025 + # These are *= 1 + (DROP_LEVEL_SCALE * (LEVEL - 1)) NORMAL_DROP_SPEED = 3 # cells per second FAST_DROP_SPEED = 30 # cells per second - MOVED_PROP_SPEED = 1 # cells per second - CLEAR_SPEED = 10 # frames per block + MOVED_DROP_SPEED = 1 # cells per second + + DROP_CLEAR_SCALE = 0.025 + # This is *= 1 - (DROP_CLEAR_SCALE * (LEVEL - 1)) + CLEAR_SPEED = 15 # frames per block + BOARD_SIZE = Size(10, 20) # Cell count CELL_SIZE = Size(30, 30) CELL_BORDER_SIZE = CELL_SIZE.width // 10 @@ -391,7 +403,7 @@ class Game: pygame.K_h, Action.Type.OPEN_HELP, False, - "Toggle the help menu.", + "Toggle the help menu/pause.", ), ) GAME_OVER_ACTION_MAP = Action.make_map( @@ -620,7 +632,8 @@ class Game: if cell and self.board[y + cy][x + cx]: return True except IndexError: - continue + # a cell was out of bounds of the board, this is a hit! + return True return False def rotate_current_piece(self, times: int): @@ -636,10 +649,24 @@ class Game: return def move_current_piece(self, dir: Direction): - dx = dir * Game.MOVE_SPEED * self.frame_time() - self.subcell_move += dx - move_cell = int(self.subcell_move) - self.subcell_move -= move_cell + if self.was_move_down != dir: + self.subcell_move = 0 + move_cell = dir + self.was_move_down = dir + self.inhibit_next_move = Game.MOVE_INITIAL_SLOWDOWN + else: + dx = dir * Game.MOVE_SPEED * self.frame_time() + self.subcell_move += dx + if self.inhibit_next_move > 0.0: + oi = self.inhibit_next_move + self.inhibit_next_move -= abs(self.subcell_move) + if self.inhibit_next_move <= 0.0: + self.inhibit_next_move = 0.0 + self.subcell_move -= oi + else: + self.subcell_move = 0 + move_cell = int(self.subcell_move) + self.subcell_move -= move_cell if move_cell: new_pos = Cell( self.current_block_pos.x + move_cell, self.current_block_pos.y @@ -658,10 +685,13 @@ class Game: self.did_user_move_or_spin = True def process_game_actions(self): - if not ( - {Action.Type.MOVE_LEFT, Action.Type.MOVE_RIGHT} - & set(self.pending_actions) - ): + move_set = {Action.Type.MOVE_LEFT, Action.Type.MOVE_RIGHT} & set( + self.pending_actions + ) + move_both = len(move_set) == 2 + if not move_set: + self.was_move_down = None + self.inhibit_next_move = 0.0 self.subcell_move = 0 if not ( {Action.Type.SOFT_DROP, Action.Type.HARD_DROP} @@ -680,19 +710,17 @@ class Game: self.soft_drop = True case Action.Type.HARD_DROP if not self.stop_drop: self.hard_drop = True - case Action.Type.MOVE_LEFT: + case Action.Type.MOVE_LEFT if not move_both: self.move_current_piece(Direction.LEFT) - case Action.Type.MOVE_RIGHT: + self.was_move_down = Direction.LEFT + case Action.Type.MOVE_RIGHT if not move_both: self.move_current_piece(Direction.RIGHT) + self.was_move_down = Direction.RIGHT case Action.Type.RESTART: self.start_new_game() self.pending_actions = [] def place_piece(self, x: int, y: int, piece: Tetromino): - if self.hard_drop: - self.score += Game.HARD_PLACE_POINTS - else: - self.score += Game.NORMAL_PLACE_POINTS for dy, row in enumerate(piece.shape): for dx, cell in enumerate(row): if cell: @@ -711,21 +739,27 @@ class Game: def advance_piece(self): speed = Game.NORMAL_DROP_SPEED if self.did_user_move_or_spin: - speed = Game.MOVED_PROP_SPEED + speed = Game.MOVED_DROP_SPEED elif self.soft_drop: speed = Game.FAST_DROP_SPEED + speed *= 1 + (Game.DROP_CLEAR_SCALE * (self.level - 1)) self.subcell_drop += speed * self.frame_time() move_cell = int(self.subcell_drop) self.subcell_drop -= move_cell if self.hard_drop: move_cell = self.cells_for_hard_drop() + if not move_cell: + return cp = self.current_block_pos if not self.intersects_board( cp.x, cp.y + move_cell, self.current_block ): + self.score += move_cell * Game.DROP_POINTS * self.level self.current_block_pos = Cell(cp.x, cp.y + move_cell) # don't place the piece if the user moved it elif not self.did_user_move_or_spin: + if self.hard_drop: + self.score += move_cell * Game.HARD_DROP_POINTS * self.level for dy in range(move_cell, -1, -1): if not self.intersects_board( cp.x, cp.y + dy, self.current_block @@ -744,6 +778,11 @@ class Game: else: return zip(range(bw // 2, -1, -1), range(bw // 2, bw)) + def calc_clear_speed(self): + return Game.CLEAR_SPEED * ( + 1 - (Game.DROP_CLEAR_SCALE * (self.level - 1)) + ) + def maybe_clear_rows(self): def do_single_clear(y: int): row = self.board[y] @@ -761,16 +800,25 @@ class Game: if not self.clearing_rows: for y, row in enumerate(self.board): if all(row): - self.score += self.CLEAR_POINTS self.clearing_rows.add(y) + if self.clearing_rows: + self.score += ( + Game.POINTS_PER_LINE[len(self.clearing_rows) - 1] + * self.level + ) else: - if self.clearing_frames == self.CLEAR_SPEED: + speed = self.calc_clear_speed() + if self.clearing_frames >= speed: if not any(self.board[next(iter(self.clearing_rows))]): + self.cleared_this_level += len(self.clearing_rows) + if self.cleared_this_level >= 10: + self.level += self.cleared_this_level // 10 + self.cleared_this_level %= 10 # Order matters here for y in sorted(self.clearing_rows): drop_rows_above_for_clear(y) self.clearing_rows = set() - self.clearing_frames = self.CLEAR_SPEED + self.clearing_frames = speed else: for y in self.clearing_rows: do_single_clear(y) @@ -780,8 +828,9 @@ class Game: def draw_score_and_instructions(self): text = ( - "Press \nfor help.\n" + "Press \nfor help\nor pause.\n" "\n" + f"Level: {self.level:02}\n" f"Score: {self.score:05}\n" f" HS: {self.high_score:05}" ) @@ -955,6 +1004,8 @@ class Game: self.process_and_draw_help_overlay() def start_new_game(self): + self.was_move_down = None + self.inhibit_next_move = 0.0 self.cur_random_peice_seq = None self.clear_board() self.next_piece = None @@ -965,8 +1016,11 @@ class Game: self.subcell_drop = 0 self.clearing_rows = set() # immediately clear the first block - self.clearing_frames = Game.CLEAR_SPEED self.score = 0 + self.level = 1 + self.cleared_this_level = 0 + # depends on level + self.clearing_frames = self.calc_clear_speed() self.held_piece = None self.game_over = False @@ -1063,8 +1117,8 @@ class Game: def move_piece_back_to_top(self): self.current_block_pos = Cell( self.BOARD_SIZE.width // 2 - - math.ceil(self.current_block.width / 2), - 0, + - math.ceil(self.current_block.bounding_box_width / 2), + 0 - self.current_block.y_adj, ) def swap_in_next_piece(self, force: bool = False): @@ -1525,6 +1579,16 @@ GLYPH_DATA = [ "** ", ], ), + Font.Glyph( + "/", + [ + " *", + " * ", + " * ", + " * ", + "* ", + ], + ), ]