From 1e1d88ad690fe3622870f09f831f19ae5f3d666b Mon Sep 17 00:00:00 2001 From: adrienmalin <41926238+adrienmalin@users.noreply.github.com> Date: Mon, 25 Feb 2019 20:42:47 +0100 Subject: [PATCH] hell --- terminis/pytetris/__init__.py | 4 +- terminis/pytetris/core.py | 48 +-- terminis/terminis.py | 556 +++++++++------------------------- 3 files changed, 175 insertions(+), 433 deletions(-) diff --git a/terminis/pytetris/__init__.py b/terminis/pytetris/__init__.py index bd96ddc..ae137aa 100644 --- a/terminis/pytetris/__init__.py +++ b/terminis/pytetris/__init__.py @@ -1,3 +1,3 @@ -from .core import Tetris +from .core import Tetris, Mino, Point -__all__ = ["Tetris"] \ No newline at end of file +__all__ = ["Tetris", "Mino", "Point"] \ No newline at end of file diff --git a/terminis/pytetris/core.py b/terminis/pytetris/core.py index ea593ae..82591d4 100644 --- a/terminis/pytetris/core.py +++ b/terminis/pytetris/core.py @@ -16,6 +16,9 @@ class Point: def __add__(self, other): return Point(self.x+other.x, self.y+other.y) + + def __eq__(self, other): + return self.x == other.x and self.y == other.y class Movement: @@ -54,11 +57,12 @@ class Tetromino: Rotation.CLOCKWISE: (Point(0, 0), Point(-1, 0), Point(-1, 1), Point(0, 2), Point(-1, -2)) } ) - lock_delay = 0.5 + INIT_POSITION = Point(4, -1) + LOCK_DELAY = 0.5 - def __init__(self, position): + def __init__(self): self.position = self.INIT_POSITION - self.minoes_position = self.MINOES_POSITIONS + self.minoes_positions = self.MINOES_POSITIONS self.orientation = 0 self.rotation_point_5_used = False self.rotated_last = False @@ -72,7 +76,7 @@ class Tetromino: class O(Tetromino): MINOES_POSITIONS = (Point(0, 0), Point(1, 0), Point(0, -1), Point(1, -1)) MINOES_TYPE = Mino.O - SUPER_ROTATION_SYSTEM = tuple() + SUPER_ROTATION_SYSTEM = (tuple(),) def _rotate(self, direction): return False @@ -153,10 +157,6 @@ class Tetris: ) def __init__(self, high_score=0): - self.matrix = [ - [Mino.NO_MINO for x in range(self.MATRIX_ROWS)] - for y in range(self.MATRIX_COLS) - ] self.high_score = high_score def _random_piece(self): @@ -166,9 +166,9 @@ class Tetris: 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.matrix = [ + [Mino.NO_MINO for x in range(self.MATRIX_COLS)] + for y in range(self.MATRIX_ROWS) ] self.level = level - 1 self.goal = 0 @@ -182,8 +182,8 @@ class Tetris: self.fall_delay = self.FALL_DELAY self.lock_delay = self.LOCK_DELAY self.time = time.time() - self.current_piece = None self.next_level() + self.current_piece = None self.new_piece() def next_level(self): @@ -197,16 +197,16 @@ class Tetris: def new_piece(self): if not self.current_piece: - self.current_piece = self.next_queue.pop(1) - self.next_queue.append(self._random_piece) + self.current_piece = self.next_queue.pop(0) + self.next_queue.append(self._random_piece()) self.current_piece.position = self.INIT_POSITION - if not self.fall(): + if not self._move(Movement.DOWN): self.game_over() def hold_piece(self): if self.current_piece.hold_enabled: self.current_piece, self.hold_piece = self.held_piece, self.current_piece - self.held_piece.minoes_position = self.held_piece.MINOES_POSITIONS + self.held_piece.minoes_positions = self.held_piece.MINOES_POSITIONS self.held_piece.hold_enabled = False self.new_piece() @@ -235,12 +235,12 @@ class Tetris: def _rotate(self, direction): rotated_minoes_positions = tuple( Point(-direction*mino_position.y, direction*mino_position.x) - for mino_position in self.minoes_position + for mino_position in self.current_piece.minoes_positions ) - for rotation_point, liberty_degree in enumerate(self.SUPER_ROTATION_SYSTEM[self.orientation][direction], start=1): + for rotation_point, liberty_degree in enumerate(self.current_piece.SUPER_ROTATION_SYSTEM[self.current_piece.orientation][direction], start=1): potential_position = self.position + liberty_degree if self._move_rotate(potential_position, rotated_minoes_positions): - self.current_piece.orientation = (self.orientation+direction) % 4 + self.current_piece.orientation = (self.current_piece.orientation+direction) % 4 self.current_piece.minoes_position = rotated_minoes_positions self.current_piece.rotated_last = True if rotation_point == 5: @@ -273,15 +273,15 @@ class Tetris: self._move(Movement.DOWN) def rotate_clockwise(self): - return self.current_piece._rotate(Rotation.CLOCKWISE) + return self._rotate(Rotation.CLOCKWISE) def rotate_counterclockwise(self): - return self.current_piece._rotate(Rotation.COUNTERCLOCKWISE) + return self._rotate(Rotation.COUNTERCLOCKWISE) def is_free_cell(self, position): return ( - 0 <= position.x < self.NB_COLS - and position.y < self.NB_LINES + 0 <= position.x < self.MATRIX_COLS + and position.y < self.MATRIX_ROWS and not (position.y >= 0 and self.matrix[position.y][position.x] != Mino.NO_MINO) ) @@ -359,4 +359,4 @@ class Tetris: self.time = time.time() - self.time def game_over(self): - self.show_text("GAME game_over") + self.show_text("GAME OVER") diff --git a/terminis/terminis.py b/terminis/terminis.py index 21dd1bf..c960ea3 100644 --- a/terminis/terminis.py +++ b/terminis/terminis.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- +from .pytetris import Tetris, Mino, Point + import sys try: @@ -13,7 +15,6 @@ pip install --user windows-curses""" else: curses.COLOR_ORANGE = 8 -import random import sched import time import os @@ -37,26 +38,6 @@ Tetris clone for terminal --level=n\tstart at level n (integer between 1 and 15)""" -class Rotation: - CLOCKWISE = 1 - COUNTERCLOCKWISE = -1 - - -class Point: - def __init__(self, x, y): - self.x = x - self.y = y - - def __add__(self, other): - return Point(self.x+other.x, self.y+other.y) - - -class Movement: - LEFT = Point(-1, 0) - RIGHT = Point(1, 0) - DOWN = Point(0, 1) - - class Scheduler(sched.scheduler, dict): def __init__(self): sched.scheduler.__init__(self, time.time, time.sleep) @@ -88,172 +69,23 @@ class Scheduler(sched.scheduler, dict): scheduler = Scheduler() -class Tetromino: - SUPER_ROTATION_SYSTEM = ( - { - Rotation.COUNTERCLOCKWISE: (Point(0, 0), Point(1, 0), Point(1, -1), Point(0, 2), Point(1, 2)), - Rotation.CLOCKWISE: (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)), - Rotation.CLOCKWISE: (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)), - Rotation.CLOCKWISE: (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)), - Rotation.CLOCKWISE: (Point(0, 0), Point(-1, 0), Point(-1, 1), Point(0, 2), Point(-1, -2)) - } - ) - lock_delay = 0.5 - fall_delay = 1 - color_pair = curses.COLOR_BLACK - - def __init__(self, matrix, position): - self.matrix = matrix - self.position = position - self.minoes_positions = self.MINOES_POSITIONS - self.orientation = 0 - self.rotation_point_5_used = False - self.rotated_last = False - self.hold_enabled = True - - def move_rotate(self, movement, minoes_positions): - potential_position = self.position + movement - if all( - self.matrix.is_free_cell(potential_position+mino_position) - for mino_position in minoes_positions - ): - self.position = potential_position - if "lock" in scheduler: - scheduler.cancel("lock") - scheduler.single_shot("lock", self.lock_delay, self.matrix.lock) - return True - else: - return False - - def move(self, movement, lock=True): - if self.move_rotate(movement, self.minoes_positions): - self.rotated_last = False - return True - else: - if ( - lock - and movement == Movement.DOWN - and "lock" not in scheduler - ): - scheduler.single_shot("lock", self.lock_delay, self.matrix.lock) - return False - - def rotate(self, direction): - rotated_minoes_positions = tuple( - Point(-direction*mino_position.y, direction*mino_position.x) - for mino_position in self.minoes_positions - ) - for rotation_point, liberty_degree in enumerate(self.SUPER_ROTATION_SYSTEM[self.orientation][direction], start=1): - if self.move_rotate(liberty_degree, rotated_minoes_positions): - self.minoes_positions = rotated_minoes_positions - self.orientation = (self.orientation+direction) % 4 - self.rotated_last = False - if rotation_point == 5: - self.rotation_point_5_used = True - return True - else: - return False - - def soft_drop(self): - if self.move(Movement.DOWN): - self.matrix.game.stats.piece_dropped(1) - - def hard_drop(self): - lines = 0 - while self.move(Movement.DOWN, lock=False): - lines += 2 - self.matrix.game.stats.piece_dropped(lines) - self.matrix.lock() - - def fall(self): - if self.move(Movement.DOWN): - self.matrix.refresh() - - def t_spin(self): - return "" - - -class O(Tetromino): - SUPER_ROTATION_SYSTEM = tuple() - MINOES_POSITIONS = (Point(0, 0), Point(1, 0), Point(0, -1), Point(1, -1)) - COLOR = curses.COLOR_YELLOW - - def rotate(self, direction): - return False - -class I(Tetromino): - SUPER_ROTATION_SYSTEM = ( - { - Rotation.COUNTERCLOCKWISE: (Point(0, 1), Point(-1, 1), Point(2, 1), Point(-1, -1), Point(2, 2)), - Rotation.CLOCKWISE: (Point(1, 0), Point(-1, 0), Point(2, 0), Point(-1, 1), Point(2, -2)), - }, - { - Rotation.COUNTERCLOCKWISE: (Point(-1, 0), Point(1, 0), Point(-2, 0), Point(1, -1), Point(-2, 2)), - Rotation.CLOCKWISE: (Point(0, 1), Point(-1, 1), Point(2, 1), Point(-1, -1), Point(2, 2)), - }, - { - Rotation.COUNTERCLOCKWISE: (Point(0, -1), Point(1, -1), Point(-2, -1), Point(1, 1), Point(-2, -2)), - Rotation.CLOCKWISE: (Point(-1, 0), Point(1, 0), Point(-2, 0), Point(1, -1), Point(-2, 2)), - }, - { - Rotation.COUNTERCLOCKWISE: (Point(1, 0), Point(-1, 0), Point(2, 0), Point(-1, 1), Point(2, -2)), - Rotation.CLOCKWISE: (Point(0, 1), Point(1, -1), Point(-2, -1), Point(1, 1), Point(-2, -2)), - }, - ) - MINOES_POSITIONS = (Point(-1, 0), Point(0, 0), Point(1, 0), Point(2, 0)) - COLOR = curses.COLOR_CYAN - -class T(Tetromino): - MINOES_POSITIONS = (Point(-1, 0), Point(0, 0), Point(0, -1), Point(1, 0)) - COLOR = curses.COLOR_MAGENTA - T_SLOT = (Point(-1, -1), Point(1, -1), Point(1, 1), Point(-1, 1)) - - def t_spin(self): - if self.rotated_last: - a = not self.matrix.is_free_cell(self.position+self.T_SLOT[self.orientation]) - b = not self.matrix.is_free_cell(self.position+self.T_SLOT[(1+self.orientation)%4]) - c = not self.matrix.is_free_cell(self.position+self.T_SLOT[(3+self.orientation)%4]) - d = not self.matrix.is_free_cell(self.position+self.T_SLOT[(2+self.orientation)%4]) - - if self.rotation_point_5_used or (a and b and (c or d)): - return "T-SPIN" - elif c and d and (a or b): - return "MINI T-SPIN" - return "" - -class L(Tetromino): - MINOES_POSITIONS = (Point(-1, 0), Point(0, 0), Point(1, 0), Point(1, -1)) - COLOR = curses.COLOR_ORANGE - -class J(Tetromino): - MINOES_POSITIONS = (Point(-1, -1), Point(-1, 0), Point(0, 0), Point(1, 0)) - COLOR = curses.COLOR_BLUE - -class S(Tetromino): - MINOES_POSITIONS = (Point(-1, 0), Point(0, 0), Point(0, -1), Point(1, -1)) - COLOR = curses.COLOR_GREEN - -class Z(Tetromino): - MINOES_POSITIONS = (Point(-1, -1), Point(0, -1), Point(0, 0), Point(1, 0)) - COLOR = curses.COLOR_RED - - class Window: - def __init__(self, width, height, begin_x, begin_y): + MINO_COLOR = { + Mino.O: 0, + Mino.I: 0, + Mino.T: 0, + Mino.L: 0, + Mino.J: 0, + Mino.S: 0, + Mino.Z: 0 + } + + def __init__(self, game, width, height, begin_x, begin_y): + self.game = game self.window = curses.newwin(height, width, begin_y, begin_x) if self.TITLE: self.title_begin_x = (width-len(self.TITLE)) // 2 + 1 self.piece = None - self.refresh() def draw_border(self): self.window.erase() @@ -261,14 +93,14 @@ class Window: if self.TITLE: self.window.addstr(0, self.title_begin_x, self.TITLE, curses.A_BOLD) - def draw_piece(self): - if self.piece: - if "lock" in scheduler: - attr = self.piece.color_pair | curses.A_BLINK | curses.A_REVERSE + def draw_piece(self, piece, position): + if piece: + if piece.prelocked: + attr = self.MINO_COLOR[piece.MINOES_TYPE] | curses.A_BLINK | curses.A_REVERSE else: - attr = self.piece.color_pair - for mino_position in self.piece.minoes_positions: - position = mino_position + self.piece.position + attr = self.MINO_COLOR[piece.MINOES_TYPE] + for mino_position in piece.minoes_positions: + position = mino_position + position self.draw_mino(position.x, position.y, attr) def draw_mino(self, x, y, attr): @@ -281,19 +113,17 @@ class Matrix(Window): NB_LINES = 21 WIDTH = NB_COLS*2+2 HEIGHT = NB_LINES+1 - PIECE_POSITION = Point(4, -1) TITLE = "" def __init__(self, game, begin_x, begin_y): begin_x += (game.WIDTH - self.WIDTH) // 2 begin_y += (game.HEIGHT - self.HEIGHT) // 2 - self.game = game self.cells = [ [None for x in range(self.NB_COLS)] for y in range(self.NB_LINES) ] self.piece = None - Window.__init__(self, self.WIDTH, self.HEIGHT, begin_x, begin_y) + Window.__init__(self, game, self.WIDTH, self.HEIGHT, begin_x, begin_y) def refresh(self, paused=False): self.draw_border() @@ -304,73 +134,39 @@ class Matrix(Window): for x, color in enumerate(line): if color is not None: self.draw_mino(x, y, color) - self.draw_piece() + self.draw_piece(self.game.current_piece, self.game.current_piece.position) self.window.refresh() - 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] is not None) - ) - - def lock(self): - if not self.piece.move(Movement.DOWN): - scheduler.cancel("fall") - scheduler.cancel("lock") - - t_spin = self.piece.t_spin() - - for mino_position in self.piece.minoes_positions: - position = mino_position + self.piece.position - if position.y >= 0: - self.cells[position.y][position.x] = self.piece.color_pair - else: - self.game.over() - return - - 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, [None for x in range(self.NB_COLS)]) - nb_lines_cleared += 1 - - self.game.stats.piece_locked(nb_lines_cleared, t_spin) - self.piece = None - self.game.new_piece() - class HoldNext(Window): HEIGHT = 6 PIECE_POSITION = Point(6, 3) - def __init__(self, width, begin_x, begin_y): - Window.__init__(self, width, self.HEIGHT, begin_x, begin_y) - - def refresh(self, paused=False): - self.draw_border() - if not paused: - self.draw_piece() - self.window.refresh() + def __init__(self, game, width, begin_x, begin_y): + Window.__init__(self, game, width, self.HEIGHT, begin_x, begin_y) class Hold(HoldNext): TITLE = "HOLD" + def refresh(self, paused=False): + self.draw_border() + if not paused: + self.draw_piece(self.game.held_piece, self.PIECE_POSITION) + self.window.refresh() + class Next(HoldNext): TITLE = "NEXT" + def refresh(self, paused=False): + self.draw_border() + if not paused: + self.draw_piece(self.game.next_queue[0], self.PIECE_POSITION) + self.window.refresh() + class Stats(Window): - SCORES = ( - {"name": "", "": 0, "MINI T-SPIN": 1, "T-SPIN": 4}, - {"name": "SINGLE", "": 1, "MINI T-SPIN": 2, "T-SPIN": 8}, - {"name": "DOUBLE", "": 3, "T-SPIN": 12}, - {"name": "TRIPLE", "": 5, "T-SPIN": 16}, - {"name": "TETRIS", "": 8} - ) TITLE = "STATS" FILE_NAME = ".high_score" if sys.platform == "win32": @@ -381,107 +177,31 @@ class Stats(Window): FILE_PATH = os.path.join(DIR_PATH, FILE_NAME) def __init__(self, game, width, height, begin_x, begin_y): - for arg in sys.argv[1:]: - if arg.startswith("--level="): - try: - self.level = int(arg[8:]) - except ValueError: - sys.exit(HELP_MSG) - else: - self.level = max(1, self.level) - self.level = min(15, self.level) - self.level -= 1 - break - else: - self.level = 0 - - self.game = game self.width = width self.height = height - self.goal = 0 - self.score = 0 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.strings = [] - Window.__init__(self, width, height, begin_x, begin_y) - self.new_level() + Window.__init__(self, game, width, height, begin_x, begin_y) 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) + self.window.addstr(2, 2, "SCORE\t{:n}".format(self.game.score)) + if self.game.score >= self.game.high_score: + self.window.addstr(3, 2, "HIGH\t{:n}".format(self.game.high_score), curses.A_BLINK|curses.A_BOLD) else: - self.window.addstr(3, 2, "HIGH\t{:n}".format(self.high_score)) - 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.addstr(3, 2, "HIGH\t{:n}".format(self.game.high_score)) + self.window.addstr(5, 2, "LEVEL\t%d" % self.game.level) + self.window.addstr(6, 2, "GOAL\t%d" % self.game.goal) self.refresh_time() def refresh_time(self): - t = time.localtime(time.time() - self.time) + t = time.localtime(time.time() - self.game.time) self.window.addstr(4, 2, "TIME\t%02d:%02d:%02d" % (t.tm_hour-1, t.tm_min, t.tm_sec)) self.window.refresh() - def new_level(self): - self.level += 1 - if self.level <= 20: - Tetromino.fall_delay = pow(0.8 - ((self.level-1)*0.007), self.level-1) - if self.level > 15: - Tetromino.lock_delay = 0.5 * pow(0.9, self.level-15) - self.goal += 5 * self.level - self.refresh() - - def piece_dropped(self, lines): - self.score += lines - if self.score > self.high_score: - self.high_score = self.score - self.refresh() - - def piece_locked(self, nb_lines, t_spin): - self.strings = [] - - if t_spin: - self.strings.append(t_spin) - if nb_lines: - self.strings.append(self.SCORES[nb_lines]["name"]) - self.combo += 1 - else: - self.combo = -1 - - if nb_lines or t_spin: - self.lines_cleared += nb_lines - ds = self.SCORES[nb_lines][t_spin] - self.goal -= ds - ds *= 100 * self.level - self.score += ds - self.strings.append(str(ds)) - - if self.combo >= 1: - self.strings.append("COMBO x%d" % self.combo) - ds = (20 if nb_lines==1 else 50) * self.combo * self.level - self.score += ds - self.strings.append(str(ds)) - - if nb_lines == 4 or (nb_lines and t_spin): - curses.beep() - if self.score > self.high_score: - self.high_score = self.score - if self.goal <= 0: - self.new_level() - else: - self.refresh() - def save(self): if not os.path.exists(self.DIR_PATH): os.makedirs(self.DIR_PATH) @@ -562,10 +282,10 @@ class ControlsParser(configparser.SafeConfigParser): class ControlsWindow(Window, ControlsParser): TITLE = "CONTROLS" - def __init__(self, width, height, begin_x, begin_y): + def __init__(self, game, width, height, begin_x, begin_y): ControlsParser.__init__(self) self.read(self.FILE_PATH) - Window.__init__(self, width, height, begin_x, begin_y) + Window.__init__(self, game, width, height, begin_x, begin_y) for action, key in self.items(self.SECTION): if key == "SPACE": self[action] = " " @@ -582,23 +302,45 @@ class ControlsWindow(Window, ControlsParser): self.window.refresh() -class Game: +class Game(Tetris): WIDTH = 80 HEIGHT = Matrix.HEIGHT - AUTOREPEAT_DELAY = 0.02 - TETROMINOES = (O, I, T, L, J, S, Z) - + MINO_COLOR = { + Mino.O: curses.COLOR_YELLOW, + Mino.I: curses.COLOR_CYAN, + Mino.T: curses.COLOR_MAGENTA, + Mino.L: curses.COLOR_ORANGE, + Mino.J: curses.COLOR_BLUE, + Mino.S: curses.COLOR_GREEN, + Mino.Z: curses.COLOR_RED + } + + HIGH_SCORE_FILE_NAME = ".high_score" + if sys.platform == "win32": + DATA_DIR_PATH = os.environ.get("appdata", os.path.expanduser("~\Appdata\Roaming")) + else: + DATA_DIR_PATH = os.environ.get("XDG_DATA_HOME", os.path.expanduser("~/.local/share")) + DATA_DIR_PATH = os.path.join(DATA_DIR_PATH, DIR_NAME) + HIGH_SCORE_FILE_PATH = os.path.join(DATA_DIR_PATH, HIGH_SCORE_FILE_NAME) + def __init__(self, scr): + try: + with open(self.HIGH_SCORE_FILE_PATH, "r") as f: + high_score = int(f.read()) + except: + high_score = 0 + Tetris.__init__(self, high_score) + if curses.has_colors(): curses.start_color() if curses.can_change_color(): curses.init_color(curses.COLOR_YELLOW, 1000, 500, 0) - for tetromino_class in self.TETROMINOES: - curses.init_pair(tetromino_class.COLOR, tetromino_class.COLOR, curses.COLOR_WHITE) - if tetromino_class.COLOR == curses.COLOR_ORANGE: - tetromino_class.color_pair = curses.color_pair(curses.COLOR_YELLOW) + for mino_type, color in self.MINO_COLOR.items(): + curses.init_pair(color, color, curses.COLOR_WHITE) + if color == curses.COLOR_ORANGE: + Window.MINO_COLOR[mino_type] = curses.color_pair(curses.COLOR_YELLOW) else: - tetromino_class.color_pair = curses.color_pair(tetromino_class.COLOR)|curses.A_BOLD + Window.MINO_COLOR[mino_type] = curses.color_pair(color)|curses.A_BOLD try: curses.curs_set(0) except curses.error: @@ -614,52 +356,59 @@ class Game: right_x = left_x + Matrix.WIDTH + side_width + 2 bottom_y = top_y + Hold.HEIGHT - self.matrix = Matrix(self, left_x, top_y) - self.hold = Hold(side_width, left_x, top_y) - self.next = Next(side_width, right_x, top_y) - self.stats = Stats(self, side_width, side_height, left_x, bottom_y) - self.controls = ControlsWindow(side_width, side_height, right_x, bottom_y) + self.matrix_window = Matrix(self, left_x, top_y) + self.hold_window = Hold(self, side_width, left_x, top_y) + self.next_window = Next(self, side_width, right_x, top_y) + self.stats_window = Stats(self, side_width, side_height, left_x, bottom_y) + self.controls_window = ControlsWindow(self, side_width, side_height, right_x, bottom_y) self.actions = { - self.controls["QUIT"]: self.quit, - self.controls["PAUSE"]: self.pause, - self.controls["HOLD"]: self.swap, - self.controls["MOVE LEFT"]: lambda: self.matrix.piece.move(Movement.LEFT), - self.controls["MOVE RIGHT"]: lambda: self.matrix.piece.move(Movement.RIGHT), - self.controls["SOFT DROP"]: lambda: self.matrix.piece.soft_drop(), - self.controls["ROTATE COUNTER"]: lambda: self.matrix.piece.rotate(Rotation.COUNTERCLOCKWISE), - self.controls["ROTATE CLOCKWISE"]: lambda: self.matrix.piece.rotate(Rotation.CLOCKWISE), - self.controls["HARD DROP"]: lambda: self.matrix.piece.hard_drop() + self.controls_window["QUIT"]: self.quit, + self.controls_window["PAUSE"]: self.pause, + self.controls_window["HOLD"]: self.hold_piece, + self.controls_window["MOVE LEFT"]: self.move_left, + self.controls_window["MOVE RIGHT"]: self.move_right, + self.controls_window["SOFT DROP"]: self.soft_drop, + self.controls_window["ROTATE COUNTER"]: self.rotate_counterclockwise, + self.controls_window["ROTATE CLOCKWISE"]: self.rotate_clockwise, + self.controls_window["HARD DROP"]: self.hard_drop } + self.paused = False - self.random_bag = [] - self.next.piece = self.random_piece() - self.new_piece() - scheduler.repeat("time", 1, self.stats.refresh_time) - scheduler.repeat("input", self.AUTOREPEAT_DELAY, self.process_input) + + for arg in sys.argv[1:]: + if arg.startswith("--level="): + try: + level = int(arg[8:]) + except ValueError: + sys.exit(HELP_MSG) + else: + level = max(1, level) + level = min(15, level) + break + else: + level = 1 + self.new_game(level) + + self.matrix_window.refresh() + self.hold_window.refresh() + self.next_window.refresh() + self.stats_window.refresh() + self.controls_window.refresh() + + scheduler.repeat("time", 1, self.stats_window.refresh_time) + scheduler.repeat("input", self.AUTOSHIFT_DELAY, self.process_input) try: scheduler.run() except KeyboardInterrupt: self.quit() - def random_piece(self): - if not self.random_bag: - self.random_bag = list(self.TETROMINOES) - random.shuffle(self.random_bag) - return self.random_bag.pop()(self.matrix, Next.PIECE_POSITION) - def new_piece(self): - if not self.matrix.piece: - self.matrix.piece, self.next.piece = self.next.piece, self.random_piece() - self.next.refresh() - self.matrix.piece.position = Matrix.PIECE_POSITION - if self.matrix.piece.move(Movement.DOWN): - scheduler.repeat("fall", Tetromino.fall_delay, self.matrix.piece.fall) - self.matrix.refresh() - else: - self.over() + Tetris.new_piece(self) + self.next_window.refresh() + self.matrix_window.refresh() def process_input(self): try: @@ -668,71 +417,64 @@ class Game: pass else: action() - self.matrix.refresh() + self.matrix_window.refresh() def pause(self): - self.stats.time = time.time() - self.stats.time + Tetris.pause(self) self.paused = True - self.hold.refresh(paused=True) - self.matrix.refresh(paused=True) - self.next.refresh(paused=True) + self.hold_window.refresh(paused=True) + self.matrix_window.refresh(paused=True) + self.next_window.refresh(paused=True) self.scr.timeout(-1) while True: key = self.scr.getkey() - if key == self.controls["QUIT"]: + if key == self.controls_window["QUIT"]: self.quit() break - elif key == self.controls["PAUSE"]: + elif key == self.controls_window["PAUSE"]: self.scr.timeout(0) - self.hold.refresh() - self.matrix.refresh() - self.next.refresh() - self.stats.time = time.time() - self.stats.time + self.hold_window.refresh() + self.matrix_window.refresh() + self.next_window.refresh() + self.stats_window.time = time.time() - self.stats_window.time break - def swap(self): - if self.matrix.piece.hold_enabled: - scheduler.cancel("fall") - scheduler.cancel("lock") - self.matrix.piece, self.hold.piece = self.hold.piece, self.matrix.piece - self.hold.piece.position = self.hold.PIECE_POSITION - self.hold.piece.minoes_positions = self.hold.piece.MINOES_POSITIONS - self.hold.piece.hold_enabled = False - self.hold.refresh() - self.new_piece() + def hold_piece(self): + Tetris.hold_piece(self) + self.hold_window.refresh() - def over(self): - self.stats.time = time.time() - self.stats.time - self.matrix.refresh() + def game_over(self): + Tetris.game_over(self) + self.time = time.time() - self.time + self.matrix_window.refresh() if curses.has_colors(): - for tetromino_class in self.TETROMINOES: - curses.init_pair(tetromino_class.COLOR, tetromino_class.COLOR, curses.COLOR_BLACK) + for color in self.MINO_COLOR.values(): + curses.init_pair(color, color, curses.COLOR_BLACK) for y, word in enumerate((("GA", "ME") ,("OV", "ER")), start=Matrix.NB_LINES//2): for x, syllable in enumerate(word, start=Matrix.NB_COLS//2-1): - color = self.matrix.cells[y][x] + color = self.matrix[y][x] if color is None: color = curses.COLOR_BLACK else: color |= curses.A_REVERSE - self.matrix.window.addstr(y, x*2+1, syllable, color) - self.matrix.window.refresh() + self.matrix_window.window.addstr(y, x*2+1, syllable, color) + self.matrix_window.window.refresh() curses.beep() self.scr.timeout(-1) - while self.scr.getkey() != self.controls["QUIT"]: + while self.scr.getkey() != self.controls_window["QUIT"]: pass - self.stats.time = time.time() - self.stats.time + self.time = time.time() - self.time self.quit() def quit(self): - self.stats.save() - t = time.localtime(time.time() - self.stats.time) + self.stats_window.save() + t = time.localtime(time.time() - self.time) sys.exit( - "SCORE\t{:n}\n".format(self.stats.score) + - "HIGH\t{:n}\n".format(self.stats.high_score) + + "SCORE\t{:n}\n".format(self.score) + + "HIGH\t{:n}\n".format(self.high_score) + "TIME\t%02d:%02d:%02d\n" % (t.tm_hour-1, t.tm_min, t.tm_sec) + - "LEVEL\t%d\n" % self.stats.level + - "LINES\t%d" % self.stats.lines_cleared + "LEVEL\t%d\n" % self.level )