Skip to content

Instantly share code, notes, and snippets.

@bobmurder
Created November 18, 2012 23:59
Show Gist options
  • Save bobmurder/4108242 to your computer and use it in GitHub Desktop.
Save bobmurder/4108242 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
import random
import re
import sys
from collections import defaultdict, namedtuple
from itertools import chain, count
from operator import add
from random import choice
from string import uppercase
DEBUG=True
VERBOSE=False
########################################################################
# LOOKUP TABLES
########################################################################
#===================================================
# Shift Table
#===================================================
def shift_table():
''' List of tuples to add with a cell to find neighbors '''
x_vals = [-1, -1, -1, 0, 0, 1, 1, 1]
y_vals = [-1, 0, 1, -1, 1, -1, 0, 1]
return zip(x_vals, y_vals)
shifts = shift_table()
#===================================================
# Valid Cells Table
#===================================================
def valid_cell_table(height=15, width=15):
''' A set of tuples where each tuple is a valid cell coordinate pair '''
return set([(i, j) for i in range(width) for j in range(height)])
valid_cells = valid_cell_table()
#===================================================
# User Cell Choice Table
#===================================================
def letter_table(height=15, width=15):
''' A dict for looking up the tuple equivalent to A1-Z26.
Range: A1: (0, 0) to "%s%s" % (letters[width], height+1) '''
keys = ['%s%s' % (c, n) for c in uppercase[:width]
for n in range(1, height + 1)]
values = [cell for cell in sorted(valid_cells)]
return {k: v for k, v in zip(keys, values)}
letters = letter_table()
#===================================================
# Neighbors lookup table
#===================================================
# Helper functions
def shift_cell(cell, shift):
''' Add cell + shift, and return the result '''
x1, x2, y1, y2 = cell + shift
candidate = (x1 + y1, x2 + y2)
return candidate
def valid_neighbors(cell):
''' Return a list of valid neighbors for `cell` '''
neighbors = []
for shift in shifts:
candidate = shift_cell(cell, shift)
if candidate in valid_cells:
neighbors.append(candidate)
return neighbors
# Table Creation
def neighbors_table():
''' Each key is a tuple (0, 0) and the value is a list of valid neighors.
neighbors[(0, 0)] == [(0, 1), (1, 0), (1, 1)] '''
return {cell: valid_neighbors(cell) for cell in valid_cells}
neighbors = neighbors_table()
nums = [str(n) for n in range(1, 9)]
#===================================================
# Cell value/symbol tables
#===================================================
cell_values = {'null': ' ',
'mine': 'X',
'safe': '9',
'flag': nums}
cell_symbols = {'mark': '!',
'poss': '?',
'hide': '#'}
########################################################################
# CLASSES / DATA STRUCTURES
########################################################################
class Cell(object):
'''
A class representing a minesweeper cell.
ARGUMENTS:
x, y - positive cartesian coordinates, starting at 0, 0
'''
def __init__(self, x, y, value=cell_values['null'],
symbol=cell_symbols['hide'], hidden=True):
self.x = x
self.y = y
self.value = value
self.symbol = symbol
self.hidden = hidden
def __str__(self):
return "(%s, %s)" % (self.x, self.y)
def coords(self):
''' Return an (x, y) tuple for lookups '''
return (self.x, self.y)
def protect(self):
''' Guard cells during mine placement to prevent the
player from losing on turn one. This swaps from
null to safe, or safe to null '''
if self.value == cell_values['null']:
self.value = cell_values['safe']
else:
self.value = cell_values['null']
def reveal(self):
''' Reveal a cell '''
self.hidden = False
self.symbol = self.value
def mark(self):
''' Change the mark a user has made on a cell. The cycle is
hide -> flag -> possible -> hide -> flag ... '''
if self.symbol == cell_symbols['hide']:
self.symbol = cell_symbols['flag']
elif self.symbol == cell_symbols['flag']:
self.symbol = cell_symbols['poss']
elif self.symbol == cell_symbols['poss']:
self.symbol = cell_symbols['hide']
else:
raise ValueError('Invalid symbol')
def check_type(self, cell_type):
''' Determine a cell's type based on its value '''
assert cell_type in cell_values.keys()
return self.value in cell_values[cell_type]
class Game(object):
''' This class holds and updates the cell grid, counter values,
and handles output. '''
def __init__(self, cells, mines, flags, height=15, width=15):
# these don't get modified by the class instance at all
self.height = height
self.width = width
self.mines = mines
self.flags = flags
# these are modified by the class instance
self.cells = cells
self.marks = []
self.revealed = set()
# initialize counters
self.turn_count = 0
self.mark_count = len(self.marks)
self.mine_count = len(self.mines)
# win flag
self.won = False
self.game_over = False
def won(self):
''' Determine if we've won '''
marked = sorted(self.marks) == sorted(self.mines)
uncovered = all([cells[cell].hidden
for cell in valid_cells if not cell in self.mines])
return marked and uncovered
# Counter methods
def increment(self, counter):
self.counter += 1
def decrement(self, counter):
self.counter -= 1
def reveal(self, origin):
# get current cell
cell = self.cells[origin]
# if it is a mine, declare a game over
if cell.value == cell_values['mine']:
self.game_over = True
return False
if origin in self.revealed:
return True
# if it is an empty cell, reveal it, reveal adjacent numbers,
# then recurse onto adjacent empty spaces
elif cell.value == cell_values['null']:
self.unhide(cell, origin)
cell_neighbors = neighbors[origin]
for neighbor in cell_neighbors:
# get neighbor cell instance
neighbor_cell = self.cells[neighbor]
# if cell value is a number
if neighbor_cell.value in cell_values['flag']:
self.unhide(neighbor_cell, neighbor)
# if cell value is ' '
elif neighbor_cell.value == cell_values['null']:
return self.reveal(neighbor)
else:
print "you idiot, fix this!!"
# if it is a number, reveal it an return
elif cell.value in cell_values['flag']:
self.unhide(cell, origin)
return
def unhide(self, cell, origin):
cell.reveal()
self.revealed.add(origin)
def output(self):
grid = symbol_grid(self.cells)
header = self._header()
display = self._display(grid)
prompt = self._prompt()
status_bar = self._status_bar()
return header + display +'\n' + prompt + '\n\n' + status_bar
def update(self, cell, choice):
if choice == 'R':
self.reveal(cell)
elif choice == 'M':
self.cells[cell].mark()
elif choice == 'Q':
# we either quit, or print the grid again
while True:
ans = raw_input("Are you sure? Y/n")
if ans == 'Y':
sys.exit('Thanks for playing')
elif ans == 'n':
self.output()
else:
sys.exit("Prevent this shit")
# private functions for creating the output string
def _header(self, head=0):
'''
Returns column headers from A-width
'''
header = [uppercase[i] for i in range(self.width)]
header.insert(head, ' ')
header.append(' \n')
return '|'.join(header)
def _display(self, grid, head=0):
for idx, row in enumerate(grid):
i = idx + 1
if len(str(i)) == 1:
row.insert(head, ' %s' % i)
else:
row.insert(head, '%s' % i)
row.append(' \n')
return ''.join(['|'.join(row) for row in grid])
def _prompt(self):
''' Prompt that prints each turn '''
return "(R)eveal, (M)ark, (Q)uit"
def _status_bar(self):
return 'Unmarked mines: %s' % (len(self.mines) - len(self.marks))
def guess():
while True:
user_guess = raw_input("Enter your guess > ")
if user_guess in valid_inputs:
return user_guess
else:
print "Invalid guess."
continue
def cells(width=15, height=15):
'''
cells returns a dict that holds all the Cell instances.
The keys are coordinate tuples, and the values are Cell instances.
A Grid instance holds a list of lists, and the list items are tuples
corresponding to the keys from cells. Cell instances are updated and
displayed by accessing values of this dictionary. Creating this
dictionary separates map generation from map updating, which should
simplify things.
'''
return {pair: Cell(*pair) for pair in valid_cells}
#===================================================
# Grid initialization functions
#===================================================
def protect_area(origin):
'''
This function is called twice. The first time, it changes the value
of each cell to '9', an otherwise impossible value. This prevents
the user from dying on turn 1. After mine placement is finished,
this function is called again, which changes all '9' valued cells
back to ' ' cells.
'''
for cell in neighbors[origin]:
cells[cell].protect()
cells[origin].protect()
def random_cell(height=15, width=15):
''' Return a tuple of valid cell coordinates '''
random_cell = (random.choice(range(width)), random.choice(range(height)))
assert random_cell in valid_cells
return random_cell
def place_mines(mines, initial_pick=(7, 7), height=15, width=15):
''' Place mines on the grid by setting a cell's value to 'X'
Return a list of mine (x, y) tuples. '''
protect_area(initial_pick)
mine_list = []
while mines:
mine = random_cell(height, width)
if cells[mine].check_type('null'):
cells[mine].value = cell_values['mine']
mine_list.append(mine)
mines -= 1
protect_area(initial_pick)
return mine_list
def place_flags(mines):
''' Place numeric flags adjacent to all mines.
Return a list of flag cells. '''
flag_cells = defaultdict(int)
for mine in mines:
neighbor_list = neighbors[mine]
for neighbor in neighbor_list:
if neighbor not in mines:
flag_cells[neighbor] += 1
for cell, value in flag_cells.iteritems():
cells[cell].value = str(value)
return flag_cells.keys()
def value_grid(cells, height=15, width=15):
return [[cells[(i, j)].value for i in range(width)] for j in range(height)]
def symbol_grid(cells, height=15, width=15):
return [[cells[(i, j)].symbol for i in range(width)] for j in range(height)]
if __name__ == '__main__':
import sys
if DEBUG or VERBOSE:
from pprint import pprint
if VERBOSE:
# print lookup tables
print 'shifts'
pprint(shifts)
print 'valid cells'
pprint(valid_cells)
print 'letters'
pprint(letters)
print 'neighbors'
pprint(neighbors)
n_mines = 20
initial_pick = (7, 7)
cells = cells()
mines = place_mines(n_mines, initial_pick)
flags = place_flags(mines)
if DEBUG:
print "#" * 40
print "VALUES"
pprint(value_grid(cells))
print "#" * 40
print "SYMBOLS"
pprint(symbol_grid(cells))
minesweeper = Game(cells, mines, flags)
minesweeper.reveal(initial_pick)
print minesweeper.output()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment