refactor(game): Menu

This commit is contained in:
Kristofers Solo 2024-01-03 00:04:38 +02:00
parent 3354f84bbe
commit d22b5dbab2
7 changed files with 116 additions and 113 deletions

View File

@ -19,7 +19,7 @@ class Game:
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()
def run(self) -> None: def run(self) -> None:
"""Run the game loop.""" """Run the game loop."""
@ -30,14 +30,14 @@ class Game:
def _update(self) -> None: def _update(self) -> None:
"""Update the game.""" """Update the game."""
# self.board.update() self.board.update()
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, 2048) # self.header.draw(self.screen, 2048)
# 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:
@ -56,7 +56,7 @@ 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.board.move(Direction.UP) self.board.move(Direction.UP)

View File

@ -1,26 +1,32 @@
from abc import ABC, ABCMeta, abstractmethod from abc import abstractmethod
from typing import Callable, Optional from typing import Callable, Optional
import pygame import pygame
from py2048.utils import Position
class ClickableUIElement(ABC, metaclass=ABCMeta): from .ui_element import UIElement
class ClickableUIElement(UIElement):
def __init__( def __init__(
self, self,
/,
hover_color: str, hover_color: str,
action: Optional[Callable[[], None]] = None, action: Optional[Callable[[], None]] = None,
*args,
**kwargs,
) -> None: ) -> None:
super().__init__(*args, **kwargs)
self.action = action self.action = action
self.hover_color = hover_color self.hover_color = hover_color
self.is_hovered = False self.is_hovered = False
@abstractmethod @abstractmethod
def on_click(self) -> None: def on_click(self, mouse_pos: Position) -> None:
"""Handle the click event.""" """Handle the click event."""
@abstractmethod @abstractmethod
def on_hover(self) -> None: def on_hover(self, mouse_pos: Position) -> None:
"""Handle the hover event.""" """Handle the hover event."""
@abstractmethod @abstractmethod

View File

@ -1,23 +1,20 @@
from abc import ABC, ABCMeta, abstractmethod from abc import abstractmethod
import pygame import pygame
from py2048.utils import Direction, Position 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 @abstractmethod
def move(self, direction: Direction) -> None: def move(self, direction: Direction) -> None:
"""Move the element in the given direction.""" """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 @property
@abstractmethod @abstractmethod
def pos(self) -> Position: def pos(self) -> Position:

View File

@ -10,63 +10,44 @@ from py2048.utils import Direction, Position
from .abc import ClickableUIElement, UIElement from .abc import ClickableUIElement, UIElement
class Button(UIElement, ClickableUIElement): class Button(ClickableUIElement, pygame.sprite.Sprite):
def __init__( def __init__(self, *args, **kwargs):
self, pygame.sprite.Sprite.__init__(self)
/, super().__init__(*args, **kwargs)
*,
hover_color: str,
action: Optional[Callable[[], None]] = None,
**kwargs,
):
super().__init__(hover_color, action)
Static.__init__(self, **kwargs)
def on_click(self) -> None: self.image = self._create_surface()
pass self.rect = self.image.get_rect()
self.rect.topleft = self.position
def on_hover(self) -> None: self.update()
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()
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."""
self._draw_background(surface)
self._draw_text()
self.image.blit(self.image, (0, 0))
self._draw_hover_background( def update(self) -> None:
surface """Update the button."""
) if self.is_hovered else self._draw_background(surface) 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: def _draw_hover_background(self, surface: pygame.Surface) -> None:
"""Draw the hover rectangle.""" """Draw the hover rectangle."""
@ -76,3 +57,17 @@ class Button(UIElement, ClickableUIElement):
self.rect, self.rect,
border_radius=self.border_radius, 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

View File

