Bitboards

A bitboard is a compact way to encode a grid as a single integer, using one bit per cell (1 = filled, 0 = empty). In Quadrille, bitboards are represented with JavaScript BigInt and, by default, arranged in row-major, big-endian order: the most significant bit (MSB) maps to the top-left cell, indices decrease leftward across each row, and continue row by row until the least significant bit (LSB) at the bottom-right.

On an 8×8 chessboard, filling the main diagonal encodes: 2⁶³ + 2⁵⁴ + 2⁴⁵ + 2³⁶ + 2²⁷ + 2¹⁸ + 2⁹ + 2⁰ = 9241421688590303745n. This is the set of cells a bishop standing on that diagonal may reach. The value is symmetric, so it remains unchanged under either endianness.

(press cells to toggle bits and watch the BigInt update)

This chapter teaches

  • How a bitboard encodes a grid as a BigInt
  • How to convert grids to and from bitboards with toBigInt() and related helpers
  • How to apply mutators like shift(dRow, dCol, wrap?) to move patterns around the board
  • How a bitboard-backed Quadrille works as an aggregated layer where every 1-bit becomes a filled cell with the same valid JavaScript value

Knight Jumps

Place a knight anywhere on an 8×8 board and instantly see every square it can reach in one move. Click a cell to position the knight; pulsing dots show the targets. Toggle Wrap to switch between edge clipping and toroidal wrap-around.

code
const COLS = 8;
const ROWS = 8;
Quadrille.cellLength = 50;
Quadrille.tileDisplay = undefined;
let wrap;
let board, knight, jumps;
// Fixed reference cell (origin of all shifts)
const refRow = 4, refCol = 4;
// Reference masks (anchored at 4,4)
let knightMask, jumpsMask;
// Current shift offset relative to reference
let dRow = 0, dCol = 0;

function setup() {
  // Initialize immutable reference masks
  knightMask = createQuadrille(COLS, ROWS, 134217728n, N);
  jumpsMask = createQuadrille(COLS, ROWS, 22136263676928n, bit);
  // Empty chessboard for background
  board = createQuadrille();
  // Initialize shifted layers (start aligned with masks)
  knight = knightMask.clone();
  jumps = jumpsMask.clone();
  createCanvas(COLS * Quadrille.cellLength, ROWS * Quadrille.cellLength);
  // Wrap toggle: toroidal vs. clipped shifting
  wrap = createCheckbox('Wrap');
  wrap.position(3, height + 10);
  wrap.changed(update);
}

function draw() {
  drawQuadrille(board);   // chessboard
  drawQuadrille(knight);  // Knight (shifted)
  drawQuadrille(jumps);   // Knight jumps (shifted)
}

function mousePressed() {
  const row = board.mouseRow;
  const col = board.mouseCol;
  if (board.isValid(row, col)) {
    // Update shift offsets
    dRow = row - refRow;
    dCol = col - refCol;
    // Update the displayed knight and jumps quadrille layers
    update();
  }
}

function update() {
  // Non-destructive shift from reference masks
  knight = Quadrille.shift(knightMask, dRow, dCol, wrap.checked());
  jumps = Quadrille.shift(jumpsMask, dRow, dCol, wrap.checked());
}

// Chess knight symbol (for display)
const N = Quadrille.chessSymbols.get('N');

// 1-bit cell display for jumps
const bit = function ({ row, col }) {
  const l = Quadrille.cellLength;
  const r = (l * 0.6) + (l * 0.1 * sin(millis() / 100));
  push();
  noStroke();
  colorMode(HSB, 360, 100, 100, 255);
  const hue = (row * 45 + col * 25) % 360;
  fill(hue, 85, 95, 220);
  ellipse(l / 2, l / 2, r, r);
  pop();
}

Strategy

  1. Define two immutable reference masks at the reference cell (4, 4): knightMask (the piece) and jumpsMask (its possible targets).
  2. On mouse clicks, compute the offset from (4, 4) and shift the masks to update the displayed knight and jumps quadrille layers.

Try this

  • Compute masks for other chess pieces (rook, bishop, queen, king, pawn) anchored at (4,4), then shift them on clicks.
  • Swap the target display (bit) for other effects (glow, pulse, wiggle) to compare feedback.
  • Ghost overlay. While hovering, color the AND (overlap) in red to preview invalid landings before clicking.
  • Export & replay. After each click, append jumps.toBigInt() to a Timeline and add simple undo/redo keys.
  • Beyond 8×8. Rebuild the same demo on 5×5 or 10×8; confirm that only the stride (board width) changes in your mask math.

