Skip to content

Instantly share code, notes, and snippets.

@bcatcho
Created October 28, 2012 20:47
Show Gist options
  • Save bcatcho/3969854 to your computer and use it in GitHub Desktop.
Save bcatcho/3969854 to your computer and use it in GitHub Desktop.
the algorithm works in its simplest of forms but doesn't handle switching direction of flow
--# Debug
dbg = {}
setmetatable(dbg, { __index = dbg })
dbg.traceback = function()
local level = 1
while true do
local info = debug.getinfo(level, "Sl")
if not info then break end
if info.what == "C" then -- is a C function?
print(level, "C function")
else -- a Lua function
print(string.format("[%s]:%d", info.short_src, info.currentline))
end
level = level + 1
end
end
dbg.timeFunc = function (func, ...)
local t = os.clock()
local result = func(unpack(arg))
return result, os.clock() - t
end
dbg.printTime = function (func, ...)
local r, t = dbg.timeFunc(func, unpack(arg))
print(t)
return r
end
--# ObserverTable
ObserverTable = class()
function ObserverTable:init()
-- you can accept and set parameters here
self.t = {}
end
function ObserverTable:observe(key, topic, delegagte)
if self.t[key] == nil then
self.t[key] = {}
end
if self.t[key][topic] == nil then
self.t[key][topic] = {}
end
table.insert(self.t[key][topic], delegate)
end
function ObserverTable:notify(key, topic, ...)
if self.t[key] ~= nil then
if self.t[key][topic] ~= nil then
for key, d in ipairs(self.t[key][topic]) do
d(unpack(arg))
end
end
end
end
--# State
State = class()
function State:init(...)
self.sm = nil
self:initState(unpack(arg))
end
function State:setStateMachine(stateMachine)
self.sm = stateMachine
end
function State:goState(state)
self.sm:transitionTo(state)
end
function State:shared()
return self.sm:getSharedProps()
end
-- virtual
function State:initState()
end
-- virtual
function State:enter()
end
-- virtual
function State:exit()
end
--# StateMachine
StateMachine = class()
function StateMachine:init()
self.state = nil
self._sharedProps = {}
end
function StateMachine:startWith(initialState)
self.state = initialState
self.state:setStateMachine(self)
self.state:enter()
end
-- properties that will be passed from state to state
function StateMachine:setSharedProperties(props)
for k,v in pairs(props) do
self._sharedProps[k] = v
end
end
function StateMachine:getSharedProps()
return self._sharedProps
end
function StateMachine:transitionTo(nextState)
self.state:exit()
self.state = nextState
self.state:setStateMachine(self)
self.state:enter()
end
function StateMachine:current()
return self.state
end
--# MathExtensions
math.betweenI = function(val, low, high)
return val >= low and val <= high
end
math.isInRect = function(pointX, pointY, rectX, rectY, rectWidth, rectHeight)
return math.betweenI(pointX, rectX, rectX+rectWidth)
and math.betweenI(pointY, rectY, rectY+rectHeight)
end
math.constrain = function(val, low, high)
return math.max(math.min(val, high), low)
end
math.wrap = function(val, low, high)
return (val > high) and low
or (val < low) and high
or val
end
--# TableExtensions
function T(t)
return setmetatable(t, {__index = table})
end
table.reduceMethods = {
sum = function (i, tot) return i + tot end
}
table.reduce = function(tbl, seed, func )
local sum = seed
for k,v in pairs(tbl) do
sum = func(v, sum)
end
return sum
end
table.sum = function(tbl, seed)
return table.reduce(tbl, seed, table.reduceMethods.sum)
end
table.doXY = function(cols, rows, func)
for r = 1, rows do
for c = 1, cols do
func(c, r)
end
end
end
table.map = function(tbl, func)
local result = T{}
for k, v in pairs(tbl) do
result:insert(func(v, k) )
end
return result
end
table.each = function(tbl, func)
for k, v in pairs(tbl) do
func(v, k)
end
end
table.eachIf = function(tbl, filter, func)
local result = T{}
for k, v in pairs(tbl) do
if filter(v,k) then
result[k] = func(v,k)
end
end
return result
end
table.eachIfA = function(tbl, filter, func)
local result = T{}
for k, v in pairs(tbl) do
if filter(v,k) then
result:insert(func(v,k))
end
end
return result
end
table.eachUntil = function(tbl, success, func)
for k, v in pairs(tbl) do
if func(v,k) == success then
return true
end
end
return false
end
table.call = function(tbl, methodName, ...)
for k, v in ipairs(tbl) do
v[methodName](v, unpack(arg))
end
end
table.filter = function(tbl, func)
local result = T{}
for k, v in pairs(tbl) do
if func(v,k) then
result[k] = v
end
end
return result
end
table.filterA = function(tbl, func)
local result = T{}
for k, v in ipairs(tbl) do
if func(v,k) then
result[#result+1] = v
end
end
return result
end
table.mapSet = function(tbl, func)
local result = T{}
local track = {} -- keep track of the values we have seen
local ouput -- optimization
for k, v in pairs(tbl) do
output = func(v, k)
if track[output] == nil then
result:insert(output)
track[output] = true
end
end
return result
end
table.cycle = function(tbl)
tbl:insert(tbl[1])
tbl:remove(1)
end
table.first = function(tbl, func)
if func then
for k,v in pairs(tbl) do
if func(v, k) then return v, k end
end
return nil, nil
end
return tbl[1]
end
--# TGrid
TGrid = class(ObserverTable)
function TGrid:init(w, h)
ObserverTable.init(self)
-- you can accept and set parameters here
self.w = w
self.h = h
self.grid = {}
self.gridByY = {}
end
-- iterator by Y value from top of screen to bottom
function TGrid:listTtoB()
local w, h = self.w, self.h
local fn = function(state, loopvar)
if state.y > 0 then
state.x = state.x+1
if state.x > w then
state.x = 1
state.y = state.y - 1
end
return self:get(state.x, state.y)
end
return nil
end
return fn, {x = 0, y = h}, 0
end
function TGrid:listBtoT()
local w, h = self.w, self.h
local fn = function(state, loopvar)
if state.y <= h then
state.x = state.x+1
if state.x > w then
state.x = 1
state.y = state.y + 1
end
return self:get(state.x, state.y)
end
return nil
end
return fn, {x = 0, y = 1}, 0
end
function TGrid:gIndex(x,y)
return x..','..y
end
function TGrid:add(x, y, value)
self.grid[self:gIndex(x,y)] = value
--table.insert(self.gridByY, (y-1), self:get(x,y))
end
function TGrid:get(x,y)
return self.grid[self:gIndex(x,y)]
end
function TGrid:getN(x,y)
return self:get(x,y-1)
end
function TGrid:getE(x,y)
return self:get(x+1,y)
end
function TGrid:getS(x,y)
return self:get(x,y+1)
end
function TGrid:getW(x,y)
return self:get(x-1,y)
end
function TGrid:set(x,y, value)
self.grid[self:gIndex(x,y)] = value
end
function TGrid:setk(x,y, key, value)
self.grid[self:gIndex(x,y)][key] = value
end
function TGrid:getAround(x,y)
return {
self:getN(x,y),
self:getE(x,y),
self:getS(x,y),
self:getW(x,y)
}
end
--# Util
-- String.format
_fmt = function(...)
return string.format(unpack(arg))
end
--# Main
-- Common 077dea
-- this project is just a container for common classes and utilities
local output = ""
local pass, fail = 0, 0
function test(name, t)
local outputFunc = function(...)
print(" "..string.format(unpack(arg)))
end
local expected, actual = t(outputFunc)
if expected == actual then
output = output.."."
pass = pass + 1
else
print(output)
output = ""
print("!! "..name)
print(" "..tostring(expected).."\n "..tostring(actual).."\n")
fail = fail + 1
end
end
function setup()
test( "table.reduce",
function ()
return 6, T{2,3}:reduce(1, function(i,tot) return i*tot end)
end)
test( "table.sum",
function ()
return 5, T{2,3}:sum(0)
end)
test( "table.doXY",
function ()
local total = 0
table.doXY(2,2, function(x,y) total = total + x + y end)
return 12, total
end)
test("table.map",
function ()
local map = table.map({"yar","word","to"}, string.len)
return 4, map[2]
end)
test("table.mapSet",
function ()
local map = table.mapSet({"yar","yar","to"}, string.format)
return "to", map[2]
end)
test("table.eachIf",
function ()
local t = T{1,3,"adf"}
local result = t:eachIf(function (v) return type(v) == "string" end, string.upper )
return "ADF", result[3]
end)
test("table.eachIfA",
function ()
local t = T{1,3,"adf"}
local result = t:eachIfA(function (v) return type(v) == "string" end, string.upper )
return "ADF", result[1]
end)
test("table.call",
function ()
local sum = 0
local function makeTbl(x)
return {val=x, fn=function(inst) sum=sum+inst.val end}
end
local t = T{ makeTbl(1), makeTbl(2), makeTbl(3)}
t:call("fn")
return 6, sum
end)
test("table.filterA",
function ()
local filteredList = table.filterA({1,2,3}, function (v,k) return v > 2 end)
return 3, filteredList[1]
end)
test("table.filter",
function ()
local filteredList = table.filter({a=1,b=2,c=3}, function (v,k) return v > 2 end)
return 3, filteredList.c
end)
test("table.cycle",
function ()
local t = T{"a", "b", "c"}
t:cycle()
return "bca", t[1]..t[2]..t[3]
end)
test("table.first",
function ()
local function gt2(n) return n > 2 end
return 3, T{0,1,2,3,4,5,6}:first(gt2)
end)
test("T()",
function ()
local tbl = T{}
tbl:insert(1.5)
tbl = tbl:map(math.floor)
return 1, tbl[1]
end)
test("_fmt",
function ()
local result = _fmt("%d,%i,%f", 123.0, 123.0, 123.0)
return "123,123,123.000000", result
end)
test("dbg.timeFunc",
function ()
local function f (a)
local sum = 0
for i=1, a do sum = sum + 1 end
return sum
end
local result, time = dbg.timeFunc(f, 100000)
return "100000,true", result..","..tostring(time < 1 and time > 0)
end)
test("math.betweenI::TestBoundaries",
function ()
return true, (math.betweenI(1, 1, 2) and math.betweenI(2,1,2))
end)
test("math.betweenI::Inside",
function ()
return true, math.betweenI(2, 1, 3)
end)
test("math.betweenI::Outside",
function ()
return false, math.betweenI(3, 1, 2) and math.betweenI(0, 1, 2)
end)
test("math.isInRect::Inside",
function ()
return true, math.isInRect(2,2, 1,1,3,3)
end)
test("math.isInRect::Boundaries",
function ()
return true, math.isInRect(1,2, 1,1,3,3)
end)
test("math.constrain",
function ()
return 3, math.constrain(10, 1, 3)
end)
test("math.wrap::inside",
function ()
return 5, math.wrap(5, 1, 10)
end)
test("math.wrap::highAndLow",
function ()
return "1,10", _fmt("%d,%d",math.wrap(11, 1, 10), math.wrap(0, 1, 10))
end)
print(output)
print((fail == 0) and "All tests passed" or _fmt("%d/%d tests passed",pass,pass+fail))
end
--# DebugButton
DebugButton = class()
function DebugButton:init(title, x, y, callback)
self.x, self.y, self.title = x, y, title
self.callback = (callback ~= nil) and callback or function()end
self.width, self.height = 128, 48
self.toggler = nil
self.togglerVals = nil
self.togglerId = 1
self.color = color(150,255,150,255)
end
function DebugButton:setToggler(name, vals)
_G[name] = vals[1]
watch(name)
self.toggler = name
self.togglerVals = vals
end
function DebugButton:cycleToggler()
local numVals = #self.togglerVals
self.togglerId = math.wrap(self.togglerId + 1, 1, numVals)
_G[self.toggler] = self.togglerVals[self.togglerId]
end
function DebugButton:draw()
pushStyle()
fill(31, 31, 31, 255)
rect(self.x, self.y, self.width, self.height)
fill(255, 255, 255, 255)
fontSize(14)
textMode(CENTER)
text(self.title, self.x+self.width/2, self.y+self.height/2 + 10)
if self.toggler then
fontSize(16)
fill(self.color)
text(_G[self.toggler], self.x+self.width/2, self.y+self.height/2 - 10)
end
popStyle()
end
function DebugButton:touched(touch)
local isInside = math.isInRect(touch.x, touch.y, self.x, self.y, self.width, self.height)
if isInside then
if touch.state == ENDED then
if self.toggler ~= nil then
self:cycleToggler()
end
self.callback(touch)
end
return true
end
return false
end
--# FlowNode
FlowNode = class()
function FlowNode:init(x, y)
self.x, self.y = x, y
self.flow = T{ n = 0, e = 0, s = 0, w = 0 }
self.neighbors = T{n=nil, e=nil, s=nil, w=nil}
self.vol = 0
self.gl = nil
self.tree = nil
self.part = nil
end
function FlowNode:waterLvl()
return self.vol + self.gl()
end
function FlowNode:initFromTree(id, tree, gridNode)
self.id = id
self.tree = tree
self.gl = function() return gridNode:groundLvl() end
self:updateNeighbors()
self.neighbors:each(print)
self.neighbors:each(function(n) n:updateNeighbors() end)
end
function FlowNode:updateNeighbors()
self.neighbors = self.tree:getNeighbors(self.x, self.y)
end
function FlowNode:debugCycleWaterLvl()
self.vol = self.vol + 1
if self:waterLvl() > 3 then
self.vol = 0
end
end
function FlowNode:setPart(p)
self.part = p
end
function FlowNode:getFlow(edge)
return self.flow[edge]
end
function FlowNode:setFlow(edge, val)
self.flow[edge] = val
end
function FlowNode:addFluid(amt)
self.vol = math.constrain(self.vol + amt, 0, 100)
end
function FlowNode:netFlow()
return self.flow.n+self.flow.e+self.flow.w+self.flow.s
end
function FlowNode:hasInwardFlow()
return self:netFlow() < 0
end
function oppDir(dir)
return (dir == "n") and "s"
or (dir == "e") and "w"
or (dir == "s") and "n"
or (dir == "w") and "e"
end
-- flow()
-- In this step, the node determines how much volume it will recieve
-- or lose based on the flow values along it's edges
function FlowNode:solveFlow()
local netFlow = self:netFlow()
if netFlow < 0 then -- flowing in
self:addFluid(math.abs(netFlow))
elseif netFlow > 0 then -- flowing out
self:addFluid(-1*netFlow)
else
self:switchFlowDir()
end
end
function FlowNode:switchFlowDir()
local outStr, outDir = self.flow:first(function(v,k) return v > 0 end)
if outDir then
local _, newOutDir = self.flow:first(function(v,k)
print(v,k, self.neighbors[k])
return v == 0 and self.neighbors[k] and k ~= outDir
end)
if newOutDir then
self:setFlow(outDir, 0)
self:setFlow(newOutDir, outStr)
end
end
end
function FlowNode:discoverNewNodes()
self.tree:discoverNewNodesAround(x,y)
end
function FlowNode:fixFlowThroughBoundaries()
-- look at all 4 neighbors that aren't flowing in to you
-- set the flow to them to == to the flow into you
end
--# FlowPart
FlowPart = class()
function FlowPart:init(index)
self.id = index
self._nodes = T{}
self.nodeCount = 0
self.avgWaterLvl = 0
end
function FlowPart:idName()
return _fmt("%c", 64+self.id)
end
function FlowPart:remove(node)
self._nodes[node.id] = nil
self.nodeCount = self.nodeCount - 1
end
function FlowPart:isEmpty()
return self.nodeCount == 0
end
function FlowPart:add(node)
node:setPart(self)
self._nodes[node.id] = node
self.nodeCount = self.nodeCount + 1
end
function FlowPart:addFromPart(partB)
for k,n in pairs(partB:nodes()) do
self:add(n)
partB:remove(n)
end
end
function FlowPart:nodes()
return self._nodes
end
function FlowPart:clean()
local newList = T{}
self.nodeCount = 0
for k,v in pairs(self._nodes) do
if v.part == self then
newList[k] = v
self.nodeCount = self.nodeCount + 1
end
end
self._nodes = newList
end
function FlowPart:calculateAvgWaterLvl()
if self.nodeCount > 0 then
local sum = self._nodes:reduce(0, function(n, tot) return n:waterLvl() + tot end)
self.avgWaterLvl = sum/self.nodeCount
end
end
--# FlowTree
FlowTree = class()
function FlowTree:init(grid)
-- nodes
self.tree = T{}
self.grid = grid
-- partitions
self.parts = T{}
for i = 1, 100 do
self.parts[i] = FlowPart(i)
end
self.partCount = 0
-- other
self.idCount = 0
self.potentialNodes = T{}
end
function FlowTree:makeId()
self.idCount = self.idCount + 1
return self.idCount
end
function FlowTree:getSurroundingNodes(x,y)
-- North, East, South, West
return T{ self:get(x,y+1), self:get(x+1,y), self:get(x,y-1), self:get(x-1,y)}
end
function FlowTree:getNeighbors(x, y)
return T{
["n"] = self:get(x,y+1),
["e"] = self:get(x+1,y),
["s"] = self:get(x,y-1),
["w"] = self:get(x-1,y)
}
end
function FlowTree:exists(x,y)
return self.tree[x+(y*100)] ~= nil
end
function FlowTree:get(x,y)
return self.tree[x+(y*100)]
end
function FlowTree:set(x,y, node)
self.tree[x+(y*100)] = node
node:initFromTree(self:makeId(), self, self.grid:get(x,y))
end
function FlowTree:unsetNode(x,y)
self.tree[x+(y*100)] = nil
end
function FlowTree:getNextAvailablePart()
for k,v in ipairs(self.parts) do
if v:isEmpty() then return v end
end
return nil
end
function FlowTree:newPart()
local part = self:getNextAvailablePart()
self.partCount = self.partCount + 1
return part
end
function FlowTree:findPartsAround(x,y)
local nodes = self:getSurroundingNodes(x,y)
return nodes:mapSet(function (n) return n.part end)
end
function FlowTree:combineParts(parts)
local destPart = parts[1]
for k, oldPart in pairs(parts) do
if destPart ~= oldPart then
destPart:addFromPart(oldPart)
self.partCount = self.partCount - 1
end
end
end
-- mutator methods
function FlowTree:add(x, y)
local node = FlowNode(x,y)
self:set(x,y, node)
local parts = self:findPartsAround(x,y)
if #parts == 0 then
self:newPart():add(node)
elseif #parts == 1 then
parts[1]:add(node)
else
-- use the earliest partition as the destination partition
table.sort(parts, function (a,b) return a.id < b.id end)
parts[1]:add(node)
self:combineParts(parts)
end
--self:discoverNewNodesAround(x,y)
end
function FlowTree:remove(x,y)
local node = self:get(x,y)
node.part:remove(node)
self:unsetNode(x,y)
self:repairPartsStartingWith(self:getSurroundingNodes(x,y))
end
function FlowTree:repairPartsStartingWith(nodes)
local partSet = T{}
local partSetIndex = 0
local partNames = T{}
local function _isUndiscovered(node)
return (partSet[node.x + node.y*100] == nil)
end
local function _addNodeToCurrentSet(node)
partSet[node.x+node.y*100] = {node, partSetIndex}
end
local function _crawlNewPartition(root)
local found = T{root}
local function _trackNode(n)
_addNodeToCurrentSet(n)
found:insert(n)
end
local windowStart = 1
local windowStop, around
while windowStart <= #found do
windowStop = #found
for i = windowStart, windowStop do
around = self:getSurroundingNodes(found[i].x, found[i].y)
around:eachIf(_isUndiscovered, _trackNode)
windowStart = windowStart + 1
end
end
end
-- crawl list of nodes and assocaiate them with a partition bucket
nodes:eachIf(_isUndiscovered,
function (n)
partSetIndex = partSetIndex + 1
_addNodeToCurrentSet(n)
_crawlNewPartition(n)
end)
local node, part, oldPart
local nameForPart = T{}
for k,v in pairs(partSet) do
node,part = unpack(v)
if part > 1 then
if nameForPart[part] == nil then
nameForPart[part] = self:newPart()
end
oldPart = node.part
oldPart:remove(node)
nameForPart[part]:add(node)
end
end
-- sum up parts
local function accumilateParts(p, sum)
return (p:isEmpty()) and sum or sum + 1
end
self.partCount = self.parts:reduce(0, accumilateParts)
end
function FlowTree:discoverNewNodesAround(x,y)
local node = self:get(x,y)
local gl = node.gl()
local wl = node:waterLvl()
self.grid:getSurroundingNodes(x,y)
:filter(function (n) return not self:exists(n.x, n.y) end)
:each(function (n)
if n:groundLvl() < wl then
self:add(n.x, n.y)
end
end)
end
function FlowTree:preProcessParts()
print("FlowTree:preProcessParts")
self.parts:call("calculateAvgWaterLvl")
end
function FlowTree:flowAllNodes()
print("FlowTree:flowAllNodes")
self.tree:each(function(n) n:solveFlow() end)
end
function FlowTree:discoverNewNodes()
print("FlowTree:dicoverNewNodes")
self.tree:each(function(n)
if n.vol >= 1 then
self:discoverNewNodesAround(n.x,n.y)
end
end)
end
function FlowTree:conserveFlow()
print("FlowTree:conserveFlow")
self.tree:each(function(n)
if n:hasInwardFlow() and n.vol > 0 then
local f = n.flow
n.neighbors:first(function(v,k)
if f[k] == 0 then
f[k] = 1
v.flow[oppDir(k)] = -1 * f[k]
return true
end
return false
end)
elseif n.vol > 0 then
local f = n.flow
n.neighbors:each(function(v,k)
if f[k] == 1 then
v.flow[oppDir(k)] = -1
elseif f[k] == 0 and v.flow[oppDir(k)] < 0 then
v.flow[oppDir(k)] = 0
end
end)
end
end)
end
-- debug
function FlowTree:debugToggle(x,y, gridNode)
local n = self:get(x,y)
if n ~= nil then
n:debugCycleWaterLvl()
if n.vol == 0 then
self:remove(x,y)
end
else
self:add(x,y)
end
end
function FlowTree:drawDebug(drawFunc)
pushStyle()
fill(177, 177, 177, 255)
textAlign(LEFT)
--text("parts: "..self.partCount, WIDTH/2,HEIGHT - 20)
popStyle()
for k,v in pairs(self.tree) do
drawFunc(v.x, v.y, v.id,v.part:idName(), v.flow, v.vol, v:waterLvl(), v:netFlow())
end
end
--# WorldNode
WorldNode = class()
function WorldNode:init(x, y)
self.x, self.y = x,y
self.part = nil
self.gl = 3
self.colorList = {
color(148, 53, 81, 255),
color(111, 63, 31, 255),
color(35, 136, 28, 255)
}
self.color = nil
self:setColorForGroundLvl()
end
function WorldNode:groundLvl()
return self.gl
end
function WorldNode:dig()
self.gl = self.gl - 1
if self.gl == 0 then
self.gl = 3
end
end
function WorldNode:setColorForGroundLvl()
self.color = self.colorList[self.gl]
end
function WorldNode:touched()
self:dig()
self:setColorForGroundLvl()
end
--# WorldGrid
WorldGrid = class()
function WorldGrid:init()
self.grid = {}
end
function WorldGrid:populate(x, y)
table.doXY(x,y, function (x,y) self:add(x,y) end)
end
function WorldGrid:makeId()
self.idCount = self.idCount + 1
return self.idCount
end
function WorldGrid:set(x,y, node)
self.grid[x+(y*100)] = node
end
function WorldGrid:add(x, y)
self:set(x,y, WorldNode(x,y))
end
function WorldGrid:get(x,y)
return self.grid[x+(y*100)]
end
-- getSurroundingNodes
-- returns WorldNodes that surround a grid point (n,e,s,w)
function WorldGrid:getSurroundingNodes(x,y)
return T{
self:get(x,y+1), self:get(x+1,y),
self:get(x,y-1), self:get(x-1,y)
}
end
function WorldGrid:drawDebug(drawFunc)
for k,v in pairs(self.grid) do
drawFunc(v.x, v.y, _fmt("%d,%d",v.x,v.y), v.color )
end
end
function WorldGrid:debugDig(x,y)
self:get(x,y):touched()
end
--# Main
-- flow 007de4
local BWIDTH, COLS, ROWS = 100, 6, 6
local grid = WorldGrid()
local tree = FlowTree(grid)
local buttons = T{}
-- Use this function to perform your initial setup
function setup()
grid:populate(COLS,ROWS)
debugDig(1,1)
debugDig(1,2)
debugDig(1,3)
debugDig(3,3)
debugDig(2,3)
local bx, by = 0, HEIGHT - 48
local function _makeToggle(name, globalName, vals, callback)
local b = DebugButton(name, bx, by, callback)
b:setToggler(globalName, vals)
buttons:insert(b)
bx = bx + 129
if bx > WIDTH then
bx, by = 0, by + 49
end
end
local function _makeButton(name, callback)
buttons:insert(DebugButton(name, bx, by, callback))
bx = bx + 129
if bx > WIDTH then
bx, by = 0, by + 49
end
end
_makeToggle("tapMode", "TAP_MODE", T{"pour","dig","flow"})
_makeButton("1:Proc_Parts", debugComputePartWaterLvl)
_makeButton("2:Do_Flow", debugDoFlow)
_makeButton("3:Find_New", debugFindNewNodes)
_makeButton("3:Conserve", debugConserveFlow)
end
function drawSquare(x, y, id, part, flow, vol, wl, netFlow)
local function drawFlow(val, x,y)
stroke(0, 0, 0, 255)
fill(T{
color(255, 129, 0, 255),
color(0, 0, 0, 255),
color(26, 255, 0, 255)
}[val+2])
text(val,x,y)
end
pushStyle()
textMode(CENTER)
local half = .5*BWIDTH -10
local _x, _y = x*BWIDTH, y*BWIDTH
-- background
pushStyle()
noFill()
if vol == 1 then
stroke(12, 143, 215, 216)
elseif vol == 2 then
stroke(215,143,143,216)
elseif vol >= 3 then
stroke(215,143,12,216)
else
stroke(12, 143, 215, 65)
end
strokeWidth(4)
rect(_x, _y, BWIDTH, BWIDTH)
popStyle()
-- data
fill(233, 233, 233, 255)
_x, _y = (x+.5)*BWIDTH, (y+.5) * BWIDTH
text(id..part, _x, _y+5)
text(_fmt("v:%d, wl:%d",vol, wl), _x, _y-5)
if netFlow ~= 0 then
fill(255, 129, 0, 255)
end
text(_fmt("nf:%d", netFlow), _x, _y-15)
drawFlow(flow.n, _x, _y + half )
drawFlow(flow.s, _x, _y - half )
drawFlow(flow.e, _x + half, _y )
drawFlow(flow.w, _x - half, _y )
popStyle()
end
function drawWorldGrid(x, y, id, bColor)
pushStyle()
fill(bColor)
rect(x*BWIDTH, y*BWIDTH, BWIDTH, BWIDTH)
popStyle()
end
function draw()
background(40, 40, 50)
fontSize(11)
pushMatrix()
translate(BWIDTH*-.5, BWIDTH*-.5)
grid:drawDebug( drawWorldGrid )
tree:drawDebug( drawSquare )
popMatrix()
buttons:each(function(b) b:draw() end)
end
function debugComputePartWaterLvl()
tree:preProcessParts()
tree.parts:eachIf(function(p) return p.nodeCount > 0 end,
function (p)
print(p:idName(), p.avgWaterLvl)
end)
end
function debugDoFlow()
tree:flowAllNodes()
end
function debugFindNewNodes()
tree:discoverNewNodes()
end
function debugConserveFlow()
tree:conserveFlow()
end
function debugDig(x,y)
grid:debugDig(x,y)
tree:discoverNewNodes()
tree:conserveFlow()
end
function debugPour(x,y)
tree:debugToggle(x,y)
end
function debugCyclFlow(x,y, tx, ty)
local n = tree:get(x,y)
if n == nil then
return
end
-- get tap edge
local dir = nil
local third = BWIDTH/3
tx,ty = tx - (x*BWIDTH), ty - (y*BWIDTH)
if math.isInRect(tx,ty, third, third*2, third,third) then -- N
dir = "n"
elseif math.isInRect(tx,ty, third*2,third, third,third) then -- E
dir = "e"
elseif math.isInRect(tx,ty, third,0, third,third) then -- S
dir = "s"
elseif math.isInRect(tx,ty, 0,third, third,third) then -- W
dir = "w"
end
-- cycle that edge's flow value
if dir then
n:setFlow(dir, n:getFlow(dir) + 1)
if n:getFlow(dir) > 1 then
n:setFlow(dir, -1)
end
end
end
function touched(touch)
if buttons:eachUntil(true, function(b) b:touched(touch) end) then
return
end
if touch.state == ENDED then
local half = BWIDTH * .5 -- due to draw offset
local _x, _y = touch.x+half, touch.y + half
x = math.floor(_x/BWIDTH)
y = math.floor(_y/BWIDTH)
if math.betweenI(x, 1, COLS) and math.betweenI(y, 1, ROWS) then
T{
dig = debugDig,
pour = debugPour,
flow = debugCyclFlow
}[TAP_MODE](x,y, _x,_y)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment