Skip to content

Instantly share code, notes, and snippets.

@Jwely
Created December 12, 2018 23:51
Show Gist options
  • Save Jwely/909d23420e9aac3e1642117d7647fd68 to your computer and use it in GitHub Desktop.
Save Jwely/909d23420e9aac3e1642117d7647fd68 to your computer and use it in GitHub Desktop.
Minesweeper implementation under 120 minutes
from typing import Tuple
import numpy as np
class MineSweeper(object):
def __init__(self, shape: Tuple, n_mines: int):
"""
:param shape:
:param n_mines:
"""
self.shape = shape
self.size = shape[0] * shape[1]
self.n_mines = n_mines
self._board = self.generate_board()
self._mines = self._board == -1 # mines are -1, positive integers are adjacent mines.
self.mask = self.generate_mask() # 1 indicates mask = True, 0 indicates space is revealed
self.flags = self.generate_flags()
self.game_over = False
assert self.size > n_mines, f"not enough space for {n_mines} mines!"
def generate_board(self):
def get_adjacent(arr: np.ndarray, i: int,j: int):
# end index is not inclusive, so + 2 is used on j values
x1 = max([i - 1, 0])
x2 = min([i + 2, arr.shape[0]])
y1 = max([j - 1, 0])
y2 = min([j + 2, arr.shape[1]])
return arr[x1: x2, y1: y2]
# generate random mine positions with n_mines.
flat = np.array([-1] * self.n_mines +
[0] * (self.size - self.n_mines))
np.random.shuffle(flat)
board = np.reshape(flat, self.shape)
new_board = board * 0 # empty blank new board.
# now populate the adjacency numbers for each area on the board
for i in range(self.shape[0]):
for j in range(self.shape[1]):
adjacent = get_adjacent(board, i, j)
new_board[i, j] = (adjacent == -1).sum()
# now change mine locations on the new board to -1
new_board[board == -1] = -1
return new_board
def generate_mask(self):
mask = np.zeros(self.shape) + 1.0
return mask.astype(bool)
def generate_flags(self):
flags = np.zeros(self.shape)
return flags.astype(bool)
def render(self) -> str:
str_hidden = " - "
str_flag = " P "
str_cleared = " + "
str_mine = " X "
def format_row(board: np.ndarray, mask: np.ndarray, flags: np.ndarray):
assert board.shape == mask.shape
for b, m, f in zip(board, mask, flags):
if m:
yield str_hidden
elif f:
yield str_flag
elif b == 0:
yield str_cleared
elif b == 0:
yield str_cleared
elif b == -1:
yield str_mine
else:
yield f" {b} "
# row by row, print the board
# TODO: hacky trial and error visual formatting here
row_strings = [
"\t\t\t columns",
" row " + "".join([f"{i}".ljust(3) for i in range(self.shape[1])])
]
for row_num in range(self.shape[0]):
row_strings.append(
f" {str(row_num).ljust(3)} |" +
"".join(
format_row(
self._board[row_num, :],
self.mask[row_num, :],
self.flags[row_num, :]
)
)
)
return "\n".join(row_strings)
def uncover(self, x, y) -> (bool, str):
""" returns false if player has lost the game by selecting a mine"""
if self.flags[x, y]:
return True, f"{x} {y} has a flag on it! remove the flag to uncover this space!"
self.mask[x, y] = 0 # punch hole in the mask revealing what's underneath
if self._mines[x, y]:
self.game_over = True
return False, f"{x} {y} was a mine! Game Over!"
else:
return True, f"uncovered {x} {y}"
def toggle_flag(self, x, y) -> str:
# remove existing flag
if self.flags[x, y] == 1:
self.flags[x, y] = 0
return f"removed flag from {x} {y}"
# place a new flag
else:
self.flags[x, y] = 1
return f"placed new flag on {x} {y}"
@staticmethod
def instructions():
return "" # TODO: finish
@staticmethod
def help():
doc = \
""" help
Command description
------- -----------
help see instructions
u [x] [y] uncovers the space at coordinates x and y
f [x] [y] places a flag at coordinates x and y, marking it as a mine.
or removes an existing flag.
"""
return doc
def move(self):
""" prompts user for input and alters the game state accordingly. """
move = input("Whats your next move?")
if move == "help":
print(self.help())
parsed = move.strip().split(" ")
if len(parsed) == 3:
move, x, y = parsed
x = int(x)
y = int(y)
if move == "u":
result, message = self.uncover(x, y)
return message
elif move == "f":
return self.toggle_flag(x, y)
else:
print(f"did not understand input {move}")
def game_end_report(self):
print("board looked like this!")
self.mask = self.mask * 0 # unmask the board!
print(self.render())
if __name__ == "__main__":
# main game loop
# start the game
# TODO: allow user input board instantiation
mf = MineSweeper((15, 15), 30)
# print instructions and help
print(mf.instructions())
print(mf.help())
# print the game map
print(mf.render())
while not mf.game_over:
print(mf.move())
print(mf.render())
mf.game_end_report()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment