Skip to content

First Quantum Game

Build a simple game where objects enter superposition and collapse on click. This tutorial covers the complete flow from setup to quantum measurement.

What We're Building

A field of circles. Click one to put it in quantum superposition (it becomes a ghost at 50% opacity). Click again to measure — it either solidifies or disappears. Entangle two quantum circles and watch how measuring one affects the other.

Step 1: Scaffold

bash
npx quantum-forge init quantum-circles --template starter
cd quantum-circles
npm run dev

Step 2: Define State

Edit src/engine/GameEngine.ts:

typescript
import { Engine } from "@quantum-native/quantum-forge/engine";

interface Circle {
  id: string;
  x: number;
  y: number;
  radius: number;
  color: string;
  isQuantum: boolean;
  existenceProbability: number;
}

export interface GameState {
  circles: Circle[];
  selected: string | null;
}

export class GameEngine extends Engine<GameState> {
  constructor(logger?: any) {
    const circles: Circle[] = [];
    for (let i = 0; i < 8; i++) {
      circles.push({
        id: `circle-${i}`,
        x: 100 + (i % 4) * 150,
        y: 150 + Math.floor(i / 4) * 150,
        radius: 40,
        color: "#a855f7",
        isQuantum: false,
        existenceProbability: 1.0,
      });
    }
    super({ circles, selected: null }, { logger });
  }

  getHelpers() {
    return {
      setQuantum: (id: string, isQuantum: boolean, prob: number) => {
        const state = this.getState();
        const circle = state.circles.find(c => c.id === id);
        if (circle) {
          circle.isQuantum = isQuantum;
          circle.existenceProbability = prob;
          this.setState({ ...state });
        }
      },

      removeCircle: (id: string) => {
        const state = this.getState();
        state.circles = state.circles.filter(c => c.id !== id);
        this.setState({ ...state });
      },

      select: (id: string | null) => {
        const state = this.getState();
        state.selected = id;
        this.setState({ ...state });
      },

      updateProbability: (id: string, prob: number) => {
        const state = this.getState();
        const circle = state.circles.find(c => c.id === id);
        if (circle) {
          circle.existenceProbability = prob;
          this.setState({ ...state });
        }
      },
    };
  }
}

Step 3: Create Quantum Registry

Create src/logic/QuantumRegistry.ts:

typescript
import { QuantumPropertyManager } from "@quantum-native/quantum-forge/quantum";

export class QuantumRegistry extends QuantumPropertyManager {
  constructor(logger?: any) {
    super({ dimension: 2, logger });
  }

  /** Put a circle into superposition */
  makeQuantum(id: string): void {
    const prop = this.acquireProperty();
    const m = this.getModule();
    m.cycle(prop);            // |0⟩ → |1⟩ (exists)
    m.hadamard(prop);         // → 50/50 superposition
    this.setProperty(id, prop);
  }

  /** Entangle two quantum circles */
  entangle(id1: string, id2: string): boolean {
    const p1 = this.getProperty(id1);
    const p2 = this.getProperty(id2);
    if (!p1 || !p2) return false;
    this.getModule().i_swap(p1, p2, 0.5);
    return true;
  }

  /** Get existence probability */
  getProbability(id: string): number {
    const prop = this.getProperty(id);
    if (!prop) return 1.0;
    const results = this.getModule().probabilities([prop]);
    for (const r of results) {
      if (r.qudit_values[0] === 1) return r.probability;
    }
    return 0;
  }

  /** Measure — collapses superposition */
  measure(id: string): number {
    const prop = this.getProperty(id);
    if (!prop) return 1;
    const [value] = this.getModule().measure_properties([prop]);
    this.deleteProperty(id);
    this.releaseProperty(prop, value);
    return value; // 1 = exists, 0 = gone
  }
}

Step 4: Wire It Up

Edit src/main.ts:

typescript
import { ensureLoaded } from "@quantum-native/quantum-forge/quantum";
import { GameLoop } from "@quantum-native/quantum-forge/rendering";
import { GameEngine } from "./engine/GameEngine";
import { QuantumRegistry } from "./logic/QuantumRegistry";