Anchoring Masks

The reference masks knightMask and jumpsMask are defined once at the reference cell (4, 4) (the board’s center).

  • A single knight there sets bit 27, corresponding to 2²⁷ = 134217728n.
  • The JUMPS mask marks the eight legal destinations from that square (excluding the knight’s own cell), encoding: 2¹⁰ + 2¹² + 2¹⁷ + 2²¹ + 2³³ + 2³⁷ + 2⁴² + 2⁴⁴ = 22136263676928n
setup() excerpt
// Reference masks (anchored at 4,4)
let knightMask, jumpsMask;

function setup() {
  // Initialize immutable reference masks
  knightMask = createQuadrille(COLS, ROWS, 134217728n, N);
  jumpsMask = createQuadrille(COLS, ROWS, 22136263676928n, bit);
  // ...
}
In JavaScript, BigInt literals must end with n. For example, 134217728n is a BigInt, while 134217728 is a regular Number.

Creating the Quadrille Layers

You may think of a Quadrille built from a bitboard as an aggregated layer where every 1-bit becomes the same value—an emoji, a color, a cell effect, or even an object.

Here the three layers are:

  • board — empty 8×8 background
  • knight — shifted copy of knightMask, initially cloned from it
  • jumps — shifted copy of jumpsMask, initially cloned from it
setup() excerpt
let board, knight, jumps;

function setup() {
  // ...
  // Empty chessboard for background
  board = createQuadrille();
  // Initialize shifted layers (start aligned with masks)
  knight = knightMask.clone();
  jumps = jumpsMask.clone();
  // ...
}

// Chess knight symbol (for display)
const N = Quadrille.chessSymbols.get('N');

// 1-bit cell display for jump targets (animated dots)
const bit = function ({ row, col }) {
  const l = Quadrille.cellLength;
  const r = (l * 0.6) + (l * 0.1 * sin(millis() / 100)); // pulsing radius
  push();
  noStroke();
  colorMode(HSB, 360, 100, 100, 255);
  const hue = (row * 45 + col * 25) % 360; // vary hue by position
  fill(hue, 85, 95, 220);
  ellipse(l / 2, l / 2, r, r);
  pop();
}

The three layers are then rendered in order:

draw()
function draw() {
  drawQuadrille(board);   // chessboard
  drawQuadrille(knight);  // Knight (shifted)
  drawQuadrille(jumps);   // Knight jumps (shifted)
}

Interaction: Shifting by Offset

Click any cell to compute the offset from (4, 4), then shift both masks by (dRow, dCol).

mousePressed()
function mousePressed() {
  const row = board.mouseRow;
  const col = board.mouseCol;
  if (board.isValid(row, col)) {
    // Update shift offsets
    dRow = row - refRow;
    dCol = col - refCol;
    // Update the displayed knight and jumps quadrille layers
    update();
  }
}

The update() function shifts the reference masks to update the displayed knight and jumps quadrille layers.

update()
function update() {
  // Non-destructive shift from reference masks
  knight = Quadrille.shift(knightMask, dRow, dCol, wrap.checked());
  jumps = Quadrille.shift(jumpsMask, dRow, dCol, wrap.checked());
}

From Bits to Quadrilles

Think in layers: precompute masks as BigInt bitboards, then turn them into Quadrilles by assigning each 1-bit a single valid JavaScript value (emoji, color, display function, or object), e.g., createQuadrille(8, 8, mask, '♘'). Once in grid form, combine them with algebraand, or, xor, not, diff—and transform them with mutators such as shift(dRow, dCol, wrap?). The API provides:

Together these methods make bitboards fully interchangeable with Quadrilles, letting you switch seamlessly between binary masks and rich quadrilles.

In high-performance chess engines like Stockfish and integral, bitboards exploit native 64-bit integers in C++ for maximum speed. JavaScript has no fixed 64-bit type, so Quadrille relies on BigInt, which supports arbitrary sizes. Within sketches, Quadrille’s own operations (clone, fill, search, shift) are ergonomic and efficient; use toBigInt() and related methods when you need to import or export bitboards for interoperability with external tools.

References

Further Reading

Bitboards aren’t tied to chess: any w × h board works as long as masks stay consistent with row-major ordering. Patterns, shifts, and searches work the same—only the stride (board width) changes.