In object-oriented programming (OOP), maximizing API use streamlines development, leading to simpler and more concise code. This clarity not only improves readability but also eases debugging. Leveraging an API effectively in OOP thus ensures a blend of simplicity, efficiency and maintainability in software development.

This demo (partially) implements the minesweeper video game using p5-v2 and the Quadrille API with a four-layer strategy. Each layer is one quadrille with a single responsibility:

  • mines — the bombs.
  • counts — the numbers next to bombs.
  • cover — a two-tone internal layer (red over filled cells, green over empty cells) that distinguishes the two click zones and drives flood fill.
  • mask — the uniformly magenta layer the player actually sees, derived from cover.

As gameplay progresses, clicking on cells uncovers them by clearing either individual or connected cells from cover; mask is re-derived from cover after each click.

(mouse click to play; press any key to init)

Code
let mines, counts, cover, mask;
let size = 20;
let n = size * 2;

function init() {
  // Layer 1: mines
  mines = createQuadrille(size, size, n, '💣');
  // Layer 2: counts (neighbor counts on empty cells of `mines`)
  counts = createQuadrille(size, size);
  mines.visit(({ row, col }) => {
    const order = mines.ring(row, col).order;
    if (order > 0) counts.fill(row, col, order.toString());
  }, ({ value }) => value === null);
  // Layer 3: cover — merge mines+counts, then recolor
  cover = Quadrille.or(mines, counts);
  cover.replace(color('red')).fill(color('green'));
  // Layer 4: mask (uniform magenta cover derived from `cover`)
  mask = cover.clone().replace(color('magenta'));
}

function setup() {
  Quadrille.cellLength = 400 / size;
  createCanvas(400, 400);
  init();
  document.oncontextmenu = () => false;
}

function draw() {
  background('moccasin');
  drawQuadrille(mines);
  drawQuadrille(counts);
  drawQuadrille(mask, { outline: color('lime') });
}

function mouseClicked() {
  const row = cover.mouseRow;
  const col = cover.mouseCol;
  if (mines.isFilled(row, col))       cover.clear();
  else if (counts.isFilled(row, col)) cover.clear(row, col);
  else                                cover.clear(row, col, true);
  mask = cover.clone().replace(color('magenta'));
}

function keyPressed() {
  init();
}

Init

The init function builds the four layers in dependency order: minescountscovermask. Each layer is set once and never reassigned.

function init() {
  // Layer 1: mines
  mines = createQuadrille(size, size, n, '💣');
  // Layer 2: counts (neighbor counts on empty cells of `mines`)
  counts = createQuadrille(size, size);
  mines.visit(({ row, col }) => {
    const order = mines.ring(row, col).order;
    if (order > 0) counts.fill(row, col, order.toString());
  }, ({ value }) => value === null);
  // Layer 3: cover — merge mines+counts, then recolor
  cover = Quadrille.or(mines, counts);
  cover.replace(color('red')).fill(color('green'));
  // Layer 4: mask (uniform magenta cover derived from `cover`)
  mask = cover.clone().replace(color('magenta'));
}

ℹ️ Ring Method

ring returns a Quadrille instance of all neighbors within radius 1. Its .order property reports how many of those neighbors are filled. Because mines contains only bombs, mines.ring(row, col).order is exactly the count of neighboring mines — no manual neighbor inspection needed.

ℹ️ Visit Predicate

The counting loop visits only empty cells thanks to the filter argument of visit, the predicate ({ value }) => value === null.

ℹ️ Building the Cover with or

Quadrille.or(mines, counts) returns a new quadrille whose cells are filled wherever either mines or counts is filled — i.e. exactly the union of the filled cells. Then replace(color('red')) recolors all of those filled cells red, and fill(color('green')) fills the remaining empties green. The two tones make cover look like a heatmap of the two click zones — but more importantly, the boundary between red and green is what stops the flood fill at the right place (see Interaction).

ℹ️ Mask Display Layer

