Skip to content

Instantly share code, notes, and snippets.

@andrinheusser
Created January 13, 2024 19:24
Show Gist options
  • Save andrinheusser/7e5ddb4f4a5d4efca58ec2dbfd111e9c to your computer and use it in GitHub Desktop.
Save andrinheusser/7e5ddb4f4a5d4efca58ec2dbfd111e9c to your computer and use it in GitHub Desktop.
stateMachineExample
type StateMachineState = string;
type StateMachineStates = readonly StateMachineState[];
interface StateMachineTransition<
State,
Targets extends StateMachineStates,
Context,
> {
from: State | State[];
action: (ctx: Context, extra?: unknown) => Targets[number];
}
const createMachine = <
States extends StateMachineStates,
Context extends Record<string, unknown>,
TransitionNames extends readonly string[],
Transition extends StateMachineTransition<
States[number],
States[number][],
Context
>,
TransitionsObject extends { [key in TransitionNames[number]]: Transition[] },
>(
{
context,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
}: {
states: States;
context: Context;
transitions: TransitionNames;
},
) => {
let tr: TransitionsObject | null = null;
let currentState: States[number] | null = null;
const defineTransitions = (trs: TransitionsObject) => {
tr = trs;
}
const setInitialState = (state: States[number]) => currentState = state;
const getCurrentState = () => currentState;
function event(
transitionName: TransitionNames[number],
) {
if (tr === null) throw new Error("Transitions not defined");
if (currentState === null) throw new Error("Initial state not set");
const ourTransition = tr[transitionName].find((t) =>
typeof t.from === "string"
? t.from === currentState
: t.from.includes(currentState as string)
);
if (ourTransition === undefined) throw new Error("Transition not found");
currentState = ourTransition.action(context);
}
return {
context,
getCurrentState,
defineTransitions,
setInitialState,
currentState,
event: event,
};
};
const states = ["ON", "OFF"] as const;
const transitions = ["toggle", "schroedinger"] as const;
const myMachine = createMachine({
context: { count: 0 },
states,
transitions,
});
myMachine.defineTransitions(
{
"schroedinger": [
{
from: ["ON", "OFF"],
action: (ctx) => {
ctx.count++;
return Math.random() > 0.5 ? "ON" : "OFF";
},
},
],
"toggle": [
{
from: "ON",
action: (ctx) => {
ctx.count++;
return "OFF";
},
},
{
from: "OFF",
action: (ctx) => {
ctx.count++;
return "ON";
},
},
],
},
);
myMachine.setInitialState("ON");
function isValidTransition(input: string): input is typeof transitions[number] {
return transitions.includes(input as typeof transitions[number]);
}
const transitionString = transitions.map((t, i) => `${i}: "${t}"`).join("\n") +
"\n Choice [0]:";
const transitionNames = transitions.map((t) => t);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
while (true) {
console.clear();
console.log({
state: myMachine.getCurrentState(),
count: myMachine.context.count,
});
const send = prompt(transitionString);
if (
send === null || send === "" || isNaN(+send) ||
+send >= transitions.length || +send < 0
) {
console.log("Invalid input");
// eslint-disable-next-line no-await-in-loop
await new Promise((resolve) => {setTimeout(resolve, 750)});
continue;
}
const transitionName = transitionNames[+send];
if (transitionName && isValidTransition(transitionName)) {
myMachine.event(transitionName);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment