Skip to content

Instantly share code, notes, and snippets.

@littletsu
Created January 30, 2024 07:40
Show Gist options
  • Save littletsu/eec03ffb5b214eb7e5f98688a1584809 to your computer and use it in GitHub Desktop.
Save littletsu/eec03ffb5b214eb7e5f98688a1584809 to your computer and use it in GitHub Desktop.
Proof of Concept State machine for Roblox simulator egg hatching
local StateMachine = {}
export type StateMachine = typeof(StateMachine.new())
export type StateMachineEnterCallback = (previousState: string|nil) -> ()
export type StateMachineState = {
Enter: StateMachineEnterCallback?,
Exit: ((nextState: string|nil) -> ())?,
Name: string?
}
---@diagnostic disable-next-line: unused-vararg
local function nop(...) end
function StateMachine.new(): StateMachine
local self: StateMachine = {}
self.States = {} :: {[string]: StateMachineState}
self.CurrentState = nil :: StateMachineState?
local onStart = nop
local onStop = nop
-- Start is a special state that is called when transitioning to a new state and there is no previous state
function self:OnStart(callback: () -> ())
onStart = callback
end
-- Stop is a special state that is only called by the Stop function
function self:OnStop(callback: () -> ())
onStop = callback
end
-- Name property on state will be replaced by the name in the arguments
function self:DeclareState(name: string, state: StateMachineState|StateMachineEnterCallback)
if type(state) == "function" then
state = {
Enter = state
}
end
state.Name = name
state.Exit = state.Exit or nop
state.Enter = state.Enter or nop
self.States[name] = state
return self
end
-- Yields until the previous state has Exited and the next state has Entered. Varargs will be passed to the enter function of the transitioning state
function self:To(nextState: string, ...)
local newState = self.States[nextState]
if newState == nil then
error(`StateMachine: no state {nextState}`)
end
local prevState
if self.CurrentState ~= nil then
prevState = self.CurrentState
self.CurrentState.Exit(nextState)
else
onStart()
end
self.CurrentState = newState
self.CurrentState.Enter(prevState and prevState.Name, ...)
return self
end
-- Calls exits the current state if there is one and calls OnStop after
function self:Stop()
if self.CurrentState ~= nil then
self.CurrentState.Exit(nil)
self.CurrentState = nil
end
onStop()
end
return self
end
local sm = StateMachine.new()
local results = {}
local currentResult = 1
local resultSm = StateMachine.new()
resultSm:DeclareState("Start", function() end)
local connectedGoNext = function()
if currentResult > #results then
resultSm:Stop()
return
end
local result = results[currentResult]
print("You hatched " .. result.Name .. " (x" .. result.Multiplier .. ")")
currentResult += 1
if currentResult > #results then
resultSm:Stop()
return
end
end
local disconnectedGoNext = function()
print("Calling disconnected go next!!!")
end
local goNextResult = disconnectedGoNext
resultSm:OnStop(function()
goNextResult = disconnectedGoNext
end)
resultSm:OnStart(function()
goNextResult = connectedGoNext
end)
local function onUserInteraction()
resultSm:Stop()
end
local wins = 100
local cost = 50
local function Hatch(amount): boolean
print("Hatching ", amount)
if wins < cost then
print("Hatch: not enough wins!")
return false
end
results = {
{Name = "Gupi", Multiplier = 7*11*13 * math.random()},
{Name = "Cat", Multiplier = 9},
{Name = "Dog", Multiplier = 7.534},
}
currentResult = 1
sm:To("EggAnim")
return true
end
local eggAnimTime = 1
sm:DeclareState("EggAnim", function()
print("HATCHING!!!")
print("HATCHING!!!!!")
print("HATCHING!!!!!!")
print("HATCHING!!!!")
sm:To("Results")
end)
sm:DeclareState("Results", {
Enter = function()
resultSm:To("Start")
sm:To("Connect")
print("You hatched", #results, "pets!")
end
})
local function connectedHatchOneInput()
onUserInteraction()
sm:To("Disconnect")
Hatch(3)
end
local function disconnectedHatchOneInput()
print("Calling disconnected hatch one input!")
end
local hatchOneInput = disconnectedHatchOneInput
local isInZone = true
sm:DeclareState("Connect", function()
if not isInZone then return print("Connect: not in zone!") end
print("Connect: Connecting inputs")
hatchOneInput = connectedHatchOneInput
end)
sm:DeclareState("Disconnect", function()
print("Disconnect: Disconnecting inputs")
hatchOneInput = disconnectedHatchOneInput
end)
sm:To("Connect")
hatchOneInput()
goNextResult()
goNextResult()
goNextResult()
hatchOneInput()
goNextResult()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment