Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,19 @@ cd muzero
python -m unittest
```
Using pycharm: right-click test folder -> run 'unittest in test'

## Environments

We evaluated the same agent (MuZero) on multiple different environments. We
increased the board area from beginner (8\*8 with 10 mines) to intermediate
(16\*16 with 40 mines) and expert (16\*30 with 99 mines). The muzero model
cannot use previously trained models on different board sizes as the model
has to be retrained. To solve this we created a "gridworld" environment where the model plays as an agent moving inside the game. The agent can view an area of 5\*5 cells around it, and to open a cell it has to stand over it and open. This reduces the observation space to always have the same size of 5\*5 and reduces the observation space to 5 actions (up, down, left, right and open the cell it is standing on). The true board can then have unbounded size. Before we tested this environment, we hypothesize that the agent will have a harder time learning the rules of the game, as it has to additionally movement and exploration of the environment in addition to the traditional minesweeper rules, but this is required for the game to be of arbitrary size.

The classical algorithm cannot solve the minesweeper board by only looking at a small view of the board as it has not been designed for this case.

We hypothesize that the trained model will learn the rules of minesweeper to a good degree, but will not beat the classical algorithm in efficiency or accuracy. We hypothesize that by combining the two, (ie. give the learned model domain-specific knowledge generated by the classical model, and only apply the trained model in cases the classical algorithm can not handle) we will see an increased win-rate.

We also tried to train the model using intermediary rewards, and without. The gridworld environment uses only intermediary results, and ends when a mine is pressed.

We expect the trained model on the gridworld environment to prioritize opening cells that are connected to the outside, and have a lower priority on islands left within the opened area.
2 changes: 1 addition & 1 deletion gym-minesweeper
7 changes: 7 additions & 0 deletions src/envs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from gym.envs.registration import register

register(
id='MinesweeperGuided-v0',
entry_point='src.envs.minesweeper_guided_env:MinesweeperGuidedEnv',
nondeterministic=False,
)
28 changes: 28 additions & 0 deletions src/envs/minesweeper_guided_env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import gym
from gym_minesweeper.envs import MinesweeperEnv
import numpy as np


class MinesweeperGuidedEnv(MinesweeperEnv):
"""
A guided minesweeper environment is the same as a minesweeper
environment, but the observation space contains an additional matrix
which contains the probability that each cell is a mine.
"""

def __init__(self, width=8, height=8, mine_count=10, flood_fill=True, enable_guide=True):
super().__init__(width, height, mine_count, flood_fill)
self.observation_space = gym.spaces.Box(low=np.float32(-2),
high=np.float32(8),
shape=(
2, self.width, self.height))

def step(self, action):
observation, *output = super(MinesweeperGuidedEnv, self).step(action)
observation.append(self.get_probability_matrix())
return observation, *output

def get_probability_matrix(self):

return None

69 changes: 69 additions & 0 deletions src/test/test_minesweeper_guided_env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import subprocess
import unittest
import gym
from ..envs.minesweeper_guided_env import MinesweeperGuidedEnv

# from minesweepr.minesweeper_util import generate_rules

class SolverException(Exception):
pass

def api_solve(payload):
try:
return subprocess.run(
[
"C:/users/sscho/anaconda3/envs/mrgris/python.exe",
"-c",
"from minesweepr.minesweeper_util import api_solve;"+
"print(api_solve({}))".format(payload)
],
capture_output=True,
check=True,
timeout=1
).stdout
except subprocess.CalledProcessError as e:
raise SolverException("api_solve errored with message below:\n\n{}"
.format(e.stderr.decode("utf-8")))


class MinesweeperGuidedEnvTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.env = gym.make("MinesweeperGuided-v0")

def setUp(self) -> None:
self.env.reset()

def test_has_correct_dimensions(self):
ob, reward, episode_over, info = self.env.step(self.env.action_space.sample())
self.env.reset()
self.assertTupleEqual(self.env.observation_space.shape, (2, 8, 8))
self.assertTupleEqual(ob.shape, (2, 8, 8))
assert not None in ob.shape[0]
print()
gym.spaces.box

def test_call_api_solve(self):
# todo use https://stackoverflow.com/questions/1191374/using-module-subprocess-with-timeout
# and subprocess to run the
rules = {"rules":[{"num_mines":1,"cells":["2-1"]},{"num_mines":0,"cells":["1-1"]},{"num_mines":0,"cells":[]}],"total_cells":2,"total_mines":1}
result = api_solve("rules")

print(rules)
print(result)

self.assertIn("'solution': {'1-1': 0.0, '2-1': 1.0}}", result)

def test_solve_error(self):
self.assertRaises(api_solve("shit"))

def test_inconsistency(self):
payload = {"rules":[{"num_mines":0,"cells":["1-1"]},{"num_mines":0,"cells":[]}],"total_cells":1,"total_mines":1}
output = api_solve(payload).decode("utf-8")
print(output)
self.assertIsNone(output.solution, None)



if __name__ == '__main__':
unittest.main()