fisher using state machine
This commit is contained in:
158
neural/apis/state.lua
Normal file
158
neural/apis/state.lua
Normal 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
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
local machine = require('neural.state')
|
||||||
|
|
||||||
local device = _G.device
|
local device = _G.device
|
||||||
local os = _G.os
|
local os = _G.os
|
||||||
|
|
||||||
@@ -19,51 +21,98 @@ local depth = -3
|
|||||||
local icon
|
local icon
|
||||||
local scales = { .2, .4, .6, .8, 1, .8, .6, .4 }
|
local scales = { .2, .4, .6, .8, 1, .8, .6, .4 }
|
||||||
local scale = 0
|
local scale = 0
|
||||||
|
local w, h
|
||||||
|
|
||||||
if canvas then
|
if canvas then
|
||||||
local w, h = canvas.getSize()
|
w, h = canvas.getSize()
|
||||||
icon = canvas.addItem({ w - 20, h - 20 }, 'minecraft:fishing_rod' )
|
icon = canvas.addItem({ w - 20, h - 20 }, 'minecraft:fishing_rod' )
|
||||||
end
|
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()
|
local function fish()
|
||||||
|
fsm:startup()
|
||||||
while true do
|
while true do
|
||||||
local meta = sensor.getMetaByName('unknown')
|
local meta = sensor.getMetaByName('unknown')
|
||||||
if not meta then
|
if isHoldingRod() then
|
||||||
local owner = sensor.getMetaOwner()
|
fsm:rod()
|
||||||
local held = owner.heldItem and owner.heldItem.getMetadata()
|
if not meta then
|
||||||
if held and held.rawName == 'item.fishingRod' then
|
fsm:cast()
|
||||||
if icon then
|
elseif meta and meta.y < depth then
|
||||||
icon.setItem('minecraft:fish', math.random(0, 3))
|
fsm:reel()
|
||||||
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])
|
|
||||||
end
|
end
|
||||||
os.sleep(.1)
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
pcall(fish)
|
local s, m = pcall(fish)
|
||||||
|
|
||||||
if icon then
|
if icon then
|
||||||
icon.remove()
|
icon.remove()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if not s and m then
|
||||||
|
error(m)
|
||||||
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user