1 //-----------------------------------------------------------------------------
2 /** @file pentobi_gtp/GtpEngine.cpp
3     @author Markus Enzenberger
4     @copyright GNU General Public License version 3 or later */
5 //-----------------------------------------------------------------------------
6 
7 #include "GtpEngine.h"
8 
9 #include <fstream>
10 #include "libboardgame_base/Writer.h"
11 #include "libpentobi_mcts/Util.h"
12 
13 using libboardgame_base::Writer;
14 using libboardgame_gtp::Failure;
15 using libpentobi_base::Board;
16 using libpentobi_base::get_color_id;
17 using libpentobi_mcts::Float;
18 
19 //-----------------------------------------------------------------------------
20 
GtpEngine(Variant variant,unsigned level,bool use_book,const string & books_dir,unsigned nu_threads)21 GtpEngine::GtpEngine(
22         Variant variant, unsigned level, bool use_book,
23         const string& books_dir, unsigned nu_threads)
24     : libpentobi_gtp::GtpEngine(variant)
25 {
26     create_player(variant, level, books_dir, nu_threads);
27     get_mcts_player().set_use_book(use_book);
28     add("get_value", &GtpEngine::cmd_get_value);
29     add("name", &GtpEngine::cmd_name);
30     add("param", &GtpEngine::cmd_param);
31     add("move_values", &GtpEngine::cmd_move_values);
32     add("save_tree", &GtpEngine::cmd_save_tree);
33     add("selfplay", &GtpEngine::cmd_selfplay);
34     add("version", &GtpEngine::cmd_version);
35 }
36 
37 GtpEngine::~GtpEngine() = default; // Non-inline to avoid GCC -Winline warning
38 
cmd_get_value(Response & response)39 void GtpEngine::cmd_get_value(Response& response)
40 {
41     response << get_search().get_tree().get_root().get_value();
42 }
43 
cmd_move_values(Response & response)44 void GtpEngine::cmd_move_values(Response& response)
45 {
46     auto children = get_search().get_tree().get_root_children();
47     if (children.empty())
48         return;
49     auto& bd = get_board();
50     vector<const Search::Node*> sorted_children;
51     sorted_children.reserve(children.size());
52     for (auto& i : children)
53         sorted_children.push_back(&i);
54     sort(sorted_children.begin(), sorted_children.end(), libpentobi_mcts::compare_node);
55     response << fixed;
56     for (auto node : sorted_children)
57         response << setprecision(0) << node->get_visit_count() << ' '
58                  << setprecision(1) << node->get_value_count() << ' '
59                  << setprecision(3) << node->get_value() << ' '
60                  << bd.to_string(node->get_move(), true) << '\n';
61 }
62 
cmd_name(Response & response)63 void GtpEngine::cmd_name(Response& response)
64 {
65     response.set("Pentobi");
66 }
67 
cmd_save_tree(Arguments args)68 void GtpEngine::cmd_save_tree(Arguments args)
69 {
70     auto& search = get_search();
71     if (! search.get_last_history().is_valid())
72         throw Failure("no search tree");
73     ofstream out(args.get<string>());
74     libpentobi_mcts::dump_tree(out, search);
75 }
76 
77 /** Let the engine play a number of games against itself.
78     This is more efficient than using twogtp if selfplay games are needed
79     because it has lower memory requirements (only one engine needed), process
80     switches between the engines are avoided and parts of the search tree can
81     be reused between moves of different players. */
cmd_selfplay(Arguments args)82 void GtpEngine::cmd_selfplay(Arguments args)
83 {
84     args.check_size(2);
85     auto nu_games = args.get<int>(0);
86     ofstream out(args.get<string>(1));
87     auto variant = get_board().get_variant();
88     auto variant_str = to_string(variant);
89     Board bd(variant);
90     auto& player = get_mcts_player();
91     ostringstream s;
92     for (int i = 0; i < nu_games; ++i)
93     {
94         s.str("");
95         Writer writer(s);
96         writer.set_indent(-1);
97         bd.init();
98         writer.begin_tree();
99         writer.begin_node();
100         writer.write_property("GM", variant_str);
101         writer.end_node();
102         while (! bd.is_game_over())
103         {
104             auto c = bd.get_effective_to_play();
105             auto mv = player.genmove(bd, c);
106             bd.play(c, mv);
107             writer.begin_node();
108             writer.write_property(get_color_id(variant, c),
109                                   bd.to_string(mv, false));
110             writer.end_node();
111         }
112         writer.end_tree();
113         out << s.str() << '\n';
114     }
115 }
116 
cmd_param(Arguments args,Response & response)117 void GtpEngine::cmd_param(Arguments args, Response& response)
118 {
119     auto& p = get_mcts_player();
120     auto& s = get_search();
121     if (args.get_size() == 0)
122         response
123             << "avoid_symmetric_draw " << s.get_avoid_symmetric_draw() << '\n'
124             << "exploration_constant " << s.get_exploration_constant() << '\n'
125             << "fixed_simulations " << p.get_fixed_simulations() << '\n'
126             << "rave_child_max " << s.get_rave_child_max() << '\n'
127             << "rave_parent_max " << s.get_rave_parent_max() << '\n'
128             << "rave_weight " << s.get_rave_weight() << '\n'
129             << "reuse_subtree " << s.get_reuse_subtree() << '\n'
130             << "use_book " << p.get_use_book() << '\n';
131     else
132     {
133         args.check_size(2);
134         auto name = args.get(0);
135         if (name == "avoid_symmetric_draw")
136             s.set_avoid_symmetric_draw(args.get<bool>(1));
137         else if (name == "exploration_constant")
138             s.set_exploration_constant(args.get<Float>(1));
139         else if (name == "fixed_simulations")
140             p.set_fixed_simulations(args.get<Float>(1));
141         else if (name == "rave_child_max")
142             s.set_rave_child_max(args.get<Float>(1));
143         else if (name == "rave_parent_max")
144             s.set_rave_parent_max(args.get<Float>(1));
145         else if (name == "rave_weight")
146             s.set_rave_weight(args.get<Float>(1));
147         else if (name == "reuse_subtree")
148             s.set_reuse_subtree(args.get<bool>(1));
149         else if (name == "use_book")
150             p.set_use_book(args.get<bool>(1));
151         else
152         {
153             ostringstream msg;
154             msg << "unknown parameter '" << name << "'";
155             throw Failure(msg.str());
156         }
157     }
158 }
159 
cmd_version(Response & response)160 void GtpEngine::cmd_version(Response& response)
161 {
162     string version;
163 #ifdef VERSION
164     version = VERSION;
165 #else
166     version = "unknown";
167 #endif
168 #ifdef LIBBOARDGAME_DEBUG
169     version.append(" (dbg)");
170 #endif
171     response.set(version);
172 }
173 
create_player(Variant variant,unsigned level,const string & books_dir,unsigned nu_threads)174 void GtpEngine::create_player(Variant variant, unsigned level,
175                            const string& books_dir, unsigned nu_threads)
176 {
177     auto max_level = level;
178     m_player = make_unique<Player>(variant, max_level, books_dir, nu_threads);
179     get_mcts_player().set_level(level);
180     set_player(*m_player);
181 }
182 
get_mcts_player()183 Player& GtpEngine::get_mcts_player()
184 {
185     try
186     {
187         return dynamic_cast<Player&>(*m_player);
188     }
189     catch (const bad_cast&)
190     {
191         throw Failure("current player is not mcts player");
192     }
193 }
194 
get_search()195 Search& GtpEngine::get_search()
196 {
197     return get_mcts_player().get_search();
198 }
199 
use_cpu_time(bool enable)200 void GtpEngine::use_cpu_time(bool enable)
201 {
202     get_mcts_player().use_cpu_time(enable);
203 }
204 
205 //-----------------------------------------------------------------------------
206