Cell Objects

In this chapter we take a key step: instead of storing plain values or display functions in grid cells, we place objects. Each object combines its own state (such as position, type, or stats) with behavior (methods for drawing, moving, or interacting). In JavaScript, as in most modern languages, a class defines the blueprint for such objects through its constructor, while the new operator instantiates them. This turns the grid into more than a board—it becomes a world of entities that act, react, and evolve, paving the way to richer gameplay in genres such as turn-based strategy, tile-matching, life simulation, or role-playing.

A minimal constructor scaffold for a Quadrille game looks like this:

constructor(game, row, col, ...otherParams) {
  this.game = game;
  this.row = row;
  this.col = col;
  this.display = field; // property or method
  // otherParams
}

Here, game is the Quadrille where the object lives; row and col locate it in that grid. If the object defines a display field, drawQuadrille will render it like any other cell value. If display is a method, it follows the same display contract as a cell display function.

ℹ️

This chapter teaches

  • Classes & instances (JS class, constructor, methods)

  • State & behavior (properties vs. methods)

  • Contracts with Quadrille

    • Quadrille.factory for object placement
    • Object display field honored by drawQuadrille (property or method)
  • Class diagrams to design and visualize how entities are organized

  • Mouse interaction with invariants

From Cell Effects to Entities

In the previous chapter, you worked with functional displays. Here, each cell can instead hold an Entity instance with its own state, behavior, and visual appearance.

(click to select and move pieces; press any key to reset the board)

code
Quadrille.cellLength = 50;
const cellLength = Quadrille.cellLength;
const cols = 8, rows = 8;
let game;
let selected = null;

// === BASE ENTITY ===
// Generic grid object with a symbol and position.
class Entity {
  constructor(game, emoji, row, col) {
    this.game = game;
    this.display = emoji;
    this.row = row;
    this.col = col;
  }

  // Move if target cell is empty
  move(newRow, newCol) {
    if (this.game.isEmpty(newRow, newCol)) {
      this.game.fill(newRow, newCol, this);
      this.game.clear(this.row, this.col);
      this.row = newRow;
      this.col = newCol;
      return true;
    }
    return false;
  }
}

function setup() {
  createCanvas(cols * cellLength, rows * cellLength);
  reset();
}

function draw() {
  background('white');
  drawQuadrille(game, { options: { origin: CENTER } });
}

function mousePressed() {
  const row = game.mouseRow;
  const col = game.mouseCol;
  const cell = game.read(row, col);
  if (!selected) {               // first click: pick entity (or null)
    selected = cell;
    return;
  }
  if (selected === cell) {
    selected = null;             // Deselect current piece
    return;
  }
  if (selected.move(row, col)) { // moved: clear selection
    selected = null;
    return;
  }
  selected = cell;               // failed move: maybe switch or deselect
}

function keyPressed() {
  reset();
}

function reset() {
  game = createQuadrille(cols, rows);
  const emojis = ['🧝', '👺', '💊'];
  const entity = Quadrille.factory(({ row, col }) =>
    new Entity(game, random(emojis), row, col)
  );
  game.rand(cols, entity);
  selected = null;
}

The Entity class follows the minimal scaffold and adds a move method: the selected object moves only if the target cell is empty, with fill and clear updating both the board and its coordinates. The following class diagram visualizes the Entity class, with state (grid reference, emoji, position) and behavior (display, movement) forming a minimal yet functional game entity.

  classDiagram
  class Entity {
    +Quadrille game
    +string emoji
    +number row
    +number col
    +display() void
    +move(newRow number, newCol number) boolean
  }

Placing Objects with Quadrille.factory

Quadrille.factory(fn) builds a creator function that runs once per cell and stores the object it returns, not the function itself.

reset()
function reset() {
  game = createQuadrille(cols, rows);
  const emojis = ['🧝', '👺', '💊'];
  const entity = Quadrille.factory(({ row, col }) =>
    new Entity(game, random(emojis), row, col)
  );
  game.rand(cols, entity);
  selected = null;
}

