Module:BATTD costs: Difference between revisions

From Blooncyclopedia, the independent Bloons knowledge base
Jump to navigation Jump to search
saving progress
mNo edit summary
 
(18 intermediate revisions by the same user not shown)
Line 1: Line 1:
local p = {}
local p = {}


-- LOCAL FUNCTIONS
-- helper functions


local function GetTotalCosts(baseCost, initialPurchasedUpgrades, accessibleUpgrades, upgradesById, upgradeLocksById, upgradePrereqsById, upgradePrereqsOfById, Y, N)
local function GetTotalCosts(baseCost, initialPurchasedUpgrades, accessibleUpgrades, startingUpgrade,
upgradesById, upgradeLocksById, upgradePrereqsById, upgradePrereqsOfById, Y, N)
local sFormat = string.format
local sFormat = string.format
local tConcat = table.concat
local tConcat = table.concat
local sGsub = string.gsub
local sGsub = string.gsub
-- create the table headers
local headerRow = {"{|class=\"wikitable sortable\" style=\"text-align:center\"\n!Total cost!!Sell value"}
local headerRow = {"{|class=\"wikitable sortable\" style=\"text-align:center\"\n!Total cost!!Sell value"}
mw.logObject(accessibleUpgrades)
for upgradeId, _ in pairs(accessibleUpgrades) do
for upgradeId, _ in ipairs(accessibleUpgrades) do
headerRow[#headerRow+1] = sFormat("!![[%s|%s]]", upgradesById[upgradeId]._pageName, upgradesById[upgradeId].name)
headerRow[#headerRow+1] = sFormat("!![[%s|%s]]", upgradesById[upgradeId]._pageName, upgradesById[upgradeId].name)
end
end
Line 31: Line 31:
end
end
local function GetPurchasedAndAvailableUpgrades(purchasedUpgrades, availableUpgradeId)
local function RecursiveGetTotalCosts(currentCost, purchasedUpgrades, myUpgradeId)
-- skip this upgrade if it is a leaf
-- skip this upgrade if we don't have all the prereqs
if not upgradePrereqsOfById[availableUpgradeId] then return nil, nil end
for _, prereqId in ipairs(upgradePrereqsById[myUpgradeId]) do
if not purchasedUpgrades[prereqId] then return false end
end
-- skip this upgrade if we don't have all the prereqs
-- skip this upgrade if it's locked by an already purchased upgrade
if upgradePrereqsById[availableUpgradeId] then
for purchasedUpgradeId, _ in pairs(purchasedUpgrades) do
for _, prereqId in ipairs(upgradePrereqsById[availableUpgradeId]) do
if upgradeLocksById[purchasedUpgradeId][myUpgradeId] then return false end
if not purchasedUpgrades[prereqId] then
return nil, nil
end
end
end
end
-- shallow copy purchasedUpgrades and availableUpgrades
--upgradeLocksById[startingUpgrade][myUpgradeId] = true
local purchasedUpgradesNext = {}
local availableUpgradesNext = {}
-- shallow copy purchasedUpgrades but with this upgrade included in it now
local purchasedUpgradesNext = { [myUpgradeId] = true }
for purchasedUpgradeId, _ in pairs(purchasedUpgrades) do
for purchasedUpgradeId, _ in pairs(purchasedUpgrades) do
if upgradeLocksById[purchasedUpgradeId] and upgradeLocksById[purchasedUpgradeId][availableUpgradeId] then
return nil, nil
end
purchasedUpgradesNext[purchasedUpgradeId] = true
purchasedUpgradesNext[purchasedUpgradeId] = true
for nextUpgradeId, _ in pairs(upgradePrereqsOfById[purchasedUpgradeId]) do
if not upgradeLocksById[purchasedUpgradeId] or not upgradeLocksById[purchasedUpgradeId][nextUpgradeId] then
availableUpgradesNext[nextUpgradeId] = true
end
end
end
end
purchasedUpgradesNext[availableUpgradeId] = true
-- add row to output
row = {sFormat("|-\n|$%s||$%s", FormatNum(currentCost), FormatNum(currentCost*0.7))}
return purchasedUpgradesNext, availableUpgradesNext
for accessibleUpgradeId, _ in pairs(accessibleUpgrades) do
end
row[#row+1] = sFormat("||%s", purchasedUpgradesNext[accessibleUpgradeId] and Y or N)
end
local function RecursiveGetTotalCosts(currentCost, purchasedUpgrades, availableUpgrades)
row = {sFormat("\n|-\n|$%s||$%s", FormatNum(currentCost), FormatNum(currentCost*0.7))}
--mw.logObject(purchasedUpgrades)
outputTable[#outputTable+1] = tConcat(row, "")
--mw.logObject(availableUpgrades)
for upgradeId, _ in pairs(accessibleUpgrades) do
-- depth first
row[#row+1] = sFormat("||%s", purchasedUpgrades[upgradeId] and Y or N)
for _, prereqOfId in ipairs(upgradePrereqsOfById[myUpgradeId]) do
RecursiveGetTotalCosts(currentCost + upgradesById[prereqOfId].cost, purchasedUpgradesNext, prereqOfId)
end
end
outputTable[#outputTable+1] = tConcat(row, "")
-- breadth next
for purchasedUpgradeId, _ in pairs(purchasedUpgrades) do
for availableUpgradeId, _ in pairs(availableUpgrades) do
for _, prereqOfId in ipairs(upgradePrereqsOfById[purchasedUpgradeId]) do
local purchasedUpgradesNext, availableUpgradesNext = GetPurchasedAndAvailableUpgrades(purchasedUpgrades, availableUpgradeId)
--RecursiveGetTotalCosts(currentCost + upgradesById[prereqOfId].cost, purchasedUpgradesNext, prereqOfId)
if purchasedUpgradesNext and availableUpgradesNext then
purchasedUpgradesNext[availableUpgradeId] = true
--RecursiveGetTotalCosts(currentCost + upgradesById[availableUpgradeId].cost, purchasedUpgradesNext, availableUpgradesNext)
RecursiveGetTotalCosts(currentCost + upgradesById[availableUpgradeId].cost, purchasedUpgradesNext, availableUpgradesNext)
end
end
end
end
return true
end
end
local rootUpgrades = {}
RecursiveGetTotalCosts(baseCost, initialPurchasedUpgrades, startingUpgrade)
for _, upgradeId in ipairs(upgradePrereqsOfById.root) do
rootUpgrades[upgradeId] = true
end
RecursiveGetTotalCosts(baseCost, initialPurchasedUpgrades, rootUpgrades)
outputTable[#outputTable+1] = "\n|}"
outputTable[#outputTable+1] = "|}"
return tConcat(outputTable, "")
return tConcat(outputTable, "\n")
end
end


local function Upgrade(towerName, upgradeName, Y, N)
local function SetUpTables(towerName)
local sFormat = string.format
local sFormat = string.format
local query = mw.ext.cargo.query
local query = mw.ext.cargo.query
local outputTable = {}
local upgradesById = { root = {} } -- key:value table  - upgrade:upgrade data
local upgradeId = ""
local upgradeLocksById = { root = {} } -- key:values table - upgrades:upgrades that this upgrade locks
local upgradePrereqsById = { root = {} } -- key:values table - upgrade:prerequisites to purchase this upgrade
local upgradePrereqsOfById = { root = {} } -- key:values table - upgrades:upgrades this is a prerequisite of
local upgradesById = {} -- key:value table  - upgrade:upgrade data
-- tower cost + total cost of this upgrade and all its prereqs
local upgradeLocksById = {} -- key:values table - upgrades:upgrades that this upgrade locks
local baseCost = tonumber(query("battd_characters", "cost", {
local upgradePrereqsById = {} -- key:values table - upgrade:prerequisites to purchase this upgrade
where=sFormat("name='%s'", towerName)
local upgradePrereqsOfById = { -- key:values table - upgrades:upgrades this is a prerequisite of
})[1].cost)
root = {}
}
local purchasedUpgrades = {}
local accessibleUpgrades = {} -- list of upgrades that we can get from this upgrade
-- cargo queries for data
-- cargo queries for data
Line 125: Line 103:
}),
}),
query("battd_upgrades=main, battd_upgrades__previous=prev", "main.id=thisId, prev._value=prevId", {
query("battd_upgrades=main, battd_upgrades__previous=prev", "main.id=thisId, prev._value=prevId", {
where=sFormat("main.tower='%s'", towerName),
where=sFormat("main.tower='%s' AND NOT unused", towerName),
join="main._ID=prev._RowID"
join="main._ID=prev._RowID"
}),
}),
query("battd_upgrades=main, battd_upgrades__locked_by_upgrades=lock", "main.id=thisId, lock._value=lockId", {
query("battd_upgrades=main, battd_upgrades__locked_by_upgrades=lock", "main.id=thisId, lock._value=lockId", {
where=sFormat("main.tower='%s'", towerName),
where=sFormat("main.tower='%s' AND NOT unused", towerName),
join="main._ID=lock._RowID"
join="main._ID=lock._RowID"
})
})
Line 136: Line 114:
for i, upgrade in ipairs(upgrades) do
for i, upgrade in ipairs(upgrades) do
upgradesById[upgrade.id] = upgrade
upgradesById[upgrade.id] = upgrade
if upgrade.name == upgradeName then upgradeId = upgrade.id end
upgradeLocksById[upgrade.id] = {}
end
end


Line 149: Line 127:
end
end
else
else
-- if this upgrade is a root upgrade
-- if this upgrade has no prereqs, give it a fake upgrade called "root" as a prereq
upgradePrereqsOfById["root"][#upgradePrereqsOfById["root"]+1] = upgrade.thisId
upgradePrereqsOfById["root"][#upgradePrereqsOfById["root"]+1] = upgrade.thisId
upgradePrereqsById[upgrade.thisId] = {"root"}
upgradePrereqsById[upgrade.thisId] = {"root"}
Line 163: Line 141:


-- build upgradeLocksById
-- build upgradeLocksById
for i, upgrade in ipairs(upgradeLocks) do
for _, upgrade in ipairs(upgradeLocks) do
if upgrade.lockId then
if upgrade.lockId then
if upgradeLocksById[upgrade.thisId] then
upgradeLocksById[upgrade.thisId][upgrade.lockId] = true
upgradeLocksById[upgrade.thisId][upgrade.lockId] = true
else
upgradeLocksById[upgrade.thisId] = {[upgrade.lockId] = true}
end
end
end
end
end
return upgradesById, upgradeLocksById, upgradePrereqsById, upgradePrereqsOfById, baseCost
end
local function Upgrade(towerName, upgradeId, Y, N)
local upgradesById, upgradeLocksById, upgradePrereqsById, upgradePrereqsOfById, baseCost = SetUpTables(towerName)
local purchasedUpgrades = {} -- this upgrade and any other upgrades that had to already be purchased to get it
local accessibleUpgrades = {} -- list of upgrades that are possible to reach from this upgrade by some route
-- build alreadyPurchasedUpgrades
-- build purchasedUpgrades recursively
local function GetPurchasedUpgrades(myUpgradeId)
local function GetPurchasedUpgrades(myUpgradeId)
local cost = upgradesById[myUpgradeId].cost
if not purchasedUpgrades[myUpgradeId] then baseCost = baseCost + upgradesById[myUpgradeId].cost end
purchasedUpgrades[myUpgradeId] = true
for _, id in ipairs(upgradePrereqsById[myUpgradeId]) do
for _, prereqId in ipairs(upgradePrereqsById[myUpgradeId]) do
if not id == "root" then cost = cost + GetPurchasedUpgrades(id) end
if prereqId ~= "root" then GetPurchasedUpgrades(prereqId) end
purchasedUpgrades[prereqId] = true
end
end
return cost
end
end
-- tower cost + total cost of this upgrade and all its prereqs
GetPurchasedUpgrades(upgradeId)
local baseCost = GetPurchasedUpgrades(upgradeId) + query("battd_characters", "cost", {
where=sFormat("name='%s'", towerName)
})[1].cost
local function GetAccessibleUpgrades(myUpgradeId)
-- generate a list of upgrades that are reachable from this upgrade, but do not have this upgrade as a prerequisite
-- also generate a list of upgrades that are immediately reachable from this upgrade
local function DetermineAccessibility(myUpgradeId)
if myUpgradeId == upgradeId then return end
for _, upgrade in ipairs(upgradePrereqsOfById[myUpgradeId]) do
-- if this upgrade is locked by an already purchased upgrade, then it is inaccessible
if not purchasedUpgrades[upgrade] then
for _, upgradeLock in ipairs(upgradeLocksById[myUpgradeId]) do
if upgradeLocksById[upgrade] then
if purchasedUpgrades[upgradeLock] then return end
for _, upgradeLock in ipairs(upgradeLocksById[upgrade]) do
end
if purchasedUpgrades[upgradeLock] then return end
end
-- find upgrades that are reachable from the current upgrades owned
end
for _, prereqId in ipairs(upgradePrereqsById[myUpgradeId]) do
-- upgrades with multiple prereqs are only accessible if we can reach all its prereqs
-- upgrades with multiple prereqs are only accessible if we can reach all prereqs
-- if it is accessible, it'll be reached again on another recursion
for _, prereqId in ipairs(upgradePrereqsById[upgrade]) do
if not (accessibleUpgrades[prereqId] or purchasedUpgrades[prereqId] or prereqId == "root") or prereqId == upgradeId then return end
if not accessibleUpgrades[prereqId] then return end
end
end
if not purchasedUpgrades[myUpgradeId] then accessibleUpgrades[myUpgradeId] = true end
accessibleUpgrades[myUpgradeId] = true
-- determine accessibility of next upgrades
if upgradePrereqsOfById[upgrade] then
if upgradePrereqsOfById[myUpgradeId] then  
GetAccessibleUpgrades(upgrade)
for _, prereqOfId in ipairs(upgradePrereqsOfById[myUpgradeId]) do
end
DetermineAccessibility(prereqOfId)
end
end
end
end
end
end
GetAccessibleUpgrades("root")
 
--return ""
-- start with the root
return GetTotalCosts(baseCost, purchasedUpgrades, accessibleUpgrades, upgradesById, upgradeLocksById, upgradePrereqsById, upgradePrereqsOfById, Y, N)
for _, prereqOfId in ipairs(upgradePrereqsOfById.root) do
DetermineAccessibility(prereqOfId)
end
return GetTotalCosts(baseCost, purchasedUpgrades, accessibleUpgrades, upgradeId,
upgradesById, upgradeLocksById, upgradePrereqsById, upgradePrereqsOfById, Y, N)
end
end


Line 220: Line 207:


p["tower"] = function(frame)
p["tower"] = function(frame)
return ""
local upgradesById, upgradeLocksById, upgradePrereqsById, upgradePrereqsOfById, baseCost = SetUpTables(frame.args[1])
end
end


p["upgrade"] = function(frame)
p["upgrade"] = function(frame)
--return upgrade(frame.args[1], frame.args[2], frame:expandTemplate{title = "Y", args = {}}, frame:expandTemplate{title = "N", args = {}})
return Upgrade(frame.args[1], frame.args[2], frame:expandTemplate{title = "Y", args = {}}, frame:expandTemplate{title = "N", args = {}})
return Upgrade(frame.args[1], frame.args[2], "Y", "N")
--return Upgrade(frame.args[1], frame.args[2], "Y", "N")
end
end


return p
return p

Latest revision as of 21:59, 6 July 2026

Documentation for this module may be created at Module:BATTD costs/doc

local p = {}

-- helper functions

local function GetTotalCosts(baseCost, initialPurchasedUpgrades, accessibleUpgrades, startingUpgrade,
	upgradesById, upgradeLocksById, upgradePrereqsById, upgradePrereqsOfById, Y, N)
	local sFormat = string.format
	local tConcat = table.concat
	local sGsub = string.gsub
	
	-- create the table headers
	local headerRow = {"{|class=\"wikitable sortable\" style=\"text-align:center\"\n!Total cost!!Sell value"}
	
	for upgradeId, _ in pairs(accessibleUpgrades) do
		headerRow[#headerRow+1] = sFormat("!![[%s|%s]]", upgradesById[upgradeId]._pageName, upgradesById[upgradeId].name)
	end
	
	local outputTable = {tConcat(headerRow, "")}
	
	-- english-only alternative to FormatNum that takes up less time
	local function FormatNum(num)
	    local formatted = tostring(num)
	    
	    -- skip if less than 1000
	    local k = (num > 999 and 1 or 0)
	    while k ~= 0 do
	        formatted, k = sGsub(formatted, "^(-?%d+)(%d%d%d)", "%1,%2")
	    end
	    
	    return formatted
	end
	
	local function RecursiveGetTotalCosts(currentCost, purchasedUpgrades, myUpgradeId)
		-- skip this upgrade if we don't have all the prereqs
		for _, prereqId in ipairs(upgradePrereqsById[myUpgradeId]) do
			if not purchasedUpgrades[prereqId] then return false end
		end
		
		-- skip this upgrade if it's locked by an already purchased upgrade
		for purchasedUpgradeId, _ in pairs(purchasedUpgrades) do
			if upgradeLocksById[purchasedUpgradeId][myUpgradeId] then return false end
		end
		
		--upgradeLocksById[startingUpgrade][myUpgradeId] = true
		
		-- shallow copy purchasedUpgrades but with this upgrade included in it now
		local purchasedUpgradesNext = { [myUpgradeId] = true }
		
		for purchasedUpgradeId, _ in pairs(purchasedUpgrades) do
			purchasedUpgradesNext[purchasedUpgradeId] = true
		end
		
		-- add row to output
		row = {sFormat("|-\n|$%s||$%s", FormatNum(currentCost), FormatNum(currentCost*0.7))}
		
		for accessibleUpgradeId, _ in pairs(accessibleUpgrades) do
			row[#row+1] = sFormat("||%s", purchasedUpgradesNext[accessibleUpgradeId] and Y or N)
		end
		
		outputTable[#outputTable+1] = tConcat(row, "")
		
		-- depth first
		for _, prereqOfId in ipairs(upgradePrereqsOfById[myUpgradeId]) do
			RecursiveGetTotalCosts(currentCost + upgradesById[prereqOfId].cost, purchasedUpgradesNext, prereqOfId)
		end
		
		-- breadth next
		for purchasedUpgradeId, _ in pairs(purchasedUpgrades) do
			for _, prereqOfId in ipairs(upgradePrereqsOfById[purchasedUpgradeId]) do
				--RecursiveGetTotalCosts(currentCost + upgradesById[prereqOfId].cost, purchasedUpgradesNext, prereqOfId)
			end
		end
		
		return true
	end
	
	RecursiveGetTotalCosts(baseCost, initialPurchasedUpgrades, startingUpgrade)
	
	outputTable[#outputTable+1] = "|}"
	
	return tConcat(outputTable, "\n")
end

local function SetUpTables(towerName)
	local sFormat = string.format
	local query = mw.ext.cargo.query
	
	local upgradesById			= { root = {} }		-- key:value table  - upgrade:upgrade data
	local upgradeLocksById		= { root = {} }		-- key:values table - upgrades:upgrades that this upgrade locks
	local upgradePrereqsById	= { root = {} }		-- key:values table - upgrade:prerequisites to purchase this upgrade
	local upgradePrereqsOfById	= { root = {} }		-- key:values table - upgrades:upgrades this is a prerequisite of
	
	-- tower cost + total cost of this upgrade and all its prereqs
	local baseCost = tonumber(query("battd_characters", "cost", {
		where=sFormat("name='%s'", towerName)
	})[1].cost)
	
	-- cargo queries for data
	local upgrades, upgradePrereqs, upgradeLocks =
	query("battd_upgrades", "_pageName, id, name, tower, cost", {
		where=sFormat("tower='%s'", towerName),
		orderBy="cost"
	}),
	query("battd_upgrades=main, battd_upgrades__previous=prev", "main.id=thisId, prev._value=prevId", {
		where=sFormat("main.tower='%s' AND NOT unused", towerName),
		join="main._ID=prev._RowID"
	}),
	query("battd_upgrades=main, battd_upgrades__locked_by_upgrades=lock", "main.id=thisId, lock._value=lockId", {
		where=sFormat("main.tower='%s' AND NOT unused", towerName),
		join="main._ID=lock._RowID"
	})

	-- build upgradesById
	for i, upgrade in ipairs(upgrades) do
		upgradesById[upgrade.id] = upgrade
		upgradeLocksById[upgrade.id] = {}
	end

	-- build upgradePrereqsById, upgradePrereqsOfById
	for i, upgrade in ipairs(upgradePrereqs) do
		-- upgrade:next table
		if upgrade.prevId then
			if upgradePrereqsOfById[upgrade.prevId] then
				upgradePrereqsOfById[upgrade.prevId][#upgradePrereqsOfById[upgrade.prevId]+1] = upgrade.thisId
			else
				upgradePrereqsOfById[upgrade.prevId] = {upgrade.thisId}
			end
		else
			-- if this upgrade has no prereqs, give it a fake upgrade called "root" as a prereq
			upgradePrereqsOfById["root"][#upgradePrereqsOfById["root"]+1] = upgrade.thisId
			upgradePrereqsById[upgrade.thisId] = {"root"}
		end
		
		-- upgrade:prev table
		if upgradePrereqsById[upgrade.thisId] then
			upgradePrereqsById[upgrade.thisId][#upgradePrereqsById[upgrade.thisId]+1] = upgrade.prevId
		else
			upgradePrereqsById[upgrade.thisId] = {upgrade.prevId}
		end
	end

	-- build upgradeLocksById
	for _, upgrade in ipairs(upgradeLocks) do
		if upgrade.lockId then
			upgradeLocksById[upgrade.thisId][upgrade.lockId] = true
		end
	end

	return upgradesById, upgradeLocksById, upgradePrereqsById, upgradePrereqsOfById, baseCost
end

local function Upgrade(towerName, upgradeId, Y, N)
	local upgradesById, upgradeLocksById, upgradePrereqsById, upgradePrereqsOfById, baseCost = SetUpTables(towerName)
	
	local purchasedUpgrades = {}		-- this upgrade and any other upgrades that had to already be purchased to get it
	local accessibleUpgrades = {}		-- list of upgrades that are possible to reach from this upgrade by some route
	
	-- build purchasedUpgrades recursively
	local function GetPurchasedUpgrades(myUpgradeId)
		if not purchasedUpgrades[myUpgradeId] then baseCost = baseCost + upgradesById[myUpgradeId].cost end
		
		for _, prereqId in ipairs(upgradePrereqsById[myUpgradeId]) do
			if prereqId ~= "root" then GetPurchasedUpgrades(prereqId) end
			purchasedUpgrades[prereqId] = true
		end
	end
	
	GetPurchasedUpgrades(upgradeId)
	
	-- generate a list of upgrades that are reachable from this upgrade, but do not have this upgrade as a prerequisite
	-- also generate a list of upgrades that are immediately reachable from this upgrade
	local function DetermineAccessibility(myUpgradeId)
		if myUpgradeId == upgradeId then return end
		
		-- if this upgrade is locked by an already purchased upgrade, then it is inaccessible
		for _, upgradeLock in ipairs(upgradeLocksById[myUpgradeId]) do
			if purchasedUpgrades[upgradeLock] then return end
		end
		
		-- find upgrades that are reachable from the current upgrades owned
		for _, prereqId in ipairs(upgradePrereqsById[myUpgradeId]) do
			-- upgrades with multiple prereqs are only accessible if we can reach all its prereqs
			-- if it is accessible, it'll be reached again on another recursion
			if not (accessibleUpgrades[prereqId] or purchasedUpgrades[prereqId] or prereqId == "root") or prereqId == upgradeId then return end
		end
		
		if not purchasedUpgrades[myUpgradeId] then accessibleUpgrades[myUpgradeId] = true end
		
		-- determine accessibility of next upgrades
		if upgradePrereqsOfById[myUpgradeId] then 
			for _, prereqOfId in ipairs(upgradePrereqsOfById[myUpgradeId]) do
				DetermineAccessibility(prereqOfId)
			end
		end
	end

	-- start with the root
	for _, prereqOfId in ipairs(upgradePrereqsOfById.root) do
		DetermineAccessibility(prereqOfId)
	end
	
	return GetTotalCosts(baseCost, purchasedUpgrades, accessibleUpgrades, upgradeId,
		upgradesById, upgradeLocksById, upgradePrereqsById, upgradePrereqsOfById, Y, N)
end

-- GLOBAL FUNCTIONS

p["tower"] = function(frame)
	local upgradesById, upgradeLocksById, upgradePrereqsById, upgradePrereqsOfById, baseCost = SetUpTables(frame.args[1])
end

p["upgrade"] = function(frame)
	return Upgrade(frame.args[1], frame.args[2], frame:expandTemplate{title = "Y", args = {}}, frame:expandTemplate{title = "N", args = {}})
	--return Upgrade(frame.args[1], frame.args[2], "Y", "N")
end

return p