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 devStep 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- Click a circle — it turns cyan and shows "50%" (in superposition)
- Click a quantum circle — it either solidifies (exists!) or vanishes (ghost!)
- 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
QuantumPropertyManagermanages the quantum lifecyclem.cycle()+m.hadamard()creates superposition from a definite statem.measure_properties()collapses superposition probabilisticallyreleaseProperty()pools for reuse (prevents qudit limit)m.i_swap()creates entanglement — measuring one affects the otherm.probabilities()reads the quantum state without collapsing it- All WASM gates are called via
getModule()(snake_case names)