basic state switching from loading to running to editing
This commit is contained in:
@@ -1,13 +1,9 @@
|
||||
export class StateMachine {
|
||||
private _states: Map<string, State> = new Map();
|
||||
private currentState: State;
|
||||
export class StateMachine<T> {
|
||||
private _states: Map<T, State<T>> = new Map();
|
||||
private currentState?: State<T>;
|
||||
|
||||
constructor(states: State[]) {
|
||||
this.currentState = states[0];
|
||||
}
|
||||
|
||||
update(dt: number) {
|
||||
this.currentState.update(dt);
|
||||
update(dt: number, ctx?: CanvasRenderingContext2D) {
|
||||
this.currentState?.update(dt, ctx);
|
||||
}
|
||||
|
||||
get current() {
|
||||
@@ -18,32 +14,37 @@ export class StateMachine {
|
||||
return this._states;
|
||||
}
|
||||
|
||||
addState(state: State) {
|
||||
addState(state: State<T>) {
|
||||
this.states.set(state.name, state);
|
||||
}
|
||||
|
||||
transitionTo(state: State) {
|
||||
if (this.current.canTransitionTo(state)) {
|
||||
transitionTo(state: T) {
|
||||
if (!this.current) {
|
||||
this.currentState = this._states.get(state)!;
|
||||
this.currentState.start();
|
||||
return;
|
||||
}
|
||||
if (this.current?.canTransitionTo(state) && this._states.has(state)) {
|
||||
this.current.stop();
|
||||
this.currentState = state;
|
||||
this.currentState = this._states.get(state)!;
|
||||
this.current.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class State<T> {
|
||||
private stateMachine: StateMachine;
|
||||
protected stateMachine: StateMachine<T>;
|
||||
protected abstract validTransitions: Set<T>;
|
||||
|
||||
abstract readonly name: T;
|
||||
|
||||
constructor(
|
||||
stateMachine: StateMachine,
|
||||
stateMachine: StateMachine<T>,
|
||||
) {
|
||||
this.stateMachine = stateMachine;
|
||||
}
|
||||
|
||||
abstract update(dt: number): void;
|
||||
abstract update(dt: number, ctx?: CanvasRenderingContext2D): void;
|
||||
abstract start(): void;
|
||||
abstract stop(): void;
|
||||
|
||||
@@ -51,3 +52,25 @@ export abstract class State<T> {
|
||||
return this.validTransitions.has(state);
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class ExtensibleState<T> extends State<T> {
|
||||
extensions: Map<string, (...args: unknown[]) => void> = new Map();
|
||||
registerExtension(name: string, cb: (...args: unknown[]) => void) {
|
||||
this.extensions.set(name, cb);
|
||||
}
|
||||
|
||||
constructor(stateMachine: StateMachine<T>) {
|
||||
super(stateMachine);
|
||||
const oldUpdate = this.update;
|
||||
this.update = function (dt: number, ctx?: CanvasRenderingContext2D) {
|
||||
oldUpdate.apply(this, [dt, ctx]);
|
||||
this.runExtensions(dt, ctx);
|
||||
};
|
||||
}
|
||||
|
||||
runExtensions(...args: unknown[]) {
|
||||
for (const [name, cb] of this.extensions) {
|
||||
cb(...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
139
state/states.ts
139
state/states.ts
@@ -1,139 +0,0 @@
|
||||
import { State } from "./machine.ts";
|
||||
|
||||
enum States {
|
||||
LOAD,
|
||||
RUNNING,
|
||||
PAUSED,
|
||||
EDIT_TRACK,
|
||||
EDIT_TRAIN,
|
||||
}
|
||||
|
||||
export class LoadState extends State<States> {
|
||||
override name: States = States.LOAD;
|
||||
override validTransitions: Set<States> = new Set([
|
||||
States.RUNNING,
|
||||
]);
|
||||
|
||||
override update(dt: number): void {
|
||||
throw new Error("Method not implemented.");
|
||||
// TODO
|
||||
// Do nothing
|
||||
}
|
||||
override start(): void {
|
||||
throw new Error("Method not implemented.");
|
||||
// TODO
|
||||
// load track into context
|
||||
// Load trains into context
|
||||
// Load resources into context
|
||||
// Switch to running state
|
||||
}
|
||||
override stop(): void {
|
||||
throw new Error("Method not implemented.");
|
||||
// TODO
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
export class RunningState extends State<States> {
|
||||
override name: States = States.RUNNING;
|
||||
override validTransitions: Set<States> = new Set([
|
||||
States.PAUSED,
|
||||
States.EDIT_TRACK,
|
||||
]);
|
||||
override update(dt: number): void {
|
||||
throw new Error("Method not implemented.");
|
||||
// TODO
|
||||
// Update trains
|
||||
// Update world
|
||||
// Handle input
|
||||
// Draw (maybe via a layer system that syncs with doodler)
|
||||
// Monitor world events
|
||||
}
|
||||
override start(): void {
|
||||
throw new Error("Method not implemented.");
|
||||
// TODO
|
||||
// Do nothing
|
||||
}
|
||||
override stop(): void {
|
||||
throw new Error("Method not implemented.");
|
||||
// TODO
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
export class PausedState extends State<States> {
|
||||
override name: States = States.PAUSED;
|
||||
override validTransitions: Set<States> = new Set([
|
||||
States.LOAD,
|
||||
States.RUNNING,
|
||||
States.EDIT_TRACK,
|
||||
States.EDIT_TRAIN,
|
||||
]);
|
||||
override update(dt: number): void {
|
||||
throw new Error("Method not implemented.");
|
||||
// TODO
|
||||
// Handle input
|
||||
// Draw ui
|
||||
}
|
||||
override start(): void {
|
||||
throw new Error("Method not implemented.");
|
||||
// TODO
|
||||
// Save tracks to cache
|
||||
// Save trains to cache
|
||||
// Save resources to cache
|
||||
}
|
||||
override stop(): void {
|
||||
throw new Error("Method not implemented.");
|
||||
// TODO
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
export class EditTrackState extends State<States> {
|
||||
override name: States = States.EDIT_TRACK;
|
||||
override validTransitions: Set<States> = new Set([
|
||||
States.RUNNING,
|
||||
States.PAUSED,
|
||||
]);
|
||||
override update(dt: number): void {
|
||||
throw new Error("Method not implemented.");
|
||||
// TODO
|
||||
// Handle input
|
||||
// Draw ui
|
||||
// Draw track
|
||||
// Draw track points
|
||||
// Draw track tangents
|
||||
}
|
||||
override start(): void {
|
||||
throw new Error("Method not implemented.");
|
||||
// TODO
|
||||
// Cache trains and save
|
||||
// Stash track in context
|
||||
}
|
||||
override stop(): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
}
|
||||
|
||||
export class EditTrainState extends State<States> {
|
||||
override name: States = States.EDIT_TRAIN;
|
||||
override validTransitions: Set<States> = new Set([
|
||||
States.RUNNING,
|
||||
States.PAUSED,
|
||||
]);
|
||||
|
||||
override update(dt: number): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
override start(): void {
|
||||
throw new Error("Method not implemented.");
|
||||
// TODO
|
||||
// Cache trains
|
||||
// Stash train in context
|
||||
// Draw track
|
||||
// Draw train (filtered by train ID)
|
||||
}
|
||||
override stop(): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
}
|
70
state/states/EditTrackState.ts
Normal file
70
state/states/EditTrackState.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { Vector } from "@bearmetal/doodler";
|
||||
import { getContextItem, setContextItem } from "../../lib/context.ts";
|
||||
import { InputManager } from "../../lib/input.ts";
|
||||
import { TrackSystem } from "../../track/system.ts";
|
||||
import { State, StateMachine } from "../machine.ts";
|
||||
import { States } from "./index.ts";
|
||||
|
||||
export class EditTrackState extends State<States> {
|
||||
override name: States = States.EDIT_TRACK;
|
||||
override validTransitions: Set<States> = new Set([
|
||||
States.RUNNING,
|
||||
States.PAUSED,
|
||||
]);
|
||||
|
||||
private heldEvents: Map<string | number, (() => void) | undefined> =
|
||||
new Map();
|
||||
|
||||
override update(dt: number): void {
|
||||
const inputManager = getContextItem<InputManager>("inputManager");
|
||||
const track = getContextItem<TrackSystem>("track");
|
||||
|
||||
const firstSegment = track.firstSegment;
|
||||
if (firstSegment) {
|
||||
const firstPoint = firstSegment.points[0].copy();
|
||||
const { x, y } = inputManager.getMouseLocation();
|
||||
firstSegment.points.forEach((p, i) => {
|
||||
const relativePoint = Vector.sub(p, firstPoint);
|
||||
p.set(x, y);
|
||||
p.add(relativePoint);
|
||||
});
|
||||
}
|
||||
|
||||
track.draw(true);
|
||||
// TODO
|
||||
// Draw ui
|
||||
// Draw track points
|
||||
// Draw track tangents
|
||||
}
|
||||
override start(): void {
|
||||
const inputManager = getContextItem<InputManager>("inputManager");
|
||||
this.heldEvents.set("e", inputManager.offKey("e"));
|
||||
this.heldEvents.set("Escape", inputManager.offKey("Escape"));
|
||||
inputManager.onKey("e", () => {
|
||||
const state = getContextItem<StateMachine<States>>("state");
|
||||
state.transitionTo(States.RUNNING);
|
||||
});
|
||||
|
||||
const track = getContextItem<TrackSystem>("track");
|
||||
setContextItem("trackCopy", track.copy());
|
||||
inputManager.onKey("Escape", () => {
|
||||
const trackCopy = getContextItem<TrackSystem>("trackCopy");
|
||||
setContextItem("track", trackCopy);
|
||||
const state = getContextItem<StateMachine<States>>("state");
|
||||
state.transitionTo(States.RUNNING);
|
||||
});
|
||||
// TODO
|
||||
// Cache trains and save
|
||||
// Stash track in context
|
||||
}
|
||||
override stop(): void {
|
||||
if (this.heldEvents.size > 0) {
|
||||
for (const [key, cb] of this.heldEvents) {
|
||||
if (cb) {
|
||||
getContextItem<InputManager>("inputManager").onKey(key, cb);
|
||||
}
|
||||
this.heldEvents.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
25
state/states/EditTrainState.ts
Normal file
25
state/states/EditTrainState.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { State } from "../machine.ts";
|
||||
import { States } from "./index.ts";
|
||||
|
||||
export class EditTrainState extends State<States> {
|
||||
override name: States = States.EDIT_TRAIN;
|
||||
override validTransitions: Set<States> = new Set([
|
||||
States.RUNNING,
|
||||
States.PAUSED,
|
||||
]);
|
||||
|
||||
override update(dt: number): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
override start(): void {
|
||||
throw new Error("Method not implemented.");
|
||||
// TODO
|
||||
// Cache trains
|
||||
// Stash train in context
|
||||
// Draw track
|
||||
// Draw train (filtered by train ID)
|
||||
}
|
||||
override stop(): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
}
|
51
state/states/LoadState.ts
Normal file
51
state/states/LoadState.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { bootstrapInputs } from "../../inputs.ts";
|
||||
import { setContextItem } from "../../lib/context.ts";
|
||||
import { InputManager } from "../../lib/input.ts";
|
||||
import { ResourceManager } from "../../lib/resources.ts";
|
||||
import { StraightTrack } from "../../track/shapes.ts";
|
||||
import { TrackSystem } from "../../track/system.ts";
|
||||
import { State } from "../machine.ts";
|
||||
import { States } from "./index.ts";
|
||||
|
||||
export class LoadState extends State<States> {
|
||||
override name: States = States.LOAD;
|
||||
override validTransitions: Set<States> = new Set([
|
||||
States.RUNNING,
|
||||
]);
|
||||
|
||||
override update(): void {
|
||||
// noop
|
||||
}
|
||||
override start(): void {
|
||||
const track = this.loadTrack() ?? new TrackSystem([new StraightTrack()]);
|
||||
setContextItem("track", track);
|
||||
|
||||
const trains = this.loadTrains() ?? [];
|
||||
setContextItem("trains", trains);
|
||||
|
||||
const resources = new ResourceManager();
|
||||
setContextItem("resources", resources);
|
||||
|
||||
const inputManager = new InputManager();
|
||||
setContextItem("inputManager", inputManager);
|
||||
|
||||
bootstrapInputs();
|
||||
|
||||
this.stateMachine.transitionTo(States.RUNNING);
|
||||
}
|
||||
override stop(): void {
|
||||
// noop
|
||||
}
|
||||
|
||||
private loadTrack() {
|
||||
const track = TrackSystem.deserialize(
|
||||
JSON.parse(localStorage.getItem("track") || "[]"),
|
||||
);
|
||||
return track;
|
||||
}
|
||||
|
||||
private loadTrains() {
|
||||
const trains = JSON.parse(localStorage.getItem("trains") || "[]");
|
||||
return trains;
|
||||
}
|
||||
}
|
30
state/states/PausedState.ts
Normal file
30
state/states/PausedState.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { State } from "../machine.ts";
|
||||
import { States } from "./index.ts";
|
||||
|
||||
export class PausedState extends State<States> {
|
||||
override name: States = States.PAUSED;
|
||||
override validTransitions: Set<States> = new Set([
|
||||
States.LOAD,
|
||||
States.RUNNING,
|
||||
States.EDIT_TRACK,
|
||||
States.EDIT_TRAIN,
|
||||
]);
|
||||
override update(dt: number): void {
|
||||
throw new Error("Method not implemented.");
|
||||
// TODO
|
||||
// Handle input
|
||||
// Draw ui
|
||||
}
|
||||
override start(): void {
|
||||
throw new Error("Method not implemented.");
|
||||
// TODO
|
||||
// Save tracks to cache
|
||||
// Save trains to cache
|
||||
// Save resources to cache
|
||||
}
|
||||
override stop(): void {
|
||||
throw new Error("Method not implemented.");
|
||||
// TODO
|
||||
// Do nothing
|
||||
}
|
||||
}
|
32
state/states/RunningState.ts
Normal file
32
state/states/RunningState.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { getContext } from "../../lib/context.ts";
|
||||
import { TrackSystem } from "../../track/system.ts";
|
||||
import { Train } from "../../train/train.ts";
|
||||
import { State } from "../machine.ts";
|
||||
import { States } from "./index.ts";
|
||||
|
||||
export class RunningState extends State<States> {
|
||||
override name: States = States.RUNNING;
|
||||
override validTransitions: Set<States> = new Set([
|
||||
States.PAUSED,
|
||||
States.EDIT_TRACK,
|
||||
]);
|
||||
override update(dt: number): void {
|
||||
const ctx = getContext() as { trains: Train[]; track: TrackSystem };
|
||||
// TODO
|
||||
// Update trains
|
||||
// Update world
|
||||
// Handle input
|
||||
// Draw (maybe via a layer system that syncs with doodler)
|
||||
ctx.track.draw();
|
||||
for (const train of ctx.trains) {
|
||||
train.draw();
|
||||
}
|
||||
// Monitor world events
|
||||
}
|
||||
override start(): void {
|
||||
// noop
|
||||
}
|
||||
override stop(): void {
|
||||
// noop
|
||||
}
|
||||
}
|
26
state/states/index.ts
Normal file
26
state/states/index.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { StateMachine } from "../machine.ts";
|
||||
import { Track } from "../../track.ts";
|
||||
import { EditTrainState } from "./EditTrainState.ts";
|
||||
import { EditTrackState } from "./EditTrackState.ts";
|
||||
import { PausedState } from "./PausedState.ts";
|
||||
import { RunningState } from "./RunningState.ts";
|
||||
import { LoadState } from "./LoadState.ts";
|
||||
|
||||
export enum States {
|
||||
LOAD,
|
||||
RUNNING,
|
||||
PAUSED,
|
||||
EDIT_TRACK,
|
||||
EDIT_TRAIN,
|
||||
}
|
||||
|
||||
export function bootstrapGameStateMachine() {
|
||||
const stateMachine = new StateMachine<States>();
|
||||
stateMachine.addState(new LoadState(stateMachine));
|
||||
stateMachine.addState(new RunningState(stateMachine));
|
||||
stateMachine.addState(new PausedState(stateMachine));
|
||||
stateMachine.addState(new EditTrackState(stateMachine));
|
||||
stateMachine.addState(new EditTrainState(stateMachine));
|
||||
stateMachine.transitionTo(States.LOAD);
|
||||
return stateMachine;
|
||||
}
|
Reference in New Issue
Block a user