Katkat

Implement the Core of Minesweeper

AmperitySoftware Engineer · Technical Screen

Last updated

Design an object-oriented Minesweeper game engine featuring state management, board generation, flood-fill cell revealing, and testable boundaries.

Task

Design and implement a testable, headless game engine for classic Minesweeper. Your engine should encapsulate domain logic (game state, cell transitions, flood-fill reveals) while exposing a clean interface for external consumers (such as a CLI or Web UI).

Requirements

Implement the Minesweeper class with the following specifications:

  1. Initialization:

    • __init__(rows: int, cols: int, mines: list[tuple[int, int]])
    • Initializes a board of size rows x cols with mines placed at the specified coordinate pairs (r, c). Accepting explicit coordinates ensures deterministic unit testing.
    • Initial game state is "IN_PROGRESS".
  2. Player Actions:

    • reveal(row: int, col: int) -> str:
      • If out of bounds, already revealed, or flagged, the board state does not change. Return the current game status.
      • If the revealed cell contains a mine, the game state transitions to "LOST".
      • If the revealed cell has 0 adjacent mines (out of the 8 neighboring cells), automatically reveal all adjacent unrevealed, unflagged cells recursively/iteratively (flood fill).
      • If all non-mine cells are revealed, the game state transitions to "WON".
      • Returns the updated game status ("IN_PROGRESS", "WON", or "LOST"). Once in "WON" or "LOST", subsequent calls should return the final status without modifying the board.
    • toggle_flag(row: int, col: int) -> None:
      • Toggles the flag status on an unrevealed cell. Has no effect on already revealed cells or when the game is over.
  3. View Boundary:

    • get_board_view() -> list[list[str]]:
      • Returns a 2D grid representation of what the player is allowed to see:
        • 'H': Hidden (unrevealed and unflagged)
        • 'F': Flagged
        • '0' to '8': Revealed cell showing count of adjacent mines
        • 'M': Exposed mine (only displayed when the game is "LOST")

Constraints

  • 1 <= rows, cols <= 50
  • 0 <= len(mines) < rows * cols
  • Mine coordinates are distinct and guaranteed to fall within [0, rows - 1] and [0, cols - 1].

Example

game = Minesweeper(rows=3, cols=3, mines=[(0, 0)])

# Initial state
game.get_board_view()
# [
#   ['H', 'H', 'H'],
#   ['H', 'H', 'H'],
#   ['H', 'H', 'H']
# ]

# Flag a mine
game.toggle_flag(0, 0)
game.get_board_view()[0][0]  # 'F'

# Reveal bottom-right corner (adjacent count is 0, triggers flood fill)
status = game.reveal(2, 2)
# Flood fill uncovers all cells except (0, 0), which is the only mine.
# Since all non-mine cells are uncovered:
# status == "WON"
game.get_board_view()
# [
#   ['F', '1', '0'],
#   ['1', '1', '0'],
#   ['0', '0', '0']
# ]

Hints

Hint 1

Separate internal cell properties (has_mine, adjacent_mines, is_revealed, is_flagged) from the external string representation generated by get_board_view.

Hint 2

Use BFS or DFS for the flood fill, ensuring you only push adjacent cells onto the queue/stack if they are unrevealed, unflagged, and inside grid boundaries.

Hint 3

Track the count of unrevealed safe cells so you can evaluate the win condition in O(1) time after each move instead of scanning the full grid.

Follow-up questions

  • How would you implement 'first-click safety' so a player is guaranteed never to click a mine on their first move?
  • How would you support a 'chording' move (clicking an already revealed number to auto-reveal adjacent cells if the number of surrounding flags matches the cell's number)?
  • If the board were sparse and scaled to 1,000,000 x 1,000,000 cells with 1,000 mines, how would you redesign the internal storage and traversal?