Merge branch 'release-2.1.0'
[purplehaze.git] / src / xboard.cpp
blobfc334a6bad28ceee12c8038b5627b24e87625cc2
1 /* Copyright (C) 2007-2012 Vincent Ollivier
3 * Purple Haze is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
8 * Purple Haze is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 #include <algorithm>
18 #include <cassert>
19 #include <ctime>
20 #include <iostream>
21 #include <iterator>
22 #include <csignal>
23 #include <sstream>
24 #include <string>
25 #include <vector>
27 #include "xboard.h"
29 typedef std::vector<std::string> Tokens;
31 static Tokens tokenize(const std::string line)
33 std::istringstream iss(line);
34 Tokens tokens;
35 std::copy(std::istream_iterator<std::string>(iss),
36 std::istream_iterator<std::string>(),
37 std::back_inserter<Tokens>(tokens));
38 assert(tokens.size() > 0);
39 return tokens;
42 static std::string read_line(Log &log)
44 std::string line;
45 getline(std::cin, line);
46 if (line.length() == 0) {
47 line = "quit";
48 } else if (line.substr(0, 4) != "ping") {
49 log.to(Log::FILE) << Log::IN << line << std::endl;
51 return line;
54 static bool is_move(const std::string move)
56 return 4 <= move.size() && move.size() <= 5 &&
57 'a' <= move[0] && move[0] <= 'h' &&
58 '1' <= move[1] && move[1] <= '8' &&
59 'a' <= move[2] && move[2] <= 'h' &&
60 '1' <= move[3] && move[3] <= '8';
63 void Xboard::think()
65 if (get_verbosity() > 1) {
66 log.to(Log::FILE) << game.board << std::endl;
69 const std::string move = search_move();
70 std::string output = "";
71 if (move == "LOST") {
72 output = "result ";
73 if (game.current_position().side() == WHITE) {
74 output += "0-1 {Black mates}";
75 } else {
76 output += "1-0 {White mates}";
78 } else if (move == "DRAW") {
79 output = "result 1/2-1/2";
80 } else if (!force_mode) {
81 play_move(move);
83 const float elapsed = game.time.elapsed() / 100.0f;
84 const float allocated = game.time.allocated() / 100.0f;
85 log.to(Log::FILE) << Log::DEBUG
86 << elapsed << " of " << allocated
87 << " second" << (allocated != 1.0f ? "s" : "")
88 << " used to play" << std::endl;
90 if (get_verbosity() > 1) {
91 log << std::endl;
92 game.print_tt_stats();
94 output = "move " + move;
96 log.to(Log::BOTH) << Log::OUT << output << std::endl;
99 void Xboard::loop()
101 signal(SIGINT, SIG_IGN);
102 log << std::endl; // Acknowledge Xboard mode
104 const std::time_t t = std::time(NULL);
105 std::string datetime = std::ctime(&t);
106 datetime.erase(datetime.end() - 1); // Remove trailing new line character
107 log.to(Log::FILE) << Log::DEBUG << "[" << datetime << "] "
108 << "Purple Haze " << VERSION << std::endl
109 << Log::IN << "xboard" << std::endl;
111 std::string cmd;
112 while ((cmd = read_line(log)) != "quit") {
113 const Tokens args = tokenize(cmd);
114 if (args[0] == "protover") {
115 assert(args.size() == 2);
116 const int version = std::stoi(args[1]);
117 if (version == 2) {
118 for (const std::string (&feature)[2] : XBOARD_FEATURES) {
119 std::string title = feature[0];
120 std::string value = feature[1];
121 if (value != "0" && value != "1") {
122 // Non boolean values must be quoted
123 value = '"' + value + '"';
125 std::string out = "feature " + title + "=" + value;
126 log.to(Log::BOTH) << Log::OUT << out << std::endl;
129 } else if (args[0] == "accepted" || args[0] == "rejected") {
130 assert(args.size() == 2);
131 const std::string feature = args[1];
133 // Given the current feature list, there is nothing to be done
134 // when a feature gets accepted or rejected.
135 } else if (args[0] == "new") {
136 assert(args.size() == 1);
137 new_game();
138 set_board(DEFAULT_FEN);
139 force_mode = false;
140 } else if (args[0] == "setboard") {
141 new_game();
142 set_board(cmd.erase(0, std::string("setboard ").length()));
143 force_mode = false;
144 } else if (args[0] == "go") {
145 assert(args.size() == 1);
146 force_mode = false;
147 if (thinker.joinable()) {
148 thinker.join();
150 thinker = std::thread(&Xboard::think, this);
151 } else if (args[0] == "?") {
152 assert(args.size() == 1);
153 game.time.abort();
154 } else if (args[0] == "force") {
155 assert(args.size() == 1);
156 force_mode = true;
157 } else if (args[0] == "ping") {
158 if (thinker.joinable()) {
159 thinker.join(); // Wait before replying to 'ping'
161 log.to(Log::FILE) << Log::IN << cmd << std::endl;
162 assert(args.size() == 2);
163 const int n = std::stoi(args[1]);
164 log.to(Log::BOTH) << Log::OUT << "pong " << n << std::endl;
165 } else if (args[0] == "level") {
166 assert(args.size() == 4);
167 int moves = std::stoi(args[1]); // Number of moves
169 // TODO "level 0 m 0" means play the entire game in 'm' minutes,
170 // but the current time management don't support it.
171 if (moves == 0) {
172 moves = 60;
175 // Time interval in minutes or minutes:seconds
176 int time;
177 size_t sep = args[2].find(":");
178 if (sep == std::string::npos) {
179 const int minutes = std::stoi(args[2]);
180 time = minutes * 60;
181 } else {
182 const int minutes = std::stoi(args[2].substr(0, sep));
183 const int seconds = std::stoi(args[2].substr(sep + 1));
184 time = minutes * 60 + seconds;
187 // Control character
188 const int control = std::stoi(args[3]);
190 if (control == 0) {
191 set_time(moves, time);
192 } else { // Not in Xboard protocol
193 // TODO If not zero, control is a time increment,
194 // but currently this time is not directly used
195 set_time(moves, time);
197 } else if (args[0] == "time") {
198 assert(args.size() == 2);
199 const int time = std::stoi(args[1]);
200 set_remaining(time);
201 } else if (args[0] == "otim") {
202 assert(args.size() == 2);
203 } else if (args[0] == "sd") {
204 assert(args.size() == 2);
205 const int d = std::stoi(args[1]);
206 set_depth(d);
207 } else if (args[0] == "undo") {
208 assert(args.size() == 1);
209 undo_move();
210 } else if (args[0] == "remove") {
211 assert(args.size() == 1);
212 undo_move();
213 undo_move();
214 } else if (args[0] == "post") {
215 assert(args.size() == 1);
216 set_output_thinking(true);
217 } else if (args[0] == "nopost") {
218 assert(args.size() == 1);
219 set_output_thinking(false);
220 } else if (args[0] == "hard") {
221 assert(args.size() == 1);
222 } else if (is_move(args[0]) && !parse_move(args[0]).is_null()) {
223 assert(args.size() == 1);
224 std::string move = args[0];
225 log.to(Log::FILE) << Log::DEBUG
226 << "move '" << move << "' successfully parsed"
227 << std::endl;
229 if (!play_move(move)) {
230 log.to(Log::BOTH) << Log::OUT
231 << "Illegal move: " << move << std::endl;
232 continue;
234 if (!force_mode) {
235 if (thinker.joinable()) {
236 thinker.join();
238 thinker = std::thread(&Xboard::think, this);
240 } else if (args[0] == "verbose") { // Debug mode
241 assert(args.size() == 1);
242 verbosity = 2;
243 } else {
244 log.to(Log::FILE) << Log::DEBUG
245 << "unrecognized command '" << cmd << "'"
246 << std::endl;
249 if (thinker.joinable()) {
250 thinker.join();