feat(marksilo): ren planlægger — kvalificering, fuldeste først, x-loft, vogn- og lagervalg med reservation
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
-- AD Field Silo Planner
|
||||
-- Ren planlægning for marksilo-flowet: hvilke (silo, vare) skal hjem, hvor mange lånte vogne sendes, og til
|
||||
-- hvilket gårdlager. Ingen spil-kald; input muteres ikke.
|
||||
|
||||
ADFieldSiloPlanner = {}
|
||||
|
||||
function ADFieldSiloPlanner.key(siloId, fillType)
|
||||
return tostring(siloId) .. ":" .. tostring(fillType)
|
||||
end
|
||||
local key = ADFieldSiloPlanner.key
|
||||
|
||||
local function dist2(a, b)
|
||||
return ((a.x or 0) - (b.x or 0)) ^ 2 + ((a.z or 0) - (b.z or 0)) ^ 2
|
||||
end
|
||||
|
||||
local function freeIn(store, fillType, reserved)
|
||||
return ((store.free or {})[fillType] or 0) - (reserved[store.id .. ":" .. tostring(fillType)] or 0)
|
||||
end
|
||||
|
||||
local function anyStoreTakes(stores, fillType, minLiters, reserved)
|
||||
for _, store in ipairs(stores or {}) do
|
||||
if store.markerId ~= nil and freeIn(store, fillType, reserved) >= minLiters then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Nærmeste lager med plads til hele turen; ellers det med mest plads (over tærsklen).
|
||||
local function chooseStore(stores, silo, fillType, liters, minLiters, reserved)
|
||||
local best, bestD, most, mostFree = nil, nil, nil, nil
|
||||
for _, store in ipairs(stores or {}) do
|
||||
local free = freeIn(store, fillType, reserved)
|
||||
if store.markerId ~= nil and free >= minLiters then
|
||||
local d = dist2(store, silo)
|
||||
if free >= liters and (bestD == nil or d < bestD) then best, bestD = store, d end
|
||||
if mostFree == nil or free > mostFree then most, mostFree = store, free end
|
||||
end
|
||||
end
|
||||
return best or most
|
||||
end
|
||||
|
||||
local function canCarry(vehicle, fillType)
|
||||
if (vehicle.capacity or 0) <= 0 then return false end
|
||||
if vehicle.cargo ~= nil and vehicle.cargo ~= fillType then return false end
|
||||
return (vehicle.carries or {})[fillType] == true
|
||||
end
|
||||
|
||||
local function nearestVehicle(vehicles, used, silo, fillType)
|
||||
local best, bestD = nil, nil
|
||||
for _, vehicle in ipairs(vehicles or {}) do
|
||||
if not used[vehicle.id] and canCarry(vehicle, fillType) then
|
||||
local d = dist2(vehicle, silo)
|
||||
if bestD == nil or d < bestD then best, bestD = vehicle, d end
|
||||
end
|
||||
end
|
||||
return best
|
||||
end
|
||||
|
||||
function ADFieldSiloPlanner.plan(input)
|
||||
local minLiters = input.minLiters or 10000
|
||||
local reserved, onWay = {}, {}
|
||||
for _, loan in ipairs(input.loans or {}) do
|
||||
local k = key(loan.siloId, loan.fillType)
|
||||
onWay[k] = (onWay[k] or 0) + (loan.liters or 0)
|
||||
end
|
||||
local status, tasks = {}, {}
|
||||
for _, silo in ipairs(input.silos or {}) do
|
||||
for fillType, liters in pairs(silo.stock or {}) do
|
||||
if (liters or 0) >= minLiters then
|
||||
local k = key(silo.id, fillType)
|
||||
local entry = {siloId = silo.id, siloName = silo.name, fillType = fillType, liters = liters, onWay = onWay[k] or 0}
|
||||
if silo.markerId == nil then
|
||||
entry.state = "noMarker"
|
||||
elseif not anyStoreTakes(input.stores, fillType, minLiters, reserved) then
|
||||
entry.state = "noFarmStore"
|
||||
elseif (input.paused or {})[k] then
|
||||
entry.state = "paused"
|
||||
else
|
||||
entry.state = "ok"
|
||||
local rest = liters - entry.onWay
|
||||
if rest >= minLiters then table.insert(tasks, {silo = silo, fillType = fillType, rest = rest}) end
|
||||
end
|
||||
table.insert(status, entry)
|
||||
end
|
||||
end
|
||||
end
|
||||
table.sort(tasks, function(a, b)
|
||||
if a.rest ~= b.rest then return a.rest > b.rest end
|
||||
if tostring(a.silo.id) ~= tostring(b.silo.id) then return tostring(a.silo.id) < tostring(b.silo.id) end
|
||||
return a.fillType < b.fillType
|
||||
end)
|
||||
table.sort(status, function(a, b)
|
||||
if tostring(a.siloId) ~= tostring(b.siloId) then return tostring(a.siloId) < tostring(b.siloId) end
|
||||
return a.fillType < b.fillType
|
||||
end)
|
||||
local freeSlots = math.max(0, (input.maxVehicles or 2) - #(input.loans or {}))
|
||||
local used, dispatch = {}, {}
|
||||
for _, task in ipairs(tasks) do
|
||||
while freeSlots > 0 and task.rest >= minLiters do
|
||||
local vehicle = nearestVehicle(input.vehicles, used, task.silo, task.fillType)
|
||||
if vehicle == nil then break end
|
||||
local liters = math.min(vehicle.capacity, task.rest)
|
||||
local store = chooseStore(input.stores, task.silo, task.fillType, liters, minLiters, reserved)
|
||||
if store == nil then break end
|
||||
used[vehicle.id] = true
|
||||
local reserveKey = store.id .. ":" .. tostring(task.fillType)
|
||||
reserved[reserveKey] = (reserved[reserveKey] or 0) + liters
|
||||
table.insert(dispatch, {vehicleId = vehicle.id, siloId = task.silo.id, siloMarkerId = task.silo.markerId,
|
||||
fillType = task.fillType, storeId = store.id, storeMarkerId = store.markerId, liters = liters})
|
||||
task.rest = task.rest - liters
|
||||
freeSlots = freeSlots - 1
|
||||
end
|
||||
end
|
||||
return {dispatch = dispatch, status = status, freeSlots = freeSlots}
|
||||
end
|
||||
Reference in New Issue
Block a user