forked from VoxeLibre/VoxeLibre
Change from Depth-First Search to Breadth-First Search; added bonus, the "135 closest" constraint comes much cheaper than with DFS.
This commit is contained in:
parent
9df16d8dc3
commit
ef3820de79
|
@ -38,147 +38,129 @@ local function node_qualifies_for_sponging(scanpos, criteria)
|
|||
return qualified, liquidclass, planstep
|
||||
end
|
||||
|
||||
-- absorption pattern 2: sponge reaches out to 7 blocks (taxicab distance).
|
||||
-- absorption pattern 2: sponge reaches out to 7 blocks (taxicab distance) along contiguous blocks of liquid.
|
||||
-- absorbs maximum 135 blocks, closest blocks first.
|
||||
--- bounds of check encompasses 15*15*15=3375 blocks; octahedral volume is 2703(?) blocks.
|
||||
--- upwards of 2703 get_node() calls, and 135 set_node() calls.
|
||||
--- may warrant use of voxel manipulator.
|
||||
-- boundary encompasses 15*15*15=3375 blocks; octahedral volume is 2703(?) blocks.
|
||||
-- upwards of 2703 get_node() calls, and 135 set_node() calls.
|
||||
-- may warrant use of voxel manipulator.
|
||||
-- returns 2 values: table, string
|
||||
local function absorption_strategy2(pos, criteria)
|
||||
local plan = {}
|
||||
local affected = 0
|
||||
local scores = {} -- table{score,pos,node}
|
||||
local adjacency = {} -- adjacency graph, such that adjacency[x][y][z] -> { d=contiguous-distance } or "" for no contiguous path
|
||||
local r = 7
|
||||
|
||||
-- FIXME: this algorithm is dumb and slow.
|
||||
-- adjacency graph to determine contiguous paths of liquid from sponge.
|
||||
-- Breadth-First Search.
|
||||
-- pos: world position of center of absorption (sponge).
|
||||
-- criteria: table of acceptable liquid => sponge type when absorbing that.
|
||||
local function absorption_strategy2(pos, criteria)
|
||||
-- plan is list of {pos, node}
|
||||
local plan = {}
|
||||
-- radius of search (taxicab distance).
|
||||
local r = 7
|
||||
-- remaining number of blocks that may be absorbed (countdown).
|
||||
local absorption_limit = 135
|
||||
-- visitation chart while searching continguous adjacent blocks.
|
||||
-- prevent unnecessary backtracking in BFS.
|
||||
local visited = {}
|
||||
for i=-r,r do
|
||||
adjacency[i] = {}
|
||||
visited[i] = {}
|
||||
for j=-r,r do
|
||||
adjacency[i][j] = {}
|
||||
-- now adjacency[i][j][k] valid
|
||||
visited[i][j] = {}
|
||||
-- now visited[i][j][k] valid
|
||||
end
|
||||
end
|
||||
local function scan_adjacency(graph, rx, ry, rz, d, liquidclass)
|
||||
if rx == nil and ry == nil and rz == nil then
|
||||
rx, ry, rz = 0, 0, 0
|
||||
visited[0][0][0] = true
|
||||
|
||||
-- breadth-first search for water nodes to absorb; map out contiguous blocks of liquids.
|
||||
-- FIXME: this algorithm is fairly dumb and slow.
|
||||
|
||||
-- rx,ry,rz - relative Cartesian coordinates
|
||||
local rx, ry, rz
|
||||
-- d - distance (search depth)
|
||||
local d
|
||||
-- liquidclass - type of liquid to absorb (do not absorb mismatched).
|
||||
-- start off as nil => seek a liquid to absorb.
|
||||
-- once a(n acceptable) liquid is found, absorb only that liquid.
|
||||
-- the resulting web sponge is specified in `criteria`
|
||||
local liquidclass = nil
|
||||
-- BFS visitation queue: list of { rx,ry,rz,d }
|
||||
local queue = { [1]={0,0,0,0} }
|
||||
local loop_steps = 0
|
||||
while #queue > 0 do
|
||||
loop_steps = loop_steps + 1
|
||||
-- flag to prune searching.
|
||||
local no_prune = true
|
||||
-- next node to search.
|
||||
rx, ry, rz, d = queue[1][1], queue[1][2], queue[1][3], queue[1][4]
|
||||
table.remove(queue, 1)
|
||||
-- don't scan center (the sponge).
|
||||
local nodeliquid, planstep
|
||||
if d > 0 then
|
||||
-- scan node.
|
||||
local scanpos = {x=pos.x+rx, y=pos.y+ry, z=pos.z+rz}
|
||||
no_prune, nodeliquid, planstep = node_qualifies_for_sponging(scanpos, criteria)
|
||||
end
|
||||
local v = graph[rx][ry][rz]
|
||||
local skip_scan =false
|
||||
if d == nil or d == 0 or (rx==0 and ry==0 and rz==0) then
|
||||
skip_scan = true
|
||||
d = 0
|
||||
end
|
||||
if not skip_scan then
|
||||
if v == "" then
|
||||
-- already visited, deemed non-contiguous (not liquid).
|
||||
return
|
||||
if no_prune then
|
||||
-- potentially absorbable.
|
||||
if liquidclass == nil then
|
||||
-- first liquid type found.
|
||||
liquidclass = nodeliquid
|
||||
end
|
||||
if liquidclass == nodeliquid then
|
||||
table.insert(plan, planstep)
|
||||
-- count down absorption limit (max 135 absorbed).
|
||||
absorption_limit = absorption_limit - 1
|
||||
if absorption_limit <= 0 then
|
||||
-- terminate all searches.
|
||||
no_prune, queue = false, {}
|
||||
break
|
||||
end
|
||||
else
|
||||
if v ~= nil then
|
||||
-- already visited, recheck distance.
|
||||
if d >= v.d then
|
||||
-- alternate path is worse. Terminate search.
|
||||
return
|
||||
end
|
||||
-- TODO: test case with really perverse water configuration.
|
||||
end
|
||||
-- (re)scan node.
|
||||
local scannode = minetest.get_node({x=pos.x+rx, y=pos.y+ry, z=pos.z+rz})
|
||||
local scandef = minetest.registered_nodes[scannode.name]
|
||||
if liquidclass == nil then
|
||||
-- adopt the liquidclass
|
||||
liquidclass = scandef.liquid_alternative_source
|
||||
if liquidclass == nil then
|
||||
-- not liquid, terminate search.
|
||||
graph[rx][ry][rz] = ""
|
||||
return
|
||||
end
|
||||
elseif liquidclass ~= scandef.liquid_alternative_source then
|
||||
-- change of substance. Leave blank, terminate search.
|
||||
return
|
||||
end
|
||||
-- contiguous.
|
||||
graph[rx][ry][rz] = { d=d, liquidclass=liquidclass }
|
||||
-- mismatched liquid (no go); prune search.
|
||||
no_prune = false
|
||||
end
|
||||
end
|
||||
-- adjacent nodes.
|
||||
if d < r then
|
||||
if rx > -r then
|
||||
scan_adjacency(graph, rx-1, ry, rz, d+1, liquidclass)
|
||||
end
|
||||
if rx < r then
|
||||
scan_adjacency(graph, rx+1, ry, rz, d+1, liquidclass)
|
||||
if d < r and no_prune then
|
||||
-- prioritize top to bottom.
|
||||
if ry < r then
|
||||
if not visited[rx][ry+1][rz] then
|
||||
visited[rx][ry+1][rz] = true
|
||||
table.insert(queue, {rx, ry+1, rz, d+1})
|
||||
end
|
||||
end
|
||||
if ry > -r then
|
||||
scan_adjacency(graph, rx, ry-1, rz, d+1, liquidclass)
|
||||
if not visited[rx][ry-1][rz] then
|
||||
visited[rx][ry-1][rz] = true
|
||||
table.insert(queue, {rx, ry-1, rz, d+1})
|
||||
end
|
||||
end
|
||||
if ry < r then
|
||||
scan_adjacency(graph, rx, ry+1, rz, d+1, liquidclass)
|
||||
-- prioritize north to south.
|
||||
if rz < r then
|
||||
if not visited[rx][ry][rz+1] then
|
||||
visited[rx][ry][rz+1] = true
|
||||
table.insert(queue, {rx, ry, rz+1, d+1})
|
||||
end
|
||||
end
|
||||
if rz > -r then
|
||||
scan_adjacency(graph, rx, ry, rz-1, d+1, liquidclass)
|
||||
if not visited[rx][ry][rz-1] then
|
||||
visited[rx][ry][rz-1] = true
|
||||
table.insert(queue, {rx, ry, rz-1, d+1})
|
||||
end
|
||||
end
|
||||
if rz < r then
|
||||
scan_adjacency(graph, rx, ry, rz+1, d+1, liquidclass)
|
||||
-- prioritize east to west.
|
||||
if rx < r then
|
||||
if not visited[rx+1][ry][rz] then
|
||||
visited[rx+1][ry][rz] = true
|
||||
table.insert(queue, {rx+1, ry, rz, d+1})
|
||||
end
|
||||
end
|
||||
else
|
||||
return
|
||||
end
|
||||
end
|
||||
-- map out contiguous blocks of liquids.
|
||||
scan_adjacency(adjacency, 0, 0, 0)
|
||||
|
||||
-- collect the contiguous blocks of liquids for sponging.
|
||||
for i=-r,r do
|
||||
local YZ = adjacency[i]
|
||||
for j=-r,r do
|
||||
local Z = YZ[j]
|
||||
for k=-r,r do
|
||||
local v = Z[k]
|
||||
if v == nil then
|
||||
-- not reachable.
|
||||
elseif v == "" then
|
||||
-- not contiguous.
|
||||
elseif (v.d <= r) then
|
||||
-- consider.
|
||||
local scanpos = { x=pos.x+i, y=pos.y+j, z=pos.z+k }
|
||||
local qualified, liquidclass, nodeplan = node_qualifies_for_sponging(scanpos, criteria)
|
||||
if qualified then
|
||||
-- Scoring algorithm to prioritize blocks from which to pick 135 for absorption.
|
||||
-- Could use improvement here. Right now, it's rather sloppy, but it's a start. -PH
|
||||
local score = v.d * 8*r*r*r
|
||||
score = score + ((-j)*4*r*r)
|
||||
score = score + ((-i)*2*r)
|
||||
score = score + (-k)
|
||||
local entry = { score=score, liquidclass=liquidclass, nodeplan=nodeplan }
|
||||
table.insert(scores, entry)
|
||||
end
|
||||
if rx > -r then
|
||||
if not visited[rx-1][ry][rz] then
|
||||
visited[rx-1][ry][rz] = true
|
||||
table.insert(queue, {rx-1, ry, rz, d+1})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local new_spongename
|
||||
if #scores > 0 then
|
||||
-- sort by score.
|
||||
table.sort(scores, function(a,b) return a.score < b.score end)
|
||||
-- maximum 135 blocks absorbed by sponge.
|
||||
local quota = 135
|
||||
-- absorb "nearest" liquid type.
|
||||
local filterclass = scores[1].liquidclass
|
||||
for i=1,#scores do
|
||||
if scores[i].liquidclass == filterclass then
|
||||
local nodeplan = scores[i].nodeplan
|
||||
table.insert(plan, nodeplan)
|
||||
quota = quota - 1
|
||||
end
|
||||
if quota <= 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
new_spongename = criteria[filterclass]
|
||||
end
|
||||
-- determine the wet sponge from the liquid type absorbed.
|
||||
local new_spongename = criteria[liquidclass]
|
||||
return plan, new_spongename
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue