~piotr-machura/sweep-ai

9f83e11fb513e3231d9afa897175587ffdb592de — Piotr Machura 10 months ago b8c621a
Simplify the Game class with some @properties
2 files changed, 66 insertions(+), 49 deletions(-)

M sweep_ai/logic.py
M sweep_ai/window.py
M sweep_ai/logic.py => sweep_ai/logic.py +2 -3
@@ 122,12 122,11 @@ class State:
            self.won = True

    def flag(self, x: int, y: int):
        """Place a flag at `(x, y)`.
        """Place a flag at `(x, y)`, or remove it if already flagged.

        A no-op if `(x, y)` is already revealed.
        """
        if not self.revealed[x, y]:
            self.flagged[x, y] = 1
        self.flagged[x, y] ^= 1

    def reveal(self, x: int, y: int):
        """Reveals `(x, y)`.

M sweep_ai/window.py => sweep_ai/window.py +64 -46
@@ 7,6 7,8 @@ import pygame_menu

from .logic import State

# pylint: disable=invalid-name


class Game:
    """Game class."""


@@ 22,10 24,7 @@ class Game:
        self.grid_s = 32
        self.border = 8
        self.menu_width = 260

        self.display_width = self.grid_s * self.size + self.border * 2 + self.menu_width
        self.display_height = self.grid_s * self.size + self.border * 2
        self.game_display = pygame.display.set_mode(
        self.surface = pygame.display.set_mode(
            (self.display_width, self.display_height),
        )



@@ 37,12 36,12 @@ class Game:
        for i in range(1, 9):
            self.sprites[i] = pygame.image.load(f'assets/grid{i}.png')

        self.menu_pos = self.grid_s * self.size + self.border * 2
        self.timer = pygame.time.Clock()
        self.configure_menu()
        self.reset()

    def configure_menu(self):
        """Configures the game menu."""
        theme = pygame_menu.Theme(
            background_color=pygame_menu.themes.TRANSPARENT_COLOR,
            title=False,


@@ 53,26 52,26 @@ class Game:
            widget_margin=(0, 10),
            widget_selection_effect=pygame_menu.widgets.NoneSelection(),
        )
        self._menu = pygame_menu.Menu(
        self.menu = pygame_menu.Menu(
            height=self.display_height,
            mouse_motion_selection=True,
            position=(self.menu_pos, 25, False),
            position=(self.menu_x, 25, False),
            theme=theme,
            title='',
            width=240,
        )
        self._menu.add.label(
        self.menu.add.label(
            'Sweep AI',
            margin=(0, 0),
            font_name=pygame_menu.font.FONT_8BIT,
            font_size=22,
        ).translate(0, -10)
        self._menu.add.label(
        self.menu.add.label(
            '',
            label_id='winlose',
            label_id='timer',
            margin=(0, 15),
        ).translate(-40, 18)
        self._menu.add.button(
        self.menu.add.button(
            'Hint',
            self.get_hint,
            padding=5,


@@ 80,7 79,7 @@ class Game:
            cursor=pygame_menu.locals.CURSOR_HAND,
            font_color=(163, 190, 140),
        ).translate(50, -30)
        self._menu.add.dropselect(
        self.menu.add.dropselect(
            '',
            list(self.DIFFICULTY.items()),
            selection_box_width=100,


@@ 92,7 91,7 @@ class Game:
            default=list(self.DIFFICULTY.values()).index(self.difficulty),
            onchange=self.set_difficulty,
        )
        self._menu.add.dropselect(
        self.menu.add.dropselect(
            '',
            list(self.SIZE.items()),
            selection_box_width=100,


@@ 104,7 103,7 @@ class Game:
            default=list(self.SIZE.values()).index(self.size),
            onchange=self.set_size,
        )
        self._menu.add.button(
        self.menu.add.button(
            'Reset',
            self.reset,
            font_size=18,


@@ 112,8 111,8 @@ class Game:
            shadow_width=100,
            cursor=pygame_menu.locals.CURSOR_HAND,
            font_color=(208, 135, 112),
        ).translate(-50, 0)
        self._menu.add.button(
        ).translate(-30, 0)
        self.menu.add.button(
            'Exit',
            pygame_menu.events.EXIT,
            font_size=18,


@@ 121,7 120,7 @@ class Game:
            cursor=pygame_menu.locals.CURSOR_HAND,
            font_color=(191, 97, 106),
        ).translate(50, -43)
        self._menu.center_content()
        self.menu.center_content()

    def reset(self):
        """Reset the game state."""


@@ 134,15 133,27 @@ class Game:
        self.difficulty = args[1]
        self.reset()

    @property
    def display_width(self):
        """Displayed window width."""
        return self.grid_s * self.size + self.border * 2 + self.menu_width

    @property
    def display_height(self):
        """Displayed window height."""
        return self.grid_s * self.size + self.border * 2

    @property
    def menu_x(self):
        """The horizontal menu position."""
        return self.grid_s * self.size + self.border * 2

    def set_size(self, *args):
        """Set a new board size."""
        self.size = args[1]
        self.display_width = self.grid_s * self.size + self.border * 2 + self.menu_width
        self.display_height = self.grid_s * self.size + self.border * 2
        self.game_display = pygame.display.set_mode(
        self.surface = pygame.display.set_mode(
            (self.display_width, self.display_height),
        )
        self.menu_pos = self.grid_s * self.size + self.border * 2
        self.configure_menu()
        self.reset()



@@ 153,9 164,8 @@ class Game:
    def within_board(self, pos_x: float, pos_y: float) -> bool:
        """Returns `true` if `pos_x`, `pos_y` is within the board."""
        return bool(
            self.border < pos_x <
            self.grid_s * self.size + self.border and self.border <
            pos_y < self.grid_s * self.size + self.border)
            self.border < pos_x < self.grid_s * self.size + self.border
            and self.border < pos_y < self.grid_s * self.size + self.border)

    def draw_square(self, x: int, y: int):
        """Draw a single square on the board."""


@@ 163,46 173,54 @@ class Game:
        y_pos = self.border + y * self.grid_s
        if self.state.revealed[x, y]:
            if self.state.bomb[x, y]:
                self.game_display.blit(self.sprites['mine'], (x_pos, y_pos))
                self.surface.blit(self.sprites['mine'], (x_pos, y_pos))
            else:
                self.game_display.blit(
                self.surface.blit(
                    self.sprites[self.state.near[x, y]],
                    (x_pos, y_pos),
                )
        else:
            if self.state.flagged[x, y]:
                self.game_display.blit(self.sprites['flag'], (x_pos, y_pos))
                self.surface.blit(self.sprites['flag'], (x_pos, y_pos))
            else:
                self.game_display.blit(self.sprites['hidden'], (x_pos, y_pos))
                self.surface.blit(self.sprites['hidden'], (x_pos, y_pos))
            if self.hint is not None:
                if (x, y) == self.hint:
                    pygame.draw.rect(
                        self.game_display,
                        self.surface,
                        (163, 190, 140),
                        (x_pos, y_pos, self.grid_s, self.grid_s),
                        3,
                    )

    def draw_banner(self):
        """Draw the win/loss banner."""
        banner = self._menu.get_widget('winlose')
        if self.state.won is None:
            self.time += 1
            banner.update_font({'color': (255, 255, 255)})
            banner.set_title(f'Time: {self.time // 15}')
        elif self.state.won is True:
            banner.update_font({'color': (0, 255, 0)})
        elif self.state.won is False:
            banner.update_font({'color': (255, 0, 0)})

    def draw(self):
        """Draw the menu and board."""
        self.game_display.fill((0, 0, 0))
        self.surface.fill((0, 0, 0))
        for x in range(self.state.size):
            for y in range(self.state.size):
                self.draw_square(x, y)
        self.draw_banner()
        self._menu.draw(self.game_display)

        timer = self.menu.get_widget('timer')
        if self.state.won is None:
            self.time += 1
            timer.update_font({'color': (255, 255, 255)})
            timer.set_title(f'Time: {self.time // 10}')
        elif self.state.won is True:
            timer.update_font({'color': (0, 255, 0)})
            s = pygame.Surface(
                (self.grid_s * self.size, self.grid_s * self.size),
                pygame.SRCALPHA)
            s.fill((0, 255, 0, 32))
            self.surface.blit(s, (self.border, self.border))
        elif self.state.won is False:
            s = pygame.Surface(
                (self.grid_s * self.size, self.grid_s * self.size),
                pygame.SRCALPHA)
            s.fill((255, 0, 0, 32))
            self.surface.blit(s, (self.border, self.border))
            timer.update_font({'color': (255, 0, 0)})

        self.menu.draw(self.surface)
        pygame.display.update()

    def handle_click(self, event: pygame.event.Event):


@@ 227,14 245,14 @@ class Game:
        while True:
            self.draw()
            events = pygame.event.get()
            self._menu.update(events)
            self.menu.update(events)
            for event in events:
                if event.type == pygame.QUIT:
                    pygame.quit()
                    sys.exit(0)
                else:
                    self.handle_click(event)
            self.timer.tick(15)
            self.timer.tick(10)


def main():