mirror of
https://github.com/kristoferssolo/2048.git
synced 2025-10-21 15:20:35 +00:00
refactor(game): Menu
This commit is contained in:
parent
3354f84bbe
commit
d22b5dbab2
@ -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)
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -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):
|
||||||
Button(
|
self.add(
|
||||||
position=Position(
|
Button(
|
||||||
Config.SCREEN.size.width / 2 - button_height // 2,
|
position=Position(
|
||||||
Config.SCREEN.size.height / len(buttons_data) * index
|
Config.SCREEN.size.width / 2 - button_width / 2,
|
||||||
- button_height // 2,
|
Config.SCREEN.size.height / len(buttons_data) * index
|
||||||
),
|
- button_height,
|
||||||
bg_color=Config.COLORSCHEME.BOARD_BG,
|
),
|
||||||
font_color=Config.COLORSCHEME.LIGHT_TEXT,
|
bg_color=Config.COLORSCHEME.BOARD_BG,
|
||||||
hover_color=Config.COLORSCHEME.TILE_0,
|
font_color=Config.COLORSCHEME.LIGHT_TEXT,
|
||||||
size=(buttons_width, button_height),
|
hover_color=Config.COLORSCHEME.TILE_0,
|
||||||
text=text,
|
size=(button_width, button_height),
|
||||||
border_radius=Config.TILE.border.radius,
|
text=text,
|
||||||
action=action,
|
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:
|
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")
|
||||||
|
|||||||
@ -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):
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user