diff --git a/TetrArcade.py b/TetrArcade.py index 07b0c43..6eb6461 100644 --- a/TetrArcade.py +++ b/TetrArcade.py @@ -20,7 +20,7 @@ import os import itertools import configparser -from tetrislogic import TetrisLogic, Color, Phase, Coord, I_Tetrimino, Movement +from tetrislogic import TetrisLogic, Color, Coord, I_Tetrimino, Movement, AbstractTimer # Constants @@ -92,8 +92,6 @@ TEXTURES = arcade.load_textures( ((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()} -NORMAL_TEXTURE = 0 -LOCKED_TEXTURE = 1 # Music MUSIC_DIR = os.path.join(RESOURCES_DIR, "musics") @@ -119,10 +117,55 @@ else: ) USER_PROFILE_DIR = os.path.join(USER_PROFILE_DIR, "TetrArcade") 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, "config.ini") + + +class Texture: + + NORMAL = 0 + LOCKED = 1 + + +class State: + + STARTING = 0 + PLAYING = 1 + PAUSED = 2 + OVER = 3 + + +class Timer(AbstractTimer): + + def __init__(self): + self.tasks = {} + + def postpone(self, task, delay): + _task = lambda _: task() + self.tasks[task] = _task + pyglet.clock.schedule_once(_task, delay) + + def cancel(self, task): + try: + _task = self.tasks[task] + except KeyError: + pass + else: + arcade.unschedule(_task) + del self.tasks[task] + + def reset(self, task, delay): + try: + _task = self.tasks[task] + except KeyError: + _task = lambda _: task() + self.tasks[task] = _task + else: + arcade.unschedule(_task) + pyglet.clock.schedule_once(_task, delay) class MinoSprite(arcade.Sprite): + def __init__(self, mino, window, alpha): super().__init__() self.alpha = alpha @@ -131,15 +174,15 @@ class MinoSprite(arcade.Sprite): self.append_texture(TEXTURES[Color.LOCKED]) self.set_texture(0) - def update(self, x, y, texture=0): + def update(self, x, y): 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.set_texture(texture) class MinoesSprites(arcade.SpriteList): + def resize(self, scale): for sprite in self: sprite.scale = scale @@ -147,6 +190,7 @@ class MinoesSprites(arcade.SpriteList): class TetrominoSprites(MinoesSprites): + def __init__(self, tetromino, window, alpha=NORMAL_ALPHA): super().__init__() self.tetromino = tetromino @@ -155,13 +199,18 @@ class TetrominoSprites(MinoesSprites): mino.sprite = MinoSprite(mino, window, alpha) self.append(mino.sprite) - def update(self, texture=NORMAL_TEXTURE): + def update(self): for mino in self.tetromino: coord = mino.coord + self.tetromino.coord - mino.sprite.update(coord.x, coord.y, texture) + mino.sprite.update(coord.x, coord.y) + + def set_texture(self, texture): + for mino in self.tetromino: + mino.sprite.set_texture(texture) class MatrixSprites(MinoesSprites): + def __init__(self, matrix): super().__init__() self.matrix = matrix @@ -180,10 +229,12 @@ class MatrixSprites(MinoesSprites): class TetrArcade(TetrisLogic, arcade.Window): + + timer = Timer() + def __init__(self): locale.setlocale(locale.LC_ALL, "") self.highlight_texts = [] - self.tasks = {} self.conf = configparser.ConfigParser() if self.conf.read(CONF_PATH): @@ -229,6 +280,8 @@ class TetrArcade(TetrisLogic, arcade.Window): else: self.music = None + self.state = State.STARTING + def new_conf(self): self.conf["WINDOW"] = { "width": WINDOW_WIDTH, @@ -263,13 +316,13 @@ class TetrArcade(TetrisLogic, arcade.Window): for action, key in self.conf["KEYBOARD"].items(): self.conf["KEYBOARD"][action] = key.upper() self.key_map = { - Phase.STARTING: { + State.STARTING: { getattr(arcade.key, self.conf["KEYBOARD"]["start"]): self.new_game, getattr( arcade.key, self.conf["KEYBOARD"]["fullscreen"] ): self.toggle_fullscreen, }, - Phase.FALLING: { + State.PLAYING: { getattr(arcade.key, self.conf["KEYBOARD"]["move left"]): self.move_left, getattr( arcade.key, self.conf["KEYBOARD"]["move right"] @@ -288,32 +341,13 @@ class TetrArcade(TetrisLogic, arcade.Window): arcade.key, self.conf["KEYBOARD"]["fullscreen"] ): self.toggle_fullscreen, }, - 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: { + State.PAUSED: { getattr(arcade.key, self.conf["KEYBOARD"]["pause"]): self.resume, getattr( arcade.key, self.conf["KEYBOARD"]["fullscreen"] ): self.toggle_fullscreen, }, - Phase.OVER: { + State.OVER: { getattr(arcade.key, self.conf["KEYBOARD"]["start"]): self.new_game, getattr( arcade.key, self.conf["KEYBOARD"]["fullscreen"] @@ -366,6 +400,8 @@ AGAIN""".format( self.music.seek(0) self.music.play() + self.state = State.PLAYING + def on_new_level(self, level): self.show_text("LEVEL\n{:n}".format(level)) @@ -377,6 +413,12 @@ AGAIN""".format( for piece, coord in zip(next_pieces, NEXT_PIECES_COORDS): piece.coord = coord + def on_falling_phase(self, falling_piece, ghost_piece): + falling_piece.sprites.set_texture(Texture.NORMAL) + + def on_lock_phase(self, falling_piece): + falling_piece.sprites.set_texture(Texture.LOCKED) + def on_locks_down(self, matrix, locked_piece): for mino in locked_piece: matrix.sprites.append(mino.sprite) @@ -417,59 +459,57 @@ AGAIN""".format( if combo_score: self.show_text("COMBO x{:n}\n{:n}".format(nb_combo, combo_score)) - def on_hold(self, held_piece, falling_piece, ghost_piece): + def on_hold(self, held_piece): held_piece.coord = HELD_PIECE_COORD if type(held_piece) == I_Tetrimino: held_piece.coord += Movement.LEFT - ghost_piece.sprites = TetrominoSprites(ghost_piece, self, GHOST_ALPHA) - def pause(self): - super().pause() + def on_pause(self): + self.state = State.PAUSED if self.music: self.music.pause() def resume(self): - super().resume() if self.music: self.music.play() + self.state = State.PLAYING def on_game_over(self): + self.state = State.OVER if self.music: self.music.pause() def on_key_press(self, key, modifiers): - for key_or_modifier in (key, modifiers): - try: - action = self.key_map[self.phase][key_or_modifier] - except KeyError: - pass - else: - self.do_action(action) + try: + action = self.key_map[self.state][key] + except KeyError: + return + else: + self.do_action(action) def on_key_release(self, key, modifiers): - for key_or_modifier in (key, modifiers): - try: - action = self.key_map[self.phase][key_or_modifier] - except KeyError: - pass - else: - self.remove_action(action) + try: + action = self.key_map[self.state][key] + except KeyError: + return + else: + self.remove_action(action) def show_text(self, text): self.highlight_texts.append(text) - self.restart(self.del_highlight_text, HIGHLIGHT_TEXT_DISPLAY_DELAY) + self.timer.postpone(self.del_highlight_text, HIGHLIGHT_TEXT_DISPLAY_DELAY) def del_highlight_text(self): if self.highlight_texts: self.highlight_texts.pop(0) else: - self.stop(self.del_highlight_text) + self.timer.cancel(self.del_highlight_text) def on_draw(self): arcade.start_render() self.bg.draw() - if self.phase not in (Phase.STARTING, Phase.PAUSED): + if self.state not in (State.STARTING, State.PAUSED): self.matrix.bg.draw() self.matrix.sprites.draw() @@ -523,11 +563,11 @@ AGAIN""".format( exploding_minoes.draw() highlight_text = { - Phase.STARTING: self.start_text, - Phase.FALLING: self.highlight_texts[0] if self.highlight_texts else "", - Phase.PAUSED: self.pause_text, - Phase.OVER: self.game_over_text, - }.get(self.phase, "") + State.STARTING: self.start_text, + State.PLAYING: self.highlight_texts[0] if self.highlight_texts else "", + State.PAUSED: self.pause_text, + State.OVER: self.game_over_text, + }.get(self.state, "") if highlight_text: arcade.draw_text( text=highlight_text, @@ -598,37 +638,12 @@ High score could not be saved: + str(e) ) - def start(self, task, period): - _task = lambda _: 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] - - def restart(self, task, period): - try: - _task = self.tasks[task] - except KeyError: - _task = lambda _: task() - self.tasks[task] = _task - else: - arcade.unschedule(_task) - arcade.schedule(_task, period) - def update(self, delta_time): for piece in [self.held.piece, self.matrix.ghost] + self.next.pieces: if piece: piece.sprites.update() if self.matrix.piece: - texture = LOCKED_TEXTURE if self.phase == Phase.LOCK else NORMAL_TEXTURE - self.matrix.piece.sprites.update(texture=texture) + self.matrix.piece.sprites.update() for exploding_minoes in self.exploding_minoes: if exploding_minoes: exploding_minoes.update() diff --git a/test.py b/test.py index efbba48..69b05ea 100644 --- a/test.py +++ b/test.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from TetrArcade import TetrArcade, Phase, MinoSprite +from TetrArcade import TetrArcade, MinoSprite, State from tetrislogic import Mino, Color, Coord game = TetrArcade() @@ -15,16 +15,22 @@ game.pause() game.resume() game.move_right() game.hold() +game.update(0) +game.on_draw() game.rotate_clockwise() game.hold() +game.update(0) +game.on_draw() game.rotate_counter() for i in range(22): game.soft_drop() game.on_draw() game.lock_phase() game.hold() +game.update(0) +game.on_draw() game.matrix.sprites.update() game.on_draw() -while game.phase != Phase.OVER: +while game.state != State.OVER: game.hard_drop() game.on_draw() diff --git a/tetrislogic/__init__.py b/tetrislogic/__init__.py index d76deed..bad968a 100644 --- a/tetrislogic/__init__.py +++ b/tetrislogic/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from .consts import LINES, COLLUMNS, NEXT_PIECES -from .utils import Movement, Rotation, Color, Coord, Phase +from .utils import Movement, Rotation, Color, Coord from .tetromino import ( Mino, Tetromino, @@ -12,4 +12,4 @@ from .tetromino import ( T_Tetrimino, Z_Tetrimino, ) -from .tetrislogic import TetrisLogic, Matrix +from .tetrislogic import TetrisLogic, Matrix, AbstractTimer diff --git a/tetrislogic/tetrislogic.py b/tetrislogic/tetrislogic.py index ee20bef..1072962 100644 --- a/tetrislogic/tetrislogic.py +++ b/tetrislogic/tetrislogic.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import pickle -from .utils import Coord, Movement, Rotation, T_Spin, Phase +from .utils import Coord, Movement, Rotation, T_Spin from .tetromino import Tetromino, T_Tetrimino from .consts import ( LINES, @@ -19,6 +19,18 @@ LINES_CLEAR_NAME = "LINES_CLEAR_NAME" CRYPT_KEY = 987943759387540938469837689379857347598347598379584857934579343 +class AbstractTimer: + + def postpone(task, delay): + raise Warning("AbstractTimer.postpone is not implemented.") + + def cancel(self, task): + raise Warning("AbstractTimer.stop is not implemented.") + + def reset(self, task, period): + self.timer.cancel(task) + self.timer.postpone(task, period) + class PieceContainer: def __init__(self): self.piece = None @@ -49,6 +61,18 @@ class Matrix(list, PieceContainer): 0 <= coord.x < self.collumns and 0 <= coord.y and not self[coord.y][coord.x] ) + def space_to_move(self, potential_coord, minoes_coord): + return all( + self.cell_is_free(potential_coord + mino_coord) + for mino_coord in minoes_coord + ) + + def space_to_fall(self): + return self.space_to_move( + self.piece.coord + Movement.DOWN, + (mino.coord for mino in self.piece), + ) + class NextQueue(PieceContainer): def __init__(self, nb_pieces): @@ -133,17 +157,16 @@ class Stats: class TetrisLogic: - LINES = LINES - COLLUMNS = COLLUMNS - NEXT_PIECES = NEXT_PIECES + # These class attributes can be redefined on inheritance AUTOREPEAT_DELAY = AUTOREPEAT_DELAY AUTOREPEAT_PERIOD = AUTOREPEAT_PERIOD MATRIX_PIECE_COORD = MATRIX_PIECE_COORD + timer = AbstractTimer() + def __init__(self, lines=LINES, collumns=COLLUMNS, next_pieces=NEXT_PIECES): self.stats = Stats() self.load_high_score() - self.phase = Phase.STARTING self.held = HoldQueue() self.matrix = Matrix(lines, collumns) self.next = NextQueue(next_pieces) @@ -154,12 +177,11 @@ class TetrisLogic: self.stats.new_game(level) self.pressed_actions = [] - self.auto_repeat = False self.matrix.reset() self.next.pieces = [Tetromino() for n in range(self.next.nb_pieces)] self.held.piece = None - self.start(self.stats.update_time, 1) + self.timer.postpone(self.stats.update_time, 1) self.on_new_game(self.next.pieces) self.new_level() @@ -175,13 +197,15 @@ class TetrisLogic: def on_new_level(self, level): pass - def generation_phase(self): - self.phase = Phase.GENERATION - self.matrix.piece = self.next.pieces.pop(0) - self.next.pieces.append(Tetromino()) + def generation_phase(self, held_piece=None): + if not held_piece: + self.matrix.piece = self.next.pieces.pop(0) + self.next.pieces.append(Tetromino()) self.matrix.piece.coord = self.MATRIX_PIECE_COORD self.matrix.ghost = self.matrix.piece.ghost() self.refresh_ghost() + if self.pressed_actions: + self.timer.postpone(self.repeat_action, self.AUTOREPEAT_DELAY) self.on_generation_phase( self.matrix, self.matrix.piece, self.matrix.ghost, self.next.pieces @@ -195,7 +219,7 @@ class TetrisLogic: 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.space_to_move( + while self.matrix.space_to_move( self.matrix.ghost.coord + Movement.DOWN, (mino.coord for mino in self.matrix.ghost), ): @@ -205,23 +229,26 @@ class TetrisLogic: pass def falling_phase(self): - self.phase = Phase.FALLING - self.stop(self.locks_down) - self.start(self.fall, self.stats.fall_delay) + self.timer.cancel(self.lock_phase) + self.matrix.piece.locked = False + self.timer.postpone(self.lock_phase, self.stats.fall_delay) self.on_falling_phase(self.matrix.piece, self.matrix.ghost) - if self.pressed_actions: - self.start(self.repeat_action, self.AUTOREPEAT_DELAY) def on_falling_phase(self, falling_piece, ghost_piece): pass - def fall(self): - self.move(Movement.DOWN) + def lock_phase(self): + self.matrix.piece.locked = True + if not self.move(Movement.DOWN): + self.locks_down() + + def on_lock_phase(self, falling_piece): + pass def move(self, movement, rotated_coords=None, lock=True): potential_coord = self.matrix.piece.coord + movement portential_minoes_coords = rotated_coords or (mino.coord for mino in self.matrix.piece) - if self.space_to_move(potential_coord, portential_minoes_coords): + if self.matrix.space_to_move(potential_coord, portential_minoes_coords): self.matrix.piece.coord = potential_coord if rotated_coords: for mino, coord in zip(self.matrix.piece, rotated_coords): @@ -229,20 +256,16 @@ class TetrisLogic: self.refresh_ghost() if movement != Movement.DOWN: self.matrix.piece.last_rotation_point = None - if self.space_to_fall: + if self.matrix.space_to_fall: self.falling_phase() else: - self.lock_phase() + self.on_lock_phase(self.matrix.piece) + if self.matrix.piece.locked: + self.timer.reset(self.locks_down, self.stats.lock_delay) return True else: return False - def space_to_fall(self): - return self.space_to_move( - self.matrix.piece.coord + Movement.DOWN, - (mino.coord for mino in self.matrix.piece), - ) - def rotate(self, rotation): rotated_coords = tuple( Coord(rotation * mino.coord.y, -rotation * mino.coord.x) @@ -260,24 +283,7 @@ class TetrisLogic: else: return False - def lock_phase(self): - self.phase = Phase.LOCK - self.on_lock_phase(self.matrix.piece) - self.restart(self.locks_down, 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 locks_down(self): - self.stop(self.locks_down) - self.stop(self.fall) - # Game over if all( (mino.coord + self.matrix.piece.coord).y >= self.matrix.lines @@ -286,9 +292,7 @@ class TetrisLogic: self.game_over() return - if self.pressed_actions: - self.auto_repeat = False - self.stop(self.repeat_action) + self.timer.cancel(self.repeat_action) for mino in self.matrix.piece: coord = mino.coord + self.matrix.piece.coord @@ -297,7 +301,7 @@ class TetrisLogic: self.on_locks_down(self.matrix, self.matrix.piece) - self.phase = Phase.PATTERN + #Pattern phase # T-Spin if ( @@ -326,16 +330,18 @@ class TetrisLogic: if lines_cleared: self.stats.lines_cleared += lines_cleared - self.phase = Phase.ANIMATE + # Animate phase + self.on_animate_phase(self.matrix, self.lines_to_remove) - self.phase = Phase.ELIMINATE + # Eliminate phase self.on_eliminate_phase(self.matrix, self.lines_to_remove) for y in self.lines_to_remove: self.matrix.pop(y) self.matrix.append_new_line() - self.phase = Phase.COMPLETION + # Completion phase + pattern_name, pattern_score, nb_combo, combo_score = self.stats.locks_down( t_spin, lines_cleared ) @@ -379,7 +385,8 @@ class TetrisLogic: return moved def hard_drop(self): - self.stop(self.locks_down) + self.timer.cancel(self.lock_phase) + self.timer.cancel(self.locks_down) while self.move(Movement.DOWN, lock=False): self.stats.score += 2 self.locks_down() @@ -389,23 +396,16 @@ class TetrisLogic: return self.matrix.piece.hold_enabled = False - self.stop(self.locks_down) - self.stop(self.fall) + self.timer.cancel(self.lock_phase) self.matrix.piece, self.held.piece = self.held.piece, self.matrix.piece 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.on_hold(self.held.piece, self.matrix.piece, self.matrix.ghost) - self.falling_phase() - else: - self.generation_phase() - self.on_hold(self.held.piece, self.matrix.piece, self.matrix.ghost) + self.on_hold(self.held.piece) + self.generation_phase(self.matrix.piece) - def on_hold(self, held_piece, falling_piece, ghost_piece): + def on_hold(self, held_piece): pass T_SLOT_COORDS = (Coord(-1, 1), Coord(1, 1), Coord(-1, 1), Coord(-1, -1)) @@ -418,21 +418,25 @@ class TetrisLogic: return not self.matrix.cell_is_free(t_slot_coord) def pause(self): - self.phase = Phase.PAUSED self.stop_all() self.pressed_actions = [] - self.auto_repeat = False - self.stop(self.repeat_action) + self.timer.cancel(self.repeat_action) + self.on_pause() + + def on_pause(self): + pass def resume(self): - self.phase = Phase.FALLING - self.start(self.fall, self.stats.fall_delay) - if self.phase == Phase.LOCK: - self.start(self.locks_down, self.stats.lock_delay) - self.start(self.stats.update_time, 1) + self.timer.postpone(self.lock_phase, self.stats.fall_delay) + if self.matrix.piece.locked: + self.timer.postpone(self.locks_down, self.stats.lock_delay) + self.timer.postpone(self.stats.update_time, 1) + self.on_resume() + + def on_resume(self): + pass def game_over(self): - self.phase = Phase.OVER self.stop_all() self.save_high_score() self.on_game_over() @@ -441,30 +445,26 @@ class TetrisLogic: pass def stop_all(self): - self.stop(self.fall) - self.stop(self.locks_down) - self.stop(self.stats.update_time) + self.timer.cancel(self.lock_phase) + self.timer.cancel(self.locks_down) + self.timer.cancel(self.stats.update_time) def do_action(self, action): action() if action in self.autorepeatable_actions: - self.auto_repeat = False self.pressed_actions.append(action) if action == self.soft_drop: delay = self.stats.fall_delay / 20 else: delay = self.AUTOREPEAT_DELAY - self.restart(self.repeat_action, delay) + self.timer.reset(self.repeat_action, delay) def repeat_action(self): - if self.pressed_actions: - self.pressed_actions[-1]() - if not self.auto_repeat: - self.auto_repeat = True - self.restart(self.repeat_action, self.AUTOREPEAT_PERIOD) - else: - self.auto_repeat = False - self.stop(self.repeat_action) + if not self.pressed_actions: + return + + self.pressed_actions[-1]() + self.timer.postpone(self.repeat_action, self.AUTOREPEAT_PERIOD) def remove_action(self, action): if action in self.autorepeatable_actions: @@ -492,13 +492,3 @@ High score is set to 0""" crypted_high_score = self.stats.high_score ^ CRYPT_KEY crypted_high_score = pickle.dumps(crypted_high_score) return crypted_high_score - - def start(task, period): - raise Warning("TetrisLogic.start is not implemented.") - - def stop(self, task): - raise Warning("TetrisLogic.stop is not implemented.") - - def restart(self, task, period): - self.stop(task) - self.start(task, period) diff --git a/tetrislogic/tetromino.py b/tetrislogic/tetromino.py index aae6e0a..f81a350 100644 --- a/tetrislogic/tetromino.py +++ b/tetrislogic/tetromino.py @@ -51,6 +51,7 @@ class TetrominoBase(list): self.orientation = 0 self.last_rotation_point = None self.hold_enabled = True + self.locked = False def ghost(self): return type(self)() diff --git a/tetrislogic/utils.py b/tetrislogic/utils.py index 26941f4..82200dc 100644 --- a/tetrislogic/utils.py +++ b/tetrislogic/utils.py @@ -37,17 +37,3 @@ class Color: ORANGE = 4 RED = 5 YELLOW = 6 - - -class Phase: - - STARTING = "STARTING" - GENERATION = "GENERATION" - FALLING = "FALLING" - LOCK = "LOCK" - PATTERN = "PATTERN" - ANIMATE = "ANIMATE" - ELIMINATE = "ELIMINATE" - COMPLETION = "COMPLETION" - PAUSED = "PAUSED" - OVER = "OVER"