The player never sees the two-tone cover directly. The visible mask is built by cloning cover and replacing every cell color (red or green) with magenta, producing a uniform cover that hides the game state while keeping cover available for the click logic.

The demo below uses cover itself as the visible layer so you can see the algorithm in action. Click around: a red cell next to a number clears one cell (clear(row, col)); a green cell clears a connected region plus its red border (clear(row, col, true)); a mine clears the entire cover (clear()). The static mines and counts layers underneath become visible as cover is cleared.

(mouse click to play; press any key to init)

Draw

The draw function renders all three visible layers: mines and counts underneath, then mask on top.

function draw() {
  background('moccasin');
  drawQuadrille(mines);
  drawQuadrille(counts);
  drawQuadrille(mask, { outline: color('lime') });
}

ℹ️ Info

  • Static layers (mines, counts): built once, never modified.
  • Dynamic layer (mask): rebuilt after each click. As cells are cleared in cover, mask reflects the updates, revealing the underlying mines and counts.

Interaction

Mouse clicks modify cover to uncover cells, then rebuild mask. The three branches map one-to-one onto the three Minesweeper rules, and each branch uses a different overload of clear:

  1. Clicked a mine → game over: clear() wipes the entire cover.
  2. Clicked a number → reveal that cell only: clear(row, col) clears one cell.
  3. Clicked an empty cell → cascade: clear(row, col, border) flood-fills the connected empty region and, because border = true, also clears the surrounding numbers — exactly the classic Minesweeper cascade rule.
function mouseClicked() {
  const row = cover.mouseRow;
  const col = cover.mouseCol;
  if (mines.isFilled(row, col))       cover.clear();              // boom
  else if (counts.isFilled(row, col)) cover.clear(row, col);      // single
  else                                cover.clear(row, col, true); // flood + border
  mask = cover.clone().replace(color('magenta'));
}

ℹ️ Why the two tones matter for flood fill

Quadrille’s flood fill clears the connected region of cells whose value matches the start cell. When the player clicks an empty cell, the start cell is green in cover; the flood expands across connected green cells and stops at the red boundary. With border = true, the bordering red cells (the surrounding numbers) are cleared too. If cover were a single uniform color, the flood would clear everything.

Further Exploration

  1. Safe first click: Recreate the board after the first click to ensure the player never starts on a mine.
  2. Difficulty levels: Adjust the grid size, mine count, or mine density.
  3. Undo / redo timeline: Store previous cover states so players can move backward and forward through the game history.
  4. Timer: Track and display the completion time.
  5. Flagging mechanic: Use right-click, or another input, to mark suspected mines.
  6. Win condition: Define when the game is complete, such as revealing all safe cells and/or correctly flagging all mines.
  7. Enhanced graphics: Replace raw emoticons with custom cell effects.
  8. Scoring system: Reward safe reveals, correct flags, speed, or efficient play.
  9. Visual hints: Add an option to display cover (the two-tone debug view) or provide other visual aids for learning and accessibility.
  10. Research-inspired feature: After studying one paper from Further Reading, propose and implement one feature inspired by it.

References

Quadrille API

Coding Train Tutorial

Further Reading

  1. Math papers. These papers present Minesweeper as a small mathematical model of constraint reasoning, logical deduction, probability, and computational complexity. Each revealed number constrains the hidden neighboring cells, and the player must reason from those constraints while managing uncertainty. The surprising result is that this reasoning can become computationally very hard, making Minesweeper a useful first example of how familiar games can lead to deep questions about algorithms, logic, and uncertainty.

  2. Research papers. Minesweeper has also been used as a compact experimental platform for studying programming, learning, usability, human-computer interaction, artificial intelligence, sensors, authentication, cognitive science, and quantum-game ideas.

  3. School papers. These papers present Minesweeper as an accessible but rich student project: it can be implemented as a game, analyzed as a computational problem, and extended with solvers based on constraint satisfaction, equations, graphical models, machine learning, flood fill, and reinforcement learning.