feat(game): set Menu as primary screen

This commit is contained in:
Kristofers Solo 2024-01-03 00:49:09 +02:00
parent d22b5dbab2
commit f95ca3aaf6
11 changed files with 177 additions and 147 deletions

View File

@ -2,12 +2,12 @@
from loguru import logger from loguru import logger
from py2048 import Game from py2048 import Menu
@logger.catch @logger.catch
def main() -> None: def main() -> None:
Game().run() Menu().run()
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -1,4 +1,4 @@
from .config import Config from .config import Config
from .game import Game from .screens import Menu
__all__ = ["Config", "Game"] __all__ = ["Config", "Menu"]

View File

@ -1,76 +0,0 @@
import sys
import pygame
from loguru import logger
from .config import Config
from .objects import Board
from .screens import Header, Menu
from .utils import Direction, setup_logger
class Game:
def __init__(self) -> None:
setup_logger()
logger.info("Initializing game")
pygame.init()
self.screen: pygame.Surface = pygame.display.set_mode(Config.SCREEN.size)
pygame.display.set_caption("2048")
self.board = Board()
self.header = Header()
self.menu = Menu()
def run(self) -> None:
"""Run the game loop."""
while True:
self._hande_events()
self._update()
self._render()
def _update(self) -> None:
"""Update the game."""
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)
pygame.display.flip()
def _hande_events(self) -> None:
"""Handle pygame events."""
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.exit()
elif event.type == pygame.KEYDOWN:
if event.key in (pygame.K_LEFT, pygame.K_a, pygame.K_h):
self.move_left()
elif event.key in (pygame.K_RIGHT, pygame.K_d, pygame.K_l):
self.move_right()
elif event.key in (pygame.K_UP, pygame.K_w, pygame.K_k):
self.move_up()
elif event.key in (pygame.K_DOWN, pygame.K_s, pygame.K_j):
self.move_down()
elif event.key == pygame.K_q:
self.exit()
self.menu._handle_events(event)
def move_up(self) -> None:
self.board.move(Direction.UP)
def move_down(self) -> None:
self.board.move(Direction.DOWN)
def move_left(self) -> None:
self.board.move(Direction.LEFT)
def move_right(self) -> None:
self.board.move(Direction.RIGHT)
def exit(self) -> None:
"""Exit the game."""
pygame.quit()
sys.exit()

View File

@ -18,7 +18,7 @@ class UIElement(ABC, metaclass=ABCMeta):
font_color: str, font_color: str,
font_size: int = Config.FONT.size, font_size: int = Config.FONT.size,
font_family: str = Config.FONT.family, font_family: str = Config.FONT.family,
size: Size = Size(50, 50), size: Optional[Size] = Size(50, 50),
text: str = "", text: str = "",
border_radius: int = 0, border_radius: int = 0,
border_width: int = 0, border_width: int = 0,

View File

