Skip to content

Instantly share code, notes, and snippets.

@Frityet
Created August 14, 2026 21:38
Show Gist options
  • Select an option

  • Save Frityet/d75aa8dff0341ffd74897e82a0cb69be to your computer and use it in GitHub Desktop.

Select an option

Save Frityet/d75aa8dff0341ffd74897e82a0cb69be to your computer and use it in GitHub Desktop.
FSM.tl
-- fsm.tl
------------------------------------------------------------
-- Common state interface
------------------------------------------------------------
local interface State
tag: string
end
------------------------------------------------------------
-- Concrete states
--
-- `where` tells Teal how to distinguish each record
-- at runtime when using `is`.
------------------------------------------------------------
local record Idle is State
where self.tag == "idle"
end
local record Running is State
where self.tag == "running"
speed: number
end
local record Jumping is State
where self.tag == "jumping"
velocity_y: number
end
------------------------------------------------------------
-- The actual state type
--
-- Not an enum.
-- A state is one of three completely different records.
------------------------------------------------------------
local type PlayerState =
Idle
| Running
| Jumping
------------------------------------------------------------
-- Input to our state machine
------------------------------------------------------------
local record Input
move: boolean
jump: boolean
landed: boolean
end
------------------------------------------------------------
-- Constructors
------------------------------------------------------------
local function idle(): Idle
return {
tag = "idle",
}
end
local function running(speed: number): Running
return {
tag = "running",
speed = speed,
}
end
local function jumping(velocity_y: number): Jumping
return {
tag = "jumping",
velocity_y = velocity_y,
}
end
------------------------------------------------------------
-- Transition function
------------------------------------------------------------
local function step(
state: PlayerState,
input: Input
): PlayerState
if state is Idle then
-- state is statically known to be Idle here
if input.jump then
return jumping(12)
end
if input.move then
return running(5)
end
return state
elseif state is Running then
-- state is statically Running here.
-- Therefore Running-only data is available:
print("running at", state.speed)
if input.jump then
return jumping(12)
end
if not input.move then
return idle()
end
return state
elseif state is Jumping then
-- state is statically Jumping here.
print("vertical velocity:", state.velocity_y)
if input.landed then
return idle()
end
return state
end
return state
end
------------------------------------------------------------
-- Display helper
------------------------------------------------------------
local function show(state: PlayerState)
if state is Idle then
print("IDLE")
elseif state is Running then
print("RUNNING, speed =", state.speed)
elseif state is Jumping then
print("JUMPING, vy =", state.velocity_y)
end
end
------------------------------------------------------------
-- Run it
------------------------------------------------------------
local state: PlayerState = idle()
show(state)
-- IDLE
state = step(state, {
move = true,
jump = false,
landed = false,
})
show(state)
-- RUNNING, speed = 5
state = step(state, {
move = true,
jump = true,
landed = false,
})
show(state)
-- JUMPING, vy = 12
state = step(state, {
move = false,
jump = false,
landed = true,
})
show(state)
-- IDLE
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment