From adf005bd29a395b653df1d990aa72694360a11dc Mon Sep 17 00:00:00 2001 From: Olivier Gayot Date: Fri, 12 Nov 2021 13:32:37 +0100 Subject: Rename the package cameltris and provide __main__.py Signed-off-by: Olivier Gayot --- __main__.py | 57 ++++++++ cameltris.py | 57 -------- cameltris/controller.py | 105 ++++++++++++++ cameltris/misc.py | 6 + cameltris/piece.py | 128 +++++++++++++++++ cameltris/screens/InGame.py | 326 ++++++++++++++++++++++++++++++++++++++++++ cameltris/screens/Pause.py | 39 +++++ cameltris/screens/Screen.py | 12 ++ pycameltris/controller.py | 105 -------------- pycameltris/misc.py | 6 - pycameltris/piece.py | 128 ----------------- pycameltris/screens/InGame.py | 326 ------------------------------------------ pycameltris/screens/Pause.py | 39 ----- pycameltris/screens/Screen.py | 12 -- 14 files changed, 673 insertions(+), 673 deletions(-) create mode 100755 __main__.py delete mode 100755 cameltris.py create mode 100644 cameltris/controller.py create mode 100644 cameltris/misc.py create mode 100644 cameltris/piece.py create mode 100644 cameltris/screens/InGame.py create mode 100644 cameltris/screens/Pause.py create mode 100644 cameltris/screens/Screen.py delete mode 100644 pycameltris/controller.py delete mode 100644 pycameltris/misc.py delete mode 100644 pycameltris/piece.py delete mode 100644 pycameltris/screens/InGame.py delete mode 100644 pycameltris/screens/Pause.py delete mode 100644 pycameltris/screens/Screen.py diff --git a/__main__.py b/__main__.py new file mode 100755 index 0000000..e823054 --- /dev/null +++ b/__main__.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 + +import argparse + +import pygame + +from cameltris.controller import Controller, KeyboardController, JoystickController +from cameltris.screens.Screen import Screen +from cameltris.screens.InGame import InGame as InGameScreen, Player +from cameltris.screens.Pause import Pause as PauseScreen +from cameltris.misc import Pause, UnPause + + +PARSER = argparse.ArgumentParser() + +PARSER.add_argument("--starting-level", type=int, choices=list(range(1, 30)), default=1) +PARSER.add_argument("--joystick", type=int, dest="joystick_id", metavar="Joystick ID") + +ARGS = vars(PARSER.parse_args()) + +print(ARGS) + +pygame.init() + +screen = pygame.display.set_mode((801, 1000)) + +controller: Controller +if ARGS["joystick_id"] is not None: + joystick = pygame.joystick.Joystick(ARGS["joystick_id"]) + joystick.init() + controller = JoystickController(joystick) +else: + controller = KeyboardController(pygame.key) + +# Just one player +players = [Player(controller, ARGS["starting_level"])] + +for player in players: + player.refresh_piece_preview_canvas() + +clock = pygame.time.Clock() + +current_screen: Screen +current_screen = ingame_screen = InGameScreen(players, screen) + +while True: + try: + current_screen.oneframe() + except Pause: + current_screen = PauseScreen(screen) + except UnPause: + current_screen = ingame_screen + + current_screen.refresh() + pygame.display.update() + + clock.tick(60) diff --git a/cameltris.py b/cameltris.py deleted file mode 100755 index 50ebb01..0000000 --- a/cameltris.py +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env python3 - -import argparse - -import pygame - -from pycameltris.controller import Controller, KeyboardController, JoystickController -from pycameltris.screens.Screen import Screen -from pycameltris.screens.InGame import InGame as InGameScreen, Player -from pycameltris.screens.Pause import Pause as PauseScreen -from pycameltris.misc import Pause, UnPause - - -PARSER = argparse.ArgumentParser() - -PARSER.add_argument("--starting-level", type=int, choices=list(range(1, 30)), default=1) -PARSER.add_argument("--joystick", type=int, dest="joystick_id", metavar="Joystick ID") - -ARGS = vars(PARSER.parse_args()) - -print(ARGS) - -pygame.init() - -screen = pygame.display.set_mode((801, 1000)) - -controller: Controller -if ARGS["joystick_id"] is not None: - joystick = pygame.joystick.Joystick(ARGS["joystick_id"]) - joystick.init() - controller = JoystickController(joystick) -else: - controller = KeyboardController(pygame.key) - -# Just one player -players = [Player(controller, ARGS["starting_level"])] - -for player in players: - player.refresh_piece_preview_canvas() - -clock = pygame.time.Clock() - -current_screen: Screen -current_screen = ingame_screen = InGameScreen(players, screen) - -while True: - try: - current_screen.oneframe() - except Pause: - current_screen = PauseScreen(screen) - except UnPause: - current_screen = ingame_screen - - current_screen.refresh() - pygame.display.update() - - clock.tick(60) diff --git a/cameltris/controller.py b/cameltris/controller.py new file mode 100644 index 0000000..86f5bcb --- /dev/null +++ b/cameltris/controller.py @@ -0,0 +1,105 @@ +import abc +import enum + +import pygame + + +class Input(enum.Enum): + ROTATE_CLOCKWISE = 0 + ROTATE_COUNTER_CLOCKWISE = 1 + MOVE_LEFT = 2 + MOVE_RIGHT = 3 + MOVE_DOWN = 4 + PAUSE = 5 + QUIT = 6 + + +class Controller(abc.ABC): + @abc.abstractmethod + def is_pressed(self, input_: Input): + pass + + @abc.abstractmethod + def get_input_down(self, event): + pass + + @abc.abstractmethod + def get_input_up(self, event): + pass + + +class JoystickController(Controller): + + class PS3Controller(enum.Enum): + CROSS = 0 + CIRCLE = 1 + TRIANGLE = 2 + SQUARE = 3 + START = 9 + DOWN = 14 + LEFT = 15 + RIGHT = 16 + + def __init__(self, joystick): + self.joystick = joystick + self.mapping = { + Input.ROTATE_CLOCKWISE: JoystickController.PS3Controller.CROSS, + Input.ROTATE_COUNTER_CLOCKWISE: JoystickController.PS3Controller.CIRCLE, + Input.MOVE_LEFT: JoystickController.PS3Controller.LEFT, + Input.MOVE_RIGHT: JoystickController.PS3Controller.RIGHT, + Input.MOVE_DOWN: JoystickController.PS3Controller.DOWN, + Input.PAUSE: JoystickController.PS3Controller.START, + } + self.downevent = pygame.JOYBUTTONDOWN + self.upevent = pygame.JOYBUTTONUP + + def is_pressed(self, input_: Input): + return self.joystick.get_button(self.mapping[input_].value) + + + def get_input_down(self, event): + for key, value in self.mapping.items(): + if value.value == event.button: + return key + + return None + + def get_input_up(self, event): + for key, value in self.mapping.items(): + if value.value == event.button: + return key + + return None + + +class KeyboardController(Controller): + def __init__(self, keyboard): + self.keyboard = keyboard + self.mapping = { + Input.ROTATE_CLOCKWISE: pygame.K_f, + Input.ROTATE_COUNTER_CLOCKWISE: pygame.K_d, + Input.MOVE_LEFT: pygame.K_h, + Input.MOVE_RIGHT: pygame.K_l, + Input.MOVE_DOWN: pygame.K_j, + Input.QUIT: pygame.K_q, + Input.PAUSE: pygame.K_RETURN, + } + self.downevent = pygame.KEYDOWN + self.upevent = pygame.KEYUP + + def is_pressed(self, input_: Input): + return self.keyboard.get_pressed()[self.mapping[input_]] + + def get_input_down(self, event): + for key, value in self.mapping.items(): + if value == event.key: + return key + + return None + + def get_input_up(self, event): + for key, value in self.mapping.items(): + if value == event.key: + return key + + return None diff --git a/cameltris/misc.py b/cameltris/misc.py new file mode 100644 index 0000000..11c2358 --- /dev/null +++ b/cameltris/misc.py @@ -0,0 +1,6 @@ +class Pause(Exception): + pass + +class UnPause(Exception): + pass + diff --git a/cameltris/piece.py b/cameltris/piece.py new file mode 100644 index 0000000..2f3e7ce --- /dev/null +++ b/cameltris/piece.py @@ -0,0 +1,128 @@ +import pygame + + +square_template = pygame.Surface((48, 48)) + +black = (0, 0, 0) +white = (0xff, 0xff, 0xff) +brown = (163, 75, 31) +blue = (30, 34, 164) +green = (30, 164, 59) +red = (164, 30, 30) +purple = (126, 30, 164) +yellow = (164, 164, 30) +cyan = (30, 164, 150) + + +class Piece: + def __init__(self): + self.square = square_template.copy() + + def rotate_clockwise(self): + self.elements = list(zip(*self.elements[::-1])) + + def rotate_counter_clockwise(self): + self.rotate_clockwise() + self.rotate_clockwise() + self.rotate_clockwise() + + +class ZPiece(Piece): + def __init__(self): + super().__init__() + + self.square.fill(blue) + self.elements = (self.square, self.square, None), (None, self.square, self.square), (None, None, None) + self.vertical = False + + def rotate_clockwise(self): + self.rotate() + + def rotate_counter_clockwise(self): + self.rotate() + + def rotate(self): + if self.vertical: + self.elements = (self.square, self.square, None), (None, self.square, self.square), (None, None, None) + else: + self.elements = (None, None, self.square), (None, self.square, self.square), (None, self.square, None) + + self.vertical = not self.vertical + + +class SPiece(Piece): + def __init__(self): + super().__init__() + + self.square.fill(green) + self.elements = (None, None, None), (None, self.square, self.square), (self.square, self.square, None) + self.vertical = False + + def rotate_clockwise(self): + self.rotate() + + def rotate_counter_clockwise(self): + self.rotate() + + def rotate(self): + if self.vertical: + self.elements = (None, self.square, self.square), (self.square, self.square, None), (None, None, None) + else: + self.elements = (None, self.square, None), (None, self.square, self.square), (None, None, self.square) + + self.vertical = not self.vertical + + +class SquarePiece(Piece): + def __init__(self): + super().__init__() + + self.square.fill(brown) + self.elements = ((self.square, self.square), (self.square, self.square)) + + +class IPiece(Piece): + def __init__(self): + super().__init__() + + self.square.fill(red) + self.elements = (None, None, None, None), (None, None, None, None), (self.square, self.square, self.square, self.square), (None, None, None, None) + self.vertical = False + + def rotate_clockwise(self): + self.rotate() + + def rotate_counter_clockwise(self): + self.rotate() + + def rotate(self): + if self.vertical: + self.elements = (None, None, None, None), (None, None, None, None), (self.square, self.square, self.square, self.square), (None, None, None, None) + else: + self.elements = (None, None, self.square, None), (None, None, self.square, None), (None, None, self.square, None), (None, None, self.square, None) + + self.vertical = not self.vertical + + +class LPiece(Piece): + def __init__(self): + super().__init__() + + self.square.fill(cyan) + self.elements = (None, None, None), (self.square, self.square, self.square), (None, None, self.square) + + +class JPiece(Piece): + def __init__(self): + super().__init__() + + self.square.fill(purple) + self.elements = (None, None, None), (self.square, self.square, self.square), (self.square, None, None) + + +class TPiece(Piece): + def __init__(self): + super().__init__() + + self.square.fill(yellow) + self.elements = (None, None, None), (self.square, self.square, self.square), (None, self.square, None) diff --git a/cameltris/screens/InGame.py b/cameltris/screens/InGame.py new file mode 100644 index 0000000..93a1d47 --- /dev/null +++ b/cameltris/screens/InGame.py @@ -0,0 +1,326 @@ +import contextlib +from functools import partial +import random +import sys +from typing import Callable, NoReturn, Optional + +import pygame + +from .Screen import Screen +from ..piece import * +from ..controller import Input, Controller, KeyboardController, JoystickController +from ..misc import Pause + + +class WouldCollide(Exception): + pass + +class PlayerQuit(Exception): + pass + +right_pane_canvas = pygame.Surface((300, 1000)) +right_pane_canvas.fill((255, 255, 255)) + +class Player: + def __init__(self, controller: Controller, starting_level: int): + self.controller = controller + self.grid = [[None for _ in range(10)] for _ in range(20)] + + self.current_piece, self.current_piece_position = self.generate_piece() + self.next_piece, self.next_piece_position = self.generate_piece() + + self.level = self.starting_level = starting_level + self.score = 0 + + self.lines_burnt = 0 + + self.das = 0 + self.pressing_down_countdown: Optional[int] = None + + self.piece_drop_frames = 0 + + self.grid_canvas = pygame.Surface((500, 1000)) + self.piece_preview_canvas = pygame.Surface((200, 200)) + self.score_canvas = pygame.Surface((296, 50)) + self.level_canvas = pygame.Surface((296, 50)) + + + def generate_piece(self) -> tuple[Piece, list[int]]: + # We may want to make this a function outside the class + piece = random.choice((TPiece, SPiece, IPiece, ZPiece, SquarePiece, LPiece, JPiece))() + + for row_id, row in enumerate(piece.elements): + if list(filter(lambda x: x is not None, row)): + break + + initial_y_position = -row_id + initial_x_position = (len(self.grid[0]) // 2) - (len(piece.elements[0]) // 2) + + return (piece, [initial_y_position, initial_x_position]) + + def burn_rows(self) -> int: + rows_to_burn = list() + + for row in self.grid: + if all(map(lambda element: element is not None, row)): + rows_to_burn.append(row) + + for row in rows_to_burn: + self.grid.insert(0, [None for _ in range(10)]) + self.grid.remove(row) + + return len(rows_to_burn) + + def lock_piece(self) -> None: + if self.has_collision(self.current_piece_position[0], self.current_piece_position[1]): + raise WouldCollide() + + for row_id, row in enumerate(self.current_piece.elements): + for col_id, element in enumerate(row): + if element is None: + continue + self.grid[row_id + self.current_piece_position[0]][col_id + self.current_piece_position[1]] = element + + count = self.burn_rows() + + if count == 1: + print("Single") + rate = 1. + elif count == 2: + print("Double") + rate = 2.5 + elif count == 3: + print("Triple") + rate = 7.5 + elif count == 4: + print("Tetris!") + rate = 30. + else: + rate = 0. + + self.lines_burnt += count + + self.score += int(self.level * 40 * rate) + + if self.lines_burnt >= self.level * 10: + self.level += 1 + self.current_piece, self.current_piece_position = self.next_piece, self.next_piece_position + self.next_piece, self.next_piece_position = self.generate_piece() + self.refresh_piece_preview_canvas() + + def has_collision(self, y: int, x: int) -> bool: + try: + for row_id, row in enumerate(self.current_piece.elements): + for col_id, element in enumerate(row): + if element is None: + continue + + if row_id + y < 0: + continue + + if col_id + x < 0: + return True + + if self.grid[row_id + y][col_id + x] is not None: + return True + + except IndexError: + return True + + return False + + def move_piece_down(self) -> None: + if not self.has_collision(self.current_piece_position[0] + 1, self.current_piece_position[1]): + self.current_piece_position[0] += 1 + else: + raise WouldCollide() + + def move_piece_up(self) -> None: + if not self.has_collision(self.current_piece_position[0] - 1, self.current_piece_position[1]): + self.current_piece_position[0] -= 1 + else: + raise WouldCollide() + + def move_piece_left(self) -> None: + if not self.has_collision(self.current_piece_position[0], self.current_piece_position[1] - 1): + self.current_piece_position[1] -= 1 + else: + raise WouldCollide() + + def move_piece_right(self) -> None: + if not self.has_collision(self.current_piece_position[0], self.current_piece_position[1] + 1): + self.current_piece_position[1] += 1 + else: + raise WouldCollide() + + def rotate_piece_counter_clockwise(self) -> None: + self.current_piece.rotate_counter_clockwise() + + if self.has_collision(self.current_piece_position[0], self.current_piece_position[1]): + self.current_piece.rotate_clockwise() + raise WouldCollide() + + def rotate_piece_clockwise(self) -> None: + self.current_piece.rotate_clockwise() + + if self.has_collision(self.current_piece_position[0], self.current_piece_position[1]): + self.current_piece.rotate_counter_clockwise() + raise WouldCollide() + + def handle_input_pressed(self, event: pygame.event.Event) -> None: + if self.controller.get_input_down(event) == Input.QUIT: + raise PlayerQuit() + + if self.controller.get_input_down(event) == Input.PAUSE: + raise Pause() + + with contextlib.suppress(WouldCollide): + if self.controller.get_input_down(event) == Input.MOVE_RIGHT: + self.move_piece_right() + self.das = 0 + if self.controller.get_input_down(event) == Input.MOVE_LEFT: + self.move_piece_left() + self.das = 0 + if self.controller.get_input_down(event) == Input.ROTATE_CLOCKWISE: + self.rotate_piece_clockwise() + if self.controller.get_input_down(event) == Input.ROTATE_COUNTER_CLOCKWISE: + self.rotate_piece_counter_clockwise() + if self.controller.get_input_down(event) == Input.MOVE_DOWN: + self.piece_drop_frames = 0 + self.pressing_down_countdown = 3 + try: + self.move_piece_down() + except WouldCollide: + self.lock_piece() + + def handle_input_released(self, event: pygame.event.Event) -> None: + if self.controller.get_input_up(event) == Input.MOVE_DOWN: + self.pressing_down_countdown = None + + def refresh_piece_preview_canvas(self) -> None: + self.piece_preview_canvas.fill(black) + + non_empty_rows = list() + for row in self.next_piece.elements: + if any(map(lambda element: element is not None, row)): + non_empty_rows.append(row) + + non_empty_cols = set() + for row in self.next_piece.elements: + for col_id, element in enumerate(row): + if element is not None: + non_empty_cols.add(col_id) + + y_offset = (4 - len(non_empty_rows)) / 2 + x_offset = (4 - len(non_empty_cols)) / 2 + + # Display the next piece + for row_idx, row in enumerate(non_empty_rows): + for col_idx, element in enumerate(row): + if element is not None: + self.piece_preview_canvas.blit(element, ((col_idx + x_offset) * 50 + 1, (row_idx + y_offset) * 50 + 1)) + + def refresh_grid_canvas(self) -> None: + self.grid_canvas.fill(black) + + for row_idx, row in enumerate(self.grid): + for col_idx, element in enumerate(row): + if element is not None: + self.grid_canvas.blit(element, (col_idx * 50 + 1, row_idx * 50 + 1)) + + # Display the current piece + for row_idx, row in enumerate(self.current_piece.elements): + for col_idx, element in enumerate(row): + if element is not None: + self.grid_canvas.blit(element, ((col_idx + self.current_piece_position[1]) * 50 + 1, (row_idx + self.current_piece_position[0]) * 50 + 1)) + +def handle_input_pressed(instance, players: list[Player], event: pygame.event.Event) -> None: + for player in players: + if isinstance(player.controller, instance): + player.handle_input_pressed(event) + + +def handle_input_released(instance, players: list[Player], event: pygame.event.Event) -> None: + for player in players: + if isinstance(player.controller, instance): + player.handle_input_released(event) + +# Number of frames +frames_per_gridcell = [48, 43, 38, 33, 28, 23, 18, 13, 8, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1] + + +class InGame(Screen): + def __init__(self, players: list[Player], screen: pygame.surface.Surface): + self.players: list[Player] = players + self.screen: pygame.surface.Surface = screen + self.event_handler: dict[int, Callable[[pygame.event.Event], None]] = {} + + def exit(_) -> NoReturn: + sys.exit() + + self.event_handler[pygame.QUIT] = exit + self.event_handler[pygame.KEYDOWN] = partial(handle_input_pressed, KeyboardController, self.players) + self.event_handler[pygame.KEYUP] = partial(handle_input_released, KeyboardController, self.players) + self.event_handler[pygame.JOYBUTTONDOWN] = partial(handle_input_pressed, JoystickController, self.players) + self.event_handler[pygame.JOYBUTTONUP] = partial(handle_input_released, JoystickController, self.players) + + def refresh_right_pane_canvas(self) -> None: + for player in self.players: + player.level_canvas.fill(black) + player.score_canvas.fill(black) + + if pygame.font: + score_font = pygame.font.Font(None, 56) + + player.score_canvas.blit(score_font.render(f"{player.score:08d}", True, white), (0, 0)) + player.level_canvas.blit(score_font.render(f"{player.level:08d}", True, white), (0, 0)) + right_pane_canvas.blit(player.score_canvas, (2, 10)) + right_pane_canvas.blit(player.level_canvas, (2, 70)) + + right_pane_canvas.blit(player.piece_preview_canvas, (50, 200)) + + + def refresh(self) -> None: + for player in self.players: + player.refresh_grid_canvas() + self.screen.blit(player.grid_canvas, (0, 0)) + self.refresh_right_pane_canvas() + self.screen.blit(right_pane_canvas, (501, 0)) + + def oneframe(self) -> None: + for player in self.players: + player.piece_drop_frames += 1 + + try: + for event in pygame.event.get(): + with contextlib.suppress(KeyError): + self.event_handler[event.type](event) + except Pause: + pygame.event.clear() + raise + + for player in self.players: + player.das += 1 + if player.das == 16: + with contextlib.suppress(WouldCollide): + if player.controller.is_pressed(Input.MOVE_RIGHT): + player.move_piece_right() + if player.controller.is_pressed(Input.MOVE_LEFT): + player.move_piece_left() + player.das = 10 + + if player.pressing_down_countdown == 0: + try: + player.move_piece_down() + except WouldCollide: + player.lock_piece() + player.pressing_down_countdown = 2 + elif player.pressing_down_countdown is not None: + player.pressing_down_countdown -= 1 + + if player.piece_drop_frames >= frames_per_gridcell[player.level - 1]: + player.piece_drop_frames = 0 + try: + player.move_piece_down() + except WouldCollide: + player.lock_piece() diff --git a/cameltris/screens/Pause.py b/cameltris/screens/Pause.py new file mode 100644 index 0000000..766a45c --- /dev/null +++ b/cameltris/screens/Pause.py @@ -0,0 +1,39 @@ +import contextlib +import sys +from typing import Callable, NoReturn + +import pygame + +from .Screen import Screen +from ..controller import Controller, KeyboardController, JoystickController, Input +from ..misc import UnPause + + +def handle_input_pressed(controller: Controller, event: pygame.event.Event): + if controller.get_input_down(event) == Input.PAUSE: + raise UnPause() + + +class Pause(Screen): + def __init__(self, screen: pygame.surface.Surface): + self.event_handler: dict[int, Callable[[pygame.event.Event], None]] = {} + + def exit(_) -> NoReturn: + sys.exit() + + self.event_handler[pygame.QUIT] = exit + self.event_handler[pygame.JOYBUTTONDOWN] = lambda evt: handle_input_pressed(JoystickController(evt.joy), evt) + self.event_handler[pygame.KEYDOWN] = lambda evt: handle_input_pressed(KeyboardController(pygame.key), evt) + + + def refresh(self) -> None: + pass + + def oneframe(self) -> None: + try: + for event in pygame.event.get(): + with contextlib.suppress(KeyError): + self.event_handler[event.type](event) + except UnPause: + pygame.event.clear() + raise diff --git a/cameltris/screens/Screen.py b/cameltris/screens/Screen.py new file mode 100644 index 0000000..419c1d2 --- /dev/null +++ b/cameltris/screens/Screen.py @@ -0,0 +1,12 @@ +import abc + + +class Screen(abc.ABC): + @abc.abstractmethod + def refresh(self) -> None: + pass + + @abc.abstractmethod + def oneframe(self) -> None: + pass + diff --git a/pycameltris/controller.py b/pycameltris/controller.py deleted file mode 100644 index 86f5bcb..0000000 --- a/pycameltris/controller.py +++ /dev/null @@ -1,105 +0,0 @@ -import abc -import enum - -import pygame - - -class Input(enum.Enum): - ROTATE_CLOCKWISE = 0 - ROTATE_COUNTER_CLOCKWISE = 1 - MOVE_LEFT = 2 - MOVE_RIGHT = 3 - MOVE_DOWN = 4 - PAUSE = 5 - QUIT = 6 - - -class Controller(abc.ABC): - @abc.abstractmethod - def is_pressed(self, input_: Input): - pass - - @abc.abstractmethod - def get_input_down(self, event): - pass - - @abc.abstractmethod - def get_input_up(self, event): - pass - - -class JoystickController(Controller): - - class PS3Controller(enum.Enum): - CROSS = 0 - CIRCLE = 1 - TRIANGLE = 2 - SQUARE = 3 - START = 9 - DOWN = 14 - LEFT = 15 - RIGHT = 16 - - def __init__(self, joystick): - self.joystick = joystick - self.mapping = { - Input.ROTATE_CLOCKWISE: JoystickController.PS3Controller.CROSS, - Input.ROTATE_COUNTER_CLOCKWISE: JoystickController.PS3Controller.CIRCLE, - Input.MOVE_LEFT: JoystickController.PS3Controller.LEFT, - Input.MOVE_RIGHT: JoystickController.PS3Controller.RIGHT, - Input.MOVE_DOWN: JoystickController.PS3Controller.DOWN, - Input.PAUSE: JoystickController.PS3Controller.START, - } - self.downevent = pygame.JOYBUTTONDOWN - self.upevent = pygame.JOYBUTTONUP - - def is_pressed(self, input_: Input): - return self.joystick.get_button(self.mapping[input_].value) - - - def get_input_down(self, event): - for key, value in self.mapping.items(): - if value.value == event.button: - return key - - return None - - def get_input_up(self, event): - for key, value in self.mapping.items(): - if value.value == event.button: - return key - - return None - - -class KeyboardController(Controller): - def __init__(self, keyboard): - self.keyboard = keyboard - self.mapping = { - Input.ROTATE_CLOCKWISE: pygame.K_f, - Input.ROTATE_COUNTER_CLOCKWISE: pygame.K_d, - Input.MOVE_LEFT: pygame.K_h, - Input.MOVE_RIGHT: pygame.K_l, - Input.MOVE_DOWN: pygame.K_j, - Input.QUIT: pygame.K_q, - Input.PAUSE: pygame.K_RETURN, - } - self.downevent = pygame.KEYDOWN - self.upevent = pygame.KEYUP - - def is_pressed(self, input_: Input): - return self.keyboard.get_pressed()[self.mapping[input_]] - - def get_input_down(self, event): - for key, value in self.mapping.items(): - if value == event.key: - return key - - return None - - def get_input_up(self, event): - for key, value in self.mapping.items(): - if value == event.key: - return key - - return None diff --git a/pycameltris/misc.py b/pycameltris/misc.py deleted file mode 100644 index 11c2358..0000000 --- a/pycameltris/misc.py +++ /dev/null @@ -1,6 +0,0 @@ -class Pause(Exception): - pass - -class UnPause(Exception): - pass - diff --git a/pycameltris/piece.py b/pycameltris/piece.py deleted file mode 100644 index 2f3e7ce..0000000 --- a/pycameltris/piece.py +++ /dev/null @@ -1,128 +0,0 @@ -import pygame - - -square_template = pygame.Surface((48, 48)) - -black = (0, 0, 0) -white = (0xff, 0xff, 0xff) -brown = (163, 75, 31) -blue = (30, 34, 164) -green = (30, 164, 59) -red = (164, 30, 30) -purple = (126, 30, 164) -yellow = (164, 164, 30) -cyan = (30, 164, 150) - - -class Piece: - def __init__(self): - self.square = square_template.copy() - - def rotate_clockwise(self): - self.elements = list(zip(*self.elements[::-1])) - - def rotate_counter_clockwise(self): - self.rotate_clockwise() - self.rotate_clockwise() - self.rotate_clockwise() - - -class ZPiece(Piece): - def __init__(self): - super().__init__() - - self.square.fill(blue) - self.elements = (self.square, self.square, None), (None, self.square, self.square), (None, None, None) - self.vertical = False - - def rotate_clockwise(self): - self.rotate() - - def rotate_counter_clockwise(self): - self.rotate() - - def rotate(self): - if self.vertical: - self.elements = (self.square, self.square, None), (None, self.square, self.square), (None, None, None) - else: - self.elements = (None, None, self.square), (None, self.square, self.square), (None, self.square, None) - - self.vertical = not self.vertical - - -class SPiece(Piece): - def __init__(self): - super().__init__() - - self.square.fill(green) - self.elements = (None, None, None), (None, self.square, self.square), (self.square, self.square, None) - self.vertical = False - - def rotate_clockwise(self): - self.rotate() - - def rotate_counter_clockwise(self): - self.rotate() - - def rotate(self): - if self.vertical: - self.elements = (None, self.square, self.square), (self.square, self.square, None), (None, None, None) - else: - self.elements = (None, self.square, None), (None, self.square, self.square), (None, None, self.square) - - self.vertical = not self.vertical - - -class SquarePiece(Piece): - def __init__(self): - super().__init__() - - self.square.fill(brown) - self.elements = ((self.square, self.square), (self.square, self.square)) - - -class IPiece(Piece): - def __init__(self): - super().__init__() - - self.square.fill(red) - self.elements = (None, None, None, None), (None, None, None, None), (self.square, self.square, self.square, self.square), (None, None, None, None) - self.vertical = False - - def rotate_clockwise(self): - self.rotate() - - def rotate_counter_clockwise(self): - self.rotate() - - def rotate(self): - if self.vertical: - self.elements = (None, None, None, None), (None, None, None, None), (self.square, self.square, self.square, self.square), (None, None, None, None) - else: - self.elements = (None, None, self.square, None), (None, None, self.square, None), (None, None, self.square, None), (None, None, self.square, None) - - self.vertical = not self.vertical - - -class LPiece(Piece): - def __init__(self): - super().__init__() - - self.square.fill(cyan) - self.elements = (None, None, None), (self.square, self.square, self.square), (None, None, self.square) - - -class JPiece(Piece): - def __init__(self): - super().__init__() - - self.square.fill(purple) - self.elements = (None, None, None), (self.square, self.square, self.square), (self.square, None, None) - - -class TPiece(Piece): - def __init__(self): - super().__init__() - - self.square.fill(yellow) - self.elements = (None, None, None), (self.square, self.square, self.square), (None, self.square, None) diff --git a/pycameltris/screens/InGame.py b/pycameltris/screens/InGame.py deleted file mode 100644 index 93a1d47..0000000 --- a/pycameltris/screens/InGame.py +++ /dev/null @@ -1,326 +0,0 @@ -import contextlib -from functools import partial -import random -import sys -from typing import Callable, NoReturn, Optional - -import pygame - -from .Screen import Screen -from ..piece import * -from ..controller import Input, Controller, KeyboardController, JoystickController -from ..misc import Pause - - -class WouldCollide(Exception): - pass - -class PlayerQuit(Exception): - pass - -right_pane_canvas = pygame.Surface((300, 1000)) -right_pane_canvas.fill((255, 255, 255)) - -class Player: - def __init__(self, controller: Controller, starting_level: int): - self.controller = controller - self.grid = [[None for _ in range(10)] for _ in range(20)] - - self.current_piece, self.current_piece_position = self.generate_piece() - self.next_piece, self.next_piece_position = self.generate_piece() - - self.level = self.starting_level = starting_level - self.score = 0 - - self.lines_burnt = 0 - - self.das = 0 - self.pressing_down_countdown: Optional[int] = None - - self.piece_drop_frames = 0 - - self.grid_canvas = pygame.Surface((500, 1000)) - self.piece_preview_canvas = pygame.Surface((200, 200)) - self.score_canvas = pygame.Surface((296, 50)) - self.level_canvas = pygame.Surface((296, 50)) - - - def generate_piece(self) -> tuple[Piece, list[int]]: - # We may want to make this a function outside the class - piece = random.choice((TPiece, SPiece, IPiece, ZPiece, SquarePiece, LPiece, JPiece))() - - for row_id, row in enumerate(piece.elements): - if list(filter(lambda x: x is not None, row)): - break - - initial_y_position = -row_id - initial_x_position = (len(self.grid[0]) // 2) - (len(piece.elements[0]) // 2) - - return (piece, [initial_y_position, initial_x_position]) - - def burn_rows(self) -> int: - rows_to_burn = list() - - for row in self.grid: - if all(map(lambda element: element is not None, row)): - rows_to_burn.append(row) - - for row in rows_to_burn: - self.grid.insert(0, [None for _ in range(10)]) - self.grid.remove(row) - - return len(rows_to_burn) - - def lock_piece(self) -> None: - if self.has_collision(self.current_piece_position[0], self.current_piece_position[1]): - raise WouldCollide() - - for row_id, row in enumerate(self.current_piece.elements): - for col_id, element in enumerate(row): - if element is None: - continue - self.grid[row_id + self.current_piece_position[0]][col_id + self.current_piece_position[1]] = element - - count = self.burn_rows() - - if count == 1: - print("Single") - rate = 1. - elif count == 2: - print("Double") - rate = 2.5 - elif count == 3: - print("Triple") - rate = 7.5 - elif count == 4: - print("Tetris!") - rate = 30. - else: - rate = 0. - - self.lines_burnt += count - - self.score += int(self.level * 40 * rate) - - if self.lines_burnt >= self.level * 10: - self.level += 1 - self.current_piece, self.current_piece_position = self.next_piece, self.next_piece_position - self.next_piece, self.next_piece_position = self.generate_piece() - self.refresh_piece_preview_canvas() - - def has_collision(self, y: int, x: int) -> bool: - try: - for row_id, row in enumerate(self.current_piece.elements): - for col_id, element in enumerate(row): - if element is None: - continue - - if row_id + y < 0: - continue - - if col_id + x < 0: - return True - - if self.grid[row_id + y][col_id + x] is not None: - return True - - except IndexError: - return True - - return False - - def move_piece_down(self) -> None: - if not self.has_collision(self.current_piece_position[0] + 1, self.current_piece_position[1]): - self.current_piece_position[0] += 1 - else: - raise WouldCollide() - - def move_piece_up(self) -> None: - if not self.has_collision(self.current_piece_position[0] - 1, self.current_piece_position[1]): - self.current_piece_position[0] -= 1 - else: - raise WouldCollide() - - def move_piece_left(self) -> None: - if not self.has_collision(self.current_piece_position[0], self.current_piece_position[1] - 1): - self.current_piece_position[1] -= 1 - else: - raise WouldCollide() - - def move_piece_right(self) -> None: - if not self.has_collision(self.current_piece_position[0], self.current_piece_position[1] + 1): - self.current_piece_position[1] += 1 - else: - raise WouldCollide() - - def rotate_piece_counter_clockwise(self) -> None: - self.current_piece.rotate_counter_clockwise() - - if self.has_collision(self.current_piece_position[0], self.current_piece_position[1]): - self.current_piece.rotate_clockwise() - raise WouldCollide() - - def rotate_piece_clockwise(self) -> None: - self.current_piece.rotate_clockwise() - - if self.has_collision(self.current_piece_position[0], self.current_piece_position[1]): - self.current_piece.rotate_counter_clockwise() - raise WouldCollide() - - def handle_input_pressed(self, event: pygame.event.Event) -> None: - if self.controller.get_input_down(event) == Input.QUIT: - raise PlayerQuit() - - if self.controller.get_input_down(event) == Input.PAUSE: - raise Pause() - - with contextlib.suppress(WouldCollide): - if self.controller.get_input_down(event) == Input.MOVE_RIGHT: - self.move_piece_right() - self.das = 0 - if self.controller.get_input_down(event) == Input.MOVE_LEFT: - self.move_piece_left() - self.das = 0 - if self.controller.get_input_down(event) == Input.ROTATE_CLOCKWISE: - self.rotate_piece_clockwise() - if self.controller.get_input_down(event) == Input.ROTATE_COUNTER_CLOCKWISE: - self.rotate_piece_counter_clockwise() - if self.controller.get_input_down(event) == Input.MOVE_DOWN: - self.piece_drop_frames = 0 - self.pressing_down_countdown = 3 - try: - self.move_piece_down() - except WouldCollide: - self.lock_piece() - - def handle_input_released(self, event: pygame.event.Event) -> None: - if self.controller.get_input_up(event) == Input.MOVE_DOWN: - self.pressing_down_countdown = None - - def refresh_piece_preview_canvas(self) -> None: - self.piece_preview_canvas.fill(black) - - non_empty_rows = list() - for row in self.next_piece.elements: - if any(map(lambda element: element is not None, row)): - non_empty_rows.append(row) - - non_empty_cols = set() - for row in self.next_piece.elements: - for col_id, element in enumerate(row): - if element is not None: - non_empty_cols.add(col_id) - - y_offset = (4 - len(non_empty_rows)) / 2 - x_offset = (4 - len(non_empty_cols)) / 2 - - # Display the next piece - for row_idx, row in enumerate(non_empty_rows): - for col_idx, element in enumerate(row): - if element is not None: - self.piece_preview_canvas.blit(element, ((col_idx + x_offset) * 50 + 1, (row_idx + y_offset) * 50 + 1)) - - def refresh_grid_canvas(self) -> None: - self.grid_canvas.fill(black) - - for row_idx, row in enumerate(self.grid): - for col_idx, element in enumerate(row): - if element is not None: - self.grid_canvas.blit(element, (col_idx * 50 + 1, row_idx * 50 + 1)) - - # Display the current piece - for row_idx, row in enumerate(self.current_piece.elements): - for col_idx, element in enumerate(row): - if element is not None: - self.grid_canvas.blit(element, ((col_idx + self.current_piece_position[1]) * 50 + 1, (row_idx + self.current_piece_position[0]) * 50 + 1)) - -def handle_input_pressed(instance, players: list[Player], event: pygame.event.Event) -> None: - for player in players: - if isinstance(player.controller, instance): - player.handle_input_pressed(event) - - -def handle_input_released(instance, players: list[Player], event: pygame.event.Event) -> None: - for player in players: - if isinstance(player.controller, instance): - player.handle_input_released(event) - -# Number of frames -frames_per_gridcell = [48, 43, 38, 33, 28, 23, 18, 13, 8, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1] - - -class InGame(Screen): - def __init__(self, players: list[Player], screen: pygame.surface.Surface): - self.players: list[Player] = players - self.screen: pygame.surface.Surface = screen - self.event_handler: dict[int, Callable[[pygame.event.Event], None]] = {} - - def exit(_) -> NoReturn: - sys.exit() - - self.event_handler[pygame.QUIT] = exit - self.event_handler[pygame.KEYDOWN] = partial(handle_input_pressed, KeyboardController, self.players) - self.event_handler[pygame.KEYUP] = partial(handle_input_released, KeyboardController, self.players) - self.event_handler[pygame.JOYBUTTONDOWN] = partial(handle_input_pressed, JoystickController, self.players) - self.event_handler[pygame.JOYBUTTONUP] = partial(handle_input_released, JoystickController, self.players) - - def refresh_right_pane_canvas(self) -> None: - for player in self.players: - player.level_canvas.fill(black) - player.score_canvas.fill(black) - - if pygame.font: - score_font = pygame.font.Font(None, 56) - - player.score_canvas.blit(score_font.render(f"{player.score:08d}", True, white), (0, 0)) - player.level_canvas.blit(score_font.render(f"{player.level:08d}", True, white), (0, 0)) - right_pane_canvas.blit(player.score_canvas, (2, 10)) - right_pane_canvas.blit(player.level_canvas, (2, 70)) - - right_pane_canvas.blit(player.piece_preview_canvas, (50, 200)) - - - def refresh(self) -> None: - for player in self.players: - player.refresh_grid_canvas() - self.screen.blit(player.grid_canvas, (0, 0)) - self.refresh_right_pane_canvas() - self.screen.blit(right_pane_canvas, (501, 0)) - - def oneframe(self) -> None: - for player in self.players: - player.piece_drop_frames += 1 - - try: - for event in pygame.event.get(): - with contextlib.suppress(KeyError): - self.event_handler[event.type](event) - except Pause: - pygame.event.clear() - raise - - for player in self.players: - player.das += 1 - if player.das == 16: - with contextlib.suppress(WouldCollide): - if player.controller.is_pressed(Input.MOVE_RIGHT): - player.move_piece_right() - if player.controller.is_pressed(Input.MOVE_LEFT): - player.move_piece_left() - player.das = 10 - - if player.pressing_down_countdown == 0: - try: - player.move_piece_down() - except WouldCollide: - player.lock_piece() - player.pressing_down_countdown = 2 - elif player.pressing_down_countdown is not None: - player.pressing_down_countdown -= 1 - - if player.piece_drop_frames >= frames_per_gridcell[player.level - 1]: - player.piece_drop_frames = 0 - try: - player.move_piece_down() - except WouldCollide: - player.lock_piece() diff --git a/pycameltris/screens/Pause.py b/pycameltris/screens/Pause.py deleted file mode 100644 index 766a45c..0000000 --- a/pycameltris/screens/Pause.py +++ /dev/null @@ -1,39 +0,0 @@ -import contextlib -import sys -from typing import Callable, NoReturn - -import pygame - -from .Screen import Screen -from ..controller import Controller, KeyboardController, JoystickController, Input -from ..misc import UnPause - - -def handle_input_pressed(controller: Controller, event: pygame.event.Event): - if controller.get_input_down(event) == Input.PAUSE: - raise UnPause() - - -class Pause(Screen): - def __init__(self, screen: pygame.surface.Surface): - self.event_handler: dict[int, Callable[[pygame.event.Event], None]] = {} - - def exit(_) -> NoReturn: - sys.exit() - - self.event_handler[pygame.QUIT] = exit - self.event_handler[pygame.JOYBUTTONDOWN] = lambda evt: handle_input_pressed(JoystickController(evt.joy), evt) - self.event_handler[pygame.KEYDOWN] = lambda evt: handle_input_pressed(KeyboardController(pygame.key), evt) - - - def refresh(self) -> None: - pass - - def oneframe(self) -> None: - try: - for event in pygame.event.get(): - with contextlib.suppress(KeyError): - self.event_handler[event.type](event) - except UnPause: - pygame.event.clear() - raise diff --git a/pycameltris/screens/Screen.py b/pycameltris/screens/Screen.py deleted file mode 100644 index 419c1d2..0000000 --- a/pycameltris/screens/Screen.py +++ /dev/null @@ -1,12 +0,0 @@ -import abc - - -class Screen(abc.ABC): - @abc.abstractmethod - def refresh(self) -> None: - pass - - @abc.abstractmethod - def oneframe(self) -> None: - pass - -- cgit v1.2.3