From 0c33da6704691f511df55f5b5737d269bc957547 Mon Sep 17 00:00:00 2001 From: adrienmalin <41926238+adrienmalin@users.noreply.github.com> Date: Sun, 29 Sep 2019 23:12:02 +0200 Subject: [PATCH] big rewrite --- tetrarcade.py | 304 ++++++++++---------- tetrislogic.py | 572 ------------------------------------- tetrislogic/__init__.py | 3 + tetrislogic/consts.py | 24 ++ tetrislogic/tetrislogic.py | 418 +++++++++++++++++++++++++++ tetrislogic/tetromino.py | 124 ++++++++ tetrislogic/utils.py | 34 +++ 7 files changed, 754 insertions(+), 725 deletions(-) delete mode 100644 tetrislogic.py create mode 100644 tetrislogic/__init__.py create mode 100644 tetrislogic/consts.py create mode 100644 tetrislogic/tetrislogic.py create mode 100644 tetrislogic/tetromino.py create mode 100644 tetrislogic/utils.py diff --git a/tetrarcade.py b/tetrarcade.py index 8e8129c..61e29f0 100644 --- a/tetrarcade.py +++ b/tetrarcade.py @@ -23,7 +23,32 @@ WINDOW_HEIGHT = 600 WINDOW_TITLE = "TETRARCADE" # Delays (seconds) -HIGHLIGHT_TEXT_DISPLAY_DELAY = 0.8 +HIGHLIGHT_TEXT_DISPLAY_DELAY = 0.7 + +# Transparency (0=invisible, 255=opaque) +NORMAL_ALPHA = 200 +PRELOCKED_ALPHA = 127 +GHOST_ALPHA = 50 +MATRIX_SPRITE_ALPHA = 100 + +# Paths +WINDOW_BG_PATH = "images/bg.jpg" +MATRIX_SPRITE_PATH = "images/matrix.png" +MINOES_SPRITES_PATHS = { + "orange": "images/orange_mino.png", + "blue": "images/blue_mino.png", + "yellow": "images/yellow_mino.png", + "cyan": "images/cyan_mino.png", + "green": "images/green_mino.png", + "red": "images/red_mino.png", + "magenta": "images/magenta_mino.png" +} +if sys.platform == "win32": + USER_PROFILE_DIR = os.environ.get("appdata", os.path.expanduser("~\Appdata\Roaming")) +else: + USER_PROFILE_DIR = os.environ.get("XDG_DATA_HOME", os.path.expanduser("~/.local/share")) +USER_PROFILE_DIR = os.path.join(USER_PROFILE_DIR, "TetrArcade") +HIGH_SCORE_PATH = os.path.join(USER_PROFILE_DIR, ".high_score") # Text TEXT_COLOR = arcade.color.BUBBLES @@ -33,21 +58,25 @@ TEXT_MARGIN = 40 FONT_SIZE = 16 TEXT_HEIGHT = 20.8 HIGHLIGHT_TEXT_FONT_SIZE = 20 -TITLE_AND_CONTROL_TEXT = """TETRARCADE + +CONTROL_TEXT = """ + CONTROLS -MOVE LEFT ← -MOVE RIGHT → -SOFT DROP ↓ -HARD DROP SPACE -ROTATE CLOCKWISE ↑ -ROTATE COUNTER Z -HOLD C -PAUSE ESC + +MOVE LEFT ← +MOVE RIGHT → +SOFT DROP ↓ +HARD DROP SPACE +ROTATE CLOCKWISE ↑ +ROTATE COUNTER Z +HOLD C +PAUSE ESC + """ -START_TEXT = TITLE_AND_CONTROL_TEXT + "PRESS [ENTER] TO START" -PAUSE_TEXT = TITLE_AND_CONTROL_TEXT + "PRESS [ESC] TO RESUME" +START_TEXT = "TETRARCADE" + CONTROL_TEXT + "PRESS [ENTER] TO START" +PAUSE_TEXT = "PAUSE" + CONTROL_TEXT + "PRESS [ESC] TO RESUME" STATS_TEXT = """SCORE HIGH SCORE @@ -68,65 +97,6 @@ PRESS TO PLAY AGAIN""" -# Paths -WINDOW_BG = "images/bg.jpg" -MATRIX_SPRITE_PATH = "images/matrix.png" -MINOES_SPRITES_PATHS = { - "orange": "images/orange_mino.png", - "blue": "images/blue_mino.png", - "yellow": "images/yellow_mino.png", - "cyan": "images/cyan_mino.png", - "green": "images/green_mino.png", - "red": "images/red_mino.png", - "magenta": "images/magenta_mino.png" -} -if sys.platform == "win32": - USER_PROFILE_DIR = os.environ.get("appdata", os.path.expanduser("~\Appdata\Roaming")) -else: - USER_PROFILE_DIR = os.environ.get("XDG_DATA_HOME", os.path.expanduser("~/.local/share")) -USER_PROFILE_DIR = os.path.join(USER_PROFILE_DIR, "TetrArcade") -HIGH_SCORE_PATH = os.path.join(USER_PROFILE_DIR, ".high_score") - -# Transparency (0=invisible, 255=opaque) -NORMAL_ALPHA = 200 -PRELOCKED_ALPHA = 127 -GHOST_ALPHA = 50 -MATRIX_SPRITE_ALPHA = 100 - - -class TetrominoSprites(arcade.SpriteList): - - def __init__(self, piece=None, matrix_sprite=None, alpha=NORMAL_ALPHA): - super().__init__() - self.piece = piece - self.alpha = alpha - self.matrix_sprite = matrix_sprite - if piece: - 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 = alpha - self.append(mino_sprite) - - def update(self): - if self.piece: - alpha = ( - PRELOCKED_ALPHA - if self.piece.prelocked - else self.alpha - ) - for mino_sprite, mino_coord in zip( - self, self.piece.minoes_coords - ): - mino_coord += self.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) - mino_sprite.alpha = alpha - - def draw(self): - self.update() - super().draw() - class TetrArcade(TetrisLogic, arcade.Window): @@ -153,9 +123,9 @@ class TetrArcade(TetrisLogic, arcade.Window): arcade.key.NUM_1: self.rotate_clockwise, arcade.key.NUM_5: self.rotate_clockwise, arcade.key.NUM_9: self.rotate_clockwise, - arcade.key.Z: self.rotate_counterclockwise, - arcade.key.NUM_3: self.rotate_counterclockwise, - arcade.key.NUM_7: self.rotate_counterclockwise, + arcade.key.Z: self.rotate_counter, + arcade.key.NUM_3: self.rotate_counter, + arcade.key.NUM_7: self.rotate_counter, arcade.key.C: self.swap, arcade.key.MOD_SHIFT: self.swap, arcade.key.NUM_0: self.swap, @@ -172,6 +142,28 @@ class TetrArcade(TetrisLogic, arcade.Window): } super().__init__() + + center_x = WINDOW_WIDTH / 2 + center_y = WINDOW_HEIGHT / 2 + self.bg_sprite = arcade.Sprite(WINDOW_BG_PATH) + self.bg_sprite.center_x = center_x + self.bg_sprite.center_y = center_y + self.matrix.sprite = arcade.Sprite(MATRIX_SPRITE_PATH) + self.matrix.sprite.alpha = MATRIX_SPRITE_ALPHA + self.matrix.sprite.center_x = center_x + self.matrix.sprite.center_y = center_y + self.matrix.sprite.left = int(self.matrix.sprite.left) + self.matrix.sprite.top = int(self.matrix.sprite.top) + self.matrix.minoes_sprites = arcade.SpriteList() + self.tetrominos_sprites = arcade.SpriteList() + self.stats_text = arcade.create_text( + text = STATS_TEXT, + color = TEXT_COLOR, + font_size = FONT_SIZE, + font_name = FONT_NAME, + anchor_x = 'right' + ) + arcade.Window.__init__( self, width = WINDOW_WIDTH, @@ -180,81 +172,67 @@ class TetrArcade(TetrisLogic, arcade.Window): resizable = False, antialiasing = False ) - - center_x = self.width / 2 - center_y = self.height / 2 - self.bg_sprite = arcade.Sprite(WINDOW_BG) - self.bg_sprite.center_x = center_x - self.bg_sprite.center_y = center_y - self.matrix_sprite = arcade.Sprite(MATRIX_SPRITE_PATH) - self.matrix_sprite.alpha = MATRIX_SPRITE_ALPHA - self.matrix_sprite.center_x = center_x - self.matrix_sprite.center_y = center_y - self.matrix_sprite.left = int(self.matrix_sprite.left) - self.matrix_sprite.top = int(self.matrix_sprite.top) - self.matrix_minoes_sprites = [] - self.held_piece_sprites = TetrominoSprites() - self.current_piece_sprites = TetrominoSprites() - self.ghost_piece_sprites = TetrominoSprites() - self.next_pieces_sprites = [] - self.general_text = arcade.create_text( - text = STATS_TEXT, - color = TEXT_COLOR, - font_size = FONT_SIZE, - font_name = FONT_NAME, - anchor_x = 'right' - ) + self.new_game() + self.on_draw() def new_game(self): self.highlight_texts = [] + self.matrix.minoes_sprites = arcade.SpriteList() super().new_game() - self.on_draw() - def new_matrix(self): - self.matrix_minoes_sprites = [] - super().new_matrix() + def load_next(self): + super().load_next() + for tetromino in self.next: + self.load_minoes_sprite(tetromino) + def new_current(self): + super().new_current() + self.update_sprites_position(self.current) + self.load_minoes_sprite(self.next[-1]) + self.load_minoes_sprite(self.ghost, GHOST_ALPHA) + for tetromino in [self.current, self.ghost] + self.next: + self.update_sprites_position(tetromino) + self.reload_all_tetrominoes_sprites() - def new_next_pieces(self): - super().new_next_pieces() - self.next_pieces_sprites = [ - TetrominoSprites(next_piece, self.matrix_sprite) - for next_piece in self.next_pieces - ] + def move(self, movement, prelock=False): + if super().move(movement, prelock): + for tetromino in (self.current, self.ghost): + self.update_sprites_position(tetromino) + return True + else: + return False - def new_current_piece(self): - super().new_current_piece() - self.current_piece_sprites = self.next_pieces_sprites.pop(0) - self.next_pieces_sprites.append(TetrominoSprites(self.next_pieces[-1], self.matrix_sprite)) - self.ghost_piece_sprites = TetrominoSprites(self.ghost_piece, self.matrix_sprite, GHOST_ALPHA) + def rotate(self, rotation): + if super().rotate(rotation): + for tetromino in (self.current, self.ghost): + self.update_sprites_position(tetromino) + return True + else: + return False - def enter_the_matrix(self): - super().enter_the_matrix() - self.current_piece_sprites.update() - for mino_coord, mino_sprite in zip( - self.current_piece.minoes_coords, - self.current_piece_sprites - ): - mino_coord += self.current_piece.coord - self.matrix_minoes_sprites[ - mino_coord.y - ].append(mino_sprite) + def swap(self): + super().swap() + self.load_minoes_sprite(self.ghost, GHOST_ALPHA) + for tetromino in [self.held, self.current, self.ghost]: + if tetromino: + self.update_sprites_position(tetromino) + self.reload_all_tetrominoes_sprites() - def append_new_line_to_matrix(self): - super().append_new_line_to_matrix() - self.matrix_minoes_sprites.append(arcade.SpriteList()) + def lock(self): + self.update_sprites_position(self.current) + super().lock() + self.matrix.minoes_sprites = arcade.SpriteList() + for line in self.matrix: + for mino in line: + if mino: + self.matrix.minoes_sprites.append(mino.sprite) def remove_line_of_matrix(self, line): super().remove_line_of_matrix(line) - self.matrix_minoes_sprites.pop(line) - for line_sprites in self.matrix_minoes_sprites[line:NB_LINES+2]: - for mino_sprite in line_sprites: - mino_sprite.center_y -= mino_sprite.height-1 - - def swap(self): - self.current_piece_sprites, self.held_piece_sprites = self.held_piece_sprites, self.current_piece_sprites - super().swap() - self.ghost_piece_sprites = TetrominoSprites(self.ghost_piece, self.matrix_sprite, GHOST_ALPHA) + for line in self.matrix[line:NB_LINES+2]: + for mino in line: + if mino: + mino.sprite.center_y -= mino.sprite.height-1 def game_over(self): super().game_over() @@ -291,20 +269,14 @@ class TetrArcade(TetrisLogic, arcade.Window): self.bg_sprite.draw() if self.state in (State.PLAYING, State.OVER): - self.matrix_sprite.draw() - for line in self.matrix_minoes_sprites: - line.draw() - - self.held_piece_sprites.draw() - self.current_piece_sprites.draw() - self.ghost_piece_sprites.draw() - for next_piece_sprites in self.next_pieces_sprites: - next_piece_sprites.draw() + self.matrix.sprite.draw() + self.matrix.minoes_sprites.draw() + self.tetrominoes_sprites.draw() arcade.render_text( - self.general_text, - self.matrix_sprite.left - TEXT_MARGIN, - self.matrix_sprite.bottom + self.stats_text, + self.matrix.sprite.left - TEXT_MARGIN, + self.matrix.sprite.bottom ) t = time.localtime(self.time) for y, text in enumerate( @@ -313,7 +285,7 @@ class TetrArcade(TetrisLogic, arcade.Window): "{:02d}:{:02d}:{:02d}".format( t.tm_hour-1, t.tm_min, t.tm_sec ), - "{:n}".format(self.nb_lines), + "{:n}".format(self.nb_lines_cleared), "{:n}".format(self.goal), "{:n}".format(self.level), "{:n}".format(self.high_score), @@ -322,8 +294,8 @@ class TetrArcade(TetrisLogic, arcade.Window): ): arcade.draw_text( text = text, - start_x = self.matrix_sprite.left - TEXT_MARGIN, - start_y = self.matrix_sprite.bottom + 2*y*TEXT_HEIGHT, + start_x = self.matrix.sprite.left - TEXT_MARGIN, + start_y = self.matrix.sprite.bottom + 2*y*TEXT_HEIGHT, color = TEXT_COLOR, font_size = FONT_SIZE, align = 'right', @@ -340,8 +312,8 @@ class TetrArcade(TetrisLogic, arcade.Window): if highlight_text: arcade.draw_text( text = highlight_text, - start_x = self.matrix_sprite.center_x, - start_y = self.matrix_sprite.center_y, + start_x = self.matrix.sprite.center_x, + start_y = self.matrix.sprite.center_y, color = HIGHLIGHT_TEXT_COLOR, font_size = HIGHLIGHT_TEXT_FONT_SIZE, align = 'center', @@ -350,6 +322,32 @@ class TetrArcade(TetrisLogic, arcade.Window): anchor_y = 'center' ) + def load_minoes_sprite(self, tetromino, alpha=NORMAL_ALPHA): + path = MINOES_SPRITES_PATHS[tetromino.MINOES_COLOR] + tetromino.alpha = alpha + for mino in tetromino: + mino.sprite = arcade.Sprite(path) + mino.sprite.alpha = alpha + + def reload_all_tetrominoes_sprites(self): + self.tetrominoes_sprites = arcade.SpriteList() + for tetromino in [self.held, self.current, self.ghost] + self.next: + if tetromino: + for mino in tetromino: + self.tetrominoes_sprites.append(mino.sprite) + + def update_sprites_position(self, tetromino): + alpha = ( + PRELOCKED_ALPHA + if tetromino.prelocked + else tetromino.alpha + ) + for mino in tetromino: + coord = mino.coord + tetromino.coord + mino.sprite.left = self.matrix.sprite.left + coord.x*(mino.sprite.width-1) + mino.sprite.bottom = self.matrix.sprite.bottom + coord.y*(mino.sprite.height-1) + mino.sprite.alpha = alpha + def load_high_score(self): try: with open(HIGH_SCORE_PATH, "r") as f: diff --git a/tetrislogic.py b/tetrislogic.py deleted file mode 100644 index 132ebec..0000000 --- a/tetrislogic.py +++ /dev/null @@ -1,572 +0,0 @@ -# -*- coding: utf-8 -*- -import random - -# Matrix -NB_LINES = 20 -NB_COLS = 10 -NB_NEXT_PIECES = 5 - -# Delays (seconds) -LOCK_DELAY = 0.5 -FALL_DELAY = 1 -AUTOREPEAT_DELAY = 0.200 # Official : 0.300 -AUTOREPEAT_PERIOD = 0.010 # Official : 0.010 - -LINES_CLEAR_NAME = "LINES_CLEAR_NAME" - - -class Coord: - - def __init__(self, x, y): - self.x = x - self.y = y - - def __add__(self, other): - return Coord(self.x+other.x, self.y+other.y) - - -# 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_COORD = Coord(-7, NB_LINES-3) -HELD_I_COORD = Coord(-7, NB_LINES-3) - - -class State: - - STARTING = "starting" - PLAYING = "playing" - PAUSED = "paused" - OVER = "over" - - -class Movement: - - LEFT = Coord(-1, 0) - RIGHT = Coord( 1, 0) - DOWN = Coord( 0, -1) - - -class Rotation: - - CLOCKWISE = 1 - COUNTERCLOCKWISE = -1 - - -class T_Spin: - - NONE = "" - MINI = "MINI\nT-SPIN" - T_SPIN = "T-SPIN" - - -class Tetromino: - - random_bag = [] - - - class MetaTetromino(type): - - def __init__(cls, name, bases, dico): - super().__init__(name, bases, dico) - cls.classes.append(cls) - - - class AbstractTetromino: - - # Super rotation system - 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)) - ), - 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)) - ) - } - CAN_SPIN = False - classes = [] - - def __init__(self): - 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 - self.prelocked = False - - def ghost(self): - return self.__class__() - - def minoes_absolute_coord(self): - return [ - mino_coord + self.coord - for mino_coord in self.minoes_coords - ] - - - class O(AbstractTetromino, metaclass=MetaTetromino): - - SRS = { - Rotation.COUNTERCLOCKWISE: (tuple(), tuple(), tuple(), tuple()), - Rotation.CLOCKWISE: (tuple(), tuple(), tuple(), tuple()) - } - MINOES_COORDS = (Coord(0, 0), Coord(1, 0), Coord(0, 1), Coord(1, 1)) - MINOES_COLOR = "yellow" - - def rotate(self, direction): - return False - - - class I(AbstractTetromino, metaclass=MetaTetromino): - - 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(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)) - ), - 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(-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)) - ) - } - MINOES_COORDS = (Coord(-1, 0), Coord(0, 0), Coord(1, 0), Coord(2, 0)) - MINOES_COLOR = "cyan" - - - class T(AbstractTetromino, metaclass=MetaTetromino): - - MINOES_COORDS = (Coord(-1, 0), Coord(0, 0), Coord(0, 1), Coord(1, 0)) - MINOES_COLOR = "magenta" - CAN_SPIN = True - - - class L(AbstractTetromino, metaclass=MetaTetromino): - - MINOES_COORDS = (Coord(-1, 0), Coord(0, 0), Coord(1, 0), Coord(1, 1)) - MINOES_COLOR = "orange" - - - class J(AbstractTetromino, metaclass=MetaTetromino): - - MINOES_COORDS = (Coord(-1, 1), Coord(-1, 0), Coord(0, 0), Coord(1, 0)) - MINOES_COLOR = "blue" - - - class S(AbstractTetromino, metaclass=MetaTetromino): - - MINOES_COORDS = (Coord(-1, 0), Coord(0, 0), Coord(0, 1), Coord(1, 1)) - MINOES_COLOR = "green" - - - class Z(AbstractTetromino, metaclass=MetaTetromino): - - MINOES_COORDS = (Coord(-1, 1), Coord(0, 1), Coord(0, 0), Coord(1, 0)) - MINOES_COLOR = "red" - - - def __new__(cls): - if not cls.random_bag: - cls.random_bag = list(Tetromino.AbstractTetromino.classes) - random.shuffle(cls.random_bag) - return cls.random_bag.pop()() - - -class TetrisLogic(): - - def __init__(self): - self.load_high_score() - self.state = State.STARTING - self.matrix = [] - self.next_pieces = [] - self.current_piece = None - self.held_piece = None - self.time = 0 - self.autorepeatable_actions = (self.move_left, self.move_right, self.soft_drop) - self.pressed_actions = [] - self._score = 0 - - @property - def score(self): - return self._score - - @score.setter - def score(self, new_score): - self._score = new_score - if self._score > self.high_score: - self.high_score = self._score - - def new_game(self): - self.level = 0 - self.score = 0 - self.nb_lines = 0 - self.goal = 0 - self.time = 0 - - self.pressed_actions = [] - self.auto_repeat = False - - self.lock_delay = LOCK_DELAY - self.fall_delay = FALL_DELAY - - self.new_matrix() - self.new_next_pieces() - self.current_piece = None - self.held_piece = None - self.state = State.PLAYING - self.start(self.update_time, 1) - - self.new_level() - - def new_next_pieces(self): - self.next_pieces = [Tetromino() for i in range(NB_NEXT_PIECES)] - - def new_matrix(self): - self.matrix = [] - for y in range(NB_LINES+3): - self.append_new_line_to_matrix() - - def new_level(self): - self.level += 1 - self.goal += 5 * self.level - if self.level <= 20: - self.fall_delay = pow(0.8 - ((self.level-1)*0.007), self.level-1) - 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.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.coord = MATRIX_PIECE_INIT_COORD - self.ghost_piece = self.current_piece.ghost() - self.move_ghost() - self.next_pieces.append(Tetromino()) - for piece, coord in zip (self.next_pieces, NEXT_PIECES_COORDS): - piece.coord = coord - - if not self.can_move( - self.current_piece.coord, - self.current_piece.minoes_coords - ): - self.game_over() - - def move_left(self): - self.move(Movement.LEFT) - - def move_right(self): - self.move(Movement.RIGHT) - - def rotate_counterclockwise(self): - self.rotate(Rotation.COUNTERCLOCKWISE) - - def rotate_clockwise(self): - self.rotate(Rotation.CLOCKWISE) - - def move_ghost(self): - 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.coord + Movement.DOWN, - self.ghost_piece.minoes_coords - ): - self.ghost_piece.coord += Movement.DOWN - - def soft_drop(self): - if self.move(Movement.DOWN): - self.score += 1 - return True - else: - return False - - def hard_drop(self): - while self.move(Movement.DOWN, prelock=False): - self.score += 2 - self.lock() - - def fall(self): - self.move(Movement.DOWN) - - def move(self, movement, prelock=True): - potential_coord = self.current_piece.coord + movement - if self.can_move(potential_coord, self.current_piece.minoes_coords): - if self.current_piece.prelocked: - self.restart(self.lock, self.lock_delay) - 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: - if ( - prelock and not self.current_piece.prelocked - and movement == Movement.DOWN - ): - self.current_piece.prelocked = True - self.start(self.lock, self.lock_delay) - return False - - def rotate(self, direction): - 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_coord = self.current_piece.coord + liberty_degree - if self.can_move(potential_coord, rotated_minoes_coords): - if self.current_piece.prelocked: - self.restart(self.lock, self.lock_delay) - self.current_piece.coord = potential_coord - self.current_piece.minoes_coords = rotated_minoes_coords - self.current_piece.orientation = ( - (self.current_piece.orientation + direction) % 4 - ) - self.current_piece.last_rotation_point_used = rotation_point - self.move_ghost() - return True - else: - return False - - SCORES = ( - {LINES_CLEAR_NAME: "", T_Spin.NONE: 0, T_Spin.MINI: 1, T_Spin.T_SPIN: 4}, - {LINES_CLEAR_NAME: "SINGLE", T_Spin.NONE: 1, T_Spin.MINI: 2, T_Spin.T_SPIN: 8}, - {LINES_CLEAR_NAME: "DOUBLE", T_Spin.NONE: 3, T_Spin.T_SPIN: 12}, - {LINES_CLEAR_NAME: "TRIPLE", T_Spin.NONE: 5, T_Spin.T_SPIN: 16}, - {LINES_CLEAR_NAME: "TETRIS", T_Spin.NONE: 8} - ) - - def lock(self): - # Piece unlocked - if self.move(Movement.DOWN): - return - - # Start lock - self.current_piece.prelocked = False - self.stop(self.lock) - if self.pressed_actions: - self.auto_repeat = False - self.restart(self.repeat_action, AUTOREPEAT_DELAY) - - # Game over - if all( - (mino_coord + self.current_piece.coord).y >= NB_LINES - for mino_coord in self.current_piece.minoes_coords - ): - self.game_over() - return - - # T-Spin - if ( - self.current_piece.CAN_SPIN - and self.current_piece.last_rotation_point_used is not None - ): - 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 - else: - t_spin = T_Spin.NONE - else: - t_spin = T_Spin.NONE - - self.enter_the_matrix() - - # Clear complete lines - nb_lines_cleared = 0 - for y, line in reversed(list(enumerate(self.matrix))): - if all(mino for mino in line): - nb_lines_cleared += 1 - self.remove_line_of_matrix(y) - self.append_new_line_to_matrix() - if nb_lines_cleared: - self.nb_lines += nb_lines_cleared - - # Scoring - lock_strings = [] - lock_score = 0 - - if t_spin: - lock_strings.append(t_spin) - if nb_lines_cleared: - lock_strings.append(self.SCORES[nb_lines_cleared][LINES_CLEAR_NAME]) - self.combo += 1 - else: - self.combo = -1 - - if nb_lines_cleared or t_spin: - ds = self.SCORES[nb_lines_cleared][t_spin] - self.goal -= ds - ds *= 100 * self.level - lock_score += ds - lock_strings.append(str(ds)) - self.show_text("\n".join(lock_strings)) - - if self.combo >= 1: - ds = (20 if nb_lines_cleared==1 else 50) * self.combo * self.level - lock_score += ds - self.show_text("COMBO x{:n}\n{:n}".format(self.combo, ds)) - - self.score += lock_score - - if self.goal <= 0: - self.new_level() - else: - self.new_current_piece() - - def enter_the_matrix(self): - 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 - - def append_new_line_to_matrix(self): - self.matrix.append([None for x in range(NB_COLS)]) - - def remove_line_of_matrix(self, line): - self.matrix.pop(line) - - 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 swap(self): - if self.current_piece.hold_enabled: - self.current_piece.hold_enabled = False - self.current_piece.prelocked = False - self.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.coord = HELD_I_COORD - else: - self.held_piece.coord = HELD_PIECE_COORD - self.held_piece.minoes_coords = self.held_piece.MINOES_COORDS - if self.current_piece: - self.current_piece.coord = MATRIX_PIECE_INIT_COORD - self.ghost_piece = self.current_piece.ghost() - self.move_ghost() - else: - self.new_current_piece() - - def pause(self): - self.state = State.PAUSED - self.stop(self.fall) - self.stop(self.lock) - self.stop(self.update_time) - self.pressed_actions = [] - self.auto_repeat = False - self.stop(self.repeat_action) - - def resume(self): - self.state = State.PLAYING - self.start(self.fall, self.fall_delay) - if self.current_piece.prelocked: - self.start(self.lock, self.lock_delay) - self.start(self.update_time, 1) - - def game_over(self): - self.state = State.OVER - self.stop(self.fall) - self.stop(self.update_time) - self.stop(self.repeat_action) - self.save_high_score() - - def update_time(self): - self.time += 1 - - def do_action(self, action): - action() - if action in self.autorepeatable_actions: - self.auto_repeat = False - self.pressed_actions.append(action) - self.restart(self.repeat_action, AUTOREPEAT_DELAY) - - def repeat_action(self): - if self.pressed_actions: - self.pressed_actions[-1]() - if not self.auto_repeat: - self.auto_repeat = True - self.restart(self.repeat_action, AUTOREPEAT_PERIOD) - else: - self.auto_repeat = False - self.stop(self.repeat_action) - - def remove_action(self, action): - if action in self.autorepeatable_actions: - try: - self.pressed_actions.remove(action) - except ValueError: - pass - - def show_text(self, text): - print(text) - raise Warning("TetrisLogic.show_text not implemented.") - - def load_high_score(self): - self.high_score = 0 - raise Warning( - """TetrisLogic.load_high_score not implemented. -High score is set to 0""" - ) - - def save_high_score(self): - print("High score: {:n}".format(self.high_score)) - raise Warning( - """TetrisLogic.save_high_score not implemented. -High score is not saved""" - ) - - def start(task, period): - raise Warning("TetrisLogic.start is not implemented.") - - def stop(self, task): - raise Warning("TetrisLogic.stop is not implemented.") - - def restart(self, task, period): - self.stop(task) - self.start(task, period) - diff --git a/tetrislogic/__init__.py b/tetrislogic/__init__.py new file mode 100644 index 0000000..c291edd --- /dev/null +++ b/tetrislogic/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +from .consts import NB_LINES, NB_COLS, NB_NEXT_PIECES +from .tetrislogic import TetrisLogic, State diff --git a/tetrislogic/consts.py b/tetrislogic/consts.py new file mode 100644 index 0000000..bd28d44 --- /dev/null +++ b/tetrislogic/consts.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +from .utils import Coord + + +# Matrix +NB_LINES = 20 +NB_COLS = 10 +NB_NEXT_PIECES = 5 + +# Delays (seconds) +LOCK_DELAY = 0.5 +FALL_DELAY = 1 +AUTOREPEAT_DELAY = 0.200 # Official : 0.300 +AUTOREPEAT_PERIOD = 0.010 # Official : 0.010 + + +# Piece init coord +CURRENT_COORD = Coord(4, NB_LINES) +NEXT_COORDS = [ + Coord(NB_COLS+6, NB_LINES-4*n-3) + for n in range(NB_NEXT_PIECES) +] +HELD_COORD = Coord(-7, NB_LINES-3) +HELD_I_COORD = Coord(-7, NB_LINES-3) \ No newline at end of file diff --git a/tetrislogic/tetrislogic.py b/tetrislogic/tetrislogic.py new file mode 100644 index 0000000..7244bd3 --- /dev/null +++ b/tetrislogic/tetrislogic.py @@ -0,0 +1,418 @@ +# -*- coding: utf-8 -*- +from .utils import Coord, Movement, Rotation, T_Spin, Line +from .tetromino import Tetromino +from .consts import ( + NB_LINES, NB_COLS, NB_NEXT_PIECES, + LOCK_DELAY, FALL_DELAY, + AUTOREPEAT_DELAY, AUTOREPEAT_PERIOD, + CURRENT_COORD, NEXT_COORDS, HELD_COORD, HELD_I_COORD +) + + +LINES_CLEAR_NAME = "LINES_CLEAR_NAME" + + +class State: + + STARTING = "STARTING" + PLAYING = "PLAYING" + PAUSED = "PAUSED" + OVER = "OVER" + + +class Matrix(list): + + def cell_is_free(self, coord): + return ( + 0 <= coord.x < NB_COLS + and 0 <= coord.y + and not self[coord.y][coord.x] + ) + + +class TetrisLogic(): + + def __init__(self): + self.load_high_score() + self.state = State.STARTING + self.matrix = Matrix() + self.next = [] + self.current = None + self.ghost = None + self.held = None + self.time = 0 + self.autorepeatable_actions = (self.move_left, self.move_right, self.soft_drop) + self.pressed_actions = [] + self._score = 0 + + def get_score(self): + return self._score + + def set_score(self, new_score): + self._score = new_score + if self._score > self.high_score: + self.high_score = self._score + + score = property(get_score, set_score) + + def new_game(self): + self.level = 0 + self.score = 0 + self.nb_lines_cleared = 0 + self.goal = 0 + self.time = 0 + + self.pressed_actions = [] + self.auto_repeat = False + + self.lock_delay = LOCK_DELAY + self.fall_delay = FALL_DELAY + + self.matrix.clear() + for y in range(NB_LINES+3): + self.append_new_line_to_matrix() + self.load_next() + self.held = None + self.state = State.PLAYING + self.start(self.update_time, 1) + + self.new_level() + + def load_next(self): + self.next = [ + Tetromino() + for i in range(NB_NEXT_PIECES) + ] + + def append_new_line_to_matrix(self): + self.matrix.append(Line(None for x in range(NB_COLS))) + + def new_level(self): + self.level += 1 + self.goal += 5 * self.level + if self.level <= 20: + self.fall_delay = pow(0.8 - ((self.level-1)*0.007), self.level-1) + 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.restart(self.fall, self.fall_delay) + + self.new_current() + + def new_current(self): + self.current = self.next.pop(0) + self.current.coord = CURRENT_COORD + self.ghost = self.current.ghost() + self.move_ghost() + self.next.append(Tetromino()) + self.next[-1].coord = NEXT_COORDS[-1] + for tetromino, coord in zip (self.next, NEXT_COORDS): + tetromino.coord = coord + + if not self.can_move( + self.current.coord, + (mino.coord for mino in self.current) + ): + self.game_over() + + def move_left(self): + self.move(Movement.LEFT) + + def move_right(self): + self.move(Movement.RIGHT) + + def rotate_clockwise(self): + self.rotate(Rotation.CLOCKWISE) + + def rotate_counter(self): + self.rotate(Rotation.COUNTER) + + def move_ghost(self): + self.ghost.coord = self.current.coord + for ghost_mino, current_mino in zip(self.ghost, self.current): + ghost_mino.coord = current_mino.coord + while self.can_move( + self.ghost.coord + Movement.DOWN, + (mino.coord for mino in self.ghost) + ): + self.ghost.coord += Movement.DOWN + + def soft_drop(self): + if self.move(Movement.DOWN): + self.score += 1 + return True + else: + return False + + def hard_drop(self): + while self.move(Movement.DOWN, prelock=False): + self.score += 2 + self.lock() + + def fall(self): + self.move(Movement.DOWN) + + def move(self, movement, prelock=True): + potential_coord = self.current.coord + movement + if self.can_move( + potential_coord, + (mino.coord for mino in self.current) + ): + if self.current.prelocked: + self.restart(self.lock, self.lock_delay) + self.current.coord = potential_coord + if not movement == Movement.DOWN: + self.current.last_rotation_point = None + self.move_ghost() + return True + else: + if ( + prelock and not self.current.prelocked + and movement == Movement.DOWN + ): + self.current.prelocked = True + self.start(self.lock, self.lock_delay) + return False + + def rotate(self, rotation): + rotated_coords = tuple( + Coord(rotation*mino.coord.y, -rotation*mino.coord.x) + for mino in self.current + ) + for rotation_point, liberty_degree in enumerate( + self.current.SRS[rotation][self.current.orientation], + start = 1 + ): + potential_coord = self.current.coord + liberty_degree + if self.can_move(potential_coord, rotated_coords): + if self.current.prelocked: + self.restart(self.lock, self.lock_delay) + self.current.coord = potential_coord + for mino, coord in zip(self.current, rotated_coords): + mino.coord = coord + self.current.orientation = ( + (self.current.orientation + rotation) % 4 + ) + self.current.last_rotation_point = rotation_point + self.move_ghost() + return True + else: + return False + + SCORES = ( + {LINES_CLEAR_NAME: "", T_Spin.NONE: 0, T_Spin.MINI: 1, T_Spin.T_SPIN: 4}, + {LINES_CLEAR_NAME: "SINGLE", T_Spin.NONE: 1, T_Spin.MINI: 2, T_Spin.T_SPIN: 8}, + {LINES_CLEAR_NAME: "DOUBLE", T_Spin.NONE: 3, T_Spin.T_SPIN: 12}, + {LINES_CLEAR_NAME: "TRIPLE", T_Spin.NONE: 5, T_Spin.T_SPIN: 16}, + {LINES_CLEAR_NAME: "TETRIS", T_Spin.NONE: 8} + ) + + def lock(self): + # Piece unlocked + if self.move(Movement.DOWN): + return + + # Start lock + self.current.prelocked = False + self.stop(self.lock) + if self.pressed_actions: + self.auto_repeat = False + self.restart(self.repeat_action, AUTOREPEAT_DELAY) + + # Game over + if all( + (mino.coord + self.current.coord).y >= NB_LINES + for mino in self.current + ): + self.game_over() + return + + # T-Spin + if ( + self.current.__class__ == Tetromino.T + and self.current.last_rotation_point is not None + ): + 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.last_rotation_point == 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 + else: + t_spin = T_Spin.NONE + else: + t_spin = T_Spin.NONE + + for mino in self.current: + coord = mino.coord + self.current.coord + del(mino.coord) + if coord.y <= NB_LINES+3: + self.matrix[coord.y][coord.x] = mino + + # Clear complete lines + nb_lines_cleared = 0 + for y, line in reversed(list(enumerate(self.matrix))): + if all(mino for mino in line): + nb_lines_cleared += 1 + self.remove_line_of_matrix(y) + self.append_new_line_to_matrix() + if nb_lines_cleared: + self.nb_lines_cleared += nb_lines_cleared + + # Scoring + lock_strings = [] + lock_score = 0 + + if t_spin: + lock_strings.append(t_spin) + if nb_lines_cleared: + lock_strings.append(self.SCORES[nb_lines_cleared][LINES_CLEAR_NAME]) + self.combo += 1 + else: + self.combo = -1 + + if nb_lines_cleared or t_spin: + ds = self.SCORES[nb_lines_cleared][t_spin] + self.goal -= ds + ds *= 100 * self.level + lock_score += ds + lock_strings.append(str(ds)) + self.show_text("\n".join(lock_strings)) + + if self.combo >= 1: + ds = (20 if nb_lines_cleared==1 else 50) * self.combo * self.level + lock_score += ds + self.show_text("COMBO x{:n}\n{:n}".format(self.combo, ds)) + + self.score += lock_score + + if self.goal <= 0: + self.new_level() + else: + self.new_current() + + def remove_line_of_matrix(self, line): + self.matrix.pop(line) + + def can_move(self, potential_coord, minoes_coords): + return all( + self.matrix.cell_is_free(potential_coord+mino_coord) + for mino_coord in minoes_coords + ) + + 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.coord + self.T_SLOT_COORDS[ + (self.current.orientation + n) % 4 + ] + return not self.matrix.cell_is_free(t_slot_coord) + + def swap(self): + if self.current.hold_enabled: + self.current.hold_enabled = False + self.current.prelocked = False + self.stop(self.lock) + self.current, self.held = self.held, self.current + if self.held.__class__ == Tetromino.I: + self.held.coord = HELD_I_COORD + else: + self.held.coord = HELD_COORD + for mino, coord in zip(self.held, self.held.MINOES_COORDS): + mino.coord = coord + + if self.current: + self.current.coord = CURRENT_COORD + self.ghost = self.current.ghost() + self.move_ghost() + else: + self.new_current() + + def pause(self): + self.state = State.PAUSED + self.stop_all() + self.pressed_actions = [] + self.auto_repeat = False + self.stop(self.repeat_action) + + def resume(self): + self.state = State.PLAYING + self.start(self.fall, self.fall_delay) + if self.current.prelocked: + self.start(self.lock, self.lock_delay) + self.start(self.update_time, 1) + + def game_over(self): + self.state = State.OVER + self.stop_all() + self.save_high_score() + + def stop_all(self): + self.stop(self.fall) + self.stop(self.lock) + self.stop(self.update_time) + + def update_time(self): + self.time += 1 + + def do_action(self, action): + action() + if action in self.autorepeatable_actions: + self.auto_repeat = False + self.pressed_actions.append(action) + self.restart(self.repeat_action, AUTOREPEAT_DELAY) + + def repeat_action(self): + if self.pressed_actions: + self.pressed_actions[-1]() + if not self.auto_repeat: + self.auto_repeat = True + self.restart(self.repeat_action, AUTOREPEAT_PERIOD) + else: + self.auto_repeat = False + self.stop(self.repeat_action) + + def remove_action(self, action): + if action in self.autorepeatable_actions: + try: + self.pressed_actions.remove(action) + except ValueError: + pass + + def show_text(self, text): + print(text) + raise Warning("TetrisLogic.show_text not implemented.") + + def load_high_score(self): + self.high_score = 0 + raise Warning( + """TetrisLogic.load_high_score not implemented. +High score is set to 0""" + ) + + def save_high_score(self): + print("High score: {:n}".format(self.high_score)) + raise Warning( + """TetrisLogic.save_high_score not implemented. +High score is not saved""" + ) + + def start(task, period): + raise Warning("TetrisLogic.start is not implemented.") + + def stop(self, task): + raise Warning("TetrisLogic.stop is not implemented.") + + def restart(self, task, period): + self.stop(task) + self.start(task, period) + diff --git a/tetrislogic/tetromino.py b/tetrislogic/tetromino.py new file mode 100644 index 0000000..c7a043a --- /dev/null +++ b/tetrislogic/tetromino.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- +import random + +from .utils import Coord, Rotation + +class Mino: + + def __init__(self, color, coord): + self.color = color + self.coord = coord + + +class Tetromino: + + random_bag = [] + + + class MetaTetromino(type): + + def __init__(cls, name, bases, dico): + super().__init__(name, bases, dico) + cls.classes.append(cls) + + + class AbstractTetromino(list): + + # Super rotation system + SRS = { + 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)), + ), + Rotation.COUNTER: ( + (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)), + ), + } + classes = [] + + def __init__(self): + super().__init__( + Mino(self.MINOES_COLOR, coord) + for coord in self.MINOES_COORDS + ) + self.orientation = 0 + self.last_rotation_point = None + self.hold_enabled = True + self.prelocked = False + + def ghost(self): + return self.__class__() + + class O(AbstractTetromino, metaclass=MetaTetromino): + + SRS = { + Rotation.COUNTER: (tuple(), tuple(), tuple(), tuple()), + Rotation.CLOCKWISE: (tuple(), tuple(), tuple(), tuple()) + } + MINOES_COORDS = (Coord(0, 0), Coord(1, 0), Coord(0, 1), Coord(1, 1)) + MINOES_COLOR = "yellow" + + def rotate(self, direction): + return False + + + class I(AbstractTetromino, metaclass=MetaTetromino): + + SRS = { + Rotation.COUNTER: ( + (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(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(-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)) + ) + } + MINOES_COORDS = (Coord(-1, 0), Coord(0, 0), Coord(1, 0), Coord(2, 0)) + MINOES_COLOR = "cyan" + + + class T(AbstractTetromino, metaclass=MetaTetromino): + + MINOES_COORDS = (Coord(-1, 0), Coord(0, 0), Coord(0, 1), Coord(1, 0)) + MINOES_COLOR = "magenta" + + + class L(AbstractTetromino, metaclass=MetaTetromino): + + MINOES_COORDS = (Coord(-1, 0), Coord(0, 0), Coord(1, 0), Coord(1, 1)) + MINOES_COLOR = "orange" + + + class J(AbstractTetromino, metaclass=MetaTetromino): + + MINOES_COORDS = (Coord(-1, 1), Coord(-1, 0), Coord(0, 0), Coord(1, 0)) + MINOES_COLOR = "blue" + + + class S(AbstractTetromino, metaclass=MetaTetromino): + + MINOES_COORDS = (Coord(-1, 0), Coord(0, 0), Coord(0, 1), Coord(1, 1)) + MINOES_COLOR = "green" + + + class Z(AbstractTetromino, metaclass=MetaTetromino): + + MINOES_COORDS = (Coord(-1, 1), Coord(0, 1), Coord(0, 0), Coord(1, 0)) + MINOES_COLOR = "red" + + + def __new__(cls): + if not cls.random_bag: + cls.random_bag = list(Tetromino.AbstractTetromino.classes) + random.shuffle(cls.random_bag) + return cls.random_bag.pop()() \ No newline at end of file diff --git a/tetrislogic/utils.py b/tetrislogic/utils.py new file mode 100644 index 0000000..ab06f59 --- /dev/null +++ b/tetrislogic/utils.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +class Coord: + + def __init__(self, x, y): + self.x = x + self.y = y + + def __add__(self, other): + return Coord(self.x+other.x, self.y+other.y) + + +class Movement: + + LEFT = Coord(-1, 0) + RIGHT = Coord( 1, 0) + DOWN = Coord( 0, -1) + + +class Rotation: + + CLOCKWISE = 1 + COUNTER = -1 + + +class T_Spin: + + NONE = "" + MINI = "MINI\nT-SPIN" + T_SPIN = "T-SPIN" + + +class Line(list): + pass +