Game Status: Not Started

Open Non Bugged Fullscreen Version

https://gates.opencodingsociety.com/gamify/gategamev2

Architecture — How the Runner Connects Everything
// click any node to inspect
📄
Runner Cell
%%js / gategame.ipynb
🎮
GameControl
GameEnginev1.1/essentials/
🗺️
Level Classes
gategame/levels/
🖱️
Clicker
{ class, data } entry
🌐
gameEnv.stats
shared context
← Click a node above to see how it fits into the architecture.
Game Loop — Update & Draw Cycle
// requestAnimationFrame drives every frame — click a step to follow execution
requestAnimationFrame
GameControl.loop()
object.update()
object.draw()
scheduleNext()
GameControl.js — core loop
loop() {
  for (const obj of this.objects) {
    obj.update();            // physics, timer checks, state
    obj.draw();             // paint to canvas
  }
  requestAnimationFrame(this.loop.bind(this));
}
requestAnimationFrame — GameControl schedules itself to run on the next paint frame (~60fps). The loop is re-registered at the end of every call, keeping it alive until destroy() cancels the frame ID.
Clicker Class — Input → State → Reward
// click a line to see what it does internally
interact() — the user-defined callback
1interact: function(clicks) {
2  const CLICKS_PER_COIN = 3;
3  const MAX_BONUS = 40;
4  if (typeof this._bonusCoinsGiven !== 'number')
5    this._bonusCoinsGiven = 0;
6  if (this._bonusCoinsGiven >= MAX_BONUS) return;
7  if (clicks % CLICKS_PER_COIN === 0) {
8    gameEnv.stats.coinsCollected++;
9    this._bonusCoinsGiven++;
10  }
11}
🎯clicks parameterL1
Passed by the Clicker class after every confirmed canvas hit. It's a running total that increments on each click and only resets when the sprite repositions (Cannonball level) or when the level is torn down.
⚙️CLICKS_PER_COINL2
A local constant — the modulo gate threshold. Changing this is all you need to make rewards easier or harder. It never needs to be stored on the instance because it's read-only config.
🔧Lazy state initL4–5
_bonusCoinsGiven lives on the data object itself. Because interact is a method on that object, this refers to the data object — so the counter survives across calls without any external storage.
🛑Cap guard (early return)L6
Once the cap is reached, interact() becomes a no-op. The Clicker still registers hits and still increments clicks — the guard only prevents the reward branch from firing.
Modulo gateL7
clicks % N === 0 fires on every Nth hit. The Clicker class owns the counter; interact() only inspects it. This keeps the reward logic in the data object, not buried in the class.
🌐gameEnv.stats writeSHARED
gameEnv is the cross-object context owned by GameControl. Writing to stats.coinsCollected here makes the score visible to the HUD, other objects, and the level-completion check — all without direct references between them.
State Management — Instance vs Shared
// two layers: per-object private state and the shared environment
🔒 Instance State — lives on this (data object)
_bonusCoinsGiven Reward counter, private to this Clicker. Persists across calls because this is the data object. local
clicks (in Clicker class) Running click total. Owned by the Clicker class, not the data object. Resets on reposition. local
INIT_POSITION, SCALE_FACTOR Read-only config. Set once in the data object, consumed during draw() but never mutated. local
🌐 Shared State — lives on gameEnv
gameEnv.stats.coinsCollected Writable by any game object. Read by the HUD, the level-completion check, and the next level on load. shared
gameEnv.canvas / ctx The canvas element and 2D rendering context. All draw() calls target this. Replaced when the window resizes. shared
gameEnv.width / height Current canvas dimensions. Used for dynamic positioning (width * 0.5) at level construction time. shared
Level Configs — What Actually Changes Per Level
// same Clicker class, different data objects — switch levels to compare
🪙 Cannonball
🗝️ Escape Room
⚡ Zone Catch
🪙 GameLevelCannonball
clickerData object
const clickerData = {
  id: 'BonusChest',
  src: 'images/.../coin.png',
  SCALE_FACTOR: 30,
  INIT_POSITION: { x: 0.45, y: 0.30 },
  // fractional coords → resolved to px by Clicker
  interact(clicks) {
    const CLICKS_PER_COIN = 3;
    const MAX_BONUS = 40;
    // ... guard + modulo gate
  }
};

this.classes = [{ class: Clicker, data: clickerData }];
Fractional INIT_POSITIONThe x: 0.45, y: 0.30 values are fractions of canvas size. Clicker.draw() multiplies these by gameEnv.width/height to get real pixel coordinates — so the sprite stays proportionally placed on any screen size.
Periodic repositionClicker.update() runs a timer check every frame. After 2800ms, it picks a new random canvas position and resets its internal clicks counter to 0. The interact callback is not involved — the reset happens at the class level before interact is ever called again.
No zIndex neededCannonball is a single-layer level. zIndex is omitted, so the Clicker draws in document order — above the background, below nothing.
🗝️ GameLevelEscaperoom
treasureClickerData object
const treasureClickerData = {
  id: 'HiddenTreasure',
  src: path + '/images/.../mastergate.png',
  SCALE_FACTOR: 22,
  INIT_POSITION: { x: 340, y: 620 },
  // absolute pixel coords — no reposition logic
  zIndex: 400,
  interact(clicks) {
    const CLICKS_PER_COIN = 3;
    const MAX_BONUS = 15;
    // ... guard + modulo gate
  }
};

this.classes = [{ class: Clicker, data: treasureClickerData }];
Absolute pixel INIT_POSITIONUnlike Cannonball, this uses raw pixel values {x:340, y:620}. Clicker detects whether coords are fractional (≤1) or absolute and handles both. Tucked bottom-left means it's hidden until the player explores.
zIndex: 400The Escape Room level stacks multiple layers (dungeon fog, walls, pickups). zIndex tells GameControl's renderer to sort this object above fog layers so it's clickable once found, not buried under a fog layer's canvas region.
No reposition timerupdate() on a static Clicker is near-empty — just bounds checks. The sprite stays fixed for the level's lifetime, so clicks never resets mid-level.
⚡ GameLevelZonecatch
powerCoreClickerData object
const powerCoreClickerData = {
  id: 'PowerCore',
  src: path + '/images/.../mastergate.png',
  SCALE_FACTOR: 18,
  INIT_POSITION: {
    x: Math.round(width * 0.5) - 64,
    y: Math.round(height * 0.12)
  },
  zIndex: 400,
  interact(clicks) {
    const CLICKS_PER_COIN = 4;
    const MAX_BONUS = 20;
    // ... guard + modulo gate
  }
};

this.classes = [{ class: Clicker, data: powerCoreClickerData }];
Dynamic INIT_POSITIONwidth and height come from gameEnv at level construction time. The expression runs once when the Level class is instantiated — not every frame. This centers the sprite on whatever canvas size the browser reports at load.
CLICKS_PER_COIN = 4This is the only difference in reward logic between levels. The Clicker class and interact body are identical — just the threshold constant changes. No subclassing needed.
Adding a new object typeTo extend any level: implement update(), draw(), and destroy() on a new class, then push { class: YourClass, data: yourData } into this.classes. GameControl handles the rest — instantiation, loop registration, and teardown.