Wed, 11 Jun 2014 15:38:01 +0200
added return code to move validation (for more informative messages) + fixed a bug where simulations added movelist items to the original gamestate
1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright 2014 Mike Becker. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 *
28 */
30 #include "game.h"
31 #include "network.h"
32 #include "input.h"
33 #include "colors.h"
34 #include <ncurses.h>
35 #include <string.h>
36 #include <inttypes.h>
37 #include <sys/select.h>
39 static const uint8_t boardx = 10, boardy = 10;
40 static int inputy = 21; /* should be overridden on game startup */
42 static int timecontrol(GameState *gamestate, GameInfo *gameinfo) {
43 if (gameinfo->timecontrol) {
44 uint16_t white = remaining_movetime(gameinfo, gamestate, WHITE);
45 uint16_t black = remaining_movetime(gameinfo, gamestate, BLACK);
46 mvprintw(boardy+4, boardx-1,
47 "White time: %4" PRIu16 ":%02" PRIu16,
48 white / 60, white % 60);
49 mvprintw(boardy+5, boardx-1,
50 "Black time: %4" PRIu16 ":%02" PRIu16,
51 black / 60, black % 60);
53 if (white == 0) {
54 move(inputy, 0);
55 printw("Time is over - Black wins!");
56 clrtobot();
57 refresh();
58 return 1;
59 }
60 if (black == 0) {
61 move(inputy, 0);
62 printw("Time is over - White wins!");
63 clrtobot();
64 refresh();
65 return 1;
66 }
67 }
69 return 0;
70 }
72 static void draw_board(GameState *gamestate) {
73 for (uint8_t y = 0 ; y < 8 ; y++) {
74 for (uint8_t x = 0 ; x < 8 ; x++) {
75 uint8_t col = gamestate->board[y][x] & COLOR_MASK;
76 uint8_t piece = gamestate->board[y][x] & PIECE_MASK;
77 char piecec;
78 if (piece) {
79 piecec = piece == PAWN ? 'P' : getpiecechr(piece);
80 } else {
81 piecec = ' ';
82 }
84 _Bool boardblack = (y&1)==(x&1);
85 attrset((col==WHITE ? A_BOLD : A_DIM)|
86 COLOR_PAIR(col == WHITE ?
87 (boardblack ? COL_WB : COL_WW) :
88 (boardblack ? COL_BB : COL_BW)
89 )
90 );
92 int cy = gamestate->mycolor == WHITE ? boardy-y : boardy-7+y;
93 int cx = gamestate->mycolor == WHITE ? boardx+x*3 : boardx+21-x*3;
94 mvaddch(cy, cx, ' ');
95 mvaddch(cy, cx+1, piecec);
96 mvaddch(cy, cx+2, ' ');
97 }
98 }
100 attrset(A_NORMAL);
101 for (uint8_t i = 0 ; i < 8 ; i++) {
102 int x = gamestate->mycolor == WHITE ? boardx+i*3+1 : boardx+22-i*3;
103 int y = gamestate->mycolor == WHITE ? boardy-i : boardy-7+i;
104 mvaddch(boardy+1, x, 'a'+i);
105 mvaddch(y, boardx-2, '1'+i);
106 }
108 /* move log */
109 // TODO: introduce window to avoid bugs with a long move log
110 uint8_t logy = 0;
111 const uint8_t logx = boardx + 30;
112 int logi = 1;
113 MoveList *logelem = gamestate->movelist;
115 while (logelem) {
116 logi++;
117 if (logi % 2 == 0) {
118 if ((logi - 2) % 4 == 0) {
119 logy++;
120 move(logy, logx);
121 }
122 printw("%d. ", logi / 2);
123 }
125 if (logelem) {
126 Move move = logelem->move;
127 if ((move.piece&PIECE_MASK) == KING &&
128 abs(move.tofile-move.fromfile) == 2) {
129 addstr(move.tofile==fileidx('c')?"O-O-O":"O-O");
130 } else {
131 char logstr[] = {
132 getpiecechr(move.piece),
133 filechr(move.fromfile), rowchr(move.fromrow),
134 move.capture ? 'x':'\0',
135 filechr(move.tofile), rowchr(move.torow),
136 move.check ? '+' : (move.promotion ? '=' : '\0'),
137 move.promotion ? getpiecechr(move.promotion) : '\0'
138 };
139 for (int stri = 0 ; stri < sizeof(logstr) ; stri++) {
140 if (logstr[stri]) {
141 addch(logstr[stri]);
142 }
143 }
144 }
145 if (!logelem->next) {
146 if (gamestate->checkmate) {
147 addstr("\b#");
148 } else if (gamestate->stalemate) {
149 addstr(" stalemate");
150 }
151 }
152 addch(' ');
154 logelem = logelem->next;
155 }
156 }
157 }
159 static void eval_move_failed_msg(int code) {
160 switch (code) {
161 case AMBIGUOUS_MOVE:
162 printw("Ambiguous move - please specify the piece to move.");
163 break;
164 case INVALID_POSITION:
165 printw("No piece can be moved this way.");
166 break;
167 case NEED_PROMOTION:
168 printw("You need to promote the pawn (append \"=Q\" e.g.)!");
169 break;
170 case KING_IN_CHECK:
171 printw("Your king is in check!");
172 break;
173 case PIECE_PINNED:
174 printw("This piece is pinned!");
175 break;
176 case INVALID_MOVE_SYNTAX:
177 printw("Can't interpret move - please use algebraic notation.");
178 break;
179 case RULES_VIOLATED:
180 printw("Move does not comply chess rules.");
181 break;
182 case KING_MOVES_INTO_CHECK:
183 printw("Can't move the king into a check position.");
184 break;
185 default:
186 printw("Unknown move parser error.");
187 }
188 }
190 #define MOVESTR_BUFLEN 8
191 static int domove_singlemachine(GameState *gamestate, GameInfo *gameinfo) {
194 size_t bufpos = 0;
195 char movestr[MOVESTR_BUFLEN];
197 flushinp();
198 while (1) {
199 if (timecontrol(gamestate, gameinfo)) {
200 return 1;
201 }
203 move(inputy, 0);
204 printw(
205 "Use chess notation to enter your move.\n"
206 "Or type 'resign' to resign or 'remis' to end with remis.\n\n"
207 "Type your move: ");
208 clrtoeol();
210 if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) {
211 if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) {
212 printw("%s resigned!",
213 gamestate->mycolor==WHITE?"White":"Black");
214 clrtoeol();
215 refresh();
216 return 1;
217 } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
218 printw("Game ends remis.");
219 clrtoeol();
220 refresh();
221 return 1;
222 } else {
223 Move move;
224 int eval_result = eval_move(gamestate, movestr, &move);
225 switch (eval_result) {
226 case VALID_MOVE_SYNTAX:
227 eval_result = validate_move(gamestate, &move);
228 if (eval_result == VALID_MOVE_SEMANTICS) {
229 apply_move(gamestate, &move);
230 if (gamestate->checkmate) {
231 printw("Checkmate!");
232 clrtoeol();
233 return 1;
234 } else if (gamestate->stalemate) {
235 printw("Stalemate!");
236 clrtoeol();
237 return 1;
238 } else {
239 return 0;
240 }
241 } else {
242 eval_move_failed_msg(eval_result);
243 }
244 break;
245 default:
246 eval_move_failed_msg(eval_result);
247 }
248 clrtoeol();
249 }
250 }
251 }
252 }
254 static int sendmove(GameState *gamestate, GameInfo *gameinfo, int opponent) {
256 size_t bufpos = 0;
257 char movestr[MOVESTR_BUFLEN];
258 _Bool remisrejected = FALSE;
259 uint8_t code;
261 flushinp();
262 while (1) {
263 if (timecontrol(gamestate, gameinfo)) {
264 net_send_code(opponent, NETCODE_TIMEOVER);
265 return 1;
266 }
268 move(inputy, 0);
269 if (remisrejected) {
270 printw(
271 "Use chess notation to enter your move.\n"
272 "Remis offer rejected - type 'resign' to resign. \n\n"
273 "Type your move: ");
274 } else {
275 printw(
276 "Use chess notation to enter your move.\n"
277 "Or type 'resign' to resign or 'remis' to offer remis.\n\n"
278 "Type your move: ");
279 }
280 clrtoeol();
282 if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) {
283 if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) {
284 printw("You resigned!");
285 clrtoeol();
286 refresh();
287 net_send_code(opponent, NETCODE_RESIGN);
288 return 1;
289 } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
290 if (!remisrejected) {
291 net_send_code(opponent, NETCODE_REMIS);
292 printw("Remis offer sent - waiting for acceptance...");
293 refresh();
294 code = net_recieve_code(opponent);
295 if (code == NETCODE_ACCEPT) {
296 printw("\rRemis accepted!");
297 clrtoeol();
298 refresh();
299 return 1;
300 } else if (code == NETCODE_CONNLOST) {
301 printw("\rYour opponent left the game.");
302 clrtoeol();
303 refresh();
304 return 1;
305 } else {
306 remisrejected = TRUE;
307 }
308 }
309 } else {
310 Move move;
311 int eval_result = eval_move(gamestate, movestr, &move);
312 switch (eval_result) {
313 case VALID_MOVE_SYNTAX:
314 net_send_data(opponent, NETCODE_MOVE, &move, sizeof(Move));
315 code = net_recieve_code(opponent);
316 move.check = code == NETCODE_CHECK ||
317 code == NETCODE_CHECKMATE;
318 gamestate->checkmate = code == NETCODE_CHECKMATE;
319 gamestate->stalemate = code == NETCODE_STALEMATE;
320 if (code == NETCODE_DECLINE) {
321 uint32_t reason;
322 net_recieve_data(opponent, &reason, sizeof(uint32_t));
323 reason = ntohl(reason);
324 eval_move_failed_msg(reason);
325 } else if (code == NETCODE_ACCEPT
326 || code == NETCODE_CHECK
327 || code == NETCODE_CHECKMATE
328 || code == NETCODE_STALEMATE) {
329 apply_move(gamestate, &move);
330 if (gamestate->checkmate) {
331 printw("Checkmate!");
332 clrtoeol();
333 return 1;
334 } else if (gamestate->stalemate) {
335 printw("Stalemate!");
336 clrtoeol();
337 return 1;
338 } else {
339 return 0;
340 }
341 } else if (code == NETCODE_CONNLOST) {
342 printw("Your opponent left the game.");
343 return 1;
344 } else {
345 printw("Invalid network response.");
346 }
347 break;
348 default:
349 eval_move_failed_msg(eval_result);
350 }
351 clrtoeol();
352 }
353 }
354 }
355 }
357 static int recvmove(GameState *gamestate, GameInfo *gameinfo, int opponent) {
359 struct timeval timeout;
360 while (1) {
361 timecontrol(gamestate, gameinfo);
363 move(inputy, 0);
364 printw("Awaiting opponent move...");
365 clrtoeol();
366 refresh();
368 fd_set readfds;
370 FD_ZERO(&readfds);
371 FD_SET(opponent, &readfds);
372 timeout.tv_sec = 0;
373 timeout.tv_usec = 1e5;
375 int result = select(opponent+1, &readfds, NULL, NULL, &timeout);
376 if (result == -1) {
377 printw("\rCannot perform asynchronous network IO");
378 cbreak(); getch();
379 exit(EXIT_FAILURE);
380 }
381 if (result > 0) {
382 uint8_t code = net_recieve_code(opponent);
384 Move move;
385 switch (code) {
386 case NETCODE_TIMEOVER:
387 printw("\rYour opponent's time ran out - you win!");
388 clrtoeol();
389 return 1;
390 case NETCODE_RESIGN:
391 printw("\rYour opponent resigned!");
392 clrtoeol();
393 return 1;
394 case NETCODE_CONNLOST:
395 printw("\rYour opponent has left the game.");
396 clrtoeol();
397 return 1;
398 case NETCODE_REMIS:
399 if (prompt_yesno(
400 "\rYour opponent offers remis - do you accept")) {
401 printw("\rRemis accepted!");
402 clrtoeol();
403 net_send_code(opponent, NETCODE_ACCEPT);
404 return 1;
405 } else {
406 net_send_code(opponent, NETCODE_DECLINE);
407 }
408 break;
409 case NETCODE_MOVE:
410 net_recieve_data(opponent, &move, sizeof(Move));
411 code = validate_move(gamestate, &move);
412 if (code == VALID_MOVE_SEMANTICS) {
413 apply_move(gamestate, &move);
414 if (move.check) {
415 net_send_code(opponent, NETCODE_CHECK);
416 } else if (gamestate->checkmate) {
417 net_send_code(opponent, NETCODE_CHECKMATE);
418 printw("\rCheckmate!");
419 clrtoeol();
420 return 1;
421 } else if (gamestate->stalemate) {
422 net_send_code(opponent, NETCODE_STALEMATE);
423 printw("\rStalemate!");
424 clrtoeol();
425 return 1;
426 } else {
427 net_send_code(opponent, NETCODE_ACCEPT);
428 }
429 return 0;
430 } else {
431 uint32_t reason = htonl(code);
432 net_send_data(opponent, NETCODE_DECLINE,
433 &reason, sizeof(uint32_t));
434 }
435 break;
436 default:
437 printw("\nInvalid network request.");
438 }
439 }
440 }
441 }
443 static void init_board(GameState *gamestate) {
444 Board initboard = {
445 {WROOK, WKNIGHT, WBISHOP, WQUEEN, WKING, WBISHOP, WKNIGHT, WROOK},
446 {WPAWN, WPAWN, WPAWN, WPAWN, WPAWN, WPAWN, WPAWN, WPAWN},
447 {0, 0, 0, 0, 0, 0, 0, 0},
448 {0, 0, 0, 0, 0, 0, 0, 0},
449 {0, 0, 0, 0, 0, 0, 0, 0},
450 {0, 0, 0, 0, 0, 0, 0, 0},
451 {BPAWN, BPAWN, BPAWN, BPAWN, BPAWN, BPAWN, BPAWN, BPAWN},
452 {BROOK, BKNIGHT, BBISHOP, BQUEEN, BKING, BBISHOP, BKNIGHT, BROOK}
453 };
454 memcpy(gamestate->board, initboard, sizeof(Board));
455 }
457 void game_start_singlemachine(Settings *settings) {
458 inputy = getmaxy(stdscr) - 6;
460 GameState gamestate;
461 memset(&gamestate, 0, sizeof(GameState));
462 init_board(&gamestate);
463 gamestate.mycolor = WHITE;
465 _Bool running;
466 do {
467 clear();
468 draw_board(&gamestate);
469 running = !domove_singlemachine(&gamestate, &(settings->gameinfo));
470 gamestate.mycolor = opponent_color(gamestate.mycolor);
471 } while (running);
472 move(0,0);
473 draw_board(&gamestate);
475 gamestate_cleanup(&gamestate);
476 }
478 void game_start(Settings *settings, int opponent) {
479 inputy = getmaxy(stdscr) - 6;
481 _Bool myturn = is_server(settings) ==
482 (settings->gameinfo.servercolor == WHITE);
484 GameState gamestate;
485 memset(&gamestate, 0, sizeof(GameState));
486 init_board(&gamestate);
487 gamestate.mycolor = myturn ? WHITE:BLACK;
489 _Bool running;
490 do {
491 clear();
492 draw_board(&gamestate);
493 if (myturn) {
494 running = !sendmove(&gamestate, &(settings->gameinfo), opponent);
495 } else {
496 running = !recvmove(&gamestate, &(settings->gameinfo), opponent);
497 }
498 myturn ^= TRUE;
499 } while (running);
501 move(0,0);
502 draw_board(&gamestate);
504 gamestate_cleanup(&gamestate);
505 }