mirror of
https://github.com/kristoferssolo/2048.git
synced 2025-10-21 15:20:35 +00:00
[major] refactor(game): add abstract classes
I don't know anymore where and what changed
This commit is contained in:
parent
c03be8f3cf
commit
624401d27b
2
main.py
2
main.py
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from py2048.game import Game
|
from py2048 import Game
|
||||||
|
|
||||||
|
|
||||||
@logger.catch
|
@logger.catch
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
from .color import ColorScheme
|
|
||||||
from .config import Config
|
from .config import Config
|
||||||
from .utils import BASE_PATH, Direction
|
from .game import Game
|
||||||
|
|
||||||
__all__ = ["Direction", "ColorScheme", "Config", "BASE_PATH"]
|
__all__ = ["Config", "Game"]
|
||||||
|
|||||||
@ -1,27 +1,12 @@
|
|||||||
from .color import ColorScheme
|
from .utils import Board, ColorScheme, Font, Header, Position, Screen, Size, Tile
|
||||||
|
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
FONT_FAMILY = "Roboto"
|
FONT = Font()
|
||||||
FONT_SIZE = 32
|
|
||||||
COLORSCHEME = ColorScheme.ORIGINAL.value
|
COLORSCHEME = ColorScheme.ORIGINAL.value
|
||||||
|
|
||||||
TILE_SIZE = 75
|
TILE = Tile()
|
||||||
TILE_BORDER_WIDTH = TILE_SIZE // 20
|
BOARD = Board()
|
||||||
TILE_BORDER_RADIUS = TILE_SIZE // 10
|
HEADER = Header()
|
||||||
INITIAL_TILE_COUNT = 2
|
SCREEN = Screen()
|
||||||
TILE_VALUE_PROBABILITY = 0.9
|
|
||||||
|
|
||||||
BOARD_SIZE = 4
|
|
||||||
BOARD_WIDTH = BOARD_SIZE * TILE_SIZE
|
|
||||||
BOARD_HEIGHT = BOARD_SIZE * TILE_SIZE
|
|
||||||
|
|
||||||
HEADER_WIDTH = BOARD_WIDTH + TILE_SIZE
|
|
||||||
HEADER_HEIGHT = TILE_SIZE
|
|
||||||
|
|
||||||
BOARD_X = TILE_SIZE // 2
|
|
||||||
BOARD_Y = HEADER_HEIGHT + TILE_SIZE // 2
|
|
||||||
|
|
||||||
SCREEN_WIDTH = HEADER_WIDTH
|
|
||||||
SCREEN_HEIGHT = BOARD_HEIGHT + TILE_SIZE + HEADER_HEIGHT
|
|
||||||
SCREEN_SIZE = SCREEN_WIDTH, SCREEN_HEIGHT
|
|
||||||
|
|||||||
@ -6,21 +6,20 @@ from loguru import logger
|
|||||||
from .config import Config
|
from .config import Config
|
||||||
from .objects import Board
|
from .objects import Board
|
||||||
from .screens import Header, Menu
|
from .screens import Header, Menu
|
||||||
from .utils import Direction, _setup_logger
|
from .utils import Direction, setup_logger
|
||||||
|
|
||||||
|
|
||||||
class Game:
|
class Game:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
_setup_logger()
|
setup_logger()
|
||||||
logger.info("Initializing game")
|
logger.info("Initializing game")
|
||||||
|
|
||||||
pygame.init()
|
pygame.init()
|
||||||
self.screen: pygame.Surface = pygame.display.set_mode(Config.SCREEN_SIZE)
|
self.screen: pygame.Surface = pygame.display.set_mode(Config.SCREEN.size)
|
||||||
pygame.display.set_caption("2048")
|
pygame.display.set_caption("2048")
|
||||||
self.board = Board()
|
self.board = Board()
|
||||||
self.header = Header()
|
self.header = Header()
|
||||||
self.menu = Menu()
|
# self.menu = Menu()
|
||||||
self.score = 0
|
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
"""Run the game loop."""
|
"""Run the game loop."""
|
||||||
@ -36,9 +35,9 @@ class Game:
|
|||||||
def _render(self) -> None:
|
def _render(self) -> None:
|
||||||
"""Render the game."""
|
"""Render the game."""
|
||||||
self.screen.fill(Config.COLORSCHEME.BG)
|
self.screen.fill(Config.COLORSCHEME.BG)
|
||||||
# self.board.draw(self.screen)
|
self.board.draw(self.screen)
|
||||||
# self.header.draw(self.screen, self.score)
|
self.header.draw(self.screen, 0)
|
||||||
self.menu.draw(self.screen)
|
# self.menu.draw(self.screen)
|
||||||
pygame.display.flip()
|
pygame.display.flip()
|
||||||
|
|
||||||
def _hande_events(self) -> None:
|
def _hande_events(self) -> None:
|
||||||
@ -57,19 +56,19 @@ class Game:
|
|||||||
self.move_down()
|
self.move_down()
|
||||||
elif event.key == pygame.K_q:
|
elif event.key == pygame.K_q:
|
||||||
self.exit()
|
self.exit()
|
||||||
self.menu._handle_events(event)
|
# self.menu._handle_events(event)
|
||||||
|
|
||||||
def move_up(self) -> None:
|
def move_up(self) -> None:
|
||||||
self.score += self.board.move(Direction.UP)
|
self.board.move(Direction.UP)
|
||||||
|
|
||||||
def move_down(self) -> None:
|
def move_down(self) -> None:
|
||||||
self.score += self.board.move(Direction.DOWN)
|
self.board.move(Direction.DOWN)
|
||||||
|
|
||||||
def move_left(self) -> None:
|
def move_left(self) -> None:
|
||||||
self.score += self.board.move(Direction.LEFT)
|
self.board.move(Direction.LEFT)
|
||||||
|
|
||||||
def move_right(self) -> None:
|
def move_right(self) -> None:
|
||||||
self.score += self.board.move(Direction.RIGHT)
|
self.board.move(Direction.RIGHT)
|
||||||
|
|
||||||
def exit(self) -> None:
|
def exit(self) -> None:
|
||||||
"""Exit the game."""
|
"""Exit the game."""
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
from .board import Board
|
from .board import Board
|
||||||
from .button import Button
|
from .button import Button
|
||||||
from .label import Label
|
from .label import Label
|
||||||
from .sprite import Sprite
|
|
||||||
from .tile import Tile
|
from .tile import Tile
|
||||||
|
|
||||||
__all__ = ["Board", "Button", "Sprite", "Tile", "Label"]
|
__all__ = ["Board", "Button", "Label", "Tile"]
|
||||||
|
|||||||
5
src/py2048/objects/abc/__init__.py
Normal file
5
src/py2048/objects/abc/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from .clickable_ui_element import ClickableUIElement
|
||||||
|
from .movable_ui_element import MovableUIElement
|
||||||
|
from .ui_element import UIElement
|
||||||
|
|
||||||
|
__all__ = ["ClickableUIElement", "MovableUIElement", "UIElement"]
|
||||||
28
src/py2048/objects/abc/clickable_ui_element.py
Normal file
28
src/py2048/objects/abc/clickable_ui_element.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
from abc import ABC, ABCMeta, abstractmethod
|
||||||
|
from typing import Callable, Optional
|
||||||
|
|
||||||
|
import pygame
|
||||||
|
|
||||||
|
|
||||||
|
class ClickableUIElement(ABC, metaclass=ABCMeta):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
/,
|
||||||
|
hover_color: str,
|
||||||
|
action: Optional[Callable[[], None]] = None,
|
||||||
|
) -> None:
|
||||||
|
self.action = action
|
||||||
|
self.hover_color = hover_color
|
||||||
|
self.is_hovered = False
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def on_click(self) -> None:
|
||||||
|
"""Handle the click event."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def on_hover(self) -> None:
|
||||||
|
"""Handle the hover event."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def _draw_hover_background(self, surface: pygame.Surface) -> None:
|
||||||
|
"""Draw the hover rectangle."""
|
||||||
24
src/py2048/objects/abc/movable_ui_element.py
Normal file
24
src/py2048/objects/abc/movable_ui_element.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
from abc import ABC, ABCMeta, abstractmethod
|
||||||
|
|
||||||
|
import pygame
|
||||||
|
|
||||||
|
from py2048.utils import Direction, Position
|
||||||
|
|
||||||
|
|
||||||
|
class MovableUIElement(ABC, metaclass=ABCMeta):
|
||||||
|
@abstractmethod
|
||||||
|
def move(self, direction: Direction) -> None:
|
||||||
|
"""Move the element in the given direction."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def update(self) -> None:
|
||||||
|
"""Update the element."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
"""Return a hash of the sprite."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def pos(self) -> Position:
|
||||||
|
"""Return the position of the element."""
|
||||||
49
src/py2048/objects/abc/ui_element.py
Normal file
49
src/py2048/objects/abc/ui_element.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
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,
|
||||||
|
size: 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(Config.FONT.family, Config.FONT.size)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def draw(self, surface: pygame.Surface) -> None:
|
||||||
|
"""Draw the element on the given surface."""
|
||||||
|
|
||||||
|
@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."""
|
||||||
@ -3,7 +3,8 @@ import random
|
|||||||
import pygame
|
import pygame
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from py2048 import Config, Direction
|
from py2048 import Config
|
||||||
|
from py2048.utils import Direction, Position
|
||||||
|
|
||||||
from .tile import Tile
|
from .tile import Tile
|
||||||
|
|
||||||
@ -11,9 +12,9 @@ from .tile import Tile
|
|||||||
class Board(pygame.sprite.Group):
|
class Board(pygame.sprite.Group):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.rect = pygame.Rect(0, 0, Config.BOARD_WIDTH, Config.BOARD_HEIGHT)
|
self.rect = pygame.Rect(0, 0, *Config.BOARD.size)
|
||||||
self.rect.x = Config.BOARD_X
|
self.score: int = 0
|
||||||
self.rect.y = Config.BOARD_Y
|
self.rect.x, self.rect.y = Config.BOARD.pos
|
||||||
self.initiate_game()
|
self.initiate_game()
|
||||||
|
|
||||||
def initiate_game(self) -> None:
|
def initiate_game(self) -> None:
|
||||||
@ -22,7 +23,6 @@ class Board(pygame.sprite.Group):
|
|||||||
|
|
||||||
def draw(self, surface: pygame.Surface) -> None:
|
def draw(self, surface: pygame.Surface) -> None:
|
||||||
"""Draw the board."""
|
"""Draw the board."""
|
||||||
tile: Tile
|
|
||||||
self._draw_background(surface)
|
self._draw_background(surface)
|
||||||
|
|
||||||
super().draw(surface)
|
super().draw(surface)
|
||||||
@ -33,17 +33,17 @@ class Board(pygame.sprite.Group):
|
|||||||
surface,
|
surface,
|
||||||
Config.COLORSCHEME.BOARD_BG,
|
Config.COLORSCHEME.BOARD_BG,
|
||||||
self.rect,
|
self.rect,
|
||||||
border_radius=Config.TILE_BORDER_RADIUS,
|
border_radius=Config.TILE.border.radius,
|
||||||
) # background
|
) # background
|
||||||
pygame.draw.rect(
|
pygame.draw.rect(
|
||||||
surface,
|
surface,
|
||||||
Config.COLORSCHEME.BOARD_BG,
|
Config.COLORSCHEME.BOARD_BG,
|
||||||
self.rect,
|
self.rect,
|
||||||
width=Config.TILE_BORDER_WIDTH,
|
width=Config.TILE.border.width,
|
||||||
border_radius=Config.TILE_BORDER_RADIUS,
|
border_radius=Config.TILE.border.radius,
|
||||||
) # border
|
) # border
|
||||||
|
|
||||||
def move(self, direction: Direction) -> int:
|
def move(self, direction: Direction) -> None:
|
||||||
"""Move the tiles in the specified direction."""
|
"""Move the tiles in the specified direction."""
|
||||||
score = 0
|
score = 0
|
||||||
tiles = self.sprites()
|
tiles = self.sprites()
|
||||||
@ -60,23 +60,22 @@ class Board(pygame.sprite.Group):
|
|||||||
tiles.sort(key=lambda tile: tile.rect.x, reverse=True)
|
tiles.sort(key=lambda tile: tile.rect.x, reverse=True)
|
||||||
|
|
||||||
for tile in tiles:
|
for tile in tiles:
|
||||||
score += tile.move(direction)
|
tile.move(direction)
|
||||||
|
self.score += tile.value
|
||||||
|
|
||||||
if not self._is_full():
|
if not self._is_full():
|
||||||
self.generate_random_tile()
|
self.generate_random_tile()
|
||||||
|
|
||||||
return score
|
|
||||||
|
|
||||||
def generate_initial_tiles(self) -> None:
|
def generate_initial_tiles(self) -> None:
|
||||||
"""Generate the initial tiles."""
|
"""Generate the initial tiles."""
|
||||||
self.generate_tile(Config.INITIAL_TILE_COUNT)
|
self.generate_tile(Config.TILE.initial_count)
|
||||||
|
|
||||||
def generate_tile(self, amount: int = 1, *pos: tuple[int, int]) -> None:
|
def generate_tile(self, amount: int = 1, *pos: Position) -> None:
|
||||||
"""Generate `amount` number of tiles or at the specified positions."""
|
"""Generate `amount` number of tiles or at the specified positions."""
|
||||||
if pos:
|
if pos:
|
||||||
for coords in pos:
|
for coords in pos:
|
||||||
x, y = coords[0] * Config.TILE_SIZE, coords[1] * Config.TILE_SIZE
|
x, y = coords.x * Config.TILE.size, coords.y * Config.TILE.size
|
||||||
self.add(Tile(x, y, self))
|
self.add(Tile(Position(x, y), self))
|
||||||
return
|
return
|
||||||
|
|
||||||
for _ in range(amount):
|
for _ in range(amount):
|
||||||
@ -86,9 +85,9 @@ class Board(pygame.sprite.Group):
|
|||||||
"""Generate a tile with random coordinates aligned with the grid."""
|
"""Generate a tile with random coordinates aligned with the grid."""
|
||||||
while True:
|
while True:
|
||||||
# Generate random coordinates aligned with the grid
|
# Generate random coordinates aligned with the grid
|
||||||
x = random.randint(0, 3) * Config.TILE_SIZE + Config.BOARD_X
|
x = random.randint(0, 3) * Config.TILE.size + Config.BOARD.pos.x
|
||||||
y = random.randint(0, 3) * Config.TILE_SIZE + Config.BOARD_Y
|
y = random.randint(0, 3) * Config.TILE.size + Config.BOARD.pos.y
|
||||||
tile = Tile(x, y, self)
|
tile = Tile(Position(x, y), self)
|
||||||
|
|
||||||
colliding_tiles = pygame.sprite.spritecollide(
|
colliding_tiles = pygame.sprite.spritecollide(
|
||||||
tile, self, False
|
tile, self, False
|
||||||
@ -100,7 +99,7 @@ class Board(pygame.sprite.Group):
|
|||||||
|
|
||||||
def _is_full(self) -> bool:
|
def _is_full(self) -> bool:
|
||||||
"""Check if the board is full."""
|
"""Check if the board is full."""
|
||||||
return len(self.sprites()) == Config.BOARD_SIZE**2
|
return len(self.sprites()) == Config.BOARD.len**2
|
||||||
|
|
||||||
def _can_move(self) -> bool:
|
def _can_move(self) -> bool:
|
||||||
"""Check if any movement is possible on the board."""
|
"""Check if any movement is possible on the board."""
|
||||||
|
|||||||
@ -1,47 +1,54 @@
|
|||||||
import sys
|
import sys
|
||||||
|
from typing import Callable, Optional
|
||||||
|
|
||||||
import pygame
|
import pygame
|
||||||
from attrs import define, field
|
from attrs import define, field
|
||||||
|
|
||||||
from py2048.color import ColorScheme
|
from py2048 import Config
|
||||||
from py2048.config import Config
|
from py2048.utils import Direction, Position
|
||||||
|
|
||||||
|
from .abc import ClickableUIElement, UIElement
|
||||||
|
|
||||||
|
|
||||||
@define
|
class Button(UIElement, ClickableUIElement):
|
||||||
class Button(pygame.sprite.Sprite):
|
def __init__(
|
||||||
def __init__(self, text: str, x: int, y: int, group: pygame.sprite.Group):
|
self,
|
||||||
super().__init__()
|
/,
|
||||||
self.text = text
|
*,
|
||||||
self.image = self._create_button_surface()
|
hover_color: str,
|
||||||
|
action: Optional[Callable[[], None]] = None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
super().__init__(hover_color, action)
|
||||||
|
Static.__init__(self, **kwargs)
|
||||||
|
|
||||||
# text: str = field(kw_only=True)
|
def on_click(self) -> None:
|
||||||
# font_family: str = field(kw_only=True)
|
pass
|
||||||
# font_size: int = field(kw_only=True)
|
|
||||||
# font_color: ColorScheme = field(kw_only=True)
|
|
||||||
# position: tuple[int, int] = field(kw_only=True)
|
|
||||||
# width: int = field(kw_only=True)
|
|
||||||
# height: int = field(kw_only=True)
|
|
||||||
# action = field(kw_only=True)
|
|
||||||
# bg_color: ColorScheme = field(kw_only=True)
|
|
||||||
# hover_color: ColorScheme = field(kw_only=True)
|
|
||||||
# font: pygame.Font = field(init=False)
|
|
||||||
# rendered_text: pygame.Surface = field(init=False)
|
|
||||||
# rect: pygame.Rect = field(init=False)
|
|
||||||
# is_hovered: bool = field(init=False, default=False)
|
|
||||||
|
|
||||||
def __attrs_post_init__(self) -> None:
|
def on_hover(self) -> None:
|
||||||
"""Initialize the button."""
|
pass
|
||||||
self.font = pygame.font.SysFont(self.font_family, self.font_size)
|
|
||||||
self._draw_text()
|
def _draw_background(self, surface: pygame.Surface) -> None:
|
||||||
|
"""Draw a rectangle with borders on the given surface."""
|
||||||
|
pygame.draw.rect(
|
||||||
|
surface,
|
||||||
|
self.bg_color,
|
||||||
|
(*self.position, *self.size),
|
||||||
|
border_radius=self.border_radius,
|
||||||
|
)
|
||||||
|
|
||||||
def _draw_text(self) -> None:
|
def _draw_text(self) -> None:
|
||||||
"""Draw the text on the button."""
|
"""Draw the text of the element."""
|
||||||
self.rendered_text = self.font.render(
|
self.rendered_text = self.font.render(
|
||||||
self.text, True, self.font_color, self.bg_color
|
self.text, True, self.font_color, self.bg_color
|
||||||
)
|
)
|
||||||
self.rect = pygame.Rect(
|
self.rect = self.rendered_text.get_rect(topleft=self.position)
|
||||||
self.position[0], self.position[1], self.width, self.height
|
|
||||||
)
|
def _create_surface(self) -> pygame.Surface:
|
||||||
|
"""Create a surface for the element."""
|
||||||
|
sprite_surface = pygame.Surface(self.size, pygame.SRCALPHA)
|
||||||
|
self._draw_background(sprite_surface)
|
||||||
|
return sprite_surface
|
||||||
|
|
||||||
def check_hover(self, mouse_pos: tuple[int, int]) -> None:
|
def check_hover(self, mouse_pos: tuple[int, int]) -> None:
|
||||||
"""Check if the mouse is hovering over the button."""
|
"""Check if the mouse is hovering over the button."""
|
||||||
@ -54,18 +61,18 @@ class Button(pygame.sprite.Sprite):
|
|||||||
|
|
||||||
def draw(self, surface: pygame.Surface) -> None:
|
def draw(self, surface: pygame.Surface) -> None:
|
||||||
"""Draw the button on the given surface."""
|
"""Draw the button on the given surface."""
|
||||||
if self.is_hovered:
|
|
||||||
self._draw_rect(surface, self.hover_color)
|
|
||||||
else:
|
|
||||||
self._draw_rect(surface, self.bg_color)
|
|
||||||
|
|
||||||
surface.blit(self.rendered_text, self.position)
|
self._draw_hover_background(
|
||||||
|
surface
|
||||||
|
) if self.is_hovered else self._draw_background(surface)
|
||||||
|
|
||||||
def _draw_rect(self, surface: pygame.Surface, color: ColorScheme) -> None:
|
surface.blit(self.rendered_text, self.rect.topleft)
|
||||||
"""Draw the button rectangle."""
|
|
||||||
|
def _draw_hover_background(self, surface: pygame.Surface) -> None:
|
||||||
|
"""Draw the hover rectangle."""
|
||||||
pygame.draw.rect(
|
pygame.draw.rect(
|
||||||
surface,
|
surface,
|
||||||
self.bg_color,
|
self.hover_color,
|
||||||
self.rect,
|
self.rect,
|
||||||
border_radius=Config.TILE_BORDER_RADIUS,
|
border_radius=self.border_radius,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
import pygame
|
import pygame
|
||||||
from attrs import define, field
|
from attrs import define, field
|
||||||
from py2048.color import ColorScheme
|
|
||||||
from py2048.config import Config
|
from py2048 import Config
|
||||||
|
|
||||||
|
|
||||||
@define
|
@define
|
||||||
class Label:
|
class Label:
|
||||||
text: str
|
text: str
|
||||||
position: tuple[int, int]
|
position: tuple[int, int]
|
||||||
bg_color: ColorScheme
|
bg_color: str
|
||||||
font_family: str
|
font_family: str
|
||||||
font_color: ColorScheme
|
font_color: str
|
||||||
font_size: int
|
font_size: int
|
||||||
font: pygame.Font = field(init=False)
|
font: pygame.Font = field(init=False)
|
||||||
rendered_text: pygame.Surface = field(init=False)
|
rendered_text: pygame.Surface = field(init=False)
|
||||||
@ -20,15 +20,15 @@ class Label:
|
|||||||
self.font = pygame.font.SysFont(self.font_family, self.font_size)
|
self.font = pygame.font.SysFont(self.font_family, self.font_size)
|
||||||
self._draw_text()
|
self._draw_text()
|
||||||
|
|
||||||
def _draw_text(self) -> None:
|
def draw(self, surface: pygame.Surface) -> None:
|
||||||
self.rendered_text = self.font.render(
|
surface.blit(self.rendered_text, self.position)
|
||||||
self.text, True, self.font_color, self.bg_color
|
|
||||||
)
|
|
||||||
self.rect = self.rendered_text.get_rect(topleft=self.position)
|
|
||||||
|
|
||||||
def update(self, new_text: str) -> None:
|
def update(self, new_text: str) -> None:
|
||||||
self.text = new_text
|
self.text = new_text
|
||||||
self._draw_text()
|
self._draw_text()
|
||||||
|
|
||||||
def draw(self, surface: pygame.Surface) -> None:
|
def _draw_text(self) -> None:
|
||||||
surface.blit(self.rendered_text, self.position)
|
self.rendered_text = self.font.render(
|
||||||
|
self.text, True, self.font_color, self.bg_color
|
||||||
|
)
|
||||||
|
self.rect = self.rendered_text.get_rect(topleft=self.position)
|
||||||
|
|||||||
@ -1,39 +0,0 @@
|
|||||||
from abc import ABC, ABCMeta, abstractmethod
|
|
||||||
|
|
||||||
import pygame
|
|
||||||
|
|
||||||
from py2048 import Config, Direction
|
|
||||||
|
|
||||||
|
|
||||||
class Sprite(ABC, pygame.sprite.Sprite, metaclass=ABCMeta):
|
|
||||||
def __init__(self, x: int, y: int, group: pygame.sprite.Group):
|
|
||||||
super().__init__()
|
|
||||||
self.image = self._create_surface()
|
|
||||||
self.rect = self.image.get_rect()
|
|
||||||
self.rect.topleft = x, y
|
|
||||||
self.font = pygame.font.SysFont(Config.FONT_FAMILY, Config.FONT_SIZE)
|
|
||||||
self.group = group
|
|
||||||
self.update()
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def draw(self, surface: pygame.Surface) -> None:
|
|
||||||
"""Draw the sprite on the given surface."""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def update(self) -> None:
|
|
||||||
"""Update the sprite."""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def move(self, direction: Direction) -> None:
|
|
||||||
"""Move the tile by `dx` and `dy`."""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def _draw_background(self, surface: pygame.Surface) -> None:
|
|
||||||
"""Draw a rounded rectangle with borders on the given surface."""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def _create_surface(self) -> pygame.Surface:
|
|
||||||
"""Create a surface for the sprite."""
|
|
||||||
sprite_surface = pygame.Surface((100, 100), pygame.SRCALPHA)
|
|
||||||
self._draw_background(sprite_surface)
|
|
||||||
return sprite_surface
|
|
||||||
@ -2,102 +2,123 @@ import random
|
|||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
import pygame
|
import pygame
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from py2048 import ColorScheme, Config, Direction
|
from py2048 import Config
|
||||||
|
from py2048.utils import ColorScheme, Direction, Position, Size
|
||||||
|
|
||||||
from .sprite import Sprite
|
from .abc import MovableUIElement, UIElement
|
||||||
|
|
||||||
|
|
||||||
def _grid_pos(pos: int) -> int:
|
def _grid_pos(pos: int) -> int:
|
||||||
"""Return the position in the grid."""
|
"""Return the position in the grid."""
|
||||||
return pos // Config.TILE_SIZE + 1
|
return pos // Config.TILE.size + 1
|
||||||
|
|
||||||
|
|
||||||
class Tile(Sprite):
|
class Tile(UIElement, MovableUIElement, pygame.sprite.Sprite):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, x: int, y: int, group: pygame.sprite.Group, value: int | None = 2
|
self,
|
||||||
|
position: Position,
|
||||||
|
group: pygame.sprite.Group,
|
||||||
):
|
):
|
||||||
super().__init__(x, y, group)
|
pygame.sprite.Sprite.__init__(self)
|
||||||
|
self.value = 2 if random.random() <= Config.TILE.value_probability else 4
|
||||||
|
|
||||||
self.value: int = (
|
super().__init__(
|
||||||
value
|
position=position,
|
||||||
if value is not None
|
text=f"{self.value}",
|
||||||
else 2
|
bg_color=Config.COLORSCHEME.TILE_0,
|
||||||
if random.random() <= Config.TILE_VALUE_PROBABILITY
|
font_color=Config.COLORSCHEME.DARK_TEXT,
|
||||||
else 4
|
size=Size(Config.TILE.size, Config.TILE.size),
|
||||||
|
border_radius=Config.TILE.border.radius,
|
||||||
|
border_width=Config.TILE.border.width,
|
||||||
)
|
)
|
||||||
|
self.score: int = 0
|
||||||
|
self.group = group
|
||||||
|
|
||||||
self.image = self._create_surface()
|
self.image = self._create_surface()
|
||||||
self.rect = self.image.get_rect()
|
self.rect = self.image.get_rect()
|
||||||
self.rect.topleft = x, y
|
self.rect.topleft = self.position
|
||||||
self.font = pygame.font.SysFont(Config.FONT_FAMILY, Config.FONT_SIZE)
|
|
||||||
self.group = group
|
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
|
def draw(self, surface: pygame.Surface) -> None:
|
||||||
|
"""Draw the value of the tile."""
|
||||||
|
self._draw_background(surface)
|
||||||
|
self._draw_text()
|
||||||
|
|
||||||
|
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:
|
def _draw_background(self, surface: pygame.Surface) -> None:
|
||||||
"""Draw a rounded rectangle with borders on the given surface."""
|
"""Draw a rounded rectangle with borders on the given surface."""
|
||||||
rect = (0, 0, Config.TILE_SIZE, Config.TILE_SIZE)
|
rect = (0, 0, *self.size)
|
||||||
pygame.draw.rect(
|
pygame.draw.rect(
|
||||||
surface, self._get_color(), rect, border_radius=Config.TILE_BORDER_RADIUS
|
surface, self._get_color(), rect, border_radius=Config.TILE.border.radius
|
||||||
) # background
|
) # background
|
||||||
pygame.draw.rect(
|
pygame.draw.rect(
|
||||||
surface,
|
surface,
|
||||||
(0, 0, 0, 0),
|
(0, 0, 0, 0),
|
||||||
rect,
|
rect,
|
||||||
border_radius=Config.TILE_BORDER_RADIUS,
|
border_radius=Config.TILE.border.radius,
|
||||||
width=Config.TILE_BORDER_WIDTH,
|
width=Config.TILE.border.width,
|
||||||
) # border
|
) # border
|
||||||
|
|
||||||
def _create_surface(self) -> pygame.Surface:
|
def _draw_text(self) -> None:
|
||||||
"""Create a surface for the tile."""
|
"""Draw the text of the sprite."""
|
||||||
sprite_surface = pygame.Surface(
|
self.rendered_text = self.font.render(self.text, True, self.font_color)
|
||||||
(Config.TILE_SIZE, Config.TILE_SIZE), pygame.SRCALPHA
|
self.image.blit(
|
||||||
|
self.rendered_text,
|
||||||
|
self.rendered_text.get_rect(center=self.image.get_rect().center),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _create_surface(self) -> pygame.Surface:
|
||||||
|
"""Create a surface for the sprite."""
|
||||||
|
sprite_surface = pygame.Surface(self.size, pygame.SRCALPHA)
|
||||||
self._draw_background(sprite_surface)
|
self._draw_background(sprite_surface)
|
||||||
return sprite_surface
|
return sprite_surface
|
||||||
|
|
||||||
def draw(self, surface: pygame.Surface) -> None:
|
def move(self, direction: Direction) -> None:
|
||||||
"""Draw the value of the tile."""
|
"""
|
||||||
text = self.font.render(str(self.value), True, Config.COLORSCHEME.DARK_TEXT)
|
Move the tile by `dx` and `dy`.
|
||||||
sprite_surface = self._create_surface()
|
If the tile collides with another tile, it will merge with it if possible.
|
||||||
|
Before moving, reset the score of the tile.
|
||||||
sprite_center: tuple[int, int] = (Config.TILE_SIZE // 2, Config.TILE_SIZE // 2)
|
"""
|
||||||
|
|
||||||
text_rect: pygame.Rect = text.get_rect(center=self.image.get_rect().center)
|
|
||||||
sprite_surface.blit(text, text_rect)
|
|
||||||
|
|
||||||
self.image.blit(sprite_surface, (0, 0))
|
|
||||||
|
|
||||||
def move(self, direction: Direction) -> int:
|
|
||||||
"""Move the tile by `dx` and `dy`."""
|
|
||||||
score = 0
|
|
||||||
while True:
|
while True:
|
||||||
new_x, new_y = self._calc_new_pos(direction)
|
new_x, new_y = self._calc_new_pos(direction)
|
||||||
|
|
||||||
if self._is_out_if_bounds(new_x, new_y):
|
if self._is_out_if_bounds(new_x, new_y):
|
||||||
return score
|
return
|
||||||
|
|
||||||
if self._has_collision(new_x, new_y):
|
if self._has_collision(new_x, new_y):
|
||||||
collided_tile = self._get_collided_tile(new_x, new_y)
|
collided_tile = self._get_collided_tile(new_x, new_y)
|
||||||
if collided_tile and self._can_merge(collided_tile):
|
if collided_tile and self._can_merge(collided_tile):
|
||||||
score += self._merge(collided_tile)
|
self._merge(collided_tile)
|
||||||
else:
|
else:
|
||||||
return score
|
return
|
||||||
|
|
||||||
self.group.remove(self)
|
self.group.remove(self)
|
||||||
self.rect.topleft = new_x, new_y
|
self.rect.topleft = new_x, new_y
|
||||||
self.group.add(self)
|
self.group.add(self)
|
||||||
|
|
||||||
|
def get_score(self) -> int:
|
||||||
|
"""Return the score of the tile."""
|
||||||
|
return self.score
|
||||||
|
|
||||||
def _calc_new_pos(self, direction: Direction) -> tuple[int, int]:
|
def _calc_new_pos(self, direction: Direction) -> tuple[int, int]:
|
||||||
"""Calculate the new position of the tile."""
|
"""Calculate the new position of the tile."""
|
||||||
dx, dy = direction * Config.TILE_SIZE
|
dx, dy = direction * Config.TILE.size
|
||||||
return self.rect.x + dx, self.rect.y + dy
|
return self.rect.x + dx, self.rect.y + dy
|
||||||
|
|
||||||
def _is_out_if_bounds(self, x: int, y: int) -> bool:
|
def _is_out_if_bounds(self, x: int, y: int) -> bool:
|
||||||
"""Return whether the tile is out of bounds."""
|
"""Return whether the tile is out of bounds."""
|
||||||
board_left = Config.BOARD_X
|
board_left = Config.BOARD.pos.x
|
||||||
board_right = Config.BOARD_X + Config.BOARD_WIDTH - Config.TILE_SIZE
|
board_right = Config.BOARD.pos.x + Config.BOARD.size.width - Config.TILE.size
|
||||||
board_top = Config.BOARD_Y
|
board_top = Config.BOARD.pos.y
|
||||||
board_bottom = Config.BOARD_Y + Config.BOARD_HEIGHT - Config.TILE_SIZE
|
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)
|
return not (board_left <= x <= board_right and board_top <= y <= board_bottom)
|
||||||
|
|
||||||
def _has_collision(self, x: int, y: int) -> bool:
|
def _has_collision(self, x: int, y: int) -> bool:
|
||||||
@ -120,18 +141,13 @@ class Tile(Sprite):
|
|||||||
"""Check if the tile can merge with another tile."""
|
"""Check if the tile can merge with another tile."""
|
||||||
return self.value == other.value
|
return self.value == other.value
|
||||||
|
|
||||||
def _merge(self, other: "Tile") -> int:
|
def _merge(self, other: "Tile") -> None:
|
||||||
"""Merge the tile with another tile."""
|
"""Merge the tile with another tile."""
|
||||||
self.group.remove(other)
|
self.group.remove(other)
|
||||||
self.group.remove(self)
|
self.group.remove(self)
|
||||||
self.value += other.value
|
self.value += other.value
|
||||||
self.update()
|
self.update()
|
||||||
self.group.add(self)
|
self.group.add(self)
|
||||||
return self.value
|
|
||||||
|
|
||||||
def update(self) -> None:
|
|
||||||
"""Update the sprite."""
|
|
||||||
self.draw()
|
|
||||||
|
|
||||||
def can_move(self) -> bool:
|
def can_move(self) -> bool:
|
||||||
"""Check if the tile can move"""
|
"""Check if the tile can move"""
|
||||||
@ -145,8 +161,8 @@ class Tile(Sprite):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _get_color(self) -> ColorScheme:
|
def _get_color(self) -> str:
|
||||||
"""Change the color of the tile based on its value"""
|
"""Change the color of the tile based on its value."""
|
||||||
color_map = {
|
color_map = {
|
||||||
2: Config.COLORSCHEME.TILE_2,
|
2: Config.COLORSCHEME.TILE_2,
|
||||||
4: Config.COLORSCHEME.TILE_4,
|
4: Config.COLORSCHEME.TILE_4,
|
||||||
@ -175,6 +191,6 @@ class Tile(Sprite):
|
|||||||
return hash((self.rect.x, self.rect.y, self.value))
|
return hash((self.rect.x, self.rect.y, self.value))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pos(self) -> tuple[int, int]:
|
def pos(self) -> Position:
|
||||||
"""Return the position of the tile."""
|
"""Return the position of the tile."""
|
||||||
return _grid_pos(self.rect.x), _grid_pos(self.rect.y)
|
return Position(_grid_pos(self.rect.x), _grid_pos(self.rect.y))
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from .header import Header
|
from .header import Header
|
||||||
from .menu import Menu
|
from .menu import Menu
|
||||||
|
|
||||||
__all__ = ["Menu", "Header"]
|
__all__ = ["Header", "Menu"]
|
||||||
|
|||||||
@ -1,19 +1,25 @@
|
|||||||
import pygame
|
import pygame
|
||||||
|
|
||||||
from py2048 import Config
|
from py2048 import Config
|
||||||
from py2048.objects import Label
|
from py2048.objects import Label
|
||||||
|
from py2048.utils import Position
|
||||||
|
|
||||||
|
|
||||||
class Header:
|
class Header:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.rect = pygame.Rect(0, 0, Config.HEADER_WIDTH, Config.HEADER_HEIGHT)
|
self.rect = pygame.Rect(0, 0, *Config.HEADER.size)
|
||||||
|
|
||||||
def draw(self, screen: pygame.Surface, score: int) -> None:
|
def draw(self, screen: pygame.Surface, score: int) -> None:
|
||||||
"""Draw the header."""
|
"""Draw the header."""
|
||||||
score = Label(
|
self.score = Label(
|
||||||
text=f"{score}",
|
text=f"{score}",
|
||||||
position=(10, 10),
|
position=Position(10, 10),
|
||||||
bg_color=Config.COLORSCHEME.BOARD_BG,
|
bg_color=Config.COLORSCHEME.BOARD_BG,
|
||||||
font_family=Config.FONT_FAMILY,
|
font_family=Config.FONT.family,
|
||||||
font_color=Config.COLORSCHEME.DARK_TEXT,
|
font_color=Config.COLORSCHEME.DARK_TEXT,
|
||||||
font_size=Config.FONT_SIZE,
|
font_size=Config.FONT.size,
|
||||||
).draw(screen)
|
).draw(screen)
|
||||||
|
|
||||||
|
def update(self, score: int) -> None:
|
||||||
|
"""Update the header."""
|
||||||
|
self.Label.text = f"{score}"
|
||||||
|
|||||||
@ -3,6 +3,7 @@ from loguru import logger
|
|||||||
|
|
||||||
from py2048 import Config
|
from py2048 import Config
|
||||||
from py2048.objects import Button
|
from py2048.objects import Button
|
||||||
|
from py2048.utils import Position
|
||||||
|
|
||||||
|
|
||||||
class Menu:
|
class Menu:
|
||||||
@ -14,22 +15,21 @@ class Menu:
|
|||||||
"Exit": self.exit,
|
"Exit": self.exit,
|
||||||
}
|
}
|
||||||
buttons_width, button_height = 120, 50
|
buttons_width, button_height = 120, 50
|
||||||
|
|
||||||
self.buttons = [
|
self.buttons = [
|
||||||
Button(
|
Button(
|
||||||
text=text,
|
position=Position(
|
||||||
font_family=Config.FONT_FAMILY,
|
Config.SCREEN.size.width / 2 - button_height // 2,
|
||||||
font_size=Config.FONT_SIZE,
|
Config.SCREEN.size.height / len(buttons_data) * index
|
||||||
font_color=Config.COLORSCHEME.LIGHT_TEXT,
|
- button_height // 2,
|
||||||
position=(
|
|
||||||
Config.SCREEN_WIDTH / 2 - 50,
|
|
||||||
Config.SCREEN_HEIGHT / (len(buttons_data) + 1) * index
|
|
||||||
- button_height,
|
|
||||||
),
|
),
|
||||||
width=buttons_width,
|
|
||||||
height=button_height,
|
|
||||||
action=action,
|
|
||||||
bg_color=Config.COLORSCHEME.BOARD_BG,
|
bg_color=Config.COLORSCHEME.BOARD_BG,
|
||||||
|
font_color=Config.COLORSCHEME.LIGHT_TEXT,
|
||||||
hover_color=Config.COLORSCHEME.TILE_0,
|
hover_color=Config.COLORSCHEME.TILE_0,
|
||||||
|
size=(buttons_width, button_height),
|
||||||
|
text=text,
|
||||||
|
border_radius=Config.TILE.border.radius,
|
||||||
|
action=action,
|
||||||
)
|
)
|
||||||
for index, (text, action) in enumerate(buttons_data.items(), start=1)
|
for index, (text, action) in enumerate(buttons_data.items(), start=1)
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,33 +0,0 @@
|
|||||||
from enum import Enum
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
from .config import Config
|
|
||||||
|
|
||||||
BASE_PATH = Path(__file__).resolve().parent.parent.parent
|
|
||||||
|
|
||||||
|
|
||||||
def _setup_logger() -> None:
|
|
||||||
logger.add(
|
|
||||||
BASE_PATH.joinpath(".logs", "game.log"),
|
|
||||||
format="{time} | {level} | {message}",
|
|
||||||
level="DEBUG" if BASE_PATH.joinpath("debug").exists() else "INFO",
|
|
||||||
rotation="1 MB",
|
|
||||||
compression="zip",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Direction(Enum):
|
|
||||||
UP = (0, -1)
|
|
||||||
DOWN = (0, 1)
|
|
||||||
LEFT = (-1, 0)
|
|
||||||
RIGHT = (1, 0)
|
|
||||||
|
|
||||||
def __mul__(self, num: int) -> tuple[int, int]:
|
|
||||||
"""Multiply the direction by a constant."""
|
|
||||||
return self.value[0] * num, self.value[1] * num
|
|
||||||
|
|
||||||
def __imul__(self, num: int) -> tuple[int, int]:
|
|
||||||
"""Multiply the direction by a constant."""
|
|
||||||
return self.value[0] * num, self.value[1] * num
|
|
||||||
34
src/py2048/utils/__init__.py
Normal file
34
src/py2048/utils/__init__.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
from .collections import Board, Font, Header, Position, Screen, Size, Tile
|
||||||
|
from .color import ColorScheme
|
||||||
|
from .enums import Direction
|
||||||
|
|
||||||
|
BASE_PATH = Path(__file__).resolve().parent.parent.parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
def setup_logger() -> None:
|
||||||
|
logger.add(
|
||||||
|
BASE_PATH.joinpath(".logs", "game.log"),
|
||||||
|
format="{time} | {level} | {message}",
|
||||||
|
level="DEBUG" if BASE_PATH.joinpath("debug").exists() else "INFO",
|
||||||
|
rotation="1 MB",
|
||||||
|
compression="zip",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"BASE_PATH",
|
||||||
|
"Board",
|
||||||
|
"ColorScheme",
|
||||||
|
"Direction",
|
||||||
|
"Font",
|
||||||
|
"Position",
|
||||||
|
"Size",
|
||||||
|
"Tile",
|
||||||
|
"setup_logger",
|
||||||
|
"Header",
|
||||||
|
"Screen",
|
||||||
|
]
|
||||||
52
src/py2048/utils/collections.py
Normal file
52
src/py2048/utils/collections.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
from typing import NamedTuple
|
||||||
|
|
||||||
|
from attr import Factory, define, field
|
||||||
|
|
||||||
|
|
||||||
|
class Position(NamedTuple):
|
||||||
|
x: int
|
||||||
|
y: int
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
18
src/py2048/utils/enums.py
Normal file
18
src/py2048/utils/enums.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
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)
|
||||||
Loading…
Reference in New Issue
Block a user