diff --git a/neural/apis/state.lua b/neural/apis/state.lua new file mode 100644 index 0000000..1d27804 --- /dev/null +++ b/neural/apis/state.lua @@ -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 diff --git a/neural/fisher.lua b/neural/fisher.lua index 5eb4195..d666f75 100644 --- a/neural/fisher.lua +++ b/neural/fisher.lua @@ -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 \ No newline at end of file +end + +if not s and m then + error(m) +end