In Minesweeper, every click—whether left, right, or a special interaction like clicking on a number—triggers specific calculations that influence the game state. Understanding these in-game calculations is crucial for anyone developing Minesweeper, as each action must be precisely handled to provide a smooth and intuitive player experience.
In this article, we’ll break down the key interactions that occur when players interact with Minesweeper cells, including code examples to illustrate these mechanics.
When a player left-clicks a cell, they reveal its content, which may be a number (indicating the count of nearby mines), an empty space, or a mine.
typescript
function handleLeftClick(cell: MinesweeperCell) {
// If the cell is flagged, ignore the click
if (cell.isFlagged) return;
// If the cell contains a mine, the game is lost
if (cell.isMine) {
revealAllMines();
gameOver();
} else {
revealCell(cell);
}
}
function revealCell(cell: MinesweeperCell) {
// Mark cell as revealed
cell.isRevealed = true;
// If the cell has no adjacent mines, reveal neighboring cells
if (cell.adjacentMines === 0) {
for (let neighbor of cell.getNeighbors()) {
if (!neighbor.isRevealed && !neighbor.isFlagged) {
revealCell(neighbor); // Recursively reveal empty cells
}
}
}
}
In this example:
revealAllMines()
is called to
end the game.Right-clicking a cell allows the player to place or remove a flag, which they can use to mark potential mines.
typescript
function handleRightClick(cell: MinesweeperCell) {
// If the cell is already revealed, ignore the click
if (cell.isRevealed) return;
// Toggle the flagged status of the cell
cell.isFlagged = !cell.isFlagged;
// Update the remaining mine count
updateMineCount();
}
This function toggles the isFlagged
property for a cell. Flags
are typically restricted to unrevealed cells only, ensuring that players
cannot flag already revealed cells.
On mobile devices, long-press gestures replace right-clicks. Here’s an example of a simple long-press detection for flagging cells:
typescript
let pressTimer: number;
function handleLongPressStart(cell: MinesweeperCell) {
pressTimer = setTimeout(() => handleRightClick(cell), 500); // 500ms for long press
}
function handleLongPressEnd() {
clearTimeout(pressTimer); // Clear timer if user releases early
}
// Attach event listeners for touch events on each cell
cellElement.addEventListener('touchstart', () => handleLongPressStart(cell));
cellElement.addEventListener('touchend', handleLongPressEnd);
In this mobile-friendly code:
handleLongPressStart()
initiates a timer when a touch
starts.
handleLongPressEnd()
cancels the timer if the user releases
the touch early, allowing flags to be set only with a sustained press.
Once a number cell is satisfied—meaning it has the correct number of flagged neighboring cells—players can click it to reveal all adjacent cells. This action speeds up gameplay by revealing multiple cells at once.
typescript
function handleNumberClick(cell: MinesweeperCell) {
// Check if the number cell is satisfied
if (isSatisfied(cell)) {
// Reveal all neighboring cells that are not flagged
for (let neighbor of cell.getNeighbors()) {
if (!neighbor.isRevealed && !neighbor.isFlagged) {
revealCell(neighbor);
}
}
}
}
function isSatisfied(cell: MinesweeperCell) {
// Count flagged neighbors and compare to the cell's number
let flagCount = 0;
for (let neighbor of cell.getNeighbors()) {
if (neighbor.isFlagged) flagCount++;
}
return flagCount === cell.adjacentMines;
}
The isSatisfied()
function checks if the number of flagged
neighbors matches the cell's adjacentMines value. If satisfied, all
unrevealed, unflagged neighbors are revealed with
revealCell()
.
When a player clicks an empty cell (a cell with 0 adjacent mines), the game reveals all connected empty cells, creating a “cascade” effect.
typescript
function revealEmptyCell(cell) {
if (!cell.isRevealed && !cell.isMine && cell.adjacentMines === 0) {
cell.isRevealed = true;
for (let neighbor of cell.getNeighbors()) {
if (!neighbor.isRevealed && !neighbor.isFlagged) {
revealEmptyCell(neighbor);
}
}
}
}
This function recursively reveals all connected empty cells and their adjacent numbered cells, providing players with immediate information about a large section of the board.
The game ends either when the player reveals a mine (loses) or successfully flags all mines and reveals all safe cells (wins).
typescript
function checkWin() {
for (let row of board) {
for (let cell of row) {
if (!cell.isMine && !cell.isRevealed) return false; // Unrevealed safe cells
}
}
return true;
}
function gameOver() {
revealAllMines();
alert("Game Over!");
}
function checkGameEnd() {
if (checkWin()) {
alert("Congratulations! You've won!");
}
}
This code checks if all safe cells are revealed, confirming a win when
true. gameOver()
is triggered upon clicking a mine, revealing
all mines to show the full board.
Each Minesweeper interaction requires precise logic to ensure smooth gameplay. From basic left- and right-clicks to advanced interactions like satisfied number cells, the code must account for all possible scenarios. Here's a quick recap of each action covered:
Reveals a cell and triggers recursive reveals for empty cells.
Toggles flag status for marking suspected mines.
Reveals all unflagged neighboring cells when flagged neighbors match the cell's number.
Uncovers large sections of the board by revealing all connected empty cells.
Checks for win or loss based on cell reveals and flagged mines.
These interactions provide Minesweeper's characteristic balance between logic and suspense. By understanding these calculations, you'll have a solid foundation for implementing Minesweeper's core mechanics in any development environment.