@ -42,12 +42,13 @@ class Button(ClickableUIElement, pygame.sprite.Sprite):
def _draw_background(self, surface: pygame.Surface) -> None: def _draw_background(self, surface: pygame.Surface) -> None:
"""Draw a rectangle on the given surface.""" """Draw a rectangle on the given surface."""
pygame.draw.rect( if self.size:
surface, pygame.draw.rect(
self.bg_color, surface,
(0, 0, *self.size), self.bg_color,
border_radius=Config.TILE.border.radius, (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."""

View File

@ -7,13 +7,15 @@ from py2048.utils import Position, Size
from .abc import UIElement from .abc import UIElement
class Label(UIElement): class Label(UIElement, pygame.sprite.Sprite):
def __init__(self, *args, **kwargs) -> None: def __init__(self, *args, **kwargs) -> None:
pygame.sprite.Sprite.__init__(self)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
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 element on the given surface.""" """Draw the element on the given surface."""
@ -29,23 +31,17 @@ class Label(UIElement):
def _draw_background(self, surface: pygame.Surface) -> None: def _draw_background(self, surface: pygame.Surface) -> None:
"""Draw a background for the given surface.""" """Draw a background for the given surface."""
rect = (0, 0, *self.size) if self.size:
pygame.draw.rect( pygame.draw.rect(
surface, self.bg_color, rect, border_radius=Config.TILE.border.radius surface,
) # background self.bg_color,
pygame.draw.rect( (0, 0, *self.size),
surface, border_radius=Config.TILE.border.radius,
(0, 0, 0, 0), )
rect,
border_radius=Config.TILE.border.radius,
width=Config.TILE.border.width,
) # border
def _draw_text(self) -> None: def _draw_text(self) -> None:
"""Draw the text of the element.""" """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.text, True, self.font_color, self.bg_color
)
self.image.blit( self.image.blit(
self.rendered_text, self.rendered_text,
self.rendered_text.get_rect(center=self.image.get_rect().center), self.rendered_text.get_rect(center=self.image.get_rect().center),

View File

@ -39,6 +39,7 @@ class Tile(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."""
@ -55,23 +56,25 @@ class Tile(MovableUIElement, pygame.sprite.Sprite):
def _draw_background(self, surface: pygame.Surface) -> None: def _draw_background(self, surface: pygame.Surface) -> None:
"""Draw a rounded rectangle on the given surface.""" """Draw a rounded rectangle on the given surface."""
pygame.draw.rect( if self.size:
surface, pygame.draw.rect(
self._get_color(), surface,
(0, 0, *self.size), self._get_color(),
border_radius=Config.TILE.border.radius, (0, 0, *self.size),
) border_radius=Config.TILE.border.radius,
self._draw_border(surface) )
self._draw_border(surface)
def _draw_border(self, surface: pygame.Surface) -> None: def _draw_border(self, surface: pygame.Surface) -> None:
"""Draw a rounded border on the given surface.""" """Draw a rounded border on the given surface."""
pygame.draw.rect( if self.size:
surface, pygame.draw.rect(
(0, 0, 0, 0), surface,
(0, 0, *self.size), (0, 0, 0, 0),
border_radius=Config.TILE.border.radius, (0, 0, *self.size),
width=Config.TILE.border.width, border_radius=Config.TILE.border.radius,
) width=Config.TILE.border.width,
)
def _draw_text(self) -> None: def _draw_text(self) -> None:
"""Draw the text of the sprite.""" """Draw the text of the sprite."""

View File

@ -1,4 +1,5 @@
from .game import Game
from .header import Header from .header import Header
from .menu import Menu from .menu import Menu
__all__ = ["Header", "Menu"] __all__ = ["Header", "Menu", "Game"]

View File

@ -0,0 +1,43 @@
import pygame
from loguru import logger
from py2048 import Config
from py2048.objects import Board
from py2048.utils import Direction, setup_logger
from .header import Header
class Game:
def __init__(self) -> None:
self.header = Header()
self.board = Board()
def draw(self, surface: pygame.Surface) -> None:
surface.fill(Config.COLORSCHEME.BG)
self.board.draw(surface)
self.header.draw(surface, 2048)
pygame.display.flip()
def handle_events(self, event: pygame.Event) -> None:
if event.type == pygame.KEYDOWN:
if event.key in (pygame.K_LEFT, pygame.K_a, pygame.K_h):
self.move_left()
elif event.key in (pygame.K_RIGHT, pygame.K_d, pygame.K_l):
self.move_right()
elif event.key in (pygame.K_UP, pygame.K_w, pygame.K_k):
self.move_up()
elif event.key in (pygame.K_DOWN, pygame.K_s, pygame.K_j):
self.move_down()
def move_up(self) -> None:
self.board.move(Direction.UP)
def move_down(self) -> None:
self.board.move(Direction.DOWN)
def move_left(self) -> None:
self.board.move(Direction.LEFT)
def move_right(self) -> None:
self.board.move(Direction.RIGHT)

View File

@ -8,20 +8,32 @@ from py2048.utils import Position, Size
class Header: class Header:
def __init__(self) -> None: def __init__(self) -> None:
self.rect = pygame.Rect(0, 0, *Config.HEADER.size) self.rect = pygame.Rect(0, 0, *Config.HEADER.size)
self.labels = self._create_labels()
def draw(self, screen: pygame.Surface, score: int) -> None: def _create_labels(self) -> pygame.sprite.Group:
"""Draw the header.""" score = Label(
self.label = Label( text=f"SCORE\n{0}",
text=f"{score}",
size=Size(50, 50), size=Size(50, 50),
position=Position(0, 0), position=Position(0, 0),
bg_color=Config.COLORSCHEME.BOARD_BG, bg_color=Config.COLORSCHEME.BOARD_BG,
font_color=Config.COLORSCHEME.DARK_TEXT, font_color=Config.COLORSCHEME.LIGHT_TEXT,
font_size=16, font_size=16,
) )
self.label.draw(screen) highscore = Label(
text=f"HIGHSCORE\n{2048}",
size=Size(50, 50),
position=Position(200, 0),
bg_color=Config.COLORSCHEME.BOARD_BG,
font_color=Config.COLORSCHEME.LIGHT_TEXT,
font_size=16,
)
return pygame.sprite.Group(score, highscore)
def draw(self, screen: pygame.Surface, score: int) -> None:
"""Draw the header."""
self.labels.draw(screen)
def update(self, score: int) -> None: def update(self, score: int) -> None:
"""Update the header.""" """Update the header."""
self.label.text = f"SCORE\n{score}" # self.labels. = f"SCORE\n{score}"
self.label.update() self.labels.update()

View File

@ -1,52 +1,99 @@
import sys
import pygame import pygame
from loguru import logger 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 from py2048.utils import Position, setup_logger
from .game import Game
class Menu(pygame.sprite.AbstractGroup): class Menu:
def __init__(self): def __init__(self):
super().__init__() setup_logger()
pygame.init()
pygame.display.set_caption("2048")
self._surface: pygame.Surface = pygame.display.set_mode(Config.SCREEN.size)
buttons_data = { logger.info("Initializing game")
self._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,
} }
button_width, button_height = 120, 50 self._game_active = False
self._ai_active = False
self._setting_active = False
self._buttons = self._create_buttons()
for index, (text, action) in enumerate(buttons_data.items(), start=1): def _create_buttons(self) -> pygame.sprite.Group:
self.add( """Create the buttons."""
width, height = 120, 50
buttons = pygame.sprite.Group()
for index, (text, action) in enumerate(self._buttons_data.items(), start=1):
buttons.add(
Button( Button(
position=Position( position=(
Config.SCREEN.size.width / 2 - button_width / 2, Config.SCREEN.size.width / 2 - width / 2,
Config.SCREEN.size.height / len(buttons_data) * index Config.SCREEN.size.height / len(self._buttons_data) * index
- button_height, - 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=(button_width, button_height), size=(width, height),
text=text, text=text,
border_radius=Config.TILE.border.radius, border_radius=Config.TILE.border.radius,
action=action, action=action,
) )
) )
def _handle_events(self, event: pygame.event.Event) -> None: return buttons
"""Handle the event."""
if event.type == pygame.MOUSEBUTTONDOWN: def draw(self) -> None:
for button in self.sprites(): self._surface.fill(Config.COLORSCHEME.BG)
button.on_click(Position(*event.pos)) self._buttons.draw(self._surface)
elif event.type == pygame.MOUSEMOTION: pygame.display.flip()
for button in self.sprites():
button.on_hover(Position(*event.pos)) def run(self) -> None:
"""Run the game loop."""
while True:
self._hande_events()
if self._game_active:
self.game.draw(self._surface)
elif self._ai_active:
pass
elif self._setting_active:
pass
else:
self.draw()
def _hande_events(self) -> None:
"""Handle pygame events."""
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
for button in self._buttons:
button.on_click(Position(*event.pos))
elif event.type == pygame.MOUSEMOTION:
for button in self._buttons:
button.on_hover(Position(*event.pos))
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_q:
self.exit()
if self._game_active:
self.game.handle_events(event)
def play(self) -> None: def play(self) -> None:
logger.debug("Play") logger.debug("Launching game")
self._game_active = True
self.game = Game()
def ai(self) -> None: def ai(self) -> None:
logger.debug("AI") logger.debug("AI")
@ -55,4 +102,7 @@ class Menu(pygame.sprite.AbstractGroup):
logger.debug("Settings") logger.debug("Settings")
def exit(self) -> None: def exit(self) -> None:
logger.debug("Exit") """Exit the game."""
logger.debug("Exiting")
pygame.quit()
sys.exit()