fisher using state machine

This commit is contained in:
kepler155c@gmail.com
2019-04-15 19:01:41 -04:00
parent 61e2a2e8bf
commit 1d6839a499
2 changed files with 238 additions and 31 deletions

158
neural/apis/state.lua Normal file
View File

@@ -0,0 +1,158 @@
-- credit: https://github.com/kyleconroy/lua-state-machine
local machine = {}
machine.__index = machine
local NONE = "none"
local ASYNC = "async"
local function call_handler(handler, params)
if handler then
return handler(unpack(params))
end
end
local function create_transition(name)
local can, to, from, params
local function transition(self, ...)
if self.asyncState == NONE then
can, to = self:can(name)
from = self.current
params = { self, name, from, to, ...}
if not can then return false end
self.currentTransitioningEvent = name
local beforeReturn = call_handler(self["onbefore" .. name], params)
local leaveReturn = call_handler(self["onleave" .. from], params)
if beforeReturn == false or leaveReturn == false then
return false
end
self.asyncState = name .. "WaitingOnLeave"
if leaveReturn ~= ASYNC then
transition(self, ...)
end
return true
elseif self.asyncState == name .. "WaitingOnLeave" then
self.current = to
local enterReturn = call_handler(self["onenter" .. to] or self["on" .. to], params)
self.asyncState = name .. "WaitingOnEnter"
if enterReturn ~= ASYNC then
transition(self, ...)
end
return true
elseif self.asyncState == name .. "WaitingOnEnter" then
call_handler(self["onafter" .. name] or self["on" .. name], params)
call_handler(self["onstatechange"], params)
self.asyncState = NONE
self.currentTransitioningEvent = nil
return true
else
if string.find(self.asyncState, "WaitingOnLeave") or string.find(self.asyncState, "WaitingOnEnter") then
self.asyncState = NONE
transition(self, ...)
return true
end
end
self.currentTransitioningEvent = nil
return false
end
return transition
end
local function add_to_map(map, event)
if type(event.from) == 'string' then
map[event.from] = event.to
else
for _, from in ipairs(event.from) do
map[from] = event.to
end
end
end
function machine.create(options)
assert(options.events)
local fsm = {}
setmetatable(fsm, machine)
fsm.options = options
fsm.current = options.initial or 'none'
fsm.asyncState = NONE
fsm.events = {}
for _, event in ipairs(options.events or {}) do
local name = event.name
fsm[name] = fsm[name] or create_transition(name)
fsm.events[name] = fsm.events[name] or { map = {} }
add_to_map(fsm.events[name].map, event)
end
for name, callback in pairs(options.callbacks or {}) do
fsm[name] = callback
end
return fsm
end
function machine:is(state)
return self.current == state
end
function machine:can(e)
local event = self.events[e]
local to = event and event.map[self.current] or event.map['*']
return to ~= nil, to
end
function machine:cannot(e)
return not self:can(e)
end
function machine:todot(filename)
local dotfile = io.open(filename,'w')
dotfile:write('digraph {\n')
local transition = function(event,from,to)
dotfile:write(string.format('%s -> %s [label=%s];\n',from,to,event))
end
for _, event in pairs(self.options.events) do
if type(event.from) == 'table' then
for _, from in ipairs(event.from) do
transition(event.name,from,event.to)
end
else
transition(event.name,event.from,event.to)
end
end
dotfile:write('}\n')
dotfile:close()
end
function machine:transition(event)
if self.currentTransitioningEvent == event then
return self[self.currentTransitioningEvent](self)
end
end
function machine:cancelTransition(event)
if self.currentTransitioningEvent == event then
self.asyncState = NONE
self.currentTransitioningEvent = nil
end
end
machine.NONE = NONE
machine.ASYNC = ASYNC
return machine

View File

@@ -1,3 +1,5 @@
local machine = require('neural.state')
local device = _G.device
local os = _G.os
@@ -19,51 +21,98 @@ local depth = -3
local icon
local scales = { .2, .4, .6, .8, 1, .8, .6, .4 }
local scale = 0
local w, h
if canvas then
local w, h = canvas.getSize()
w, h = canvas.getSize()
icon = canvas.addItem({ w - 20, h - 20 }, 'minecraft:fishing_rod' )
end
local fsm = machine.create({
events = {
{ name = 'startup', from = 'none', to = 'wait' },
{ name = 'rod', from = 'wait', to = 'idle' },
{ name = 'norod', from = 'idle', to = 'wait' },
{ name = 'norod', from = 'fishing', to = 'wait' },
{ name = 'cast', from = 'idle', to = 'fishing' },
{ name = 'reel', from = 'fishing', to = 'idle' },
},
callbacks = {
-- events
oncast = function()
kinetic.use(.2)
os.sleep(.5)
local meta = sensor.getMetaByName('unknown')
depth = meta and meta.y - .5 or depth
return true
end,
onreel = function()
kinetic.use(.3)
os.sleep(.5)
return true
end,
-- state changes
onenterwait = function()
print('waitng for fishing rod to be selected')
if icon then
icon.remove()
icon = canvas.addItem({ w - 20, h - 20 }, 'minecraft:fishing_rod' )
end
end,
onleavewait = function()
print('fishing...')
end,
onenterfishing = function()
if icon then
icon.remove()
scale = 0
icon = canvas.addItem({ w - 20, h - 20 }, 'minecraft:fish', math.random(0, 3) )
end
end,
}
})
local function isHoldingRod()
local owner = sensor.getMetaOwner()
local held = owner.heldItem and owner.heldItem.getMetadata()
return held and held.rawName == 'item.fishingRod'
end
local function fish()
fsm:startup()
while true do
local meta = sensor.getMetaByName('unknown')
if not meta then
local owner = sensor.getMetaOwner()
local held = owner.heldItem and owner.heldItem.getMetadata()
if held and held.rawName == 'item.fishingRod' then
if icon then
icon.setItem('minecraft:fish', math.random(0, 3))
end
kinetic.use(.2)
print('casting')
os.sleep(.5)
meta = sensor.getMetaByName('unknown')
depth = meta and meta.y - .5 or depth
else
if icon then
icon.setItem('minecraft:fishing_rod')
icon.setScale(1)
end
print('waiting for fishing rod to be selected')
end
os.sleep(1)
else
if meta.y < depth then
kinetic.use(.3)
print('reeled in')
end
if icon then
scale = scale + 1
icon.setScale(scales[(scale % #scales) + 1])
if isHoldingRod() then
fsm:rod()
if not meta then
fsm:cast()
elseif meta and meta.y < depth then
fsm:reel()
end
os.sleep(.1)
else
fsm:norod()
os.sleep(1)
end
if icon and fsm.current == 'fishing' then
scale = scale + 1
icon.setScale(scales[(scale % #scales) + 1])
end
end
end
pcall(fish)
local s, m = pcall(fish)
if icon then
icon.remove()
end
end
if not s and m then
error(m)
end