@ -15,7 +15,7 @@ def _grid_pos(pos: int) -> int:
return pos // Config.TILE.size + 1 return pos // Config.TILE.size + 1
class Tile(UIElement, MovableUIElement, pygame.sprite.Sprite): class Tile(MovableUIElement, pygame.sprite.Sprite):
def __init__( def __init__(
self, self,
position: Position, position: Position,
@ -39,12 +39,12 @@ class Tile(UIElement, MovableUIElement, pygame.sprite.Sprite):
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 = self.position self.rect.topleft = self.position
self.update()
def draw(self, surface: pygame.Surface) -> None: def draw(self, surface: pygame.Surface) -> None:
"""Draw the value of the tile.""" """Draw the value of the tile."""
self._draw_background(surface) self._draw_background(surface)
self._draw_text() self._draw_text()
self.image.blit(self.image, (0, 0))
def update(self) -> None: def update(self) -> None:
"""Update the sprite.""" """Update the sprite."""
@ -54,18 +54,24 @@ class Tile(UIElement, MovableUIElement, pygame.sprite.Sprite):
self.image.blit(self.image, (0, 0)) 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 on the given surface."""
rect = (0, 0, *self.size)
pygame.draw.rect( pygame.draw.rect(
surface, self._get_color(), rect, border_radius=Config.TILE.border.radius surface,
) # background 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( pygame.draw.rect(
surface, surface,
(0, 0, 0, 0), (0, 0, 0, 0),
rect, (0, 0, *self.size),
border_radius=Config.TILE.border.radius, border_radius=Config.TILE.border.radius,
width=Config.TILE.border.width, width=Config.TILE.border.width,
) # border )
def _draw_text(self) -> None: def _draw_text(self) -> None:
"""Draw the text of the sprite.""" """Draw the text of the sprite."""
@ -77,9 +83,9 @@ class Tile(UIElement, MovableUIElement, pygame.sprite.Sprite):
def _create_surface(self) -> pygame.Surface: def _create_surface(self) -> pygame.Surface:
"""Create a surface for the sprite.""" """Create a surface for the sprite."""
sprite_surface = pygame.Surface(self.size, pygame.SRCALPHA) surface = pygame.Surface(self.size, pygame.SRCALPHA)
self._draw_background(sprite_surface) self._draw_background(surface)
return sprite_surface return surface
def move(self, direction: Direction) -> None: def move(self, direction: Direction) -> None:
""" """

View File

@ -6,45 +6,44 @@ from py2048.objects import Button
from py2048.utils import Position from py2048.utils import Position
class Menu: class Menu(pygame.sprite.AbstractGroup):
def __init__(self): def __init__(self):
super().__init__()
buttons_data = { buttons_data = {
"Play": self.play, "Play": self.play,
"AI": self.ai, "AI": self.ai,
"Settings": self.settings, "Settings": self.settings,
"Exit": self.exit, "Exit": self.exit,
} }
buttons_width, button_height = 120, 50 button_width, button_height = 120, 50
self.buttons = [ for index, (text, action) in enumerate(buttons_data.items(), start=1):
self.add(
Button( Button(
position=Position( position=Position(
Config.SCREEN.size.width / 2 - button_height // 2, Config.SCREEN.size.width / 2 - button_width / 2,
Config.SCREEN.size.height / len(buttons_data) * index Config.SCREEN.size.height / len(buttons_data) * index
- button_height // 2, - button_height,
), ),
bg_color=Config.COLORSCHEME.BOARD_BG, bg_color=Config.COLORSCHEME.BOARD_BG,
font_color=Config.COLORSCHEME.LIGHT_TEXT, font_color=Config.COLORSCHEME.LIGHT_TEXT,
hover_color=Config.COLORSCHEME.TILE_0, hover_color=Config.COLORSCHEME.TILE_0,
size=(buttons_width, button_height), size=(button_width, button_height),
text=text, text=text,
border_radius=Config.TILE.border.radius, border_radius=Config.TILE.border.radius,
action=action, action=action,
) )
for index, (text, action) in enumerate(buttons_data.items(), start=1) )
]
def _handle_events(self, event: pygame.event.Event) -> None: def _handle_events(self, event: pygame.event.Event) -> None:
if event.type == pygame.MOUSEMOTION: """Handle the event."""
for button in self.buttons: if event.type == pygame.MOUSEBUTTONDOWN:
button.check_hover(event.pos) for button in self.sprites():
elif event.type == pygame.MOUSEBUTTONDOWN: button.on_click(Position(*event.pos))
for button in self.buttons: elif event.type == pygame.MOUSEMOTION:
button.check_click(event.pos) for button in self.sprites():
button.on_hover(Position(*event.pos))
def draw(self, surface: pygame.Surface) -> None:
for button in self.buttons:
button.draw(surface)
def play(self) -> None: def play(self) -> None:
logger.debug("Play") logger.debug("Play")

View File

@ -4,8 +4,8 @@ from attr import Factory, define, field
class Position(NamedTuple): class Position(NamedTuple):
x: int x: int | float
y: int y: int | float
class Size(NamedTuple): class Size(NamedTuple):