pachi_py/goutil.cpp (149 lines of code) (raw):

#include "goutil.hpp" #include "exceptions.hpp" #include <sstream> #include <stdexcept> #include <iostream> #include <algorithm> extern "C" { #include "stone.h" #include "engine.h" #include "uct/uct.h" #include "uct/internal.h" #include "montecarlo/montecarlo.h" #include "random/random.h" #include "timeinfo.h" #include "fbook.h" #include "playout.h" #include "ownermap.h" #include "mq.h" } void GetLegalMoves(PachiBoardPtr b, stone color, bool filter_suicides, std::vector<coord_t>* out) { out->clear(); out->push_back(pass); board* pb = b->pachiboard(); foreach_free_point(pb) { assert(board_at(pb, c) == S_NONE); bool valid = filter_suicides ? board_is_valid_play_no_suicide(pb, color, c) : board_is_valid_play(pb, color, c); if (valid) { out->push_back(c); } } foreach_free_point_end; } bool IsTerminal(PachiBoardPtr b) { board* pb = b->pachiboard(); // last move is a resign // if (b->num_moves() >= 1 && is_resign(b->get_last_move(0).coord)) { if (pb->moves > 0 && is_resign(pb->last_move.coord)) { return true; } // last two moves are passes // if (b->num_moves() >= 2 && is_pass(b->get_last_move(0).coord) && is_pass(b->get_last_move(1).coord)) { if (pb->moves > 0 && is_pass(pb->last_move.coord) && pb->last_move.color != S_NONE && is_pass(pb->last_move2.coord) && pb->last_move2.color != S_NONE) { return true; } return false; } // RAII for board_ownermap struct OwnerMap { board_ownermap ownermap; OwnerMap(board *b) { ownermap.map = (sig_atomic_t (*)[S_MAX]) malloc(board_size2(b) * sizeof(ownermap.map[0])); memset(ownermap.map, 0, board_size2(b) * sizeof(ownermap.map[0])); board_ownermap_fill(&ownermap, b); } ~OwnerMap() { free(ownermap.map); } }; typedef ptr<OwnerMap> OwnerMapPtr; /* How big proportion of ownermap counts must be of one color to consider * the point sure. */ static move_queue GetDeadGroups(board *b, OwnerMapPtr ownermap) { /* Make sure enough playouts are simulated to get a reasonable dead group list. */ // while (u->ownermap.playouts < GJ_MINGAMES) // uct_playout(u, b, color, u->t); if (!ownermap) { ownermap.reset(new OwnerMap(b)); } move_queue mq = { .moves = 0 }; gj_state gs_array[board_size2(b)]; struct group_judgement gj = { .thres = GJ_THRES, .gs = gs_array }; board_ownermap_judge_groups(b, &ownermap->ownermap, &gj); groups_of_status(b, &gj, GS_DEAD, &mq); return mq; } float OfficialScore(PachiBoardPtr b) { move_queue mq = GetDeadGroups(b->pachiboard(), OwnerMapPtr()); return board_official_score(b->pachiboard(), &mq); } static std::string trim(const std::string &s) { auto notspace = [](char c){ return !std::isspace(c); }; auto a = std::find_if(s.begin(), s.end(), notspace); auto b = std::find_if(s.rbegin(), std::string::const_reverse_iterator(a), notspace).base(); return std::string(a, b); } std::string ToString(PachiBoardPtr b) { char *s = board_print(b->pachiboard(), NULL); std::string out(s); free(s); // s is constructed with strdup, so we need to free it here return trim(out); } static inline bool moves_equal(const move& m1, const move& m2) { return m1.coord == m2.coord && m1.color == m2.color; } void PlayInPlace(PachiBoardPtr b, const move& m) { board* pb = b->pachiboard(); if (board_play(pb, const_cast<move*>(&m)) < 0) { char* tmp = coord2str(m.coord, pb); std::stringstream ss; ss << "Illegal move by " << (m.color == S_BLACK ? "black" : "white") << " at " << tmp << ". Current board:\n"; ss << ToString(b); free(tmp); throw IllegalMove(ss.str()); } } PachiBoardPtr Play(PachiBoardPtr b, const move& m) { ptr<PachiBoard> new_state = b->clone(); PlayInPlace(new_state, m); return new_state; } void PlayRandomInPlace(PachiBoardPtr b, stone color, coord_t *coord) { board_play_random(b->pachiboard(), color, coord, NULL, NULL); } PachiBoardPtr PlayRandom(PachiBoardPtr b, stone color, coord_t *coord) { ptr<PachiBoard> new_state = b->clone(); PlayRandomInPlace(new_state, color, coord); return new_state; } PachiEngine::PachiEngine(PachiBoardPtr bptr, const std::string& engine_type, std::string arg) : m_engine_type(engine_type), m_board(bptr) { auto engine_init_fn = engine_random_init; if (engine_type == "random") { engine_init_fn = engine_random_init; } else if (engine_type == "montecarlo") { engine_init_fn = engine_montecarlo_init; } else if (engine_type == "uct") { engine_init_fn = engine_uct_init; // seems to leak memory when pondering is on if (arg != "") { arg += ","; } arg += "pondering=0"; } else { throw PachiEngineError("engine not supported: " + engine_type); } char* tmp_arg = arg == "" ? nullptr : strdup(arg.c_str()); m_engine = engine_init_fn(tmp_arg, m_board->pachiboard()); if (tmp_arg != nullptr) { free(tmp_arg); } } coord_t PachiEngine::genmove(stone curr_color, const std::string& timestr) { // Set pachi timing options (max sims, etc.) time_info ti; ti.period = time_info::TT_NULL; if (timestr != "" && !time_parse(&ti, const_cast<char*>(timestr.c_str()))) { std::stringstream ss; ss << "Invalid timekeeping specification for Pachi: " << timestr << '\n'; ss << "Format:\n"; ss << "* =NUM - fixed number of simulations per move\n"; ss << "* NUM - number of seconds to spend per move (can be floating_t)\n"; ss << "* _NUM - number of seconds to spend per game\n"; throw PachiEngineError(ss.str()); } // Play coord_t* c = m_engine->genmove(m_engine, m_board->pachiboard(), &ti, curr_color, false); coord_t out = *c; coord_done(c); return out; } void PachiEngine::notify(coord_t move_coord, stone move_color) { if (!m_engine->notify_play) { return; } move m = { move_coord, move_color }; m_engine->notify_play(m_engine, m_board->pachiboard(), &m, NULL); } PachiEngine::~PachiEngine() { if (m_engine->stop) { m_engine->stop(m_engine); } if (m_engine->done) { m_engine->done(m_engine); } if (m_engine->data) { free(m_engine->data); } free(m_engine); }