@@ 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'])
@@ 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)