sots-engine/src/app/treaty.cpp

89 lines
3.6 KiB
C++

#include "app/treaty.h"
namespace sots::app {
using mars::stream::shapes::DipStat;
using mars::stream::shapes::Player;
using mars::stream::shapes::PlayerEntry;
namespace {
// "This kind of treaty was last in force on turn N", one field per treaty kind. A fresh
// entry starts all three at -1, which is what distinguishes "never" from "on turn 0".
constexpr std::int32_t kNever = -1;
std::int32_t* StampField(DipStat& e, Relation r) {
switch (r) {
case Relation::Allied: return &e.lastally;
case Relation::NonAggression: return &e.lastnap;
case Relation::CeaseFire: return &e.lastcf;
case Relation::War: return nullptr;
}
return nullptr;
}
} // namespace
Relation RelationTo(const Player& self, const Player& other) {
// Self first, and by the index field -- two players with the same index are the same
// player as far as this test is concerned, which is how the original short-circuits.
if (self.plyrIdx == other.plyrIdx) return Relation::Allied;
// x86 masks a variable shift count to five bits. Reproduced rather than corrected, for
// the same reason `alliance.cpp` reproduces it: the point is to be the original.
const std::uint32_t bit = 1u << (static_cast<unsigned>(other.plyrIdx) & 31u);
// The alliance mask is tested UNCONDITIONALLY here -- there is no alliance-id guard, and
// that is the difference from the shared-vision mask in alliance.cpp.
if (static_cast<std::uint32_t>(self.alliances.al) & bit) return Relation::Allied;
if (static_cast<std::uint32_t>(self.alliances.na) & bit) return Relation::NonAggression;
if (static_cast<std::uint32_t>(self.alliances.cf) & bit) return Relation::CeaseFire;
return Relation::War;
}
DipStat NewDipStat(std::int32_t otherPlayerId) {
DipStat e;
e.other = otherPlayerId;
e.lastnap = kNever;
e.lastally = kNever;
e.lastcf = kNever;
return e;
}
TreatyStampResult StampTreatyTurns(std::vector<PlayerEntry>& players, std::int32_t frame) {
TreatyStampResult r;
const std::size_t n = players.size();
for (std::size_t i = 0; i < n; ++i) {
Player& a = players[i].player;
for (std::size_t j = 0; j < n; ++j) {
if (i == j) continue; // the original compares the POINTERS, not the indices
const Player& b = players[j].player;
const Relation rel = RelationTo(a, b);
if (rel == Relation::War) continue;
++r.pairsStamped;
const std::int32_t otherId = players[j].playerID;
DipStat* e = nullptr;
for (auto& cand : a.dipstats) // linear, first match, exactly as the original
if (cand.other == otherId) {
e = &cand;
break;
}
if (!e) {
a.dipstats.push_back(NewDipStat(otherId)); // appended at the END
e = &a.dipstats.back();
++r.entriesCreated;
}
std::int32_t* field = StampField(*e, rel);
if (field && *field != frame) ++r.fieldsWritten;
if (field) *field = frame;
}
}
// The phase's own accounting: one for each stamp that moved a value, plus one for each
// record that did not exist before. It is deliberately NOT an attempt to predict the
// divergence report's leaf count, which also charges two container leaves to each player
// whose ledger goes from empty to non-empty -- on the reference pair this reports 28
// where the report closes 26. The report is the authority; this is what the phase did.
r.leafWrites = r.fieldsWritten + r.entriesCreated;
return r;
}
} // namespace sots::app