Created
October 28, 2012 20:47
-
-
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
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
--# 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 | |
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
--# 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