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 bydrawQuadrille
(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
- Reusing one instance
const entity = new Entity(game, random(emojis), row, col);
game.rand(cols, entity); // ❌ all cells share *the same object*
- 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
- JavaScript Classes (MDN) — Syntax, constructors, methods, and inheritance basics.
- Inheritance in JavaScript (MDN) — How JavaScript supports prototypal and class-based inheritance.
- p5.js Reference — Core functions for drawing, interaction, and object handling.
- Quadrille API — see entries for
fill
,clear
,drawQuadrille
, and mouse helpers.
Further Reading
- Object-oriented programming (Wikipedia) — General overview of classes, instances, and encapsulation.
- Design by contract — The idea of requiring objects to meet specific contracts (as used here with
display
). - Entity–Component–System (Wikipedia) — A popular alternative to deep class hierarchies in games; useful when entities gain many attributes (AI, animation, inventory, etc.).
- Role-playing video game — Context for RPG-inspired mechanics built from entities on grids.
- Turn-based strategy — Broader game genre that benefits from cell objects, movement rules, and selection invariants.
🧩 Try this
- Define several entity classes like
Creature
andTonic
, 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.