Skip to content

Instantly share code, notes, and snippets.

@ksylvan
Last active June 11, 2020 22:46
Show Gist options
  • Save ksylvan/e9d6ecbd62fb831f54a4131a0c55a8a2 to your computer and use it in GitHub Desktop.
Save ksylvan/e9d6ecbd62fb831f54a4131a0c55a8a2 to your computer and use it in GitHub Desktop.
MineSweeper
from random import randrange
class MinesGrid:
# Implement a sparse array to optimize space.
MINE = -1
class Row:
# Each row in the __a dict has the following
# structure a list, containing:
# [ empty_cells, {} ]
def __init__(self, cols):
self.__cols = cols
self.__empty_cells = list(range(cols))
self.__r = {}
def __str__(self):
d = { MinesGrid.MINE: 'X', 0: '.' }
return "".join(map(str,
(d[self[i]] if self[i] in d else self[i]
for i in range(self.__cols))))
def __repr__(self):
return f"Row(cols={self.__cols}, empty_cells={self.__empty_cells}, r={self.__r})"
def __getitem__(self, k):
return self.__r[k] if k in self.__r else 0
def __setitem__(self, k, v):
if k not in self.__r:
if k not in self.__empty_cells:
raise ValueError(f"{k} is not in a valid range.")
if v == MinesGrid.MINE:
self.__empty_cells.remove(k)
self.__r[k] = v
@property
def number_empty(self):
return len(self.__empty_cells)
@property
def random_cell(self):
if l := self.number_empty:
r = randrange(l)
return self.__empty_cells[r]
return None
class Empty:
def __getitem__(self, k):
return 0
def __init__(self, rows, cols, mines):
if rows <= 0 or cols <= 0:
raise ValueError(f"rows and columns need to be positive integers. rows={rows}, cols={cols}")
if mines > rows * cols:
raise ValueError(f"Too many mines: {mines}")
self.__rows = rows
self.__cols = cols
self.__empty = rows * cols
self.__a = {} # This a a dict of Row objects
self.__empty_row = MinesGrid.Empty()
for _ in range(mines):
self.__place_mine()
def __repr__(self):
return f"MinesGrid(rows={self.__rows}, cols={self.__cols}, empty={self.__empty}, rows={self.__a}"
def __str__(self):
empty = "." * self.__cols
return "\n".join(empty if r not in self.__a else str(self.__a[r])
for r in range(self.__rows))
def __place_mine(self):
if self.__empty:
r = randrange(self.__rows)
while r in self.__a and self.__a[r].number_empty == 0:
r = (r + 1) % self.__cols
if r not in self.__a:
self.__a[r] = MinesGrid.Row(self.__cols)
c = self.__a[r].random_cell
self.__a[r][c] = MinesGrid.MINE
self.__update_counts(r, c)
self.__empty -= 1
def __update_counts(self, r, c):
t = (
(-1,-1), (-1, 0), (-1, 1),
(0, -1), (0, 1),
(1, -1), (1, 0), (1, 1))
for t1, t2 in t:
if 0 <= r+t1 < self.__rows and 0 <= c+t2 < self.__cols:
u_r, u_c = r+t1, c+t2
if u_r not in self.__a:
self.__a[u_r] = MinesGrid.Row(self.__cols)
if self.__a[u_r][u_c] != MinesGrid.MINE:
self.__a[u_r][u_c] += 1
def __getitem__(self, k):
return self.__a[k] if k in self.__a else self.__empty_row
def __setitem__(self, k, v):
self.__a[k] = v
if __name__ == "__main__":
tests = ((16, 12, 4), (8, 8, 5), (5,5, 20))
for r,c,m in tests:
o = MinesGrid(r, c, m)
print(f"{r}X{c} Grid with {m} mines:\n")
print(o)
print("")
# Sample output:
#
# 16X12 Grid with 4 mines:
#
# 11..........
# X1..........
# 11..........
# ............
# ............
# ............
# ............
# ............
# ...111......
# ...1X1......
# ...222......
# ...1X1......
# ...111......
# ............
# .111........
# .1X1........
#
# 8X8 Grid with 5 mines:
#
# 11..111.
# X1..1X21
# 11.1222X
# ...1X111
# ...111..
# ........
# ......11
# ......1X
#
# 5X5 Grid with 20 mines:
#
# XX4XX
# XX7XX
# XXXXX
# XXXXX
# X434X
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment