diff --git a/TetrArcade.py b/TetrArcade.py index 4c41b38..87821db 100644 --- a/TetrArcade.py +++ b/TetrArcade.py @@ -39,12 +39,25 @@ GHOST_ALPHA = 30 MATRIX_BG_ALPHA = 100 BAR_ALPHA = 75 +# Mino size +MINO_SIZE = 20 +MINO_SPRITE_SIZE = 21 + +if getattr(sys, 'frozen', False): + # The application is frozen + DATA_DIR = os.path.dirname(sys.executable) +else: + # The application is not frozen + # Change this bit to match where you store your data files: + DATA_DIR = os.path.dirname(__file__) +DATA_DIR = os.path.join(DATA_DIR, "res") + # Sprites -WINDOW_BG_PATH = "res/bg.jpg" -MATRIX_BG_PATH = "res/matrix.png" -HELD_BG_PATH = "res/held.png" -NEXT_BG_PATH = "res/next.png" -MINOES_SPRITES_PATH = "res/minoes.png" +WINDOW_BG_PATH = os.path.join(DATA_DIR, "bg.jpg") +MATRIX_BG_PATH = os.path.join(DATA_DIR, "matrix.png") +HELD_BG_PATH = os.path.join(DATA_DIR, "held.png") +NEXT_BG_PATH = os.path.join(DATA_DIR, "next.png") +MINOES_SPRITES_PATH = os.path.join(DATA_DIR, "minoes.png") Color.PRELOCKED = 7 MINOES_COLOR_ID = { Color.BLUE: 0, @@ -56,8 +69,6 @@ MINOES_COLOR_ID = { Color.YELLOW: 6, Color.PRELOCKED: 7, } -MINO_SIZE = 20 -MINO_SPRITE_SIZE = 21 TEXTURES = arcade.load_textures( MINOES_SPRITES_PATH, ((i * MINO_SPRITE_SIZE, 0, MINO_SPRITE_SIZE, MINO_SPRITE_SIZE) for i in range(8)) ) @@ -74,13 +85,16 @@ CONF_PATH = os.path.join(USER_PROFILE_DIR, "TetrArcade.ini") # Text TEXT_COLOR = arcade.color.BUBBLES -FONT_NAME = "res/joystix monospace.ttf" +FONT_NAME = os.path.join(DATA_DIR, "joystix monospace.ttf") STATS_TEXT_MARGIN = 40 STATS_TEXT_SIZE = 14 STATS_TEXT_WIDTH = 150 HIGHLIGHT_TEXT_COLOR = arcade.color.BUBBLES HIGHLIGHT_TEXT_SIZE = 20 +# Music +MUSIC_PATH = os.path.join(DATA_DIR, "Tetris - Song A.mp3") + class MinoSprite(arcade.Sprite): def __init__(self, mino, window, alpha): @@ -94,8 +108,8 @@ class MinoSprite(arcade.Sprite): def refresh(self, x, y, prelocked=False): self.scale = self.window.scale size = MINO_SIZE * self.scale - self.left = self.window.matrix_bg.left + x * size - self.bottom = self.window.matrix_bg.bottom + y * size + self.left = self.window.matrix.bg.left + x * size + self.bottom = self.window.matrix.bg.bottom + y * size self.set_texture(prelocked) @@ -166,14 +180,17 @@ class TetrArcade(TetrisLogic, arcade.Window): arcade.set_background_color(BG_COLOR) self.set_minimum_size(WINDOW_MIN_WIDTH, WINDOW_MIN_HEIGHT) self.bg = arcade.Sprite(WINDOW_BG_PATH) - self.matrix_bg = arcade.Sprite(MATRIX_BG_PATH) - self.matrix_bg.alpha = MATRIX_BG_ALPHA - self.held_bg = arcade.Sprite(HELD_BG_PATH) - self.held_bg.alpha = BAR_ALPHA - self.next_bg = arcade.Sprite(NEXT_BG_PATH) - self.next_bg.alpha = BAR_ALPHA + self.matrix.bg = arcade.Sprite(MATRIX_BG_PATH) + self.matrix.bg.alpha = MATRIX_BG_ALPHA + self.held.bg = arcade.Sprite(HELD_BG_PATH) + self.held.bg.alpha = BAR_ALPHA + self.next.bg = arcade.Sprite(NEXT_BG_PATH) + self.next.bg.alpha = BAR_ALPHA self.matrix.sprites = MatrixSprites(self.matrix) self.on_resize(self.init_width, self.init_height) + if self.play_music: + self.music = arcade.Sound(MUSIC_PATH) + self.music_player = None def new_conf(self): self.conf["WINDOW"] = {"width": WINDOW_WIDTH, "height": WINDOW_HEIGHT, "fullscreen": False} @@ -189,6 +206,9 @@ class TetrArcade(TetrisLogic, arcade.Window): "pause": "ESCAPE", "fullscreen": "F11", } + self.conf["MUSIC"] = { + "play": True + } self.conf["AUTO-REPEAT"] = {"delay": 0.3, "period": 0.01} self.load_conf() if not os.path.exists(USER_PROFILE_DIR): @@ -252,48 +272,72 @@ AGAIN""".format( self.conf["KEYBOARD"]["start"] ) + self.play_music = self.conf["MUSIC"].getboolean("play") + def new_game(self): self.highlight_texts = [] super().new_game() + if self.play_music: + if self.music_player: + self.music_player.seek(0) + self.music_player.play() + else: + self.music_player = self.music.player.play() + self.music_player.loop = True def new_tetromino(self): tetromino = super().new_tetromino() tetromino.sprites = TetrominoSprites(tetromino, self) return tetromino - def new_current(self): + def new_matrix_piece(self): self.matrix.sprites = MatrixSprites(self.matrix) - super().new_current() - self.ghost.sprites = TetrominoSprites(self.ghost, self, GHOST_ALPHA) - for tetromino in [self.current, self.ghost] + self.next: + super().new_matrix_piece() + self.matrix.ghost.sprites = TetrominoSprites(self.matrix.ghost, self, GHOST_ALPHA) + for tetromino in [self.matrix.piece, self.matrix.ghost] + self.next.pieces: tetromino.sprites.refresh() def move(self, movement, prelock=True): moved = super().move(movement, prelock) - self.current.sprites.refresh() + self.matrix.piece.sprites.refresh() if moved: - self.ghost.sprites.refresh() + self.matrix.ghost.sprites.refresh() return moved def rotate(self, rotation): rotated = super().rotate(rotation) if rotated: - for tetromino in (self.current, self.ghost): + for tetromino in (self.matrix.piece, self.matrix.ghost): tetromino.sprites.refresh() return rotated def swap(self): super().swap() - self.ghost.sprites = TetrominoSprites(self.ghost, self, GHOST_ALPHA) - for tetromino in [self.held, self.current, self.ghost]: + 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 lock(self): - self.current.prelocked = False - self.current.sprites.refresh() + self.matrix.piece.prelocked = False + self.matrix.piece.sprites.refresh() super().lock() + def pause(self): + super().pause() + if self.play_music: + self.music_player.pause() + + def resume(self): + super().resume() + if self.play_music: + self.music_player.play() + + def game_over(self): + super().game_over() + if self.play_music: + self.music_player.pause() + def on_key_press(self, key, modifiers): for key_or_modifier in (key, modifiers): try: @@ -327,12 +371,12 @@ AGAIN""".format( self.bg.draw() if self.state in (State.PLAYING, State.OVER): - self.matrix_bg.draw() - self.held_bg.draw() - self.next_bg.draw() + self.matrix.bg.draw() + self.held.bg.draw() + self.next.bg.draw() self.matrix.sprites.draw() - for tetromino in [self.held, self.current, self.ghost] + self.next: + for tetromino in [self.held.piece, self.matrix.piece, self.matrix.ghost] + self.next.pieces: if tetromino: tetromino.sprites.draw() @@ -341,8 +385,8 @@ AGAIN""".format( for y, text in enumerate(("TIME", "LINES", "GOAL", "LEVEL", "HIGH SCORE", "SCORE")): arcade.draw_text( text=text, - 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_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, color=TEXT_COLOR, font_size=font_size, align="right", @@ -361,8 +405,8 @@ AGAIN""".format( ): arcade.draw_text( text=text, - start_x=self.matrix_bg.left - STATS_TEXT_MARGIN * self.scale, - start_y=self.matrix_bg.bottom + 3 * y * font_size, + start_x=self.matrix.bg.left - STATS_TEXT_MARGIN * self.scale, + start_y=self.matrix.bg.bottom + 3 * y * font_size, color=TEXT_COLOR, font_size=font_size, align="right", @@ -379,8 +423,8 @@ AGAIN""".format( if highlight_text: arcade.draw_text( text=highlight_text, - start_x=self.matrix_bg.center_x, - start_y=self.matrix_bg.center_y, + start_x=self.matrix.bg.center_x, + start_y=self.matrix.bg.center_y, color=HIGHLIGHT_TEXT_COLOR, font_size=HIGHLIGHT_TEXT_SIZE * self.scale, align="center", @@ -405,23 +449,23 @@ AGAIN""".format( self.bg.center_x = center_x self.bg.center_y = center_y - self.matrix_bg.scale = self.scale - self.matrix_bg.center_x = center_x - self.matrix_bg.center_y = center_y - self.matrix_bg.left = int(self.matrix_bg.left) - self.matrix_bg.top = int(self.matrix_bg.top) + self.matrix.bg.scale = self.scale + self.matrix.bg.center_x = center_x + self.matrix.bg.center_y = center_y + self.matrix.bg.left = int(self.matrix.bg.left) + self.matrix.bg.top = int(self.matrix.bg.top) - self.held_bg.scale = self.scale - self.held_bg.right = self.matrix_bg.left - self.held_bg.top = self.matrix_bg.top + self.held.bg.scale = self.scale + self.held.bg.right = self.matrix.bg.left + self.held.bg.top = self.matrix.bg.top - self.next_bg.scale = self.scale - self.next_bg.left = self.matrix_bg.right - self.next_bg.top = self.matrix_bg.top + self.next.bg.scale = self.scale + self.next.bg.left = self.matrix.bg.right + self.next.bg.top = self.matrix.bg.top self.matrix.sprites.resize(self.scale) - for tetromino in [self.held, self.current, self.ghost] + self.next: + for tetromino in [self.held.piece, self.matrix.piece, self.matrix.ghost] + self.next.pieces: if tetromino: tetromino.sprites.resize(self.scale) @@ -476,6 +520,8 @@ High score could not be saved: def on_close(self): self.save_high_score() + if self.play_music: + self.music_player.pause() super().on_close() diff --git a/res/Tetris - Song A.mp3 b/res/Tetris - Song A.mp3 new file mode 100644 index 0000000..ee27d32 Binary files /dev/null and b/res/Tetris - Song A.mp3 differ diff --git a/setup.py b/setup.py index a760b2c..40383d8 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ options = { } setup( name = "TetrArcade", - version = "0.1", + version = "0.2-dev", description = "Tetris clone", author = "AdrienMalin", executables = [executable], diff --git a/test.py b/test.py index 3e9e918..40d1eb1 100644 --- a/test.py +++ b/test.py @@ -6,6 +6,7 @@ game = TetrArcade() game.new_game() game.move_left() game.move_right() +game.swap() game.rotate_clockwise() game.rotate_counter() for i in range(12): diff --git a/tetrislogic/tetrislogic.py b/tetrislogic/tetrislogic.py index 4216770..57af958 100644 --- a/tetrislogic/tetrislogic.py +++ b/tetrislogic/tetrislogic.py @@ -3,7 +3,7 @@ import random import pickle from .utils import Coord, Movement, Rotation, T_Spin -from .tetromino import Tetromino, T, I +from .tetromino import Tetromino, T_Tetrimino, I_Tetrimino from .consts import ( NB_LINES, NB_COLS, @@ -30,11 +30,33 @@ class State: OVER = "OVER" -class Matrix(list): +class PieceContainer: + + def __init__(self): + self.piece = None + + +class HoldQueue(PieceContainer): + pass + + +class Matrix(list, PieceContainer): + + def __init__(self, *args, **kargs): + list.__init__(self, *args, **kargs) + PieceContainer.__init__(self) + def cell_is_free(self, coord): return 0 <= coord.x < NB_COLS and 0 <= coord.y and not self[coord.y][coord.x] +class NextQueue(PieceContainer): + + def __init__(self): + super().__init__() + self.pieces = [] + + class TetrisLogic: NB_LINES = NB_LINES @@ -52,11 +74,10 @@ class TetrisLogic: def __init__(self): self.load_high_score() self.state = State.STARTING + self.held = HoldQueue() self.matrix = Matrix() - self.next = [] - self.current = None - self.ghost = None - self.held = None + self.matrix.ghost = None + self.next = NextQueue() self.time = 0 self.autorepeatable_actions = (self.move_left, self.move_right, self.soft_drop) self.pressed_actions = [] @@ -88,8 +109,8 @@ class TetrisLogic: self.matrix.clear() for y in range(self.NB_LINES + 3): self.append_new_line_to_matrix() - self.next = [self.new_tetromino() for n in range(self.NB_NEXT)] - self.held = None + self.next.pieces = [self.new_tetromino() for n in range(self.NB_NEXT)] + self.held.piece = None self.state = State.PLAYING self.start(self.update_time, 1) @@ -113,20 +134,19 @@ class TetrisLogic: 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_matrix_piece() - self.new_current() - - def new_current(self): - self.current = self.next.pop(0) - self.current.coord = self.CURRENT_COORD - self.ghost = self.current.ghost() + def new_matrix_piece(self): + self.matrix.piece = self.next.pieces.pop(0) + self.matrix.piece.coord = self.CURRENT_COORD + self.matrix.ghost = self.matrix.piece.ghost() self.move_ghost() - self.next.append(self.new_tetromino()) - self.next[-1].coord = self.NEXT_COORDS[-1] - for tetromino, coord in zip(self.next, self.NEXT_COORDS): + self.next.pieces.append(self.new_tetromino()) + self.next.pieces[-1].coord = self.NEXT_COORDS[-1] + for tetromino, coord in zip(self.next.pieces, self.NEXT_COORDS): tetromino.coord = coord - if not self.can_move(self.current.coord, (mino.coord for mino in self.current)): + if not self.can_move(self.matrix.piece.coord, (mino.coord for mino in self.matrix.piece)): self.game_over() def move_left(self): @@ -142,11 +162,11 @@ class TetrisLogic: 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): + self.matrix.ghost.coord = self.matrix.piece.coord + for ghost_mino, current_mino in zip(self.matrix.ghost, self.matrix.piece): 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 + while self.can_move(self.matrix.ghost.coord + Movement.DOWN, (mino.coord for mino in self.matrix.ghost)): + self.matrix.ghost.coord += Movement.DOWN def soft_drop(self): moved = self.move(Movement.DOWN) @@ -163,33 +183,33 @@ class TetrisLogic: 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: + potential_coord = self.matrix.piece.coord + movement + if self.can_move(potential_coord, (mino.coord for mino in self.matrix.piece)): + if self.matrix.piece.prelocked: self.restart(self.lock, self.lock_delay) - self.current.coord = potential_coord + self.matrix.piece.coord = potential_coord if not movement == Movement.DOWN: - self.current.last_rotation_point = None + self.matrix.piece.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 + if prelock and not self.matrix.piece.prelocked and movement == Movement.DOWN: + self.matrix.piece.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 + 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.current.prelocked: + if self.matrix.piece.prelocked: self.restart(self.lock, self.lock_delay) - self.current.coord = potential_coord - for mino, coord in zip(self.current, rotated_coords): + self.matrix.piece.coord = potential_coord + for mino, coord in zip(self.matrix.piece, rotated_coords): mino.coord = coord - self.current.orientation = (self.current.orientation + rotation) % 4 - self.current.last_rotation_point = rotation_point + self.matrix.piece.orientation = (self.matrix.piece.orientation + rotation) % 4 + self.matrix.piece.last_rotation_point = rotation_point self.move_ghost() return True else: @@ -204,15 +224,15 @@ class TetrisLogic: ) def lock(self): - self.current.prelocked = False + self.matrix.piece.prelocked = False self.stop(self.lock) # Piece unlocked - if self.can_move(self.current.coord + Movement.DOWN, (mino.coord for mino in self.current)): + if self.can_move(self.matrix.piece.coord + Movement.DOWN, (mino.coord for mino in self.matrix.piece)): return # Game over - if all((mino.coord + self.current.coord).y >= self.NB_LINES for mino in self.current): + if all((mino.coord + self.matrix.piece.coord).y >= self.NB_LINES for mino in self.matrix.piece): self.game_over() return @@ -221,12 +241,12 @@ class TetrisLogic: self.restart(self.repeat_action, self.AUTOREPEAT_DELAY) # T-Spin - if type(self.current) == T and self.current.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) 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)): + if self.matrix.piece.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 @@ -235,8 +255,8 @@ class TetrisLogic: else: t_spin = T_Spin.NONE - for mino in self.current: - coord = mino.coord + self.current.coord + for mino in self.matrix.piece: + coord = mino.coord + self.matrix.piece.coord del mino.coord if coord.y <= self.NB_LINES + 3: self.matrix[coord.y][coord.x] = mino @@ -281,7 +301,7 @@ class TetrisLogic: if self.goal <= 0: self.new_level() else: - self.new_current() + self.new_matrix_piece() 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) @@ -289,28 +309,27 @@ class TetrisLogic: 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] + 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) def swap(self): - if self.current.hold_enabled: - self.current.hold_enabled = False - self.current.prelocked = False + if self.matrix.piece.hold_enabled: + self.matrix.piece.hold_enabled = False + self.matrix.piece.prelocked = False self.stop(self.lock) - self.current, self.held = self.held, self.current - if type(self.held) == I: - self.held.coord = self.HELD_COORD + Movement.LEFT - else: - self.held.coord = self.HELD_COORD - for mino, coord in zip(self.held, self.held.MINOES_COORDS): + self.matrix.piece, self.held.piece = self.held.piece, self.matrix.piece + self.held.piece.coord = self.HELD_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.current: - self.current.coord = self.CURRENT_COORD - self.ghost = self.current.ghost() + if self.matrix.piece: + self.matrix.piece.coord = self.CURRENT_COORD + self.matrix.ghost = self.matrix.piece.ghost() self.move_ghost() else: - self.new_current() + self.new_matrix_piece() def pause(self): self.state = State.PAUSED @@ -322,7 +341,7 @@ class TetrisLogic: def resume(self): self.state = State.PLAYING self.start(self.fall, self.fall_delay) - if self.current.prelocked: + if self.matrix.piece.prelocked: self.start(self.lock, self.lock_delay) self.start(self.update_time, 1) diff --git a/tetrislogic/tetromino.py b/tetrislogic/tetromino.py index 5401b96..aad8cdb 100644 --- a/tetrislogic/tetromino.py +++ b/tetrislogic/tetromino.py @@ -44,7 +44,7 @@ class Tetromino(list): return type(self)() -class O(Tetromino, metaclass=MetaTetromino): +class O_Tetrimino(Tetromino, metaclass=MetaTetromino): SRS = { Rotation.CLOCKWISE: (tuple(), tuple(), tuple(), tuple()), @@ -57,7 +57,7 @@ class O(Tetromino, metaclass=MetaTetromino): return False -class I(Tetromino, metaclass=MetaTetromino): +class I_Tetrimino(Tetromino, metaclass=MetaTetromino): SRS = { Rotation.CLOCKWISE: ( @@ -77,31 +77,31 @@ class I(Tetromino, metaclass=MetaTetromino): MINOES_COLOR = Color.CYAN -class T(Tetromino, metaclass=MetaTetromino): +class T_Tetrimino(Tetromino, metaclass=MetaTetromino): MINOES_COORDS = (Coord(-1, 0), Coord(0, 0), Coord(0, 1), Coord(1, 0)) MINOES_COLOR = Color.MAGENTA -class L(Tetromino, metaclass=MetaTetromino): +class L_Tetrimino(Tetromino, metaclass=MetaTetromino): MINOES_COORDS = (Coord(-1, 0), Coord(0, 0), Coord(1, 0), Coord(1, 1)) MINOES_COLOR = Color.ORANGE -class J(Tetromino, metaclass=MetaTetromino): +class J_Tetrimino(Tetromino, metaclass=MetaTetromino): MINOES_COORDS = (Coord(-1, 1), Coord(-1, 0), Coord(0, 0), Coord(1, 0)) MINOES_COLOR = Color.BLUE -class S(Tetromino, metaclass=MetaTetromino): +class S_Tetrimino(Tetromino, metaclass=MetaTetromino): MINOES_COORDS = (Coord(-1, 0), Coord(0, 0), Coord(0, 1), Coord(1, 1)) MINOES_COLOR = Color.GREEN -class Z(Tetromino, metaclass=MetaTetromino): +class Z_Tetrimino(Tetromino, metaclass=MetaTetromino): MINOES_COORDS = (Coord(-1, 1), Coord(0, 1), Coord(0, 0), Coord(1, 0)) MINOES_COLOR = Color.RED