From d51f3ee36bcd6373b178cc724bdc35af35d4406e Mon Sep 17 00:00:00 2001 From: Twan van Laarhoven Date: Tue, 19 Nov 2019 21:52:40 +0100 Subject: [PATCH] clean up simulation code --- src/repl.cpp | 101 ++++++++++++++-------------------------- src/simulation.hpp | 112 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+), 67 deletions(-) create mode 100644 src/simulation.hpp diff --git a/src/repl.cpp b/src/repl.cpp index f83419c..f53a220 100644 --- a/src/repl.cpp +++ b/src/repl.cpp @@ -1,4 +1,5 @@ #include "battle.hpp" +#include "simulation.hpp" #include #include #include @@ -26,7 +27,7 @@ struct REPL { vector history; // simulating - int default_num_runs = 1000; + int default_num_runs = DEFAULT_NUM_RUNS; // error messages string filename; @@ -277,22 +278,22 @@ void REPL::parse_line(std::string const& line) { in >> n; do_run(n); } else if (cmd == "runs") { - int n = 1000; - if (in >> n) { + int n = DEFAULT_NUM_RUNS; + if (in >> n && n > 0) { default_num_runs = n; } else { error() << "Please specify number of runs" << endl; } } else if (cmd == "level") { int n = 0; - if (in >> n) { + if (in >> n && n >= 0) { players[current_player].level = n; } else { error() << "Expected number" << endl; } } else if (cmd == "health") { int n = 0; - if (in >> n) { + if (in >> n && n >= 0) { players[current_player].health = n; } else { error() << "Expected number" << endl; @@ -472,40 +473,16 @@ void REPL::do_add_minion(Minion const& m) { } } -vector simulate(Battle const& b, int n) { - vector results; - results.reserve(n); - for (int i=0; i const& xs) { - // work around emscripten bug - //return accumulate(xs.begin(), xs.end(), 0.) / xs.size(); - double sum = 0.; - for(int x : xs) sum += x; - return sum / xs.size(); -} - -double mean_damage(vector const& xs, int level, int sign = 1) { - double sum = 0.; - for(int x : xs) if (sign*x < 0) sum += level - sign*x; - return sum / xs.size(); -} - void print_stats(ostream& out, vector const& results) { - int wins = count_if(results.begin(), results.end(), [](int i) {return i > 0;}); - int losses = count_if(results.begin(), results.end(), [](int i) {return i < 0;}); - int ties = (int)results.size() - wins - losses; - int n = static_cast(results.size()); - out << "win: " << (wins*100/n) << "%, tie: " << (ties*100)/n << "%, lose: " << (losses*100)/n << "%" << endl; - out << "mean score: " << mean(results); + ScoreSummary summary(results); + int n = summary.num_runs(); + out.precision(1); + out.setf(std::ios::fixed, std:: ios::floatfield); + out << "win: " << 100*summary.win_rate() << "%, "; + out << "tie: " << 100*summary.draw_rate() << "%, "; + out << "lose: " << 100*summary.loss_rate() << "%" << endl; + out.precision(3); + out << "mean score: " << summary.mean_score(); out << ", median score: " << results[results.size()/2] << endl; int steps = 10; out << "percentiles: "; @@ -514,12 +491,22 @@ void print_stats(ostream& out, vector const& results) { } out << endl; } - -int percentile(int i, vector const& results) { - auto bounds = equal_range(results.begin(), results.end(), i); - int a = static_cast(bounds.first - results.begin()); - int b = static_cast(bounds.second - results.begin()); - return 100 * (a + b) / 2 / (results.size() - 1); +void print_outcome_percentile(ostream& out, int outcome, vector const& results) { + int p = percentile(outcome,results); + out << "actual outcome: " << outcome << ", is at the " << p << "-th percentile" + << (p < 15 ? ", you got unlucky" : p > 85 ? ", you got lucky" : "") << endl; +} + +void print_damage_taken(ostream& out, int enemy_level, int health, vector const& results, int sign = 1) { + if (enemy_level <= 0) return; + double dmg = mean_damage_taken(results, enemy_level, sign); + out.precision(3); + out << "mean damage " << (sign == 1 ? "taken" : "dealt") << ": " << dmg << endl; + if (health > 0) { + if (sign < 0) out << "their "; + out << "expected health afterwards: " << (health - dmg); + out << ", " << death_rate(results, enemy_level, health, sign) << "% chance to die" << endl; + } } void REPL::do_run(int n) { @@ -528,30 +515,10 @@ void REPL::do_run(int n) { out << "--------------------------------" << endl; print_stats(out, results); for (int o : actual_outcomes) { - int p =percentile(o,results); - out << "actual outcome: " << o << ", is at the " << p << "-th percentile" - << (p < 15 ? ", you got unlucky" : p > 85 ? ", you got lucky" : "") << endl; - } - if (players[1].level > 0) { - double dmg = mean_damage(results, players[1].level); - out << "mean damage taken: " << dmg << endl; - if (players[0].health > 0) { - out << "expected health afterwards: " << (players[0].health - dmg); - int deaths = 0; - for(int x : results) if (x < 0 && (players[1].level-x) >= players[0].health) deaths++; - out << ", " << (deaths*100)/n << "% chance to die" << endl; - } - } - if (players[0].level > 0) { - double dmg = mean_damage(results, players[0].level, -1); - out << "mean damage dealt: " << dmg << endl; - if (players[1].health > 0) { - out << "expected enemy health afterwards: " << (players[1].health - dmg); - int deaths = 0; - for(int x : results) if (-x < 0 && (players[0].level+x) >= players[1].health) deaths++; - out << ", " << (deaths*100)/n << "% chance they die" << endl; - } + print_outcome_percentile(out, o, results); } + print_damage_taken(out, players[1].level, players[0].health, results); + print_damage_taken(out, players[0].level, players[1].health, results, -1); out << "--------------------------------" << endl; used = true; } diff --git a/src/simulation.hpp b/src/simulation.hpp new file mode 100644 index 0000000..00b834f --- /dev/null +++ b/src/simulation.hpp @@ -0,0 +1,112 @@ +#include "battle.hpp" +#include +#include +using std::vector; + +// ----------------------------------------------------------------------------- +// Simulation +// ----------------------------------------------------------------------------- + +const int DEFAULT_NUM_RUNS = 1000; + +inline int simulate_single(Battle const& battle) { + Battle copy(battle); + copy.verbose = 0; + copy.run(); + return copy.score(); +} + +vector simulate(Battle const& b, int n = DEFAULT_NUM_RUNS) { + vector results; + results.reserve(n); + for (int i=0; i const& scores) { + add_scores(scores); + } + + int num_runs() const { return num_wins + num_draws + num_losses; } + double win_rate() const { return (double)num_wins / num_runs(); } + double draw_rate() const { return (double)num_draws / num_runs(); } + double loss_rate() const { return (double)num_losses / num_runs(); } + double mean_score() const { return (double)total_score / num_runs(); } + // for optimization use this score: + inline double optimization_score() { + return win_rate() + draw_rate() * 0.5; + } + + void add_score(int score) { + total_score += score; + if (score > 0) num_wins++; + else if (score == 0) num_draws++; + else num_losses++; + } + void add_scores(vector const& scores) { + for (auto x : scores) add_score(x); + } +}; + +ScoreSummary simulate_summary(Battle const& battle, int runs = DEFAULT_NUM_RUNS) { + ScoreSummary results; + for (int i=0; i const& xs) { + // work around emscripten bug (missing accumulate function) + //return std::accumulate(xs.begin(), xs.end(), 0.) / xs.size(); + double sum = 0.; + for(int x : xs) sum += x; + return sum / xs.size(); +} + +double mean_damage_taken(vector const& xs, int enemy_level, int sign = 1) { + double sum = 0.; + for(int x : xs) if (sign*x < 0) sum += enemy_level - sign*x; + return sum / xs.size(); +} + +double mean_damage_dealt(vector const& xs, int level) { + return mean_damage_taken(xs,level,-1); +} + +double death_rate(vector const& results, int enemy_level, int health, int sign = 1) { + int deaths = 0; + for(int x : results) if (sign*x < 0 && (enemy_level - sign*x) >= health) deaths++; + return (double)deaths / results.size(); +} + +int percentile(int i, vector const& results) { + auto bounds = equal_range(results.begin(), results.end(), i); + int a = static_cast(bounds.first - results.begin()); + int b = static_cast(bounds.second - results.begin()); + return 100 * (a + b) / 2 / (results.size() - 1); +} + +// ----------------------------------------------------------------------------- +// Optimization +// ----------------------------------------------------------------------------- + +