diff --git a/include/solver/dfpn.h b/include/solver/dfpn.h index 4a43d93..d80c960 100644 --- a/include/solver/dfpn.h +++ b/include/solver/dfpn.h @@ -26,9 +26,9 @@ class DFPN { short getResult() const; std::string formatResult() const; static void signalHandler([[maybe_unused]] int signum); + helper::Move bestMove() const; inline helper::PROOF_VALUE result() const { return result_; } - inline helper::Move bestMove() const { return best_move_; } private: Node root_; diff --git a/include/third_party/tqdm.hpp b/include/third_party/tqdm.hpp new file mode 100644 index 0000000..94ed0af --- /dev/null +++ b/include/third_party/tqdm.hpp @@ -0,0 +1,517 @@ +#pragma once + +/* + *Copyright (c) 2018-2019 + * + *Permission is hereby granted, free of charge, to any person + *obtaining a copy of this software and associated documentation + *files (the "Software"), to deal in the Software without + *restriction, including without limitation the rights to use, + *copy, modify, merge, publish, distribute, sublicense, and/or sell + *copies of the Software, and to permit persons to whom the + *Software is furnished to do so, subject to the following + *conditions: + * + *The above copyright notice and this permission notice shall be + *included in all copies or substantial portions of the Software. + * + *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + *EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + *OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + *NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + *HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + *WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + *FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + *OTHER DEALINGS IN THE SOFTWARE. + */ + +// https : //gitlab.com/miguelraggi/tqdm-cpp + +#include +#include +#include +#include +#include +#include +#include + +// -------------------- chrono stuff -------------------- + +namespace tq { +using index = std::ptrdiff_t; // maybe std::size_t, but I hate unsigned types. +using time_point_t = std::chrono::time_point; + +inline double elapsed_seconds(time_point_t from, time_point_t to) { + using seconds = std::chrono::duration; + return std::chrono::duration_cast(to - from).count(); +} + +class Chronometer { + public: + Chronometer() : start_(std::chrono::steady_clock::now()) {} + + double reset() { + auto previous = start_; + start_ = std::chrono::steady_clock::now(); + + return elapsed_seconds(previous, start_); + } + + double peek() const { + auto now = std::chrono::steady_clock::now(); + + return elapsed_seconds(start_, now); + } + + time_point_t start_; +}; + +// -------------------- progress_bar -------------------- +void clamp(double& x, double a, double b) { + if (x < a) x = a; + if (x > b) x = b; +} + +class progress_bar { + public: + void restart() { + chronometer_.reset(); + refresh_.reset(); + } + + void update(double progress) { + if (time_since_refresh() > min_time_per_update_ || progress == 0.0 || progress == 1.0) { + reset_refresh_timer(); + display(progress); + } + suffix_.str(""); + } + + void set_ostream(std::ostream& os) { os_ = &os; } + void set_prefix(std::string s) { prefix_ = std::move(s); } + void set_bar_size(int size) { bar_size_ = size; } + void set_min_update_time(double time) { min_time_per_update_ = time; } + + template + progress_bar& operator<<(const T& t) { + suffix_ << t; + return *this; + } + + double elapsed_time() const { return chronometer_.peek(); } + + private: + void display(double progress) { + clamp(progress, 0.0, 1.0); + + auto flags = os_->flags(); + + double t = chronometer_.peek(); + double eta = t / progress - t; + + std::stringstream bar; + + bar << '\r' << prefix_ << '{' << std::fixed << std::setprecision(1) + << std::setw(5) << 100 * progress << "%} "; + + print_bar(bar, progress); + + bar << " (" << t << "s < " << eta << "s) "; + + std::string sbar = bar.str(); + std::string suffix = suffix_.str(); + + index out_size = sbar.size() + suffix.size(); + term_cols_ = std::max(term_cols_, out_size); + index num_blank = term_cols_ - out_size; + + (*os_) << sbar << suffix << std::string(num_blank, ' ') << std::flush; + + os_->flags(flags); + } + + void print_bar(std::stringstream& ss, double filled) const { + auto num_filled = static_cast(std::round(filled * bar_size_)); + ss << '[' << std::string(num_filled, '#') + << std::string(bar_size_ - num_filled, ' ') << ']'; + } + + double time_since_refresh() const { return refresh_.peek(); } + void reset_refresh_timer() { refresh_.reset(); } + + Chronometer chronometer_{}; + Chronometer refresh_{}; + double min_time_per_update_{0.15}; // found experimentally + + std::ostream* os_{&std::cerr}; + + index bar_size_{40}; + index term_cols_{1}; + + std::string prefix_{}; + std::stringstream suffix_{}; +}; + +// -------------------- iter_wrapper -------------------- + +template +class iter_wrapper { + public: + using iterator_category = typename ForwardIter::iterator_category; + using value_type = typename ForwardIter::value_type; + using difference_type = typename ForwardIter::difference_type; + using pointer = typename ForwardIter::pointer; + using reference = typename ForwardIter::reference; + + iter_wrapper(ForwardIter it, Parent* parent) + : current_(it), parent_(parent) {} + + auto operator*() { return *current_; } + + void operator++() { ++current_; } + + template + bool operator!=(const Other& other) const { + parent_->update(); // here and not in ++ because I need to run update before first advancement! + return current_ != other; + } + + bool operator!=(const iter_wrapper& other) const { + parent_->update(); // here and not in ++ because I need to run update before first advancement! + return current_ != other.current_; + } + + const ForwardIter& get() const { return current_; } + + private: + friend Parent; + ForwardIter current_; + Parent* parent_; +}; + +// -------------------- tqdm_for_lvalues -------------------- + +template +class tqdm_for_lvalues { + public: + using this_t = tqdm_for_lvalues; + using iterator = iter_wrapper; + using value_type = typename ForwardIter::value_type; + using size_type = index; + using difference_type = index; + + tqdm_for_lvalues(ForwardIter begin, EndIter end) + : first_(begin, this), last_(end), num_iters_(std::distance(begin, end)) {} + + tqdm_for_lvalues(ForwardIter begin, EndIter end, index total) + : first_(begin, this), last_(end), num_iters_(total) {} + + template + explicit tqdm_for_lvalues(Container& C) + : first_(C.begin(), this), last_(C.end()), num_iters_(C.size()) {} + + template + explicit tqdm_for_lvalues(const Container& C) + : first_(C.begin(), this), last_(C.end()), num_iters_(C.size()) {} + + tqdm_for_lvalues(const tqdm_for_lvalues&) = delete; + tqdm_for_lvalues(tqdm_for_lvalues&&) = delete; + tqdm_for_lvalues& operator=(tqdm_for_lvalues&&) = delete; + tqdm_for_lvalues& operator=(const tqdm_for_lvalues&) = delete; + + template + tqdm_for_lvalues(Container&&) = delete; // prevent misuse! + + iterator begin() { + bar_.restart(); + iters_done_ = 0; + return first_; + } + + EndIter end() const { return last_; } + + void update() { + bar_.update(calc_progress()); + ++iters_done_; + } + + void set_ostream(std::ostream& os) { bar_.set_ostream(os); } + void set_prefix(std::string s) { bar_.set_prefix(std::move(s)); } + void set_bar_size(int size) { bar_.set_bar_size(size); } + void set_min_update_time(double time) { bar_.set_min_update_time(time); } + + template + tqdm_for_lvalues& operator<<(const T& t) { + bar_ << t; + return *this; + } + + void manually_set_progress(double to) { + if (to > 1.) to = 1.; + if (to < 0.) to = 0.; + iters_done_ = std::round(to * num_iters_); + } + + private: + double calc_progress() const { + return iters_done_ / (num_iters_ + 0.0000000000001); + } + + iterator first_; + EndIter last_; + index num_iters_; + index iters_done_{0}; + progress_bar bar_; +}; + +template +tqdm_for_lvalues(Container&) -> tqdm_for_lvalues; + +template +tqdm_for_lvalues(const Container&) + -> tqdm_for_lvalues; + +// -------------------- tqdm_for_rvalues -------------------- + +template +class tqdm_for_rvalues { + public: + using iterator = typename Container::iterator; + using const_iterator = typename Container::const_iterator; + using value_type = typename Container::value_type; + + explicit tqdm_for_rvalues(Container&& C) + : C_(std::forward(C)), tqdm_(C_) {} + + auto begin() { return tqdm_.begin(); } + + auto end() { return tqdm_.end(); } + + void update() { return tqdm_.update(); } + + void set_ostream(std::ostream& os) { tqdm_.set_ostream(os); } + void set_prefix(std::string s) { tqdm_.set_prefix(std::move(s)); } + void set_bar_size(int size) { tqdm_.set_bar_size(size); } + void set_min_update_time(double time) { tqdm_.set_min_update_time(time); } + + template + auto& operator<<(const T& t) { + return tqdm_ << t; + } + + void advance(index amount) { tqdm_.advance(amount); } + + void manually_set_progress(double to) { + tqdm_.manually_set_progress(to); + } + + private: + Container C_; + tqdm_for_lvalues tqdm_; +}; + +template +tqdm_for_rvalues(Container&&) -> tqdm_for_rvalues; + +// -------------------- tqdm -------------------- +template +auto tqdm(const ForwardIter& first, const ForwardIter& last) { + return tqdm_for_lvalues(first, last); +} + +template +auto tqdm(const ForwardIter& first, const ForwardIter& last, index total) { + return tqdm_for_lvalues(first, last, total); +} + +template +auto tqdm(const Container& C) { + return tqdm_for_lvalues(C); +} + +template +auto tqdm(Container& C) { + return tqdm_for_lvalues(C); +} + +template +auto tqdm(Container&& C) { + return tqdm_for_rvalues(std::forward(C)); +} + +// -------------------- int_iterator -------------------- + +template +class int_iterator { + public: + using iterator_category = std::random_access_iterator_tag; + using value_type = IntType; + using difference_type = IntType; + using pointer = IntType*; + using reference = IntType&; + + explicit int_iterator(IntType val) : value_(val) {} + + IntType& operator*() { return value_; } + + int_iterator& operator++() { + ++value_; + return *this; + } + int_iterator& operator--() { + --value_; + return *this; + } + + int_iterator& operator+=(difference_type d) { + value_ += d; + return *this; + } + + difference_type operator-(const int_iterator& other) const { + return value_ - other.value_; + } + + bool operator!=(const int_iterator& other) const { + return value_ != other.value_; + } + + private: + IntType value_; +}; + +// -------------------- range -------------------- +template +class range { + public: + using iterator = int_iterator; + using const_iterator = iterator; + using value_type = IntType; + + range(IntType first, IntType last) : first_(first), last_(last) {} + explicit range(IntType last) : first_(0), last_(last) {} + + iterator begin() const { return first_; } + iterator end() const { return last_; } + index size() const { return last_ - first_; } + + private: + iterator first_; + iterator last_; +}; + +template +auto trange(IntType first, IntType last) { + return tqdm(range(first, last)); +} + +template +auto trange(IntType last) { + return tqdm(range(last)); +} + +// -------------------- timing_iterator -------------------- + +struct timing_iterator_end_sentinel { + public: + explicit timing_iterator_end_sentinel(double num_seconds) : num_seconds_(num_seconds) {} + + double num_seconds_; +}; + +class timing_iterator { + public: + using iterator_category = std::forward_iterator_tag; + using value_type = double; + using difference_type = double; + using pointer = double*; + using reference = double&; + + double& operator*() const { + workaround_ = chrono_.peek(); + return workaround_; + } + + timing_iterator& operator++() { + return *this; + } + + bool operator!=(const timing_iterator_end_sentinel& other) const { + return chrono_.peek() < other.num_seconds_; + } + + private: + tq::Chronometer chrono_; + mutable double workaround_; +}; + +// -------------------- timer ------------------- +struct timer { + public: + using iterator = timing_iterator; + using end_iterator = timing_iterator_end_sentinel; + using const_iterator = iterator; + using value_type = double; + + explicit timer(double num_seconds) : num_seconds(num_seconds) { + } + + iterator begin() const { return iterator(); } + end_iterator end() const { return end_iterator(num_seconds); } + + double num_seconds; +}; + +class tqdm_timer { + public: + using iterator = iter_wrapper; + using end_iterator = timer::end_iterator; + using value_type = typename timing_iterator::value_type; + using size_type = index; + using difference_type = index; + + explicit tqdm_timer(double num_seconds) + : num_seconds_(num_seconds) {} + + tqdm_timer(const tqdm_timer&) = delete; + tqdm_timer(tqdm_timer&&) = delete; + tqdm_timer& operator=(tqdm_timer&&) = delete; + tqdm_timer& operator=(const tqdm_timer&) = delete; + + template + tqdm_timer(Container&&) = delete; // prevent misuse! + + iterator begin() { + bar_.restart(); + return iterator(timing_iterator(), this); + } + + end_iterator end() const { return end_iterator(num_seconds_); } + + void update() { + double t = bar_.elapsed_time(); + + bar_.update(t / num_seconds_); + } + + void set_ostream(std::ostream& os) { bar_.set_ostream(os); } + void set_prefix(std::string s) { bar_.set_prefix(std::move(s)); } + void set_bar_size(int size) { bar_.set_bar_size(size); } + void set_min_update_time(double time) { bar_.set_min_update_time(time); } + + template + tqdm_timer& operator<<(const T& t) { + bar_ << t; + return *this; + } + + private: + double num_seconds_; + progress_bar bar_; +}; + +auto tqdm(timer t) { + return tqdm_timer(t.num_seconds); +} + +} // namespace tq diff --git a/src/solver/dfpn.cpp b/src/solver/dfpn.cpp index 0ab0980..8e39833 100644 --- a/src/solver/dfpn.cpp +++ b/src/solver/dfpn.cpp @@ -199,5 +199,9 @@ short DFPN::getResult() const { } } +helper::Move DFPN::bestMove() const { + return best_move_; +} + } // namespace dfpn } // namespace solver \ No newline at end of file diff --git a/src/solver/minimax.cpp b/src/solver/minimax.cpp index 8981945..8c35391 100644 --- a/src/solver/minimax.cpp +++ b/src/solver/minimax.cpp @@ -2,7 +2,7 @@ * @author Yanqing Wu * @email meet.yanqing.wu@gmail.com * @create date 2023-03-15 13:57:51 - * @modify date 2023-04-05 23:32:49 + * @modify date 2023-04-09 14:32:07 * * TODO: is there a better way to get the best_move? * We are now returning the immediate win move if found one; otherwise return random. @@ -12,6 +12,7 @@ #include "solver/minimax.h" // std #include +#include namespace solver { namespace minimax { @@ -295,7 +296,16 @@ helper::Move Minimax::bestMove() const { } // if has time limit (the agent is probably forced to stop) return a random move - auto move = possible_moves_[rand() % possible_moves_.size() + 1]; + // auto move = possible_moves_[rand() % possible_moves_.size() + 1]; // probably causing Henisenbug + + // Seed with a real random value, if available + std::random_device r; + + // Choose a random number + std::default_random_engine e1(r()); + std::uniform_int_distribution uniform_dist(1, possible_moves_.size()); + int idx = uniform_dist(e1); + auto move = possible_moves_[idx]; return move; } diff --git a/src/solver/negamax.cpp b/src/solver/negamax.cpp index a3029b3..6ca2fa7 100644 --- a/src/solver/negamax.cpp +++ b/src/solver/negamax.cpp @@ -2,10 +2,12 @@ * @author Yanqing Wu * @email meet.yanqing.wu@gmail.com * @create date 2023-03-18 11:18:44 - * @modify date 2023-04-05 23:32:38 + * @modify date 2023-04-09 14:32:05 */ #include "solver/negamax.h" +// std +#include namespace solver { namespace negamax { @@ -257,7 +259,17 @@ helper::Move Negamax::bestMove() const { } // if has time limit (the agent is probably forced to stop) return a random move - auto move = possible_moves_[rand() % possible_moves_.size() + 1]; + // auto move = possible_moves_[rand() % possible_moves_.size() + 1]; + + // Seed with a real random value, if available + std::random_device r; + + // Choose a random number + std::default_random_engine e1(r()); + std::uniform_int_distribution uniform_dist(1, possible_moves_.size()); + int idx = uniform_dist(e1); + auto move = possible_moves_[idx]; + return move; return move; } diff --git a/test/other_tests/agent_fight.cpp b/test/other_tests/agent_fight.cpp index 0a7493c..5f7444d 100644 --- a/test/other_tests/agent_fight.cpp +++ b/test/other_tests/agent_fight.cpp @@ -21,6 +21,7 @@ #include "solver/minimax.h" #include "solver/negamax.h" #include "solver/pns.h" +#include "third_party/tqdm.hpp" using std::cout; using std::endl; @@ -145,35 +146,66 @@ bool playSingleMatch(std::string p1, std::string p2, int board_size) { bool is_win = false; ///////////////////////////////////////////////// - cout << curr_game << endl; + // cout << curr_game << endl; while (!curr_game.isTerminal()) { if (turn % 2 == 1) { next_move = getMove(p1, curr_game); if (next_move.value == 0) { - cout << "Turn " << turn << ": B (" << p1 << ") resigns." << endl; + // cout << "Turn " << turn << ": B (" << p1 << ") resigns." << endl; is_win = false; break; } - cout << "Turn " << turn << ": B (" << p1 << ") plays " << next_move.toString() << endl; + // cout << "Turn " << turn << ": B (" << p1 << ") plays " << next_move.toString() << endl; } else { next_move = getMove(p2, curr_game); if (next_move.value == 0) { - cout << "Turn " << turn << ": W (" << p2 << ") resigns." << endl; + // cout << "Turn " << turn << ": W (" << p2 << ") resigns." << endl; is_win = true; break; } - cout << "Turn " << turn << ": W (" << p2 << ") plays " << next_move.toString() << endl; + // cout << "Turn " << turn << ": W (" << p2 << ") plays " << next_move.toString() << endl; } curr_game.unsafePlay(next_move.pos, next_move.value); - cout << curr_game << endl; + // cout << curr_game << endl; turn += 1; } + cout << curr_game.toString() << endl; return is_win; } } // namespace fgtest +void runExperiment() { + std::vector agents = {"pns", "dfpn", "mini-ab-tt", "nega-ab-tt", "mcts"}; + + uint16_t num_runs = 100; + int board_size = 3; + + (void)!freopen("agent_fight.txt", "w", stdout); + + // p1 plays first, p2 plays second + for (auto p1 : agents) { + for (auto p2 : agents) { + if (p1 == p2) { + // no self-play + continue; + } + cout << p1 << " vs " << p2 << endl; + uint16_t wins = 0; + for ([[maybe_unused]] auto x : tq::trange(num_runs)) { + bool res = fgtest::playSingleMatch(p1, p2, board_size); + if (res) { + wins += 1; + } + } + cout << p1 << ": " << wins << "/" << num_runs << endl; + } + } + + return; +} + /////////////////////////////////////// // Main /////////////////////////////////////// @@ -208,6 +240,7 @@ void printHelp() { } int main(int argc, char* argv[]) { + /* if (argc != 4) { printHelp(); throw std::invalid_argument("Invalid inputs"); @@ -230,6 +263,9 @@ int main(int argc, char* argv[]) { } fgtest::playSingleMatch(player1, player2, board_size); + */ + + runExperiment(); return 0; }