improve tetrislogic API
This commit is contained in:
		
							
								
								
									
										253
									
								
								TetrArcade.py
									
									
									
									
									
								
							
							
						
						
									
										253
									
								
								TetrArcade.py
									
									
									
									
									
								
							| @ -1,10 +1,12 @@ | |||||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||||
| import sys | import sys | ||||||
|  |  | ||||||
| try: | try: | ||||||
|     import arcade |     import arcade | ||||||
| except ImportError as e: | except ImportError as e: | ||||||
|     sys.exit( |     sys.exit( | ||||||
|         str(e) + """ |         str(e) | ||||||
|  |         + """ | ||||||
| This game require arcade library. | This game require arcade library. | ||||||
| You can install it with: | You can install it with: | ||||||
| python -m pip install --user arcade""" | python -m pip install --user arcade""" | ||||||
| @ -17,14 +19,14 @@ import os | |||||||
| import itertools | import itertools | ||||||
| import configparser | import configparser | ||||||
|  |  | ||||||
| from tetrislogic import TetrisLogic, Color, State, Coord | from tetrislogic import TetrisLogic, Color, Phase, Coord, I_Tetrimino, Movement | ||||||
|  |  | ||||||
|  |  | ||||||
| # Constants | # Constants | ||||||
| # Matrix | # Matrix | ||||||
| NB_LINES = 20 | LINES = 20 | ||||||
| NB_COLS = 10 | COLLUMNS = 10 | ||||||
| NB_NEXT = 5 | NEXT_PIECES = 5 | ||||||
|  |  | ||||||
| # Delays (seconds) | # Delays (seconds) | ||||||
| LOCK_DELAY = 0.5 | LOCK_DELAY = 0.5 | ||||||
| @ -33,9 +35,9 @@ AUTOREPEAT_DELAY = 0.300 | |||||||
| AUTOREPEAT_PERIOD = 0.010 | AUTOREPEAT_PERIOD = 0.010 | ||||||
|  |  | ||||||
| # Piece init coord | # Piece init coord | ||||||
| MATRIX_PIECE_COORD = Coord(4, NB_LINES) | MATRIX_PIECE_COORD = Coord(4, LINES) | ||||||
| NEXT_PIECE_COORDS = [Coord(NB_COLS + 4, NB_LINES - 4 * n - 3) for n in range(NB_NEXT)] | NEXT_PIECES_COORDS = [Coord(COLLUMNS + 4, LINES - 4 * n - 3) for n in range(COLLUMNS)] | ||||||
| HELD_PIECE_COORD = Coord(-5, NB_LINES - 3) | HELD_PIECE_COORD = Coord(-5, LINES - 3) | ||||||
|  |  | ||||||
| # Window | # Window | ||||||
| WINDOW_WIDTH = 800 | WINDOW_WIDTH = 800 | ||||||
| @ -49,7 +51,7 @@ BG_COLOR = (7, 11, 21) | |||||||
| HIGHLIGHT_TEXT_DISPLAY_DELAY = 0.7 | HIGHLIGHT_TEXT_DISPLAY_DELAY = 0.7 | ||||||
|  |  | ||||||
| # Transparency (0=invisible, 255=opaque) | # Transparency (0=invisible, 255=opaque) | ||||||
| NORMAL_ALPHA = 200 | NORMAL_ALPHA = 255 | ||||||
| PRELOCKED_ALPHA = 100 | PRELOCKED_ALPHA = 100 | ||||||
| GHOST_ALPHA = 30 | GHOST_ALPHA = 30 | ||||||
| MATRIX_BG_ALPHA = 100 | MATRIX_BG_ALPHA = 100 | ||||||
| @ -59,7 +61,7 @@ BAR_ALPHA = 75 | |||||||
| MINO_SIZE = 20 | MINO_SIZE = 20 | ||||||
| MINO_SPRITE_SIZE = 21 | MINO_SPRITE_SIZE = 21 | ||||||
|  |  | ||||||
| if getattr(sys, 'frozen', False): | if getattr(sys, "frozen", False): | ||||||
|     # The application is frozen |     # The application is frozen | ||||||
|     PROGRAM_DIR = os.path.dirname(sys.executable) |     PROGRAM_DIR = os.path.dirname(sys.executable) | ||||||
| else: | else: | ||||||
| @ -86,9 +88,12 @@ MINOES_COLOR_ID = { | |||||||
|     Color.PRELOCKED: 7, |     Color.PRELOCKED: 7, | ||||||
| } | } | ||||||
| TEXTURES = arcade.load_textures( | TEXTURES = arcade.load_textures( | ||||||
|     MINOES_SPRITES_PATH, ((i * MINO_SPRITE_SIZE, 0, MINO_SPRITE_SIZE, MINO_SPRITE_SIZE) for i in range(8)) |     MINOES_SPRITES_PATH, | ||||||
|  |     ((i * MINO_SPRITE_SIZE, 0, MINO_SPRITE_SIZE, MINO_SPRITE_SIZE) for i in range(8)), | ||||||
| ) | ) | ||||||
| TEXTURES = {color: TEXTURES[i] for color, i in MINOES_COLOR_ID.items()} | TEXTURES = {color: TEXTURES[i] for color, i in MINOES_COLOR_ID.items()} | ||||||
|  | NORMAL_TEXTURE = 0 | ||||||
|  | LOCKED_TEXTURE = 1 | ||||||
|  |  | ||||||
| # Music | # Music | ||||||
| MUSIC_DIR = os.path.join(RESOURCES_DIR, "musics") | MUSIC_DIR = os.path.join(RESOURCES_DIR, "musics") | ||||||
| @ -105,16 +110,19 @@ HIGHLIGHT_TEXT_SIZE = 20 | |||||||
|  |  | ||||||
| # User profile path | # User profile path | ||||||
| if sys.platform == "win32": | if sys.platform == "win32": | ||||||
|     USER_PROFILE_DIR = os.environ.get("appdata", os.path.expanduser("~\Appdata\Roaming")) |     USER_PROFILE_DIR = os.environ.get( | ||||||
|  |         "appdata", os.path.expanduser("~\Appdata\Roaming") | ||||||
|  |     ) | ||||||
| else: | else: | ||||||
|     USER_PROFILE_DIR = os.environ.get("XDG_DATA_HOME", os.path.expanduser("~/.local/share")) |     USER_PROFILE_DIR = os.environ.get( | ||||||
|  |         "XDG_DATA_HOME", os.path.expanduser("~/.local/share") | ||||||
|  |     ) | ||||||
| USER_PROFILE_DIR = os.path.join(USER_PROFILE_DIR, "TetrArcade") | USER_PROFILE_DIR = os.path.join(USER_PROFILE_DIR, "TetrArcade") | ||||||
| HIGH_SCORE_PATH = os.path.join(USER_PROFILE_DIR, ".high_score") | HIGH_SCORE_PATH = os.path.join(USER_PROFILE_DIR, ".high_score") | ||||||
| CONF_PATH = os.path.join(USER_PROFILE_DIR, "TetrArcade.ini") | CONF_PATH = os.path.join(USER_PROFILE_DIR, "TetrArcade.ini") | ||||||
|  |  | ||||||
|  |  | ||||||
| class MinoSprite(arcade.Sprite): | class MinoSprite(arcade.Sprite): | ||||||
|  |  | ||||||
|     def __init__(self, mino, window, alpha): |     def __init__(self, mino, window, alpha): | ||||||
|         super().__init__() |         super().__init__() | ||||||
|         self.alpha = alpha |         self.alpha = alpha | ||||||
| @ -123,16 +131,15 @@ class MinoSprite(arcade.Sprite): | |||||||
|         self.append_texture(TEXTURES[Color.PRELOCKED]) |         self.append_texture(TEXTURES[Color.PRELOCKED]) | ||||||
|         self.set_texture(0) |         self.set_texture(0) | ||||||
|  |  | ||||||
|     def refresh(self, x, y, prelocked=False): |     def refresh(self, x, y, texture=0): | ||||||
|         self.scale = self.window.scale |         self.scale = self.window.scale | ||||||
|         size = MINO_SIZE * self.scale |         size = MINO_SIZE * self.scale | ||||||
|         self.left = self.window.matrix.bg.left + x * size |         self.left = self.window.matrix.bg.left + x * size | ||||||
|         self.bottom = self.window.matrix.bg.bottom + y * size |         self.bottom = self.window.matrix.bg.bottom + y * size | ||||||
|         self.set_texture(prelocked) |         self.set_texture(texture) | ||||||
|  |  | ||||||
|  |  | ||||||
| class MinoesSprites(arcade.SpriteList): | class MinoesSprites(arcade.SpriteList): | ||||||
|  |  | ||||||
|     def resize(self, scale): |     def resize(self, scale): | ||||||
|         for sprite in self: |         for sprite in self: | ||||||
|             sprite.scale = scale |             sprite.scale = scale | ||||||
| @ -140,7 +147,6 @@ class MinoesSprites(arcade.SpriteList): | |||||||
|  |  | ||||||
|  |  | ||||||
| class TetrominoSprites(MinoesSprites): | class TetrominoSprites(MinoesSprites): | ||||||
|  |  | ||||||
|     def __init__(self, tetromino, window, alpha=NORMAL_ALPHA): |     def __init__(self, tetromino, window, alpha=NORMAL_ALPHA): | ||||||
|         super().__init__() |         super().__init__() | ||||||
|         self.tetromino = tetromino |         self.tetromino = tetromino | ||||||
| @ -149,14 +155,13 @@ class TetrominoSprites(MinoesSprites): | |||||||
|             mino.sprite = MinoSprite(mino, window, alpha) |             mino.sprite = MinoSprite(mino, window, alpha) | ||||||
|             self.append(mino.sprite) |             self.append(mino.sprite) | ||||||
|  |  | ||||||
|     def refresh(self): |     def refresh(self, texture=NORMAL_TEXTURE): | ||||||
|         for mino in self.tetromino: |         for mino in self.tetromino: | ||||||
|             coord = mino.coord + self.tetromino.coord |             coord = mino.coord + self.tetromino.coord | ||||||
|             mino.sprite.refresh(coord.x, coord.y, self.tetromino.prelocked) |             mino.sprite.refresh(coord.x, coord.y, texture) | ||||||
|  |  | ||||||
|  |  | ||||||
| class MatrixSprites(MinoesSprites): | class MatrixSprites(MinoesSprites): | ||||||
|  |  | ||||||
|     def __init__(self, matrix): |     def __init__(self, matrix): | ||||||
|         super().__init__() |         super().__init__() | ||||||
|         self.matrix = matrix |         self.matrix = matrix | ||||||
| @ -175,7 +180,6 @@ class MatrixSprites(MinoesSprites): | |||||||
|  |  | ||||||
|  |  | ||||||
| class TetrArcade(TetrisLogic, arcade.Window): | class TetrArcade(TetrisLogic, arcade.Window): | ||||||
|  |  | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         locale.setlocale(locale.LC_ALL, "") |         locale.setlocale(locale.LC_ALL, "") | ||||||
|         self.highlight_texts = [] |         self.highlight_texts = [] | ||||||
| @ -192,7 +196,7 @@ class TetrArcade(TetrisLogic, arcade.Window): | |||||||
|             self.new_conf() |             self.new_conf() | ||||||
|             self.load_conf() |             self.load_conf() | ||||||
|  |  | ||||||
|         super().__init__(NB_LINES, NB_COLS, NB_NEXT) |         super().__init__(LINES, COLLUMNS, NEXT_PIECES) | ||||||
|         arcade.Window.__init__( |         arcade.Window.__init__( | ||||||
|             self, |             self, | ||||||
|             width=self.init_width, |             width=self.init_width, | ||||||
| @ -219,16 +223,21 @@ class TetrArcade(TetrisLogic, arcade.Window): | |||||||
|             try: |             try: | ||||||
|                 self.music = pyglet.media.Player() |                 self.music = pyglet.media.Player() | ||||||
|                 playlist = itertools.cycle( |                 playlist = itertools.cycle( | ||||||
|                     pyglet.media.load(path) |                     pyglet.media.load(path) for path in MUSICS_PATHS | ||||||
|                     for path in MUSICS_PATHS |  | ||||||
|                 ) |                 ) | ||||||
|                 self.music.queue(playlist) |                 self.music.queue(playlist) | ||||||
|             except: |             except: | ||||||
|                 Warning("Can't play music.") |                 Warning("Can't play music.") | ||||||
|                 self.play_music = False |                 self.music = None | ||||||
|  |         else: | ||||||
|  |             self.music = None | ||||||
|  |  | ||||||
|     def new_conf(self): |     def new_conf(self): | ||||||
|         self.conf["WINDOW"] = {"width": WINDOW_WIDTH, "height": WINDOW_HEIGHT, "fullscreen": False} |         self.conf["WINDOW"] = { | ||||||
|  |             "width": WINDOW_WIDTH, | ||||||
|  |             "height": WINDOW_HEIGHT, | ||||||
|  |             "fullscreen": False, | ||||||
|  |         } | ||||||
|         self.conf["KEYBOARD"] = { |         self.conf["KEYBOARD"] = { | ||||||
|             "start": "ENTER", |             "start": "ENTER", | ||||||
|             "move left": "LEFT", |             "move left": "LEFT", | ||||||
| @ -241,9 +250,7 @@ class TetrArcade(TetrisLogic, arcade.Window): | |||||||
|             "pause": "ESCAPE", |             "pause": "ESCAPE", | ||||||
|             "fullscreen": "F11", |             "fullscreen": "F11", | ||||||
|         } |         } | ||||||
|         self.conf["MUSIC"] = { |         self.conf["MUSIC"] = {"play": True} | ||||||
|             "play": True |  | ||||||
|         } |  | ||||||
|         self.conf["AUTO-REPEAT"] = {"delay": 0.3, "period": 0.01} |         self.conf["AUTO-REPEAT"] = {"delay": 0.3, "period": 0.01} | ||||||
|         self.load_conf() |         self.load_conf() | ||||||
|         if not os.path.exists(USER_PROFILE_DIR): |         if not os.path.exists(USER_PROFILE_DIR): | ||||||
| @ -259,28 +266,61 @@ class TetrArcade(TetrisLogic, arcade.Window): | |||||||
|         for action, key in self.conf["KEYBOARD"].items(): |         for action, key in self.conf["KEYBOARD"].items(): | ||||||
|             self.conf["KEYBOARD"][action] = key.upper() |             self.conf["KEYBOARD"][action] = key.upper() | ||||||
|         self.key_map = { |         self.key_map = { | ||||||
|             State.STARTING: { |             Phase.STARTING: { | ||||||
|                 getattr(arcade.key, self.conf["KEYBOARD"]["start"]): self.new_game, |                 getattr(arcade.key, self.conf["KEYBOARD"]["start"]): self.new_game, | ||||||
|                 getattr(arcade.key, self.conf["KEYBOARD"]["fullscreen"]): self.toggle_fullscreen, |                 getattr( | ||||||
|  |                     arcade.key, self.conf["KEYBOARD"]["fullscreen"] | ||||||
|  |                 ): self.toggle_fullscreen, | ||||||
|             }, |             }, | ||||||
|             State.PLAYING: { |             Phase.FALLING: { | ||||||
|                 getattr(arcade.key, self.conf["KEYBOARD"]["move left"]): self.move_left, |                 getattr(arcade.key, self.conf["KEYBOARD"]["move left"]): self.move_left, | ||||||
|                 getattr(arcade.key, self.conf["KEYBOARD"]["move right"]): self.move_right, |                 getattr( | ||||||
|  |                     arcade.key, self.conf["KEYBOARD"]["move right"] | ||||||
|  |                 ): self.move_right, | ||||||
|                 getattr(arcade.key, self.conf["KEYBOARD"]["soft drop"]): self.soft_drop, |                 getattr(arcade.key, self.conf["KEYBOARD"]["soft drop"]): self.soft_drop, | ||||||
|                 getattr(arcade.key, self.conf["KEYBOARD"]["hard drop"]): self.hard_drop, |                 getattr(arcade.key, self.conf["KEYBOARD"]["hard drop"]): self.hard_drop, | ||||||
|                 getattr(arcade.key, self.conf["KEYBOARD"]["rotate clockwise"]): self.rotate_clockwise, |                 getattr( | ||||||
|                 getattr(arcade.key, self.conf["KEYBOARD"]["rotate counter"]): self.rotate_counter, |                     arcade.key, self.conf["KEYBOARD"]["rotate clockwise"] | ||||||
|                 getattr(arcade.key, self.conf["KEYBOARD"]["hold"]): self.swap, |                 ): self.rotate_clockwise, | ||||||
|  |                 getattr( | ||||||
|  |                     arcade.key, self.conf["KEYBOARD"]["rotate counter"] | ||||||
|  |                 ): self.rotate_counter, | ||||||
|  |                 getattr(arcade.key, self.conf["KEYBOARD"]["hold"]): self.hold, | ||||||
|                 getattr(arcade.key, self.conf["KEYBOARD"]["pause"]): self.pause, |                 getattr(arcade.key, self.conf["KEYBOARD"]["pause"]): self.pause, | ||||||
|                 getattr(arcade.key, self.conf["KEYBOARD"]["fullscreen"]): self.toggle_fullscreen, |                 getattr( | ||||||
|  |                     arcade.key, self.conf["KEYBOARD"]["fullscreen"] | ||||||
|  |                 ): self.toggle_fullscreen, | ||||||
|             }, |             }, | ||||||
|             State.PAUSED: { |             Phase.LOCK: { | ||||||
|  |                 getattr(arcade.key, self.conf["KEYBOARD"]["move left"]): self.move_left, | ||||||
|  |                 getattr( | ||||||
|  |                     arcade.key, self.conf["KEYBOARD"]["move right"] | ||||||
|  |                 ): self.move_right, | ||||||
|  |                 getattr(arcade.key, self.conf["KEYBOARD"]["soft drop"]): self.soft_drop, | ||||||
|  |                 getattr(arcade.key, self.conf["KEYBOARD"]["hard drop"]): self.hard_drop, | ||||||
|  |                 getattr( | ||||||
|  |                     arcade.key, self.conf["KEYBOARD"]["rotate clockwise"] | ||||||
|  |                 ): self.rotate_clockwise, | ||||||
|  |                 getattr( | ||||||
|  |                     arcade.key, self.conf["KEYBOARD"]["rotate counter"] | ||||||
|  |                 ): self.rotate_counter, | ||||||
|  |                 getattr(arcade.key, self.conf["KEYBOARD"]["hold"]): self.hold, | ||||||
|  |                 getattr(arcade.key, self.conf["KEYBOARD"]["pause"]): self.pause, | ||||||
|  |                 getattr( | ||||||
|  |                     arcade.key, self.conf["KEYBOARD"]["fullscreen"] | ||||||
|  |                 ): self.toggle_fullscreen, | ||||||
|  |             }, | ||||||
|  |             Phase.PAUSED: { | ||||||
|                 getattr(arcade.key, self.conf["KEYBOARD"]["pause"]): self.resume, |                 getattr(arcade.key, self.conf["KEYBOARD"]["pause"]): self.resume, | ||||||
|                 getattr(arcade.key, self.conf["KEYBOARD"]["fullscreen"]): self.toggle_fullscreen, |                 getattr( | ||||||
|  |                     arcade.key, self.conf["KEYBOARD"]["fullscreen"] | ||||||
|  |                 ): self.toggle_fullscreen, | ||||||
|             }, |             }, | ||||||
|             State.OVER: { |             Phase.OVER: { | ||||||
|                 getattr(arcade.key, self.conf["KEYBOARD"]["start"]): self.new_game, |                 getattr(arcade.key, self.conf["KEYBOARD"]["start"]): self.new_game, | ||||||
|                 getattr(arcade.key, self.conf["KEYBOARD"]["fullscreen"]): self.toggle_fullscreen, |                 getattr( | ||||||
|  |                     arcade.key, self.conf["KEYBOARD"]["fullscreen"] | ||||||
|  |                 ): self.toggle_fullscreen, | ||||||
|             }, |             }, | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @ -291,12 +331,21 @@ class TetrArcade(TetrisLogic, arcade.Window): | |||||||
|             "\n\n\nCONTROLS\n\n" |             "\n\n\nCONTROLS\n\n" | ||||||
|             + "\n".join( |             + "\n".join( | ||||||
|                 "{:<16s}{:>6s}".format(key, action) |                 "{:<16s}{:>6s}".format(key, action) | ||||||
|                 for key, action in tuple(self.conf["KEYBOARD"].items()) + (("QUIT", "ALT+F4"),) |                 for key, action in tuple(self.conf["KEYBOARD"].items()) | ||||||
|  |                 + (("QUIT", "ALT+F4"),) | ||||||
|             ) |             ) | ||||||
|             + "\n\n\n" |             + "\n\n\n" | ||||||
|         ) |         ) | ||||||
|         self.start_text = "TETRARCADE" + controls_text + "PRESS [{}] TO START".format(self.conf["KEYBOARD"]["start"]) |         self.start_text = ( | ||||||
|         self.pause_text = "PAUSE" + controls_text + "PRESS [{}] TO RESUME".format(self.conf["KEYBOARD"]["pause"]) |             "TETRARCADE" | ||||||
|  |             + controls_text | ||||||
|  |             + "PRESS [{}] TO START".format(self.conf["KEYBOARD"]["start"]) | ||||||
|  |         ) | ||||||
|  |         self.pause_text = ( | ||||||
|  |             "PAUSE" | ||||||
|  |             + controls_text | ||||||
|  |             + "PRESS [{}] TO RESUME".format(self.conf["KEYBOARD"]["pause"]) | ||||||
|  |         ) | ||||||
|         self.game_over_text = """GAME |         self.game_over_text = """GAME | ||||||
| OVER | OVER | ||||||
|  |  | ||||||
| @ -309,74 +358,73 @@ AGAIN""".format( | |||||||
|  |  | ||||||
|         self.play_music = self.conf["MUSIC"].getboolean("play") |         self.play_music = self.conf["MUSIC"].getboolean("play") | ||||||
|  |  | ||||||
|     def on_new_game(self): |     def on_new_game(self, next_pieces): | ||||||
|         self.highlight_texts = [] |         self.highlight_texts = [] | ||||||
|  |  | ||||||
|         self.matrix.sprites = MatrixSprites(self.matrix) |         self.matrix.sprites = MatrixSprites(self.matrix) | ||||||
|         if self.play_music: |         for piece in next_pieces: | ||||||
|  |             piece.sprites = TetrominoSprites(piece, self) | ||||||
|  |  | ||||||
|  |         if self.music: | ||||||
|             self.music.seek(0) |             self.music.seek(0) | ||||||
|             self.music.play() |             self.music.play() | ||||||
|  |  | ||||||
|     def on_new_piece(self, piece): |  | ||||||
|         piece.sprites = TetrominoSprites(piece, self) |  | ||||||
|  |  | ||||||
|     def on_new_level(self, level): |     def on_new_level(self, level): | ||||||
|         self.show_text("LEVEL\n{:n}".format(level)) |         self.show_text("LEVEL\n{:n}".format(level)) | ||||||
|  |  | ||||||
|     def on_generation_phase(self, piece): |     def on_generation_phase(self, matrix, falling_piece, ghost_piece, next_pieces): | ||||||
|         self.matrix.sprites.refresh() |         matrix.sprites.refresh() | ||||||
|         self.matrix.ghost.sprites = TetrominoSprites(self.matrix.ghost, self, GHOST_ALPHA) |         falling_piece.sprites = TetrominoSprites(falling_piece, self) | ||||||
|         for tetromino in [self.matrix.piece, self.matrix.ghost] + self.next.pieces: |         ghost_piece.sprites = TetrominoSprites(ghost_piece, self, GHOST_ALPHA) | ||||||
|             tetromino.sprites.refresh() |         next_pieces[-1].sprites = TetrominoSprites(next_pieces[-1], self) | ||||||
|  |         for piece, coord in zip(next_pieces, NEXT_PIECES_COORDS): | ||||||
|  |             piece.coord = coord | ||||||
|  |             piece.sprites.refresh() | ||||||
|  |  | ||||||
|     def on_falling_phase(self): |     def on_falling_phase(self, falling_piece, ghost_piece): | ||||||
|         self.matrix.piece.sprites.refresh() |         falling_piece.sprites.refresh() | ||||||
|  |         ghost_piece.sprites.refresh() | ||||||
|  |  | ||||||
|     def on_moved(self, moved): |     def on_lock_phase(self, locked_piece): | ||||||
|         self.matrix.piece.sprites.refresh() |         locked_piece.sprites.refresh(texture=LOCKED_TEXTURE) | ||||||
|         self.matrix.ghost.sprites.refresh() |  | ||||||
|  |  | ||||||
|     def on_rotated(self, direction): |     def on_locked(self, matrix, locked_piece): | ||||||
|         for tetromino in (self.matrix.piece, self.matrix.ghost): |         for mino in locked_piece: | ||||||
|             tetromino.sprites.refresh() |             matrix.sprites.append(mino.sprite) | ||||||
|  |  | ||||||
|     def on_lock_phase(self): |     def on_line_remove(self, matrix, y): | ||||||
|         self.matrix.piece.sprites.refresh() |         matrix.sprites.remove_line(y) | ||||||
|         self.matrix.sprites.refresh() |  | ||||||
|  |  | ||||||
|     def on_locked(self, piece): |     def on_pattern_phase(self, pattern_name, pattern_score, nb_combo, combo_score): | ||||||
|         piece.sprites.refresh() |         if pattern_score: | ||||||
|         for mino in piece: |             self.show_text("{:s}\n{:n}".format(pattern_name, pattern_score)) | ||||||
|             self.matrix.sprites.append(mino.sprite) |         if combo_score: | ||||||
|  |             self.show_text("COMBO x{:n}\n{:n}".format(nb_combo, combo_score)) | ||||||
|  |  | ||||||
|     def on_line_remove(self, y): |     def on_hold(self, held_piece): | ||||||
|         self.matrix.sprites.remove_line(y) |         held_piece.coord = HELD_PIECE_COORD | ||||||
|  |         if type(held_piece) == I_Tetrimino: | ||||||
|     def swap(self): |             held_piece.coord += Movement.LEFT | ||||||
|         super().swap() |         held_piece.sprites.refresh() | ||||||
|         self.matrix.ghost.sprites = TetrominoSprites(self.matrix.ghost, self, GHOST_ALPHA) |  | ||||||
|         for tetromino in (self.held.piece, self.matrix.piece, self.matrix.ghost): |  | ||||||
|             if tetromino: |  | ||||||
|                 tetromino.sprites.refresh() |  | ||||||
|  |  | ||||||
|     def pause(self): |     def pause(self): | ||||||
|         super().pause() |         super().pause() | ||||||
|         if self.play_music: |         if self.music: | ||||||
|             self.music.pause() |             self.music.pause() | ||||||
|  |  | ||||||
|     def resume(self): |     def resume(self): | ||||||
|         super().resume() |         super().resume() | ||||||
|         if self.play_music: |         if self.music: | ||||||
|             self.music.play() |             self.music.play() | ||||||
|  |  | ||||||
|     def game_over(self): |     def on_game_over(self): | ||||||
|         super().game_over() |         if self.music: | ||||||
|         if self.play_music: |  | ||||||
|             self.music.pause() |             self.music.pause() | ||||||
|  |  | ||||||
|     def on_key_press(self, key, modifiers): |     def on_key_press(self, key, modifiers): | ||||||
|         for key_or_modifier in (key, modifiers): |         for key_or_modifier in (key, modifiers): | ||||||
|             try: |             try: | ||||||
|                 action = self.key_map[self.state][key_or_modifier] |                 action = self.key_map[self.phase][key_or_modifier] | ||||||
|             except KeyError: |             except KeyError: | ||||||
|                 pass |                 pass | ||||||
|             else: |             else: | ||||||
| @ -385,7 +433,7 @@ AGAIN""".format( | |||||||
|     def on_key_release(self, key, modifiers): |     def on_key_release(self, key, modifiers): | ||||||
|         for key_or_modifier in (key, modifiers): |         for key_or_modifier in (key, modifiers): | ||||||
|             try: |             try: | ||||||
|                 action = self.key_map[self.state][key_or_modifier] |                 action = self.key_map[self.phase][key_or_modifier] | ||||||
|             except KeyError: |             except KeyError: | ||||||
|                 pass |                 pass | ||||||
|             else: |             else: | ||||||
| @ -405,22 +453,29 @@ AGAIN""".format( | |||||||
|         arcade.start_render() |         arcade.start_render() | ||||||
|         self.bg.draw() |         self.bg.draw() | ||||||
|  |  | ||||||
|         if self.state in (State.PLAYING, State.OVER): |         if self.phase not in (Phase.STARTING, Phase.PAUSED): | ||||||
|             self.matrix.bg.draw() |             self.matrix.bg.draw() | ||||||
|             self.held.bg.draw() |             self.held.bg.draw() | ||||||
|             self.next.bg.draw() |             self.next.bg.draw() | ||||||
|             self.matrix.sprites.draw() |             self.matrix.sprites.draw() | ||||||
|  |  | ||||||
|             for tetromino in [self.held.piece, self.matrix.piece, self.matrix.ghost] + self.next.pieces: |             for tetromino in [ | ||||||
|  |                 self.held.piece, | ||||||
|  |                 self.matrix.piece, | ||||||
|  |                 self.matrix.ghost, | ||||||
|  |             ] + self.next.pieces: | ||||||
|                 if tetromino: |                 if tetromino: | ||||||
|                     tetromino.sprites.draw() |                     tetromino.sprites.draw() | ||||||
|  |  | ||||||
|             t = time.localtime(self.stats.time) |             t = time.localtime(self.stats.time) | ||||||
|             font_size = STATS_TEXT_SIZE * self.scale |             font_size = STATS_TEXT_SIZE * self.scale | ||||||
|             for y, text in enumerate(("TIME", "LINES", "GOAL", "LEVEL", "HIGH SCORE", "SCORE")): |             for y, text in enumerate( | ||||||
|  |                 ("TIME", "LINES", "GOAL", "LEVEL", "HIGH SCORE", "SCORE") | ||||||
|  |             ): | ||||||
|                 arcade.draw_text( |                 arcade.draw_text( | ||||||
|                     text=text, |                     text=text, | ||||||
|                     start_x=self.matrix.bg.left - self.scale * (STATS_TEXT_MARGIN + STATS_TEXT_WIDTH), |                     start_x=self.matrix.bg.left | ||||||
|  |                     - self.scale * (STATS_TEXT_MARGIN + STATS_TEXT_WIDTH), | ||||||
|                     start_y=self.matrix.bg.bottom + 1.5 * (2 * y + 1) * font_size, |                     start_y=self.matrix.bg.bottom + 1.5 * (2 * y + 1) * font_size, | ||||||
|                     color=TEXT_COLOR, |                     color=TEXT_COLOR, | ||||||
|                     font_size=font_size, |                     font_size=font_size, | ||||||
| @ -450,11 +505,11 @@ AGAIN""".format( | |||||||
|                 ) |                 ) | ||||||
|  |  | ||||||
|         highlight_text = { |         highlight_text = { | ||||||
|             State.STARTING: self.start_text, |             Phase.STARTING: self.start_text, | ||||||
|             State.PLAYING: self.highlight_texts[0] if self.highlight_texts else "", |             Phase.FALLING: self.highlight_texts[0] if self.highlight_texts else "", | ||||||
|             State.PAUSED: self.pause_text, |             Phase.PAUSED: self.pause_text, | ||||||
|             State.OVER: self.game_over_text, |             Phase.OVER: self.game_over_text, | ||||||
|         }.get(self.state, "") |         }.get(self.phase, "") | ||||||
|         if highlight_text: |         if highlight_text: | ||||||
|             arcade.draw_text( |             arcade.draw_text( | ||||||
|                 text=highlight_text, |                 text=highlight_text, | ||||||
| @ -500,7 +555,11 @@ AGAIN""".format( | |||||||
|  |  | ||||||
|         self.matrix.sprites.resize(self.scale) |         self.matrix.sprites.resize(self.scale) | ||||||
|  |  | ||||||
|         for tetromino in [self.held.piece, self.matrix.piece, self.matrix.ghost] + self.next.pieces: |         for tetromino in [ | ||||||
|  |             self.held.piece, | ||||||
|  |             self.matrix.piece, | ||||||
|  |             self.matrix.ghost, | ||||||
|  |         ] + self.next.pieces: | ||||||
|             if tetromino: |             if tetromino: | ||||||
|                 tetromino.sprites.resize(self.scale) |                 tetromino.sprites.resize(self.scale) | ||||||
|  |  | ||||||
| @ -555,7 +614,7 @@ High score could not be saved: | |||||||
|  |  | ||||||
|     def on_close(self): |     def on_close(self): | ||||||
|         self.save_high_score() |         self.save_high_score() | ||||||
|         if self.play_music: |         if self.music: | ||||||
|             self.music.pause() |             self.music.pause() | ||||||
|         super().on_close() |         super().on_close() | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										13
									
								
								test.py
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								test.py
									
									
									
									
									
								
							| @ -1,6 +1,6 @@ | |||||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
| from TetrArcade import TetrArcade, State, MinoSprite | from TetrArcade import TetrArcade, Phase, MinoSprite | ||||||
| from tetrislogic import Mino, Color, Coord | from tetrislogic import Mino, Color, Coord | ||||||
|  |  | ||||||
| game = TetrArcade() | game = TetrArcade() | ||||||
| @ -14,12 +14,17 @@ game.move_left() | |||||||
| game.pause() | game.pause() | ||||||
| game.resume() | game.resume() | ||||||
| game.move_right() | game.move_right() | ||||||
| game.swap() | game.hold() | ||||||
| game.rotate_clockwise() | game.rotate_clockwise() | ||||||
|  | game.hold() | ||||||
| game.rotate_counter() | game.rotate_counter() | ||||||
| for i in range(12): | for i in range(22): | ||||||
|     game.soft_drop() |     game.soft_drop() | ||||||
|  |     game.on_draw() | ||||||
|  | game.lock_phase() | ||||||
|  | game.hold() | ||||||
| game.matrix.sprites.refresh() | game.matrix.sprites.refresh() | ||||||
| game.on_draw() | game.on_draw() | ||||||
| while game.state != State.OVER: | while game.phase != Phase.OVER: | ||||||
|     game.hard_drop() |     game.hard_drop() | ||||||
|  | game.on_draw() | ||||||
|  | |||||||
| @ -1,5 +1,15 @@ | |||||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||||
| from .consts import LINES, COLLUMNS, NEXT_PIECES | from .consts import LINES, COLLUMNS, NEXT_PIECES | ||||||
| from .utils import Movement, Rotation, Color, Coord | from .utils import Movement, Rotation, Color, Coord, Phase | ||||||
| from .tetromino import Mino, Tetromino | from .tetromino import ( | ||||||
| from .tetrislogic import TetrisLogic, State, Matrix |     Mino, | ||||||
|  |     Tetromino, | ||||||
|  |     I_Tetrimino, | ||||||
|  |     J_Tetrimino, | ||||||
|  |     L_Tetrimino, | ||||||
|  |     O_Tetrimino, | ||||||
|  |     S_Tetrimino, | ||||||
|  |     T_Tetrimino, | ||||||
|  |     Z_Tetrimino, | ||||||
|  | ) | ||||||
|  | from .tetrislogic import TetrisLogic, Matrix | ||||||
|  | |||||||
| @ -15,5 +15,3 @@ AUTOREPEAT_PERIOD = 0.010  # Official : 0.010 s | |||||||
|  |  | ||||||
| # Piece init coord | # Piece init coord | ||||||
| MATRIX_PIECE_COORD = Coord(4, LINES) | MATRIX_PIECE_COORD = Coord(4, LINES) | ||||||
| NEXT_PIECE_COORDS = [Coord(COLLUMNS + 4, LINES - 4 * n - 3) for n in range(NEXT_PIECES)] |  | ||||||
| HELD_PIECE_COORD = Coord(-5, LINES - 3) |  | ||||||
|  | |||||||
| @ -1,9 +1,8 @@ | |||||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||||
| import random |  | ||||||
| import pickle | import pickle | ||||||
|  |  | ||||||
| from .utils import Coord, Movement, Rotation, T_Spin | from .utils import Coord, Movement, Rotation, T_Spin, Phase | ||||||
| from .tetromino import Tetromino, T_Tetrimino, I_Tetrimino | from .tetromino import Tetromino, T_Tetrimino | ||||||
| from .consts import ( | from .consts import ( | ||||||
|     LINES, |     LINES, | ||||||
|     COLLUMNS, |     COLLUMNS, | ||||||
| @ -13,8 +12,6 @@ from .consts import ( | |||||||
|     AUTOREPEAT_DELAY, |     AUTOREPEAT_DELAY, | ||||||
|     AUTOREPEAT_PERIOD, |     AUTOREPEAT_PERIOD, | ||||||
|     MATRIX_PIECE_COORD, |     MATRIX_PIECE_COORD, | ||||||
|     NEXT_PIECE_COORDS, |  | ||||||
|     HELD_PIECE_COORD, |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -22,16 +19,7 @@ LINES_CLEAR_NAME = "LINES_CLEAR_NAME" | |||||||
| CRYPT_KEY = 987943759387540938469837689379857347598347598379584857934579343 | CRYPT_KEY = 987943759387540938469837689379857347598347598379584857934579343 | ||||||
|  |  | ||||||
|  |  | ||||||
| class State: |  | ||||||
|  |  | ||||||
|     STARTING = "STARTING" |  | ||||||
|     PLAYING = "PLAYING" |  | ||||||
|     PAUSED = "PAUSED" |  | ||||||
|     OVER = "OVER" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PieceContainer: | class PieceContainer: | ||||||
|  |  | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         self.piece = None |         self.piece = None | ||||||
|  |  | ||||||
| @ -41,12 +29,12 @@ class HoldQueue(PieceContainer): | |||||||
|  |  | ||||||
|  |  | ||||||
| class Matrix(list, PieceContainer): | class Matrix(list, PieceContainer): | ||||||
|  |  | ||||||
|     def __init__(self, lines, collumns): |     def __init__(self, lines, collumns): | ||||||
|         list.__init__(self) |         list.__init__(self) | ||||||
|         PieceContainer.__init__(self) |         PieceContainer.__init__(self) | ||||||
|         self.lines = lines |         self.lines = lines | ||||||
|         self.collumns = collumns |         self.collumns = collumns | ||||||
|  |         self.ghost = None | ||||||
|  |  | ||||||
|     def reset(self): |     def reset(self): | ||||||
|         self.clear() |         self.clear() | ||||||
| @ -57,11 +45,12 @@ class Matrix(list, PieceContainer): | |||||||
|         self.append([None for x in range(self.collumns)]) |         self.append([None for x in range(self.collumns)]) | ||||||
|  |  | ||||||
|     def cell_is_free(self, coord): |     def cell_is_free(self, coord): | ||||||
|         return 0 <= coord.x < self.collumns and 0 <= coord.y and not self[coord.y][coord.x] |         return ( | ||||||
|  |             0 <= coord.x < self.collumns and 0 <= coord.y and not self[coord.y][coord.x] | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |  | ||||||
| class NextQueue(PieceContainer): | class NextQueue(PieceContainer): | ||||||
|  |  | ||||||
|     def __init__(self, nb_pieces): |     def __init__(self, nb_pieces): | ||||||
|         super().__init__() |         super().__init__() | ||||||
|         self.nb_pieces = nb_pieces |         self.nb_pieces = nb_pieces | ||||||
| @ -70,6 +59,14 @@ class NextQueue(PieceContainer): | |||||||
|  |  | ||||||
| class Stats: | class Stats: | ||||||
|  |  | ||||||
|  |     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 _get_score(self): |     def _get_score(self): | ||||||
|         return self._score |         return self._score | ||||||
|  |  | ||||||
| @ -106,6 +103,32 @@ class Stats: | |||||||
|     def update_time(self): |     def update_time(self): | ||||||
|         self.time += 1 |         self.time += 1 | ||||||
|  |  | ||||||
|  |     def pattern_phase(self, t_spin, lines_cleared): | ||||||
|  |         pattern_name = [] | ||||||
|  |         pattern_score = 0 | ||||||
|  |         combo_score = 0 | ||||||
|  |  | ||||||
|  |         if t_spin: | ||||||
|  |             pattern_name.append(t_spin) | ||||||
|  |         if lines_cleared: | ||||||
|  |             pattern_name.append(self.SCORES[lines_cleared][LINES_CLEAR_NAME]) | ||||||
|  |             self.combo += 1 | ||||||
|  |         else: | ||||||
|  |             self.combo = -1 | ||||||
|  |  | ||||||
|  |         if lines_cleared or t_spin: | ||||||
|  |             pattern_score = self.SCORES[lines_cleared][t_spin] | ||||||
|  |             self.goal -= pattern_score | ||||||
|  |             pattern_score *= 100 * self.level | ||||||
|  |             pattern_name = "\n".join(pattern_name) | ||||||
|  |  | ||||||
|  |         if self.combo >= 1: | ||||||
|  |             combo_score = (20 if lines_cleared == 1 else 50) * self.combo * self.level | ||||||
|  |  | ||||||
|  |         self.score += pattern_score + combo_score | ||||||
|  |  | ||||||
|  |         return pattern_name, pattern_score, self.combo, combo_score | ||||||
|  |  | ||||||
|  |  | ||||||
| class TetrisLogic: | class TetrisLogic: | ||||||
|  |  | ||||||
| @ -115,22 +138,13 @@ class TetrisLogic: | |||||||
|     AUTOREPEAT_DELAY = AUTOREPEAT_DELAY |     AUTOREPEAT_DELAY = AUTOREPEAT_DELAY | ||||||
|     AUTOREPEAT_PERIOD = AUTOREPEAT_PERIOD |     AUTOREPEAT_PERIOD = AUTOREPEAT_PERIOD | ||||||
|     MATRIX_PIECE_COORD = MATRIX_PIECE_COORD |     MATRIX_PIECE_COORD = MATRIX_PIECE_COORD | ||||||
|     NEXT_PIECE_COORDS = NEXT_PIECE_COORDS |  | ||||||
|     HELD_PIECE_COORD = HELD_PIECE_COORD |  | ||||||
|     random_bag = [] |  | ||||||
|  |  | ||||||
|     def __init__( |     def __init__(self, lines=LINES, collumns=COLLUMNS, next_pieces=NEXT_PIECES): | ||||||
|             self, |  | ||||||
|             lines=LINES, |  | ||||||
|             collumns=COLLUMNS, |  | ||||||
|             next_pieces=NEXT_PIECES, |  | ||||||
|             ): |  | ||||||
|         self.stats = Stats() |         self.stats = Stats() | ||||||
|         self.load_high_score() |         self.load_high_score() | ||||||
|         self.state = State.STARTING |         self.phase = Phase.STARTING | ||||||
|         self.held = HoldQueue() |         self.held = HoldQueue() | ||||||
|         self.matrix = Matrix(lines, collumns) |         self.matrix = Matrix(lines, collumns) | ||||||
|         self.matrix.ghost = None |  | ||||||
|         self.next = NextQueue(next_pieces) |         self.next = NextQueue(next_pieces) | ||||||
|         self.autorepeatable_actions = (self.move_left, self.move_right, self.soft_drop) |         self.autorepeatable_actions = (self.move_left, self.move_right, self.soft_drop) | ||||||
|         self.pressed_actions = [] |         self.pressed_actions = [] | ||||||
| @ -142,35 +156,19 @@ class TetrisLogic: | |||||||
|         self.auto_repeat = False |         self.auto_repeat = False | ||||||
|  |  | ||||||
|         self.matrix.reset() |         self.matrix.reset() | ||||||
|         self.next.pieces = [self.new_piece() for n in range(self.next.nb_pieces)] |         self.next.pieces = [Tetromino() for n in range(self.next.nb_pieces)] | ||||||
|         self.held.piece = None |         self.held.piece = None | ||||||
|         self.state = State.PLAYING |  | ||||||
|         self.start(self.stats.update_time, 1) |         self.start(self.stats.update_time, 1) | ||||||
|  |  | ||||||
|         self.on_new_game() |         self.on_new_game(self.next.pieces) | ||||||
|  |  | ||||||
|         self.new_level() |         self.new_level() | ||||||
|  |  | ||||||
|     def on_new_game(self): |     def on_new_game(self, next_pieces): | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     def new_piece(self): |  | ||||||
|         if not self.random_bag: |  | ||||||
|             self.random_bag = list(Tetromino.shapes) |  | ||||||
|             random.shuffle(self.random_bag) |  | ||||||
|         piece = self.random_bag.pop()() |  | ||||||
|         self.on_new_piece(piece) |  | ||||||
|         return piece |  | ||||||
|  |  | ||||||
|     def on_new_piece(self, piece): |  | ||||||
|         pass |         pass | ||||||
|  |  | ||||||
|     def new_level(self): |     def new_level(self): | ||||||
|         self.stats.new_level() |         self.stats.new_level() | ||||||
|         self.restart(self.fall, self.stats.fall_delay) |  | ||||||
|  |  | ||||||
|         self.on_new_level(self.stats.level) |         self.on_new_level(self.stats.level) | ||||||
|  |  | ||||||
|         self.generation_phase() |         self.generation_phase() | ||||||
|  |  | ||||||
|     def on_new_level(self, level): |     def on_new_level(self, level): | ||||||
| @ -178,122 +176,133 @@ class TetrisLogic: | |||||||
|  |  | ||||||
|     def generation_phase(self): |     def generation_phase(self): | ||||||
|         self.matrix.piece = self.next.pieces.pop(0) |         self.matrix.piece = self.next.pieces.pop(0) | ||||||
|  |         self.next.pieces.append(Tetromino()) | ||||||
|         self.matrix.piece.coord = self.MATRIX_PIECE_COORD |         self.matrix.piece.coord = self.MATRIX_PIECE_COORD | ||||||
|         self.matrix.ghost = self.matrix.piece.ghost() |         self.matrix.ghost = self.matrix.piece.ghost() | ||||||
|         self.move_ghost() |  | ||||||
|         self.next.pieces.append(self.new_piece()) |  | ||||||
|         self.next.pieces[-1].coord = self.NEXT_PIECE_COORDS[-1] |  | ||||||
|         for tetromino, coord in zip(self.next.pieces, self.NEXT_PIECE_COORDS): |  | ||||||
|             tetromino.coord = coord |  | ||||||
|  |  | ||||||
|         self.on_generation_phase(self.matrix.piece) |         self.on_generation_phase( | ||||||
|         self.on_falling_phase() |             self.matrix, self.matrix.piece, self.matrix.ghost, self.next.pieces | ||||||
|  |         ) | ||||||
|         if not self.can_move(self.matrix.piece.coord, (mino.coord for mino in self.matrix.piece)): |         if not self.move(Movement.DOWN): | ||||||
|             self.game_over() |             self.game_over() | ||||||
|  |         else: | ||||||
|  |             self.restart(self.fall, self.stats.fall_delay) | ||||||
|  |             self.falling_phase() | ||||||
|  |  | ||||||
|     def on_generation_phase(self, piece): |     def on_generation_phase(self, matrix, falling_piece, ghost_piece, next_pieces): | ||||||
|         pass |         pass | ||||||
|  |  | ||||||
|     def on_falling_phase(self): |     def falling_phase(self): | ||||||
|         pass |         self.phase = Phase.FALLING | ||||||
|  |  | ||||||
|     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.matrix.ghost.coord = self.matrix.piece.coord |         self.matrix.ghost.coord = self.matrix.piece.coord | ||||||
|         for ghost_mino, current_mino in zip(self.matrix.ghost, self.matrix.piece): |         for ghost_mino, current_mino in zip(self.matrix.ghost, self.matrix.piece): | ||||||
|             ghost_mino.coord = current_mino.coord |             ghost_mino.coord = current_mino.coord | ||||||
|         while self.can_move(self.matrix.ghost.coord + Movement.DOWN, (mino.coord for mino in self.matrix.ghost)): |         while self.space_to_move( | ||||||
|  |             self.matrix.ghost.coord + Movement.DOWN, | ||||||
|  |             (mino.coord for mino in self.matrix.ghost), | ||||||
|  |         ): | ||||||
|             self.matrix.ghost.coord += Movement.DOWN |             self.matrix.ghost.coord += Movement.DOWN | ||||||
|  |  | ||||||
|     def soft_drop(self): |         self.on_falling_phase(self.matrix.piece, self.matrix.ghost) | ||||||
|         moved = self.move(Movement.DOWN) |  | ||||||
|         if moved: |  | ||||||
|             self.stats.score += 1 |  | ||||||
|         return moved |  | ||||||
|  |  | ||||||
|     def hard_drop(self): |     def on_falling_phase(self, falling_piece, ghost_piece): | ||||||
|         while self.move(Movement.DOWN, prelock=False): |         pass | ||||||
|             self.stats.score += 2 |  | ||||||
|         self.lock() |  | ||||||
|  |  | ||||||
|     def fall(self): |     def fall(self): | ||||||
|         self.move(Movement.DOWN) |         self.move(Movement.DOWN) | ||||||
|  |  | ||||||
|     def move(self, movement, prelock=True): |     def move(self, movement, rotated_coords=None, lock=True): | ||||||
|         potential_coord = self.matrix.piece.coord + movement |         potential_coord = self.matrix.piece.coord + movement | ||||||
|         if self.can_move(potential_coord, (mino.coord for mino in self.matrix.piece)): |         if self.space_to_move( | ||||||
|             if self.matrix.piece.prelocked: |             potential_coord, | ||||||
|                 self.restart(self.lock, self.stats.lock_delay) |             rotated_coords or (mino.coord for mino in self.matrix.piece), | ||||||
|  |         ): | ||||||
|             self.matrix.piece.coord = potential_coord |             self.matrix.piece.coord = potential_coord | ||||||
|             if not movement == Movement.DOWN: |             if rotated_coords: | ||||||
|                 self.matrix.piece.last_rotation_point = None |  | ||||||
|             self.move_ghost() |  | ||||||
|             self.on_moved(movement) |  | ||||||
|             return True |  | ||||||
|         else: |  | ||||||
|             if prelock and not self.matrix.piece.prelocked and movement == Movement.DOWN: |  | ||||||
|                 self.matrix.piece.prelocked = True |  | ||||||
|                 self.start(self.lock, self.stats.lock_delay) |  | ||||||
|                 self.on_lock_phase() |  | ||||||
|             return False |  | ||||||
|  |  | ||||||
|     def on_moved(self, movement): |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     def rotate(self, rotation): |  | ||||||
|         rotated_coords = tuple(Coord(rotation * mino.coord.y, -rotation * mino.coord.x) for mino in self.matrix.piece) |  | ||||||
|         for rotation_point, liberty_degree in enumerate(self.matrix.piece.SRS[rotation][self.matrix.piece.orientation], start=1): |  | ||||||
|             potential_coord = self.matrix.piece.coord + liberty_degree |  | ||||||
|             if self.can_move(potential_coord, rotated_coords): |  | ||||||
|                 if self.matrix.piece.prelocked: |  | ||||||
|                     self.restart(self.lock, self.stats.lock_delay) |  | ||||||
|                 self.matrix.piece.coord = potential_coord |  | ||||||
|                 for mino, coord in zip(self.matrix.piece, rotated_coords): |                 for mino, coord in zip(self.matrix.piece, rotated_coords): | ||||||
|                     mino.coord = coord |                     mino.coord = coord | ||||||
|                 self.matrix.piece.orientation = (self.matrix.piece.orientation + rotation) % 4 |             else: | ||||||
|  |                 if movement != Movement.DOWN: | ||||||
|  |                     self.matrix.piece.last_rotation_point = None | ||||||
|  |                 if self.phase == Phase.LOCK: | ||||||
|  |                     self.restart(self.pattern_phase, self.stats.lock_delay) | ||||||
|  |             self.falling_phase() | ||||||
|  |             return True | ||||||
|  |         else: | ||||||
|  |             if lock and self.phase != Phase.LOCK and movement == Movement.DOWN: | ||||||
|  |                 self.lock_phase() | ||||||
|  |             return False | ||||||
|  |  | ||||||
|  |     def lock_phase(self): | ||||||
|  |         self.phase = Phase.LOCK | ||||||
|  |         self.on_lock_phase(self.matrix.piece) | ||||||
|  |         self.start(self.pattern_phase, self.stats.lock_delay) | ||||||
|  |  | ||||||
|  |     def on_lock_phase(self, locked_piece): | ||||||
|  |         pass | ||||||
|  |  | ||||||
|  |     def space_to_move(self, potential_coord, minoes_coord): | ||||||
|  |         return all( | ||||||
|  |             self.matrix.cell_is_free(potential_coord + mino_coord) | ||||||
|  |             for mino_coord in minoes_coord | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def rotate(self, rotation): | ||||||
|  |         rotated_coords = tuple( | ||||||
|  |             Coord(rotation * mino.coord.y, -rotation * mino.coord.x) | ||||||
|  |             for mino in self.matrix.piece | ||||||
|  |         ) | ||||||
|  |         for rotation_point, liberty_degree in enumerate( | ||||||
|  |             self.matrix.piece.SRS[rotation][self.matrix.piece.orientation], start=1 | ||||||
|  |         ): | ||||||
|  |             if self.move(liberty_degree, rotated_coords): | ||||||
|  |                 self.matrix.piece.orientation = ( | ||||||
|  |                     self.matrix.piece.orientation + rotation | ||||||
|  |                 ) % 4 | ||||||
|                 self.matrix.piece.last_rotation_point = rotation_point |                 self.matrix.piece.last_rotation_point = rotation_point | ||||||
|                 self.move_ghost() |  | ||||||
|                 self.on_rotated(rotation) |  | ||||||
|                 return True |                 return True | ||||||
|         else: |         else: | ||||||
|             return False |             return False | ||||||
|  |  | ||||||
|     def on_rotated(self, direction): |     def hold(self): | ||||||
|  |         if not self.matrix.piece.hold_enabled: | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         self.matrix.piece.hold_enabled = False | ||||||
|  |         self.stop(self.pattern_phase) | ||||||
|  |         self.stop(self.fall) | ||||||
|  |         self.matrix.piece, self.held.piece = self.held.piece, self.matrix.piece | ||||||
|  |  | ||||||
|  |         self.on_hold(self.held.piece) | ||||||
|  |  | ||||||
|  |         if self.matrix.piece: | ||||||
|  |             self.matrix.piece.coord = self.MATRIX_PIECE_COORD | ||||||
|  |             self.matrix.ghost = self.matrix.piece.ghost() | ||||||
|  |             self.falling_phase() | ||||||
|  |         else: | ||||||
|  |             self.generation_phase() | ||||||
|  |  | ||||||
|  |     def on_hold(self, held_piece): | ||||||
|         pass |         pass | ||||||
|  |  | ||||||
|     def on_lock_phase(self): |     def pattern_phase(self): | ||||||
|         pass |         self.phase = Phase.PATTERN | ||||||
|  |  | ||||||
|     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): |  | ||||||
|         self.matrix.piece.prelocked = False |         self.matrix.piece.prelocked = False | ||||||
|         self.stop(self.lock) |         self.stop(self.pattern_phase) | ||||||
|  |         self.stop(self.fall) | ||||||
|  |  | ||||||
|         # Piece unlocked |         # Piece unlocked | ||||||
|         if self.can_move(self.matrix.piece.coord + Movement.DOWN, (mino.coord for mino in self.matrix.piece)): |         if self.space_to_move( | ||||||
|  |             self.matrix.piece.coord + Movement.DOWN, | ||||||
|  |             (mino.coord for mino in self.matrix.piece), | ||||||
|  |         ): | ||||||
|             return |             return | ||||||
|  |  | ||||||
|         # Game over |         # Game over | ||||||
|         if all((mino.coord + self.matrix.piece.coord).y >= self.matrix.lines for mino in self.matrix.piece): |         if all( | ||||||
|  |             (mino.coord + self.matrix.piece.coord).y >= self.matrix.lines | ||||||
|  |             for mino in self.matrix.piece | ||||||
|  |         ): | ||||||
|             self.game_over() |             self.game_over() | ||||||
|             return |             return | ||||||
|  |  | ||||||
| @ -305,13 +314,13 @@ class TetrisLogic: | |||||||
|             coord = mino.coord + self.matrix.piece.coord |             coord = mino.coord + self.matrix.piece.coord | ||||||
|             if coord.y <= self.matrix.lines + 3: |             if coord.y <= self.matrix.lines + 3: | ||||||
|                 self.matrix[coord.y][coord.x] = mino |                 self.matrix[coord.y][coord.x] = mino | ||||||
|         self.on_locked(self.matrix.piece) |         self.on_locked(self.matrix, self.matrix.piece) | ||||||
|  |  | ||||||
|         self.pattern_phase() |  | ||||||
|  |  | ||||||
|     def pattern_phase(self): |  | ||||||
|         # T-Spin |         # T-Spin | ||||||
|         if type(self.matrix.piece) == T_Tetrimino and self.matrix.piece.last_rotation_point is not None: |         if ( | ||||||
|  |             type(self.matrix.piece) == T_Tetrimino | ||||||
|  |             and self.matrix.piece.last_rotation_point is not None | ||||||
|  |         ): | ||||||
|             a = self.is_t_slot(0) |             a = self.is_t_slot(0) | ||||||
|             b = self.is_t_slot(1) |             b = self.is_t_slot(1) | ||||||
|             c = self.is_t_slot(3) |             c = self.is_t_slot(3) | ||||||
| @ -330,38 +339,16 @@ class TetrisLogic: | |||||||
|         for y, line in reversed(list(enumerate(self.matrix))): |         for y, line in reversed(list(enumerate(self.matrix))): | ||||||
|             if all(mino for mino in line): |             if all(mino for mino in line): | ||||||
|                 lines_cleared += 1 |                 lines_cleared += 1 | ||||||
|                 self.on_line_remove(y) |                 self.on_line_remove(self.matrix, y) | ||||||
|                 self.matrix.pop(y) |                 self.matrix.pop(y) | ||||||
|                 self.matrix.append_new_line() |                 self.matrix.append_new_line() | ||||||
|         if lines_cleared: |         if lines_cleared: | ||||||
|             self.stats.lines_cleared += lines_cleared |             self.stats.lines_cleared += lines_cleared | ||||||
|  |  | ||||||
|         # Scoring |         pattern_name, pattern_score, nb_combo, combo_score = self.stats.pattern_phase( | ||||||
|         lock_strings = [] |             t_spin, lines_cleared | ||||||
|         lock_score = 0 |         ) | ||||||
|  |         self.on_pattern_phase(pattern_name, pattern_score, nb_combo, combo_score) | ||||||
|         if t_spin: |  | ||||||
|             lock_strings.append(t_spin) |  | ||||||
|         if lines_cleared: |  | ||||||
|             lock_strings.append(self.SCORES[lines_cleared][LINES_CLEAR_NAME]) |  | ||||||
|             self.stats.combo += 1 |  | ||||||
|         else: |  | ||||||
|             self.stats.combo = -1 |  | ||||||
|  |  | ||||||
|         if lines_cleared or t_spin: |  | ||||||
|             ds = self.SCORES[lines_cleared][t_spin] |  | ||||||
|             self.stats.goal -= ds |  | ||||||
|             ds *= 100 * self.stats.level |  | ||||||
|             lock_score += ds |  | ||||||
|             lock_strings.append(str(ds)) |  | ||||||
|             self.show_text("\n".join(lock_strings)) |  | ||||||
|  |  | ||||||
|         if self.stats.combo >= 1: |  | ||||||
|             ds = (20 if lines_cleared == 1 else 50) * self.stats.combo * self.stats.level |  | ||||||
|             lock_score += ds |  | ||||||
|             self.show_text("COMBO x{:n}\n{:n}".format(self.stats.combo, ds)) |  | ||||||
|  |  | ||||||
|         self.stats.score += lock_score |  | ||||||
|  |  | ||||||
|         if self.stats.goal <= 0: |         if self.stats.goal <= 0: | ||||||
|             self.new_level() |             self.new_level() | ||||||
| @ -371,62 +358,73 @@ class TetrisLogic: | |||||||
|         if self.pressed_actions: |         if self.pressed_actions: | ||||||
|             self.start(self.repeat_action, self.AUTOREPEAT_DELAY) |             self.start(self.repeat_action, self.AUTOREPEAT_DELAY) | ||||||
|  |  | ||||||
|     def on_locked(piece): |     def on_locked(self, matrix, locked_piece): | ||||||
|         pass |         pass | ||||||
|  |  | ||||||
|     def on_line_remove(self, y): |     def on_line_remove(self, matrix, y): | ||||||
|         pass |         pass | ||||||
|  |  | ||||||
|     def can_move(self, potential_coord, minoes_coords): |     def on_pattern_phase(self, pattern_name, pattern_score, nb_combo, combo_score): | ||||||
|         return all(self.matrix.cell_is_free(potential_coord + mino_coord) for mino_coord in minoes_coords) |         pass | ||||||
|  |  | ||||||
|  |     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 soft_drop(self): | ||||||
|  |         moved = self.move(Movement.DOWN) | ||||||
|  |         if moved: | ||||||
|  |             self.stats.score += 1 | ||||||
|  |         return moved | ||||||
|  |  | ||||||
|  |     def hard_drop(self): | ||||||
|  |         while self.move(Movement.DOWN, lock=False): | ||||||
|  |             self.stats.score += 2 | ||||||
|  |         self.pattern_phase() | ||||||
|  |  | ||||||
|     T_SLOT_COORDS = (Coord(-1, 1), Coord(1, 1), Coord(-1, 1), Coord(-1, -1)) |     T_SLOT_COORDS = (Coord(-1, 1), Coord(1, 1), Coord(-1, 1), Coord(-1, -1)) | ||||||
|  |  | ||||||
|     def is_t_slot(self, n): |     def is_t_slot(self, n): | ||||||
|         t_slot_coord = self.matrix.piece.coord + self.T_SLOT_COORDS[(self.matrix.piece.orientation + n) % 4] |         t_slot_coord = ( | ||||||
|  |             self.matrix.piece.coord | ||||||
|  |             + self.T_SLOT_COORDS[(self.matrix.piece.orientation + n) % 4] | ||||||
|  |         ) | ||||||
|         return not self.matrix.cell_is_free(t_slot_coord) |         return not self.matrix.cell_is_free(t_slot_coord) | ||||||
|  |  | ||||||
|     def swap(self): |  | ||||||
|         if self.matrix.piece.hold_enabled: |  | ||||||
|             self.matrix.piece.hold_enabled = False |  | ||||||
|             self.matrix.piece.prelocked = False |  | ||||||
|             self.stop(self.lock) |  | ||||||
|             self.matrix.piece, self.held.piece = self.held.piece, self.matrix.piece |  | ||||||
|             self.held.piece.coord = self.HELD_PIECE_COORD |  | ||||||
|             if type(self.held.piece) == I_Tetrimino: |  | ||||||
|                 self.held.piece.coord += Movement.LEFT |  | ||||||
|             for mino, coord in zip(self.held.piece, self.held.piece.MINOES_COORDS): |  | ||||||
|                 mino.coord = coord |  | ||||||
|  |  | ||||||
|             if self.matrix.piece: |  | ||||||
|                 self.matrix.piece.coord = self.MATRIX_PIECE_COORD |  | ||||||
|                 self.matrix.ghost = self.matrix.piece.ghost() |  | ||||||
|                 self.move_ghost() |  | ||||||
|             else: |  | ||||||
|                 self.generation_phase() |  | ||||||
|  |  | ||||||
|     def pause(self): |     def pause(self): | ||||||
|         self.state = State.PAUSED |         self.phase = Phase.PAUSED | ||||||
|         self.stop_all() |         self.stop_all() | ||||||
|         self.pressed_actions = [] |         self.pressed_actions = [] | ||||||
|         self.auto_repeat = False |         self.auto_repeat = False | ||||||
|         self.stop(self.repeat_action) |         self.stop(self.repeat_action) | ||||||
|  |  | ||||||
|     def resume(self): |     def resume(self): | ||||||
|         self.state = State.PLAYING |         self.phase = Phase.FALLING | ||||||
|         self.start(self.fall, self.stats.fall_delay) |         self.start(self.fall, self.stats.fall_delay) | ||||||
|         if self.matrix.piece.prelocked: |         if self.phase == Phase.LOCK: | ||||||
|             self.start(self.lock, self.stats.lock_delay) |             self.start(self.pattern_phase, self.stats.lock_delay) | ||||||
|         self.start(self.stats.update_time, 1) |         self.start(self.stats.update_time, 1) | ||||||
|  |  | ||||||
|     def game_over(self): |     def game_over(self): | ||||||
|         self.state = State.OVER |         self.phase = Phase.OVER | ||||||
|         self.stop_all() |         self.stop_all() | ||||||
|         self.save_high_score() |         self.save_high_score() | ||||||
|  |         self.on_game_over() | ||||||
|  |  | ||||||
|  |     def on_game_over(self): | ||||||
|  |         pass | ||||||
|  |  | ||||||
|     def stop_all(self): |     def stop_all(self): | ||||||
|         self.stop(self.fall) |         self.stop(self.fall) | ||||||
|         self.stop(self.lock) |         self.stop(self.pattern_phase) | ||||||
|         self.stop(self.stats.update_time) |         self.stop(self.stats.update_time) | ||||||
|  |  | ||||||
|     def do_action(self, action): |     def do_action(self, action): | ||||||
|  | |||||||
| @ -1,4 +1,6 @@ | |||||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||||
|  | import random | ||||||
|  |  | ||||||
| from .utils import Coord, Rotation, Color | from .utils import Coord, Rotation, Color | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -14,9 +16,20 @@ class MetaTetromino(type): | |||||||
|         Tetromino.shapes.append(cls) |         Tetromino.shapes.append(cls) | ||||||
|  |  | ||||||
|  |  | ||||||
| class Tetromino(list): | class Tetromino: | ||||||
|  |  | ||||||
|     shapes = [] |     shapes = [] | ||||||
|  |     random_bag = [] | ||||||
|  |  | ||||||
|  |     def __new__(cls): | ||||||
|  |         if not cls.random_bag: | ||||||
|  |             cls.random_bag = list(cls.shapes) | ||||||
|  |             random.shuffle(cls.random_bag) | ||||||
|  |         return cls.random_bag.pop()() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TetrominoBase(list): | ||||||
|  |  | ||||||
|     # Super rotation system |     # Super rotation system | ||||||
|     SRS = { |     SRS = { | ||||||
|         Rotation.CLOCKWISE: ( |         Rotation.CLOCKWISE: ( | ||||||
| @ -44,7 +57,7 @@ class Tetromino(list): | |||||||
|         return type(self)() |         return type(self)() | ||||||
|  |  | ||||||
|  |  | ||||||
| class O_Tetrimino(Tetromino, metaclass=MetaTetromino): | class O_Tetrimino(TetrominoBase, metaclass=MetaTetromino): | ||||||
|  |  | ||||||
|     SRS = { |     SRS = { | ||||||
|         Rotation.CLOCKWISE: (tuple(), tuple(), tuple(), tuple()), |         Rotation.CLOCKWISE: (tuple(), tuple(), tuple(), tuple()), | ||||||
| @ -57,7 +70,7 @@ class O_Tetrimino(Tetromino, metaclass=MetaTetromino): | |||||||
|         return False |         return False | ||||||
|  |  | ||||||
|  |  | ||||||
| class I_Tetrimino(Tetromino, metaclass=MetaTetromino): | class I_Tetrimino(TetrominoBase, metaclass=MetaTetromino): | ||||||
|  |  | ||||||
|     SRS = { |     SRS = { | ||||||
|         Rotation.CLOCKWISE: ( |         Rotation.CLOCKWISE: ( | ||||||
| @ -77,31 +90,31 @@ class I_Tetrimino(Tetromino, metaclass=MetaTetromino): | |||||||
|     MINOES_COLOR = Color.CYAN |     MINOES_COLOR = Color.CYAN | ||||||
|  |  | ||||||
|  |  | ||||||
| class T_Tetrimino(Tetromino, metaclass=MetaTetromino): | class T_Tetrimino(TetrominoBase, metaclass=MetaTetromino): | ||||||
|  |  | ||||||
|     MINOES_COORDS = (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 = Color.MAGENTA |     MINOES_COLOR = Color.MAGENTA | ||||||
|  |  | ||||||
|  |  | ||||||
| class L_Tetrimino(Tetromino, metaclass=MetaTetromino): | class L_Tetrimino(TetrominoBase, metaclass=MetaTetromino): | ||||||
|  |  | ||||||
|     MINOES_COORDS = (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 = Color.ORANGE |     MINOES_COLOR = Color.ORANGE | ||||||
|  |  | ||||||
|  |  | ||||||
| class J_Tetrimino(Tetromino, metaclass=MetaTetromino): | class J_Tetrimino(TetrominoBase, metaclass=MetaTetromino): | ||||||
|  |  | ||||||
|     MINOES_COORDS = (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 = Color.BLUE |     MINOES_COLOR = Color.BLUE | ||||||
|  |  | ||||||
|  |  | ||||||
| class S_Tetrimino(Tetromino, metaclass=MetaTetromino): | class S_Tetrimino(TetrominoBase, metaclass=MetaTetromino): | ||||||
|  |  | ||||||
|     MINOES_COORDS = (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 = Color.GREEN |     MINOES_COLOR = Color.GREEN | ||||||
|  |  | ||||||
|  |  | ||||||
| class Z_Tetrimino(Tetromino, metaclass=MetaTetromino): | class Z_Tetrimino(TetrominoBase, metaclass=MetaTetromino): | ||||||
|  |  | ||||||
|     MINOES_COORDS = (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 = Color.RED |     MINOES_COLOR = Color.RED | ||||||
|  | |||||||
| @ -37,3 +37,13 @@ class Color: | |||||||
|     ORANGE = 4 |     ORANGE = 4 | ||||||
|     RED = 5 |     RED = 5 | ||||||
|     YELLOW = 6 |     YELLOW = 6 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Phase: | ||||||
|  |  | ||||||
|  |     STARTING = "STARTING" | ||||||
|  |     FALLING = "FALLING" | ||||||
|  |     LOCK = "LOCK" | ||||||
|  |     PATTERN = "PATTERN" | ||||||
|  |     PAUSED = "PAUSED" | ||||||
|  |     OVER = "OVER" | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user