~piotr-machura/sweep-ai

e69fac5c4ee5bddfd58d49457513684dbf57cc38 — Piotr Machura 10 months ago d3924e6
Add many convenience properties to State
2 files changed, 106 insertions(+), 6 deletions(-)

M sweep_ai/logic.py
M tests/test_logic.py
M sweep_ai/logic.py => sweep_ai/logic.py +36 -3
@@ 17,6 17,7 @@ class State:
        revealed[x, y]: 1 if `(x, y)` has been revealed
        flagged[x, y]: 1 if `(x, y)` has been flagged
        won: `True` if user has won, `None` if the game has not finished
        clicks: tracks the number of total clicks done by the user
    """

    def __init__(


@@ 40,6 41,7 @@ class State:
        self.flagged = np.copy(self.bomb)
        self.near = np.copy(self.bomb)
        self.won: Optional[bool] = None
        self.clicks = 0

        # If the positions were not explicitly provided randomise them
        if bomb_positions is None:


@@ 59,15 61,35 @@ class State:
                self.near[n_x, n_y] += 1

    @property
    def not_bomb(self):
    def safe(self):
        """The inverse of `self.bombs`."""
        return (self.bomb + 1) % 2

    @property
    def not_revealed(self):
    def hidden(self):
        """The inverse of `self.revealed`."""
        return (self.revealed + 1) % 2

    @property
    def revealed_n(self):
        """Total number of revealed tiles."""
        return np.sum(self.revealed)

    @property
    def hidden_n(self):
        """Total number of hidden tiles."""
        return self.size**2 - self.revealed_n

    @property
    def bomb_n(self):
        """Total number of bombs."""
        return np.sum(self.bomb)

    @property
    def safe_n(self):
        """Total number of safe tiles."""
        return self.size**2 - self.bomb_n

    def click(self, x: int, y: int):
        """Simulate a click on the `(x, y)` position.



@@ 76,6 98,7 @@ class State:

        If the game has not ended performs a recursive reveal.
        """
        self.clicks += 1
        if self.bomb[x, y] == 1:
            # Show all the bombs
            self.revealed[self.bomb == 1] = 1


@@ 86,7 109,7 @@ class State:
            self.reveal(x, y)

        # Check if the player has won
        if np.array_equal(self.revealed, self.not_bomb):
        if np.array_equal(self.revealed, self.safe):
            self.won = True

    def reveal(self, x: int, y: int):


@@ 115,3 138,13 @@ class State:
    def flag(self, x: int, y: int):
        """Mark `(x, y)` as flagged."""
        self.flagged[x, y] = 1

    @property
    def score(self):
        """Calcualte the score as a simple revealed tiles / safe tiles ratio."""
        if self.won is not False:
            # We have not lost yet
            return self.revealed_n / self.safe_n
        # If we lost then bombs got revealed too but they should not count
        # towards the score
        return (self.revealed_n - self.bomb_n) / self.safe_n

M tests/test_logic.py => tests/test_logic.py +70 -3
@@ 15,7 15,7 @@ def test_bombs():
            [0, 0, 1, 0],
        ]))
    assert np.array_equal(
        state.not_bomb,
        state.safe,
        np.array([
            [0, 1, 1, 1],
            [1, 1, 1, 1],


@@ 29,7 29,8 @@ def test_bombs():
        (12, 0.34),
    ]:
        state = logic.State(size, percentage)
        assert np.sum(state.bomb) == round(size**2 * percentage)
        assert state.bomb_n == round(size**2 * percentage)
        assert state.safe_n == state.size ** 2 - round(size**2 * percentage)


def test_neighbors():


@@ 82,13 83,15 @@ def test_reveal():
            [1, 1, 1, 1],
        ]))
    assert np.array_equal(
        state.not_revealed,
        state.hidden,
        np.array([
            [1, 0, 0, 0],
            [0, 0, 0, 0],
            [0, 0, 0, 0],
            [0, 0, 0, 0],
        ]))
    assert state.hidden_n == 1
    assert state.revealed_n == 15

    state = logic.State(4, bomb_positions=[(0, 0), (1, 1)])
    state.reveal(3, 3)


@@ 100,6 103,8 @@ def test_reveal():
            [1, 1, 1, 1],
            [1, 1, 1, 1],
        ]))
    assert state.hidden_n == 4
    assert state.revealed_n == 12

    state = logic.State(4, bomb_positions=[(0, 0), (1, 1), (3, 3)])
    state.reveal(3, 0)


@@ 111,10 116,15 @@ def test_reveal():
            [1, 1, 1, 0],
            [1, 1, 1, 0],
        ]))
    assert state.hidden_n == 10
    assert state.revealed_n == 6


def test_click():
    state = logic.State(4, bomb_positions=[(0, 0), (1, 1), (3, 3)])
    assert state.won is None
    assert state.clicks == 0

    state.click(3, 0)
    assert np.array_equal(
        state.revealed,


@@ 125,6 135,7 @@ def test_click():
            [1, 1, 1, 0],
        ]))
    assert state.won is None
    assert state.clicks == 1

    # The same click again - should be a no-op
    state.click(3, 0)


@@ 137,6 148,7 @@ def test_click():
            [1, 1, 1, 0],
        ]))
    assert state.won is None
    assert state.clicks == 2

    # Click on a bomb = defeat
    state.click(1, 1)


@@ 149,3 161,58 @@ def test_click():
            [1, 1, 1, 1],
        ]))
    assert not state.won
    assert state.clicks == 3

def test_game():
    state = logic.State(4, bomb_positions=[(0, 0), (1, 1), (3, 3)])
    state.click(3, 0)
    assert np.array_equal(
        state.revealed,
        np.array([
            [0, 0, 0, 0],
            [0, 0, 0, 0],
            [1, 1, 1, 0],
            [1, 1, 1, 0],
        ]))
    assert state.won is None
    assert state.clicks == 1
    assert state.score == 6.0 / 13.0

    state.click(0, 3)
    assert np.array_equal(
        state.revealed,
        np.array([
            [0, 0, 1, 1],
            [0, 0, 1, 1],
            [1, 1, 1, 1],
            [1, 1, 1, 0],
        ]))
    assert state.won is None
    assert state.clicks == 2
    assert state.score == 11.0 / 13.0

    state.click(1, 0)
    assert np.array_equal(
        state.revealed,
        np.array([
            [0, 0, 1, 1],
            [1, 0, 1, 1],
            [1, 1, 1, 1],
            [1, 1, 1, 0],
        ]))
    assert state.won is None
    assert state.clicks == 3
    assert state.score == 12.0 / 13.0

    state.click(0, 1)
    assert np.array_equal(
        state.revealed,
        np.array([
            [0, 1, 1, 1],
            [1, 0, 1, 1],
            [1, 1, 1, 1],
            [1, 1, 1, 0],
        ]))
    assert state.won is True
    assert state.clicks == 4
    assert state.score == 13.0 / 13.0