Last active
February 6, 2019 04:41
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
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