89 lines
3.6 KiB
C++
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
|