diff --git a/src/py2048/__init__.py b/src/py2048/__init__.py deleted file mode 100644 index d1c7a88..0000000 --- a/src/py2048/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .config import Config -from .screens import Menu - -__all__ = ["Config", "Menu"] diff --git a/src/py2048/config.py b/src/py2048/config.py deleted file mode 100644 index 12f8c5a..0000000 --- a/src/py2048/config.py +++ /dev/null @@ -1,12 +0,0 @@ -from .utils import Board, ColorScheme, Font, Header, Position, Screen, Size, Tile - - -class Config: - FONT = Font() - - COLORSCHEME = ColorScheme.ORIGINAL.value - - TILE = Tile() - BOARD = Board() - HEADER = Header() - SCREEN = Screen() diff --git a/src/py2048/objects/__init__.py b/src/py2048/objects/__init__.py deleted file mode 100644 index 8eac17f..0000000 --- a/src/py2048/objects/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from .board import Board -from .button import Button -from .label import Label -from .score_label import ScoreLabel -from .tile import Tile - -__all__ = ["Board", "Button", "Label", "Tile", "ScoreLabel"] diff --git a/src/py2048/objects/abc/__init__.py b/src/py2048/objects/abc/__init__.py deleted file mode 100644 index 409beb3..0000000 --- a/src/py2048/objects/abc/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .clickable_ui_element import ClickableUIElement -from .movable_ui_element import MovableUIElement -from .ui_element import UIElement - -__all__ = ["ClickableUIElement", "MovableUIElement", "UIElement"] diff --git a/src/py2048/objects/abc/clickable_ui_element.py b/src/py2048/objects/abc/clickable_ui_element.py deleted file mode 100644 index 2c54bb0..0000000 --- a/src/py2048/objects/abc/clickable_ui_element.py +++ /dev/null @@ -1,34 +0,0 @@ -from abc import abstractmethod -from typing import Callable, Optional - -import pygame - -from py2048.utils import Position - -from .ui_element import UIElement - - -class ClickableUIElement(UIElement): - def __init__( - self, - hover_color: str, - action: Optional[Callable[[], None]] = None, - *args, - **kwargs, - ) -> None: - super().__init__(*args, **kwargs) - self.action = action - self.hover_color = hover_color - self.is_hovered = False - - @abstractmethod - def on_click(self, mouse_pos: Position) -> None: - """Handle the click event.""" - - @abstractmethod - def on_hover(self, mouse_pos: Position) -> None: - """Handle the hover event.""" - - @abstractmethod - def _draw_hover_background(self, surface: pygame.Surface) -> None: - """Draw the hover rectangle.""" diff --git a/src/py2048/objects/abc/movable_ui_element.py b/src/py2048/objects/abc/movable_ui_element.py deleted file mode 100644 index f8f6f23..0000000 --- a/src/py2048/objects/abc/movable_ui_element.py +++ /dev/null @@ -1,22 +0,0 @@ -from abc import abstractmethod -from typing import Any - -import pygame - -from py2048.utils import Direction, Position - -from .ui_element import UIElement - - -class MovableUIElement(UIElement): - def __init__(self, *args, **kwargs) -> None: - UIElement.__init__(self, *args, **kwargs) - - @abstractmethod - def move(self, direction: Direction) -> Any: - """Move the element in the given direction.""" - - @property - @abstractmethod - def pos(self) -> Position: - """Return the position of the element.""" diff --git a/src/py2048/objects/abc/ui_element.py b/src/py2048/objects/abc/ui_element.py deleted file mode 100644 index c3d98ce..0000000 --- a/src/py2048/objects/abc/ui_element.py +++ /dev/null @@ -1,55 +0,0 @@ -from abc import ABC, ABCMeta, abstractmethod -from typing import Optional - -import pygame -from loguru import logger - -from py2048 import Config -from py2048.utils import Position, Size - - -class UIElement(ABC, metaclass=ABCMeta): - def __init__( - self, - /, - *, - position: Position, - bg_color: str, - font_color: str, - font_size: int = Config.FONT.size, - font_family: str = Config.FONT.family, - size: Optional[Size] = Size(50, 50), - text: str = "", - border_radius: int = 0, - border_width: int = 0, - ): - super().__init__() - self.text = text - self.size = size - self.bg_color = bg_color - self.font_color = font_color - self.border_radius = border_radius - self.border_width = border_width - self.position = position - self.x, self.y = self.position - self.font = pygame.font.SysFont(font_family, font_size) - - @abstractmethod - def draw(self, surface: pygame.Surface) -> None: - """Draw the element.""" - - @abstractmethod - def update(self) -> None: - """Update the element.""" - - @abstractmethod - def _draw_background(self, surface: pygame.Surface) -> None: - """Draw a background for the given surface.""" - - @abstractmethod - def _draw_text(self) -> None: - """Draw the text of the element.""" - - @abstractmethod - def _create_surface(self) -> pygame.Surface: - """Create a surface for the element.""" diff --git a/src/py2048/objects/board.py b/src/py2048/objects/board.py deleted file mode 100644 index 92fd2e0..0000000 --- a/src/py2048/objects/board.py +++ /dev/null @@ -1,151 +0,0 @@ -import random -from typing import Optional - -import pygame -from loguru import logger - -from py2048 import Config -from py2048.utils import Direction, Position - -from .tile import Tile - - -class Board(pygame.sprite.Group): - def __init__(self): - super().__init__() - self.rect = pygame.Rect(0, 0, *Config.BOARD.size) - self.score: int = 0 - self.rect.x, self.rect.y = Config.BOARD.pos - self._initiate_game() - - def _initiate_game(self) -> None: - """Initiate the game.""" - self.generate_initial_tiles() - - def draw(self, surface: pygame.Surface) -> None: - """Draw the board.""" - self._draw_background(surface) - super().draw(surface) - - def _draw_background(self, surface: pygame.Surface) -> None: - """Draw the board background.""" - pygame.draw.rect( - surface, - Config.COLORSCHEME.BOARD_BG, - self.rect, - border_radius=Config.TILE.border.radius, - ) # background - pygame.draw.rect( - surface, - Config.COLORSCHEME.BOARD_BG, - self.rect, - width=Config.TILE.border.width, - border_radius=Config.TILE.border.radius, - ) # border - - def move(self, direction: Direction) -> None: - """Move the tiles in the specified direction.""" - tiles = self.sprites() - tile: Tile - - match direction: - case Direction.UP: - tiles.sort(key=lambda tile: tile.rect.y) - case Direction.DOWN: - tiles.sort(key=lambda tile: tile.rect.y, reverse=True) - case Direction.LEFT: - tiles.sort(key=lambda tile: tile.rect.x) - case Direction.RIGHT: - tiles.sort(key=lambda tile: tile.rect.x, reverse=True) - - for tile in tiles: - self.score += tile.move(direction) - - if not self._is_full(): - self.generate_random_tile() - - def generate_initial_tiles(self) -> None: - """Generate the initial tiles.""" - self.generate_tile(Config.TILE.initial_count) - - def generate_tile(self, amount: int = 1, *pos: Position) -> None: - """Generate `amount` number of tiles or at the specified positions.""" - if pos: - for coords in pos: - x, y = coords.x * Config.TILE.size, coords.y * Config.TILE.size - self.add(Tile(Position(x, y), self)) - return - - for _ in range(amount): - self.generate_random_tile() - - def generate_random_tile(self) -> None: - """Generate a tile with random coordinates aligned with the grid.""" - while True: - # Generate random coordinates aligned with the grid - x = random.randint(0, 3) * Config.TILE.size + Config.BOARD.pos.x - y = random.randint(0, 3) * Config.TILE.size + Config.BOARD.pos.y - tile = Tile(Position(x, y), self) - - colliding_tiles = pygame.sprite.spritecollide( - tile, self, False - ) # check for collisions - - if not colliding_tiles: - self.add(tile) - return - - def _is_full(self) -> bool: - """Check if the board is full.""" - return len(self.sprites()) == Config.BOARD.len**2 - - def _can_move(self) -> bool: - """Check if any movement is possible on the board.""" - tile: Tile - for tile in self.sprites(): - if tile.can_move(): - return True - return False - - def is_game_over(self) -> bool: - """Check if the game is over.""" - return self._is_full() and not self._can_move() - - def reset(self) -> None: - """Reset the board.""" - self.empty() - self._initiate_game() - - def max_val(self) -> int: - """Return the maximum value of the tiles.""" - tile: Tile - return int(max(tile.value for tile in self.sprites())) - - def get_tile(self, position: Position) -> Optional[Tile]: - """Return the tile at the specified position.""" - tile: Tile - for tile in self.sprites(): - if tile.pos == position: - return tile - return None - - def get_tile_value(self, position: Position) -> int: - """Return the value of the tile at the specified position.""" - tile = self.get_tile(position) - if tile: - return tile.value - return 0 - - def matrix(self) -> list[int]: - """Return a 1d matrix of values of the tiles.""" - matrix: list[int] = [] - - for i in range(1, Config.BOARD.len + 1): - for j in range(1, Config.BOARD.len + 1): - tile = self.get_tile(Position(j, i)) - if tile: - matrix.append(tile.value) - else: - matrix.append(0) - - return matrix diff --git a/src/py2048/objects/button.py b/src/py2048/objects/button.py deleted file mode 100644 index 208b527..0000000 --- a/src/py2048/objects/button.py +++ /dev/null @@ -1,71 +0,0 @@ -import sys -from typing import Callable, Optional - -import pygame -from attrs import define, field - -from py2048 import Config -from py2048.utils import Direction, Position - -from .abc import ClickableUIElement - - -class Button(ClickableUIElement, pygame.sprite.Sprite): - def __init__(self, *args, **kwargs): - pygame.sprite.Sprite.__init__(self) - super().__init__(*args, **kwargs) - - self.image = self._create_surface() - self.rect = self.image.get_rect() - self.rect.topleft = self.position - self.update() - - def draw(self, surface: pygame.Surface) -> None: - """Draw the button on the given surface.""" - self._draw_background(surface) - self._draw_text() - self.image.blit(self.image, (0, 0)) - - def update(self) -> None: - """Update the button.""" - self._draw_background(self.image) - self._draw_text() - - def on_click(self, mouse_pos: Position) -> None: - """Handle the click event.""" - if self.rect.collidepoint(mouse_pos) and self.action: - self.action() - - def on_hover(self, mouse_pos: Position) -> None: - pass - - def _draw_background(self, surface: pygame.Surface) -> None: - """Draw a rectangle on the given surface.""" - if self.size: - pygame.draw.rect( - surface, - self.bg_color, - (0, 0, *self.size), - border_radius=Config.TILE.border.radius, - ) - - def _draw_hover_background(self, surface: pygame.Surface) -> None: - """Draw the hover rectangle.""" - pygame.draw.rect( - surface, - self.hover_color, - self.rect, - border_radius=self.border_radius, - ) - - def _draw_text(self) -> None: - """Draw the text of the element.""" - text = self.font.render(self.text, True, self.font_color) - rect = text.get_rect(center=self.image.get_rect().center) - self.image.blit(text, rect) - - def _create_surface(self) -> pygame.Surface: - """Create a surface for the element.""" - surface = pygame.Surface(self.size, pygame.SRCALPHA) - self._draw_background(surface) - return surface diff --git a/src/py2048/objects/label.py b/src/py2048/objects/label.py deleted file mode 100644 index 9e5d009..0000000 --- a/src/py2048/objects/label.py +++ /dev/null @@ -1,52 +0,0 @@ -import pygame -from loguru import logger - -from py2048 import Config -from py2048.utils import Position, Size - -from .abc import UIElement - - -class Label(UIElement, pygame.sprite.Sprite): - def __init__(self, *args, **kwargs) -> None: - pygame.sprite.Sprite.__init__(self) - super().__init__(*args, **kwargs) - - self.image = self._create_surface() - self.rect = self.image.get_rect() - self.rect.topleft = self.position - self.update() - - def draw(self, surface: pygame.Surface) -> None: - """Draw the element on the given surface.""" - self._draw_background(surface) - self._draw_text() - self.image.blit(self.image, (0, 0)) - - def update(self) -> None: - """Update the sprite.""" - self._draw_background(self.image) - self._draw_text() - self.image.blit(self.image, (0, 0)) - - def _draw_background(self, surface: pygame.Surface) -> None: - """Draw a background for the given surface.""" - if self.size: - pygame.draw.rect( - surface, - self.bg_color, - (0, 0, *self.size), - border_radius=Config.TILE.border.radius, - ) - - def _draw_text(self) -> None: - """Draw the text of the element.""" - text = self.font.render(self.text, True, self.font_color) - rect = text.get_rect(center=self.image.get_rect().center) - self.image.blit(text, rect) - - def _create_surface(self) -> pygame.Surface: - """Create a surface for the element.""" - surface = pygame.Surface(self.size, pygame.SRCALPHA) - self._draw_background(surface) - return surface diff --git a/src/py2048/objects/score_label.py b/src/py2048/objects/score_label.py deleted file mode 100644 index 01a9d85..0000000 --- a/src/py2048/objects/score_label.py +++ /dev/null @@ -1,65 +0,0 @@ -from typing import Optional - -import pygame -from loguru import logger - -from py2048 import Config -from py2048.utils import Position, Size - -from .abc import UIElement - - -class ScoreLabel(UIElement, pygame.sprite.Sprite): - def __init__(self, value: int, *args, **kwargs) -> None: - pygame.sprite.Sprite.__init__(self) - super().__init__(*args, **kwargs) - self.value = value - self.image = self._create_surface() - self.rect = self.image.get_rect() - self.rect.topleft = self.position - self.update() - - def draw(self, surface: pygame.Surface) -> None: - """Draw the element on the given surface.""" - self._draw_background(surface) - self._draw_text() - - def update(self) -> None: - """Update the sprite.""" - self._draw_background(self.image) - self._draw_text() - - def update_score(self, score: int) -> None: - """Update the score value.""" - self.value = score - self.update() - - def _draw_background(self, surface: pygame.Surface) -> None: - """Draw a background for the given surface.""" - if self.size: - pygame.draw.rect( - surface, - self.bg_color, - (0, 0, *self.size), - border_radius=self.border_radius, - ) - - def _draw_text(self) -> None: - """Draw the text of the element.""" - centerx, centery = self.image.get_rect().center - - # Render text - label_text = self.font.render(self.text, True, self.font_color) - label_rect = label_text.get_rect(center=(centerx, centery - 10)) - self.image.blit(label_text, label_rect) - - # Render value - score_text = self.font.render(f"{self.value}", True, self.font_color) - score_rect = score_text.get_rect(center=(centerx, centery + 10)) - self.image.blit(score_text, score_rect) - - def _create_surface(self) -> pygame.Surface: - """Create a surface for the element.""" - surface = pygame.Surface(self.size, pygame.SRCALPHA) - self._draw_background(surface) - return surface diff --git a/src/py2048/objects/tile.py b/src/py2048/objects/tile.py deleted file mode 100644 index 380c06f..0000000 --- a/src/py2048/objects/tile.py +++ /dev/null @@ -1,201 +0,0 @@ -import random -from typing import Union - -import pygame -from loguru import logger - -from py2048 import Config -from py2048.utils import ColorScheme, Direction, Position, Size - -from .abc import MovableUIElement, UIElement - - -def _grid_pos(position: Position) -> Position: - """Return the position in the grid.""" - x = (position.x - Config.BOARD.pos.x) // Config.TILE.size + 1 - y = (position.y - Config.BOARD.pos.y) // Config.TILE.size + 1 - return Position(x, y) - - -class Tile(MovableUIElement, pygame.sprite.Sprite): - def __init__( - self, - position: Position, - group: pygame.sprite.Group, - ): - pygame.sprite.Sprite.__init__(self) - self.value = 2 if random.random() <= Config.TILE.value_probability else 4 - - super().__init__( - position=position, - text=f"{self.value}", - bg_color=Config.COLORSCHEME.TILE_0, - font_color=Config.COLORSCHEME.DARK_TEXT, - size=Size(Config.TILE.size, Config.TILE.size), - border_radius=Config.TILE.border.radius, - border_width=Config.TILE.border.width, - ) - self.group = group - - self.image = self._create_surface() - self.rect = self.image.get_rect() - self.rect.topleft = self.position - self.update() - - def draw(self, surface: pygame.Surface) -> None: - """Draw the value of the tile.""" - self._draw_background(surface) - self._draw_text() - self.image.blit(self.image, (0, 0)) - - def update(self) -> None: - """Update the sprite.""" - self._draw_background(self.image) - self.text = f"{self.value}" - self._draw_text() - self.image.blit(self.image, (0, 0)) - - def _draw_background(self, surface: pygame.Surface) -> None: - """Draw a rounded rectangle on the given surface.""" - if self.size: - pygame.draw.rect( - surface, - self._get_color(), - (0, 0, *self.size), - border_radius=Config.TILE.border.radius, - ) - self._draw_border(surface) - - def _draw_border(self, surface: pygame.Surface) -> None: - """Draw a rounded border on the given surface.""" - if self.size: - pygame.draw.rect( - surface, - (0, 0, 0, 0), - (0, 0, *self.size), - border_radius=Config.TILE.border.radius, - width=Config.TILE.border.width, - ) - - def _draw_text(self) -> None: - """Draw the text of the sprite.""" - text = self.font.render(self.text, True, self.font_color) - rect = (text.get_rect(center=self.image.get_rect().center),) - self.image.blit(text, rect) - - def _create_surface(self) -> pygame.Surface: - """Create a surface for the sprite.""" - surface = pygame.Surface(self.size, pygame.SRCALPHA) - self._draw_background(surface) - return surface - - def move(self, direction: Direction) -> int: - """ - Move the tile by `dx` and `dy`. - If the tile collides with another tile, it will merge with it if possible. - """ - score = 0 - while True: - new_x, new_y = self._calc_new_pos(direction) - - if self._is_out_if_bounds(new_x, new_y): - return score - - if self._has_collision(new_x, new_y): - collided_tile = self._get_collided_tile(new_x, new_y) - if collided_tile and self._can_merge(collided_tile): - score += self._merge(collided_tile) - else: - return score - - self.group.remove(self) - self.rect.topleft = new_x, new_y - self.group.add(self) - - def _calc_new_pos(self, direction: Direction) -> tuple[int, int]: - """Calculate the new position of the tile.""" - dx, dy = direction * Config.TILE.size - return self.rect.x + dx, self.rect.y + dy - - def _is_out_if_bounds(self, x: int, y: int) -> bool: - """Return whether the tile is out of bounds.""" - board_left = Config.BOARD.pos.x - board_right = Config.BOARD.pos.x + Config.BOARD.size.width - Config.TILE.size - board_top = Config.BOARD.pos.y - board_bottom = Config.BOARD.pos.y + Config.BOARD.size.height - Config.TILE.size - return not (board_left <= x <= board_right and board_top <= y <= board_bottom) - - def _has_collision(self, x: int, y: int) -> bool: - """Checks whether the tile has a collision with any other tile.""" - return any(tile.rect.collidepoint(x, y) for tile in self.group if tile != self) - - def _get_collided_tile(self, x: int, y: int) -> Union["Tile", None]: - """Get the tile that collides with the given tile.""" - - return next( - ( - tile - for tile in self.group - if tile != self and tile.rect.collidepoint(x, y) - ), - None, - ) - - def _can_merge(self, other: "Tile") -> bool: - """Check if the tile can merge with another tile.""" - return self.value == other.value - - def _merge(self, other: "Tile") -> int: - """Merge the tile with another tile.""" - self.group.remove(other) - self.group.remove(self) - self.value += other.value - self.update() - self.group.add(self) - return self.value - - def can_move(self) -> bool: - """Check if the tile can move""" - for direction in Direction: - new_x, new_y = self._calc_new_pos(direction) - if not self._is_out_if_bounds(new_x, new_y) and self._has_collision( - new_x, new_y - ): - collided_tile = self._get_collided_tile(new_x, new_y) - if collided_tile and self._can_merge(collided_tile): - return True - return False - - def _get_color(self) -> str: - """Change the color of the tile based on its value.""" - color_map = { - 2: Config.COLORSCHEME.TILE_2, - 4: Config.COLORSCHEME.TILE_4, - 8: Config.COLORSCHEME.TILE_8, - 16: Config.COLORSCHEME.TILE_16, - 32: Config.COLORSCHEME.TILE_32, - 64: Config.COLORSCHEME.TILE_64, - 128: Config.COLORSCHEME.TILE_128, - 256: Config.COLORSCHEME.TILE_256, - 512: Config.COLORSCHEME.TILE_512, - 1024: Config.COLORSCHEME.TILE_1024, - 2048: Config.COLORSCHEME.TILE_2048, - } - return color_map.get(self.value, Config.COLORSCHEME.TILE_ELSE) - - def __repr__(self) -> str: - """Return a string representation of the tile.""" - return f"Tile({id(self)}): {self.pos} num={self.value}" - - def __str__(self) -> str: - """Return a string representation of the tile.""" - return self.__repr__() - - def __hash__(self) -> int: - """Return a hash of the tile.""" - return hash((self.rect.x, self.rect.y)) - - @property - def pos(self) -> Position: - """Return the position of the tile.""" - return _grid_pos(Position(*self.rect.topleft)) diff --git a/src/py2048/screens/__init__.py b/src/py2048/screens/__init__.py deleted file mode 100644 index 139bbae..0000000 --- a/src/py2048/screens/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .game import Game -from .menu import Menu - -__all__ = ["Menu", "Game"] diff --git a/src/py2048/screens/game.py b/src/py2048/screens/game.py deleted file mode 100644 index 7ec346e..0000000 --- a/src/py2048/screens/game.py +++ /dev/null @@ -1,114 +0,0 @@ -import neat -import pygame -from loguru import logger - -from py2048 import Config -from py2048.objects import Board, Button, ScoreLabel -from py2048.utils import Direction, Position, Size - - -class Game: - def __init__(self) -> None: - self.rect = pygame.Rect(0, 0, *Config.HEADER.size) - self.labels = self._create_labels() - self.buttons = self._create_buttons() - self.board = Board() - - def update_score(self, new_score: int) -> None: - """Updates the score to `new_score`.""" - self.score.update_score(new_score) - - def draw(self, surface: pygame.Surface) -> None: - surface.fill(Config.COLORSCHEME.BG) - self.board.draw(surface) - self.labels.draw(surface) - self.buttons.draw(surface) - pygame.display.flip() - - def handle_events(self, event: pygame.Event) -> None: - if event.type == pygame.MOUSEBUTTONDOWN: - for button in self.buttons: - button.on_click(Position(*event.pos)) - elif event.type == pygame.MOUSEMOTION: - for button in self.buttons: - button.on_hover(Position(*event.pos)) - elif event.type == pygame.KEYDOWN: - if event.key in (pygame.K_LEFT, pygame.K_a, pygame.K_h): - self.move_left() - elif event.key in (pygame.K_RIGHT, pygame.K_d, pygame.K_l): - self.move_right() - elif event.key in (pygame.K_UP, pygame.K_w, pygame.K_k): - self.move_up() - elif event.key in (pygame.K_DOWN, pygame.K_s, pygame.K_j): - self.move_down() - - def move(self, direction: Direction) -> None: - """Moved the board in the given direction and updates the score.""" - self.board.move(direction) - self.update_score(self.board.score) - # if self.board.is_game_over(): - # logger.info(f"Game over! Score was {self.board.score}.") - # self.restart() - - def move_up(self) -> None: - self.move(Direction.UP) - - def move_down(self) -> None: - self.move(Direction.DOWN) - - def move_left(self) -> None: - self.move(Direction.LEFT) - - def move_right(self) -> None: - self.move(Direction.RIGHT) - - def restart(self) -> None: - self.board.reset() - self.board.score = 0 - self.update_score(0) - - def _create_labels(self) -> pygame.sprite.Group: - size = Size(60, 40) - - self.score = ScoreLabel( - value=0, - text="Score", - size=size, - position=Position( - Config.SCREEN.size.width - Config.TILE.size // 2 - size.width * 2 - 10, - 10, - ), - bg_color=Config.COLORSCHEME.BOARD_BG, - font_color=Config.COLORSCHEME.LIGHT_TEXT, - font_size=16, - border_radius=2, - ) - highscore = ScoreLabel( - value=2048, - text="Best", - size=size, - position=Position( - Config.SCREEN.size.width - Config.TILE.size // 2 - size.width, 10 - ), - bg_color=Config.COLORSCHEME.BOARD_BG, - font_color=Config.COLORSCHEME.LIGHT_TEXT, - font_size=16, - border_radius=2, - ) - - return pygame.sprite.Group(self.score, highscore) - - def _create_buttons(self) -> pygame.sprite.Group: - reset = Button( - position=(10, 10), - bg_color=Config.COLORSCHEME.BOARD_BG, - font_color=Config.COLORSCHEME.LIGHT_TEXT, - hover_color=Config.COLORSCHEME.TILE_0, - size=(100, 30), - text="New Game", - border_radius=Config.TILE.border.radius, - action=self.restart, - font_size=16, - border_width=2, - ) - return pygame.sprite.Group(reset) diff --git a/src/py2048/screens/menu.py b/src/py2048/screens/menu.py deleted file mode 100644 index 0801590..0000000 --- a/src/py2048/screens/menu.py +++ /dev/null @@ -1,129 +0,0 @@ -import sys -from pathlib import Path - -import neat -import pygame -from ai import get_config, read_genome -from loguru import logger - -from py2048 import Config -from py2048.objects import Button -from py2048.utils import Position - -from .game import Game - - -class Menu: - def __init__(self): - pygame.init() - pygame.display.set_caption("2048") - self._surface: pygame.Surface = pygame.display.set_mode(Config.SCREEN.size) - - logger.info("Initializing game") - - self._buttons_data = { - "Play": self.play, - "AI": self.ai, - "Settings": self.settings, - "Exit": self.exit, - } - self._game_active = False - self._ai_active = False - self._setting_active = False - self._buttons = self._create_buttons() - - def _create_buttons(self) -> pygame.sprite.Group: - """Create the buttons.""" - width, height = 120, 50 - buttons = pygame.sprite.Group() - for index, (text, action) in enumerate(self._buttons_data.items(), start=1): - buttons.add( - Button( - position=( - Config.SCREEN.size.width / 2 - width / 2, - Config.SCREEN.size.height / len(self._buttons_data) * index - - height, - ), - bg_color=Config.COLORSCHEME.BOARD_BG, - font_color=Config.COLORSCHEME.LIGHT_TEXT, - hover_color=Config.COLORSCHEME.TILE_0, - size=(width, height), - text=text, - border_radius=Config.TILE.border.radius, - action=action, - ) - ) - - return buttons - - def draw(self) -> None: - self._surface.fill(Config.COLORSCHEME.BG) - self._buttons.draw(self._surface) - pygame.display.flip() - - def run(self) -> None: - """Run the game loop.""" - - while True: - self._hande_events() - - if self._game_active: - self.game.draw(self._surface) - elif self._ai_active: - self._ai_move() - self.game.draw(self._surface) - elif self._setting_active: - pass - else: - self.draw() - - def _hande_events(self) -> None: - """Handle pygame events.""" - for event in pygame.event.get(): - if event.type == pygame.QUIT: - self.exit() - elif event.type == pygame.MOUSEBUTTONDOWN: - for button in self._buttons: - button.on_click(Position(*event.pos)) - elif event.type == pygame.MOUSEMOTION: - for button in self._buttons: - button.on_hover(Position(*event.pos)) - elif event.type == pygame.KEYDOWN: - if event.key == pygame.K_q: - self.exit() - if self._game_active or self._ai_active: - self.game.handle_events(event) - - def play(self) -> None: - logger.debug("Launching game") - self._game_active = True - self.game = Game() - - def ai(self) -> None: - logger.debug("AI") - - self._ai_active = True - self.game = Game() - self.network = neat.nn.FeedForwardNetwork.create(read_genome(), get_config()) - - def _ai_move(self) -> None: - decisions = { - 0: self.game.move_up, - 1: self.game.move_down, - 2: self.game.move_left, - 3: self.game.move_right, - } - - output = self.network.activate((*self.game.board.matrix(),)) - decision = output.index(max(output)) - - decisions[decision]() - - def settings(self) -> None: - logger.debug("Settings") - - def exit(self) -> None: - """Exit the game.""" - logger.debug("Exiting") - pygame.quit() - sys.exit() diff --git a/src/py2048/screens/settings.py b/src/py2048/screens/settings.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/py2048/utils/__init__.py b/src/py2048/utils/__init__.py deleted file mode 100644 index bab2b62..0000000 --- a/src/py2048/utils/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -from path import BASE_PATH - -from .collections import Board, Font, Header, Position, Screen, Size, Tile -from .color import ColorScheme -from .enums import Direction - -__all__ = [ - "Board", - "ColorScheme", - "Direction", - "Font", - "Position", - "Size", - "Tile", - "Header", - "Screen", -] diff --git a/src/py2048/utils/collections.py b/src/py2048/utils/collections.py deleted file mode 100644 index f317a98..0000000 --- a/src/py2048/utils/collections.py +++ /dev/null @@ -1,52 +0,0 @@ -from typing import NamedTuple - -from attr import Factory, define, field - - -class Position(NamedTuple): - x: int | float - y: int | float - - -class Size(NamedTuple): - width: int - height: int - - -@define -class Font: - family: str = "Roboto" - size: int = 32 - - -@define -class Border: - width: int - radius: int - - -@define -class Tile: - size: int = 75 - border: Border = Border(size // 20, size // 10) - initial_count: int = 2 - value_probability: float = 0.9 - - -@define -class Board: - len: int = 4 - size: Size = Size(len * Tile().size, len * Tile().size) - pos: Position = Position(Tile().size // 2, Tile().size + Tile().size // 2) - - -@define -class Header: - size: Size = Size(Board().size.width + Tile().size, Tile().size) - - -@define -class Screen: - size: Size = Size( - Header().size.width, Board().size.height + Tile().size + Header().size.height - ) diff --git a/src/py2048/utils/color.py b/src/py2048/utils/color.py deleted file mode 100644 index 646a050..0000000 --- a/src/py2048/utils/color.py +++ /dev/null @@ -1,27 +0,0 @@ -from enum import Enum - - -class Original: - TILE_0 = "#cdc1b4" - TILE_2 = "#eee4da" - TILE_4 = "#eee1c9" - TILE_8 = "#f3b27a" - TILE_16 = "#f69664" - TILE_32 = "#f77c5f" - TILE_64 = "#f75f3b" - TILE_128 = "#edcf72" - TILE_256 = "#edcc61" - TILE_512 = "#edc850" - TILE_1024 = "#edc53f" - TILE_2048 = "#edc22e" - TILE_ELSE = "#ff0000" - LIGHT_TEXT = "#f9f6f2" - DARK_TEXT = "#776e65" - OTHER = "#000000" - BG = "#faf8ef" - BOARD_BG = "#bbada0" - - -class ColorScheme(Enum): - ORIGINAL = Original - DARK = ... # TODO: Implement dark color scheme diff --git a/src/py2048/utils/enums.py b/src/py2048/utils/enums.py deleted file mode 100644 index 32dedf9..0000000 --- a/src/py2048/utils/enums.py +++ /dev/null @@ -1,18 +0,0 @@ -from enum import Enum - -from .collections import Position - - -class Direction(Enum): - UP = Position(0, -1) - DOWN = Position(0, 1) - LEFT = Position(-1, 0) - RIGHT = Position(1, 0) - - def __mul__(self, num: int) -> Position: - """Multiply the direction by a constant.""" - return Position(self.value.x * num, self.value.y * num) - - def __imul__(self, num: int) -> tuple[int, int]: - """Multiply the direction by a constant.""" - return Position(self.value.x * num, self.value.y * num)