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")
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)

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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:
"""

View File

@ -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")

View File

@ -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):