~piotr-machura/sweep-ai

5bb4bee5ba3b2132b6f7293c0d22f70e5dfe1622 — Piotr Machura 10 months ago 6b57599
Training in separate thread
2 files changed, 77 insertions(+), 50 deletions(-)

M sweep_ai/ai.py
M sweep_ai/window.py
M sweep_ai/ai.py => sweep_ai/ai.py +24 -21
@@ 33,9 33,9 @@ class Player:
                keras.layers.Dense(1, activation='sigmoid'),
            ])
        self.brain.compile(
            keras.optimizers.Adam(learning_rate=0.0025),
            keras.optimizers.Adam(learning_rate=0.001),
            loss='binary_crossentropy',
            metrics=['accuracy'],
            metrics=['binary_accuracy'],
        )

    @staticmethod


@@ 65,7 65,8 @@ class Player:
    @staticmethod
    def prosess_tile(state: State, x: int, y: int) -> bool:
        """Returns `true` if `(x, y)` is suitable for making a move."""
        return not (state.revealed[x, y] or state.flagged[x, y])
        return not (state.revealed[x, y] or state.flagged[x, y]) and any(
            state.revealed[x_n, y_n] for x_n, y_n in state.neighbors(x, y))

    def move(self, state: State) -> Optional[Tuple[int, int]]:
        """Make a move.


@@ 74,16 75,19 @@ class Player:
        `(x, y)` move otherwise.
        """
        if self.trained:
            pos = max(
                [
                    (x, y)
                    for x in range(state.size)
                    for y in range(state.size)
                    if self.prosess_tile(state, x, y)
                ],
                key=lambda pos: self.predict(state, *pos),
            )
            return pos
            try:
                pos = max(
                    [
                        (x, y)
                        for x in range(state.size)
                        for y in range(state.size)
                        if self.prosess_tile(state, x, y)
                    ],
                    key=lambda pos: self.predict(state, *pos),
                )
                return pos
            except ValueError:
                return None
        return None

    def train(self):


@@ 92,7 96,7 @@ class Player:

        x_train = []
        y_train = []
        while len(x_train) < 1e4:
        while len(x_train) < 2e4:
            state = State(self.size, 0.2)
            # CLick on a guaranteed empty space
            x_click, y_click = np.transpose(np.nonzero(state.near == 0))[0]


@@ 110,10 114,8 @@ class Player:
                state.click(*state.cheat())

        x_train = StandardScaler().fit_transform(np.array(x_train))
        print(state.bomb_n)
        y_train = np.array(y_train)
        x_train, y_train = shuffle(x_train, y_train)
        print(y_train)
        weights = dict(
            zip(
                np.unique(y_train),


@@ 129,16 131,17 @@ class Player:
            x_train,
            y_train,
            class_weight=weights,
            epochs=50,
            batch_size=50,
            epochs=20,
            batch_size=100,
            verbose=1,
            validation_split=0.25,
            validation_split=0.2,
            use_multiprocessing=True,
            shuffle=True,
        )
        _, axs = plt.subplots(1, 2)
        axs[0].set_title('Accuracy')
        axs[0].plot(history.history['val_accuracy'])
        axs[0].plot(history.history['accuracy'])
        axs[0].plot(history.history['val_binary_accuracy'])
        axs[0].plot(history.history['binary_accuracy'])

        axs[1].set_title('Loss')
        axs[1].plot(history.history['val_loss'])

M sweep_ai/window.py => sweep_ai/window.py +53 -29
@@ 1,12 1,13 @@
"""Window handling module."""
import sys
from typing import Optional, Tuple, Dict
from threading import Thread
from typing import Optional, Tuple

import pygame
import pygame_menu

from .logic import State
from .ai import Player
from .logic import State

# pylint: disable=invalid-name



@@ 33,14 34,18 @@ class Game:
        self.difficulty = self.DIFFICULTY['easy']
        self.size = self.SIZE['small']
        self.hint: Optional[Tuple[int, int]] = None
        self.grid_s = 32
        self.border = 16
        self.menu_width = 260

        self._grid_s = 32
        self._border = 16
        self._menu_width = 260
        self.surface = pygame.display.set_mode(
            (self.display_width, self.display_height),
        )
        self.events = []
        self.player = Player(self.size, self.difficulty)

        self.player = Player(self.size, 0.2)
        self.train_thread = Thread(target=lambda _: _)
        self.hint_thread = Thread(target=lambda _: _)

        self.sprites = {}
        self.sprites['flag'] = pygame.image.load('assets/flag.png')


@@ 88,6 93,7 @@ class Game:
        self.menu.add.button(
            '[Hint]',
            self.get_hint,
            button_id='hint_btn',
            padding=5,
            margin=(0, 0),
            cursor=pygame_menu.locals.CURSOR_HAND,


@@ 133,7 139,7 @@ class Game:
            padding=5,
            cursor=pygame_menu.locals.CURSOR_HAND,
            font_color=(191, 97, 106),
        ).translate(50, -43)
        ).translate(50, -44)
        self.menu.center_content()

    def reset(self):


