From a062ba1ac1ee6e051386197f3f4b4236a3d804a1 Mon Sep 17 00:00:00 2001 From: adrien Date: Tue, 8 Oct 2019 03:22:49 +0200 Subject: [PATCH] comments --- TetrArcade.py | 42 +++++----- tetrislogic/__init__.py | 4 +- tetrislogic/consts.py | 4 +- tetrislogic/tetrislogic.py | 165 ++++++++++++++++++++++++++++--------- 4 files changed, 150 insertions(+), 65 deletions(-) diff --git a/TetrArcade.py b/TetrArcade.py index 99f38bf..a1554ca 100644 --- a/TetrArcade.py +++ b/TetrArcade.py @@ -20,12 +20,12 @@ import os import itertools import configparser -from tetrislogic import TetrisLogic, Color, Coord, I_Tetrimino, Movement, AbstractTimer +from tetrislogic import TetrisLogic, Color, Coord, I_Tetrimino, Movement, AbstractScheduler # Constants # Matrix -LINES = 20 +ROWS = 20 COLLUMNS = 10 NEXT_PIECES = 6 @@ -37,9 +37,9 @@ AUTOREPEAT_PERIOD = 0.010 PARTICULE_ACCELERATION = 1.1 # Piece init coord -MATRIX_PIECE_COORD = Coord(4, LINES) -NEXT_PIECES_COORDS = [Coord(COLLUMNS + 4, LINES - 4 * n) for n in range(NEXT_PIECES)] -HELD_PIECE_COORD = Coord(-5, LINES) +MATRIX_PIECE_COORD = Coord(4, ROWS) +NEXT_PIECES_COORDS = [Coord(COLLUMNS + 4, ROWS - 4 * n) for n in range(NEXT_PIECES)] +HELD_PIECE_COORD = Coord(-5, ROWS) # Window WINDOW_WIDTH = 800 @@ -134,7 +134,7 @@ class State: OVER = 3 -class Timer(AbstractTimer): +class Scheduler(AbstractScheduler): def __init__(self): self.tasks = {} @@ -213,12 +213,12 @@ class MatrixSprites(MinoesSprites): self.update() def update(self): - for y, line in enumerate(self.matrix): - for x, mino in enumerate(line): + for y, row in enumerate(self.matrix): + for x, mino in enumerate(row): if mino: mino.sprite.update(x, y) - def remove_line(self, y): + def remove_row(self, y): for mino in self.matrix[y]: if mino: self.remove(mino.sprite) @@ -226,7 +226,7 @@ class MatrixSprites(MinoesSprites): class TetrArcade(TetrisLogic, arcade.Window): - timer = Timer() + timer = Scheduler() def __init__(self): locale.setlocale(locale.LC_ALL, "") @@ -243,7 +243,7 @@ class TetrArcade(TetrisLogic, arcade.Window): self.new_conf() self.load_conf() - super().__init__(LINES, COLLUMNS, NEXT_PIECES) + super().__init__(ROWS, COLLUMNS, NEXT_PIECES) arcade.Window.__init__( self, width=self.init_width, @@ -261,7 +261,7 @@ class TetrArcade(TetrisLogic, arcade.Window): self.matrix.bg.alpha = MATRIX_BG_ALPHA self.matrix.sprites = MatrixSprites(self.matrix) self.on_resize(self.init_width, self.init_height) - self.exploding_minoes = [None for y in range(LINES)] + self.exploding_minoes = [None for y in range(ROWS)] if self.play_music: try: @@ -420,14 +420,14 @@ AGAIN""".format( for mino in falling_piece: matrix.sprites.append(mino.sprite) - def on_animate_phase(self, matrix, lines_to_remove): - for y in lines_to_remove: - line_textures = tuple(TEXTURES[mino.color] for mino in matrix[y]) + def on_animate_phase(self, matrix, rows_to_remove): + for y in rows_to_remove: + row_textures = tuple(TEXTURES[mino.color] for mino in matrix[y]) self.exploding_minoes[y] = arcade.Emitter( center_xy=(matrix.bg.left, matrix.bg.bottom + (y + 0.5) * MINO_SIZE), emit_controller=arcade.EmitBurst(COLLUMNS), particle_factory=lambda emitter: arcade.LifetimeParticle( - filename_or_texture=random.choice(line_textures), + filename_or_texture=random.choice(row_textures), change_xy=arcade.rand_in_rect( (-COLLUMNS * MINO_SIZE, -4 * MINO_SIZE), 2 * COLLUMNS * MINO_SIZE, @@ -446,9 +446,9 @@ AGAIN""".format( particule.change_x *= PARTICULE_ACCELERATION particule.change_y *= PARTICULE_ACCELERATION - def on_eliminate_phase(self, matrix, lines_to_remove): - for y in lines_to_remove: - matrix.sprites.remove_line(y) + def on_eliminate_phase(self, matrix, rows_to_remove): + for y in rows_to_remove: + matrix.sprites.remove_row(y) def on_completion_phase(self, pattern_name, pattern_score, nb_combo, combo_score): if pattern_score: @@ -521,7 +521,7 @@ AGAIN""".format( t = time.localtime(self.stats.time) font_size = STATS_TEXT_SIZE * self.scale for y, text in enumerate( - ("TIME", "LINES", "GOAL", "LEVEL", "HIGH SCORE", "SCORE") + ("TIME", "ROWS", "GOAL", "LEVEL", "HIGH SCORE", "SCORE") ): arcade.draw_text( text=text, @@ -537,7 +537,7 @@ AGAIN""".format( for y, text in enumerate( ( "{:02d}:{:02d}:{:02d}".format(t.tm_hour - 1, t.tm_min, t.tm_sec), - "{:n}".format(self.stats.lines_cleared), + "{:n}".format(self.stats.rows_cleared), "{:n}".format(self.stats.goal), "{:n}".format(self.stats.level), "{:n}".format(self.stats.high_score), diff --git a/tetrislogic/__init__.py b/tetrislogic/__init__.py index cf43502..2267197 100644 --- a/tetrislogic/__init__.py +++ b/tetrislogic/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from .consts import LINES, COLLUMNS, NEXT_PIECES +from .consts import ROWS, COLLUMNS, NEXT_PIECES from .utils import Movement, Spin, Color, Coord from .tetromino import ( Mino, @@ -12,4 +12,4 @@ from .tetromino import ( T_Tetrimino, Z_Tetrimino, ) -from .tetrislogic import TetrisLogic, Matrix, AbstractTimer +from .tetrislogic import TetrisLogic, Matrix, AbstractScheduler diff --git a/tetrislogic/consts.py b/tetrislogic/consts.py index cb25db6..5e30e90 100644 --- a/tetrislogic/consts.py +++ b/tetrislogic/consts.py @@ -3,7 +3,7 @@ from .utils import Coord, T_Spin # Matrix -LINES = 20 +ROWS = 20 COLLUMNS = 10 NEXT_PIECES = 5 @@ -14,7 +14,7 @@ AUTOREPEAT_DELAY = 0.300 # Official : 0.300 s AUTOREPEAT_PERIOD = 0.010 # Official : 0.010 s # Piece init coord -MATRIX_PIECE_COORD = Coord(4, LINES) +MATRIX_PIECE_COORD = Coord(4, ROWS) # Scores LINES_CLEAR_NAME = "LINES_CLEAR_NAME" diff --git a/tetrislogic/tetrislogic.py b/tetrislogic/tetrislogic.py index 5d21bbe..9c9563f 100644 --- a/tetrislogic/tetrislogic.py +++ b/tetrislogic/tetrislogic.py @@ -4,7 +4,7 @@ import pickle from .utils import Coord, Movement, Spin, T_Spin, T_Slot from .tetromino import Tetromino, T_Tetrimino from .consts import ( - LINES, + ROWS, COLLUMNS, NEXT_PIECES, LOCK_DELAY, @@ -20,41 +20,52 @@ from .consts import ( CRYPT_KEY = 987943759387540938469837689379857347598347598379584857934579343 -class AbstractTimer: +class AbstractScheduler: + """Scheduler to implement""" def postpone(task, delay): + """Schedule callable once after delay in seconds""" raise Warning("AbstractTimer.postpone is not implemented.") def cancel(self, task): + """Unschedule task or pass if task is not scheduled""" raise Warning("AbstractTimer.stop is not implemented.") - def reset(self, task, period): + def reset(self, task, delay): + """Cancel schedule and reschedule task after delay in seconds""" self.timer.cancel(task) - self.timer.postpone(task, period) + self.timer.postpone(task, delay) class PieceContainer: + """Object with piece attribute: None or Tetromino""" def __init__(self): self.piece = None class HoldQueue(PieceContainer): + """The storage place where players can Hold any falling tetrimino for use later. + When called for, the held tetrimino swaps places with the currently falling tetrimino, + and begins falling again at the generation point.""" pass class Matrix(list, PieceContainer): - def __init__(self, lines, collumns): + """The rectangular arrangement of cells creating the active game area, + usually 10 columns wide by 20 rows high. + Tetriminos fall from the top-middle just above the Skyline (off-screen) to the bottom.""" + def __init__(self, rows, collumns): list.__init__(self) PieceContainer.__init__(self) - self.lines = lines + self.rows = rows self.collumns = collumns self.ghost = None def reset(self): self.clear() - for y in range(self.lines + 3): - self.append_new_line() + for y in range(self.rows + 3): + self.append_new_row() - def append_new_line(self): + def append_new_row(self): self.append([None for x in range(self.collumns)]) def cell_is_free(self, coord): @@ -75,13 +86,16 @@ class Matrix(list, PieceContainer): class NextQueue(PieceContainer): - def __init__(self, nb_pieces): + """Displays the next tetrimino(s) to be placed (generated) just above the Matrix. + If hardware permits, the next six tetriminos should be shown.""" + def __init__(self, number): super().__init__() - self.nb_pieces = nb_pieces + self.number = number self.pieces = [] class Stats: + """Game statistics""" def _get_score(self): return self._score @@ -100,7 +114,7 @@ class Stats: def new_game(self, level): self.level = level - 1 self.score = 0 - self.lines_cleared = 0 + self.rows_cleared = 0 self.goal = 0 self.time = 0 self.combo = -1 @@ -119,27 +133,27 @@ class Stats: def update_time(self): self.time += 1 - def locks_down(self, t_spin, lines_cleared): + def locks_down(self, t_spin, rows_cleared): pattern_name = [] pattern_score = 0 combo_score = 0 if t_spin: pattern_name.append(t_spin) - if lines_cleared: - pattern_name.append(SCORES[lines_cleared][LINES_CLEAR_NAME]) + if rows_cleared: + pattern_name.append(SCORES[rows_cleared][LINES_CLEAR_NAME]) self.combo += 1 else: self.combo = -1 - if lines_cleared or t_spin: - pattern_score = SCORES[lines_cleared][t_spin] + if rows_cleared or t_spin: + pattern_score = SCORES[rows_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 + combo_score = (20 if rows_cleared == 1 else 50) * self.combo * self.level self.score += pattern_score + combo_score @@ -147,19 +161,19 @@ class Stats: class TetrisLogic: - + """Tetris game logic intended to implement with GUI""" # These class attributes can be redefined on inheritance AUTOREPEAT_DELAY = AUTOREPEAT_DELAY AUTOREPEAT_PERIOD = AUTOREPEAT_PERIOD MATRIX_PIECE_COORD = MATRIX_PIECE_COORD - timer = AbstractTimer() + timer = AbstractScheduler() - def __init__(self, lines=LINES, collumns=COLLUMNS, next_pieces=NEXT_PIECES): + def __init__(self, rows=ROWS, collumns=COLLUMNS, next_pieces=NEXT_PIECES): self.stats = Stats() self.load_high_score() self.held = HoldQueue() - self.matrix = Matrix(lines, collumns) + self.matrix = Matrix(rows, collumns) self.next = NextQueue(next_pieces) self.autorepeatable_actions = (self.move_left, self.move_right, self.soft_drop) self.pressed_actions = [] @@ -238,6 +252,12 @@ class TetrisLogic: pass def move(self, movement, rotated_coords=None, lock=True): + """The tetrimino in play falls from just above the Skyline one cell at a time, + and moves left and right one cell at a time. + Each Mino of a tetrimino “snaps” to the appropriate cell position at the completion of a move, + although intermediate tetrimino movement appears smooth. + Only right, left, and downward movement are allowed. + Movement into occupied cells and Matrix walls and floors is not allowed.""" potential_coord = self.matrix.piece.coord + movement potential_minoes_coords = rotated_coords or ( mino.coord for mino in self.matrix.piece @@ -253,6 +273,11 @@ class TetrisLogic: if self.matrix.space_to_fall(): self.falling_phase() else: + + """Classic Lock down rules apply. + Like Infinite Placement, the Lock down timer starts counting down from 0.5 seconds once the + tetrimino in play lands on a Surface. the y-coordinate of the tetrimino must decrease (i.e., the + tetrimino falls further down in the Matrix) in order for the timer to be reset.""" self.matrix.piece.locked = True self.on_locked(self.matrix.piece) self.timer.reset(self.locks_down, self.stats.lock_delay) @@ -261,6 +286,12 @@ class TetrisLogic: return False def rotate(self, spin): + """Tetriminos can rotate clockwise and counterclockwise using the Super Rotation System. this + system allows tetrimino rotation in situations that the original Classic Rotation System did not + allow, such as rotating against walls. + each time a rotation button is pressed, the tetrimino in play rotates 90 degrees in the clockwise + or counterclockwise direction. Rotation can be performed while the tetrimino is Auto- + Repeating left or right. there is no Auto-Repeat for rotation itself.""" rotated_coords = tuple(mino.coord @ spin for mino in self.matrix.piece) for rotation_point, liberty_degree in enumerate( self.matrix.piece.SRS[spin][self.matrix.piece.orientation], start=1 @@ -277,12 +308,15 @@ class TetrisLogic: return False def locks_down(self): - # self.timer.cancel(self.repeat_action) + """A tetrimino that is Hard dropped Locks down immediately. + However, if a tetrimino naturally falls or Soft drops onto a Surface, + it is given 0.5 seconds (less after level 20) on a Lock down timer + before it actually Locks down.""" self.timer.cancel(self.lock_phase) # Game over if all( - (mino.coord + self.matrix.piece.coord).y >= self.matrix.lines + (mino.coord + self.matrix.piece.coord).y >= self.matrix.rows for mino in self.matrix.piece ): self.game_over() @@ -290,7 +324,7 @@ class TetrisLogic: for mino in self.matrix.piece: coord = mino.coord + self.matrix.piece.coord - if coord.y <= self.matrix.lines + 3: + if coord.y <= self.matrix.rows + 3: self.matrix[coord.y][coord.x] = mino self.on_locks_down(self.matrix, self.matrix.piece) @@ -298,14 +332,30 @@ class TetrisLogic: # Pattern phase # T-Spin + """A t-Spin or Mini t-Spin is a special rotation of the t-tetrimino into a t-Slot, and when + accomplished, awards a scoring or line bonus in most variants. A t-Slot is defined as any Block + formation such that when the t-tetrimino is spun in it, any three of the four cells diagonally + adjacent to the center of the t-tetrimino are occupied by existing Blocks. In order to be + considered a t-Spin or Mini t-Spin, the t-tetrimino must spin clockwise or counterclockwise first + (it cannot merely be moved or dropped into a t-Slot). In addition to a scoring or other bonus, + t-Spins and Mini t-Spins can also continue a Back-to-Back sequence.""" if type(self.matrix.piece) == T_Tetrimino and self.matrix.piece.rotated_last: a = self.is_t_slot(T_Slot.A) b = self.is_t_slot(T_Slot.B) c = self.is_t_slot(T_Slot.C) d = self.is_t_slot(T_Slot.D) if a and b and (c or d): + """A rotation is considered a t-Spin if any of the following conditions are met: + • Sides A and B + (C or d) are touching a Surface when the tetrimino Locks down. + • the t-tetrimino fills a t-Slot completely with no holes. + • Rotation Point 5 is used to rotate the tetrimino into the t-Slot. + Any further rotation will be considered a t-Spin, not a Mini t-Spin.""" t_spin = T_Spin.T_SPIN elif c and d and (a or b): + """A rotation is considered a Mini t-Spin if either of the following conditions are met: + • Sides C and d + (A or B) are touching a Surface when the tetrimino Locks down. + • the t-tetrimino creates holes in a t-Slot. However, if Rotation Point 5 was used to rotate + the tetrimino into the t-Slot, the rotation is considered a t-Spin. """ if self.matrix.piece.rotation_point_5_used: t_spin = T_Spin.T_SPIN else: @@ -315,29 +365,29 @@ class TetrisLogic: else: t_spin = T_Spin.NONE - # Clear complete lines - self.lines_to_remove = [] - for y, line in reversed(list(enumerate(self.matrix))): - if all(mino for mino in line): - self.lines_to_remove.append(y) - lines_cleared = len(self.lines_to_remove) - if lines_cleared: - self.stats.lines_cleared += lines_cleared + # Clear complete rows + self.rows_to_remove = [] + for y, row in reversed(list(enumerate(self.matrix))): + if all(mino for mino in row): + self.rows_to_remove.append(y) + rows_cleared = len(self.rows_to_remove) + if rows_cleared: + self.stats.rows_cleared += rows_cleared # Animate phase - self.on_animate_phase(self.matrix, self.lines_to_remove) + self.on_animate_phase(self.matrix, self.rows_to_remove) # Eliminate phase - self.on_eliminate_phase(self.matrix, self.lines_to_remove) - for y in self.lines_to_remove: + self.on_eliminate_phase(self.matrix, self.rows_to_remove) + for y in self.rows_to_remove: self.matrix.pop(y) - self.matrix.append_new_line() + self.matrix.append_new_row() # Completion phase pattern_name, pattern_score, nb_combo, combo_score = self.stats.locks_down( - t_spin, lines_cleared + t_spin, rows_cleared ) self.on_completion_phase(pattern_name, pattern_score, nb_combo, combo_score) @@ -349,10 +399,10 @@ class TetrisLogic: def on_locks_down(self, matrix, falling_piece): pass - def on_animate_phase(self, matrix, lines_to_remove): + def on_animate_phase(self, matrix, rows_to_remove): pass - def on_eliminate_phase(self, matrix, lines_to_remove): + def on_eliminate_phase(self, matrix, rows_to_remove): pass def on_completion_phase(self, pattern_name, pattern_score, nb_combo, combo_score): @@ -373,12 +423,23 @@ class TetrisLogic: self.rotate(Spin.COUNTER) def soft_drop(self): + """when the Soft drop command is pressed, the tetrimino in play drops at a rate 20 times faster + than the normal fall Speed, measured in seconds per line. the tetrimino resumes its normal + fall Speed once the Soft drop button is released. for example, if the normal fall Speed is 0.5 + seconds per line, then the Soft drop speed is (0.5 / 20) = 0.025 seconds per line. + note that if the player Soft drops a tetrimino until it lands on a Surface, Lock down does not + occur until the Lock down timer hits zero. + Press and hold the Soft drop button to continue the downward movement. Soft drop continues + to the next tetrimino (after Lock down) as long as the button remains pressed.""" moved = self.move(Movement.DOWN) if moved: self.stats.score += 1 return moved def hard_drop(self): + """The Hard drop command instantly drops the tetrimino + and locks it down on the Surface directly below it. + There is no Auto-Repeat for a Hard drop.""" self.timer.cancel(self.lock_phase) self.timer.cancel(self.locks_down) while self.move(Movement.DOWN, lock=False): @@ -386,6 +447,16 @@ class TetrisLogic: self.locks_down() def hold(self): + """Using the Hold command places the tetrimino in play into the Hold Queue. + The previously held tetrimino (if one exists) will then start falling from the top of the Matrix, + beginning from its generation position and north facing orientation. + Only one tetrimino may be held at a time. + A Lock down must take place between Holds. + Ror example, at the beginning, the first tetrimino is generated and begins to fall. + The player decides to hold this tetrimino. + Immediately the next tetrimino is generated from the next Queue and begins to fall. + The player must first Lock down this tetrimino before holding another tetrimino. + In other words, you may not Hold the same tetrimino more than once.""" if not self.matrix.piece.hold_enabled: return @@ -454,6 +525,20 @@ class TetrisLogic: self.timer.reset(self.repeat_action, delay) def repeat_action(self): + """tapping the move button allows a single cell movement of the tetrimino in the direction +pressed. Holding down the move button triggers an Auto-Repeat movement that allows the +player to move a tetrimino from one side of the Matrix to the other in about 0.5 seconds. this is +essential on higher levels when the fall Speed of a tetrimino is very fast. +there must be a slight delay between the time the move button is pressed and the time when +Auto-Repeat kicks in, roughly 0.3 seconds. this delay prevents unwanted extra movement of a +tetrimino. Auto-Repeat only affects Left/Right movement. Auto-Repeat continues to the next +tetrimino (after Lock down) as long as the move button remains pressed. +In addition, when Auto-Repeat begins, and the player then holds the opposite direction button, +the tetrimino must then begin moving the opposite direction with the initial delay. this mainly +applies to devices with movement buttons—such as a keyboard or mobile phone—where more +than one direction button is able to be pressed simultaneously. when any single button is then +released, the tetrimino should again move in the direction still held, with the Auto-Repeat delay +of roughly 0.3 seconds applied once more.""" if not self.pressed_actions: return