1 //-----------------------------------------------------------------------------
2 /** @file libpentobi_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 <memory>
11 #include "libboardgame_base/CpuTime.h"
12 #include "libboardgame_base/Log.h"
13 #include "libboardgame_base/RandomGenerator.h"
14 #include "libboardgame_base/SgfUtil.h"
15 #include "libboardgame_base/TreeReader.h"
16 #include "libpentobi_base/MoveMarker.h"
17 #include "libpentobi_base/PentobiTreeWriter.h"
18
19 namespace libpentobi_gtp {
20
21 using namespace std;
22 using libboardgame_base::RandomGenerator;
23 using libboardgame_base::TreeReader;
24 using libboardgame_base::get_last_node;
25 using libboardgame_gtp::Failure;
26 using libpentobi_base::Grid;
27 using libpentobi_base::Move;
28 using libpentobi_base::MoveList;
29 using libpentobi_base::MoveMarker;
30 using libpentobi_base::PentobiTreeWriter;
31 using libpentobi_base::Point;
32 using libpentobi_base::SgfNode;
33
34 //-----------------------------------------------------------------------------
35
GtpEngine(Variant variant)36 GtpEngine::GtpEngine(Variant variant)
37 : m_game(variant)
38 {
39 add("all_legal", &GtpEngine::cmd_all_legal);
40 add("clear_board", &GtpEngine::cmd_clear_board);
41 add("cputime", &GtpEngine::cmd_cputime);
42 add("final_score", &GtpEngine::cmd_final_score);
43 add("loadsgf", &GtpEngine::cmd_loadsgf);
44 add("point_integers", &GtpEngine::cmd_point_integers);
45 add("move_info", &GtpEngine::cmd_move_info);
46 add("p", &GtpEngine::cmd_p);
47 add("param_base", &GtpEngine::cmd_param_base);
48 add("play", &GtpEngine::cmd_play);
49 add("savesgf", &GtpEngine::cmd_savesgf);
50 add("set_game", &GtpEngine::cmd_set_game);
51 add("set_random_seed", &GtpEngine::cmd_set_random_seed);
52 add("showboard", &GtpEngine::cmd_showboard);
53 add("undo", &GtpEngine::cmd_undo);
54 }
55
board_changed()56 void GtpEngine::board_changed()
57 {
58 if (m_show_board)
59 LIBBOARDGAME_LOG(get_board());
60 }
61
cmd_all_legal(Arguments args,Response & response)62 void GtpEngine::cmd_all_legal(Arguments args, Response& response)
63 {
64 auto& bd = get_board();
65 auto moves = make_unique<MoveList>();
66 auto marker = make_unique<MoveMarker>();
67 bd.gen_moves(get_color_arg(args), *marker, *moves);
68 for (auto mv : *moves)
69 response << bd.to_string(mv, false) << '\n';
70 }
71
cmd_clear_board()72 void GtpEngine::cmd_clear_board()
73 {
74 m_game.init();
75 board_changed();
76 }
77
cmd_cputime(Response & response)78 void GtpEngine::cmd_cputime(Response& response)
79 {
80 auto time = libboardgame_base::cpu_time();
81 if (time < 0)
82 throw Failure("cannot determine cpu time");
83 response << time;
84 }
85
cmd_final_score(Response & response)86 void GtpEngine::cmd_final_score(Response& response)
87 {
88 auto& bd = get_board();
89 if (get_nu_players(bd.get_variant()) > 2)
90 {
91 for (Color c : bd.get_colors())
92 response << bd.get_points(c) << ' ';
93 }
94 else
95 {
96 auto score = bd.get_score_twoplayer(Color(0));
97 if (score > 0)
98 response << "B+" << score;
99 else if (score < 0)
100 response << "W+" << (-score);
101 else
102 response << "0";
103 }
104 }
105
cmd_g(Response & response)106 void GtpEngine::cmd_g(Response& response)
107 {
108 genmove(get_board().get_effective_to_play(), response);
109 }
110
cmd_genmove(Arguments args,Response & response)111 void GtpEngine::cmd_genmove(Arguments args, Response& response)
112 {
113 genmove(get_color_arg(args), response);
114 }
115
cmd_loadsgf(Arguments args)116 void GtpEngine::cmd_loadsgf(Arguments args)
117 {
118 args.check_size_less_equal(2);
119 auto file = args.get<string>(0);
120 unsigned move_number = 0;
121 if (args.get_size() == 2)
122 move_number = args.get_min<unsigned>(1, 1);
123 try
124 {
125 TreeReader reader;
126 reader.read(file);
127 auto tree = reader.get_tree_transfer_ownership();
128 m_game.init(tree);
129 const SgfNode* node = nullptr;
130 if (move_number > 0)
131 node = m_game.get_tree().get_node_before_move_number(move_number - 1);
132 if (node == nullptr)
133 node = &get_last_node(m_game.get_root());
134 m_game.goto_node(*node);
135 board_changed();
136 }
137 catch (const runtime_error& e)
138 {
139 throw Failure(e.what());
140 }
141 }
142
143 /** Return move info of a move given by its integer or string representation. */
cmd_move_info(Arguments args,Response & response)144 void GtpEngine::cmd_move_info(Arguments args, Response& response)
145 {
146 auto& bd = get_board();
147 Move mv;
148 try
149 {
150 mv = Move(args.get<Move::IntType>());
151 }
152 catch (const Failure&)
153 {
154 if (! bd.from_string(mv, args.get<string>()))
155 {
156 ostringstream msg;
157 msg << "invalid argument '" << args.get()
158 << "' (expected move or move ID)";
159 throw Failure(msg.str());
160 }
161 }
162 auto& geo = bd.get_geometry();
163 auto piece = bd.get_move_piece(mv);
164 auto& info_ext_2 = bd.get_move_info_ext_2(mv);
165 response
166 << "\n"
167 << "ID: " << mv.to_int() << "\n"
168 << "Piece: " << static_cast<int>(piece.to_int())
169 << " (" << bd.get_piece_info(piece).get_name() << ")\n"
170 << "Points:";
171 for (auto p : bd.get_move_points(mv))
172 response << ' ' << geo.to_string(p);
173 response
174 << "\n"
175 << "BrkSym: " << info_ext_2.breaks_symmetry << "\n"
176 << "SymMv: " << bd.to_string(info_ext_2.symmetric_move);
177 }
178
cmd_p(Arguments args)179 void GtpEngine::cmd_p(Arguments args)
180 {
181 play(get_board().get_to_play(), args, 0);
182 }
183
cmd_param_base(Arguments args,Response & response)184 void GtpEngine::cmd_param_base(Arguments args, Response& response)
185 {
186 if (args.get_size() == 0)
187 response
188 << "accept_illegal " << m_accept_illegal << '\n'
189 << "resign " << m_resign << '\n';
190 else
191 {
192 args.check_size(2);
193 auto name = args.get(0);
194 if (name == "accept_illegal")
195 m_accept_illegal = args.get<bool>(1);
196 else if (name == "resign")
197 m_resign = args.get<bool>(1);
198 else
199 {
200 ostringstream msg;
201 msg << "unknown parameter '" << name << "'";
202 throw Failure(msg.str());
203 }
204 }
205 }
206
cmd_play(Arguments args)207 void GtpEngine::cmd_play(Arguments args)
208 {
209 play(get_color_arg(args, 0), args, 1);
210 }
211
cmd_point_integers(Response & response)212 void GtpEngine::cmd_point_integers(Response& response)
213 {
214 auto& geo = get_board().get_geometry();
215 Grid<Point::IntType> grid;
216 for (Point p : geo)
217 grid[p] = p.to_int();
218 response << '\n' << grid.to_string(geo);
219 }
220
cmd_reg_genmove(Arguments args,Response & response)221 void GtpEngine::cmd_reg_genmove(Arguments args, Response& response)
222 {
223 RandomGenerator::set_global_seed_last();
224 Move move = get_player().genmove(get_board(), get_color_arg(args));
225 if (move.is_null())
226 throw Failure("player failed to generate a move");
227 response << get_board().to_string(move, false);
228 }
229
cmd_savesgf(Arguments args)230 void GtpEngine::cmd_savesgf(Arguments args)
231 {
232 ofstream out(args.get<string>());
233 PentobiTreeWriter writer(out, m_game.get_tree());
234 writer.set_indent(1);
235 writer.write();
236 if (! out)
237 throw Failure(strerror(errno));
238 }
239
240 /** Set the game variant.
241 Argument: game variant as in GM property of Pentobi SGF files
242 <br>
243 This command is similar to the command that is used by Quarry
244 (http://home.gna.org/quarry/) to set a game at GTP engines that support
245 multiple games. */
cmd_set_game(Arguments args)246 void GtpEngine::cmd_set_game(Arguments args)
247 {
248 Variant variant;
249 string line(&*args.get_line().begin(), args.get_line().size());
250 if (! parse_variant(line, variant))
251 throw Failure("invalid argument");
252 m_game.init(variant);
253 board_changed();
254 }
255
256 /** Set global random seed.
257 Arguments: random seed */
cmd_set_random_seed(Arguments args)258 void GtpEngine::cmd_set_random_seed(Arguments args)
259 {
260 RandomGenerator::set_global_seed(args.get<RandomGenerator::ResultType>());
261 }
262
cmd_showboard(Response & response)263 void GtpEngine::cmd_showboard(Response& response)
264 {
265 response << '\n' << get_board();
266 }
267
cmd_undo()268 void GtpEngine::cmd_undo()
269 {
270 auto& bd = get_board();
271 if (bd.get_nu_moves() == 0)
272 throw Failure("cannot undo");
273 m_game.undo();
274 board_changed();
275 }
276
genmove(Color c,Response & response)277 void GtpEngine::genmove(Color c, Response& response)
278 {
279 auto& bd = get_board();
280 auto& player = get_player();
281 auto mv = player.genmove(bd, c);
282 if (mv.is_null())
283 {
284 response << "pass";
285 return;
286 }
287 if (! bd.is_legal(c, mv))
288 {
289 ostringstream msg;
290 msg << "player generated illegal move: " << bd.to_string(mv);
291 throw Failure(msg.str());
292 }
293 if (m_resign && player.resign())
294 {
295 response << "resign";
296 return;
297 }
298 m_game.play(c, mv, true);
299 response << bd.to_string(mv, false);
300 board_changed();
301 }
302
get_color_arg(Arguments args) const303 Color GtpEngine::get_color_arg(Arguments args) const
304 {
305 if (args.get_size() > 1)
306 throw Failure("too many arguments");
307 return get_color_arg(args, 0);
308 }
309
get_color_arg(Arguments args,unsigned i) const310 Color GtpEngine::get_color_arg(Arguments args, unsigned i) const
311 {
312 string s = args.get_tolower(i);
313 auto& bd = get_board();
314 auto variant = bd.get_variant();
315 if (get_nu_colors(variant) == 2)
316 {
317 if (s == "blue" || s == "black" || s == "b")
318 return Color(0);
319 if (s == "green" || s == "white" || s == "w")
320 return Color(1);
321 }
322 else
323 {
324 if (s == "1" || s == "blue")
325 return Color(0);
326 if (s == "2" || s == "yellow")
327 return Color(1);
328 if (s == "3" || s == "red")
329 return Color(2);
330 if (s == "4" || s == "green")
331 return Color(3);
332 }
333 throw Failure("invalid color argument '" + s + "'");
334 }
335
get_player() const336 PlayerBase& GtpEngine::get_player() const
337 {
338 if (m_player == nullptr)
339 throw Failure("no player set");
340 return *m_player;
341 }
342
on_handle_cmd_begin()343 void GtpEngine::on_handle_cmd_begin()
344 {
345 libboardgame_base::flush_log();
346 }
347
play(Color c,Arguments args,unsigned arg_move_begin)348 void GtpEngine::play(Color c, Arguments args, unsigned arg_move_begin)
349 {
350 auto& bd = get_board();
351 if (bd.get_nu_moves() >= Board::max_moves)
352 throw Failure("too many moves");
353 Move mv;
354 if (arg_move_begin == 0)
355 {
356 auto line = args.get_line();
357 if (! bd.from_string(mv, string(&*line.begin(), line.size())))
358 throw Failure("invalid move ");
359 }
360 else
361 {
362 auto line = args.get_remaining_line(arg_move_begin - 1);
363 if (! bd.from_string(mv, string(&*line.begin(), line.size())))
364 throw Failure("invalid move ");
365 }
366 if (mv.is_null())
367 throw Failure("play pass not supported (anymore)");
368 // Board::play() can handle illegal moves at arbitrary positions, even
369 // overlapping, but it does not check (for performance reasons) if the
370 // piece-left count is already zero.
371 if (! bd.is_piece_left(c, bd.get_move_piece(mv)))
372 throw Failure("piece already played");
373 if (! m_accept_illegal && ! bd.is_legal(c, mv))
374 throw Failure("illegal move");
375 m_game.play(c, mv, true);
376 board_changed();
377 }
378
set_player(PlayerBase & player)379 void GtpEngine::set_player(PlayerBase& player)
380 {
381 m_player = &player;
382 add("genmove", &GtpEngine::cmd_genmove);
383 add("g", &GtpEngine::cmd_g);
384 add("reg_genmove", &GtpEngine::cmd_reg_genmove);
385 }
386
set_show_board(bool enable)387 void GtpEngine::set_show_board(bool enable)
388 {
389 if (enable && ! m_show_board)
390 LIBBOARDGAME_LOG(get_board());
391 m_show_board = enable;
392 }
393
394 //-----------------------------------------------------------------------------
395
396 } // namespace libpentobi_gtp
397