@@ 151,17 157,17 @@ class Game:
    @property
    def display_width(self):
        """Displayed window width."""
        return self.grid_s * self.size + self.border * 2 + self.menu_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
        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
        return self._grid_s * self.size + self._border * 2

    def set_size(self, *args):
        """Set a new board size."""


@@ 174,22 180,30 @@ class Game:

    def get_hint(self):
        """Highlight the position suggested by the AI player."""
        hint = self.player.move(self.state)
        if hint is None:
            self.player.train()
            hint = self.player.move(self.state)
        self.hint = hint
        # Hint-getting callback
        def hint_cb():
            if not self.train_thread.is_alive():
                if not self.player.trained:
                    self.train_thread = Thread(target=self.player.train)
                    self.train_thread.start()
            else:
                self.train_thread.join()
            self.hint = self.player.move(self.state)

        if not self.hint_thread.is_alive():
            self.hint_thread = Thread(target=hint_cb)
            self.hint_thread.start()

    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."""
        x_pos = self.border + x * self.grid_s
        y_pos = self.border + y * self.grid_s
        x_pos = self._border + x * self._grid_s
        y_pos = self._border + y * self._grid_s
        if self.state.revealed[x, y]:
            if self.state.bomb[x, y]:
                self.surface.blit(self.sprites['mine'], (x_pos, y_pos))


@@ 208,7 222,7 @@ class Game:
                    pygame.draw.rect(
                        self.surface,
                        (163, 190, 140),
                        (x_pos, y_pos, self.grid_s, self.grid_s),
                        (x_pos, y_pos, self._grid_s, self._grid_s),
                        3,
                    )



@@ 224,21 238,31 @@ class Game:
            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)
                (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))
            self.surface.blit(s, (self._border, self._border))
            timer.update_font({'color': (0, 255, 0)})

        elif self.state.won is False:
            s = pygame.Surface(
                (self.grid_s * self.size, self.grid_s * self.size),
                pygame.SRCALPHA)
                (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))
            self.surface.blit(s, (self._border, self._border))
            timer.update_font({'color': (255, 0, 0)})

        btn = self.menu.get_widget('hint_btn')
        if self.train_thread.is_alive():
            btn.set_title('[...]')
        else:
            btn.set_title('[Hint]')

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



@@ 251,8 275,8 @@ class Game:
        if not self.within_board(pos_x, pos_y):
            return

        x = int((pos_x - self.border) / self.grid_s)
        y = int((pos_y - self.border) / self.grid_s)
        x = int((pos_x - self._border) / self._grid_s)
        y = int((pos_y - self._border) / self._grid_s)
        if event.button == 1:
            self.hint = None
            self.state.click(x, y)