From 395d64c621d7d8133aed0534c9e95460a7e3c82c Mon Sep 17 00:00:00 2001 From: Twan van Laarhoven Date: Tue, 19 Nov 2019 22:31:40 +0100 Subject: [PATCH] Added minion order optimization --- src/battle.cpp | 10 +++++----- src/battle.hpp | 4 ++-- src/repl.cpp | 31 +++++++++++++++++++++++++++++-- src/simulation.hpp | 38 ++++++++++++++++++++++++++++++++++++++ web/index.html | 8 +++----- 5 files changed, 77 insertions(+), 14 deletions(-) diff --git a/src/battle.cpp b/src/battle.cpp index bd48ccd..7dd2a35 100644 --- a/src/battle.cpp +++ b/src/battle.cpp @@ -47,7 +47,7 @@ void Battle::single_attack_by(int player, int from) { Board& enemy = board[1-player]; int target = attacker.type == MinionType::ZappSlywick ? enemy.lowest_attack_target() : enemy.random_attack_target(); - if (verbose) { + if (verbose && log) { *log << "attack by " << player << "." << from << ", " << attacker << (cleave ? "[C]" : "") << " to " << target << endl; } // Make a snapshot of the defending minion, so we know attack values @@ -96,7 +96,7 @@ void Battle::on_after_friendly_attack(Minion const& attacker, int player) { bool Battle::damage(int player, int pos, int amount, bool poison) { if (amount <= 0) return false; Minion& m = board[player].minions[pos]; - if (verbose >= 2) { + if (verbose >= 2 && log) { *log << "damage of " << amount << (poison ? "[P]" : "") << " to " << player << "." << pos << ", " << m << endl; } if (m.divine_shield) { @@ -115,7 +115,7 @@ bool Battle::damage(int player, int pos, int amount, bool poison) { } bool Battle::damage(Minion const& attacker, int player, int pos) { - if (verbose >= 4) { + if (verbose >= 4 && log) { *log << "damage by " << attacker << " to " << player << "." << pos << endl; } return damage(player, pos, attacker.attack, attacker.poison); @@ -205,7 +205,7 @@ void Battle::destroy_minion(int player, int pos) { */ void Battle::on_death(Minion const& dead_minion, int player, int pos) { - if (verbose) { + if (verbose && log) { *log << "death: " << dead_minion << " at " << player << "." << pos << endl; } do_deathrattle(dead_minion, player, pos); @@ -275,7 +275,7 @@ void Battle::on_summoned(Minion& summoned, int player) { void Battle::do_hero_powers() { for (int player=0; player<2; ++player) { HeroPower& hp = board[player].hero_power; - if (verbose >= 2 && hp != HeroPower::None) { + if (verbose >= 2 && log && hp != HeroPower::None) { *log << "Hero power " << hp << " for " << player << endl; } do_hero_power(hp, player); diff --git a/src/battle.hpp b/src/battle.hpp index 836c342..51af234 100644 --- a/src/battle.hpp +++ b/src/battle.hpp @@ -21,8 +21,8 @@ struct Battle { int verbose = 0; ostream* log; - Battle(ostream* log = &std::cout) : log(log) {} - Battle(Board const& b0, Board const& b1, ostream* log = &std::cout) : board{b0,b1}, log(log) { + Battle(ostream* log = nullptr) : log(log) {} + Battle(Board const& b0, Board const& b1, ostream* log = nullptr) : board{b0,b1}, log(log) { recompute_auras(); } diff --git a/src/repl.cpp b/src/repl.cpp index f53a220..d61f992 100644 --- a/src/repl.cpp +++ b/src/repl.cpp @@ -59,6 +59,7 @@ struct REPL { void do_help(); void do_quit(); void do_board(int player); + void do_swap(); void do_show(); void do_reset(); void do_step(); @@ -67,6 +68,7 @@ struct REPL { void do_list_minions(); void do_list_hero_powers(); void do_run(int runs = -1); + void do_optimize_order(int runs = -1); void do_add_minion(Minion const&); void do_end_input(); }; @@ -257,6 +259,8 @@ void REPL::parse_line(std::string const& line) { do_board(0); } else if (cmd == "vs") { do_board(1); + } else if (cmd == "swap") { + do_swap(); } else if (cmd == "info" || cmd == "msg" || cmd == "message" || cmd == "print" || cmd == "echo") { std::string msg; getline(in,msg); @@ -277,6 +281,8 @@ void REPL::parse_line(std::string const& line) { int n = -1; in >> n; do_run(n); + } else if (cmd == "optimize") { + do_optimize_order(); } else if (cmd == "runs") { int n = DEFAULT_NUM_RUNS; if (in >> n && n > 0) { @@ -411,7 +417,7 @@ void REPL::do_help() { out << "-- Running simulations" << endl; out << "actual = tell about actual outcome (used in simulation display)" << endl; out << "run [] = run n simulations (default: 100)" << endl; - //out << "optimize = optimize the minion order to maximize win%" << endl; + out << "optimize = optimize the minion order to maximize winrate" << endl; out << endl; out << "-- Stepping through a single battle" << endl; out << "show = show the board state" << endl; @@ -464,6 +470,10 @@ void REPL::do_board(int player) { used = false; } +void REPL::do_swap() { + std::swap(players[0], players[1]); +} + void REPL::do_add_minion(Minion const& m) { if (players[current_player].full()) { error() << "Player already has a full board" << endl; @@ -511,7 +521,7 @@ void print_damage_taken(ostream& out, int enemy_level, int health, vector c void REPL::do_run(int n) { if (n <= 0) n = default_num_runs; - vector results = simulate(Battle(players[0], players[1], &out), n); + vector results = simulate(Battle(players[0], players[1]), n); out << "--------------------------------" << endl; print_stats(out, results); for (int o : actual_outcomes) { @@ -523,6 +533,23 @@ void REPL::do_run(int n) { used = true; } +void REPL::do_optimize_order(int n) { + if (n <= 0) n = default_num_runs; + OptimizeMinionOrder opt(players[0], players[1], n); + if (opt.current_score >= opt.best_score) { + out << "Your winrate cannot be improved by reordering your minions" << endl; + } else { + out.precision(1); + out.setf(std::ios::fixed, std:: ios::floatfield); + out << "Your winrate can be improved from " << 100*opt.current_score << "% to " << 100*opt.best_score << "% by reordering your minions:" << endl; + Board new_board = players[0]; + permute_minions(new_board, players[0].minions, opt.best_order.data(), opt.n); + out << new_board; + // TODO: significance test + } + used = true; +} + void REPL::do_show() { if (!battle_started) { step_battle = Battle(players[0], players[1], &out); diff --git a/src/simulation.hpp b/src/simulation.hpp index 00b834f..08aa9cf 100644 --- a/src/simulation.hpp +++ b/src/simulation.hpp @@ -1,5 +1,6 @@ #include "battle.hpp" #include +#include #include using std::vector; @@ -109,4 +110,41 @@ int percentile(int i, vector const& results) { // Optimization // ----------------------------------------------------------------------------- +void permute_minions(Board& board, Minion const original[], int const perm[], int n) { + // note: original != board.minions + for (int i=0; i best_order; + double current_score; + double best_score; + int n; + + OptimizeMinionOrder(Board const& board, Board const& enemy, int runs = DEFAULT_NUM_RUNS) { + n = board.size(); + // current situation + std::array order; + for (int i=0; i best_score) { + best_score = score; + best_order = order; + } + } while (std::next_permutation(order.begin(), order.begin() + n)); + } +}; diff --git a/web/index.html b/web/index.html index 90cce32..d68d17e 100644 --- a/web/index.html +++ b/web/index.html @@ -10,9 +10,6 @@ var out = document.getElementById("output"); out.textContent = Module.run(input.value + "\n" + cmd); } - function do_trace() { - do_run("trace"); - } function do_help(cmd="help") { var out = document.getElementById("output"); out.textContent = Module.run(cmd); @@ -58,8 +55,9 @@

Hearthstone Battlegrounds Battle Simulator

- - + + +