Without a factory (explicit loop) — equivalent but longer:

reset()
function reset() {
  game = createQuadrille(cols, rows);
  const emojis = ['🧝', '👺', '💊'];
  let placed = 0;
  while (placed < cols) {
    const row = int(random(rows));
    const col = int(random(cols));
    if (game.isEmpty(row, col)) {
      const entity = new Entity(game, random(emojis), row, col);
      game.fill(row, col, entity);
      placed++;
    }
  }
  selected = null;
}

Common pitfalls

  1. Reusing one instance
const entity = new Entity(game, random(emojis), row, col);
game.rand(cols, entity); // ❌ all cells share *the same object*
  1. Passing a plain function (no factory)
const entity = ({ row, col }) => new Entity(game, random(emojis), row, col);
game.rand(cols, entity); // ❌ stores the function, not its return value

✅ Use Quadrille.factory(fn) so each placement calls your function and stores its result.

🧩 Try this

  • Use different emojis by row/col or assign a random class field.
  • Add a health stat, reduce it on clicks, and draw it in the cell.

Turning display into a Method

To make entities more interactive, change display from a property (just holding the emoji) to a method that draws the emoji and adds a visual effect when the piece is selected.

(click to select and move highlighted pieces; press any key to reset.)

code
// same variables as before
class Entity {
  constructor(game, emoji, row, col) {
    this.game = game;
    this.emoji = emoji;
    this.row = row;
    this.col = col;
  }

  // Draw emoji and highlight if selected
  display() {
    textAlign(CENTER, CENTER);
    textSize(cellLength * 0.5);
    text(this.emoji, 0, 0);
    if (selected === this) {
      noStroke();
      fill(255, 255, 0, 80);
      rectMode(CENTER);
      rect(0, 0, cellLength, cellLength);
    }
  }

  move(newRow, newCol) {
    if (this.game.isEmpty(newRow, newCol)) {
      this.game.fill(newRow, newCol, this);
      this.game.clear(this.row, this.col);
      this.row = newRow;
      this.col = newCol;
      return true;
    }
    return false;
  }
}

The new display() method follows the cell display contract: it renders the emoji and highlights the cell if this is the selected object. The move() method is unchanged—it still updates both the board and the entity’s coordinates using fill and clear.

You can also extend display({ row, col }) with positional information. For example, you might highlight border cells when entities reach the grid’s edge:

(click to select and move pieces with edge highlights; press any key to reset.)

🧩 Try this

  • Extend display() to highlight border cells when entities reach the grid’s edge.
  • Update the class diagram to show display({ row, col }) with its parameter object.

Mouse Interaction as Invariants

The click logic stays simple by enforcing a single invariant: selected is always either null or a cell object.

With that rule in place, every mouse press reduces to one of four cases: select, deselect, move, or switch.

mousePressed()
function mousePressed() {
  const row = game.mouseRow;
  const col = game.mouseCol;
  const cell = game.read(row, col);
  if (!selected) {               // first click: pick entity (or null)
    selected = cell;
    return;
  }
  if (selected === cell) {
    selected = null;             // Deselect current piece
    return;
  }
  if (selected.move(row, col)) { // moved: clear selection
    selected = null;
    return;
  }
  selected = cell;               // failed move: maybe switch or deselect
}

🧩 Try this

  • Add a “wait” flag so pieces can’t move twice in a row.
  • Extend display({ row, col }) to preview a range aura (e.g. within Manhattan distance ≤ 2).

References

Further Reading

🧩 Try this

  • Define several entity classes like Creature and Tonic, each with its own constructor and rendering logic.
  • Place instances of these different classes on the same board. Notice they must be handled independently.
    → This contrast sets the stage for the next chapter on Polymorphic Cell Objects, where inheritance abstracts heterogeneous entities under a single type.