Uploaded running version of the minesweeper game
Thanks to oysteikt for the colorization code; an extension of this would be very welcome. The game ended up on the torus because that is just what modulo results in and I couldn't be hassled to _not_ identify the edges with each other.
This commit is contained in:
parent
5209759065
commit
8b4df7dbeb
|
@ -0,0 +1,227 @@
|
|||
import numpy as np
|
||||
from random import sample
|
||||
|
||||
def linear_to_pair(i, s):
|
||||
x = i // s
|
||||
y = i - x * s
|
||||
return x, y
|
||||
|
||||
def offset_inside(x, y, ox, oy, s):
|
||||
_x = (x + ox) % s
|
||||
_y = (y + oy) % s
|
||||
return (_x, _y)
|
||||
|
||||
def reveal_around(x, y, mf):
|
||||
unchecked_interior = set()
|
||||
boundary = set()
|
||||
offsets = [(-1, 0), (1, 0), (0, -1), (0, 1)]
|
||||
|
||||
for ox, oy in offsets:
|
||||
_x, _y = offset_inside(x, y, ox, oy, mf.shape[0])
|
||||
if mf[_x, _y] == 0:
|
||||
unchecked_interior.add((_x, _y))
|
||||
else:
|
||||
boundary.add((_x, _y))
|
||||
return unchecked_interior, boundary
|
||||
|
||||
|
||||
def generate_revelation_matrix(x, y, mf):
|
||||
reveal = np.zeros(mf.shape)
|
||||
if mf[x, y] == -1:
|
||||
return None
|
||||
elif mf[x, y] > 0:
|
||||
reveal[x, y] = 1
|
||||
return reveal
|
||||
else:
|
||||
checked_interior = set()
|
||||
checked_interior.add((x, y))
|
||||
unchecked_interor, boundary = reveal_around(x, y, mf)
|
||||
while len(unchecked_interor) > 0:
|
||||
x, y = unchecked_interor.pop()
|
||||
int_nbhd, bdry_nbdh = reveal_around(x, y, mf)
|
||||
unchecked_interor.update(int_nbhd.difference(checked_interior))
|
||||
boundary.update(bdry_nbdh)
|
||||
checked_interior.add((x, y))
|
||||
|
||||
interior = list(checked_interior)
|
||||
boundary = list(boundary)
|
||||
|
||||
for x, y in interior:
|
||||
reveal[x, y] = 1
|
||||
for x, y in boundary:
|
||||
if mf[x, y] > 0:
|
||||
reveal[x, y] = 1
|
||||
else:
|
||||
print("Mine in boundary")
|
||||
return reveal
|
||||
|
||||
class Minesweeper:
|
||||
def __init__(self, size, mines):
|
||||
self.m = mines
|
||||
self.s = size
|
||||
self.f = mines
|
||||
self.lost = False
|
||||
self.initialize_field()
|
||||
|
||||
def initialize_field(self):
|
||||
minefield = np.zeros((self.s, self.s))
|
||||
fog_of_war = np.zeros((self.s, self.s))
|
||||
flagged = set()
|
||||
|
||||
mine_positions = sample(range(self.s*self.s), self.m)
|
||||
xs = [-1, 0, 1]
|
||||
ys = [-1, 0, 1]
|
||||
|
||||
for i in mine_positions:
|
||||
x, y = linear_to_pair(i, self.s)
|
||||
minefield[x, y] = -1
|
||||
|
||||
for i in range(self.s*self.s):
|
||||
x, y = linear_to_pair(i, self.s)
|
||||
if minefield[x, y] == -1:
|
||||
continue
|
||||
else:
|
||||
sub = np.zeros((3, 3))
|
||||
for ox in xs:
|
||||
for oy in ys:
|
||||
sub[ox+1, oy+1] = minefield[offset_inside(x, y, ox, oy, self.s)]
|
||||
num_mines = np.sum(sub < 0)
|
||||
minefield[x, y] = num_mines
|
||||
|
||||
self.mf = minefield
|
||||
self.fow = fog_of_war
|
||||
self.flagged = flagged
|
||||
|
||||
def flag_at(self, x, y) -> None:
|
||||
self.flagged.add((x, y))
|
||||
|
||||
def unflag_at(self, x, y) -> None:
|
||||
self.flagged.remove((x, y))
|
||||
|
||||
def reveal_at(self, x, y):
|
||||
r = generate_revelation_matrix(x, y, self.mf)
|
||||
if r is None:
|
||||
self.lost = True
|
||||
else:
|
||||
self.fow = np.logical_or(self.fow, r)
|
||||
|
||||
def show_minefield(self):
|
||||
field = np.abs(self.mf * self.fow)
|
||||
return '\n'.join(' '.join(self.colorize(int(i)) for i in row) for row in field)
|
||||
|
||||
@classmethod
|
||||
def colorize(cls, i: int) -> str:
|
||||
return [
|
||||
lambda x: f'\033[0m{x}\033[0m',
|
||||
lambda x: f'\033[31m{x}\033[0m',
|
||||
lambda x: f'\033[32m{x}\033[0m',
|
||||
lambda x: f'\033[33m{x}\033[0m',
|
||||
lambda x: f'\033[34m{x}\033[0m',
|
||||
][i](i)
|
||||
|
||||
def __repr__(self):
|
||||
field = np.abs(self.mf * self.fow)
|
||||
s = ""
|
||||
|
||||
upper = " |"
|
||||
lower = " |"
|
||||
divider = "---"
|
||||
for x in range(self.s):
|
||||
u, l = numeral_vertical(x, len(str(self.s)), "b")
|
||||
upper = upper + u + "|"
|
||||
lower = lower + l + "|"
|
||||
divider += "--"
|
||||
s = s + upper + "\n" + lower + "\n" + divider + "\n"
|
||||
|
||||
for x in range(self.s):
|
||||
s = s + numeral_horizontal(x, len(str(self.s)), "r") + "|"
|
||||
for y in range(self.s):
|
||||
# if self.mf[x, y] == -1:
|
||||
# s += "m "
|
||||
if (x, y) in self.flagged:
|
||||
s += "f "
|
||||
elif self.fow[x, y] == 0:
|
||||
s += "x "
|
||||
else:
|
||||
s += self.colorize(int(field[x, y]))
|
||||
s += " "
|
||||
s = s[:-1]
|
||||
s += "|\n"
|
||||
return s[:-1] + "\n" + divider
|
||||
|
||||
def check_victory(self):
|
||||
return (
|
||||
len(self.flagged) == self.m # all mines are flagged
|
||||
and
|
||||
int(np.sum(self.fow)) == (self.s**2 - self.m) # no stone unturned
|
||||
)
|
||||
|
||||
def numeral_horizontal(number, space, justification):
|
||||
n = str(number)
|
||||
if justification == "l":
|
||||
s = n + " " * (space - len(n))
|
||||
elif justification == "r":
|
||||
s = (n[::-1] + " " * (space - len(n)))[::-1]
|
||||
elif justification == "c":
|
||||
pre = (space - len(n)) // 2
|
||||
s = " " * pre + n + " " * (space - len(n) - pre)
|
||||
return s
|
||||
|
||||
def numeral_vertical(number, space, justification):
|
||||
just_map = {"t": "l", "b": "r", "c": "c"}
|
||||
n = numeral_horizontal(number, space, just_map[justification])
|
||||
return list(n)
|
||||
|
||||
def dispatch_choice(ms: Minesweeper, choice) -> None:
|
||||
cmd = choice.split(" ")
|
||||
|
||||
if cmd[0] == "f":
|
||||
x = int(cmd[1])
|
||||
y = int(cmd[2])
|
||||
ms.flag_at(x, y)
|
||||
elif cmd[0] == "u":
|
||||
x = int(cmd[1])
|
||||
y = int(cmd[2])
|
||||
ms.unflag_at(x, y)
|
||||
elif cmd[0] == "r":
|
||||
ms.s = int(cmd[1])
|
||||
ms.m = int(cmd[2])
|
||||
ms.initialize_field()
|
||||
elif cmd[0] == "o":
|
||||
x = int(cmd[1])
|
||||
y = int(cmd[2])
|
||||
ms.reveal_at(x, y)
|
||||
else:
|
||||
print("""Legal actions are:
|
||||
"f x y" to place a flag
|
||||
"u x y" to remove a flag
|
||||
"o x y" to open a tile
|
||||
"r s m" o reset the board with size s and m mines
|
||||
"q" to quit
|
||||
""")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# ms = Minesweeper(20, 1)
|
||||
is_running = True
|
||||
has_won = False
|
||||
|
||||
print("You're playing minesweeper on the torus.")
|
||||
size = int(input("What size of board would you like to play? "))
|
||||
num_mines = int(input("How many mines would you like? "))
|
||||
ms = Minesweeper(size, num_mines)
|
||||
|
||||
while is_running:
|
||||
if has_won:
|
||||
print("You won!")
|
||||
has_won = False
|
||||
else:
|
||||
print(ms)
|
||||
print(f"Flagged {len(ms.flagged)} of {ms.m} mines. Board size is {ms.s}.")
|
||||
print(f"Revealed {int(np.sum(ms.fow))} of {ms.s**2} tiles.")
|
||||
cmd = input("What would you like to do? ")
|
||||
if cmd == "q":
|
||||
is_running = False
|
||||
else:
|
||||
dispatch_choice(ms, cmd)
|
||||
has_won = ms.check_victory()
|
Loading…
Reference in New Issue