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 py2048 import Game
from py2048 import Menu
@logger.catch
def main() -> None:
Game().run()
Menu().run()
if __name__ == "__main__":

View File

@ -1,4 +1,4 @@
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_size: int = Config.FONT.size,
font_family: str = Config.FONT.family,
size: Size = Size(50, 50),
size: Optional[Size] = Size(50, 50),
text: str = "",
border_radius: 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:
"""Draw a rectangle on the given surface."""
pygame.draw.rect(
surface,
self.bg_color,
(0, 0, *self.size),
border_radius=Config.TILE.border.radius,
)
if self.size:
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."""

View File

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

View File

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

View File

@ -1,4 +1,5 @@
from .game import Game
from .header import Header
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:
def __init__(self) -> None:
self.rect = pygame.Rect(0, 0, *Config.HEADER.size)
self.labels = self._create_labels()
def draw(self, screen: pygame.Surface, score: int) -> None:
"""Draw the header."""
self.label = Label(
text=f"{score}",
def _create_labels(self) -> pygame.sprite.Group:
score = Label(
text=f"SCORE\n{0}",
size=Size(50, 50),
position=Position(0, 0),
bg_color=Config.COLORSCHEME.BOARD_BG,
font_color=Config.COLORSCHEME.DARK_TEXT,
font_color=Config.COLORSCHEME.LIGHT_TEXT,
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:
"""Update the header."""
self.label.text = f"SCORE\n{score}"
self.label.update()
# self.labels. = f"SCORE\n{score}"
self.labels.update()

View File

@ -1,52 +1,99 @@
import sys
import pygame
from loguru import logger
from py2048 import Config
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):
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,
"AI": self.ai,
"Settings": self.settings,
"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):
self.add(
def _create_buttons(self) -> pygame.sprite.Group:
"""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(
position=Position(
Config.SCREEN.size.width / 2 - button_width / 2,
Config.SCREEN.size.height / len(buttons_data) * index
- button_height,
position=(
Config.SCREEN.size.width / 2 - width / 2,
Config.SCREEN.size.height / len(self._buttons_data) * index
- height,
),
bg_color=Config.COLORSCHEME.BOARD_BG,
font_color=Config.COLORSCHEME.LIGHT_TEXT,
hover_color=Config.COLORSCHEME.TILE_0,
size=(button_width, button_height),
size=(width, height),
text=text,
border_radius=Config.TILE.border.radius,
action=action,
)
)
def _handle_events(self, event: pygame.event.Event) -> None:
"""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))
return buttons
def draw(self) -> None:
self._surface.fill(Config.COLORSCHEME.BG)
self._buttons.draw(self._surface)
pygame.display.flip()
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:
logger.debug("Play")
logger.debug("Launching game")
self._game_active = True
self.game = Game()
def ai(self) -> None:
logger.debug("AI")
@ -55,4 +102,7 @@ class Menu(pygame.sprite.AbstractGroup):
logger.debug("Settings")
def exit(self) -> None:
logger.debug("Exit")
"""Exit the game."""
logger.debug("Exiting")
pygame.quit()
sys.exit()