async function main() {
  await ensureLoaded();

  const canvas = document.getElementById("game-canvas") as HTMLCanvasElement;
  const ctx = canvas.getContext("2d")!;
  const engine = new GameEngine();
  const registry = new QuantumRegistry();
  const helpers = engine.getHelpers();

  // Click handler
  canvas.addEventListener("click", (e) => {
    const rect = canvas.getBoundingClientRect();
    const mx = e.clientX - rect.left;
    const my = e.clientY - rect.top;

    const state = engine.getState();
    const clicked = state.circles.find(c => {
      const dx = mx - c.x, dy = my - c.y;
      return dx * dx + dy * dy < c.radius * c.radius;
    });

    if (!clicked) {
      helpers.select(null);
      return;
    }

    if (!clicked.isQuantum) {
      // First click: make quantum
      registry.makeQuantum(clicked.id);
      helpers.setQuantum(clicked.id, true, 0.5);
    } else {
      // Second click: measure
      const exists = registry.measure(clicked.id);
      if (exists === 1) {
        helpers.setQuantum(clicked.id, false, 1.0);
      } else {
        helpers.removeCircle(clicked.id);
      }
    }
  });

  // Render loop
  const loop = new GameLoop({
    update: () => {
      // Update probabilities for quantum circles
      const state = engine.getState();
      for (const circle of state.circles) {
        if (circle.isQuantum && registry.hasProperty(circle.id)) {
          const prob = registry.getProbability(circle.id);
          helpers.updateProbability(circle.id, prob);
        }
      }
    },
    render: () => {
      const state = engine.getState();
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      ctx.fillStyle = "#06080c";
      ctx.fillRect(0, 0, canvas.width, canvas.height);

      for (const c of state.circles) {
        ctx.globalAlpha = c.isQuantum ? c.existenceProbability * 0.8 + 0.2 : 1.0;
        ctx.fillStyle = c.isQuantum ? "#06b6d4" : c.color;
        ctx.beginPath();
        ctx.arc(c.x, c.y, c.radius, 0, Math.PI * 2);
        ctx.fill();

        // Label
        ctx.globalAlpha = 1;
        ctx.fillStyle = "#e8edf4";
        ctx.font = "14px Oxanium, sans-serif";
        ctx.textAlign = "center";
        const label = c.isQuantum
          ? `${(c.existenceProbability * 100).toFixed(0)}%`
          : "click me";
        ctx.fillText(label, c.x, c.y + 5);
      }
    },
    targetFps: 60,
  });

  loop.start();
}

main().catch(console.error);

Step 5: Try It

bash
npm run dev
  1. Click a circle — it turns cyan and shows "50%" (in superposition)
  2. Click a quantum circle — it either solidifies (exists!) or vanishes (ghost!)
  3. Observe that entangled pairs (if you add entanglement) collapse together

Step 6: Add Entanglement

Extend the click handler to support shift-click entanglement:

typescript
canvas.addEventListener("click", (e) => {
  // ... find clicked circle ...

  if (e.shiftKey && clicked.isQuantum) {
    const selected = state.selected;
    if (selected && selected !== clicked.id) {
      const success = registry.entangle(selected, clicked.id);
      if (success) {
        helpers.select(null);
        // Update probabilities — they may have changed
      }
    } else {
      helpers.select(clicked.id);
    }
    return;
  }

  // ... existing click logic ...
});

Now shift-click two quantum circles to entangle them. Measuring one will collapse both.

What You've Learned

  • QuantumPropertyManager manages the quantum lifecycle
  • m.cycle() + m.hadamard() creates superposition from a definite state
  • m.measure_properties() collapses superposition probabilistically
  • releaseProperty() pools for reuse (prevents qudit limit)
  • m.i_swap() creates entanglement — measuring one affects the other
  • m.probabilities() reads the quantum state without collapsing it
  • All WASM gates are called via getModule() (snake_case names)

Powered by Quantum Forge