Skip to content

Engine ​

The Engine<TState> base class is the state coordinator at the heart of every game.

Usage ​

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

interface GameState {
  player: { x: number; y: number; health: number };
  enemies: Enemy[];
  score: number;
}

class MyEngine extends Engine<GameState> {
  constructor(logger?: any) {
    const initialState: GameState = {
      player: { x: 400, y: 300, health: 100 },
      enemies: [],
      score: 0,
    };
    super(initialState, { logger });
  }

  getHelpers() {
    return {
      movePlayer: (dx: number, dy: number) => {
        const state = this.getState();
        this.setState({
          ...state,
          player: { ...state.player, x: state.player.x + dx, y: state.player.y + dy },
        });
      },

      spawnEnemy: (x: number, y: number) => {
        const state = this.getState();
        this.setState({
          ...state,
          enemies: [...state.enemies, { x, y, health: 50 }],
        });
      },

      addScore: (points: number) => {
        const state = this.getState();
        this.setState({ ...state, score: state.score + points });
      },
    };
  }

  reset() {
    this.setState({
      player: { x: 400, y: 300, health: 100 },
      enemies: [],
      score: 0,
    });
  }
}

API ​

MethodDescription
getState()Returns Readonly<TState>. Spread it into a new object instead of mutating
getHelpers()Abstract. Implement it to return state-mutating functions
reset()Abstract. Implement it to reset to initial state

setState(newState) is protected. It is the helper's tool, not part of the public surface, so callers outside the subclass cannot reach it.

Config ​

typescript
interface EngineConfig {
  logger?: LoggerInterface;
  eventBus?: EventBus<any>;
}

With an eventBus, every setState() emits "state-changed" carrying { state }. That is the hook renderers and UI subscribe to instead of polling getState().

typescript
class MyEngine extends Engine<GameState> {
  constructor(config: EngineConfig = {}) {
    super(initialState, config);
  }
  // ...
}

const eventBus = new EventBus<MyGameEvents>();
const engine = new MyEngine({ logger, eventBus });

eventBus.on("state-changed", ({ state }) => hud.update(state));

See Events.

Quantum state in the engine ​

Quantum handles live in the engine's state, on the objects they describe. Helpers create the handles, run gates on them, and dispose them when their object leaves:

typescript
import { quantum, type Quantum } from "quantum-forge/quantum";

interface Ball {
  id: string;
  x: number;
  y: number;
  exists?: Quantum<boolean>; // no handle: a classical ball that always exists
}

class PongEngine extends Engine<PongState> {
  constructor(logger?: any) {
    super(initialState, { logger });
  }

  reset() {
    for (const ball of this.getState().balls) ball.exists?.dispose();
    this.setState(initialState);
  }

  getHelpers() {
    return {
      enterQuantumZone: (ballId: string) => {
        const state = this.getState();
        const ball = state.balls.find((b) => b.id === ballId);
        if (!ball || ball.exists) return;
        const original = quantum([false, true]).flip(); // exists
        const ghost = quantum([false, true]);           // does not
        original.iSwap(ghost, 0.5);                     // exactly one of them exists
        this.setState({
          ...state,
          balls: [
            ...state.balls.map((b) => (b.id === ballId ? { ...b, exists: original } : b)),
            { ...ball, id: `${ballId}-ghost`, exists: ghost },
          ],
        });
      },

      scoreBall: (ballId: string) => {
        const state = this.getState();
        const ball = state.balls.find((b) => b.id === ballId);
        if (!ball) return;
        const exists = ball.exists ? ball.exists.measure() : true;
        ball.exists?.dispose(); // the twin has already collapsed
        this.setState({
          ...state,
          score: state.score + (exists ? 1 : 0),
          balls: state.balls.filter((b) => b.id !== ballId),
        });
      },
    };
  }
}

A handle is a reference, so a shallow copy of the ball in setState keeps the same handle. Dispose a handle when the object it belongs to leaves the game, or keep the objects in an EntityManager, which disposes them for you. See Lifecycle.

Powered by Quantum Forge