diff --git a/tetrarcade.py b/tetrarcade.py index 1482692..208aa14 100644 --- a/tetrarcade.py +++ b/tetrarcade.py @@ -249,7 +249,7 @@ class TetrArcade(TetrisLogic, arcade.Window): def reload_piece(self, piece): piece_sprites = arcade.SpriteList() - for mino_position in piece.minoes_positions: + for mino_coord in piece.minoes_coords: mino_sprite_path = MINOES_SPRITES_PATHS[piece.MINOES_COLOR] mino_sprite = arcade.Sprite(mino_sprite_path) mino_sprite.alpha = NORMAL_ALPHA @@ -262,7 +262,7 @@ class TetrArcade(TetrisLogic, arcade.Window): def reload_next_pieces(self): self.next_pieces_sprites = arcade.SpriteList() for piece in self.next_pieces: - for mino_position in piece.minoes_positions: + for mino_coord in piece.minoes_coords: mino_sprite_path = MINOES_SPRITES_PATHS[piece.MINOES_COLOR] mino_sprite = arcade.Sprite(mino_sprite_path) mino_sprite.alpha = NORMAL_ALPHA @@ -287,12 +287,12 @@ class TetrArcade(TetrisLogic, arcade.Window): def update_piece(self, piece, piece_sprites): if piece: - for mino_sprite, mino_position in zip( - piece_sprites, piece.minoes_positions + for mino_sprite, mino_coord in zip( + piece_sprites, piece.minoes_coords ): - mino_position += piece.position - mino_sprite.left = self.matrix_sprite.left + mino_position.x*(mino_sprite.width-1) - mino_sprite.bottom = self.matrix_sprite.bottom + mino_position.y*(mino_sprite.height-1) + mino_coord += piece.coord + mino_sprite.left = self.matrix_sprite.left + mino_coord.x*(mino_sprite.width-1) + mino_sprite.bottom = self.matrix_sprite.bottom + mino_coord.y*(mino_sprite.height-1) def on_draw(self): arcade.start_render() @@ -322,12 +322,12 @@ class TetrArcade(TetrisLogic, arcade.Window): self.ghost_piece_sprites.draw() for n, piece in enumerate(self.next_pieces): - for mino_sprite, mino_position in zip( - self.next_pieces_sprites[4*n:4*(n+1)], piece.minoes_positions + for mino_sprite, mino_coord in zip( + self.next_pieces_sprites[4*n:4*(n+1)], piece.minoes_coords ): - mino_position += piece.position - mino_sprite.left = self.matrix_sprite.left + mino_position.x*(mino_sprite.width-1) - mino_sprite.bottom = self.matrix_sprite.bottom + mino_position.y*(mino_sprite.height-1) + mino_coord += piece.coord + mino_sprite.left = self.matrix_sprite.left + mino_coord.x*(mino_sprite.width-1) + mino_sprite.bottom = self.matrix_sprite.bottom + mino_coord.y*(mino_sprite.height-1) self.next_pieces_sprites.draw() arcade.render_text( diff --git a/tetrislogic.py b/tetrislogic.py index a7e38c4..8e9ccc6 100644 --- a/tetrislogic.py +++ b/tetrislogic.py @@ -23,14 +23,14 @@ class Coord: return Coord(self.x+other.x, self.y+other.y) -# Piece init position -MATRIX_PIECE_INIT_POSITION = Coord(4, NB_LINES) -NEXT_PIECES_POSITIONS = [ +# Piece init coord +MATRIX_PIECE_INIT_COORD = Coord(4, NB_LINES) +NEXT_PIECES_COORDS = [ Coord(NB_COLS+6, NB_LINES-4*n-3) for n in range(NB_NEXT_PIECES) ] -HELD_PIECE_POSITION = Coord(-7, NB_LINES-3) -HELD_I_POSITION = Coord(-7, NB_LINES-3) +HELD_PIECE_COORD = Coord(-7, NB_LINES-3) +HELD_I_COORD = Coord(-7, NB_LINES-3) class State: @@ -50,14 +50,14 @@ class Movement: class Rotation: - CLOCKWISE = -1 - COUNTERCLOCKWISE = 1 + CLOCKWISE = 1 + COUNTERCLOCKWISE = -1 class T_Spin: NO_T_SPIN = "" - MINI_T_SPIN = "MINI T-SPIN" + MINI_T_SPIN = "MINI\nT-SPIN" T_SPIN = "T-SPIN" @@ -82,23 +82,22 @@ class Tetromino: SRS = { Rotation.COUNTERCLOCKWISE: ( (Coord(0, 0), Coord(1, 0), Coord(1, 1), Coord(0, -2), Coord(1, -2)), - (Coord(0, 0), Coord(-1, 0), Coord(-1, -1), Coord(0, 2), Coord(-1, 2)), + (Coord(0, 0), Coord(1, 0), Coord(1, -1), Coord(0, 2), Coord(1, 2)), (Coord(0, 0), Coord(-1, 0), Coord(-1, 1), Coord(0, -2), Coord(-1, -2)), - (Coord(0, 0), Coord(1, 0), Coord(1, -1), Coord(0, 2), Coord(1, 2)) + (Coord(0, 0), Coord(-1, 0), Coord(-1, -1), Coord(0, 2), Coord(-1, 2)) ), Rotation.CLOCKWISE: ( (Coord(0, 0), Coord(-1, 0), Coord(-1, 1), Coord(0, -2), Coord(-1, -2)), - (Coord(0, 0), Coord(-1, 0), Coord(-1, -1), Coord(0, -2), Coord(-1, 2)), + (Coord(0, 0), Coord(1, 0), Coord(1, -1), Coord(0, 2), Coord(1, 2)), (Coord(0, 0), Coord(1, 0), Coord(1, 1), Coord(0, -2), Coord(1, -2)), - (Coord(0, 0), Coord(1, 0), Coord(1, -1), Coord(0, 2), Coord(1, 2)) + (Coord(0, 0), Coord(-1, 0), Coord(-1, -1), Coord(0, -2), Coord(-1, 2)) ) } - lock_delay = LOCK_DELAY - fall_delay = FALL_DELAY + CAN_SPIN = False def __init__(self): - self.position = NEXT_PIECES_POSITIONS[-1] - self.minoes_positions = self.MINOES_POSITIONS + self.coord = NEXT_PIECES_COORDS[-1] + self.minoes_coords = self.MINOES_COORDS self.orientation = 0 self.last_rotation_point_used = None self.hold_enabled = True @@ -114,7 +113,7 @@ class Tetromino: Rotation.COUNTERCLOCKWISE: (tuple(), tuple(), tuple(), tuple()), Rotation.CLOCKWISE: (tuple(), tuple(), tuple(), tuple()) } - MINOES_POSITIONS = (Coord(0, 0), Coord(1, 0), Coord(0, 1), Coord(1, 1)) + MINOES_COORDS = (Coord(0, 0), Coord(1, 0), Coord(0, 1), Coord(1, 1)) MINOES_COLOR = "yellow" def rotate(self, direction): @@ -126,48 +125,49 @@ class Tetromino: SRS = { Rotation.COUNTERCLOCKWISE: ( (Coord(0, -1), Coord(-1, -1), Coord(2, -1), Coord(-1, 1), Coord(2, -2)), - (Coord(1, 0), Coord(-1, 0), Coord(2, 0), Coord(-1, -1), Coord(2, 2)), + (Coord(-1, 0), Coord(1, 0), Coord(-2, 0), Coord(1, 1), Coord(-2, -2)), (Coord(0, 1), Coord(1, 1), Coord(-2, 1), Coord(1, -1), Coord(-2, 2)), - (Coord(-1, 0), Coord(1, 0), Coord(-2, 0), Coord(1, 1), Coord(-2, -2)) + (Coord(1, 0), Coord(-1, 0), Coord(2, 0), Coord(-1, -1), Coord(2, 2)) ), Rotation.CLOCKWISE: ( (Coord(1, 0), Coord(-1, 0), Coord(2, 0), Coord(-1, -1), Coord(2, 2)), - (Coord(0, -1), Coord(1, 1), Coord(-2, 1), Coord(1, -1), Coord(-2, 2)), + (Coord(0, -1), Coord(-1, -1), Coord(2, -1), Coord(-1, 1), Coord(2, -2)), (Coord(-1, 0), Coord(1, 0), Coord(-2, 0), Coord(1, 1), Coord(-2, -2)), - (Coord(0, -1), Coord(-1, -1), Coord(2, -1), Coord(-1, 1), Coord(2, -2)) + (Coord(0, -1), Coord(1, 1), Coord(-2, 1), Coord(1, -1), Coord(-2, 2)) ) } - MINOES_POSITIONS = (Coord(-1, 0), Coord(0, 0), Coord(1, 0), Coord(2, 0)) + MINOES_COORDS = (Coord(-1, 0), Coord(0, 0), Coord(1, 0), Coord(2, 0)) MINOES_COLOR = "cyan" class T(AbstractTetromino): - MINOES_POSITIONS = (Coord(-1, 0), Coord(0, 0), Coord(0, 1), Coord(1, 0)) + MINOES_COORDS = (Coord(-1, 0), Coord(0, 0), Coord(0, 1), Coord(1, 0)) MINOES_COLOR = "magenta" + CAN_SPIN = True class L(AbstractTetromino): - MINOES_POSITIONS = (Coord(-1, 0), Coord(0, 0), Coord(1, 0), Coord(1, 1)) + MINOES_COORDS = (Coord(-1, 0), Coord(0, 0), Coord(1, 0), Coord(1, 1)) MINOES_COLOR = "orange" class J(AbstractTetromino): - MINOES_POSITIONS = (Coord(-1, 1), Coord(-1, 0), Coord(0, 0), Coord(1, 0)) + MINOES_COORDS = (Coord(-1, 1), Coord(-1, 0), Coord(0, 0), Coord(1, 0)) MINOES_COLOR = "blue" class S(AbstractTetromino): - MINOES_POSITIONS = (Coord(-1, 0), Coord(0, 0), Coord(0, 1), Coord(1, 1)) + MINOES_COORDS = (Coord(-1, 0), Coord(0, 0), Coord(0, 1), Coord(1, 1)) MINOES_COLOR = "green" class Z(AbstractTetromino): - MINOES_POSITIONS = (Coord(-1, 1), Coord(0, 1), Coord(0, 0), Coord(1, 0)) + MINOES_COORDS = (Coord(-1, 1), Coord(0, 1), Coord(0, 0), Coord(1, 0)) MINOES_COLOR = "red" @@ -183,15 +183,6 @@ class Tetromino: class TetrisLogic(): - - T_SLOT = (Coord(-1, 1), Coord(1, 1), Coord(1, -1), Coord(-1, -1)) - SCORES = ( - {"name": "", T_Spin.NO_T_SPIN: 0, T_Spin.MINI_T_SPIN: 1, T_Spin.T_SPIN: 4}, - {"name": "SINGLE", T_Spin.NO_T_SPIN: 1, T_Spin.MINI_T_SPIN: 2, T_Spin.T_SPIN: 8}, - {"name": "DOUBLE", T_Spin.NO_T_SPIN: 3, T_Spin.MINI_T_SPIN: 12}, - {"name": "TRIPLE", T_Spin.NO_T_SPIN: 5, T_Spin.T_SPIN: 16}, - {"name": "TETRIS", T_Spin.NO_T_SPIN: 8} - ) scheduler = AbstractScheduler() def __init__(self): @@ -237,20 +228,20 @@ class TetrisLogic(): if self.level > 15: self.lock_delay = 0.5 * pow(0.9, self.level-15) self.show_text("LEVEL\n{:n}".format(self.level)) - self.scheduler.start(self.fall, self.fall_delay) + self.scheduler.restart(self.fall, self.fall_delay) self.new_current_piece() def new_current_piece(self): self.current_piece = self.next_pieces.pop(0) - self.current_piece.position = MATRIX_PIECE_INIT_POSITION + self.current_piece.coord = MATRIX_PIECE_INIT_COORD self.ghost_piece = self.current_piece.ghost() self.move_ghost() self.next_pieces.append(Tetromino()) - for piece, position in zip (self.next_pieces, NEXT_PIECES_POSITIONS): - piece.position = position + for piece, coord in zip (self.next_pieces, NEXT_PIECES_COORDS): + piece.coord = coord if not self.can_move( - self.current_piece.position, - self.current_piece.minoes_positions + self.current_piece.coord, + self.current_piece.minoes_coords ): self.game_over() @@ -267,13 +258,13 @@ class TetrisLogic(): self.rotate(Rotation.CLOCKWISE) def move_ghost(self): - self.ghost_piece.position = self.current_piece.position - self.ghost_piece.minoes_positions = self.current_piece.minoes_positions + self.ghost_piece.coord = self.current_piece.coord + self.ghost_piece.minoes_coords = self.current_piece.minoes_coords while self.can_move( - self.ghost_piece.position + Movement.DOWN, - self.ghost_piece.minoes_positions + self.ghost_piece.coord + Movement.DOWN, + self.ghost_piece.minoes_coords ): - self.ghost_piece.position += Movement.DOWN + self.ghost_piece.coord += Movement.DOWN def soft_drop(self): if self.move(Movement.DOWN): @@ -291,12 +282,13 @@ class TetrisLogic(): self.move(Movement.DOWN) def move(self, movement, prelock=True): - potential_position = self.current_piece.position + movement - if self.can_move(potential_position, self.current_piece.minoes_positions): + potential_coord = self.current_piece.coord + movement + if self.can_move(potential_coord, self.current_piece.minoes_coords): if self.current_piece.prelocked: self.scheduler.restart(self.lock, self.lock_delay) - self.current_piece.position = potential_position - self.current_piece.last_rotation_point_used = None + self.current_piece.coord = potential_coord + if not movement == Movement.DOWN: + self.current_piece.last_rotation_point_used = None self.move_ghost() return True else: @@ -309,20 +301,20 @@ class TetrisLogic(): return False def rotate(self, direction): - rotated_minoes_positions = tuple( - Coord(-direction*mino_position.y, direction*mino_position.x) - for mino_position in self.current_piece.minoes_positions + rotated_minoes_coords = tuple( + Coord(direction*mino_coord.y, -direction*mino_coord.x) + for mino_coord in self.current_piece.minoes_coords ) for rotation_point, liberty_degree in enumerate( self.current_piece.SRS[direction][self.current_piece.orientation], start = 1 ): - potential_position = self.current_piece.position + liberty_degree - if self.can_move(potential_position, rotated_minoes_positions): + potential_coord = self.current_piece.coord + liberty_degree + if self.can_move(potential_coord, rotated_minoes_coords): if self.current_piece.prelocked: self.scheduler.restart(self.lock, self.lock_delay) - self.current_piece.position = potential_position - self.current_piece.minoes_positions = rotated_minoes_positions + self.current_piece.coord = potential_coord + self.current_piece.minoes_coords = rotated_minoes_coords self.current_piece.orientation = ( (self.current_piece.orientation + direction) % 4 ) @@ -332,46 +324,41 @@ class TetrisLogic(): else: return False - def can_move(self, potential_position, minoes_positions): - return all( - self.cell_is_free(potential_position+mino_position) - for mino_position in minoes_positions - ) - - def cell_is_free(self, position): - return ( - 0 <= position.x < NB_COLS - and 0 <= position.y - and not self.matrix[position.y][position.x] - ) - - def add_to_score(self, ds): - self.score += ds - if self.score > self.high_score: - self.high_score = self.score + SCORES = ( + {"name": "", T_Spin.NO_T_SPIN: 0, T_Spin.MINI_T_SPIN: 1, T_Spin.T_SPIN: 4}, + {"name": "SINGLE", T_Spin.NO_T_SPIN: 1, T_Spin.MINI_T_SPIN: 2, T_Spin.T_SPIN: 8}, + {"name": "DOUBLE", T_Spin.NO_T_SPIN: 3, T_Spin.MINI_T_SPIN: 12}, + {"name": "TRIPLE", T_Spin.NO_T_SPIN: 5, T_Spin.T_SPIN: 16}, + {"name": "TETRIS", T_Spin.NO_T_SPIN: 8} + ) def lock(self): + # Piece unlocked if self.move(Movement.DOWN): return + # Start lock self.current_piece.prelocked = False self.scheduler.stop(self.lock) if self.pressed_actions: self.stop_autorepeat() self.scheduler.start(self.repeat_action, AUTOREPEAT_DELAY) + # Game over if all( - (mino_position + self.current_piece.position).y >= NB_LINES - for mino_position in self.current_piece.minoes_positions + (mino_coord + self.current_piece.coord).y >= NB_LINES + for mino_coord in self.current_piece.minoes_coords ): self.game_over() return - for mino_position in self.current_piece.minoes_positions: - position = mino_position + self.current_piece.position - if position.y <= NB_LINES+3: - self.matrix[position.y][position.x] = self.current_piece.MINOES_COLOR + # Insert tetromino in the matrix + for mino_coord in self.current_piece.minoes_coords: + coord = mino_coord + self.current_piece.coord + if coord.y <= NB_LINES+3: + self.matrix[coord.y][coord.x] = self.current_piece.MINOES_COLOR + # Clear complete lines nb_lines_cleared = 0 for y, line in reversed(list(enumerate(self.matrix))): if all(mino for mino in line): @@ -381,19 +368,18 @@ class TetrisLogic(): if nb_lines_cleared: self.nb_lines += nb_lines_cleared + # T-Spin if ( - self.current_piece.__class__ == Tetromino.T + self.current_piece.CAN_SPIN and self.current_piece.last_rotation_point_used is not None ): - position = self.current_piece.position - orientation = self.current_piece.orientation - nb_orientations = len(self.current_piece.SRS[Rotation.CLOCKWISE]) - a = not self.cell_is_free(position+self.T_SLOT[orientation]) - b = not self.cell_is_free(position+self.T_SLOT[(orientation-1)%nb_orientations]) - c = not self.cell_is_free(position+self.T_SLOT[(orientation-3)%nb_orientations]) - d = not self.cell_is_free(position+self.T_SLOT[(orientation-2)%nb_orientations]) - - if self.current_piece.last_rotation_point_used == 5 or (a and b and (c or d)): + a = self.is_t_slot(0) + b = self.is_t_slot(1) + c = self.is_t_slot(3) + d = self.is_t_slot(2) + if self.current_piece.last_rotation_point_used == 5 or ( + a and b and (c or d) + ): t_spin = T_Spin.T_SPIN elif c and d and (a or b): t_spin = T_Spin.MINI_T_SPIN @@ -402,6 +388,7 @@ class TetrisLogic(): else: t_spin = T_Spin.NO_T_SPIN + # Scoring lock_strings = [] lock_score = 0 @@ -432,11 +419,41 @@ class TetrisLogic(): self.add_to_score(lock_score) if self.goal <= 0: - self.scheduler.stop(self.fall) self.new_level() else: self.new_current_piece() + def can_move(self, potential_coord, minoes_coords): + return all( + self.cell_is_free(potential_coord+mino_coord) + for mino_coord in minoes_coords + ) + + def cell_is_free(self, coord): + return ( + 0 <= coord.x < NB_COLS + and 0 <= coord.y + and not self.matrix[coord.y][coord.x] + ) + + T_SLOT_COORDS = ( + Coord(-1, 1), + Coord( 1, 1), + Coord(-1, 1), + Coord(-1, -1) + ) + + def is_t_slot(self, n): + t_slot_coord = self.current_piece.coord + self.T_SLOT_COORDS[ + (self.current_piece.orientation + n) % 4 + ] + return not self.cell_is_free(t_slot_coord) + + def add_to_score(self, delta_score): + self.score += delta_score + if self.score > self.high_score: + self.high_score = self.score + def swap(self): if self.current_piece.hold_enabled: self.current_piece.hold_enabled = False @@ -444,12 +461,12 @@ class TetrisLogic(): self.scheduler.stop(self.lock) self.current_piece, self.held_piece = self.held_piece, self.current_piece if self.held_piece.__class__ == Tetromino.I: - self.held_piece.position = HELD_I_POSITION + self.held_piece.coord = HELD_I_COORD else: - self.held_piece.position = HELD_PIECE_POSITION - self.held_piece.minoes_positions = self.held_piece.MINOES_POSITIONS + self.held_piece.coord = HELD_PIECE_COORD + self.held_piece.minoes_coords = self.held_piece.MINOES_COORDS if self.current_piece: - self.current_piece.position = MATRIX_PIECE_INIT_POSITION + self.current_piece.coord = MATRIX_PIECE_INIT_COORD self.ghost_piece = self.current_piece.ghost() self.move_ghost() else: