diff --git a/tetrarcade.py b/tetrarcade.py index 06a456a..ffe98a2 100644 --- a/tetrarcade.py +++ b/tetrarcade.py @@ -13,7 +13,7 @@ python -m pip install --user arcade """ ) -from tetris import Tetris, Status +from tetris import Tetris, Status, Scheduler # Constants @@ -35,6 +35,10 @@ TEXT_MARGIN = 40 FONT_SIZE = 10 HIGHLIGHT_TEXT_FONT_SIZE = 20 TEXT_HEIGHT = 13.2 +START_TEXT = """PRESS +[ENTER] +TO +START""" STATS_TEXT = """SCORE HIGH SCORE TIME @@ -89,14 +93,40 @@ GHOST_ALPHA = 50 MATRIX_SRITE_ALPHA = 100 +class ArcadeScheduler(Scheduler): + + def __init__(self): + self._tasks = {} + + def start(self, task, period): + def _task(delta_time): + task() + self._tasks[task] = _task + arcade.schedule(_task, period) + + def stop(self, task): + try: + _task = self._tasks[task] + except KeyError: + pass + else: + arcade.unschedule(_task) + del self._tasks[task] + + class TetrArcade(Tetris, arcade.Window): + scheduler = ArcadeScheduler() + def __init__(self): super().__init__() locale.setlocale(locale.LC_ALL, '') self.actions = { + Status.STARTING: { + arcade.key.ENTER: self.new_game + }, Status.PLAYING: { arcade.key.LEFT: self.move_left, arcade.key.NUM_4: self.move_left, @@ -171,7 +201,7 @@ class TetrArcade(Tetris, arcade.Window): self.reload_matrix() if self.pressed_actions: self.stop_autorepeat() - arcade.schedule(self.repeat_action, AUTOREPEAT_DELAY) + self.scheduler.start(self.repeat_action, AUTOREPEAT_DELAY) def lock(self, _=0): super().lock() @@ -191,7 +221,7 @@ class TetrArcade(Tetris, arcade.Window): def game_over(self): super().game_over() - self.unschedule(self.repeat_action) + self.scheduler.stop(self.repeat_action) def on_key_press(self, key, modifiers): for key_or_modifier in (key, modifiers): @@ -204,7 +234,7 @@ class TetrArcade(Tetris, arcade.Window): if action in self.autorepeatable_actions: self.stop_autorepeat() self.pressed_actions.append(action) - arcade.schedule(self.repeat_action, AUTOREPEAT_DELAY) + self.scheduler.start(self.repeat_action, AUTOREPEAT_DELAY) def on_key_release(self, key, modifiers): try: @@ -220,31 +250,32 @@ class TetrArcade(Tetris, arcade.Window): else: if not self.pressed_actions: self.stop_autorepeat() - arcade.schedule(self.repeat_action, AUTOREPEAT_DELAY) + self.scheduler.start(self.repeat_action, AUTOREPEAT_DELAY) - def repeat_action(self, delta_time=0): + def repeat_action(self, _=0): if self.pressed_actions: self.pressed_actions[-1]() if not self.auto_repeat: self.auto_repeat = True - arcade.unschedule(self.repeat_action) - arcade.schedule(self.repeat_action, AUTOREPEAT_INTERVAL) + self.scheduler.stop(self.repeat_action) + self.scheduler.start(self.repeat_action, AUTOREPEAT_INTERVAL) else: self.auto_repeat = False - arcade.unschedule(self.repeat_action) + self.scheduler.stop(self.repeat_action) def stop_autorepeat(self): self.auto_repeat = False - self.unschedule(self.repeat_action) + self.scheduler.stop(self.repeat_action) def show_text(self, text): self.highlight_texts.append(text) - self.schedule(self.del_highlight_text, HIGHLIGHT_TEXT_DISPLAY_DELAY) + self.scheduler.start(self.del_highlight_text, HIGHLIGHT_TEXT_DISPLAY_DELAY) - def del_highlight_text(self, _=0): - self.highlight_texts.pop(0) - if not self.highlight_texts: - self.unschedule(self.del_highlight_text) + def del_highlight_text(self): + if self.highlight_texts: + self.highlight_texts.pop(0) + else: + self.scheduler.stop(self.del_highlight_text) def reload_piece(self, piece): piece_sprites = arcade.SpriteList() @@ -296,8 +327,9 @@ class TetrArcade(Tetris, arcade.Window): def on_draw(self): arcade.start_render() self.bg_sprite.draw() - self.matrix_sprite.draw() - if not self.status == Status.PAUSED: + + if self.status in (Status.PLAYING, Status.OVER): + self.matrix_sprite.draw() self.matrix_minoes_sprites.draw() self.update_piece(self.held_piece, self.held_piece_sprites) @@ -324,34 +356,36 @@ class TetrArcade(Tetris, arcade.Window): mino_sprite.bottom = self.matrix_sprite.bottom + mino_position.y*(mino_sprite.height-1) self.next_pieces_sprites.draw() - arcade.render_text( - self.general_text, - self.matrix_sprite.left - TEXT_MARGIN, - self.matrix_sprite.bottom - ) - t = time.localtime(self.time) - for y, text in enumerate( - ( - "{:n}".format(self.nb_lines), - "{:n}".format(self.goal), - "{:n}".format(self.level), - "{:02d}:{:02d}:{:02d}".format(t.tm_hour-1, t.tm_min, t.tm_sec), - "{:n}".format(self.high_score), - "{:n}".format(self.score) - ), - start=14 - ): - arcade.draw_text( - text = text, - start_x = self.matrix_sprite.left - TEXT_MARGIN, - start_y = self.matrix_sprite.bottom + y*TEXT_HEIGHT, - color = TEXT_COLOR, - font_size = FONT_SIZE, - align = 'right', - font_name = FONT_NAME, - anchor_x = 'right' + arcade.render_text( + self.general_text, + self.matrix_sprite.left - TEXT_MARGIN, + self.matrix_sprite.bottom ) + t = time.localtime(self.time) + for y, text in enumerate( + ( + "{:n}".format(self.nb_lines), + "{:n}".format(self.goal), + "{:n}".format(self.level), + "{:02d}:{:02d}:{:02d}".format(t.tm_hour-1, t.tm_min, t.tm_sec), + "{:n}".format(self.high_score), + "{:n}".format(self.score) + ), + start=14 + ): + arcade.draw_text( + text = text, + start_x = self.matrix_sprite.left - TEXT_MARGIN, + start_y = self.matrix_sprite.bottom + y*TEXT_HEIGHT, + color = TEXT_COLOR, + font_size = FONT_SIZE, + align = 'right', + font_name = FONT_NAME, + anchor_x = 'right' + ) + highlight_text = { + Status.STARTING: START_TEXT, Status.PLAYING: self.highlight_texts[0] if self.highlight_texts else "", Status.PAUSED: PAUSE_TEXT, Status.OVER: GAME_OVER_TEXT @@ -380,16 +414,9 @@ class TetrArcade(Tetris, arcade.Window): self.matrix_sprite.top = int(self.matrix_sprite.top) self.reload_matrix() - def schedule(self, task, period): - arcade.schedule(task, period) - - def unschedule(self, task): - arcade.unschedule(task) - def main(): - tetrarcade = TetrArcade() - tetrarcade.new_game() + TetrArcade() arcade.run() if __name__ == "__main__": diff --git a/tetris.py b/tetris.py index 15826ee..c21008e 100644 --- a/tetris.py +++ b/tetris.py @@ -25,10 +25,10 @@ class Coord: # Piece init position MATRIX_PIECE_INIT_POSITION = Coord(4, NB_LINES) NEXT_PIECES_POSITIONS = [ - Coord(NB_COLS+3, NB_LINES-4*n-3) + Coord(NB_COLS+6, NB_LINES-4*n-3) for n in range(NB_NEXT_PIECES) ] -HELD_PIECE_POSITION = Coord(-4, NB_LINES-3) +HELD_PIECE_POSITION = Coord(-7, NB_LINES-3) HELD_I_POSITION = Coord(-5, NB_LINES-3) @@ -167,8 +167,18 @@ class Tetromino: return cls.random_bag.pop()() +class Scheduler: + + def start(task, period): + raise NotImplementedError + + def stop(self, task): + raise NotImplementedError + + class Tetris(): + T_SLOT = (Coord(-1, 1), Coord(1, 1), Coord(1, -1), Coord(-1, -1)) SCORES = ( {"name": "", T_Spin.NO_T_SPIN: 0, T_Spin.MINI_T_SPIN: 1, T_Spin.T_SPIN: 4}, @@ -177,6 +187,7 @@ class Tetris(): {"name": "TRIPLE", T_Spin.NO_T_SPIN: 5, T_Spin.T_SPIN: 16}, {"name": "TETRIS", T_Spin.NO_T_SPIN: 8} ) + scheduler = Scheduler() def __init__(self): self.high_score = 0 @@ -205,7 +216,7 @@ class Tetris(): self.current_piece = None self.held_piece = None self.status = Status.PLAYING - self.schedule(self.clock, 1) + self.scheduler.start(self.clock, 1) self.new_level() def new_level(self): @@ -216,7 +227,7 @@ class Tetris(): 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.schedule(self.drop, self.fall_delay) + self.scheduler.start(self.drop, self.fall_delay) self.new_current_piece() def new_current_piece(self): @@ -250,8 +261,8 @@ class Tetris(): potential_position = self.current_piece.position + movement if self.can_move(potential_position, self.current_piece.minoes_positions): if self.current_piece.prelocked: - self.unschedule(self.lock) - self.schedule(self.lock, self.lock_delay) + self.scheduler.stop(self.lock) + self.scheduler.start(self.lock, self.lock_delay) self.current_piece.position = potential_position self.current_piece.last_rotation_point_used = None self.move_ghost() @@ -262,13 +273,13 @@ class Tetris(): and movement == Movement.DOWN ): self.current_piece.prelocked = True - self.schedule(self.lock, self.lock_delay) + self.scheduler.start(self.lock, self.lock_delay) return False - def move_left(self, delta_time=0): + def move_left(self): self.move(Movement.LEFT) - def move_right(self, delta_time=0): + def move_right(self): self.move(Movement.RIGHT) def rotate(self, direction): @@ -283,8 +294,8 @@ class Tetris(): potential_position = self.current_piece.position + liberty_degree if self.can_move(potential_position, rotated_minoes_positions): if self.current_piece.prelocked: - self.unschedule(self.lock) - self.schedule(self.lock, self.lock_delay) + self.scheduler.stop(self.lock) + self.scheduler.start(self.lock, self.lock_delay) self.current_piece.position = potential_position self.current_piece.minoes_positions = rotated_minoes_positions self.current_piece.orientation = ( @@ -296,10 +307,10 @@ class Tetris(): else: return False - def rotate_counterclockwise(self, delta_time=0): + def rotate_counterclockwise(self): self.rotate(Rotation.COUNTERCLOCKWISE) - def rotate_clockwise(self, delta_time=0): + def rotate_clockwise(self): self.rotate(Rotation.CLOCKWISE) def move_ghost(self): @@ -311,7 +322,7 @@ class Tetris(): ): self.ghost_piece.position += Movement.DOWN - def drop(self, _=0): + def drop(self): self.move(Movement.DOWN) def add_to_score(self, ds): @@ -335,6 +346,9 @@ class Tetris(): if self.move(Movement.DOWN): return + self.current_piece.prelocked = False + self.scheduler.stop(self.lock) + if all( (mino_position + self.current_piece.position).y >= NB_LINES for mino_position in self.current_piece.minoes_positions @@ -342,8 +356,6 @@ class Tetris(): self.game_over() return - self.unschedule(self.lock) - for mino_position in self.current_piece.minoes_positions: position = mino_position + self.current_piece.position if position.y <= NB_LINES+3: @@ -409,7 +421,7 @@ class Tetris(): self.add_to_score(lock_score) if self.goal <= 0: - self.unschedule(self.drop) + self.scheduler.stop(self.drop) self.new_level() else: self.new_current_piece() @@ -418,7 +430,7 @@ class Tetris(): if self.current_piece.hold_enabled: self.current_piece.hold_enabled = False self.current_piece.prelocked = False - self.unschedule(self.lock) + self.scheduler.stop(self.lock) self.current_piece, self.held_piece = self.held_piece, self.current_piece if self.held_piece.__class__ == Tetromino.I: self.held_piece.position = HELD_I_POSITION @@ -432,35 +444,28 @@ class Tetris(): else: self.new_current_piece() - def pause(self, _=0): + def pause(self): self.status = Status.PAUSED - self.unschedule(self.drop) - self.unschedule(self.lock) - self.unschedule(self.clock) + self.scheduler.stop(self.drop) + self.scheduler.stop(self.lock) + self.scheduler.stop(self.clock) self.pressed_actions = [] self.stop_autorepeat() - def resume(self, delta_time=0): + def resume(self): self.status = Status.PLAYING - self.schedule(self.drop, self.fall_delay) + self.scheduler.start(self.drop, self.fall_delay) if self.current_piece.prelocked: - self.schedule(self.lock, self.lock_delay) - self.schedule(self.clock, 1) + self.scheduler.start(self.lock, self.lock_delay) + self.scheduler.start(self.clock, 1) def clock(self, delta_time=1): self.time += delta_time def game_over(self): self.status = Status.OVER - self.unschedule(self.lock) - self.unschedule(self.drop) - self.unschedule(self.clock) - - def schedule(task, period): - raise NotImplementedError - - def unschedule(self, task): - raise NotImplementedError + self.scheduler.stop(self.drop) + self.scheduler.stop(self.clock) def show_text(self, text): print(text)