From 6afb76d41c400f4b6045910f113c855d49b57b66 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 27 Aug 2018 22:29:14 -0500 Subject: [PATCH] autofarm: make crop assignment more stable --- plugins/devel/autofarm.cpp | 61 ++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/plugins/devel/autofarm.cpp b/plugins/devel/autofarm.cpp index aaee2764e..0fc02728b 100644 --- a/plugins/devel/autofarm.cpp +++ b/plugins/devel/autofarm.cpp @@ -22,10 +22,13 @@ #include "modules/Maps.h" #include "modules/World.h" +#include + using std::vector; using std::string; using std::map; using std::set; +using std::queue; using std::endl; using namespace DFHack; using namespace df::enums; @@ -195,27 +198,53 @@ public: } } - void set_farms(color_ostream& out, vector plants, vector farms) + void set_farms(color_ostream& out, set plants, vector farms) { - if (farms.empty()) - return; - - if (plants.empty()) - plants = vector{ -1 }; + // this algorithm attempts to change as few farms as possible, while ensuring that + // the number of farms planting each eligible plant is "as equal as possible" + + if (farms.empty() || plants.empty()) + return; // do nothing if there are no farms or no plantable plants int season = *df::global::cur_season; - for (int idx = 0; idx < farms.size(); idx++) + int min = farms.size() / plants.size(); // the number of farms that should plant each eligible plant, rounded down + int extra = farms.size() - min * plants.size(); // the remainder that cannot be evenly divided + + map counters; + counters.empty(); + + queue toChange; + toChange.empty(); + + for (auto farm : farms) { - df::building_farmplotst* farm = farms[idx]; int o = farm->plant_id[season]; - int n = plants[idx % plants.size()]; - if (n != o) + if (plants.count(o)==0 || counters[o] > min || (counters[o] == min && extra == 0)) + toChange.push(farm); // this farm is an excess instance for the plant it is currently planting + else { - farm->plant_id[season] = plants[idx % plants.size()]; + if (counters[o] == min) + extra--; // allocate off one of the remainder farms + counters[o]++; + } + } + + for (auto n : plants) + { + int c = counters[n]; + while (toChange.size() > 0 && (c < min || (c == min && extra > 0))) + { + // pick one of the excess farms and change it to plant this plant + df::building_farmplotst* farm = toChange.front(); + int o = farm->plant_id[season]; + farm->plant_id[season] = n; out << "autofarm: changing farm #" << farm->id << - " from " << ((o == -1) ? "NONE" : world->raws.plants.all[o]->name) << + " from " << ((o == -1) ? "NONE" : world->raws.plants.all[o]->name) << " to " << ((n == -1) ? "NONE" : world->raws.plants.all[n]->name) << endl; + toChange.pop(); + if (c++ == min) + extra--; } } } @@ -248,7 +277,7 @@ public: } } - map> plants; + map> plants; plants.clear(); for (auto plantable : plantable_plants) @@ -257,7 +286,7 @@ public: if (lastCounts[plant->index] < getThreshold(plant->index)) for (auto biome : plantable.second) { - plants[biome].push_back(plant->index); + plants[biome].insert(plant->index); } } @@ -362,7 +391,9 @@ static command_result autofarm(color_ostream &out, vector & parameters) { CoreSuspender suspend; - if (parameters.size() == 1 && parameters[0] == "enable") + if (parameters.size() == 1 && parameters[0] == "runonce") + autofarmInstance->process(out); + else if (parameters.size() == 1 && parameters[0] == "enable") plugin_enable(out, true); else if (parameters.size() == 1 && parameters[0] == "disable") plugin_enable(out, false);