yay-machine is a modern, simple, lightweight, zero-dependency, TypeScript state-machine library.
State-machines have many desirable qualities, eg
- they are versatile, and can model many different problem domains
- they are declarative, focusing on the important "what", "when", "how" and "why" questions
- they are concise, conveying a lot of information in a small amount of co-located code
- they are extremely predictable
- and more
Read the Why state-machines? article for more on this.
Unlike other state-machine libraries that are complex and vast, or overly simplistic, yay-machine
gives you power and flexibility without cognitive load.
Read the About yay-machine article for more on this.
npm add yay-machine
// guessMachine.ts
import { defineMachine } from "yay-machine";
interface GuessState {
readonly name:
| "pickNumber"
| "playing"
| "guessedCorrectly"
| "tooManyIncorrectGuesses";
readonly answer: number;
readonly numGuesses: number;
readonly maxGuesses: number;
}
interface GuessEvent {
readonly type: "GUESS";
readonly guess: number;
}
interface NewGameEvent {
readonly type: "NEW_GAME";
}
const incrementNumGuesses = ({
state,
}: {
readonly state: GuessState;
}): GuessState => ({
...state,
numGuesses: state.numGuesses + 1,
});
/**
* Guess the number from 1 to 10
*/
export const guessMachine = defineMachine<
GuessState,
GuessEvent | NewGameEvent
>({
initialState: { name: "pickNumber", answer: 0, numGuesses: 0, maxGuesses: 5 },
states: {
pickNumber: {
always: {
to: "playing",
data: ({ state }) => ({
...state,
answer: Math.ceil(Math.random() * 10),
numGuesses: 0,
}),
},
},
playing: {
on: {
GUESS: [
{
to: "guessedCorrectly",
when: ({ state, event }) => state.answer === event.guess,
data: incrementNumGuesses,
},
{
to: "tooManyIncorrectGuesses",
when: ({ state }) => state.numGuesses + 1 === state.maxGuesses,
data: incrementNumGuesses,
},
{
to: "playing",
data: incrementNumGuesses,
},
],
},
},
guessedCorrectly: {
on: {
NEW_GAME: { to: "pickNumber", data: ({ state }) => state },
},
},
tooManyIncorrectGuesses: {
on: {
NEW_GAME: { to: "pickNumber", data: ({ state }) => state },
},
},
},
});
import assert from "assert";
import { guessMachine } from "./guessMachine";
// create and start a new instance of the guess game machine
const guess = guessMachine.newInstance().start();
assert(guess.state.name === "playing");
// subscribe to the machine's state as it changes
const unsubscribe = guess.subscribe(({ state }) => {
if (state.name === "guessedCorrectly") {
console.log("game over: yay, we won :)");
} else if (state.name === "tooManyIncorrectGuesses") {
console.log("game over: boo, we lost :(");
} else {
return;
}
unsubscribe();
});
// play a single game
while (guess.state.name === "playing") {
guess.send({ type: "GUESS", guess: Math.ceil(Math.random() * 10) });
}