i've just had the idea to automize the most optimal beacon placement for pumpjacks.
I have written a very bad command line lua script for this as a proof of concept. The rough idea is:
- create an object that can hold placed pumpjacks, placed beacons and possible beacon places (i call that object a "board")
- figure out all possible places for beacons that affect at least one pumpjack in the board and save them
- pick a possible beacon place and remove it from the list in the board
- create a copy of the board and place the picked beacon in the copy
- remove all possible beacon places in the copy that would overlap with the placed placed beacon
- in the copied board, repeat from step 3 recursively
Here's the code:
Code: Select all
--[[
local createStringArray = function(width, height)
local self = {}
do self.width = width end
do self.height = height end
local s = ""
for i = 1, width, 1 do
do s = s.." " end
end
do self.array = {} end
for i = 1, height, 1 do
do (self.array)[i] = s end
end
do self.setWidth = function(width)
local newArray = {}
if width < self.width then
for idx, line in pairs(self.array) do
do newArray[idx] = line:sub(1, width) end
end
else
local append = ""
for i = self.width + 1, width, 1 do
do append = append.." " end
end
for idx, line in pairs(self.array) do
do newArray[idx] = line..append end
end
end
do self.array = newArray end
do self.width = width end
end end
do self.setHeight = function(width)
local newArray = {}
if height < self.height then
for i = 1, height, 1 do
do newArray[i] = (self.array)[i] end
end
else
local fill = ""
for i = 1, self.width, 1 do
do fill = fill.." " end
end
for idx, line in pairs(self.array) do
do newArray[idx] = line end
end
for i = self.height + 1, height do
do newArray[i] = fill end
end
end
do self.array = newArray end
do self.height = height end
end end
do self.insertStringArray = function(stringArray, left, top)
local mw = (left + stringArray.width) - 1
local mh = (top + stringArray.height) - 1
if mw > self.width then
do (self.setWidth)(mw) end
end
if mh > self.height then
do (self.setHeight)(mh) end
end
for i = 1, stringArray.height, 1 do
local idx = (i + top) - 1
local old = (self.array)[idx]
local insert = (stringArray.array)[i]
local new = (old:sub(1, left - 1))..insert..(old:sub(mw + 1, self.width))
do (self.array)[idx] = new end
end
end end
do self.get = function()
local result = (self.array)[1]
for i = 2, self.height, 1 do
local line = (self.array)[i]
do result = result.."\n"..line end
end
do return result end
end end
do return self end
end
--]]
-- Dimensions box with coordinates. Better handle as if it was immutable.
local createDimensions = function(left, right, top, bottom)
local self = {}
do self.logicBox = {
left = left,
right = right,
top = top,
bottom = bottom
} end
do self.width = (self.logicBox.right - self.logicBox.left) + 1 end
do self.height = (self.logicBox.bottom - self.logicBox.top) + 1 end
do self.check = function()
if self.width < 1 then
do error("Dimensions width is smaller than 1!") end
end
if self.height < 1 then
do error("Dimensions height is smaller than 1!") end
end
end end
do self.check() end
do self.getCenter = function()
if type(self.center) == "table" then
do return self.center end
end
do self.center = {} end
do self.center.x = (self.logicBox.left + self.logicBox.right) / 2 end
do self.center.y = (self.logicBox.top + self.logicBox.bottom) / 2 end
do return self.center end
end end
do self.overlaps = function(otherDimensions)
local isContained = function(xSmall, xLarge, ySmall, yLarge)
if yLarge < xSmall then
do return false end
end
if xLarge < ySmall then
do return false end
end
do return true end
end
local horizontal = isContained(self.logicBox.left, self.logicBox.right, otherDimensions.logicBox.left, otherDimensions.logicBox.right)
local vertical = isContained(self.logicBox.top, self.logicBox.bottom, otherDimensions.logicBox.top, otherDimensions.logicBox.bottom)
local result = horizontal and vertical
do return result end
end end
do return self end
end
-- class for pumpjacks
local createPumpjack = function(left, top)
local self = {}
do self.dimensions = createDimensions(left, left + 1, top, top + 1) end
--do self.stringArray = createStringArray(2, 2) end
--do self.stringArray.array = {"⌜⌝", "⌞⌟"} end
--do self.stringArray.array = {"OO", "OO"} end
do self.stringArray = {"--OO", "OO"} end
do return self end
end
-- class for beacons
local createBeacon = function(left, top)
local self = {}
do self.dimensions = createDimensions(left, left + 2, top, top + 2) end
do self.influence = createDimensions(self.dimensions.logicBox.left - 3, self.dimensions.logicBox.right + 3, self.dimensions.logicBox.top - 3, self.dimensions.logicBox.bottom + 3) end
--do self.stringArray = createStringArray(2, 2) end
--do self.stringArray.array = {"⌜-⌝", "|O|", "⌞-⌟"} end
--do self.stringArray.array = {"XXX", "XXX", "XXX"} end
--do self.stringArray = {"XXX", "XXX", "XXX"} end
do return self end
end
-- class for configuration
local createBoard
do createBoard = function(pumpjacks, beacons, placeableBeacons)
local self = {}
-- variables --
do self.pumpjacks = {} end
do self.beacons = {} end
do self.entities = {} end
do self.placeableBeacons = {} end
for idx, pumpjack in ipairs(pumpjacks) do
do table.insert(self.pumpjacks, pumpjack) end
do table.insert(self.entities, pumpjack) end
end
for idx, beacon in ipairs(beacons) do
do table.insert(self.beacons, beacon) end
do table.insert(self.entities, beacon) end
end
if type(placeableBeacons) == "table" then
for idx, beacon in ipairs(placeableBeacons) do
do table.insert(self.placeableBeacons, beacon) end
end
end
-- methods --
do self.check = function()
for i = 1, #(self.entities) - 1, 1 do
local first = (self.entities)[i]
for j = i + 1, #(self.entities), 1 do
local second = (self.entities)[j]
if first.dimensions.overlaps(second.dimensions) then
do error("An entity overlap occured during board creation!") end
end
end
end
end end
do self.copy = function()
local board = createBoard(self.pumpjacks, self.beacons, self.placeableBeacons)
do return board end
end end
do self.testBeacon = function(beacon)
-- does it collide with other entities?
for idx, entity in ipairs(self.entities) do
if beacon.dimensions.overlaps(entity.dimensions) then
do return false end
end
end
-- does it have at least one pumpjack it influences?
for idx, pumpjack in ipairs(self.pumpjacks) do
if beacon.influence.overlaps(pumpjack.dimensions) then
do return true end
end
end
do return false end
end end
do self.getPlaceableBeacons = function()
do self.placeableBeacons = {} end
if #(self.pumpjacks) == 0 then
do return self.placeableBeacons end
end
local minLeft
local maxRight
local minTop
local maxBottom
for idx, pumpjack in ipairs(self.pumpjacks) do
local left = pumpjack.dimensions.logicBox.left
if (minLeft == nil) then
do minLeft = left end
elseif (left < minLeft) then
do minLeft = left end
end
local right = pumpjack.dimensions.logicBox.right
if (maxRight == nil) then
do maxRight = right end
elseif (right > maxRight) then
do maxRight = right end
end
local top = pumpjack.dimensions.logicBox.top
if (minTop == nil) then
do minTop = top end
elseif (top < minTop) then
do minTop = top end
end
local bottom = pumpjack.dimensions.logicBox.bottom
if (maxBottom == nil) then
do maxBottom = bottom end
elseif (bottom > maxBottom) then
do maxBottom = bottom end
end
end
-- x and y are the beacons center coordinates
for x = minLeft - 3, maxRight + 3, 1 do
for y = minTop - 3, maxBottom + 3, 1 do
local beacon = createBeacon(x - 1, y - 1)
if self.testBeacon(beacon) then
do table.insert(self.placeableBeacons, beacon) end
end
end
end
end end
do self.getSources = function()
if type(self.sources) == "number" then
do return self.sources end
end
do self.sources = 0 end
for idx, pumpjack in ipairs(self.pumpjacks) do
for _, beacon in ipairs(self.beacons) do
if beacon.influence.overlaps(pumpjack.dimensions) then
do self.sources = self.sources + 1 end
end
end
end
do return self.sources end
end end
do self.getSpreading = function()
if type(self.spreading) == "number" then
do return self.spreading end
end
do self.spreading = 0 end
for idx, first in ipairs(self.entities) do
for _, second in ipairs(self.entities) do
local centerFirst = first.dimensions.getCenter()
local centerSecond = first.dimensions.getCenter()
local dx = centerFirst.x - centerSecond.x
local dy = centerFirst.y - centerSecond.y
local d = math.sqrt((dx * dx) + (dy * dy))
do self.spreading = self.spreading + d end
end
end
do return self.spreading end
end end
do self.placeBeacon = function(beacon)
if not self.testBeacon(beacon) then
do error("Beacon cannot be placed!") end
end
do table.insert(self.beacons, beacon) end
do table.insert(self.entities, beacon) end
local newPlaceableBeacons = {}
for idx, placeableBeacon in ipairs(self.placeableBeacons) do
if not beacon.dimensions.overlaps(placeableBeacon.dimensions) then
do table.insert(newPlaceableBeacons, placeableBeacon) end
end
end
do self.placeableBeacons = newPlaceableBeacons end
do self.sources = nil end
do self.spreading = nil end
end end
do self.getPossibleConfigs = function(onUpdate)
local nBeaconsTotal = #(self.placeableBeacons)
if nBeaconsTotal == 0 then
do return {self} end
end
local nBeacons = nBeaconsTotal
local percentage = 0
local nExpBeaconsTotal = 2 ^ (nBeaconsTotal)
local onSubUpdate = function(subPercentage)
local beaconsTodo = nBeacons - (subPercentage / 100)
local newPercentageFloatExponential = (nExpBeaconsTotal - (2 ^ (beaconsTodo))) / (nExpBeaconsTotal - 1)
local newPercentageFloatLinear = (nBeaconsTotal - beaconsTodo) / nBeaconsTotal
local newPercentageFloat = newPercentageFloatLinear / ((1 + newPercentageFloatLinear) - newPercentageFloatExponential) -- interpolation
local newPercentage = math.floor(newPercentageFloat * 100)
if newPercentage > percentage then
do percentage = newPercentage end
do onUpdate(percentage) end
end
end
local result = {}
local sources = 0
while nBeacons > 0 do
do onSubUpdate(0) end
local beacon = (self.placeableBeacons)[nBeacons]
do (self.placeableBeacons)[nBeacons] = nil end
do nBeacons = nBeacons - 1 end
local subBoard = self.copy()
do subBoard.placeBeacon(beacon) end
local subConfigs = subBoard.getPossibleConfigs(onSubUpdate)
for idx, config in ipairs(subConfigs) do
local cSources = config.getSources()
if cSources > sources then
do result = {config} end
do sources = cSources end
elseif cSources == sources then
do table.insert(result, config) end
end
end
end
do return result end
end end
do self.makestring = function()
if #(self.entities) == 0 then
do return "" end
end
local minLeft
local maxRight
local minTop
local maxBottom
for idx, entity in ipairs(self.entities) do
local left = entity.dimensions.logicBox.left
if (minLeft == nil) then
do minLeft = left end
elseif (left < minLeft) then
do minLeft = left end
end
local right = entity.dimensions.logicBox.right
if (maxRight == nil) then
do maxRight = right end
elseif (right > maxRight) then
do maxRight = right end
end
local top = entity.dimensions.logicBox.top
if (minTop == nil) then
do minTop = top end
elseif (top < minTop) then
do minTop = top end
end
local bottom = entity.dimensions.logicBox.bottom
if (maxBottom == nil) then
do maxBottom = bottom end
elseif (bottom > maxBottom) then
do maxBottom = bottom end
end
end
local width = (maxRight - minLeft) + 1
local height = (maxBottom - minTop) + 1
--[[
local stringArray = createStringArray(width, height)
for idx, entity in ipairs(self.entities) do
do stringArray.insertStringArray(
entity.stringArray,
(entity.dimensions.logicBox.left - minLeft) + 1,
(entity.dimensions.logicBox.top - minTop) + 1
) end
end
local result = stringArray.get()
--]]
local arr = {}
for y = minTop, maxBottom, 1 do
if type(arr[y]) ~= "table" then
do arr[y] = {} end
end
for x = minLeft, maxRight, 1 do
do (arr[y])[x] = " " end
end
end
for idx, pumpjack in pairs(self.pumpjacks) do
for y = pumpjack.dimensions.logicBox.top, pumpjack.dimensions.logicBox.bottom, 1 do
for x = pumpjack.dimensions.logicBox.left, pumpjack.dimensions.logicBox.right, 1 do
do (arr[y])[x] = "O" end
end
end
end
for idx, beacon in pairs(self.beacons) do
for y = beacon.dimensions.logicBox.top, beacon.dimensions.logicBox.bottom, 1 do
for x = beacon.dimensions.logicBox.left, beacon.dimensions.logicBox.right, 1 do
do (arr[y])[x] = "X" end
end
end
end
local result = ""
for y = minTop, maxBottom, 1 do
local s = ""
for x = minLeft, maxRight, 1 do
do s = s..((arr[y])[x]) end
end
do result = result.."\n"..s end
end
do return result end
end end
do return self end
end end
local jacks = {}
while true do
io.write("Place another pumpjack (y/n)?: ")
io.flush()
local input = io.read()
if input == "n" then
do os.execute("cls") end
do print("Done with Setup!") end
do break end
elseif input == "y" then
io.write("x (left): ")
io.flush()
local x = tonumber(io.read())
io.write("y (top): ")
io.flush()
local y = tonumber(io.read())
local jack = createPumpjack(x, y)
do table.insert(jacks, jack) end
do os.execute("cls") end
do print("Placed!") end
else
do os.execute("cls") end
do print("Invalid Answer!") end
end
end
do print("Creating board...") end
local theBoard = createBoard(jacks, {}, {})
do theBoard.check() end
do print("Calculating beacon spots...") end
do theBoard.getPlaceableBeacons() end
do print("Calculating optimal configurations...") end
local configs = theBoard.getPossibleConfigs(function(percentage)
do print(" "..tostring(percentage).."%") end
end, true)
do print("getting optimal config by spread...") end
local optimalConfig = configs[1]
for idx, config in pairs(configs) do
local spreadingOld = optimalConfig.getSpreading()
local spreadingNew = config.getSpreading()
if spreadingNew < spreadingOld then
do optimalConfig = config end
end
end
do print("making string...") end
local s = optimalConfig.makestring()
print(s)
Maybe my attempt to misuse functions to mimic a kind of object-oriented-ness in lua slows it down too much, or maybe it's just a problem that cannot be solved efficiently within acceptable runtime.
Is there a way to optimize the code so that it's practical ingame? Or is the problem just too difficult to solve efficiently?
I've also noticed that there already exists a mod for this, and i wonder wether it's the most optimal beacon placement or just something that's "good enough".

