diff --git a/src/main/java/link/locutus/discord/apiv1/enums/city/ICity.java b/src/main/java/link/locutus/discord/apiv1/enums/city/ICity.java index 7d697d8f..115fbbea 100644 --- a/src/main/java/link/locutus/discord/apiv1/enums/city/ICity.java +++ b/src/main/java/link/locutus/discord/apiv1/enums/city/ICity.java @@ -7,10 +7,12 @@ import link.locutus.discord.apiv1.enums.city.project.Project; import link.locutus.discord.apiv1.enums.city.project.Projects; import link.locutus.discord.commands.manager.v2.binding.annotation.Command; +import link.locutus.discord.util.PW; import java.util.AbstractMap; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.function.Predicate; public interface ICity { @@ -20,6 +22,37 @@ public interface ICity { double getInfra(); + default double getFreeInfra() { + return getInfra() - getNumBuildings() * 50; + } + + default double caclulateBuildingCostConverted(JavaCity from) { + double total = 0; + for (Building building : Buildings.values()) { + int amtA = getBuilding(building); + int amtB = from.getBuilding(building); + if (amtA != amtB) { + if (amtB > amtA) { + total += building.getNMarketCost((amtB - amtA) * 0.5); + } else { + total += building.getNMarketCost(amtB - amtA); + } + } + } + return total; + } + + default double calculateCostConverted(JavaCity from) { + double total = caclulateBuildingCostConverted(from); + if (this.getInfra() > from.getInfra()) { + total += PW.City.Infra.calculateInfra(from.getInfra(), getInfra()); + } + if (!Objects.equals(getLand(), from.getLand())) { + total += PW.City.Land.calculateLand(from.getLand(), getLand()); + } + return total; + } + double getLand(); int getBuilding(Building building); diff --git a/src/main/java/link/locutus/discord/apiv1/enums/city/JavaCity.java b/src/main/java/link/locutus/discord/apiv1/enums/city/JavaCity.java index 62533a23..c6ff8f8f 100644 --- a/src/main/java/link/locutus/discord/apiv1/enums/city/JavaCity.java +++ b/src/main/java/link/locutus/discord/apiv1/enums/city/JavaCity.java @@ -5,6 +5,7 @@ import link.locutus.discord.apiv1.enums.city.building.*; import link.locutus.discord.commands.info.optimal.CityBranch; import link.locutus.discord.config.Settings; +import link.locutus.discord.db.entities.CityNode; import link.locutus.discord.db.entities.DBCity; import link.locutus.discord.db.entities.DBNation; import link.locutus.discord.db.entities.MMRInt; @@ -72,21 +73,13 @@ public void clear() { Arrays.fill(buildings, (byte) 0); } - public void set(JavaCity other, int maxBuildingIndex) { -// if (this.metrics != null) { -// if (other.metrics != null) { -// this.metrics.profit = other.metrics.profit; -// this.metrics.population = other.metrics.population; -// this.metrics.disease = other.metrics.disease; -// this.metrics.crime = other.metrics.crime; -// this.metrics.pollution = other.metrics.pollution; -// this.metrics.commerce = other.metrics.commerce; -// } else { -// clearMetrics(); -// } -// } -// maxBuildingIndex = Math.min(buildings.length, maxBuildingIndex); -// for (int i = 0; i <= maxBuildingIndex; i++) { + public void setBuildings(ICity other) { + for (Building building : Buildings.values()) { + set(building, other.getBuilding(building)); + } + } + + public void set(JavaCity other) { for (int i = 0; i < buildings.length; i++) { buildings[i] = other.buildings[i]; } @@ -104,10 +97,14 @@ public void clearMetrics() { } } - public long getNukeTurn() { + public int getNukeTurn() { return nuke_turn; } + public void setNuke_turn(int nuke_turn) { + this.nuke_turn = nuke_turn; + } + public int[] getMMRArray() { return new int[]{getBuilding(Buildings.BARRACKS), getBuilding(Buildings.FACTORY), getBuilding(Buildings.HANGAR), getBuilding(Buildings.DRYDOCK)}; } @@ -136,15 +133,7 @@ public void recalculate(JavaCity city, Predicate hasProject) { pollution = 0; commerce = 0; - double pollutionMax = 400d; - int turnsMax = 11 * 12; - long turns = TimeUtil.getTurn() - city.nuke_turn; - if (turns < turnsMax) { - double nukePollution = (turnsMax - turns) * pollutionMax / (turnsMax); - if (nukePollution > 0) { - pollution += (int) nukePollution; - } - } + this.pollution = PW.City.getNukePollution(city.nuke_turn); for (Building building : Buildings.POLLUTION_BUILDINGS) { int amt = city.buildings[building.ordinal()]; @@ -207,6 +196,20 @@ public void recalculate(JavaCity city, Predicate hasProject) { } } + public int getMaxCommerce(Predicate hasProject) { + int maxCommerce; + if (hasProject.test(Projects.INTERNATIONAL_TRADE_CENTER)) { + if (hasProject.test(Projects.TELECOMMUNICATIONS_SATELLITE)) { + maxCommerce = 125; + } else { + maxCommerce = 115; + } + } else { + maxCommerce = 100; + } + return maxCommerce; + } + public Metrics getCachedMetrics() { return metrics; } @@ -216,7 +219,6 @@ public Metrics getMetrics(Predicate hasProject) { metrics = new Metrics(); } if (metrics.commerce == null) { - metrics.recalculate(this, hasProject); } return metrics; @@ -467,30 +469,43 @@ public JavaCity zeroNonMilitary() { return this; } - public JavaCity optimalBuild(DBNation nation, long timeout) { - return optimalBuild(nation.getContinent(), nation.getRads(), nation.getCities(), nation::hasProject, nation.getGrossModifier(), timeout); - } - - public JavaCity optimalBuild(Continent continent, double rads, int numCities, Predicate hasProject, double grossModifier, long timeout) { - return optimalBuild(continent, rads, numCities, hasProject, grossModifier, timeout, f -> f, null); - } - - public JavaCity optimalBuild(Continent continent, double rads, int numCities, Predicate hasProject, double grossModifier, long timeout, - Function, Function> modifyValueFunc, Function goal) { - Predicate finalHasProject = Projects.hasProjectCached(hasProject); - Function valueFunction = javaCity -> { - return javaCity.profitConvertedCached(continent, rads, finalHasProject, numCities, grossModifier) / javaCity.getNumBuildings(); - }; - valueFunction = modifyValueFunc.apply(valueFunction); - - return optimalBuild(continent, valueFunction, goal, finalHasProject, timeout, rads); - } - - public JavaCity roiBuild(Continent continent, double rads, int numCities, Predicate hasProject, double grossModifier, int days, long timeout) { - return roiBuild(continent, rads, numCities, hasProject, grossModifier, days, timeout, f -> f, null); - } - - public JavaCity roiBuild(Continent continent, double rads, int numCities, Predicate hasProject, double grossModifier, int days, long timeout, Function, Function> modifyValueFunc, Function goal) { + public JavaCity optimalBuild(DBNation nation, long timeout, boolean selfSufficient, Double infraLow) { + // Continent continent, + // int numCities, + // Function valueFunction, + // Function goal, + // Predicate hasProject, + // long timeout, double rads, + // boolean selfSufficient, + // double grossModifier, + // Double infraLow + return optimalBuild(nation.getContinent(), + nation.getCities(), + CityNode::getRevenueConverted, + null, + nation::hasProject, + timeout, + nation.getRads(), + selfSufficient, + nation.getGrossModifier(), + infraLow); + } + +// public JavaCity optimalBuild(Continent continent, double rads, int numCities, Predicate hasProject, double grossModifier, long timeout, boolean selfSufficient, +// Function, Function> modifyValueFunc, Function goal) { +// Predicate finalHasProject = Projects.optimize(hasProject); +// Function valueFunction = javaCity -> { +// return javaCity.profitConvertedCached(continent, rads, finalHasProject, numCities, grossModifier) / javaCity.getNumBuildings(); +// }; +// valueFunction = modifyValueFunc.apply(valueFunction); +// return optimalBuild(continent, valueFunction, goal, finalHasProject, timeout, rads, selfSufficient); +// } + + public JavaCity roiBuild(Continent continent, double rads, int numCities, Predicate hasProject, double grossModifier, int days, long timeout, boolean selfSufficient, Double infraLow) { + return roiBuild(continent, rads, numCities, hasProject, grossModifier, days, timeout, selfSufficient, infraLow, f -> f, null); + } + + public JavaCity roiBuild(Continent continent, double rads, int numCities, Predicate hasProject, double grossModifier, int days, long timeout, boolean selfSufficient, Double infraLow, Function, Function> modifyValueFunc, Function goal) { JavaCity origin = new JavaCity(this); origin.setAge(this.getAgeDays() + days / 2); @@ -508,27 +523,17 @@ public JavaCity roiBuild(Continent continent, double rads, int numCities, Predic double baseProfitNonTax = (baseProfit - zeroedProfit); double profitPerImp = baseProfitNonTax / impOverZero; - Predicate finalHasProject = Projects.hasProjectCached(hasProject); - Function valueFunction = javaCity -> { - double newProfit = javaCity.profitConvertedCached(continent, rads, finalHasProject, numCities, grossModifier); - + Predicate finalHasProject = Projects.optimize(hasProject); + Function valueFunction = javaCity -> { + double newProfit = javaCity.getRevenueConverted(); double cost = javaCity.calculateCostConverted(origin); - -// int imp = javaCity.getImpTotal() - baseImp; -// double expectedProfit = profitPerImp * imp; return (newProfit * days - cost) / javaCity.getNumBuildings(); }; - valueFunction = modifyValueFunc.apply(valueFunction); - - JavaCity optimal = zeroed.optimalBuild(continent, valueFunction, goal, finalHasProject, timeout, rads); + JavaCity optimal = zeroed.optimalBuild(continent, numCities, valueFunction, goal, finalHasProject, timeout, rads, selfSufficient, grossModifier, infraLow); return optimal; } - public JavaCity optimalBuild(Continent continent, Function valueFunction, Predicate hasProject, long timeout, double rads) { - return optimalBuild(continent, valueFunction, null, hasProject, timeout, rads); - } - public JavaCity zeroPower() { for (int i = 0; i < buildings.length; i++) { if (!(Buildings.get(i) instanceof PowerBuilding)) continue; @@ -570,44 +575,14 @@ public JavaCity setOptimalPower(Continent continent) { return this; } - public JavaCity optimalBuild(Continent continent, Function valueFunction, Function goal, Predicate hasProject, long timeout, double rads) { - if (goal == null) { - goal = javaCity -> javaCity.getFreeInfra() < 50; - } - CityBranch searchServices = new CityBranch(this, continent, false, rads, hasProject); - - Function, Double> valueFunction2 = e -> valueFunction.apply(e.getKey()); - Function finalGoal = goal; - Function, Boolean> goal2 = e -> finalGoal.apply(e.getKey()); - - PriorityQueue> queue = new ObjectHeapPriorityQueue>(500000, - (o1, o2) -> Double.compare(valueFunction2.apply(o2), valueFunction2.apply(o1))); - - JavaCity origin = new JavaCity(this); - - Function, Double>> valueCompletionFunction; - - valueCompletionFunction = factor -> (Function, Double>) entry -> { - Double parentValue = valueFunction2.apply(entry); - int imps = entry.getKey().getNumBuildings(); - - if (factor <= 1) { - parentValue = (parentValue * imps) * factor + (parentValue * (1 - factor)); - } else { - double factor2 = factor - 1; - parentValue = imps * factor2 + (parentValue * imps) * (1 - factor2); - } - return parentValue; - }; - - if (origin.getRequiredInfra() > origin.getInfra()) { - throw new IllegalArgumentException("The city infrastructure (" + MathMan.format(origin.getInfra()) + ") is too low for the required buildings (required infra: " + MathMan.format(origin.getRequiredInfra()) + ", mmr: " + origin.getMMR() + ")"); - } - - Map.Entry optimized = BFSUtil.search(goal2, valueFunction2, valueCompletionFunction, searchServices, searchServices, new AbstractMap.SimpleEntry<>(origin, 0), queue, timeout); - - System.out.println("Buildings " + optimized.getKey().getNumBuildings()); - return optimized == null ? null : optimized.getKey(); + public JavaCity optimalBuild(Continent continent, int numCities, Function valueFunction, Function goal, Predicate hasProject, long timeout, double rads, boolean selfSufficient, double grossModifier, Double infraLow) { + JavaCity copy = new JavaCity(this); + CityNode.CachedCity cached = new CityNode.CachedCity(this, continent, selfSufficient, hasProject, numCities, grossModifier, rads, infraLow); + CityBranch searchServices = new CityBranch(cached); + CityNode optimized = searchServices.toOptimal(valueFunction, goal, timeout); + System.out.println("Buildings " + (optimized == null ? "null" : optimized.getNumBuildings())); + if (optimized != null) copy.setBuildings(optimized); + return optimized == null ? null : copy; } public double profitConvertedCached(Continent continent, double rads, Predicate hasProject, int numCities, double grossModifier) { @@ -695,29 +670,6 @@ public double[] calculateCost(JavaCity from) { return calculateCost(from, new double[ResourceType.values.length]); } - public double calculateCostConverted(JavaCity from) { - double total = 0; - for (int i = 0; i < buildings.length; i++) { - int amtOther = from.buildings[i]; - int amt = buildings[i]; - if (amt != amtOther) { - if (amtOther > amt) { - total += Buildings.get(i).getNMarketCost((amt - amtOther) * 0.5); - } else { - total += Buildings.get(i).getNMarketCost(amt - amtOther); - } - } - } - - if (this.getInfra() > from.getInfra()) { - total += PW.City.Infra.calculateInfra(from.getInfra(), getInfra()); - } - if (!Objects.equals(getLand(), from.getLand())) { - total += PW.City.Land.calculateLand(from.getLand(), getLand()); - } - return total; - } - public double[] calculateCost(JavaCity from, double[] buffer) { return calculateCost(from, buffer, true, true); } @@ -817,6 +769,14 @@ public JavaCity setAge(Integer age) { return this; } + public void setDateCreated(long dateCreated) { + this.dateCreated = dateCreated; + } + + public long getDateCreated() { + return dateCreated; + } + public JavaCity setLand(Double land) { this.land_ = land; return this; diff --git a/src/main/java/link/locutus/discord/apiv1/enums/city/building/Building.java b/src/main/java/link/locutus/discord/apiv1/enums/city/building/Building.java index e775b1f0..ed505b9d 100644 --- a/src/main/java/link/locutus/discord/apiv1/enums/city/building/Building.java +++ b/src/main/java/link/locutus/discord/apiv1/enums/city/building/Building.java @@ -112,7 +112,7 @@ default boolean canBuild(Continent continent) { double profitConverted(Continent continent, double rads, Predicate hasProject, ICity city, int amt); - double[] profit(Continent continent, double rads, long date, Predicate hasProject, ICity city, double[] profitBuffer, int turns); + double[] profit(Continent continent, double rads, long date, Predicate hasProject, ICity city, double[] profitBuffer, int turns, int amt); @Command(desc = "Get the continents this building can be built on") default Set getContinents() { diff --git a/src/main/java/link/locutus/discord/apiv1/enums/city/building/ResourceBuilding.java b/src/main/java/link/locutus/discord/apiv1/enums/city/building/ResourceBuilding.java index eaa82142..2f43adb4 100644 --- a/src/main/java/link/locutus/discord/apiv1/enums/city/building/ResourceBuilding.java +++ b/src/main/java/link/locutus/discord/apiv1/enums/city/building/ResourceBuilding.java @@ -19,4 +19,6 @@ default boolean canBuild(Continent continent) { ResourceBuilding continents(Continent... continents); int getBaseProduction(); + + int getBaseInput(); } diff --git a/src/main/java/link/locutus/discord/apiv1/enums/city/building/imp/ABuilding.java b/src/main/java/link/locutus/discord/apiv1/enums/city/building/imp/ABuilding.java index 5fa397be..5db1c01c 100644 --- a/src/main/java/link/locutus/discord/apiv1/enums/city/building/imp/ABuilding.java +++ b/src/main/java/link/locutus/discord/apiv1/enums/city/building/imp/ABuilding.java @@ -84,8 +84,7 @@ public double profitConverted(Continent continent, double rads, Predicate hasProject, ICity city, double[] profitBuffer, int turns) { - int amt = city.getBuilding(this); + public double[] profit(Continent continent, double rads, long date, Predicate hasProject, ICity city, double[] profitBuffer, int turns, int amt) { if (amt > 0) { for (ResourceType type : ResourceType.values) { profitBuffer[type.ordinal()] -= upkeep(type, hasProject) * amt * turns / 12; diff --git a/src/main/java/link/locutus/discord/apiv1/enums/city/building/imp/APowerBuilding.java b/src/main/java/link/locutus/discord/apiv1/enums/city/building/imp/APowerBuilding.java index cbf6dca7..249b64df 100644 --- a/src/main/java/link/locutus/discord/apiv1/enums/city/building/imp/APowerBuilding.java +++ b/src/main/java/link/locutus/discord/apiv1/enums/city/building/imp/APowerBuilding.java @@ -60,7 +60,7 @@ public double consumptionConverted(int infra) { } else { amt = (Math.min(infra, this.infra) + infraLevels - 1) / infraLevels; } - return -(amt * resourceValue); + return -(amt * resourceValue * inputArr[resource.ordinal()]); } @Override @@ -75,7 +75,7 @@ public double[] consumption(int infra, double[] profitBuffer, int turns) { @Override public double getPowerResourceConsumed(int infra) { if (resource != null) { - return (Math.min(infra, this.infra) + infraLevels - 1d) / infraLevels; + return (Math.min(infra, this.infra) + infraLevels - 1) / infraLevels; } return 0; } diff --git a/src/main/java/link/locutus/discord/apiv1/enums/city/building/imp/AResourceBuilding.java b/src/main/java/link/locutus/discord/apiv1/enums/city/building/imp/AResourceBuilding.java index 71af020d..b2e9ff65 100644 --- a/src/main/java/link/locutus/discord/apiv1/enums/city/building/imp/AResourceBuilding.java +++ b/src/main/java/link/locutus/discord/apiv1/enums/city/building/imp/AResourceBuilding.java @@ -43,13 +43,12 @@ public AResourceBuilding(BuildingBuilder parent, int baseInput, double boostFact } else { this.inputValueFunc = (c0, r0, h0, b0, a0, p0) -> { double factor = 1d / output.getManufacturingMultiplier(); - InputValueFunc newFunc; if (inputs.length == 1) { double value = ResourceType.convertedTotalNegative(inputs[0], 1); newFunc = (c, r, h, b, a, p) -> { double inputAmt = factor * p; - return -value * inputAmt; + return -(value * inputAmt); }; } else { double[] values = new double[inputs.length]; @@ -71,7 +70,7 @@ public AResourceBuilding(BuildingBuilder parent, int baseInput, double boostFact } return 0d; }; - return newFunc.apply(c0, r0, h0, b0, a0, p0); + return inputValueFunc.apply(c0, r0, h0, b0, a0, p0); }; } @@ -101,13 +100,18 @@ public int getBaseProduction() { return baseInput * 3; } + @Override + public int getBaseInput() { + return baseInput; + } + @Override public List getResourceTypesConsumed() { return List.of(inputs); } @Override - public double profitConverted(Continent continent, double rads, Predicate hasProjects, ICity city, int amt) { + public double profitConverted(Continent continent, double rads, Predicate hasProjects, ICity city, int amt) { double profit = super.profitConverted(continent, rads, hasProjects, city, amt); double production = output.getProduction(continent, rads, hasProjects, city.getLand(), amt, -1); profit += production * outputValue.get(); @@ -116,17 +120,16 @@ public double profitConverted(Continent continent, double rads, Predicate hasPro } @Override - public double[] profit(Continent continent, double rads, long date, Predicate hasProjects, ICity city, double[] profitBuffer, int turns) { - profitBuffer = super.profit(continent, rads, date, hasProjects, city, profitBuffer, turns); + public double[] profit(Continent continent, double rads, long date, Predicate hasProjects, ICity city, double[] profitBuffer, int turns, int amt) { + profitBuffer = super.profit(continent, rads, date, hasProjects, city, profitBuffer, turns, amt); ResourceType type = getResourceProduced(); - int improvements = city.getBuilding(this); - double production = type.getProduction(continent, rads, hasProjects, city.getLand(), improvements, date); + double production = type.getProduction(continent, rads, hasProjects, city.getLand(), amt, date); if (production != 0) { profitBuffer[type.ordinal()] += production * turns / 12; - double inputAmt = type.getInput(continent, rads, hasProjects, city, improvements); + double inputAmt = type.getInput(continent, rads, hasProjects, city, amt); for (ResourceType input : inputs) { if (inputAmt != 0) { diff --git a/src/main/java/link/locutus/discord/apiv1/enums/city/project/Projects.java b/src/main/java/link/locutus/discord/apiv1/enums/city/project/Projects.java index 44d129c3..418b2127 100644 --- a/src/main/java/link/locutus/discord/apiv1/enums/city/project/Projects.java +++ b/src/main/java/link/locutus/discord/apiv1/enums/city/project/Projects.java @@ -415,6 +415,15 @@ public static int getScore() { return 20; } + public static Predicate optimize(Predicate projects) { + int maxId = Arrays.stream(values).mapToInt(Project::ordinal).max().orElse(0); + boolean[] hasProject = new boolean[maxId + 1]; + for (Project project : values) { + hasProject[project.ordinal()] = projects.test(project); + } + return p -> hasProject[p.ordinal()]; + } + // Recycling Initiative @@ -515,12 +524,4 @@ public static Project get(String name) { } return null; } - - public static Predicate hasProjectCached(Predicate hasProject) { - Map cached = new HashMap<>(); - for (Project project : Projects.values) { - cached.put(project, hasProject.test(project)); - } - return cached::get; - } } diff --git a/src/main/java/link/locutus/discord/commands/info/optimal/CityBranch.java b/src/main/java/link/locutus/discord/commands/info/optimal/CityBranch.java index 55c0c1cb..22c2620e 100644 --- a/src/main/java/link/locutus/discord/commands/info/optimal/CityBranch.java +++ b/src/main/java/link/locutus/discord/commands/info/optimal/CityBranch.java @@ -2,48 +2,98 @@ import it.unimi.dsi.fastutil.PriorityQueue; import it.unimi.dsi.fastutil.objects.ObjectArrayFIFOQueue; +import it.unimi.dsi.fastutil.objects.ObjectHeapPriorityQueue; import link.locutus.discord.apiv1.enums.Continent; import link.locutus.discord.apiv1.enums.ResourceType; +import link.locutus.discord.apiv1.enums.city.ICity; import link.locutus.discord.apiv1.enums.city.JavaCity; import link.locutus.discord.apiv1.enums.city.building.*; import link.locutus.discord.apiv1.enums.city.project.Project; import link.locutus.discord.apiv1.enums.city.project.Projects; +import link.locutus.discord.db.entities.CityNode; +import link.locutus.discord.util.MathMan; +import link.locutus.discord.util.PW; +import link.locutus.discord.util.search.BFSUtil; import java.util.*; import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Predicate; -public class CityBranch implements BiConsumer, PriorityQueue>>, Consumer> { - private final Continent continent; - private final Predicate hasProject; - private final ObjectArrayFIFOQueue> pool; +public class CityBranch implements BiConsumer>, Consumer { private final Building[] buildings; private boolean usedOrigin = false; private Building originBuilding = null; - public CityBranch(JavaCity origin, Continent continent, boolean selfSufficient, double rads, Predicate hasProject) { - this.continent = continent; - this.hasProject = hasProject; + + private final CityNode.CachedCity origin; + + public CityNode toOptimal(Function valueFunction, Function goal, long timeout) { + if (goal == null) { + goal = f -> f.getFreeSlots() <= 0; + } + + Function valueFunction2 = valueFunction::apply; + Function finalGoal = goal; + Function goal2 = finalGoal::apply; + + PriorityQueue queue = new ObjectHeapPriorityQueue(500000, + (o1, o2) -> Double.compare(valueFunction2.apply(o2), valueFunction2.apply(o1))); + + CityNode init = origin.create(); + origin.setMaxIndex(buildings.length); + + Function> valueCompletionFunction; + + valueCompletionFunction = factor -> (Function) entry -> { + Double parentValue = valueFunction2.apply(entry); + int imps = entry.getNumBuildings(); + if (factor <= 1) { + parentValue = (parentValue * imps) * factor + (parentValue * (1 - factor)); + } else { + double factor2 = factor - 1; + parentValue = imps * factor2 + (parentValue * imps) * (1 - factor2); + } + return parentValue; + }; + + if (init.getRequiredInfra() > init.getInfra()) { + throw new IllegalArgumentException("The city infrastructure (" + MathMan.format(init.getInfra()) + ") is too low for the required buildings (required infra: " + MathMan.format(init.getRequiredInfra()) + ")"); + } + + CityNode optimized = BFSUtil.search(goal2, valueFunction2, valueCompletionFunction, this, this, init, queue, timeout); + return optimized; + } + + public CityBranch(CityNode.CachedCity cached) { + this.origin = cached; + CityNode tmpNode = origin.create(); Map rssBuildings = new HashMap<>(); for (Building building : Buildings.values()) { - if (!building.canBuild(continent)) continue; + if (!building.canBuild(origin.getContinent())) continue; if (!(building instanceof ResourceBuilding rssBuild)) continue; - if (rads <= 0 && building == Buildings.FARM) continue; - int cap = rssBuild.cap(hasProject); - double profit = rssBuild.profitConverted(continent, rads, hasProject, origin, cap) / cap; + if (origin.getRads() <= 0 && building == Buildings.FARM) continue; + int cap = Math.min(10, rssBuild.cap(origin.hasProject())); + double profit = rssBuild.profitConverted(origin.getContinent(), origin.getRads(), origin.hasProject(), tmpNode, cap) / cap; + if (profit <= 0) continue; rssBuildings.put(rssBuild, profit); } - List rssSorted; - if (selfSufficient) { - List rawSorted = new ArrayList<>(rssBuildings.entrySet().stream().filter(e -> e.getKey().getResourceProduced().isRaw()).sorted(Map.Entry.comparingByValue()).map(Map.Entry::getKey).toList().reversed()); - List manuSorted = new ArrayList<>(rssBuildings.entrySet().stream().filter(e -> !e.getKey().getResourceProduced().isRaw()).sorted(Map.Entry.comparingByValue()).map(Map.Entry::getKey).toList().reversed()); - rssSorted = new ArrayList<>(rawSorted); - rssSorted.addAll(manuSorted); - } else { - rssSorted = new ArrayList<>(rssBuildings.entrySet().stream().sorted(Map.Entry.comparingByValue()).map(Map.Entry::getKey).toList().reversed()); + List rssSorted = new ArrayList<>(rssBuildings.entrySet().stream().sorted(Map.Entry.comparingByValue()).map(Map.Entry::getKey).toList().reversed()); + if (origin.selfSufficient()) { + rssSorted.removeIf(building -> { + if (building.getResourceProduced().isManufactured()) { + for (ResourceType req : building.getResourceTypesConsumed()) { + if (!origin.getContinent().hasResource(req)) { + return true; + } + } + } + return false; + }); + resortList(rssSorted); } for (Building building : rssSorted) { - System.out.println(building.name() + " " + rssBuildings.get(building) + " profit"); + System.out.println("rmv:|| " + building.name() + " " + rssBuildings.get(building) + " profit"); } List allBuildings = new ArrayList<>(rssSorted); @@ -54,129 +104,141 @@ public CityBranch(JavaCity origin, Continent continent, boolean selfSufficient, if (building instanceof ServiceBuilding) allBuildings.add(building); } this.buildings = allBuildings.toArray(new Building[0]); - this.pool = new ObjectArrayFIFOQueue<>(500000); + for (Building building : buildings) { + System.out.println("rmv:|| All " + building.name()); + } } - private Map.Entry create(Map.Entry originPair, Building building, int index) { - JavaCity origin = originPair.getKey(); + private static void resortList(List rssSorted) { + Set seen = new HashSet<>(); + for (int i = 0; i < rssSorted.size(); i++) { + ResourceBuilding building = (ResourceBuilding) rssSorted.get(i); + seen.add(building.getResourceProduced()); + if (!building.getResourceProduced().isManufactured()) { + continue; + } + for (ResourceType r : building.getResourceTypesConsumed()) { + if (!seen.contains(r)) { + for (int j = i + 1; j < rssSorted.size(); j++) { + if (rssSorted.get(j).getResourceProduced() == r) { + rssSorted.remove(i); + rssSorted.add(j, building); + break; + } + } + i--; + break; + } + } + } + } + + private CityNode create(CityNode origin, int index) { if (!usedOrigin) { usedOrigin = true; - originBuilding = building; - - origin.set(building); - origin.clearMetrics(); - originPair.setValue(index); - - return originPair; - } - if (this.pool.isEmpty()) { - JavaCity newCity = new JavaCity(origin); - newCity.remove(originBuilding); - newCity.set(building); - return new AbstractMap.SimpleEntry<>(newCity, index); + origin.setIndex(index); + return origin; } - Map.Entry elem = this.pool.dequeue(); - JavaCity newCity = (elem.getKey()); - int maxIndex = Math.max(index, elem.getValue()); - - newCity.set(origin, maxIndex); + CityNode newCity = origin.clone(); newCity.remove(originBuilding); + newCity.setIndex(index); + return newCity; + } - newCity.set(building); - newCity.clearMetrics(); - elem.setValue(index); - - return elem; + private CityNode create(CityNode origin, Building building, int index) { + if (!usedOrigin) { + usedOrigin = true; + originBuilding = building; + origin.add(building); + origin.setIndex(index); + return origin; + } + CityNode copy = origin.clone(); + copy.remove(originBuilding); + copy.add(building); + copy.setIndex(index); + return copy; } @Override - public void accept(Map.Entry originPair, PriorityQueue> cities) { + public void accept(CityNode origin, PriorityQueue cities) { usedOrigin = false; - - JavaCity origin = originPair.getKey(); int freeSlots = origin.getFreeSlots(); - if (freeSlots <= 0) return; - - int minBuilding = originPair.getValue(); - int maxBuilding = this.buildings.length; - Building minBuildingType = buildings[minBuilding]; - boolean buildMore = true; - { - int amt = origin.getBuildingOrdinal(minBuildingType.ordinal()); - if (amt != 0) { - if (minBuildingType instanceof ResourceBuilding rssBuild) { - if (amt < minBuildingType.cap(hasProject)) { - buildMore = false; -// if (rssBuild.getResourceProduced().isRaw()) { -// buildMoreRaws = false; -// } else { -// buildMoreManu = false; -// } - } - } else if (minBuildingType instanceof CommerceBuilding) { - minBuildingType.cap(hasProject); - } - } + if (freeSlots <= 0) { + return; } - int maxCommerce; - if (hasProject.test(Projects.INTERNATIONAL_TRADE_CENTER)) { - if (hasProject.test(Projects.TELECOMMUNICATIONS_SATELLITE)) { - maxCommerce = 125; - } else { - maxCommerce = 115; + int currBuildingIndex = origin.getIndex(); + + Building building = buildings[currBuildingIndex]; + int amt = origin.getBuildingOrdinal(building.ordinal()); + while (amt >= building.cap(this.origin.hasProject())) { + currBuildingIndex++; + if (currBuildingIndex >= this.buildings.length) { + return; } - } else { - maxCommerce = 100; + building = buildings[currBuildingIndex]; + amt = origin.getBuilding(building); } - for (int i = minBuilding; i < maxBuilding; i++) { - Building building = buildings[i]; - int ordinal = building.ordinal(); - int amt = origin.getBuildingOrdinal(ordinal); - if (amt >= building.cap(hasProject)) continue; - - if (building instanceof ResourceBuilding rssBuild) { - ResourceType rss = rssBuild.getResourceProduced(); - if (buildMore || i == minBuilding) { - cities.enqueue(create(originPair, building, i)); - } -// if (rss.isRaw()) { -// -// } else if (buildMoreManu || i == minBuilding) { -// cities.enqueue(create(originPair, building, i)); -// } - } else if (building instanceof CommerceBuilding) { - if (origin.calcCommerce(hasProject) >= maxCommerce) continue; - { - cities.enqueue(create(originPair, building, i)); + + if (building instanceof ResourceBuilding rssBuild) { + if (this.origin.selfSufficient() && rssBuild.getResourceProduced().isManufactured()) { + double reqAmt = rssBuild.getBaseInput() / 3d; + if (this.origin.hasProject().test(rssBuild.getResourceProduced().getProject())) { + reqAmt *= rssBuild.getResourceProduced().getBoostFactor(); } - } else if (building instanceof ServiceBuilding) { - if (building == Buildings.HOSPITAL) { - Double disease = origin.calcDisease(hasProject); - if (disease > 0) { - cities.enqueue(create(originPair, building, i)); - } - } else if (building == Buildings.RECYCLING_CENTER) { - Integer pollution = origin.calcPollution(hasProject); - if (pollution > 0) { - cities.enqueue(create(originPair, building, i)); - } - } else if (building == Buildings.POLICE_STATION) { - Double crime = origin.calcCrime(hasProject); - if (crime > 0) { - cities.enqueue(create(originPair, building, i)); + for (ResourceType req : rssBuild.getResourceTypesConsumed()) { + if (origin.getBuilding(req.getBuilding()) < reqAmt) { + cities.enqueue(create(origin, currBuildingIndex + 1)); + return; } } } + CityNode next = create(origin, building, currBuildingIndex); + cities.enqueue(next); +// CityNode nextCity = next.getKey(); +// boolean checkDisease = nextCity.calcDisease(hasProject) > 0 && nextCity.getBuilding(Buildings.HOSPITAL) < Buildings.HOSPITAL.cap(hasProject); +// if (checkDisease) { +// cities.enqueue(create(next, Buildings.HOSPITAL, currBuildingIndex)); +// } +// boolean checkRecycling = nextCity.calcPollution(hasProject) > 0 && nextCity.getBuilding(Buildings.RECYCLING_CENTER) < Buildings.RECYCLING_CENTER.cap(hasProject); +// if (checkRecycling) { +// cities.enqueue(create(next, Buildings.RECYCLING_CENTER, currBuildingIndex)); +// } + } else if (building instanceof CommerceBuilding) { + int commerce = origin.getCommerce(); + if (commerce < this.origin.getMaxCommerce()) { + cities.enqueue(create(origin, building, currBuildingIndex)); + } + } else if (building instanceof ServiceBuilding) { + if (building == Buildings.HOSPITAL) { + double disease = origin.calcDisease(this.origin.hasProject()); + if (disease > 0) { + cities.enqueue(create(origin, building, currBuildingIndex)); + } + } else if (building == Buildings.RECYCLING_CENTER) { + int pollution = origin.getPollution(); + if (pollution > 0) { + cities.enqueue(create(origin, building, currBuildingIndex)); + } + } else if (building == Buildings.POLICE_STATION) { + double crime = origin.calcCrime(this.origin.hasProject()); + if (crime > 0) { + cities.enqueue(create(origin, building, currBuildingIndex)); + } + } } - - if (!usedOrigin) { - pool.enqueue(originPair); + if (currBuildingIndex < this.buildings.length - 1) { + cities.enqueue(create(origin, currBuildingIndex + 1)); } +// if (!usedOrigin) +// { +// pool.enqueue(originPair); +// } } @Override - public void accept(Map.Entry originPair) { - pool.enqueue(originPair); + public void accept(CityNode originPair) { +// pool.enqueue(originPair); } } diff --git a/src/main/java/link/locutus/discord/commands/info/optimal/OptimalBuild.java b/src/main/java/link/locutus/discord/commands/info/optimal/OptimalBuild.java index 6cb0b357..84f4ead8 100644 --- a/src/main/java/link/locutus/discord/commands/info/optimal/OptimalBuild.java +++ b/src/main/java/link/locutus/discord/commands/info/optimal/OptimalBuild.java @@ -9,14 +9,12 @@ import link.locutus.discord.commands.manager.v2.impl.pw.TaxRate; import link.locutus.discord.config.Settings; import link.locutus.discord.db.GuildDB; -import link.locutus.discord.db.entities.Coalition; -import link.locutus.discord.db.entities.DBCity; -import link.locutus.discord.db.entities.NationMeta; -import link.locutus.discord.db.entities.DBNation; +import link.locutus.discord.db.entities.*; import link.locutus.discord.pnw.json.CityBuild; import link.locutus.discord.user.Roles; import link.locutus.discord.util.MathMan; import link.locutus.discord.util.PW; +import link.locutus.discord.util.TimeUtil; import link.locutus.discord.util.task.ia.IACheckup; import link.locutus.discord.apiv1.enums.Continent; import link.locutus.discord.apiv1.enums.MilitaryUnit; @@ -32,6 +30,7 @@ import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.function.Predicate; @@ -70,7 +69,8 @@ public String desc() { "To require cash positive add `cash=true`\n" + "To avoid importing raws, use. `manu=false`\n" + "For radiation. `radiation=123`\n" + - "To specify a tax rate, use `tax=25/25`" + + "To specify a tax rate, use `tax=25/25`\n" + + "To spend longer finding a build, use e.g. `timeout:60s`\n" + "With an exported build:\n" + "```" + Settings.commandPrefix(true) + "OptimalBuild 30 {\n" + " \"infra_needed\": 3850,\n" + @@ -128,6 +128,10 @@ public String onCommand(IMessageIO io, Guild guild, User author, DBNation me, Li boolean manu = true; String mmr = null; + GuildDB db = Locutus.imp().getGuildDB(guild); + Guild root = Locutus.imp().getServer(); + long timeout = db.isWhitelisted() && (Roles.ADMIN.hasOnRoot(author) || db.hasCoalitionPermsOnRoot(Coalition.RAIDPERMS)) ? 20000 : 9000; + Double crimeLimit = null; Double diseaseLimit = null; Double popLimit = null; @@ -154,10 +158,17 @@ public String onCommand(IMessageIO io, Guild guild, User author, DBNation me, Li iter.remove(); continue; } - String[] split = next.split("[=<>]"); if (split.length == 2) { switch (split[0].toLowerCase()) { + case "timeout" -> { + String timeoutStr = split[1]; + timeout = TimeUtil.timeToSec(timeoutStr) * 1000; + if (timeout > TimeUnit.MINUTES.toMillis(2)) { + throw new IllegalArgumentException("Timeout too long (max: 120s)"); + } + iter.remove(); + } case "mmr" -> { String mmrStr = split[1]; if (MathMan.isInteger(mmrStr) && mmrStr.length() == 4) { @@ -315,54 +326,54 @@ public String onCommand(IMessageIO io, Guild guild, User author, DBNation me, Li DBNation finalMe = me; Continent finalContinent = continent; - Predicate hasProject = project -> addProject.contains(project) || project.get(finalMe) > 0; + Predicate hasProject = Projects.optimize(project -> addProject.contains(project) || project.get(finalMe) > 0); double grossModifier = finalMe.getGrossModifier(); CompletableFuture future = io.send("Please wait..."); - Function valueFunc; + Function valueFunc; if (taxes != null) { double[] buffer = new double[ResourceType.values.length]; double moneyFactor = (100 - taxes.money) / 100d; double rssFactor = (100 - taxes.resources) / 100d; valueFunc = javaCity -> { Arrays.fill(buffer, 0); - double[] profit = javaCity.profit(finalContinent, rads, -1L, hasProject, buffer, numCities, grossModifier, 12); + double[] profit = javaCity.profit(buffer); profit[0] *= moneyFactor; for (int i = 1; i < profit.length; i++) { if (profit[i] > 0) { profit[i] *= rssFactor; } } - return ResourceType.convertedTotal(profit) / javaCity.getNumBuildings(); + return ResourceType.convertedTotal(profit); }; } else { - valueFunc = javaCity -> javaCity.profitConvertedCached(finalContinent, rads, hasProject, numCities, finalMe.getGrossModifier()) / javaCity.getNumBuildings(); + valueFunc = javaCity -> javaCity.getRevenueConverted(); } - if (infraLow != null) { - Function parent = valueFunc; - Double finalInfraLow = infraLow; - Double popLowFinal = popLow; - valueFunc = city -> { - if (city.getFreeSlots() <= 0) { - double currentInfra = city.getInfra(); - city.setInfra(Math.min(currentInfra, finalInfraLow)); - city.getMetrics(hasProject).recalculate(city, hasProject); - if (popLowFinal != null && city.calcPopulation(hasProject) < popLowFinal) - return Double.NEGATIVE_INFINITY; - Double value = parent.apply(city); - city.setInfra(currentInfra); - return value; - } - return parent.apply(city); - }; - } +// if (infraLow != null) { +// Function parent = valueFunc; +// Double finalInfraLow = infraLow; +// Double popLowFinal = popLow; +// valueFunc = city -> { +// if (city.getFreeSlots() <= 0) { +// double currentInfra = city.getInfra(); +// city.setInfra(Math.min(currentInfra, finalInfraLow)); +// city.getMetrics(hasProject).recalculate(city, hasProject); +// if (popLowFinal != null && city.calcPopulation(hasProject) < popLowFinal) +// return Double.NEGATIVE_INFINITY; +// Double value = parent.apply(city); +// city.setInfra(currentInfra); +// return value; +// } +// return parent.apply(city); +// }; +// } if (popLimit != null) { Double finalpopLimit = popLimit; - Function parent = valueFunc; + Function parent = valueFunc; valueFunc = city -> { if (city.calcPopulation(hasProject) < finalpopLimit) return Double.NEGATIVE_INFINITY; return parent.apply(city); @@ -372,28 +383,28 @@ public String onCommand(IMessageIO io, Guild guild, User author, DBNation me, Li if (popLow != null) { Double finalpopLimit = popLow; - Function parent = valueFunc; + Function parent = valueFunc; valueFunc = city -> { if (city.calcPopulation(hasProject) < finalpopLimit) return Double.NEGATIVE_INFINITY; return parent.apply(city); }; } - - if (!manu) { - Function parent = valueFunc; - valueFunc = city -> { - if (city.getBuilding(Buildings.MUNITIONS_FACTORY) > city.getBuilding(Buildings.LEAD_MINE)) - return Double.NEGATIVE_INFINITY; - if (city.getBuilding(Buildings.GAS_REFINERY) > city.getBuilding(Buildings.OIL_WELL)) return Double.NEGATIVE_INFINITY; - if (city.getBuilding(Buildings.ALUMINUM_REFINERY) > city.getBuilding(Buildings.BAUXITE_MINE)) - return Double.NEGATIVE_INFINITY; - if (city.getBuilding(Buildings.STEEL_MILL) > city.getBuilding(Buildings.COAL_MINE) || city.getBuilding(Buildings.STEEL_MILL) > city.getBuilding(Buildings.IRON_MINE)) - return Double.NEGATIVE_INFINITY; - return parent.apply(city); - }; - } - - Function goal = javaCity -> javaCity.getFreeInfra() < 50; +// +// if (!manu) { +// Function parent = valueFunc; +// valueFunc = city -> { +// if (city.getBuilding(Buildings.MUNITIONS_FACTORY) > city.getBuilding(Buildings.LEAD_MINE)) +// return Double.NEGATIVE_INFINITY; +// if (city.getBuilding(Buildings.GAS_REFINERY) > city.getBuilding(Buildings.OIL_WELL)) return Double.NEGATIVE_INFINITY; +// if (city.getBuilding(Buildings.ALUMINUM_REFINERY) > city.getBuilding(Buildings.BAUXITE_MINE)) +// return Double.NEGATIVE_INFINITY; +// if (city.getBuilding(Buildings.STEEL_MILL) > city.getBuilding(Buildings.COAL_MINE) || city.getBuilding(Buildings.STEEL_MILL) > city.getBuilding(Buildings.IRON_MINE)) +// return Double.NEGATIVE_INFINITY; +// return parent.apply(city); +// }; +// } + + Function goal = javaCity -> javaCity.getFreeSlots() <= 0; if (diseaseLimit != null) { Double finalDiseaseLimit = diseaseLimit; @@ -402,7 +413,7 @@ public String onCommand(IMessageIO io, Guild guild, User author, DBNation me, Li double recyclingPct = (-Buildings.RECYCLING_CENTER.pollution(hasProject)) * 0.05; double subwayPct = (-Buildings.SUBWAY.pollution(hasProject)) * 0.05; - Function parent = valueFunc; + Function parent = valueFunc; valueFunc = city -> { Double disease = city.calcDisease(hasProject); if (disease > finalDiseaseLimit) { @@ -460,20 +471,25 @@ public String onCommand(IMessageIO io, Guild guild, User author, DBNation me, Li int max = Buildings.POLICE_STATION.cap(hasProject); Double finalCrimeLimit = crimeLimit; - Function parent = valueFunc; + int maxIndex = Buildings.size() - 1; + int policeIndex = Buildings.POLICE_STATION.ordinal(); + int diff = maxIndex - policeIndex; + + Function parent = valueFunc; valueFunc = city -> { double crime = city.calcCrime(hasProject); if (crime > finalCrimeLimit) { int remainingSlots = city.getFreeSlots(); if (remainingSlots == 0) return Double.NEGATIVE_INFINITY; - double reduced = crime - Math.min(max, remainingSlots) * policePct; + int currentPolice = city.getBuilding(Buildings.POLICE_STATION); + if (currentPolice >= max) return Double.NEGATIVE_INFINITY; + double reduced = crime - Math.min(max - currentPolice, remainingSlots) * policePct; if (reduced > crime) return Double.NEGATIVE_INFINITY; - - // if has any building past police station - byte[] buildings = city.getBuildings(); - for (int i = Buildings.POLICE_STATION.ordinal() + 1; i < buildings.length; i++) { - if (buildings[i] > 0) return Double.NEGATIVE_INFINITY; + int index = city.getIndex(); + int cachedMaxIndex = city.getCached().getMaxIndex() - 1; + if (index > cachedMaxIndex - diff) { + return Double.NEGATIVE_INFINITY; } } return parent.apply(city); @@ -481,16 +497,13 @@ public String onCommand(IMessageIO io, Guild guild, User author, DBNation me, Li } if (positiceCash) { - Function parentGoal = goal; - + Function parentGoal = goal; double[] profitBuffer = new double[ResourceType.values.length]; - goal = city -> { if (parentGoal.apply(city)) { Arrays.fill(profitBuffer, 0); - city.profit(finalContinent, rads, -1L, hasProject, profitBuffer, numCities, finalMe.getGrossModifier(), 12); + city.profit(profitBuffer); profitBuffer[0] += 500000d / numCities; - for (MilitaryUnit unit : MilitaryUnit.values) { MilitaryBuilding building = unit.getBuilding(); if (building == null) continue; @@ -507,19 +520,13 @@ public String onCommand(IMessageIO io, Guild guild, User author, DBNation me, Li }; } - GuildDB db = Locutus.imp().getGuildDB(guild); - Guild root = Locutus.imp().getServer(); - GuildDB rootDb = Locutus.imp().getGuildDB(root); - long timeout = db.isWhitelisted() && (Roles.ADMIN.hasOnRoot(author) || db.hasCoalitionPermsOnRoot(Coalition.RAIDPERMS)) ? 15000 : 5000; - - Function finalValueFunc = valueFunc; - Function, Function> modifyValueFunc = f -> finalValueFunc; - JavaCity optimized; if (days == null) { - optimized = origin.optimalBuild(continent, rads, numCities, hasProject, finalMe.getGrossModifier(), timeout, modifyValueFunc, goal); + optimized = origin.optimalBuild(continent, numCities, valueFunc, goal, hasProject, timeout, rads, !manu, finalMe.getGrossModifier(), infraLow); } else { - optimized = origin.roiBuild(continent, rads, numCities, hasProject, finalMe.getGrossModifier(), days, timeout, modifyValueFunc, goal); + Function finalValueFunc = valueFunc; + Function, Function> modifyValueFunc = f -> finalValueFunc; + optimized = origin.roiBuild(continent, rads, numCities, hasProject, finalMe.getGrossModifier(), days, timeout, !manu, infraLow, modifyValueFunc, goal); } optimized.setInfra(origin.getInfra()); @@ -554,8 +561,9 @@ public String onCommand(IMessageIO io, Guild guild, User author, DBNation me, Li String command = Settings.commandPrefix(true) + "grant {usermention} " + json; result.append(" Disease: ").append(optimized.calcDisease(hasProject)).append("\n"); + result.append(" Pollution: ").append(optimized.calcPollution(hasProject)).append("\n"); result.append(" Crime: ").append(optimized.calcCrime(hasProject)).append("\n"); - result.append(" Commerce: ").append(optimized.calcCommerce(hasProject)).append("\n"); + result.append(" Commerce: ").append(optimized.calcCommerce(hasProject) + "/" + optimized.getMaxCommerce(hasProject)).append("\n"); result.append(" Population: ").append(optimized.calcPopulation(hasProject)).append("\n"); result.append(" Click ").append(emoji).append(" to request a grant"); diff --git a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/BankCommands.java b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/BankCommands.java index 358afc97..6fbcdf1d 100644 --- a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/BankCommands.java +++ b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/BankCommands.java @@ -1555,7 +1555,7 @@ public String revenueSheet(@Me IMessageIO io, @Me GuildDB db, NationList nations JavaCity origin = new JavaCity(city1); origin.zeroNonMilitary().setOptimalPower(nation.getContinent()); try { - JavaCity optimal = origin.optimalBuild(nation, 0); + JavaCity optimal = origin.optimalBuild(nation, 0, false, null); double profitOptimal = 0; if (optimal != null) { profitOptimal = optimal.profitConvertedCached(nation.getContinent(), nation.getRads(), nation::hasProject, nation.getCities(), nation.getGrossModifier()); diff --git a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/UnsortedCommands.java b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/UnsortedCommands.java index 64a77777..adfba906 100644 --- a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/UnsortedCommands.java +++ b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/UnsortedCommands.java @@ -1074,7 +1074,7 @@ public String cityRevenue(@Me Guild guild, @Me IMessageIO channel, @Me User user } msg.append("\nLand: " + jCity.getLand()); - msg.append("\nCommerce: " + metrics.population); + msg.append("\nPopulation: " + metrics.population); if (metrics.commerce > 0) msg.append("\nCommerce: " + MathMan.format(metrics.commerce)); if (metrics.crime > 0) msg.append("\ncrime: " + MathMan.format(metrics.crime)); @@ -1829,7 +1829,8 @@ public String optimalBuild(@Me JSONObject command, @Me IMessageIO io, @Me Guild @Switch("t")TaxRate taxRate, @Arg(value = "Return a result on discord in plain text", group = 3) - @Switch("w") boolean writePlaintext + @Switch("w") boolean writePlaintext, + @Switch("calc") @Timediff Long calc_time ) throws Exception { @@ -1855,6 +1856,7 @@ public String optimalBuild(@Me JSONObject command, @Me IMessageIO io, @Me Guild cmd.add(build.toString()); if (buildMMR != null) cmd.add("mmr=" + buildMMR); + if (calc_time != null && calc_time > 9000) cmd.add("timeout=" + TimeUtil.secToTime(TimeUnit.MILLISECONDS, calc_time)); if (buildMMR != null) cmd.add("mmr=" + buildMMR); if (age != null) cmd.add("age=" + age); if (infra != null) cmd.add("infra=" + infra); diff --git a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/UtilityCommands.java b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/UtilityCommands.java index af1285a8..31d1bad4 100644 --- a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/UtilityCommands.java +++ b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/UtilityCommands.java @@ -2366,11 +2366,11 @@ public String infraROI(DBCity city, @Range(min=600,max=3000) int infraLevel, Predicate hasProject = forceProjects != null ? f -> forceProjects.contains(f) || nation.hasProject(f) : nation::hasProject; double grossModifier = DBNation.getGrossModifier(false, openMarkets, hasProject.test(Projects.GOVERNMENT_SUPPORT_AGENCY)); - JavaCity optimal1 = origin.optimalBuild(nation, 5000); + JavaCity optimal1 = origin.optimalBuild(nation, 5000, false, null); if (optimal1 == null) { return "Cannot generate optimal city build"; } - JavaCity optimal2 = originMinus50.optimalBuild(nation, 5000); + JavaCity optimal2 = originMinus50.optimalBuild(nation, 5000, false, null); if (optimal2 == null) { return "Cannot generate optimal city build"; } @@ -2416,11 +2416,11 @@ public String landROI(DBCity city, @Range(min=600,max=10000) double landLevel, Predicate hasProject = forceProjects != null ? f -> forceProjects.contains(f) || nation.hasProject(f) : nation::hasProject; double grossModifier = DBNation.getGrossModifier(false, openMarkets, hasProject.test(Projects.GOVERNMENT_SUPPORT_AGENCY)); - JavaCity optimal1 = origin.optimalBuild(nation, 5000); + JavaCity optimal1 = origin.optimalBuild(nation, 5000, false, null); if (optimal1 == null) { return "Cannot generate optimal city build"; } - JavaCity optimal2 = originMinus50.optimalBuild(nation, 5000); + JavaCity optimal2 = originMinus50.optimalBuild(nation, 5000, false, null); if (optimal2 == null) { return "Cannot generate optimal city build"; } diff --git a/src/main/java/link/locutus/discord/commands/sheets/ROI.java b/src/main/java/link/locutus/discord/commands/sheets/ROI.java index 16e16fd3..4d83b0f7 100644 --- a/src/main/java/link/locutus/discord/commands/sheets/ROI.java +++ b/src/main/java/link/locutus/discord/commands/sheets/ROI.java @@ -377,7 +377,7 @@ public static void roi(DBNation nation, long cityTurns, long projectTurns2, Java Predicate hasProjects = p -> p.get(nation) > 0; { - JavaCity optimal = existingCity.roiBuild(nation.getContinent(), rads, numCities, hasProjects, nation.getGrossModifier(), days, timeout); + JavaCity optimal = existingCity.roiBuild(nation.getContinent(), rads, numCities, hasProjects, nation.getGrossModifier(), days, timeout, false, null); if (optimal != null) { double baseOptimizedProfit = optimal.profitConvertedCached(nation.getContinent(), rads, hasProjects, numCities, nation.getGrossModifier()); if (baseOptimizedProfit > baseProfit) { @@ -411,7 +411,7 @@ public boolean test(Project p) { } }; - JavaCity optimal = existingCity.roiBuild(nation.getContinent(), rads, numCities, hasProjectsProxy, nation.getGrossModifier(), days, timeout); + JavaCity optimal = existingCity.roiBuild(nation.getContinent(), rads, numCities, hasProjectsProxy, nation.getGrossModifier(), days, timeout, false, null); if (optimal != null) { double profit = optimal.profitConvertedCached(nation.getContinent(), rads, hasProjectsProxy, numCities, nation.getGrossModifier()); Map cost = ResourceType.add( @@ -440,7 +440,7 @@ public boolean test(Project p) { return project == p || p.get(nation) > 0; } }; - JavaCity optimal = existingCity.roiBuild(nation.getContinent(), rads, numCities, hasProjectsProxy, nation.getGrossModifier(), days, timeout); + JavaCity optimal = existingCity.roiBuild(nation.getContinent(), rads, numCities, hasProjectsProxy, nation.getGrossModifier(), days, timeout, false, null); if (optimal != null) { double profit = optimal.profitConvertedCached(nation.getContinent(), rads, hasProjectsProxy, numCities, nation.getGrossModifier()); Map cost = ResourceType.add( @@ -489,7 +489,7 @@ public boolean test(Project p) { { JavaCity withInfra = new JavaCity(existingCity).setInfra(existingCity.getInfra() + Math.max(100, 1500 - existingCity.getInfra())); - withInfra = withInfra.roiBuild(nation.getContinent(), rads, numCities, hasProjects, nation.getGrossModifier(), days, timeout); + withInfra = withInfra.roiBuild(nation.getContinent(), rads, numCities, hasProjects, nation.getGrossModifier(), days, timeout, false, null); if (withInfra != null) { double profit = withInfra.profitConvertedCached(nation.getContinent(), rads, hasProjects, numCities, nation.getGrossModifier()); double[] cost = withInfra.calculateCost(existingCity); @@ -505,7 +505,7 @@ public boolean test(Project p) { { JavaCity withLand = new JavaCity(existingCity).setLand(existingCity.getLand() + 500); - withLand = withLand.roiBuild(nation.getContinent(), rads, numCities, hasProjects, nation.getGrossModifier(), days, timeout); + withLand = withLand.roiBuild(nation.getContinent(), rads, numCities, hasProjects, nation.getGrossModifier(), days, timeout, false, null); if (withLand != null) { double profit = withLand.profitConvertedCached(nation.getContinent(), rads, hasProjects, numCities, nation.getGrossModifier()); double[] cost = withLand.calculateCost(existingCity); diff --git a/src/main/java/link/locutus/discord/db/entities/CityNode.java b/src/main/java/link/locutus/discord/db/entities/CityNode.java new file mode 100644 index 00000000..0c180e45 --- /dev/null +++ b/src/main/java/link/locutus/discord/db/entities/CityNode.java @@ -0,0 +1,445 @@ +package link.locutus.discord.db.entities; + +import link.locutus.discord.apiv1.enums.Continent; +import link.locutus.discord.apiv1.enums.ResourceType; +import link.locutus.discord.apiv1.enums.city.ICity; +import link.locutus.discord.apiv1.enums.city.JavaCity; +import link.locutus.discord.apiv1.enums.city.building.*; +import link.locutus.discord.apiv1.enums.city.building.imp.APowerBuilding; +import link.locutus.discord.apiv1.enums.city.project.Project; +import link.locutus.discord.apiv1.enums.city.project.Projects; +import link.locutus.discord.util.MathMan; +import link.locutus.discord.util.PW; + +import java.util.*; +import java.util.function.*; + +public class CityNode implements ICity { + private final byte[] modifiable; + private double revenue = 0; + private int numBuildings; + private int commerce; + private int pollution; + + private int index; + + private CachedCity cached; + + private static int modIs = 4; + private static int modIe = Buildings.values().length - 4; + private static int LENGTH = modIe - modIs; + + private static final BiFunction[] getNumBuildings; + static { + getNumBuildings = new BiFunction[Buildings.values().length]; + for (int i = 0; i < modIs; i++) { + int finalI = i; + getNumBuildings[i] = (n, c) -> c.getBuildingOrdinal(finalI); + } + for (int i = modIs; i < modIe; i++) { + int finalI = i; + getNumBuildings[i] = (n, c) -> (int) n.modifiable[finalI - modIs]; + } + for (int i = modIe; i < Buildings.values().length; i++) { + int finalI = i; + getNumBuildings[i] = (n, c) -> c.getBuildingOrdinal(finalI); + } + } + + public void setIndex(int index) { + this.index = index; + } + + public int getFreeSlots() { + return cached.maxSlots - numBuildings; + } + + public int getIndex() { + return index; + } + + public int getCommerce() { + return commerce; + } + + public int getPollution() { + return pollution; + } + + public double calculateCostConverted(JavaCity from) { + return caclulateBuildingCostConverted(from); + } + + public CachedCity getCached() { + return cached; + } + + public static class CachedCity { + private final Consumer[] ADD_BUILDING; + private final Consumer[] REMOVE_BUILDING; + + private final byte[] buildings; + private final int basePollution; + private final List existing; + private final List buildable; + private final Building[] buildableArr; + private final Building[] buildablePollution; + private final int maxCommerce; + private final int baseCommerce; + private final double basePopulation; + private final double ageBonus; + private final double baseDisease; + private final double newPlayerBonus; + private final double[] crimeCache; + private final double[] commerceIncome; + private final double food; + private final double baseProfitConverted; + private final double[] baseProfit; + private final double hospitalPct; + private final double policePct; + private final int ageDays; + private final double buildingInfra; + private final double land; + private final int numBuildings; + private final Predicate hasProject; + private final double rads; + private final Continent continent; + private final boolean selfSufficient; + private final int maxSlots; + private final double infraLow; + private int maxIndex; + + public int getBuildingOrdinal(int ordinal) { + return buildings[ordinal]; + } + + public CachedCity(JavaCity city, Continent continent, boolean selfSufficient, Predicate hasProject, int numCities, double grossModifier, double rads, Double infraLow) { + this.hasProject = hasProject; + this.selfSufficient = selfSufficient; + this.ageDays = city.getAgeDays(); + this.rads = rads; + this.continent = continent; + this.buildingInfra = city.getInfra(); + this.land = city.getLand(); + this.buildings = city.getBuildings(); + this.maxSlots = (int) (buildingInfra / 50); + int basePollution = PW.City.getNukePollution(city.getNukeTurn()); + this.infraLow = infraLow == null ? city.getInfra() : infraLow; + + this.existing = new ArrayList<>(); + this.buildable = new ArrayList<>(); + for (Building building : Buildings.values()) { + if (building instanceof PowerBuilding) { + existing.add(building); + } else if (building instanceof MilitaryBuilding) { + existing.add(building); + } else { + buildable.add(building); + } + } + buildable.removeIf(f -> !f.canBuild(continent)); + if (selfSufficient) { + buildable.removeIf(f -> { + if (f instanceof ResourceBuilding rssBuild) { + if (rssBuild.getResourceProduced().isManufactured()) { + for (ResourceType req : rssBuild.getResourceTypesConsumed()) { + if (!continent.hasResource(req)) { + return true; + } + } + } + } + return false; + }); + } + + this.buildableArr = buildable.toArray(new Building[0]); + this.buildablePollution = Arrays.stream(buildableArr).filter(f -> f.pollution(hasProject) > 0).toArray(Building[]::new); + + this.maxCommerce = city.getMaxCommerce(hasProject); + this.baseCommerce = hasProject.test(Projects.TELECOMMUNICATIONS_SATELLITE) ? 2 : 0; + + this.basePopulation = this.infraLow * 100; + this.ageBonus = (1 + Math.log(Math.max(1, city.getAgeDays())) * 0.0666666666666666666666666666666); + this.baseDisease = ((0.01 * MathMan.sqr((this.infraLow * 100) / (city.getLand() + 0.001)) - 25) * 0.01d) + (this.infraLow * 0.001); + this.newPlayerBonus = 1 + Math.max(1 - (numCities - 1) * 0.05, 0); + + this.crimeCache = new double[126]; + this.commerceIncome = new double[126]; + for (int i = 0; i <= 125; i++) { + crimeCache[i] = (MathMan.sqr(103 - i) + (this.infraLow * 100))*(0.000009d); + commerceIncome[i] = Math.max(0, (((i * 0.02) * 0.725) + 0.725) * newPlayerBonus) * grossModifier; + } + + this.hospitalPct = hasProject.test(Projects.CLINICAL_RESEARCH_CENTER) ? 3.5 : 2.5; + this.policePct = hasProject.test(Projects.SPECIALIZED_POLICE_TRAINING_PROGRAM) ? 3.5 : 2.5; + + double baseProfitConverted = 0; + this.baseProfit = ResourceType.getBuffer(); + double infraUnpowered = this.infraLow; + int numBuildings = 0; + for (Building building : existing) { + int num = city.getBuilding(building); + if (num > 0) { + numBuildings += num; + basePollution += num * building.pollution(hasProject); + if (building instanceof PowerBuilding power) { + for (int i = 0; i < num; i++) { + if (infraUnpowered > 0) { + baseProfitConverted += power.consumptionConverted((int) infraUnpowered); + power.consumption((int) infraUnpowered, this.baseProfit, 12); + infraUnpowered -= power.getInfraMax(); + } + } + } + baseProfitConverted += building.profitConverted(continent, rads, hasProject, city, num); + building.profit(continent, rads, -1L, hasProject, city, this.baseProfit, 12, num); + } + } + this.numBuildings = numBuildings; + this.basePollution = basePollution; + this.food = (Math.pow(basePopulation, 2)) / 125_000_000 + ((basePopulation) * (1 + Math.log(city.getAgeDays()) / 15d) - basePopulation) / 850; + this.baseProfit[ResourceType.FOOD.ordinal()] -= food; + baseProfitConverted -= ResourceType.convertedTotalNegative(ResourceType.FOOD, food); + this.baseProfitConverted = baseProfitConverted; + + ADD_BUILDING = new Consumer[modIe - modIs]; + REMOVE_BUILDING = new Consumer[modIe - modIs]; + for (int i = modIs, j = 0; i < modIe; i++, j++) { + int finalJ = j; + Building building = Buildings.get(i); + int addCommerce = building.getCommerce(); + int addPollution = building.getPollution(hasProject); + if (addCommerce != 0) { + if (addPollution != 0) { + ADD_BUILDING[j] = (n) -> { + n.commerce += addCommerce; + n.pollution += addPollution; + n.numBuildings++; + n.modifiable[finalJ]++; + n.revenue = 0; + }; + REMOVE_BUILDING[j] = (n) -> { + n.commerce -= addCommerce; + n.pollution -= addPollution; + n.numBuildings--; + n.modifiable[finalJ]--; + n.revenue = 0; + }; + } else { + ADD_BUILDING[j] = (n) -> { + n.commerce += addCommerce; + n.numBuildings++; + n.modifiable[finalJ]++; + n.revenue = 0; + }; + REMOVE_BUILDING[j] = (n) -> { + n.commerce -= addCommerce; + n.numBuildings--; + n.modifiable[finalJ]--; + n.revenue = 0; + }; + } + } else if (addPollution != 0) { + ADD_BUILDING[j] = (n) -> { + n.pollution += addPollution; + n.numBuildings++; + n.modifiable[finalJ]++; + n.revenue = 0; + }; + REMOVE_BUILDING[j] = (n) -> { + n.pollution -= addPollution; + n.numBuildings--; + n.modifiable[finalJ]--; + n.revenue = 0; + }; + } else { + ADD_BUILDING[j] = (n) -> { + n.numBuildings++; + n.modifiable[finalJ]++; + n.revenue = 0; + }; + REMOVE_BUILDING[j] = (n) -> { + n.numBuildings--; + n.modifiable[finalJ]--; + n.revenue = 0; + }; + } + } + } + + public CityNode create() { + CityNode node = new CityNode(this, new byte[LENGTH], numBuildings, baseCommerce, basePollution, 0); + return node; + } + + public Continent getContinent() { + return continent; + } + + public double getRads() { + return rads; + } + + public Predicate hasProject() { + return hasProject; + } + + public boolean selfSufficient() { + return selfSufficient; + } + + public int getMaxCommerce() { + return maxCommerce; + } + + public void setMaxIndex(int length) { + this.maxIndex = length; + } + + public int getMaxIndex() { + return maxIndex; + } + } + + public CityNode(CachedCity origin, byte[] modifiable, int numBuildings, int commerce, int pollution, int index) { + this.cached = origin; + this.modifiable = modifiable; + this.numBuildings = numBuildings; + this.commerce = commerce; + this.pollution = pollution; + this.index = index; + } + + public CityNode clone() { + return new CityNode(cached, modifiable.clone(), numBuildings, commerce, pollution, index); + } + + @Override + public Boolean getPowered() { + return true; + } + + @Override + public int getBuilding(Building building) { + return getBuildingOrdinal(building.ordinal()); + } + + @Override + public int getPoweredInfra() { + return Integer.MAX_VALUE; + } + + @Override + public double getInfra() { + return cached.buildingInfra; + } + + @Override + public double getLand() { + return cached.land; + } + + @Override + public int getAgeDays() { + return cached.ageDays; + } + + @Override + public int getBuildingOrdinal(int ordinal) { + return getNumBuildings[ordinal].apply(this, cached); + } + + public void add(Building building) { + cached.ADD_BUILDING[building.ordinal() - modIs].accept(this); + } + + public void remove(Building building) { + cached.REMOVE_BUILDING[building.ordinal() - modIs].accept(this); + } + + public double getRevenueConverted() { + if (revenue != 0) { + return revenue; + } + int population = calcPopulation(cached.hasProject); + double revenue = cached.baseProfitConverted + cached.commerceIncome[commerce] * population; + for (int i = 0; i < modifiable.length; i++) { + byte amt = modifiable[i]; + if (amt == 0) continue; + Building building = Buildings.get(i + modIs); + revenue += building.profitConverted(cached.continent, cached.rads, cached.hasProject, this, amt); + } + return this.revenue = revenue; + } + + public double[] profit(double[] profitBuffer) { + for (int i = 0; i < profitBuffer.length; i++) { + profitBuffer[i] = cached.baseProfit[i]; + } + int population = calcPopulation(cached.hasProject); + double revenue = cached.baseProfitConverted + cached.commerceIncome[commerce] * population; + profitBuffer[0] += revenue; + + for (int i = 0; i < modifiable.length; i++) { + byte amt = modifiable[i]; + if (amt == 0) continue; + Building building = Buildings.get(i + modIs); + building.profit(cached.continent, cached.rads, -1L, cached.hasProject, this, profitBuffer, 12, amt); + } + return profitBuffer; + } + + @Override + public int calcPopulation(Predicate hasProject) { + double disease = calcDisease(hasProject); + double crime = calcCrime(hasProject); + return calcPopulation(disease, crime); + } + + public int calcPopulation(double disease, double crime) { + double diseaseDeaths = ((disease * 0.01) * cached.basePopulation); + double crimeDeaths = Math.max((crime * 0.1) * cached.basePopulation - 25, 0); + return (int) Math.round(Math.max(10, ((cached.basePopulation - diseaseDeaths - crimeDeaths) * cached.ageBonus))); + } + + @Override + public double calcDisease(Predicate hasProject) { + int hospitals = this.modifiable[Buildings.HOSPITAL.ordinal() - modIs]; + double hospitalModifier; + if (hospitals > 0) { + hospitalModifier = hospitals * cached.hospitalPct; + } else { + hospitalModifier = 0; + } + double pollutionModifier = pollution * 0.05; + return Math.max(0, cached.baseDisease - hospitalModifier + pollutionModifier); + } + + @Override + public double calcCrime(Predicate hasProject) { + int police = this.modifiable[Buildings.POLICE_STATION.ordinal() - modIs]; + double policeMod; + if (police > 0) { + policeMod = police * (cached.policePct); + } else { + policeMod = 0; + } + return Math.max(0, cached.crimeCache[commerce] - policeMod); + } + + @Override + public int calcCommerce(Predicate hasProject) { + return commerce; + } + + @Override + public int calcPollution(Predicate hasProject) { + return pollution; + } + + @Override + public int getNumBuildings() { + return numBuildings; + } +} diff --git a/src/main/java/link/locutus/discord/db/entities/DBCity.java b/src/main/java/link/locutus/discord/db/entities/DBCity.java index f6963594..95078dc0 100644 --- a/src/main/java/link/locutus/discord/db/entities/DBCity.java +++ b/src/main/java/link/locutus/discord/db/entities/DBCity.java @@ -552,7 +552,7 @@ public int getPollution() { @Override public int calcPollution(Predicate hasProject) { - return PW.City.getPollution(hasProject, this::getBuilding, nuke_turn); + return PW.City.getPollution(hasProject, this::getBuilding, PW.City.getNukePollution(nuke_turn)); } @Command(desc = "Get city commerce") @@ -618,7 +618,7 @@ public double getDisease() { @Override public double calcDisease(Predicate hasProject) { - double pollution = PW.City.getPollution(hasProject, this::getBuilding, nuke_turn); + double pollution = PW.City.getPollution(hasProject, this::getBuilding, PW.City.getNukePollution(nuke_turn)); return PW.City.getDisease(hasProject, this::getBuilding, infra_cents, land_cents, pollution); } @@ -629,7 +629,7 @@ public int getPopulation() { @Override public int calcPopulation(Predicate hasProject) { - int pollution = PW.City.getPollution(hasProject, this::getBuilding, nuke_turn); + int pollution = PW.City.getPollution(hasProject, this::getBuilding, PW.City.getNukePollution(nuke_turn)); double disease = PW.City.getDisease(hasProject, this::getBuilding, infra_cents, land_cents, pollution); int commerce = PW.City.getCommerce(hasProject, this::getBuilding); double crime = PW.City.getCrime(hasProject, this::getBuilding, infra_cents, commerce); diff --git a/src/main/java/link/locutus/discord/db/entities/grant/BuildTemplate.java b/src/main/java/link/locutus/discord/db/entities/grant/BuildTemplate.java index c77995bd..a831c621 100644 --- a/src/main/java/link/locutus/discord/db/entities/grant/BuildTemplate.java +++ b/src/main/java/link/locutus/discord/db/entities/grant/BuildTemplate.java @@ -188,7 +188,7 @@ public Map parse(DBNation receiver, String value) { city.setMMR(mmr); } city.zeroNonMilitary(); - city.optimalBuild(receiver, 5000); + city.optimalBuild(receiver, 5000, false, null); // generate build = city.toCityBuild(); } diff --git a/src/main/java/link/locutus/discord/util/PW.java b/src/main/java/link/locutus/discord/util/PW.java index e944c5d7..4536fc53 100644 --- a/src/main/java/link/locutus/discord/util/PW.java +++ b/src/main/java/link/locutus/discord/util/PW.java @@ -16,11 +16,7 @@ import link.locutus.discord.apiv1.enums.ResourceType; import link.locutus.discord.apiv1.enums.city.ICity; import link.locutus.discord.apiv1.enums.city.JavaCity; -import link.locutus.discord.apiv1.enums.city.building.Building; -import link.locutus.discord.apiv1.enums.city.building.Buildings; -import link.locutus.discord.apiv1.enums.city.building.CommerceBuilding; -import link.locutus.discord.apiv1.enums.city.building.MilitaryBuilding; -import link.locutus.discord.apiv1.enums.city.building.ResourceBuilding; +import link.locutus.discord.apiv1.enums.city.building.*; import link.locutus.discord.apiv1.enums.city.building.imp.APowerBuilding; import link.locutus.discord.apiv1.enums.city.building.imp.AResourceBuilding; import link.locutus.discord.apiv1.enums.city.project.Project; @@ -78,6 +74,20 @@ public class PW { public static class City { + public static int getNukePollution(int nukeTurn) { + int pollution = 0; + double pollutionMax = 400d; + int turnsMax = 11 * 12; + long turns = TimeUtil.getTurn() - nukeTurn; + if (turns < turnsMax) { + double nukePollution = (turnsMax - turns) * pollutionMax / (turnsMax); + if (nukePollution > 0) { + pollution += (int) nukePollution; + } + } + return pollution; + } + public static class Land { public static double calculateLand(double from, double to) { if (from < 0 || from == to) return 0; @@ -162,19 +172,7 @@ public static int calculateInfraAttackValue(int avg_infra, int cities) { } } - public static int getPollution(Predicate hasProject, Function getBuildings, int nuke_turn) { - int pollution = 0; - if (nuke_turn > 0) { - double pollutionMax = 400d; - int turnsMax = 11 * 12; - long turns = TimeUtil.getTurn() - nuke_turn; - if (turns < turnsMax) { - double nukePollution = (turnsMax - turns) * pollutionMax / (turnsMax); - if (nukePollution > 0) { - pollution += (int) nukePollution; - } - } - } + public static int getPollution(Predicate hasProject, Function getBuildings, int pollution) { for (Building building : Buildings.POLLUTION_BUILDINGS) { int amt = getBuildings.apply(building); if (amt == 0) continue; @@ -186,16 +184,25 @@ public static int getPollution(Predicate hasProject, Function hasProject, Function getBuildings) { - int commerce = 0; + public static int getCommerce(Predicate hasProject, Function getBuildings, int maxCommerce, int commerce) { for (Building building : Buildings.COMMERCE_BUILDINGS) { int amt = getBuildings.apply(building); if (amt == 0) continue; - commerce += amt * ((CommerceBuilding) building).getCommerce(); + commerce += amt * building.getCommerce(); + } + + if (commerce > maxCommerce) { + commerce = maxCommerce; } + return commerce; + } + + public static int getCommerce(Predicate hasProject, Function getBuildings) { + int commerce = 0; int maxCommerce; if (hasProject.test(Projects.INTERNATIONAL_TRADE_CENTER)) { if (hasProject.test(Projects.TELECOMMUNICATIONS_SATELLITE)) { + commerce += 2; maxCommerce = 125; } else { maxCommerce = 115; @@ -203,10 +210,7 @@ public static int getCommerce(Predicate hasProject, Function maxCommerce) { - commerce = maxCommerce; - } - return commerce; + return getCommerce(hasProject, getBuildings, maxCommerce, commerce); } public static double getCrime(Predicate hasProject, Function getBuildings, long infra_cents, int commerce) { @@ -231,9 +235,7 @@ public static double profitConverted(Continent continent, double rads, Predicate for (int ordinal = 0; ordinal < 4; ordinal++) { int amt = city.getBuildingOrdinal(ordinal); if (amt == 0) continue; - Building building = Buildings.get(ordinal); - for (int i = 0; i < amt; i++) { if (unpoweredInfra > 0) { profit += ((APowerBuilding) building).consumptionConverted(unpoweredInfra); @@ -245,7 +247,6 @@ public static double profitConverted(Continent continent, double rads, Predicate for (int ordinal = Buildings.GAS_REFINERY.ordinal(); ordinal < Buildings.size(); ordinal++) { int amt = city.getBuildingOrdinal(ordinal); if (amt == 0) continue; - Building building = Buildings.get(ordinal); profit += building.profitConverted(continent, rads, hasProject, city, amt); } @@ -261,11 +262,10 @@ public static double profitConverted(Continent continent, double rads, Predicate int commerce = powered ? city.calcCommerce(hasProject) : 0; - double newPlayerBonus = numCities < 10 ? Math.max(1, (200d - ((numCities - 1) * 10d)) * 0.01) : 1; + double newPlayerBonus = 1 + Math.max(1 - (numCities - 1) * 0.05, 0); double income = Math.max(0, (((commerce * 0.02) * 0.725) + 0.725) * city.calcPopulation(hasProject) * newPlayerBonus) * grossModifier;; - profit += income; double basePopulation = city.getInfra() * 100; @@ -303,13 +303,12 @@ public static double[] profit(Continent continent, for (Building building : Buildings.values()) { int amt = city.getBuilding(building); if (amt == 0) continue; - if (!powered) { if (building instanceof CommerceBuilding || building instanceof MilitaryBuilding || (building instanceof ResourceBuilding && ((AResourceBuilding) building).getResourceProduced().isManufactured())) { continue; } } - profitBuffer = building.profit(continent, rads, date, hasProject, city, profitBuffer, turns); + profitBuffer = building.profit(continent, rads, date, hasProject, city, profitBuffer, turns, amt); if (building instanceof APowerBuilding) { for (int i = 0; i < amt; i++) { if (unpoweredInfra > 0) { @@ -322,7 +321,7 @@ public static double[] profit(Continent continent, int commerce = city.calcCommerce(hasProject); double newPlayerBonus = 1 + Math.max(1 - (numCities - 1) * 0.05, 0); - double income = (((commerce/50d) * 0.725d) + 0.725d) * city.calcPopulation(hasProject) * newPlayerBonus * grossModifier; + double income = (((commerce * 0.02d) * 0.725d) + 0.725d) * city.calcPopulation(hasProject) * newPlayerBonus * grossModifier; profitBuffer[ResourceType.MONEY.ordinal()] += income * turns / 12; diff --git a/src/main/java/link/locutus/discord/util/search/BFSUtil.java b/src/main/java/link/locutus/discord/util/search/BFSUtil.java index 59a9d854..46cac6b7 100644 --- a/src/main/java/link/locutus/discord/util/search/BFSUtil.java +++ b/src/main/java/link/locutus/discord/util/search/BFSUtil.java @@ -4,6 +4,7 @@ import it.unimi.dsi.fastutil.PriorityQueue; import it.unimi.dsi.fastutil.objects.ObjectArrayFIFOQueue; import it.unimi.dsi.fastutil.objects.ObjectHeapPriorityQueue; +import link.locutus.discord.db.entities.CityNode; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -28,7 +29,8 @@ public static T search(Function goal, Function val T max = null; double maxValue = Double.NEGATIVE_INFINITY; - long start = System.currentTimeMillis(); + long originalStart = System.currentTimeMillis(); + long start = originalStart; long maxTimeout = TimeUnit.MINUTES.toMillis(1); double completeFactor = 0; @@ -73,17 +75,9 @@ public static T search(Function goal, Function val oldQueue = null; } - { - Map.Entry nextCity = (Map.Entry) next; - JavaCity city = nextCity.getKey(); - } - start = System.currentTimeMillis() - timeout + delay; } else if (System.currentTimeMillis() - start > maxTimeout) { break; - } else { - Map.Entry nextCity = (Map.Entry) next; - JavaCity city = nextCity.getKey(); } } } @@ -111,8 +105,8 @@ public static T search(Function goal, Function val branch.accept(next, queue); } - long diff = System.currentTimeMillis() - start; + long diff = System.currentTimeMillis() - originalStart; System.out.println("BFS. Searched " + i + " in " + diff + " for a rate of " + (i * 1000d / diff) + " per second"); return max; } -} +} \ No newline at end of file