Adam L Barrett PRO
Adam L Barrett is a JavaScript and Front-End consultant, a contributor to open source, and avid bike-shedder.
An abstract machine that can be in exactly one of a finite number of states at any given time
The FSM can change from one state to another in response to some external inputs; the change from one state to another is called a transition
An FSM is defined by a list of its states, its initial state, and the conditions for each transition.
A state is a description of the status of a system that is waiting to execute a transition
Critical Hit
Roll
Double Click
Do Nothing
Use Tech
Check HP
Idle
Tech Effect
Attack
Play Attack
Play Special Attack
Play Destroyed
async function attack(robot, target) {
if (!robot.isIdle) {
return;
}
const roll = engine.rollAttack(robot);
if (roll.isCritical) {
await SpecialAttackAnimation(robot);
} else {
await AttackAnimation(robot);
}
robot.power > 0 ? PoweredIdleAnimation(robot) : IdleAnimation(robot);
}
async function useTech(robot, tech) {
if (!robot.isIdle) {
return;
}
const roll = engine.useTech(robot, tech);
await TechAnimation(robot, tech);
if (robot.hp <= 0) {
await DestroyedAnimation(robot);
} else {
robot.power > 0 ? PoweredIdleAnimation(robot) : IdleAnimation(robot);
}
}
async function attack(robot, target) {
if (!robot.isIdle) {
return;
}
const roll = engine.rollAttack(robot);
if (roll.isCritical) {
await SpecialAttackAnimation(robot);
} else {
await AttackAnimation(robot);
}
robot.power > 0 ? PoweredIdleAnimation(robot) : IdleAnimation(robot);
}
async function useTech(robot, tech) {
if (!robot.isIdle) {
return;
}
const roll = engine.useTech(robot, tech);
await TechAnimation(robot, tech);
if (robot.hp <= 0) {
await DestroyedAnimation(robot);
} else {
robot.power > 0 ? PoweredIdleAnimation(robot) : IdleAnimation(robot);
}
}
Idle
Attacking
Critical Hit
Roll
Special
Attacking
Use Tech
Check HP
Destroyed
Using Tech
Attack
Powered Idle
export let robotAttackMachine = {
state: 'idle',
robot: { atk: 1, def: 0, hp: 1, power: 0 },
dispatch(event) {
},
states: {
idle: {},
attacking: {},
specialAttacking: {},
usingTech: {},
destroyed: {},
},
};
export let robotAttackMachine = {
state: 'idle',
robot: { atk: 1, def: 0, hp: 1, power: 0 },
dispatch(event) {
const state = this.states[this.state];
const transition = state.on[event.type];
if (typeof transition === 'function') {
transition.call(this, event);
} else if (transition in this.states) {
this.state = transition;
}
},
states: {
idle: {},
attacking: {},
specialAttacking: {},
usingTech: {},
destroyed: {},
},
};
export let robotAttackMachine = {
state: 'idle',
robot: { atk: 1, def: 0, hp: 1, power: 0 },
dispatch(event) {
const state = this.states[this.state];
const transition = state.on[event.type];
if (typeof transition === 'function') {
transition.call(this, event);
} else if (transition in this.states) {
this.state = transition;
}
},
states: {
idle: {},
attacking: {
on: {
ANIMATION_END: 'idle',
},
},
specialAttacking: {
on: {
ANIMATION_END: 'idle',
},
},
usingTech: {
on: {
ANIMATION_END: 'idle',
},
},
destroyed: {},
},
};
export let robotAttackMachine = {
state: 'idle',
robot: { atk: 1, def: 0, hp: 1, power: 0 },
dispatch(event) {
const state = this.states[this.state];
const transition = state.on[event.type];
if (typeof transition === 'function') {
transition.call(this, event);
} else if (transition in this.states) {
this.state = transition;
}
},
states: {
idle: {},
attacking: {
on: {
ANIMATION_END: 'idle',
},
},
specialAttacking: {
on: {
ANIMATION_END: 'idle',
},
},
usingTech: {
on: {
ANIMATION_END: 'idle',
},
},
destroyed: {
type: 'final',
},
},
};
export let robotAttackMachine = {
state: 'idle',
robot: { atk: 1, def: 0, hp: 1, power: 0 },
dispatch(event) {
const state = this.states[this.state];
if (state.type === 'final') {
return;
}
const transition = state.on[event.type];
if (typeof transition === 'function') {
transition.call(this, event);
} else if (transition in this.states) {
this.state = transition;
}
},
states: {
idle: {},
attacking: {
on: {
ANIMATION_END: 'idle',
},
},
specialAttacking: {
on: {
ANIMATION_END: 'idle',
},
},
usingTech: {
on: {
ANIMATION_END: 'idle',
},
},
destroyed: {
type: 'final',
},
},
};
function attack(event) {
const roll = engine.roll(event.robot);
if (roll.isCritical) {
this.state = 'specialAttacking';
} else {
this.state = 'attacking';
}
}
export let robotAttackMachine = {
state: 'idle',
robot: { atk: 1, def: 0, hp: 1, power: 0 },
dispatch(event) {
const state = this.states[this.state];
if (state.type === 'final') {
return;
}
const transition = state.on[event.type];
if (typeof transition === 'function') {
transition.call(this, event);
} else if (transition in this.states) {
this.state = transition;
}
},
states: {
idle: {},
attacking: {
on: {
ANIMATION_END: 'idle',
},
},
specialAttacking: {
on: {
ANIMATION_END: 'idle',
},
},
usingTech: {
on: {
ANIMATION_END: 'idle',
},
},
destroyed: {
type: 'final',
},
},
};
function attack(event) {
const roll = engine.roll(event.robot);
if (roll.isCritical) {
this.state = 'specialAttacking';
} else {
this.state = 'attacking';
}
}
export let robotAttackMachine = {
state: 'idle',
robot: { atk: 1, def: 0, hp: 1, power: 0 },
dispatch(event) {
const state = this.states[this.state];
if (state.type === 'final') {
return;
}
const transition = state.on[event.type];
if (typeof transition === 'function') {
transition.call(this, event);
} else if (transition in this.states) {
this.state = transition;
}
},
states: {
idle: {
on: {
ATTACK: attack,
},
},
attacking: {
on: {
ANIMATION_END: 'idle',
},
},
specialAttacking: {
on: {
ANIMATION_END: 'idle',
},
},
usingTech: {
on: {
ANIMATION_END: 'idle',
},
},
destroyed: {
type: 'final',
},
},
};
function attack(event) {
const roll = engine.roll(event.robot);
if (roll.isCritical) {
this.state = 'specialAttacking';
} else {
this.state = 'attacking';
}
}
function useTech(event) {
const robot = this.robot;
engine.useTech(robot, event.tech);
if (robot.hp <= 0) {
this.state = 'usingTechLethal';
} else {
this.state = 'usingTech';
}
}
export let robotAttackMachine = {
state: 'idle',
robot: { atk: 1, def: 0, hp: 1, power: 0 },
dispatch(event) {
const state = this.states[this.state];
if (state.type === 'final') {
return;
}
const transition = state.on[event.type];
if (typeof transition === 'function') {
transition.call(this, event);
} else if (transition in this.states) {
this.state = transition;
}
},
states: {
idle: {
on: {
ATTACK: attack,
},
},
attacking: {
on: {
ANIMATION_END: 'idle',
},
},
specialAttacking: {
on: {
ANIMATION_END: 'idle',
},
},
usingTech: {
on: {
ANIMATION_END: 'idle',
},
},
destroyed: {
type: 'final',
},
},
};
function attack(event) {
const roll = engine.roll(event.robot);
if (roll.isCritical) {
this.state = 'specialAttacking';
} else {
this.state = 'attacking';
}
}
function useTech(event) {
const robot = this.robot;
engine.useTech(robot, event.tech);
if (robot.hp <= 0) {
this.state = 'usingTechLethal';
} else {
this.state = 'usingTech';
}
}
export let robotAttackMachine = {
state: 'idle',
robot: { atk: 1, def: 0, hp: 1, power: 0 },
dispatch(event) {
const state = this.states[this.state];
if (state.type === 'final') {
return;
}
const transition = state.on[event.type];
if (typeof transition === 'function') {
transition.call(this, event);
} else if (transition in this.states) {
this.state = transition;
}
},
states: {
idle: {
on: {
ATTACK: attack,
USE_TECH: useTech,
},
},
attacking: {
on: {
ANIMATION_END: 'idle',
},
},
specialAttacking: {
on: {
ANIMATION_END: 'idle',
},
},
usingTech: {
on: {
ANIMATION_END: 'idle',
},
},
destroyed: {
type: 'final',
},
},
};
function attack(event) {
const roll = engine.roll(event.robot);
if (roll.isCritical) {
this.state = 'specialAttacking';
} else {
this.state = 'attacking';
}
}
function useTech(event) {
const robot = this.robot;
engine.useTech(robot, event.tech);
if (robot.hp <= 0) {
this.state = 'usingTechLethal';
} else {
this.state = 'usingTech';
}
}
export let robotAttackMachine = {
state: 'idle',
robot: { atk: 1, def: 0, hp: 1, power: 0 },
dispatch(event) {
const state = this.states[this.state];
if (state.type === 'final') {
return;
}
const transition = state.on[event.type];
if (typeof transition === 'function') {
transition.call(this, event);
} else if (transition in this.states) {
this.state = transition;
}
},
states: {
idle: {
on: {
ATTACK: attack,
USE_TECH: useTech,
},
},
attacking: {
on: {
ANIMATION_END: 'idle',
},
},
specialAttacking: {
on: {
ANIMATION_END: 'idle',
},
},
usingTech: {
on: {
ANIMATION_END: 'idle',
},
},
usingTechLethal: {
on: {
ANIMATION_END: 'destroyed',
},
},
destroyed: {
type: 'final',
},
},
};
function attack(event) {
const roll = engine.roll(event.robot);
if (roll.isCritical) {
this.state = 'specialAttacking';
} else {
this.state = 'attacking';
}
}
function useTech(event) {
const robot = this.robot;
engine.useTech(robot, event.tech);
if (robot.hp <= 0) {
this.state = 'usingTechLethal';
} else {
this.state = 'usingTech';
}
}
export let robotAttackMachine = {
state: 'idle',
robot: { atk: 1, def: 0, hp: 1, power: 0 },
dispatch(event) {
const state = this.states[this.state];
if (state.type === 'final') {
return;
}
const transition = state.on[event.type];
if (typeof transition === 'function') {
transition.call(this, event);
} else if (transition in this.states) {
this.state = transition;
}
},
states: {
idle: {
on: {
ATTACK: attack,
USE_TECH: useTech,
},
},
attacking: {
on: {
ANIMATION_END: 'idle',
},
},
specialAttacking: {
on: {
ANIMATION_END: 'idle',
},
},
usingTech: {
on: {
ANIMATION_END: 'idle',
},
},
usingTechLethal: {
on: {
ANIMATION_END: 'destroyed',
},
},
destroyed: {
type: 'final',
},
},
};
function attack(event) {
const roll = engine.roll(event.robot);
if (roll.isCritical) {
this.state = 'specialAttacking';
} else {
this.state = 'attacking';
}
}
function useTech(event) {
const robot = this.robot;
engine.useTech(robot, event.tech);
if (robot.hp <= 0) {
this.state = 'usingTechLethal';
} else {
this.state = 'usingTech';
}
}
export let robotAttackMachine = {
state: 'idle',
robot: { atk: 1, def: 0, hp: 1, power: 0 },
dispatch(event) {
const state = this.states[this.state];
if (state.type === 'final') {
return;
}
const transition = state.on[event.type];
if (typeof transition === 'function') {
transition.call(this, event);
} else if (transition in this.states) {
this.state = transition;
}
},
states: {
idle: {
on: {
enter() {
if (this.robot.power > 0) {
this.state = 'poweredIdle';
}
},
ATTACK: attack,
USE_TECH: useTech,
},
},
attacking: {
on: {
ANIMATION_END: 'idle',
},
},
specialAttacking: {
on: {
ANIMATION_END: 'idle',
},
},
usingTech: {
on: {
ANIMATION_END: 'idle',
},
},
usingTechLethal: {
on: {
ANIMATION_END: 'destroyed',
},
},
destroyed: {
type: 'final',
},
},
};
function attack(event) {
const roll = engine.roll(event.robot);
if (roll.isCritical) {
this.state = 'specialAttacking';
} else {
this.state = 'attacking';
}
}
function useTech(event) {
const robot = this.robot;
engine.useTech(robot, event.tech);
if (robot.hp <= 0) {
this.state = 'usingTechLethal';
} else {
this.state = 'usingTech';
}
}
export let robotAttackMachine = {
state: 'idle',
robot: { atk: 1, def: 0, hp: 1, power: 0 },
dispatch(event) {
const state = this.states[this.state];
if (state.type === 'final') {
return;
}
const transition = state.on[event.type];
if (typeof transition === 'function') {
transition.call(this, event);
} else if (transition in this.states) {
this.state = transition;
}
},
states: {
idle: {
on: {
enter() {
if (this.robot.power > 0) {
this.state = 'poweredIdle';
}
},
ATTACK: attack,
USE_TECH: useTech,
},
},
poweredIdle: {
on: {
ATTACK: attack,
USE_TECH: useTech,
},
},
attacking: {
on: {
ANIMATION_END: 'idle',
},
},
specialAttacking: {
on: {
ANIMATION_END: 'idle',
},
},
usingTech: {
on: {
ANIMATION_END: 'idle',
},
},
usingTechLethal: {
on: {
ANIMATION_END: 'destroyed',
},
},
destroyed: {
type: 'final',
},
},
};
function attack(event) {
const roll = engine.roll(event.robot);
if (roll.isCritical) {
this.state = 'specialAttacking';
} else {
this.state = 'attacking';
}
}
function useTech(event) {
const robot = this.robot;
engine.useTech(robot, event.tech);
if (robot.hp <= 0) {
this.state = 'usingTechLethal';
} else {
this.state = 'usingTech';
}
}
export let robotAttackMachine = {
_state: 'idle',
get state() {
return this._state;
},
set state(value) {
this._state = value;
const state = this.states[this._state];
if (state?.on?.enter) {
state.on.enter.call(this);
}
},
robot: { atk: 1, def: 0, hp: 1, power: 0 },
dispatch(event) {
const state = this.states[this.state];
if (state.type === 'final') {
return;
}
const transition = state.on[event.type];
if (typeof transition === 'function') {
transition.call(this, event);
} else if (transition in this.states) {
this.state = transition;
}
},
states: {
idle: {
on: {
enter() {
if (this.robot.power > 0) {
this.state = 'poweredIdle';
}
},
ATTACK: attack,
USE_TECH: useTech,
},
},
poweredIdle: {
on: {
ATTACK: attack,
USE_TECH: useTech,
},
},
attacking: {
on: {
ANIMATION_END: 'idle',
},
},
specialAttacking: {
on: {
ANIMATION_END: 'idle',
},
},
usingTech: {
on: {
ANIMATION_END: 'idle',
},
},
usingTechLethal: {
on: {
ANIMATION_END: 'destroyed',
},
},
destroyed: {
type: 'final',
},
},
};
function attack(event) {
const roll = engine.roll(event.robot);
if (roll.isCritical) {
this.state = 'specialAttacking';
} else {
this.state = 'attacking';
}
}
function useTech(event) {
const robot = this.robot;
engine.useTech(robot, event.tech);
if (robot.hp <= 0) {
this.state = 'usingTechLethal';
} else {
this.state = 'usingTech';
}
}
export let robotAttackMachine = {
_state: 'idle',
get state() {
return this._state;
},
set state(value) {
this._state = value;
const state = this.states[this._state];
if (state?.on?.enter) {
state.on.enter.call(this);
}
},
robot: { atk: 1, def: 0, hp: 1, power: 0 },
dispatch(event) {
const state = this.states[this.state];
if (state.type === 'final') {
return;
}
const transition = state.on[event.type];
if (typeof transition === 'function') {
transition.call(this, event);
} else if (transition in this.states) {
this.state = transition;
}
},
states: {
idle: {
on: {
enter() {
if (this.robot.power > 0) {
this.state = 'poweredIdle';
}
},
ATTACK: attack,
USE_TECH: useTech,
},
},
poweredIdle: {
on: {
ATTACK: attack,
USE_TECH: useTech,
},
},
attacking: {
on: {
ANIMATION_END: 'idle',
},
},
specialAttacking: {
on: {
ANIMATION_END: 'idle',
},
},
usingTech: {
on: {
ANIMATION_END: 'idle',
},
},
usingTechLethal: {
on: {
ANIMATION_END: 'destroyed',
},
},
destroyed: {
type: 'final',
},
},
};
function attack(event) {
const roll = engine.roll(event.robot);
if (roll.isCritical) {
this.state = 'specialAttacking';
} else {
this.state = 'attacking';
}
}
function useTech(event) {
const robot = this.robot;
engine.useTech(robot, event.tech);
if (robot.hp <= 0) {
this.state = 'usingTechLethal';
} else {
this.state = 'usingTech';
}
}
export let robotAttackMachine = {
_state: 'idle',
get state() {
return this._state;
},
set state(value) {
this._state = value;
const state = this.states[this._state];
if (state?.on?.enter) {
state.on.enter.call(this);
}
},
robot: { atk: 1, def: 0, hp: 1, power: 0 },
dispatch(event) {
const state = this.states[this.state];
if (state.type === 'final') {
return;
}
const transition = state.on[event.type];
if (typeof transition === 'function') {
transition.call(this, event);
} else if (transition in this.states) {
this.state = transition;
}
},
states: {
idle: {
on: {
enter() {
if (this.robot.power > 0) {
this.state = 'poweredIdle';
}
},
ATTACK: attack,
USE_TECH: useTech,
},
},
poweredIdle: {
on: {
ATTACK: attack,
USE_TECH: useTech,
},
},
attacking: {
on: {
ANIMATION_END: 'idle',
},
},
specialAttacking: {
on: {
ANIMATION_END: 'idle',
},
},
usingTech: {
on: {
ANIMATION_END: 'idle',
},
},
usingTechLethal: {
on: {
ANIMATION_END: 'destroyed',
},
},
destroyed: {
type: 'final',
},
},
};
function attack(event) {
const roll = engine.roll(event.robot);
if (roll.isCritical) {
this.state = 'specialAttacking';
} else {
this.state = 'attacking';
}
}
function useTech(event) {
const robot = this.robot;
engine.useTech(robot, event.tech);
if (robot.hp <= 0) {
this.state = 'usingTechLethal';
} else {
this.state = 'usingTech';
}
}
export let robotAttackMachine = {
_state: 'idle',
get state() {
return this._state;
},
set state(value) {
this._state = value;
const state = this.states[this._state];
if (state?.on?.enter) {
state.on.enter.call(this);
}
},
robot: { atk: 1, def: 0, hp: 1, power: 0 },
dispatch(event) {
const state = this.states[this.state];
if (state.type === 'final') {
return;
}
const transition = state.on[event.type];
if (typeof transition === 'function') {
transition.call(this, event);
} else if (transition in this.states) {
this.state = transition;
}
},
states: {
idle: {
on: {
enter() {
if (this.robot.power > 0) {
this.state = 'poweredIdle';
}
},
ATTACK: attack,
USE_TECH: useTech,
},
},
poweredIdle: {
on: {
ATTACK: attack,
USE_TECH: useTech,
},
},
attacking: {
on: {
ANIMATION_END: 'idle',
},
},
specialAttacking: {
on: {
ANIMATION_END: 'idle',
},
},
usingTech: {
on: {
ANIMATION_END: 'idle',
},
},
usingTechLethal: {
on: {
ANIMATION_END: 'destroyed',
},
},
destroyed: {
type: 'final',
},
},
};
Idle
Attacking
Critical Hit
Roll
Special
Attacking
Use Tech
Check HP
Destroyed
Using Tech
Attack
Powered Idle
&
By Adam L Barrett
Finite State Machines are a powerful tool used to fight complexity in software. State Machines in JavaScript make our code more readable and maintainable. Let's learn about state machines and refactor a simple game where awesome battle robots fight horrible monsters from the deep.
Adam L Barrett is a JavaScript and Front-End consultant, a contributor to open source, and avid bike-shedder.