Object Orientation, Reading Code
This is another challenge that focuses on learning to read longer bits of code and understand someone else’s data structures and object-oriented design.
We’ve written most of a Minesweeper game for you, including tests for it.
However, one method is unimplemented.
from random import sample
from string import ascii_lowercase as lowercase
import time
class GameLostException(Exception):
"""Raise when game is lost."""
class GameWonException(Exception):
"""Raise when game is won."""
class Cell(object):
"""A cell in the board grid."""
NEIGHBOR_DELTAS = [(-1, -1), (0, -1), (+1, -1),
(-1, 0), (+1, 0),
(-1, +1), (0, +1), (+1, +1)]
def __init__(self, board, col, row):
self.board = board # board object
self.col = col # 0 .. width-1
self.row = row # 0 .. height-1
self.mine = False # Is this a mine?
self.revealed = False # Is this cell revealed
self.number = 0 # Number of mine neighbors
def __repr__(self):
return "<%s,%s-%s%s-%s>" % (
self.col,
self.row,
'*' if self.mine else '',
'R' if self.revealed else '',
self.number)
def neighbors(self):
"""Return valid neighbor cells for a cell."""
return [self.board.cells[self.row + r][self.col + c]
for c, r in self.NEIGHBOR_DELTAS
if (0 <= self.col + c < self.board.width and
0 <= self.row + r < self.board.height)]
def click(self):
"""Click on a cell.
- If it's a mine, reveal all cells and end the game in loss.
- Check and reveal the neighbors.
If all cells are revealed but mines, end in win.
"""
if self.mine:
raise GameLostException()
else:
self.reveal_and_check_neighbors()
if self.board.left == 0:
raise GameWonException()
def reveal_and_check_neighbors(self):
"""Reveal this cell and all of its neighbor cells, recursively.
If a cell is not a mine, reveal it and then do the same for all of its
neighbors. Repeat until there are no more cells found to check.
"""
def show(self, show_mines=False):
"""Show a cell."""
if not show_mines and not self.revealed:
return "."
elif self.mine:
return "*"
else:
return self.number
class Board(object):
"""The board."""
def __init__(self, game, width, height):
"""Create the board.
Set game, width, height, # cells left, and create the raw grid.
"""
assert 1 <= height <= len(lowercase) and 1 <= width <= len(lowercase)
self.game = game # game object
self.width = width
self.height = height
self.left = width * height # number of non-mine, non-revealed cells
self.cells = [
[Cell(self, x, y) for x in range(width)]
for y in range(height)]
def place_mines(self, num_mines):
"""PLace mines and update neighbors' mine counts."""
for mine_cell in sample(range(self.width * self.height), num_mines):
cell = self.cells[mine_cell // self.width][mine_cell % self.width]
cell.mine = True
self.left -= 1
for n in cell.neighbors():
n.number += 1
def show(self, show_mines=False):
"""Show board."""
# Print heading
print("\n ", end=' ')
for col in lowercase[:self.width]:
print(col, end=' ')
print()
# Print each row, with row heading on left
for i, row in enumerate(self.cells):
print(lowercase[i], end=' ')
for cell in row:
print(cell.show(show_mines=show_mines), end=' ')
print()
print()
class Game(object):
"""Minesweeper."""
def __init__(self, width=11, height=11, num_mines=11):
"""Initialize game.
- Set up bord
- Place mines
- Note time (so at game end the delta can be shown)
"""
self.board = Board(self, width, height)
self.board.place_mines(num_mines)
self.start = time.time()
def get_move(self):
"""Get a move, looping until we get a legal move"""
while True:
try:
move = input(
"Move (col row, like 'ab' - %d left) > " % self.board.left)
col = ord(move[0].upper()) - ord('A') # A -> 0, B -> 1, ...
row = ord(move[1].upper()) - ord('A')
cell = self.board.cells[row][col]
# We got a legal move, we can stop asking for a move
return cell
except (IndexError, EOFError) as e:
print("\n(%s: try again)\n" % e)
def play(self):
"""Main game loop."""
try:
while True:
self.board.show()
self.get_move().click()
except GameWonException:
end = "win"
except GameLostException:
end = "lost"
self.board.show(show_mines=True)
print("*** You %s in %.0f secs ***\n" % (end, time.time() - self.start))
if __name__ == '__main__':
import sys
g_width = int(sys.argv[1]) if len(sys.argv) > 1 else 11
g_height = int(sys.argv[2]) if len(sys.argv) > 2 else 11
g_num_mines = int(sys.argv[3]) if len(sys.argv) > 3 else 11
Game(g_width, g_height, g_num_mines).play()
Read and understand the code. Take your time; there’s a lot of sublety in the data structures.
Read and understand the tests.
Complete the unimplemented method, Cell.reveal_and_check_neighbors:
class Cell(object): # ...
def reveal_and_check_neighbors(self):
"""Reveal this cell and all of its neighbor cells, recursively.
If a cell is not a mine, reveal it and then do the same for all of its
neighbors. Repeat until there are no more cells found to check.
"""
You can use the test coverage to know when it’s working.