Preview
Loading Lively Minds
Portfolio Entertainment System
Web & AI

Lively Minds

Studio:Playpower LabsRole:Web Game Developer

Retro web game where players chat with AI historical figures, one-on-one or in group conversations. Phaser + React client and FastAPI + Gemini backend, built solo.

Tech Stack

  • Phaser
  • React
  • TailwindCSS
  • Google Gemini

A retro 2D browser game where players talk with AI-powered historical figures. The gameplay is built in Phaser and embedded in a React app that handles UI and progression.

Key Contributions

  • NPC behaviour runs on a modular finite state machine. Every state implements the same IState contract (onEnter, onUpdate, onExit).
  • A reusable StateMachine controller handles all transitions, so each state has a single job.
  • Transitions are triggered by events (interaction, timeouts, collisions) defined as enums, instead of chains of conditions, which keeps them predictable and easy for designers to follow.
  • Movement, animation and timing live in the states; the core engine doesn't need to know about them.
export interface IState {
  stateName: StateEnum;
  onEnter(): void;
  onUpdate(deltaTime: number): void;
  onExit(): void;
}

export enum StateEnum {
  IDLE = "idle",
  WALK = "walk",
  DANCE = "dance",
  TALK = "talk",
  READ = "read",
  WRITE = "write",
}

export enum StateEvent {
  WALK_REQUESTED = "walkRequested",
  TALK_REQUESTED = "talkRequested",
  BOOK_COLLISION = "bookCollision",
  WRITE_REQUESTED = "writeRequested",
  TIMEOUT = "timeout",
}
import { IState } from "./IState";

export class StateMachine<S extends string, E extends string> {
  private transitions: Record<S, Partial<Record<E, S>>>;
  private states: Map<S, IState>;
  private currentState: S | null = null;

  constructor(transitions: Record<S, Partial<Record<E, S>>>) {
    this.transitions = transitions;
    this.states = new Map();
  }

  addState(state: IState & { stateName: S }): this {
    this.states.set(state.stateName, state);
    return this;
  }

  setInitialState(state: S): void {
    this.setState(state);
  }

  private setState(newState: S): void {
    if (this.currentState === newState) return;

    if (this.currentState) this.states.get(this.currentState)?.onExit();
    this.currentState = newState;
    this.states.get(this.currentState)?.onEnter();
  }

  updateState(deltaTime: number): void {
    if (!this.currentState) return;
    this.states.get(this.currentState)?.onUpdate(deltaTime);
  }

  handleEvent(event: E): void {
    if (!this.currentState) return;

    const next = this.transitions[this.currentState]?.[event];
    if (!next) return;

    if (next !== this.currentState) this.setState(next);
  }

  getCurrentState(): S | null {
    return this.currentState;
  }
}

  • An NPC factory spawns characters from JSON: identity, metadata, sprites and quotes. Adding a character needs no code.
  • Random placement with custom collision resolution against other NPCs, the player and the world.
  • Fallbacks for missing data or missing sprites.
[
  {
    "name": "Pythagoras",
    "dates": "570–495 BCE",
    "about": "Pythagoras was an ancient Greek philosopher and mathematician best known for the Pythagorean theorem.",
    "image": "assets/Pythagoras/front_breathe1.png",
    "keyIdeas": ["Everything is number"],
    "quote": "Number rules the universe."
  }
]

  • Hover highlights, focus targeting and click to interact.
  • Name banners and info panels that appear in context.
  • Players start conversations straight from the world map.
  • Players can eavesdrop on and join ongoing NPC-to-NPC conversations.

  • Stateless FastAPI chat service that routes conversations.
  • Google Gemini generates historically accurate dialogue.
  • Multi-character prompts for group conversations, keeping each NPC's personality distinct.
  • The API scales horizontally to serve many game sessions at once.

  • One animation layer shared by the player and NPCs, built on an extensible base interface for playback and transitions.
  • Supports both flat sprites and layered animations for more complex visuals.
  • Kept separate from gameplay and state logic.

  • Built the game client and backend solo.
  • Connected Phaser gameplay to the React UI through React context and event listeners.
  • Prototyped with free assets, then swapped in custom sprite sheets for some of the core characters.