Hi everyone,
It's not returning won or lost yet. I'd just like some OO / code structure feedback on this.
Any suggestions welcome
from enum import Enum
from random import sample
import numpy as np
# 8 coordinates around a point
COORDINATES = [(0, 1), (1, 0), (-1, 0), (0, -1), (1, 1), (-1, -1), (1, -1), (-1, 1)]
class TileType(Enum):
"""
'M' mine, unrevealed
'E' empty square, revealed
'B' blank square, revealed, that has no adjacent minde
'1' - '8' represents how many mines are adjacent to this revealed square
'X' represents a revealed exploded mine.
"""
MINE = "M"
EMPTY = "E"
REVEALED_EMPTY = "B"
REVEALED_MINE = "X"
class Tile:
""" A single tile in the grid.
See TileType Enum for all possible values.
"""
def __init__(self, value: TileType = TileType.EMPTY) -> None:
self._value = value
self._visible = False
@property
def visible(self):
return self._visible
@property
def value(self):
return self._value
@value.setter
def value(self, value):
self._value = value
if value in [TileType.REVEALED_MINE, TileType.REVEALED_EMPTY]:
self._visible = True
def is_mine(self) -> bool:
return self._value == TileType.MINE
def is_empty(self) -> bool:
return self._value == TileType.EMPTY
def is_revealed_empty(self) -> bool:
return self._value == TileType.REVEALED_EMPTY
def __repr__(self) -> str:
if type(self._value) is int:
return str(self._value)
return str(self._value.value)
class Board:
"""Board class for the MineSweeper game"""
def __init__(self, rows: int, cols: int) -> None:
self.rows = rows
self.cols = cols
self._board = [[Tile() for _ in range(cols)] for _ in range(rows)]
@property
def data(self):
return self._board
def at(self, row: int, col: int) -> Tile:
return self._board[row][col]
def in_bounds(self, row: int, col: int) -> bool:
"""Checks row and col are in bound in the grid"""
if not (0 <= row < self.rows and 0 <= col < self.cols):
return False
return True
def place_mines(self, num_mines: int) -> None:
"""Generate random mines coordinates and place them on the board"""
candidates = [(x, y) for y in range(self.cols) for x in range(self.rows)]
mines_coordinates = sample(candidates, num_mines)
for x, y in mines_coordinates:
self._board[x][y] = Tile(TileType.MINE)
def update_board(self, row: int, col: int) -> None:
"""Trigger the explore() and update properties of affected Tiles"""
if self._board[row][col].is_mine():
self._board[row][col].value = TileType.REVEALED_MINE
return
self.explore(row, col)
def explore(self, row: int, col: int) -> None:
"""DFS to check surrounding mines and reveal all visible tiles"""
if not self.in_bounds(row, col) or not self._board[row][col].is_empty():
return
mines = self.find_surrounding_mines(row, col)
if mines > 0:
self._board[row][col].value = mines
return
self._board[row][col].value = TileType.REVEALED_EMPTY
for x, y in COORDINATES:
self.explore(row + x, col + y) # DFS
def find_surrounding_mines(self, row: int, col: int):
mines_around = 0
for x, y in COORDINATES:
if (
self.in_bounds(row + x, col + y)
and self._board[row + x][col + y].is_mine()
):
mines_around += 1
return mines_around
class MineSweeper:
"""The MineSweeper game class, initiates the board, handle on_click and print board"""
def __init__(self, rows: int, cols: int, num_mines: int) -> None:
self._board = Board(rows, cols)
self._board.place_mines(num_mines)
def on_click(self, row: int, col: int) -> bool:
"""Handles click validation and updates the board"""
if not self._board.in_bounds(row, col):
# raise Exception("Not in bounds")
print("Not in bounds")
return False
if self._board.at(row, col).visible:
# raise Exception("Already visible")
print("Already visible")
return False
self._board.update_board(row, col)
return False
def print_board(self) -> None:
# print(np.matrix(self._board._board)) # how the board looks under the hood
for row in range(self._board.rows):
for col in range(self._board.cols):
if (
self._board._board[row][col].is_mine()
or self._board._board[row][col].is_empty()
):
print("-", end=" ")
elif self._board._board[row][col].is_revealed_empty():
print("0", end=" ")
else:
print(self._board._board[row][col], end=" ")
print("")
print("-" * (self._board.rows + self._board.cols))
num_mines = 7
mine_sweeper = MineSweeper(10, 10, num_mines)
mine_sweeper.on_click(5, 1)
mine_sweeper.print_board()
# mine_sweeper.on_click(8, 5);
# mine_sweeper.print_board()