diff --git a/src/py2048/game.py b/src/py2048/game.py index 649c282..412f331 100644 --- a/src/py2048/game.py +++ b/src/py2048/game.py @@ -19,7 +19,7 @@ class Game: pygame.display.set_caption("2048") self.board = Board() self.header = Header() - # self.menu = Menu() + self.menu = Menu() def run(self) -> None: """Run the game loop.""" @@ -30,14 +30,14 @@ class Game: def _update(self) -> None: """Update the game.""" - # self.board.update() + self.board.update() def _render(self) -> None: """Render the game.""" self.screen.fill(Config.COLORSCHEME.BG) - self.board.draw(self.screen) - self.header.draw(self.screen, 2048) - # self.menu.draw(self.screen) + # self.board.draw(self.screen) + # self.header.draw(self.screen, 2048) + self.menu.draw(self.screen) pygame.display.flip() def _hande_events(self) -> None: @@ -56,7 +56,7 @@ class Game: self.move_down() elif event.key == pygame.K_q: self.exit() - # self.menu._handle_events(event) + self.menu._handle_events(event) def move_up(self) -> None: self.board.move(Direction.UP) diff --git a/src/py2048/objects/abc/clickable_ui_element.py b/src/py2048/objects/abc/clickable_ui_element.py index b74e77a..2c54bb0 100644 --- a/src/py2048/objects/abc/clickable_ui_element.py +++ b/src/py2048/objects/abc/clickable_ui_element.py @@ -1,26 +1,32 @@ -from abc import ABC, ABCMeta, abstractmethod +from abc import abstractmethod from typing import Callable, Optional import pygame +from py2048.utils import Position -class ClickableUIElement(ABC, metaclass=ABCMeta): +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) -> None: + def on_click(self, mouse_pos: Position) -> None: """Handle the click event.""" @abstractmethod - def on_hover(self) -> None: + def on_hover(self, mouse_pos: Position) -> None: """Handle the hover event.""" @abstractmethod diff --git a/src/py2048/objects/abc/movable_ui_element.py b/src/py2048/objects/abc/movable_ui_element.py index 2f20746..413a6fe 100644 --- a/src/py2048/objects/abc/movable_ui_element.py +++ b/src/py2048/objects/abc/movable_ui_element.py @@ -1,23 +1,20 @@ -from abc import ABC, ABCMeta, abstractmethod +from abc import abstractmethod 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) -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: diff --git a/src/py2048/objects/button.py b/src/py2048/objects/button.py index 1ccb375..a271147 100644 --- a/src/py2048/objects/button.py +++ b/src/py2048/objects/button.py @@ -10,63 +10,44 @@ from py2048.utils import Direction, Position from .abc import ClickableUIElement, UIElement -class Button(UIElement, ClickableUIElement): - def __init__( - self, - /, - *, - hover_color: str, - action: Optional[Callable[[], None]] = None, - **kwargs, - ): - super().__init__(hover_color, action) - Static.__init__(self, **kwargs) +class Button(ClickableUIElement, pygame.sprite.Sprite): + def __init__(self, *args, **kwargs): + pygame.sprite.Sprite.__init__(self) + super().__init__(*args, **kwargs) - def on_click(self) -> None: - pass - - def on_hover(self) -> None: - pass - - 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: - """Draw the text of the element.""" - 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) - - 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: - """Check if the mouse is hovering over the button.""" - self.is_hovered = self.rect.collidepoint(mouse_pos) - - def check_click(self, mouse_pos: tuple[int, int]) -> None: - """Check if the button is clicked.""" - if self.rect.collidepoint(mouse_pos) and self.action: - self.action() + 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)) - self._draw_hover_background( - surface - ) if self.is_hovered else self._draw_background(surface) + def update(self) -> None: + """Update the button.""" + self._draw_background(self.image) + self._draw_text() + self.image.blit(self.image, (0, 0)) - surface.blit(self.rendered_text, self.rect.topleft) + 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.""" + 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.""" @@ -76,3 +57,17 @@ class Button(UIElement, ClickableUIElement): self.rect, border_radius=self.border_radius, ) + + def _draw_text(self) -> None: + """Draw the text of the element.""" + self.rendered_text = self.font.render(self.text, True, self.font_color) + 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 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 index 6e82aee..ea5426b 100644 --- a/src/py2048/objects/tile.py +++ b/src/py2048/objects/tile.py @@ -15,7 +15,7 @@ def _grid_pos(pos: int) -> int: return pos // Config.TILE.size + 1 -class Tile(UIElement, MovableUIElement, pygame.sprite.Sprite): +class Tile(MovableUIElement, pygame.sprite.Sprite): def __init__( self, position: Position, @@ -39,12 +39,12 @@ class Tile(UIElement, MovableUIElement, pygame.sprite.Sprite): 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.""" @@ -54,18 +54,24 @@ class Tile(UIElement, MovableUIElement, pygame.sprite.Sprite): self.image.blit(self.image, (0, 0)) def _draw_background(self, surface: pygame.Surface) -> None: - """Draw a rounded rectangle with borders on the given surface.""" - rect = (0, 0, *self.size) + """Draw a rounded rectangle on the given surface.""" pygame.draw.rect( - surface, self._get_color(), rect, border_radius=Config.TILE.border.radius - ) # background + 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.""" pygame.draw.rect( surface, (0, 0, 0, 0), - rect, + (0, 0, *self.size), border_radius=Config.TILE.border.radius, width=Config.TILE.border.width, - ) # border + ) def _draw_text(self) -> None: """Draw the text of the sprite.""" @@ -77,9 +83,9 @@ class Tile(UIElement, MovableUIElement, pygame.sprite.Sprite): 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) - return sprite_surface + surface = pygame.Surface(self.size, pygame.SRCALPHA) + self._draw_background(surface) + return surface def move(self, direction: Direction) -> None: """ diff --git a/src/py2048/screens/menu.py b/src/py2048/screens/menu.py index 3c8f237..f446077 100644 --- a/src/py2048/screens/menu.py +++ b/src/py2048/screens/menu.py @@ -6,45 +6,44 @@ from py2048.objects import Button from py2048.utils import Position -class Menu: +class Menu(pygame.sprite.AbstractGroup): def __init__(self): + super().__init__() + buttons_data = { "Play": self.play, "AI": self.ai, "Settings": self.settings, "Exit": self.exit, } - buttons_width, button_height = 120, 50 + button_width, button_height = 120, 50 - self.buttons = [ - Button( - position=Position( - Config.SCREEN.size.width / 2 - button_height // 2, - Config.SCREEN.size.height / len(buttons_data) * index - - button_height // 2, - ), - bg_color=Config.COLORSCHEME.BOARD_BG, - font_color=Config.COLORSCHEME.LIGHT_TEXT, - 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): + self.add( + Button( + position=Position( + Config.SCREEN.size.width / 2 - button_width / 2, + Config.SCREEN.size.height / len(buttons_data) * index + - button_height, + ), + bg_color=Config.COLORSCHEME.BOARD_BG, + font_color=Config.COLORSCHEME.LIGHT_TEXT, + hover_color=Config.COLORSCHEME.TILE_0, + size=(button_width, button_height), + text=text, + border_radius=Config.TILE.border.radius, + action=action, + ) ) - for index, (text, action) in enumerate(buttons_data.items(), start=1) - ] def _handle_events(self, event: pygame.event.Event) -> None: - if event.type == pygame.MOUSEMOTION: - for button in self.buttons: - button.check_hover(event.pos) - elif event.type == pygame.MOUSEBUTTONDOWN: - for button in self.buttons: - button.check_click(event.pos) - - def draw(self, surface: pygame.Surface) -> None: - for button in self.buttons: - button.draw(surface) + """Handle the event.""" + if event.type == pygame.MOUSEBUTTONDOWN: + for button in self.sprites(): + button.on_click(Position(*event.pos)) + elif event.type == pygame.MOUSEMOTION: + for button in self.sprites(): + button.on_hover(Position(*event.pos)) def play(self) -> None: logger.debug("Play") diff --git a/src/py2048/utils/collections.py b/src/py2048/utils/collections.py index 0167ada..f317a98 100644 --- a/src/py2048/utils/collections.py +++ b/src/py2048/utils/collections.py @@ -4,8 +4,8 @@ from attr import Factory, define, field class Position(NamedTuple): - x: int - y: int + x: int | float + y: int | float class Size(NamedTuple):