#include "card_pool.h"
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <iterator>
#include <iomanip>
#include <sstream>
#include <string>
#include <vector>
std::vector<CardPoolEntry> CardPoolService::calculate(
const std::vector<Card>& cards) const
{
std::vector<Card> eligible;
std::ranges::copy_if(cards, std::back_inserter(eligible),
[](const Card& card)
{
return card.rarity > 0;
});
std::ranges::sort(eligible, {}, &Card::id);
if(eligible.empty())
{
return {};
}
const std::int64_t minimum = std::ranges::min(
eligible, {}, &Card::rarity).rarity;
double total = 0;
std::vector<CardPoolEntry> result;
result.reserve(eligible.size());
for(const Card& card : eligible)
{
const double weight = std::exp2(
static_cast<double>(minimum) -
static_cast<double>(card.rarity));
total += weight;
result.push_back({card, weight, 0});
}
for(CardPoolEntry& entry : result)
{
entry.probability = entry.scaled_weight / total;
}
return result;
}
mw::E<CardPoolEntry> CardPoolService::select(
const std::vector<CardPoolEntry>& entries,
mw::CryptoInterface& crypto) const
{
if(entries.empty())
{
return std::unexpected(mw::runtimeError("Card pool is empty"));
}
auto bytes = crypto.randomBytes(8);
if(!bytes)
{
return std::unexpected(std::move(bytes.error()));
}
std::uint64_t value = 0;
for(std::byte byte : *bytes)
{
value = (value << 8) | std::to_integer<std::uint64_t>(byte);
}
const std::uint64_t high_53 = value >> 11;
const double fraction = std::ldexp(static_cast<double>(high_53), -53);
double total = 0;
for(const CardPoolEntry& entry : entries)
{
total += entry.scaled_weight;
}
const double target = fraction * total;
double cumulative = 0;
const CardPoolEntry* last_positive = nullptr;
for(const CardPoolEntry& entry : entries)
{
if(entry.scaled_weight == 0)
{
continue;
}
last_positive = &entry;
cumulative += entry.scaled_weight;
if(target < cumulative)
{
return entry;
}
}
if(last_positive != nullptr)
{
return *last_positive;
}
return std::unexpected(mw::runtimeError(
"Card pool has no represented weight"));
}
std::string formatProbability(double probability)
{
if(probability <= 0)
{
return "0%";
}
const double percentage = probability * 100;
if(percentage < 0.000001)
{
return "<0.000001%";
}
std::ostringstream output;
output << std::fixed << std::setprecision(6) << percentage;
std::string result = output.str();
result.erase(result.find_last_not_of('0') + 1);
if(result.ends_with('.'))
{
result.pop_back();
}
return result + '%';
}