Created
January 13, 2024 19:24
-
-
Save andrinheusser/7e5ddb4f4a5d4efca58ec2dbfd111e9c to your computer and use it in GitHub Desktop.
stateMachineExample
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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