diff --git a/main.py b/main.py index bd3c0af..256fa24 100755 --- a/main.py +++ b/main.py @@ -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__": diff --git a/src/py2048/__init__.py b/src/py2048/__init__.py index 6e9484b..d1c7a88 100644 --- a/src/py2048/__init__.py +++ b/src/py2048/__init__.py @@ -1,4 +1,4 @@ from .config import Config -from .game import Game +from .screens import Menu -__all__ = ["Config", "Game"] +__all__ = ["Config", "Menu"] diff --git a/src/py2048/game.py b/src/py2048/game.py deleted file mode 100644 index 412f331..0000000 --- a/src/py2048/game.py +++ /dev/null @@ -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() diff --git a/src/py2048/objects/abc/ui_element.py b/src/py2048/objects/abc/ui_element.py index 2caaac2..c3d98ce 100644 --- a/src/py2048/objects/abc/ui_element.py +++ b/src/py2048/objects/abc/ui_element.py @@ -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, diff --git a/src/py2048/objects/button.py b/src/py2048/objects/button.py index a271147..ae1385d 100644 --- a/src/py2048/objects/button.py +++ b/src/py2048/objects/button.py @@ -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.""" diff --git a/src/py2048/objects/label.py b/src/py2048/objects/label.py index cbd44f5..eaaf6e7 100644 --- a/src/py2048/objects/label.py +++ b/src/py2048/objects/label.py @@ -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), diff --git a/src/py2048/objects/tile.py b/src/py2048/objects/tile.py index ea5426b..90a3f67 100644 --- a/src/py2048/objects/tile.py +++ b/src/py2048/objects/tile.py @@ -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.""" diff --git a/src/py2048/screens/__init__.py b/src/py2048/screens/__init__.py index f4dd85f..be30153 100644 --- a/src/py2048/screens/__init__.py +++ b/src/py2048/screens/__init__.py @@ -1,4 +1,5 @@ +from .game import Game from .header import Header from .menu import Menu -__all__ = ["Header", "Menu"] +__all__ = ["Header", "Menu", "Game"] diff --git a/src/py2048/screens/game.py b/src/py2048/screens/game.py new file mode 100644 index 0000000..b2e8b06 --- /dev/null +++ b/src/py2048/screens/game.py @@ -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) diff --git a/src/py2048/screens/header.py b/src/py2048/screens/header.py index f07c144..e00e528 100644 --- a/src/py2048/screens/header.py +++ b/src/py2048/screens/header.py @@ -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() diff --git a/src/py2048/screens/menu.py b/src/py2048/screens/menu.py index f446077..f9e6f91 100644 --- a/src/py2048/screens/menu.py +++ b/src/py2048/screens/menu.py @@ -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()