Skip to content

Commit

Permalink
Added minion order optimization
Browse files Browse the repository at this point in the history
  • Loading branch information
twanvl committed Nov 19, 2019
1 parent d51f3ee commit 395d64c
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 14 deletions.
10 changes: 5 additions & 5 deletions src/battle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions src/battle.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down
31 changes: 29 additions & 2 deletions src/repl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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();
};
Expand Down Expand Up @@ -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);
Expand All @@ -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) {
Expand Down Expand Up @@ -411,7 +417,7 @@ void REPL::do_help() {
out << "-- Running simulations" << endl;
out << "actual <i> = tell about actual outcome (used in simulation display)" << endl;
out << "run [<n>] = 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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -511,7 +521,7 @@ void print_damage_taken(ostream& out, int enemy_level, int health, vector<int> c

void REPL::do_run(int n) {
if (n <= 0) n = default_num_runs;
vector<int> results = simulate(Battle(players[0], players[1], &out), n);
vector<int> results = simulate(Battle(players[0], players[1]), n);
out << "--------------------------------" << endl;
print_stats(out, results);
for (int o : actual_outcomes) {
Expand All @@ -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);
Expand Down
38 changes: 38 additions & 0 deletions src/simulation.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "battle.hpp"
#include <vector>
#include <array>
#include <algorithm>
using std::vector;

Expand Down Expand Up @@ -109,4 +110,41 @@ int percentile(int i, vector<int> 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<n; ++i) {
board.minions[i] = original[perm[i]];
}
}

struct OptimizeMinionOrder {
std::array<int,BOARDSIZE> 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<int,BOARDSIZE> order;
for (int i=0; i<n; ++i) order[i] = i;
// optimize
bool current = true;
do {
// check
Battle battle(board, enemy);
permute_minions(battle.board[0], board.minions, order.data(), n);
double score = simulate_optimization_score(battle, runs);
if (current) { // first permutation = current situation
current = false;
current_score = score;
best_score = score;
best_order = order;
} else if (score > best_score) {
best_score = score;
best_order = order;
}
} while (std::next_permutation(order.begin(), order.begin() + n));
}
};

8 changes: 3 additions & 5 deletions web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -58,8 +55,9 @@ <h1>Hearthstone Battlegrounds Battle Simulator</h1>
</textarea>
</div>
<div>
<button class="btn btn-primary" onclick="do_run();">Simulate</button>
<button class="btn btn-primary" onclick="do_trace();">Trace</button>
<button class="btn btn-primary" onclick="do_run();" title="Run the battle many times">Simulate</button>
<button class="btn btn-primary" onclick="do_run('optimize');" title="Reorder minions to optimize winrate">Optimize order</button>
<button class="btn btn-primary" onclick="do_run('trace');">Trace</button>
<button class="btn btn-info" onclick="do_help('minions');">List of minions</button>
<button class="btn btn-info" onclick="do_help();">Help</button>
</div>
Expand Down

0 comments on commit 395d64c

Please sign in to comment.