Implementing a Minesweeper Hint System

Can we implement a hint system that reveals a safe cell when the player is stuck?

Absolutely! Implementing a hint system can greatly enhance the player experience, especially for beginners or when facing particularly challenging board configurations. Let's explore how we can add this feature to our Minesweeper game.

Hint System Design

Our hint system will reveal a safe cell that is adjacent to an already revealed cell. This approach ensures that the hint is always useful and doesn't just reveal a random safe cell that might be disconnected from the current game state.

Here's how we can implement this:

  1. Find all revealed cells with unrevealed neighbors.
  2. Among these neighbors, identify the safe ones (non-bomb cells).
  3. Randomly select one of these safe cells to reveal as a hint.

Let's modify our MinesweeperGrid class to include this hint system:

#include <algorithm>
#include <iostream>
#include <random>
#include <vector>

class MinesweeperCell {
public:
  bool hasBomb{false};
  bool isRevealed{false};
  int adjacentBombs{0};
};

class MinesweeperGrid {
public:
  MinesweeperGrid(int rows, int cols,
                  int bombs) :
      rows{rows},
      cols{cols}, bombCount{bombs},
      grid(rows,
           std::vector<MinesweeperCell>(cols)) {
    placeBombs();
    calculateAdjacentBombs();
  }

  bool revealCell(int row, int col) {
    if (row < 0 || row >= rows || col < 0 ||
        col >= cols)
      return false;
    if (grid[row][col].isRevealed) return false;

    grid[row][col].isRevealed = true;
    return !grid[row][col].hasBomb;
  }

  std::pair<int, int> getHint() {
    std::vector<std::pair<int, int>> safeCells;

    for (int i = 0; i < rows; ++i) {
      for (int j = 0; j < cols; ++j) {
        if (grid[i][j].isRevealed &&
            hasUnrevealedNeighbors(i, j)) {
          auto neighbors =
            getSafeUnrevealedNeighbors(i, j);
          safeCells.insert(safeCells.end(),
                           neighbors.begin(),
                           neighbors.end());
        }
      }
    }

    if (safeCells.empty())
      return {-1, -1}; // No hint available

    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> dis(
      0, safeCells.size() - 1);
    return safeCells[dis(gen)];
  }

  void printGrid() {
    for (const auto &row : grid) {
      for (const auto &cell : row) {
        if (cell.isRevealed) {
          std::cout
            << (cell.hasBomb
                  ? "*"
                  : std::to_string(
                      cell.adjacentBombs))
            << " ";
        } else {
          std::cout << ". ";
        }
      }
      std::cout << "\n";
    }
  }

private:
  int rows, cols, bombCount;
  std::vector<std::vector<MinesweeperCell>>
    grid;

  void placeBombs() {
    std::vector<int> cells(rows * cols);
    std::iota(cells.begin(), cells.end(), 0);
    std::random_device rd;
    std::mt19937 g(rd());
    std::shuffle(cells.begin(), cells.end(), g);

    for (int i = 0; i < bombCount; ++i) {
      int row = cells[i] / cols;
      int col = cells[i] % cols;
      grid[row][col].hasBomb = true;
    }
  }

  void calculateAdjacentBombs() {
    for (int i = 0; i < rows; ++i) {
      for (int j = 0; j < cols; ++j) {
        if (!grid[i][j].hasBomb) {
          grid[i][j].adjacentBombs =
            countAdjacentBombs(i, j);
        }
      }
    }
  }

  int countAdjacentBombs(int row, int col) {
    int count = 0;
    for (int i = std::max(0, row - 1);
         i <= std::min(rows - 1, row + 1);
         ++i) {
      for (int j = std::max(0, col - 1);
           j <= std::min(cols - 1, col + 1);
           ++j) {
        if (grid[i][j].hasBomb) ++count;
      }
    }
    return count;
  }

  bool hasUnrevealedNeighbors(int row,
                              int col) {
    for (int i = std::max(0, row - 1);
         i <= std::min(rows - 1, row + 1);
         ++i) {
      for (int j = std::max(0, col - 1);
           j <= std::min(cols - 1, col + 1);
           ++j) {
        if (!grid[i][j].isRevealed) return true;
      }
    }
    return false;
  }

  std::vector<std::pair<int, int>>
    getSafeUnrevealedNeighbors(int row,
                               int col) {
    std::vector<std::pair<int, int>>
      safeNeighbors;
    for (int i = std::max(0, row - 1);
         i <= std::min(rows - 1, row + 1);
         ++i) {
      for (int j = std::max(0, col - 1);
           j <= std::min(cols - 1, col + 1);
           ++j) {
        if (!grid[i][j].isRevealed &&
            !grid[i][j].hasBomb) {
          safeNeighbors.emplace_back(i, j);
        }
      }
    }
    return safeNeighbors;
  }
};

int main() {
  MinesweeperGrid game(8, 8, 10);
  // Simulate some gameplay
  game.revealCell(0, 0);
  game.revealCell(1, 1);
  game.printGrid();

  std::cout << "\nGetting a hint...\n";
  auto [hintRow, hintCol] = game.getHint();
  if (hintRow != -1 && hintCol != -1) {
    std::cout << "Hint: Try cell (" << hintRow
              << ", " << hintCol << ")\n";
    game.revealCell(hintRow, hintCol);
    game.printGrid();
  } else {
    std::cout << "No hint available.\n";
  }
}

This implementation adds a getHint() method to our MinesweeperGrid class. When called, it searches for a safe cell to reveal and returns its coordinates. The main function demonstrates how to use this hint system in the game loop.

Remember to integrate this hint system with your existing game logic and UI. You might want to limit the number of hints available to maintain the game's challenge level.

Adjacent Cells and Bomb Counting

Implement the techniques for detecting nearby bombs and clearing empty cells automatically.

Questions & Answers

Answers are generated by AI models and may not have been reviewed. Be mindful when running any code on your device.

Generating Solvable Minesweeper Boards
Is it possible to generate guaranteed solvable Minesweeper boards?
Using static_cast in Event Handling
Why do we use static_cast when handling events instead of a regular cast?
Or Ask your Own Question
Purchase the course to ask your own questions