forgot to commit before week-end
This commit is contained in:
		| @ -1 +1,3 @@ | |||||||
| __all__ = [] | from .core import Tetris | ||||||
|  |  | ||||||
|  | __all__ = ["Tetris"] | ||||||
| @ -22,7 +22,6 @@ class Movement: | |||||||
|     LEFT  = Point(-1, 0) |     LEFT  = Point(-1, 0) | ||||||
|     RIGHT = Point(1, 0) |     RIGHT = Point(1, 0) | ||||||
|     DOWN  = Point(0, 1) |     DOWN  = Point(0, 1) | ||||||
|     STILL = Point(0, 0) |  | ||||||
|      |      | ||||||
|      |      | ||||||
| class Mino: | class Mino: | ||||||
| @ -38,6 +37,7 @@ class Mino: | |||||||
|  |  | ||||||
| class Tetromino: | class Tetromino: | ||||||
|     INIT_POSITION = Point(4, 0) |     INIT_POSITION = Point(4, 0) | ||||||
|  |     CAN_ROTATE = True | ||||||
|     SUPER_ROTATION_SYSTEM = ( |     SUPER_ROTATION_SYSTEM = ( | ||||||
|         { |         { | ||||||
|             Rotation.COUNTERCLOCKWISE: (Point(0, 0), Point(1, 0), Point(1, -1), Point(0, 2), Point(1, 2)), |             Rotation.COUNTERCLOCKWISE: (Point(0, 0), Point(1, 0), Point(1, -1), Point(0, 2), Point(1, 2)), | ||||||
| @ -73,6 +73,7 @@ class Tetromino: | |||||||
| class O(Tetromino): | class O(Tetromino): | ||||||
|     MINOES_POSITIONS = (Point(0, 0), Point(1, 0), Point(0, -1), Point(1, -1)) |     MINOES_POSITIONS = (Point(0, 0), Point(1, 0), Point(0, -1), Point(1, -1)) | ||||||
|     MINOES_TYPE = Mino.O |     MINOES_TYPE = Mino.O | ||||||
|  |     CAN_ROTATE = False | ||||||
|  |  | ||||||
|     def _rotate(self, direction): |     def _rotate(self, direction): | ||||||
|         return False |         return False | ||||||
| @ -134,79 +135,15 @@ class Z(Tetromino): | |||||||
|     MINOES_TYPE = Mino.Z |     MINOES_TYPE = Mino.Z | ||||||
|  |  | ||||||
|  |  | ||||||
| class Matrix: | class Tetris: | ||||||
|     NB_COLS = 10 |  | ||||||
|     NB_ROWS = 40 |  | ||||||
|     PIECE_POSITION = Point(4, 0) |  | ||||||
|  |  | ||||||
|     def __init__(self): |  | ||||||
|         self.cells = [ |  | ||||||
|             [Mino.NO_MINO for x in range(self.NB_COLS)] |  | ||||||
|             for y in range(self.NB_ROWS) |  | ||||||
|         ] |  | ||||||
|  |  | ||||||
|     def is_free_cell(self, position): |  | ||||||
|         return ( |  | ||||||
|             0 <= position.x < self.NB_COLS |  | ||||||
|             and position.y < self.NB_LINES |  | ||||||
|             and not (position.y >= 0 and self.cells[position.y][position.x] != Mino.NO_MINO) |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def lock(self, piece): |  | ||||||
|         for mino_position in piece.minoes_position: |  | ||||||
|             position = mino_position + piece.position |  | ||||||
|             if position.y >= 0: |  | ||||||
|                 self.cells[position.y][position.x] = piece.MINOES_TYPE |  | ||||||
|             else: |  | ||||||
|                 return None |  | ||||||
|         else: |  | ||||||
|             nb_lines_cleared = 0 |  | ||||||
|             for y, line in enumerate(self.cells): |  | ||||||
|                 if all(mino for mino in line): |  | ||||||
|                     self.cells.pop(y) |  | ||||||
|                     self.cells.insert(0, [Mino.NO_MINO for x in range(self.NB_COLS)]) |  | ||||||
|                     nb_lines_cleared += 1 |  | ||||||
|             return nb_lines_cleared |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Stats(Window): |  | ||||||
|         try: |  | ||||||
|             with open(self.FILE_PATH, "r") as f: |  | ||||||
|                self.high_score = int(f.read()) |  | ||||||
|         except: |  | ||||||
|             self.high_score = 0 |  | ||||||
|         self.combo = -1 |  | ||||||
|         self.time = time.time() |  | ||||||
|         self.lines_cleared = 0 |  | ||||||
|         self.clock_timer = None |  | ||||||
|         self.strings = [] |  | ||||||
|         Window.__init__(self, width, height, begin_x, begin_y) |  | ||||||
|         self.new_level() |  | ||||||
|  |  | ||||||
|     def refresh(self): |  | ||||||
|         self.draw_border() |  | ||||||
|         self.window.addstr(2, 2, "SCORE\t{:n}".format(self.score)) |  | ||||||
|         if self.score >= self.high_score: |  | ||||||
|             self.window.addstr(3, 2, "HIGH\t{:n}".format(self.high_score), curses.A_BLINK|curses.A_BOLD) |  | ||||||
|         else: |  | ||||||
|             self.window.addstr(3, 2, "HIGH\t{:n}".format(self.high_score)) |  | ||||||
|         t = time.localtime(time.time() - self.time) |  | ||||||
|         self.window.addstr(4, 2, "TIME\t%02d:%02d:%02d" % (t.tm_hour-1, t.tm_min, t.tm_sec)) |  | ||||||
|         self.window.addstr(5, 2, "LEVEL\t%d" % self.level) |  | ||||||
|         self.window.addstr(6, 2, "GOAL\t%d" % self.goal) |  | ||||||
|         self.window.addstr(7, 2, "LINES\t%d" % self.lines_cleared) |  | ||||||
|         start_y = self.height - len(self.strings) - 2 |  | ||||||
|         for y, string in enumerate(self.strings, start=start_y): |  | ||||||
|             x = (self.width-len(string)) // 2 + 1 |  | ||||||
|             self.window.addstr(y, x, string) |  | ||||||
|         self.window.refresh() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Game: |  | ||||||
|     AUTOREPEAT_DELAY = 0.02 |  | ||||||
|     LOCK_DELAY = 0.5 |  | ||||||
|     FALL_DELAY = 1 |  | ||||||
|     TETROMINOES = (O, I, T, L, J, S, Z) |     TETROMINOES = (O, I, T, L, J, S, Z) | ||||||
|  |     LEN_NEXT_QUEUE = 1 | ||||||
|  |     MATRIX_ROWS = 20 | ||||||
|  |     MATRIX_COLS = 10 | ||||||
|  |     INIT_POSITION = Point(4, 0) | ||||||
|  |     FALL_DELAY = 1 | ||||||
|  |     LOCK_DELAY = 0.5 | ||||||
|  |     AUTOSHIFT_DELAY = 0.2 | ||||||
|     SCORES = ( |     SCORES = ( | ||||||
|         {"name": "",       "": 0, "MINI T-SPIN": 1, "T-SPIN": 4}, |         {"name": "",       "": 0, "MINI T-SPIN": 1, "T-SPIN": 4}, | ||||||
|         {"name": "SINGLE", "": 1, "MINI T-SPIN": 2, "T-SPIN": 8}, |         {"name": "SINGLE", "": 1, "MINI T-SPIN": 2, "T-SPIN": 8}, | ||||||
| @ -214,29 +151,40 @@ class Game: | |||||||
|         {"name": "TRIPLE", "": 5, "T-SPIN": 16}, |         {"name": "TRIPLE", "": 5, "T-SPIN": 16}, | ||||||
|         {"name": "TETRIS", "": 8} |         {"name": "TETRIS", "": 8} | ||||||
|     ) |     ) | ||||||
|     LEN_NEXT_QUEUE = 1 |  | ||||||
|      |      | ||||||
|     def __init__(self, level=1): |     def __init__(self, high_score=0): | ||||||
|         self.matrix = Matrix() |         self.matrix = [ | ||||||
|         self.paused = False |             [Mino.NO_MINO for x in range(self.MATRIX_ROWS)] | ||||||
|         self.start_next_piece() |             for y in range(self.MATRIX_COLS) | ||||||
|         self.score = 0 |         ] | ||||||
|         self.level = level - 1 |         self.high_score = high_score | ||||||
|         self.combo = -1 |  | ||||||
|         self.random_bag = [] |  | ||||||
|         self.next_queue = [self.random_piece() for _ in range(self.LEN_NEXT_QUEUE)] |  | ||||||
|         self.held_piece = None |  | ||||||
|         self.time = time.time() |  | ||||||
|         self.playing = True |  | ||||||
|         self.next_level() |  | ||||||
|         self.new_piece() |  | ||||||
|  |  | ||||||
|     def random_piece(self): |     def _random_piece(self): | ||||||
|         if not self.random_bag: |         if not self.random_bag: | ||||||
|             self.random_bag = list(self.TETROMINOES) |             self.random_bag = list(self.TETROMINOES) | ||||||
|             random.shuffle(self.random_bag) |             random.shuffle(self.random_bag) | ||||||
|         return self.random_bag.pop()() |         return self.random_bag.pop()() | ||||||
|          |          | ||||||
|  |     def new_game(self, level=1): | ||||||
|  |         self.matrix.cells = [ | ||||||
|  |             [Mino.NO_MINO for x in range(self.NB_COLS)] | ||||||
|  |             for y in range(self.NB_ROWS) | ||||||
|  |         ] | ||||||
|  |         self.level = level - 1 | ||||||
|  |         self.goal = 0 | ||||||
|  |         self.score = 0 | ||||||
|  |         self.random_bag = [] | ||||||
|  |         self.next_queue = [ | ||||||
|  |             self._random_piece() | ||||||
|  |             for _ in range(self.LEN_NEXT_QUEUE) | ||||||
|  |         ] | ||||||
|  |         self.held_piece = None | ||||||
|  |         self.fall_delay = self.FALL_DELAY | ||||||
|  |         self.lock_delay = self.LOCK_DELAY | ||||||
|  |         self.time = time.time() | ||||||
|  |         self.next_level() | ||||||
|  |         self.new_piece() | ||||||
|  |  | ||||||
|     def next_level(self): |     def next_level(self): | ||||||
|         self.level += 1 |         self.level += 1 | ||||||
|         if self.level <= 20: |         if self.level <= 20: | ||||||
| @ -244,9 +192,11 @@ class Game: | |||||||
|         if self.level > 15: |         if self.level > 15: | ||||||
|             self.lock_delay = 0.5 * pow(0.9, self.level-15) |             self.lock_delay = 0.5 * pow(0.9, self.level-15) | ||||||
|         self.goal += 5 * self.level |         self.goal += 5 * self.level | ||||||
|  |         self.show_text("LEVEL %d" % self.level) | ||||||
|  |  | ||||||
|     def new_piece(self): |     def new_piece(self): | ||||||
|         self.current_piece, self.next_piece = self.next_piece, self.random_piece() |         self.current_piece = self.next_queue.pop(1) | ||||||
|  |         self.next_queue.append(self._random_piece) | ||||||
|         self.start_piece() |         self.start_piece() | ||||||
|  |  | ||||||
|     def hold_piece(self): |     def hold_piece(self): | ||||||
| @ -261,105 +211,157 @@ class Game: | |||||||
|                 self.new_piece() |                 self.new_piece() | ||||||
|              |              | ||||||
|     def start_piece(self): |     def start_piece(self): | ||||||
|         self.current_piece.position = self.current_piece.INIT_POSITION |         self.current_piece.position = self.INIT_POSITION | ||||||
|         if not  |         if not self.shape_fits(self.current_piece.position, self.current_piece.minoes_positions): | ||||||
|             self.over() |             self.over() | ||||||
|  |  | ||||||
|     def _possible_position(self, minoes_position, movement): |     def _move(self, movement): | ||||||
|         potential_position = self.position + movement |         potential_position = self.current_piece.position + movement | ||||||
|         if all( |         if self.shape_fits(potential_position, self.current_piece.minoes_positions): | ||||||
|             self.matrix.is_free_cell(mino_position+potential_position) |             self.current_piece.position = potential_position | ||||||
|             for mino_position in minoes_position |             self.current_piece.rotated_last = False | ||||||
|         ): |             self.postpone_lock() | ||||||
|             return potential_position |  | ||||||
|          |  | ||||||
|     def piece_blocked(self): |  | ||||||
|         return not self.current_piece._possible_position(self.current_piece.minoes_position, Movement.STILL) |  | ||||||
|  |  | ||||||
|     def move(self, movement): |  | ||||||
|         possible_position = self._possible_position(self.minoes_position, movement) |  | ||||||
|         if possible_position: |  | ||||||
|             self.position = possible_position |  | ||||||
|             self.rotated_last = False |  | ||||||
|             return True |             return True | ||||||
|         else: |         else: | ||||||
|  |             self.prelock() | ||||||
|  |             return False | ||||||
|  |  | ||||||
|  |     def _rotate(self, direction): | ||||||
|  |         if not self.current_piece.CAN_ROTATE: | ||||||
|             return False |             return False | ||||||
|              |              | ||||||
|     def rotate(self, direction): |  | ||||||
|         potential_minoes_positions = tuple( |         potential_minoes_positions = tuple( | ||||||
|             Point(-direction*mino_position.y, direction*mino_position.x) |             Point(-direction*mino_position.y, direction*mino_position.x) | ||||||
|             for mino_position in self.minoes_position |             for mino_position in self.minoes_position | ||||||
|         ) |         ) | ||||||
|         for rotation_point, liberty_degree in enumerate(self.SUPER_ROTATION_SYSTEM[self.orientation][direction], start=1): |         for rotation_point, liberty_degree in enumerate(self.SUPER_ROTATION_SYSTEM[self.orientation][direction], start=1): | ||||||
|             possible_position = self._possible_position(potential_minoes_positions, liberty_degree) |             potential_position = self.position + liberty_degree | ||||||
|             if possible_position: |             if self.shape_fits(potential_position, potential_minoes_positions): | ||||||
|                 self.orientation = (self.orientation+direction) % 4 |                 self.current_piece.orientation = (self.orientation+direction) % 4 | ||||||
|                 self.position = possible_position |                 self.current_piece.position = potential_position | ||||||
|                 self.minoes_position = potential_minoes_positions |                 self.current_piece.minoes_position = potential_minoes_positions | ||||||
|                 self.rotated_last = True |                 self.current_piece.rotated_last = True | ||||||
|                 if rotation_point == 5: |                 if rotation_point == 5: | ||||||
|                     self.rotation_point_5_used = True |                     self.current_piece.rotation_point_5_used = True | ||||||
|  |                 self.postpone_lock() | ||||||
|                 return True |                 return True | ||||||
|  |         else: | ||||||
|  |             self.prelock() | ||||||
|  |             return False | ||||||
|          |          | ||||||
|     def move_left(self): |     def move_left(self): | ||||||
|         self.current_piece.move(Movement.LEFT) |         self._move(Movement.LEFT) | ||||||
|          |          | ||||||
|     def move_right(self): |     def move_right(self): | ||||||
|         self.current_piece.move(Movement.RIGHT) |         self._move(Movement.RIGHT) | ||||||
|  |  | ||||||
|     def soft_drop(self): |     def soft_drop(self): | ||||||
|         if self.current_piece.move(Movement.DOWN): |         if self._move(Movement.DOWN): | ||||||
|             self.score += 1 |             self.rows_dropped(1) | ||||||
|          |  | ||||||
|     def fall(self): |  | ||||||
|         self.current_piece.move(Movement.DOWN) |  | ||||||
|  |  | ||||||
|     def hard_drop(self): |     def hard_drop(self): | ||||||
|         while self.current_piece.move(Movement.DOWN): |         points = 0 | ||||||
|             self.score += 2 |         while self._move(Movement.DOWN): | ||||||
|  |             points += 2 | ||||||
|  |         self.rows_dropped(points) | ||||||
|         self.lock_piece() |         self.lock_piece() | ||||||
|              |              | ||||||
|  |     def rows_dropped(self, points): | ||||||
|  |         self.update_score(points, "") | ||||||
|  |          | ||||||
|  |     def fall(self): | ||||||
|  |         self._move(Movement.DOWN) | ||||||
|  |          | ||||||
|     def rotate_clockwise(self): |     def rotate_clockwise(self): | ||||||
|         return self.current_piece.rotate(Rotation.CLOCKWISE) |         return self.current_piece._rotate(Rotation.CLOCKWISE) | ||||||
|          |          | ||||||
|     def rotate_counterclockwise(self): |     def rotate_counterclockwise(self): | ||||||
|         return self.current_piece.rotate(Rotation.COUNTERCLOCKWISE) |         return self.current_piece._rotate(Rotation.COUNTERCLOCKWISE) | ||||||
|  |  | ||||||
|  |     def is_free_cell(self, position): | ||||||
|  |         return ( | ||||||
|  |             0 <= position.x < self.NB_COLS | ||||||
|  |             and position.y < self.NB_LINES | ||||||
|  |             and not (position.y >= 0 and self.matrix[position.y][position.x] != Mino.NO_MINO) | ||||||
|  |         ) | ||||||
|  |          | ||||||
|  |     def shape_fits(self, piece_position, minoes_positions): | ||||||
|  |         return all( | ||||||
|  |             self.is_free_cell(piece_position+mino_position) | ||||||
|  |             for mino_position in minoes_positions | ||||||
|  |         ) | ||||||
|  |          | ||||||
|  |     def prelock(self): | ||||||
|  |         """ | ||||||
|  |         Schedules self.lock in self.lock_delay | ||||||
|  |         """ | ||||||
|  |         raise NotImplementedError | ||||||
|  |      | ||||||
|  |     def postpone_lock(self): | ||||||
|  |         """ | ||||||
|  |         Reset timer calling self.lock to self.lock_delay | ||||||
|  |         """ | ||||||
|  |         raise NotImplementedError | ||||||
|  |  | ||||||
|     def lock_piece(self): |     def lock_piece(self): | ||||||
|         t_spin = self.current_piece.t_spin() |         if self.shape_fits(self.current_piece.position+Movement.DOWN, self.current_piece.minoes_positions): | ||||||
|         nb_lines = self.matrix.lock(self.current_piece) |             self.postpone_lock() | ||||||
|  |             return | ||||||
|          |          | ||||||
|         if nb_lines is None: |         t_spin = self.current_piece.t_spin() | ||||||
|  |          | ||||||
|  |         for mino_position in self.current_piece.minoes_position: | ||||||
|  |             position = mino_position + self.current_piece.position | ||||||
|  |             if position.y >= 0: | ||||||
|  |                 self.matrix[position.y][position.x] = self.current_piece.MINOES_TYPE | ||||||
|  |             else: | ||||||
|                 self.over() |                 self.over() | ||||||
|                 return |                 return | ||||||
|              |              | ||||||
|         if nb_lines: |         nb_rows = 0 | ||||||
|             self.combo += 1 |         for y, row in enumerate(self.cells): | ||||||
|         else: |             if all(mino for mino in row): | ||||||
|             self.combo = -1 |                 self.cells.pop(y) | ||||||
|  |                 self.cells.insert(0, [Mino.NO_MINO for x in range(self.NB_COLS)]) | ||||||
|  |                 nb_rows += 1 | ||||||
|  |         self.piece_locked(nb_rows, t_spin) | ||||||
|  |  | ||||||
|         if nb_lines or t_spin: |         if t_spin or nb_rows: | ||||||
|             ds = self.SCORES[nb_lines][t_spin] |             points = self.SCORES[nb_rows][t_spin] | ||||||
|             self.goal -= ds |             self.goal -= points | ||||||
|             ds *= 100 * self.level |             points *= 100 * self.level | ||||||
|             self.score += ds |             text = t_spin | ||||||
|  |             if t_spin and nb_rows: | ||||||
|  |                 text += " " | ||||||
|  |             if nb_rows: | ||||||
|  |                 text += self.SCORES[nb_rows]["name"] | ||||||
|  |             self.update_score(points, text) | ||||||
|              |              | ||||||
|  |         self.combo = self.combo + 1 if nb_rows else -1 | ||||||
|         if self.combo >= 1: |         if self.combo >= 1: | ||||||
|             self.strings.append("COMBO x%d" % self.combo) |             points = (20 if nb_rows==1 else 50) * self.combo * self.level | ||||||
|             ds = (20 if nb_lines==1 else 50) * self.combo * self.level |             text = "COMBO x%d" % self.combo | ||||||
|             self.score += ds |             self.update_score(points, text) | ||||||
|             self.strings.append(str(ds)) |  | ||||||
|          |          | ||||||
|         if self.goal <= 0: |         if self.goal <= 0: | ||||||
|             self.new_level() |             self.new_level() | ||||||
|              |              | ||||||
|  |         self.new_piece() | ||||||
|  |              | ||||||
|  |     def update_score(self, points, text): | ||||||
|  |         self.score += points | ||||||
|  |         if self.score > self.high_score: | ||||||
|  |             self.high_score = self.score | ||||||
|  |         self.show_text("%s\n%d" % (text, points)) | ||||||
|  |          | ||||||
|  |     def show_text(self, text): | ||||||
|  |         print(text) | ||||||
|  |              | ||||||
|     def pause(self): |     def pause(self): | ||||||
|         self.time = time.time() - self.time |         self.time = time.time() - self.time | ||||||
|         self.paused = True |  | ||||||
|          |          | ||||||
|     def resume(self): |     def resume(self): | ||||||
|         self.time = time.time() - self.time |         self.time = time.time() - self.time | ||||||
|         self.paused = False |  | ||||||
|  |  | ||||||
|     def over(self): |     def over(self): | ||||||
|         self.playing = False |         self.show_text("GAME OVER") | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user