SWIsland Class Reference

Documenting SWIsland & Coordinates — the JavaScript mirrors of Island.java & Coordinates.java

Overview

SWIsland and Coordinates are the two JavaScript classes introduced in Stage 7 of the SWIsland Recursion Saga. They encapsulate the island-building algorithm that was previously spread across six standalone sketch functions, moving it into reusable, self-contained classes that directly mirror the Java source taught in APCS A.

Before Stage 7, the sketch handled seed selection, adjacency testing, recursive growth, and step recording all inline. Stage 7 extracts all of that logic into the two classes below, so the sketch only handles orchestration and drawing. Functionally, the output is identical to Stage 6b — the refactoring is entirely architectural.

Coordinates

A tiny immutable value object storing a single (row, col) position in the ocean grid.

  • Source: shapeClasses/coordinates.js
  • Mirrors: Coordinates.java
  • Cells in SWIsland._cells[] are Coordinates instances.
SWIsland

Represents a single island grown by recursive backtracking. Holds all cells, the step queue, and the three-rule validity check.

  • Source: shapeClasses/swIsland.js
  • Mirrors: Island.java
  • Entry point: static SWIsland.buildIsland()

Java Connection

The SWIsland Recursion Saga runs in parallel with a Java project studied in APCS A. The JavaScript classes mirror the Java source one-to-one so you can compare the two languages side-by-side using the same algorithm.

Java class / method JavaScript equivalent Notes
Coordinates.java coordinates.js → Coordinates Identical constructor, accessors, and toString()
Island.java swIsland.js → SWIsland Same rules, same direction arrays, same method names
Island.contains() SWIsland.contains(row, col) Uses Coordinates instances instead of int arrays
Island.isValidExpansion() SWIsland.isValidExpansion(row, col, ocean, numRows, numCols) Same 3 rules; instance method so Rule 3 can call contains()
Island.grow() SWIsland.grow(targetSize, ocean, numRows, numCols, useRandom, maxSteps) JS adds step recording and a browser-safety abort limit
Island.buildIsland() SWIsland.buildIsland(ocean, targetSize, numRows, numCols, useRandom, maxSteps) JS adds a BFS reachability pre-check (Step 3)
Island.toString() SWIsland.toString() Identical output format: SWIsland [size=5]: (3, 4) -> ...
Island.DR4 / DC4 / DR8 / DC8 SWIsland.DR4 / DC4 / DR8 / DC8 JavaScript static class fields; same values

Coordinates Class

Coordinates is a small immutable value object that stores a single (row, col) position. Every cell in an SWIsland is a Coordinates instance.

class Coordinates { // constructor — throws if row or col is negative constructor(row, col) { ... } getRow() // → number getCol() // → number toString() // → "(row, col)" e.g. "(3, 5)" }
Kind Signature Description
instance new Coordinates(row, col) Creates a coordinate. Throws Error if either value is negative.
instance getRow() → number Returns the zero-based row index.
instance getCol() → number Returns the zero-based column index.
instance toString() → string Returns "(row, col)" — exactly matches Coordinates.toString() in Java.
Usage
const c = new Coordinates(3, 5); c.getRow(); // → 3 c.getCol(); // → 5 c.toString(); // → "(3, 5)"

SWIsland Class

SWIsland represents a single island grown by recursive backtracking. It stores its cells as Coordinates instances, enforces the three-rule expansion validity check, and records every ADD / BACKTRACK decision into an internal step queue for animation replay.

Static Constants

These four direction arrays are static fields and mirror the static final int[] arrays in Island.java exactly.

// Orthogonal only — used in grow() to collect expansion candidates SWIsland.DR4 = [-1, 1, 0, 0]; // row offsets: UP, DOWN, LEFT, RIGHT SWIsland.DC4 = [ 0, 0, -1, 1]; // col offsets // All 8 directions — used in isValidExpansion() Rule 3 and buildIsland() seed check SWIsland.DR8 = [-1, -1, -1, 0, 0, 1, 1, 1]; SWIsland.DC8 = [-1, 0, 1, -1, 1, -1, 0, 1];

Constructor

Kind Signature Description
instance new SWIsland(startCell) Creates a new island seeded with one land cell. startCell must be a Coordinates instance and must already be stamped as land (ocean[r][c] = 1) by the caller before construction. Mirrors Island(Coordinates startCell) in Java.

Instance Methods

Kind Signature Description
instance getCells() → Coordinates[] Returns the array of land cells. Mirrors Island.getCells().
instance getSize() → number Returns the number of land cells. Mirrors Island.getSize().
JS only getStepQueue() → {type, row, col}[] Returns the ADD / BACKTRACK step array recorded by grow(). Each element is { type: 'ADD' | 'BACKTRACK', row: number, col: number }. No Java equivalent.
JS only wasAborted() → boolean Returns true if grow() exceeded the maxSteps safety limit. No Java equivalent.
instance contains(row, col) → boolean Returns true if this island already owns (row, col). Used by isValidExpansion() Rule 3. Mirrors Island.contains().
instance isValidExpansion(row, col, ocean, numRows, numCols) → boolean Returns true only if all three rules pass:
  1. In bounds(row, col) is inside the ocean grid.
  2. Open waterocean[row][col] === 0 (pits and land both fail).
  3. No conflict — none of the 8 surrounding neighbors is land belonging to a different island.
Mirrors Island.isValidExpansion().
instance grow(targetSize, ocean, numRows, numCols, useRandom, maxSteps) → boolean Recursively grows this island to targetSize cells. Mutates ocean during growth; restores it on backtrack. Records every ADD and BACKTRACK step in this._stepQueue. Returns true on success, false on failure. Mirrors Island.grow(); see The Algorithm for the full breakdown.
instance toString() → string Returns a human-readable description of the island.
Example: "SWIsland [size=5]: (3, 4) -> (3, 5) -> (4, 5) -> (5, 5) -> (5, 4)"
Mirrors Island.toString() — matches console output from RecursiveIslandDriver.java.

Static Methods

Kind Signature Description
static SWIsland.buildIsland(ocean, targetSize, numRows, numCols, useRandom, maxSteps) → SWIsland|null Primary entry point. Tries random seed positions until it finds one where grow() succeeds. Returns the finished island, an aborted partial island, or null if all positions are exhausted. Mirrors Island.buildIsland() with a BFS reachability pre-check added. See Usage Example.
static JS only SWIsland.countReachableCells(seedRow, seedCol, ocean, numRows, numCols) → number BFS flood-fill from the (already-stamped) seed. Returns the count of open-water cells reachable orthogonally. Used internally by buildIsland() to fast-fail seeds with insufficient reachable space before recursion begins.

Separation Rule

The fundamental constraint that isValidExpansion() enforces:

No two islands may touch — not even at a corner.
Every island must be separated from every other island by at least one open-water cell in all 8 surrounding directions.

This rule is enforced at two points:

  • During growth — isValidExpansion() Rule 3: Before adding any candidate cell, all 8 of its neighbors are checked. If any neighbor is land (ocean === 1) belonging to a different island, the candidate is rejected.
  • During seed placement — buildIsland() Step 2: A seed position is rejected if any of its 8 neighbors is already occupied by existing island land, preventing a new island from starting too close to an existing one.

The stricter 8-direction rule (introduced in Stage 6b over Stage 6a's 4-direction rule) measurably increases backtrack cost — the algorithm has fewer valid expansion paths available at each step.

Direction Arrays

Two pairs of parallel offset arrays drive all neighbor scanning. Indexing DR4[i] and DC4[i] together gives one orthogonal step; DR8[i] and DC8[i] give one of the 8 surrounding cells.

DR4 / DC4 — Orthogonal (used in grow())
↑ UP
← LEFT
(r, c)
RIGHT →
↓ DOWN

4 orthogonal neighbors only

SWIsland.DR4 = [-1, 1, 0, 0]; // up, down, left, right SWIsland.DC4 = [ 0, 0, -1, 1];
DR8 / DC8 — All 8 (used in isValidExpansion() & seed check)
(r, c)

All 8 surrounding cells

SWIsland.DR8 = [-1,-1,-1, 0, 0, 1, 1, 1]; SWIsland.DC8 = [-1, 0, 1,-1, 1,-1, 0, 1];

The Algorithm — grow()

grow() is a classic recursive backtracking function. Each call either commits to adding a new cell or undoes its choice and tries another.

grow(targetSize, ocean, numRows, numCols, useRandom, maxSteps): // BASE CASE — island is complete if cells.length >= targetSize → return true // Collect all valid orthogonal neighbors (passes isValidExpansion, no duplicates) candidates ← [ valid neighbors via DR4/DC4 ] if candidates is empty → return false // dead end // Shuffle (organic shape) or sort (deterministic) if useRandom → Fisher-Yates shuffle else → sort top-to-bottom, left-to-right for each [r, c] in candidates: ADD: cells.push( new Coordinates(r, c) ); ocean[r][c] = 1; stepQueue ← 'ADD' RECURSE: if grow(...) → return true // success! propagate up BACKTRACK: cells.pop(); ocean[r][c] = 0; stepQueue ← 'BACKTRACK' return false // all candidates exhausted — signal backtrack to caller
Base Case

When cells.length >= targetSize, the island has enough cells. Return true to propagate success all the way up the call stack.

ADD Step

Tentatively commit to a candidate cell: push it onto _cells, stamp ocean[r][c] = 1, and record { type:'ADD', row, col } in the step queue.

BACKTRACK Step

If recursion from the added cell returned false, undo: pop _cells, restore ocean[r][c] = 0, and record { type:'BACKTRACK', row, col }.

Browser Safety: maxSteps

grow() checks this._stepQueue.length after each ADD. If the queue reaches maxSteps (default 150 000), it sets this._buildAborted = true and returns false immediately, preventing the browser thread from hanging on adversarial ocean layouts. The sketch displays a TOO SLOW warning in this case. This guard has no equivalent in the Java version.

Step Queue

The step queue is a JavaScript-only addition to the algorithm — Island.java has no equivalent. Its purpose is to record every decision grow() makes so the sketch can replay the algorithm as a frame-by-frame animation after the synchronous build phase completes.

Each element in the queue is a plain object:

// ADD — a cell was tentatively committed { type: 'ADD', row: number, col: number } // BACKTRACK — that commit was undone (path failed) { type: 'BACKTRACK', row: number, col: number }

The sketch calls island.getStepQueue() after buildIsland() returns and splices those steps between ISLAND_START and ISLAND_SUCCESS markers in the global animation queue. The animation then replays them at the chosen speed (1–10 frames per step) so you can watch ADD flashes and BACKTRACK flashes in real time.

Global Step Types (sketch-level)

In addition to the 'ADD' and 'BACKTRACK' steps from SWIsland, the sketch inserts marker steps into the merged queue:

Step typeSourceMeaning
'ADD'SWIsland.grow()A candidate cell was tentatively added to the island.
'BACKTRACK'SWIsland.grow()The previous ADD failed; the cell was removed and the ocean restored.
'ISLAND_START'sketchA new island's animation is beginning; sets up color and seed display.
'ISLAND_SUCCESS'sketchIsland reached target size; logs island.toString() to the breadcrumb panel.
'ISLAND_FAIL'sketchNo valid seed position was found; island skipped.
'TOOSLOW'sketchgrow() hit maxSteps; browser-safety abort.
'ALL_DONE'sketchAll islands have been processed; animation enters the DONE state.

Usage Example

The typical call pattern from swIsland7Sketch.js:

// 1. Build a 10×15 ocean grid (all zeros = open water) const ROWS = 10, COLS = 15; const ocean = Array.from({ length: ROWS }, () => Array(COLS).fill(0)); // 2. Optionally mark pit cells (impassable) as ocean[r][c] = 2 ocean[3][7] = 2; // example pit // 3. Call buildIsland() — the static factory handles seed search + grow() const island = SWIsland.buildIsland(ocean, 8, ROWS, COLS, true, 150_000); if (island === null) { console.log("No room for another island."); } else if (island.wasAborted()) { console.log("Build aborted — too slow."); } else { // 4. Log the island (matches RecursiveIslandDriver.java output) console.log(island.toString()); // → "SWIsland [size=8]: (2, 3) -> (2, 4) -> (3, 4) -> ..." // 5. Retrieve the animation step queue for replay const steps = island.getStepQueue(); console.log(`Steps recorded: ${steps.length} (ADDs + BACKTRACKs)`); // 6. Inspect individual cells for (const cell of island.getCells()) { console.log(cell.toString()); // → "(2, 3)", "(2, 4)", ... } }
Checking validity manually
// Ask whether (4, 6) is a legal expansion for an existing SWIsland instance const ok = island.isValidExpansion(4, 6, ocean, ROWS, COLS); console.log(ok); // true / false // Check how much open water is reachable from a candidate seed (must stamp first) ocean[5][5] = 1; // temporarily stamp as land const reach = SWIsland.countReachableCells(5, 5, ocean, ROWS, COLS); ocean[5][5] = 0; // restore console.log(`Reachable cells: ${reach}`);

Java vs. JavaScript Comparison

The table below highlights every deliberate difference between the Java and JavaScript implementations. Where a cell says "identical", the logic is a direct port.

Feature Java (Island.java) JavaScript (SWIsland)
Cell representation Coordinates objects (int fields) Coordinates instances (JS class) — identical API
Direction arrays static final int[] static class fields — identical values
isValidExpansion() rules Rules 1, 2, 3 — identical Rules 1, 2, 3 — identical
grow() backtracking Identical recursive structure Identical + step recording + maxSteps abort
Step queue Not present JS only this._stepQueue records every ADD and BACKTRACK for animation
Browser safety abort Not needed (JVM handles it) JS only maxSteps = 150,000 guard prevents browser hang
BFS reachability pre-check Not present JS only countReachableCells() inside buildIsland() fast-fails impossible seeds
toString() output SWIsland [size=N]: (r, c) -> ... Identical — matches RecursiveIslandDriver.java console output
Ocean grid type int[][] number[][] — semantically identical (0 = water, 1 = land, 2 = pit)