Skip to content

Instantly share code, notes, and snippets.

@timorthi
Last active October 14, 2018 22:31
Show Gist options
  • Save timorthi/3b5f115392920663bc05fb787ae7e77b to your computer and use it in GitHub Desktop.
Save timorthi/3b5f115392920663bc05fb787ae7e77b to your computer and use it in GitHub Desktop.
Python implementation of Minesweeper
import random
class Cell:
'''
Helper class to encapsulate the state of a given cell in the Minesweeper grid.
'''
def __init__(self, row, col, revealed=False, is_bomb=False, value=0):
self.row = row
self.col = col
self.revealed = revealed
self.is_bomb = is_bomb
self.value = value
def __repr__(self):
if self.revealed:
return '*' if self.is_bomb else str(self.value)
else:
return '-'
class Minesweeper:
BOMB_PCT = 0.15
DELTAS = [
(-1, -1), (0, 1), (1, 1),
(-1, 0), (1, 0),
(-1, 1), (0, -1), (1, -1),
]
def __init__(self, rows, cols):
Minesweeper.__validate(rows, cols)
self.rows = int(rows)
self.cols = int(cols)
self.has_bombed = False
self.revealed_count = 0
self.num_bombs = 0
self.grid = self.__generate_grid()
def print_grid(self):
'''
Helper method to print the grid in its current state with row and column labels.
'''
col_labels = "\t".join(map(str, [x for x in range(1, self.cols+1)]))
print(f'\t{col_labels}\n')
for i in range(0, self.rows):
row_vals = "\t".join(map(str, self.grid[i]))
print(f'{i+1}\t{row_vals}')
def sweep(self, row, col):
'''
Accepts a row and column to select and reveal a cell. If the cell is a bomb, game is over.
Otherwise, recursively reveal until all revealed cells are non-blank.
'''
row = int(row) - 1
col = int(col) - 1
cell = self.grid[row][col]
if cell.revealed:
print("Please select a cell that isn't revealed yet.")
return
if cell.is_bomb:
self.grid[row][col].revealed = True
self.revealed_count += 1
self.has_bombed = True
else:
self.__expand_blanks(cell)
def is_game_over(self):
'''
Returns whether the game is over.
'''
return self.has_bombed or self.__all_cells_revealed()
def reveal_all_cells(self):
'''
Helper method to reveal all cells at the end of a game.
'''
for i in range(0, self.rows):
row = self.grid[i]
for j in range(0, self.cols):
row[j].revealed = True
def print_end_message(self):
'''
Helper method to print message depending on game end condition.
'''
if self.has_bombed:
print('You swept a mine. Game over!')
else:
print('Congratulations! You swept the field without blowing something up.')
@staticmethod
def __validate(rows, cols):
'''
Validates number of rows and columns during initialization
'''
rows = int(rows)
cols = int(cols)
if rows < 5 or cols < 5:
raise ValueError('Rows or columns cannot be less than 5.')
elif rows > 20 or cols > 20:
raise ValueError('Rows or columns cannot be larger than 20.')
def __generate_grid(self):
'''
Returns a `self.rows x self.cols` 2D array with about self.BOMB_PCT chance for each cell
to have a bomb.
'''
grid = [None] * self.rows
# Populate grid with bombs
for i in range(0, self.rows):
row = [None] * self.cols
for j in range(0, self.cols):
is_bomb = (random.random() <= self.BOMB_PCT)
if is_bomb:
self.num_bombs += 1
row[j] = Cell(i, j, is_bomb=is_bomb)
grid[i] = row
# Compute adjacent values for non-bomb cells
for i in range(0, self.rows):
row = grid[i]
for j in range(0, self.cols):
cell = row[j]
if not cell.is_bomb:
cell.value = self.__get_adjacent_bomb_count(grid, i, j)
return grid
def __get_adjacent_bomb_count(self, grid, row, col):
'''
Returns the number of bombs adjacent to the given cell.
'''
count = 0
for delta in self.DELTAS:
d_row, d_col = row + delta[1], col + delta[0]
if self.__is_in_range(d_row, d_col) and grid[d_row][d_col].is_bomb:
count += 1
return count
def __expand_blanks(self, root):
'''
Recursively reveal non-blank, non-revealed cells in the grid starting from root.
'''
if root.revealed:
return
root.revealed = True
self.revealed_count += 1
if root.value == 0:
for delta in self.DELTAS:
d_row, d_col = root.row + delta[1], root.col + delta[0]
if self.__is_in_range(d_row, d_col):
self.__expand_blanks(self.grid[d_row][d_col])
def __is_in_range(self, row, col):
'''
Returns whether row and col fall within the grid's range.
'''
return (0 <= row < self.rows) and (0 <= col < self.cols)
def __all_cells_revealed(self):
'''
Returns whether all non-bomb cells have been revealed
'''
return self.revealed_count >= (self.rows * self.cols) - self.num_bombs
def main():
print('Welcome to Minesweeper!')
print('Please enter grid size in the format "rows cols"')
user_input = input('--> ')
ms = Minesweeper(*user_input.split(' '))
while not ms.is_game_over():
ms.print_grid()
print('Please enter the cell you wish to sweep in the format "row col"')
user_input = input('--> ')
ms.sweep(*user_input.split(' '))
ms.print_grid()
ms.print_end_message()
print('\n\n\nFinal Grid:')
ms.reveal_all_cells()
ms.print_grid()
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment