Skip to content

Instantly share code, notes, and snippets.

@1bardesign
Last active February 6, 2019 04:41
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 1bardesign/8d21b626af888a73fcac79d6a62394c1 to your computer and use it in GitHub Desktop.
Save 1bardesign/8d21b626af888a73fcac79d6a62394c1 to your computer and use it in GitHub Desktop.
state management code - fsm and a push/pop "game state manager" - see state_machine.lua for the main interesting bit
--[[
oop basics
]]
function class(inherits)
local c = {}
c.__mt = {__index = c}
--handle single inheritence
if type(inherits) == "table" and inherits.__mt then
setmetatable(c, inherits.__mt)
end
--common class functions
--internal initialisation
--sets up an initialised object with a default value table
--performing a super construction if necessary and assigning the right metatable
function c:init(t, ...)
if inherits then
--super ctor, then overlay args table
t = table.overlay(inherits:new(...), t)
end
--upgrade to this class and return
return setmetatable(t, self.__mt)
end
--constructor
--generally to be overridden
function c:new()
return self:init({})
end
--todo: collect stats on classes here
--get the inherited class for super calls if/as needed
--allows overrides that still refer to superclass behaviour
function c:super()
return inherits
end
--done
return c
end
--[[
state machine
a finite state machine implementation;
each state is a table with optional enter, exit, update and draw callbacks
which each optionally take the machine, and the state table as arguments
on changing state, the outgoing state's exit callback is called, then the incoming state's
enter callback is called.
on update, the current state's update callback is called
on draw, the current state's draw callback is called
TODO: consider coroutine friendliness
]]
require("src.core.oo")
local state_machine = class()
function state_machine:new(states, start)
local ret = self:init({
states = states or {},
current_state = ""
})
if type(start) == "string" then
ret:set_state(start)
end
return ret
end
-------------------------------------------------------------------------------
--internal helpers
function state_machine:_get_state()
return self.states[self.current_state]
end
--make an internal call, with up to 4 arguments
function state_machine:_call(name, a, b, c, d)
local state = self:_get_state()
if state and type(state[name]) == "function" then
return state[name](self, state, a, b, c, d)
end
return nil
end
-------------------------------------------------------------------------------
--various checks
function state_machine:in_state(name)
return self.current_state == name
end
function state_machine:has_state(name)
return self.states[name] ~= nil
end
-------------------------------------------------------------------------------
--state adding/removing
--add a state
function state_machine:add_state(name, data)
if self.has_state(name) then
error("error: added duplicate state "..name)
else
self.states[name] = data
if self:in_state(name) then
self:_call("enter")
end
end
return self
end
--remove a state
function state_machine:remove_state(name)
if not self.has_state(name) then
error("error: removed missed state "..name)
else
if self:in_state(name) then
self:_call("exit")
end
self.states[name] = nil
end
return self
end
--hard-replace a state table
--if do_transitions is truthy and we're replacing the current state,
--exit is called on the old state and enter is called on the new state
function state_machine:replace_state(name, data, do_transitions)
local current = self:in_state(name)
if do_transitions and current then
self:_call("exit")
end
self.states[name] = data
if do_transitions and current then
self:_call("enter")
end
return self
end
--ensure a state doesn't exist
function state_machine:clear_state(name)
return self:replace_state(name, nil, true)
end
-------------------------------------------------------------------------------
--transitions and updates
function state_machine:set_state(state, reset)
if self.current_state ~= state or reset then
self:_call("exit")
self.current_state = state
self:_call("enter")
end
return self
end
--perform an update
--pass in an optional delta time which is passed as an arg to the state functions
function state_machine:update(dt)
return self:_call("update", dt)
end
function state_machine:draw()
self:_call("draw")
end
return state_machine
--[[
state manager
another state machine on top of the game states with
a way to signal transitions without access to the machine
]]
local state_machine = require("src.core.state_machine")
state_manager = class()
--construct a state manager
--args table required:
-- states - a map of state name to state constructor function
-- initial_state - string name of the first state
function state_manager:new(args)
return self:init({
state_stack = {},
state_ctors = table.copy(args.states),
}):push_state(args.initial_state)
end
--helper methods
--perform "cleanup" on the current state
function state_manager:_transition_out()
local s = self:current_state()
if s then
--transition out if the current top state
--has a set state method itself
if type(s.set_state) == "function" then
s:set_state("")
end
--clear out anything that might need clearing
keyboard:clear()
end
end
--construct a state
function state_manager:_construct_state(name)
local ctor = self.state_ctors[name]
if ctor then
return ctor()
end
return nil
end
--push a new state onto the stack
function state_manager:push_state(name)
table.insert(self.state_stack, self:_construct_state(name))
return self
end
--pop a state off the stack, "transitioning out" as required
function state_manager:pop_state()
self:_transition_out()
table.remove(self.state_stack)
return self
end
--get the current state
function state_manager:current_state()
return self.state_stack[#self.state_stack]
end
--replace the current state
function state_manager:set_state(name)
return self:pop_state():push_state(name)
end
--update
function state_manager:update(dt)
local s = self:current_state()
if s then
local transition = s:update(dt)
if transition then
self:set_state(transition)
end
end
end
--draw
function state_manager:draw()
local s = self:current_state()
if s then
s:draw()
end
end
function state_manager:draw_hud()
local s = self:current_state()
if s then
if type(s.draw_hud) == "function" then
s:draw_hud()
elseif type(s._call) == "function" then
s:_call("draw_hud")
end
end
end
return state_manager
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment