309 lines
12 KiB
C++
309 lines
12 KiB
C++
#include "game/sim/colony.h"
|
|
|
|
#include "check.h"
|
|
|
|
using namespace sots::sim;
|
|
|
|
static TuningTable tuning() {
|
|
TuningTable t;
|
|
t.POPULATION_GROWTH_MOD = 1.2;
|
|
t.POPULATION_GROWTH_EXP = 2.0;
|
|
t.INDSYS_IMPERIAL_POPULATION_MOD = 0.1;
|
|
t.SLAVES_DEATH_RATE = 0.05;
|
|
t.SLAVES_DEATH_RATE_BYHAZARD = 0.5;
|
|
t.SLAVES_DEATH_RATE_BYOUTPUT = 0.1;
|
|
t.SLAVES_MIN_DEATHS = 0;
|
|
t.SLAVES_MAX_DEATHS = -1;
|
|
t.MORALE_INCREASE_OUTPUT = 75;
|
|
t.MORALE_INCREASE_OUTPUT_MOD = 1.1;
|
|
t.MORALE_DECREASE_OUTPUT = 25;
|
|
t.MORALE_DECREASE_OUTPUT_MOD = 0.9;
|
|
t.STATION_BONUS_IMPERIAL_OUTPUT = 0.1;
|
|
t.STATION_BONUS_SHIPCON = 0.25;
|
|
t.ADDICTION_OUTPUT_MOD = 0.5;
|
|
t.SYSTEMBONUS_MINTURNS = 10;
|
|
t.SYSTEMBONUS_POPBONUS = 0.1;
|
|
t.SYSTEMBONUS_POPBONUS_HOME = 0.2;
|
|
t.SYSTEMBONUS_POPBONUS_INC = 0.01;
|
|
t.SYSTEMBONUS_INFRABONUS = 0.2;
|
|
t.SYSTEMBONUS_INFRABONUS_HOME = 0.5;
|
|
t.SYSTEMBONUS_INFRABONUS_INC = 0.05;
|
|
return t;
|
|
}
|
|
|
|
static void test_capacity() {
|
|
TuningTable t = tuning();
|
|
CapacityInputs c;
|
|
c.planetSize = 5;
|
|
CHECK_EQ(CarryingCapacity(c, t), std::int64_t{500000000});
|
|
c.hazardMod = 0.5;
|
|
CHECK_EQ(CarryingCapacity(c, t), std::int64_t{250000000});
|
|
c.arcologyTech = true;
|
|
CHECK_EQ(CarryingCapacity(c, t), std::int64_t{350000000});
|
|
c.group = PopGroup::Civilian;
|
|
CHECK_EQ(CarryingCapacity(c, t), std::int64_t{450000000});
|
|
c.group = PopGroup::Slaves;
|
|
CHECK_EQ(CarryingCapacity(c, t), std::int64_t{250000000}); // no arcology bonus for slaves
|
|
c.group = PopGroup::Imperial;
|
|
c.groupMaxEnabled = true;
|
|
c.groupMax = 300000000;
|
|
CHECK_EQ(CarryingCapacity(c, t), std::int64_t{300000000});
|
|
c.ownerIsNpc = true;
|
|
CHECK_EQ(CarryingCapacity(c, t), std::int64_t{30000000});
|
|
c.ownerIsNpc = false;
|
|
c.ownerIsDifferentSpecies = true;
|
|
c.crossSpeciesMod = 0.5;
|
|
CHECK_EQ(CarryingCapacity(c, t), std::int64_t{225000000}); // 5e8 x 0.5 x 0.5 + 1e8
|
|
c.species = Species::NPC;
|
|
CHECK_EQ(CarryingCapacity(c, t), std::int64_t{0});
|
|
c.species = Species::Liir;
|
|
c.speciesCanLive = false;
|
|
CHECK_EQ(CarryingCapacity(c, t), std::int64_t{0});
|
|
c.speciesCanLive = true;
|
|
c.planetSize = 0;
|
|
CHECK_EQ(CarryingCapacity(c, t), std::int64_t{100000000}); // arcology alone
|
|
|
|
CHECK_NEAR(HazardModifierShape(0.5, 0.5, 0.2), 1.0, 0.0);
|
|
CHECK_NEAR(HazardModifierShape(0.6, 0.5, 0.2), 0.5, 1e-12);
|
|
CHECK_NEAR(HazardModifierShape(0.9, 0.5, 0.2), 0.0, 0.0);
|
|
}
|
|
|
|
static void test_growth() {
|
|
TuningTable t = tuning();
|
|
GrowthInputs g;
|
|
g.capacity = 1000000;
|
|
|
|
g.pop = 0; // empty colony still grows by 1
|
|
CHECK_EQ(PopulationGrowthDelta(g, t), std::int64_t{1});
|
|
|
|
g.pop = 500000; // (1-0.5)^2 = 0.25; x1.2 = 0.3; x5e5 = 150000
|
|
CHECK_EQ(PopulationGrowthDelta(g, t), std::int64_t{150000});
|
|
|
|
g.playerPopMod = 0.5; // 75000
|
|
CHECK_EQ(PopulationGrowthDelta(g, t), std::int64_t{75000});
|
|
g.playerPopMod = 1.0;
|
|
g.groupGrowthMult = 2.0; // 300000
|
|
CHECK_EQ(PopulationGrowthDelta(g, t), std::int64_t{300000});
|
|
g.groupGrowthMult = 0.0; // zero column is ignored
|
|
CHECK_EQ(PopulationGrowthDelta(g, t), std::int64_t{150000});
|
|
g.flaggedByPlayer = true;
|
|
g.flaggedFactor = 0.0;
|
|
CHECK_EQ(PopulationGrowthDelta(g, t), std::int64_t{0}); // g becomes 0 -> no minimum 1
|
|
g.flaggedByPlayer = false;
|
|
|
|
g.pop = 1000000; // at capacity: no growth
|
|
CHECK_EQ(PopulationGrowthDelta(g, t), std::int64_t{0});
|
|
g.pop = 2000000; // over capacity clamps to 0 growth
|
|
CHECK_EQ(PopulationGrowthDelta(g, t), std::int64_t{0});
|
|
|
|
g.pop = 1000000000;
|
|
g.capacity = 2000000000; // 0.3 x 1e9 = 3e8 -> capped at 5e7
|
|
CHECK_EQ(PopulationGrowthDelta(g, t), std::int64_t{50000000});
|
|
|
|
g.blockaded = true;
|
|
CHECK_EQ(PopulationGrowthDelta(g, t), std::int64_t{0});
|
|
g.blockaded = false;
|
|
g.capacity = 0;
|
|
CHECK_EQ(PopulationGrowthDelta(g, t), std::int64_t{0});
|
|
|
|
CHECK_EQ(ApplyImperialGrowth(500000, 1000000, 150000), std::int64_t{650000});
|
|
CHECK_EQ(ApplyImperialGrowth(999999, 1000000, 5), std::int64_t{1000000});
|
|
CHECK_EQ(ApplyImperialGrowth(2000000, 1000000, 0), std::int64_t{1000000});
|
|
CHECK_EQ(ApplyImperialGrowth(1000000000, 100000000, 0), std::int64_t{950000000}); // shrink capped
|
|
CHECK_EQ(ApplyImperialGrowth(50, 10, 0), std::int64_t{50}); // floor min(pop, 100)
|
|
CHECK_EQ(ApplyImperialGrowth(500, 10, 0), std::int64_t{100});
|
|
}
|
|
|
|
static void test_infra_terraform() {
|
|
CHECK_EQ(InfrastructurePointsNeeded(0.0), 30304); // ceil(30303.03)
|
|
CHECK_EQ(InfrastructurePointsNeeded(1.0), 0);
|
|
CHECK_NEAR(InfrastructureGain(500), 0.0165, 1e-12);
|
|
CHECK_NEAR(InfrastructureGain(1000), 0.033, 1e-12);
|
|
int used = -1;
|
|
double infra = ApplyInfrastructurePoints(0.5, 100000, &used);
|
|
CHECK_EQ(used, 15152); // ceil(0.5 / 3.3e-5)
|
|
CHECK(infra >= 1.0 && infra < 1.0001);
|
|
infra = ApplyInfrastructurePoints(0.5, 1000, &used);
|
|
CHECK_EQ(used, 1000);
|
|
CHECK_NEAR(infra, 0.533, 1e-12);
|
|
CHECK_NEAR(DecayUnownedInfrastructure(0.5), 0.48, 1e-12);
|
|
CHECK_NEAR(DecayUnownedInfrastructure(0.01), 0.0, 0.0);
|
|
|
|
CHECK_EQ(TerraformPointsNeeded(0.5, 0.8), 3333); // 0.3 / 9e-5
|
|
CHECK_EQ(TerraformPointsNeeded(0.8, 0.5), 3333);
|
|
CHECK_EQ(TerraformPointsNeeded(0.5, 0.5), 0);
|
|
CHECK_NEAR(TerraformDelta(1000, 1.0, 0.5, 0.8), 0.09, 1e-12);
|
|
CHECK_NEAR(TerraformDelta(1000, 1.0, 0.8, 0.5), -0.09, 1e-12);
|
|
CHECK_NEAR(TerraformDelta(1000, 2.0, 0.5, 0.8), 0.18, 1e-12);
|
|
CHECK_NEAR(TerraformDelta(0, 2.0, 0.5, 0.8), 0.0, 0.0);
|
|
}
|
|
|
|
static void test_slaves() {
|
|
TuningTable t = tuning();
|
|
SpeciesTechFlags f;
|
|
// 0.5 x 0.1 + 0.2 x 0.5 + 0.05 = 0.2
|
|
CHECK_NEAR(SlaveDeathRate(0.5, 0.3, 0.5, f, t), 0.2, 1e-12);
|
|
f.slaveDeathTech0 = true;
|
|
CHECK_NEAR(SlaveDeathRate(0.5, 0.3, 0.5, f, t), 0.16, 1e-12);
|
|
f.slaveDeathTech1 = f.slaveDeathTech2 = true;
|
|
CHECK_NEAR(SlaveDeathRate(0.5, 0.3, 0.5, f, t), 0.08, 1e-12);
|
|
SpeciesTechFlags none;
|
|
CHECK_NEAR(SlaveDeathRate(0.0, 0.5, 0.5, none, t), 0.05, 1e-12); // base rate only
|
|
|
|
CHECK_EQ(SlaveDeaths(1000, 0.2, t), std::int64_t{200});
|
|
CHECK_EQ(SlaveDeaths(0, 0.2, t), std::int64_t{0});
|
|
t.SLAVES_MIN_DEATHS = 300;
|
|
CHECK_EQ(SlaveDeaths(1000, 0.2, t), std::int64_t{300});
|
|
CHECK_EQ(SlaveDeaths(100, 0.2, t), std::int64_t{100}); // never more than present
|
|
t.SLAVES_MAX_DEATHS = 150;
|
|
CHECK_EQ(SlaveDeaths(1000, 0.2, t), std::int64_t{150});
|
|
t.SLAVES_MIN_DEATHS = 0;
|
|
CHECK_EQ(SlaveDeaths(7, 0.2, t), std::int64_t{1}); // 1.4 truncates
|
|
}
|
|
|
|
static void test_output() {
|
|
TuningTable t = tuning();
|
|
OutputRates r = NormaliseOutputRates({1, 1, 1, 1}, false, false);
|
|
CHECK_NEAR(r.trade, 0.25, 1e-12);
|
|
r = NormaliseOutputRates({1, 1, 1, 1}, true, false);
|
|
CHECK_NEAR(r.terraform, 0.0, 0.0);
|
|
CHECK_NEAR(r.infra, 1.0 / 3.0, 1e-12);
|
|
r = NormaliseOutputRates({0, 0, 0, 0}, false, false);
|
|
CHECK_NEAR(r.construction, 0.25, 0.0);
|
|
r = NormaliseOutputRates({-1, 3, 0, 1}, false, true);
|
|
CHECK_NEAR(r.trade, 0.0, 0.0);
|
|
CHECK_NEAR(r.construction, 1.0, 0.0);
|
|
CHECK_NEAR(r.infra, 0.0, 0.0);
|
|
|
|
CHECK_NEAR(MoraleOutputMultiplier(80, t), 1.1, 0.0);
|
|
CHECK_NEAR(MoraleOutputMultiplier(75, t), 1.1, 0.0);
|
|
CHECK_NEAR(MoraleOutputMultiplier(50, t), 1.0, 0.0);
|
|
CHECK_NEAR(MoraleOutputMultiplier(25, t), 0.9, 0.0);
|
|
|
|
OutputModifiers m;
|
|
m.baseOutput = 1000;
|
|
m.morale = 80;
|
|
m.stations = 1;
|
|
CHECK_EQ(TotalSystemOutput(m, t), 1210); // 1000 x 1.1 x 1.1
|
|
m.addictionPhase3 = true;
|
|
CHECK_EQ(TotalSystemOutput(m, t), 605);
|
|
m.addictionPhase3 = false;
|
|
m.playerOutMod = 0.5;
|
|
m.systemOutMod = 0.5;
|
|
CHECK_EQ(TotalSystemOutput(m, t), 303); // 302.5 rounds up
|
|
m.baseOutput = 0;
|
|
CHECK_EQ(TotalSystemOutput(m, t), 0);
|
|
|
|
OutputSplit s = SplitOutput(1000, {0.5, 0.25, 0.125, 0.125});
|
|
CHECK_EQ(s.trade, 500);
|
|
CHECK_EQ(s.construction, 250);
|
|
CHECK_EQ(s.terraform, 125);
|
|
CHECK_EQ(s.infra, 125);
|
|
|
|
CHECK_EQ(ConstructionPoints(250, 2, t), 375);
|
|
CHECK_EQ(ConstructionPoints(250, 0, t), 250);
|
|
|
|
OutputSplit l = SplitLeftover(100, {0.5, 0.25, 0.125, 0.125}, false, false);
|
|
CHECK_EQ(l.trade, 67);
|
|
CHECK_EQ(l.terraform, 17);
|
|
CHECK_EQ(l.infra, 17);
|
|
l = SplitLeftover(100, {0, 1, 0, 0}, true, false); // construction-only slider
|
|
CHECK_EQ(l.trade, 50);
|
|
CHECK_EQ(l.terraform, 0);
|
|
CHECK_EQ(l.infra, 50);
|
|
l = SplitLeftover(100, {0, 1, 0, 0}, true, true);
|
|
CHECK_EQ(l.trade, 100);
|
|
l = SplitLeftover(0, {0.5, 0.25, 0.125, 0.125}, false, false);
|
|
CHECK_EQ(l.trade, 0);
|
|
|
|
CHECK_EQ(SystemMoneyIncomeShape(1000, 1.5, 1.1, 10.0), 1640); // 1650 - 10
|
|
CHECK_EQ(SystemMoneyIncomeShape(0, 1.5, 1.1, 10.0), -10);
|
|
}
|
|
|
|
static void test_bonuses() {
|
|
TuningTable t = tuning();
|
|
std::int64_t pop = 900, bonus = 500;
|
|
ApplyPopulationBonus(pop, 1000, bonus);
|
|
CHECK_EQ(pop, std::int64_t{1000});
|
|
CHECK_EQ(bonus, std::int64_t{400});
|
|
pop = 1200; // over cap: nothing applied
|
|
ApplyPopulationBonus(pop, 1000, bonus);
|
|
CHECK_EQ(pop, std::int64_t{1200});
|
|
CHECK_EQ(bonus, std::int64_t{400});
|
|
|
|
double infra = 0.95, ibon = 0.1;
|
|
ApplyInfrastructureBonus(infra, ibon);
|
|
CHECK_NEAR(infra, 1.0, 1e-12);
|
|
CHECK_NEAR(ibon, 0.05, 1e-12);
|
|
|
|
SystemBonusInputs in;
|
|
in.stable = true;
|
|
in.turnsOwned = 11;
|
|
in.turnsSinceRebellion = 11;
|
|
in.capacity = 1000000;
|
|
std::int64_t pbon = 0;
|
|
double ibonus = 0;
|
|
AccrueSystemBonus(in, pbon, ibonus, t);
|
|
CHECK_EQ(pbon, std::int64_t{10000}); // 1e6 x 0.01
|
|
CHECK_NEAR(ibonus, 0.05, 1e-12);
|
|
for (int i = 0; i < 20; ++i) AccrueSystemBonus(in, pbon, ibonus, t);
|
|
CHECK_EQ(pbon, std::int64_t{100000}); // capped at 1e6 x 0.1
|
|
CHECK_NEAR(ibonus, 0.2, 1e-12); // capped at INFRABONUS
|
|
in.homeSystem = true;
|
|
for (int i = 0; i < 20; ++i) AccrueSystemBonus(in, pbon, ibonus, t);
|
|
CHECK_EQ(pbon, std::int64_t{200000});
|
|
CHECK_NEAR(ibonus, 0.5, 1e-12);
|
|
|
|
std::int64_t p2 = 0;
|
|
double i2 = 0;
|
|
in.turnsOwned = 10; // not strictly more than MINTURNS
|
|
AccrueSystemBonus(in, p2, i2, t);
|
|
CHECK_EQ(p2, std::int64_t{0});
|
|
in.turnsOwned = 11;
|
|
in.stable = false;
|
|
AccrueSystemBonus(in, p2, i2, t);
|
|
CHECK_EQ(p2, std::int64_t{0});
|
|
}
|
|
|
|
static void test_build_queue() {
|
|
std::vector<BuildOrder> q = {{1, 11, 100, 100, 50}, {2, 12, 200, 200, 0}, {3, 13, 300, 300, 70}};
|
|
BuildQueueResult r = ProcessBuildQueue(q, 250);
|
|
CHECK_EQ(r.completedOrderIds.size(), std::size_t{1});
|
|
CHECK_EQ(r.completedOrderIds[0], 11);
|
|
CHECK_EQ(r.moneyCharged, 50);
|
|
CHECK_EQ(r.pointsLeft, 0);
|
|
CHECK_EQ(q.size(), std::size_t{2});
|
|
CHECK_EQ(q[0].orderId, 12);
|
|
CHECK_EQ(q[0].constructionLeft, 50);
|
|
CHECK_EQ(q[1].constructionLeft, 300);
|
|
|
|
r = ProcessBuildQueue(q, 700);
|
|
CHECK_EQ(r.completedOrderIds.size(), std::size_t{2});
|
|
CHECK_EQ(r.moneyCharged, 70);
|
|
CHECK_EQ(r.pointsLeft, 350);
|
|
CHECK(q.empty());
|
|
|
|
r = ProcessBuildQueue(q, 100); // empty queue: points pass through
|
|
CHECK_EQ(r.pointsLeft, 100);
|
|
|
|
std::vector<BuildOrder> exact = {{1, 21, 100, 100, 0}};
|
|
r = ProcessBuildQueue(exact, 100); // exactly enough completes
|
|
CHECK_EQ(r.completedOrderIds.size(), std::size_t{1});
|
|
CHECK(exact.empty());
|
|
|
|
std::vector<BuildOrder> zero = {{1, 31, 100, 100, 0}};
|
|
r = ProcessBuildQueue(zero, 0);
|
|
CHECK(r.completedOrderIds.empty());
|
|
CHECK_EQ(zero[0].constructionLeft, 100);
|
|
}
|
|
|
|
int main() {
|
|
test_capacity();
|
|
test_growth();
|
|
test_infra_terraform();
|
|
test_slaves();
|
|
test_output();
|
|
test_bonuses();
|
|
test_build_queue();
|
|
return simtest::finish("test_colony");
|
|
}
|