src/game.c

changeset 129
189c7c77aaab
parent 128
ce38ee9bc3af
child 130
3fc6b1d6cbe9
equal deleted inserted replaced
128:ce38ee9bc3af 129:189c7c77aaab
1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright 2016 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 */
29
30 #include "game.h"
31 #include "network.h"
32 #include "input.h"
33 #include "colors.h"
34 #include "chess/rules.h"
35 #include "chess/pgn.h"
36 #include <ncurses.h>
37 #include <string.h>
38 #include <stdio.h>
39 #include <errno.h>
40
41 static const uint8_t boardx = 4, boardy = 10;
42 static int inputy = 21; /* should be overridden on game startup */
43
44 static int timecontrol(GameState *gamestate, GameInfo *gameinfo) {
45 if (gameinfo->timecontrol) {
46 uint16_t white = remaining_movetime(gameinfo, gamestate, WHITE);
47 uint16_t black = remaining_movetime(gameinfo, gamestate, BLACK);
48 char clkstr[16];
49 bool always_hours = gameinfo->time >= 3600;
50 print_clk(white, clkstr, always_hours);
51 mvprintw(boardy+4, boardx-1, "White time: %s", clkstr);
52 print_clk(black, clkstr, always_hours);
53 mvprintw(boardy+5, boardx-1, "Black time: %s", clkstr);
54
55 if (white == 0) {
56 move(inputy, 0);
57 printw("Time is over - Black wins!");
58 clrtobot();
59 refresh();
60 return 1;
61 }
62 if (black == 0) {
63 move(inputy, 0);
64 printw("Time is over - White wins!");
65 clrtobot();
66 refresh();
67 return 1;
68 }
69 }
70
71 return 0;
72 }
73
74 static void draw_board(GameState *gamestate,
75 uint8_t perspective,
76 bool unicode) {
77 char fen[90];
78 compute_fen(fen, gamestate);
79 mvaddstr(0, 0, fen);
80
81 for (uint8_t y = 0 ; y < 8 ; y++) {
82 for (uint8_t x = 0 ; x < 8 ; x++) {
83 uint8_t col = gamestate->board[y][x] & COLOR_MASK;
84 uint8_t piece = gamestate->board[y][x];
85 char piecestr[5];
86 if (piece) {
87 if (unicode) {
88 char* uc = getpieceunicode(piece);
89 strncpy(piecestr, uc, 5);
90 } else {
91 piecestr[0] = (piece & PIECE_MASK) == PAWN
92 ? 'P' : getpiecechr(piece);
93 piecestr[1] = '\0';
94 }
95 } else {
96 piecestr[0] = ' ';
97 piecestr[1] = '\0';
98 }
99
100 bool boardblack = (y&1)==(x&1);
101 attrset((col==WHITE ? A_BOLD : A_DIM)|
102 COLOR_PAIR(col == WHITE ?
103 (boardblack ? COL_WB : COL_WW) :
104 (boardblack ? COL_BB : COL_BW)
105 )
106 );
107
108 int cy = perspective == WHITE ? boardy-y : boardy-7+y;
109 int cx = perspective == WHITE ? boardx+x*3 : boardx+21-x*3;
110 mvprintw(cy, cx, " %s ", piecestr);
111 }
112 }
113
114 attrset(A_NORMAL);
115 for (uint8_t i = 0 ; i < 8 ; i++) {
116 int x = perspective == WHITE ? boardx+i*3+1 : boardx+22-i*3;
117 int y = perspective == WHITE ? boardy-i : boardy-7+i;
118 mvaddch(boardy+1, x, 'a'+i);
119 mvaddch(y, boardx-2, '1'+i);
120 }
121
122 /* move log */
123 uint8_t logy = 2;
124 const uint8_t logx = boardx + 28;
125 move(logy, logx);
126
127 /* count full moves */
128 unsigned int logi = 0;
129
130 /* wrap log after 45 moves */
131 while (gamestate->movecount/6-logi/3 >= 15) {
132 logi++;
133 }
134
135 for (unsigned mi = logi*2 ; mi < gamestate->movecount ; mi++) {
136 bool iswhite = mi % 2 == 0;
137 if (iswhite) {
138 logi++;
139 printw("%d. ", logi);
140 }
141
142 addstr(gamestate->moves[mi].string);
143 if (!iswhite && logi%3 == 0) {
144 move(++logy, logx);
145 } else {
146 addch(' ');
147 }
148 }
149 }
150
151 static void eval_move_failed_msg(int code) {
152 switch (code) {
153 case AMBIGUOUS_MOVE:
154 printw("Ambiguous move - please specify the piece to move.");
155 break;
156 case INVALID_POSITION:
157 printw("No piece can be moved this way.");
158 break;
159 case NEED_PROMOTION:
160 printw("You need to promote the pawn (append \"=Q\" e.g.)!");
161 break;
162 case KING_IN_CHECK:
163 printw("Your king is in check!");
164 break;
165 case PIECE_PINNED:
166 printw("This piece is pinned!");
167 break;
168 case INVALID_MOVE_SYNTAX:
169 printw("Can't interpret move - please use algebraic notation.");
170 break;
171 case RULES_VIOLATED:
172 printw("Move does not comply chess rules.");
173 break;
174 case KING_MOVES_INTO_CHECK:
175 printw("Can't move the king into a check position.");
176 break;
177 default:
178 printw("Unknown move parser error.");
179 }
180 }
181
182 static void save_pgn(GameState *gamestate, GameInfo *gameinfo) {
183 int y = getcury(stdscr);
184
185 /* ask for player names */
186 {
187 char pname[PLAYER_NAME_BUFLEN];
188 printw("\rWhite's name (%s): ", pgn_player_name(gamestate, WHITE));
189 clrtoeol();
190 if (getnstr(pname, PLAYER_NAME_BUFLEN) == OK && pname[0] != '\0') {
191 strncpy(gamestate->wname, pname, PLAYER_NAME_BUFLEN);
192 }
193 move(y, 0);
194 printw("\rBlack's name (%s): ", pgn_player_name(gamestate, BLACK));
195 clrtoeol();
196 if (getnstr(pname, PLAYER_NAME_BUFLEN) == OK && pname[0] != '\0') {
197 strncpy(gamestate->bname, pname, PLAYER_NAME_BUFLEN);
198 }
199 move(y, 0);
200 }
201
202 bool export_comments = prompt_yesno("Export with comments");
203
204 printw("\rFilename: ");
205 clrtoeol();
206
207 char filename[64];
208 if (getnstr(filename, 64) == OK && filename[0] != '\0') {
209 move(y, 0);
210 FILE *file = fopen(filename, "w");
211 if (file) {
212 write_pgn(file, gamestate, gameinfo, export_comments);
213 fclose(file);
214 printw("File saved.");
215 } else {
216 printw("Can't write to file (%s).", strerror(errno));
217 }
218 clrtoeol();
219 }
220 }
221
222 #define MOVESTR_BUFLEN 10
223 static int domove_singlemachine(GameState *gamestate,
224 GameInfo *gameinfo, uint8_t curcolor) {
225
226
227 size_t bufpos = 0;
228 char movestr[MOVESTR_BUFLEN];
229
230 flushinp();
231 while (1) {
232 const char *curcolorstr = curcolor == WHITE ? "White" : "Black";
233 if (timecontrol(gamestate, gameinfo)) {
234 return 1;
235 }
236 move(inputy, 0);
237 printw(
238 "Use chess notation to enter your move.\n"
239 "Or use a command: remis, resign, savepgn\n\n"
240 "%s to move: ", curcolorstr);
241 clrtoeol();
242
243 if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) {
244 if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) {
245 if (curcolor == WHITE) {
246 gamestate->wresign = true;
247 } else {
248 gamestate->bresign = true;
249 }
250 return 1;
251 } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
252 gamestate->remis = true;
253 return 1;
254 } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) {
255 save_pgn(gamestate, gameinfo);
256 } else if (movestr[0] == 0) {
257 /* ignore empty move strings and ask again */
258 } else {
259 Move move;
260 int result = eval_move(gamestate, movestr, &move, curcolor);
261 if (result == VALID_MOVE_SYNTAX) {
262 result = validate_move(gamestate, &move);
263 if (result == VALID_MOVE_SEMANTICS) {
264 apply_move(gamestate, &move);
265 if (gamestate->checkmate) {
266 return 1;
267 } else if (gamestate->stalemate) {
268 return 1;
269 } else {
270 return 0;
271 }
272 } else {
273 eval_move_failed_msg(result);
274 }
275 } else {
276 eval_move_failed_msg(result);
277 }
278 clrtoeol();
279 }
280 }
281 }
282 }
283
284 static int sendmove(GameState *gamestate, GameInfo *gameinfo,
285 int opponent, uint8_t mycolor) {
286
287 size_t bufpos = 0;
288 char movestr[MOVESTR_BUFLEN];
289 bool remis_rejected = false;
290 bool remis_suggested = false;
291 bool resign_suggested = false;
292 bool use_premove = false;
293 uint8_t code;
294
295 if (*gamestate->premove) {
296 use_premove = true;
297 const unsigned mlen = sizeof(gamestate->premove);
298 strncpy(movestr, gamestate->premove, mlen);
299 movestr[mlen] = '\0';
300 memset(gamestate->premove, 0, mlen);
301 }
302
303 flushinp();
304 while (1) {
305 if (timecontrol(gamestate, gameinfo)) {
306 net_send_code(opponent, NETCODE_TIMEOVER);
307 return 1;
308 }
309
310 move(inputy, 0);
311 printw("Use chess notation to enter your move.\n");
312 if (resign_suggested) {
313 if (remis_suggested) {
314 printw("The opponent asks you to resign or accept remis. \n\n");
315 } else {
316 printw("The opponent asks you to resign. \n\n");
317 }
318 } else if (remis_suggested) {
319 printw("The opponent offers remis. Type remis to accept. \n\n");
320 } else if (remis_rejected) {
321 printw("Remis offer rejected. \n\n");
322 } else {
323 printw("Or use a command: remis, resign, savepgn \n\n");
324 }
325 printw("Type your move: ");
326 clrtoeol();
327
328 /* check if the opponent sent us something */
329 code = net_recieve_code_async(opponent);
330 switch (code) {
331 case NETCODE_REMIS:
332 remis_suggested = true;
333 break;
334 case NETCODE_TAUNT:
335 resign_suggested = true;
336 break;
337 case NETCODE_RESIGN:
338 if (mycolor == WHITE) {
339 gamestate->bresign = true;
340 } else {
341 gamestate->wresign = true;
342 }
343 return 1;
344 case NETCODE_CONNLOST:
345 gamestate->ragequit = true;
346 return 1;
347 case NETCODE_ERROR:
348 printw("\rCannot perform asynchronous network IO");
349 cbreak(); getch();
350 exit(EXIT_FAILURE);
351 case NETCODE_AGAIN:
352 /* try again */
353 break;
354 default:
355 printw("\nThe opponent sent an invalid network pacakge.");
356 }
357
358 /* read move */
359 if (use_premove || asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) {
360 bool was_premove = use_premove;
361 use_premove = false;
362 if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) {
363 if (mycolor == WHITE) {
364 gamestate->wresign = true;
365 } else {
366 gamestate->bresign = true;
367 }
368 net_send_code(opponent, NETCODE_RESIGN);
369 return 1;
370 } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) {
371 save_pgn(gamestate, gameinfo);
372 } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
373 if (remis_suggested) {
374 net_send_code(opponent, NETCODE_REMIS);
375 gamestate->remis = true;
376 return 1;
377 } if (!remis_rejected) {
378 net_send_code(opponent, NETCODE_REMIS);
379 printw("Remis offer sent - waiting for acceptance...");
380 refresh();
381 code = net_recieve_code(opponent);
382 if (code == NETCODE_ACCEPT) {
383 gamestate->remis = true;
384 return 1;
385 } else if (code == NETCODE_CONNLOST) {
386 gamestate->ragequit = true;
387 return 1;
388 } else {
389 remis_rejected = true;
390 }
391 }
392 } else if (movestr[0] == 0) {
393 /* ignore empty move strings and ask again */
394 } else {
395 Move move;
396 int eval_result = eval_move(gamestate, movestr, &move, mycolor);
397 switch (eval_result) {
398 case VALID_MOVE_SYNTAX:
399 net_send_data(opponent, NETCODE_MOVE, &move, sizeof(Move));
400 code = net_recieve_code(opponent);
401 move.check = code == NETCODE_CHECK ||
402 code == NETCODE_CHECKMATE;
403 gamestate->checkmate = code == NETCODE_CHECKMATE;
404 gamestate->stalemate = code == NETCODE_STALEMATE;
405 if (code == NETCODE_DECLINE) {
406 uint32_t reason;
407 net_recieve_data(opponent, &reason, sizeof(uint32_t));
408 reason = ntohl(reason);
409 eval_move_failed_msg(reason);
410 } else if (code == NETCODE_ACCEPT
411 || code == NETCODE_CHECK
412 || code == NETCODE_CHECKMATE
413 || code == NETCODE_STALEMATE) {
414 apply_move(gamestate, &move);
415 if (gamestate->checkmate || gamestate->stalemate) {
416 return 1;
417 } else {
418 return 0;
419 }
420 } else if (code == NETCODE_CONNLOST) {
421 printw("Your opponent left the game.");
422 return 1;
423 } else {
424 printw("Invalid network response.");
425 }
426 break;
427 default:
428 if (was_premove) {
429 printw("\nThe prepared move could not be executed.");
430 } else {
431 eval_move_failed_msg(eval_result);
432 }
433 }
434 clrtoeol();
435 }
436 }
437 }
438 }
439
440 static int recvmove(GameState *gamestate, GameInfo *gameinfo,
441 int opponent, uint8_t mycolor) {
442 memset(gamestate->premove, 0, sizeof(gamestate->premove));
443
444 size_t bufpos = 0;
445 char movestr[MOVESTR_BUFLEN];
446 bool remis_suggested = false, resign_suggested = false;
447 while (1) {
448 timecontrol(gamestate, gameinfo);
449
450 move(inputy, 0);
451 printw("Waiting for opponent. Use chess notation to prepare a move.\n");
452 if (*gamestate->premove) {
453 printw("Current pre-move: %s \n\n",
454 gamestate->premove);
455 } else if (remis_suggested && !resign_suggested) {
456 printw("Suggested remis. \n\n");
457 } else if (resign_suggested) {
458 if (remis_suggested) {
459 printw("Suggested to resign or at least to accept remis. \n\n");
460 } else {
461 printw("Suggested to resign. \n\n");
462 }
463 } else {
464 printw("Or use a command: remis, resign, taunt, savepgn \n\n");
465 }
466 printw("Prepare your next move: ");
467 clrtoeol();
468 refresh();
469
470 /* allow the player to prepare a move */
471 if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) {
472 if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) {
473 if (mycolor == WHITE) {
474 gamestate->wresign = true;
475 } else {
476 gamestate->bresign = true;
477 }
478 net_send_code(opponent, NETCODE_RESIGN);
479 return 1;
480 } else if (strncmp(movestr, "taunt", MOVESTR_BUFLEN) == 0) {
481 resign_suggested = true;
482 net_send_code(opponent, NETCODE_TAUNT);
483 } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
484 remis_suggested = true;
485 net_send_code(opponent, NETCODE_REMIS);
486 } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) {
487 save_pgn(gamestate, gameinfo);
488 } else if (movestr[0] == 0) {
489 memset(gamestate->premove, 0, sizeof(gamestate->premove));
490 } else {
491 int res = check_move(movestr, mycolor);
492 if (res == VALID_MOVE_SYNTAX) {
493 strncpy(gamestate->premove, movestr, 8);
494 memset(movestr, 0, MOVESTR_BUFLEN);
495 bufpos = 0;
496 clrtobot();
497 } else {
498 eval_move_failed_msg(res);
499 }
500 }
501 }
502
503 /* read opponent's move */
504 uint8_t code = net_recieve_code_async(opponent);
505 switch (code) {
506 case NETCODE_TIMEOVER:
507 /* redraw the time control */
508 timecontrol(gamestate, gameinfo);
509 return 1;
510 case NETCODE_RESIGN:
511 if (mycolor == WHITE) {
512 gamestate->bresign = true;
513 } else {
514 gamestate->wresign = true;
515 }
516 return 1;
517 case NETCODE_CONNLOST:
518 gamestate->ragequit = true;
519 return 1;
520 case NETCODE_REMIS:
521 if (remis_suggested) {
522 gamestate->remis = true;
523 return 1;
524 } else {
525 if (prompt_yesno(
526 "\rYour opponent offers remis - do you accept")) {
527 gamestate->remis = true;
528 net_send_code(opponent, NETCODE_ACCEPT);
529 return 1;
530 } else {
531 net_send_code(opponent, NETCODE_DECLINE);
532 }
533 }
534 break;
535 case NETCODE_MOVE: {
536 Move move;
537 net_recieve_data(opponent, &move, sizeof(Move));
538 code = validate_move(gamestate, &move);
539 if (code == VALID_MOVE_SEMANTICS) {
540 apply_move(gamestate, &move);
541 if (gamestate->checkmate) {
542 net_send_code(opponent, NETCODE_CHECKMATE);
543 return 1;
544 } else if (gamestate->stalemate) {
545 net_send_code(opponent, NETCODE_STALEMATE);
546 return 1;
547 } else if (move.check) {
548 net_send_code(opponent, NETCODE_CHECK);
549 } else {
550 net_send_code(opponent, NETCODE_ACCEPT);
551 }
552 return 0;
553 } else {
554 uint32_t reason = htonl(code);
555 net_send_data(opponent, NETCODE_DECLINE,
556 &reason, sizeof(uint32_t));
557 }
558 break;
559 }
560 case NETCODE_ERROR:
561 printw("\rCannot perform asynchronous network IO");
562 cbreak(); getch();
563 exit(EXIT_FAILURE);
564 case NETCODE_AGAIN:
565 /* try again */
566 break;
567 default:
568 printw("\nInvalid network request.");
569 }
570 }
571 }
572
573 void game_review(Settings* settings, GameState *gamestate) {
574 const unsigned page_moves = 10;
575 GameInfo *gameinfo = &(settings->gameinfo);
576 GameState viewedstate = {0};
577 unsigned viewedmove = gamestate->movecount;
578 bool redraw = true;
579
580 noecho();
581 int c;
582 do {
583 if (redraw) {
584 gamestate_cleanup(&viewedstate);
585 gamestate_at_move(gamestate, viewedmove, &viewedstate);
586
587 erase(); /* don't use clear() to avoid flickering */
588 draw_board(&viewedstate, WHITE, settings->unicode);
589 timecontrol(&viewedstate, gameinfo);
590
591 move(getmaxy(stdscr)-5, 0);
592 if (gamestate->wresign) {
593 addstr("White resigned.\n");
594 } else if (gamestate->bresign) {
595 addstr("Black resigned.\n");
596 } else if (gamestate->remis) {
597 addstr("The game ended remis.\n");
598 } else if (gamestate->stalemate) {
599 addstr("The game ended in a stalemate.\n");
600 } else if (gamestate->checkmate) {
601 printw("%s was checkmated.\n",
602 gamestate->movecount % 2 == 0 ? "White" : "Black");
603 } else if (gamestate->ragequit) {
604 printw("Your opponent disconnected.\n");
605 }
606 addstr("\nPress 'q' to quit, 's' to save the position as PGN, or\n"
607 "arrow keys, home/end, page up/down to review the game.\n");
608 flushinp();
609 redraw = false;
610 }
611 c = getch();
612 if (c == 's') {
613 addch('\r');
614 echo();
615 save_pgn(&viewedstate, gameinfo);
616 noecho();
617 redraw = true;
618 } else if (c == KEY_UP || c == KEY_LEFT) {
619 if (viewedmove > 0) {
620 viewedmove--;
621 redraw = true;
622 }
623 } else if (c == KEY_DOWN || c == KEY_RIGHT) {
624 if (viewedmove < gamestate->movecount) {
625 viewedmove++;
626 redraw = true;
627 }
628 } else if (c == KEY_HOME) {
629 viewedmove = 0;
630 redraw = true;
631 } else if (c == KEY_END) {
632 viewedmove = gamestate->movecount;
633 redraw = true;
634 } else if (c == KEY_PPAGE) {
635 if (viewedmove > page_moves) {
636 viewedmove -= page_moves;
637 } else {
638 viewedmove = 0;
639 }
640 redraw = true;
641 } else if (c == KEY_NPAGE) {
642 viewedmove += page_moves;
643 if (viewedmove > gamestate->movecount) {
644 viewedmove = gamestate->movecount;
645 }
646 redraw = true;
647 }
648 } while (c != 'q');
649 echo();
650 gamestate_cleanup(&viewedstate);
651 }
652
653 void game_play_singlemachine(Settings *settings) {
654 inputy = getmaxy(stdscr) - 6;
655
656 GameState gamestate;
657 gamestate_init(&gamestate);
658 uint8_t curcol = WHITE;
659
660 if (settings->continuepgn) {
661 FILE *pgnfile = fopen(settings->continuepgn, "r");
662 if (pgnfile) {
663 int result = read_pgn(pgnfile, &gamestate, &(settings->gameinfo));
664 long position = ftell(pgnfile);
665 fclose(pgnfile);
666 if (result) {
667 printw("Invalid PGN file content at position %ld:\n%s\n",
668 position, pgn_error_str(result));
669 return;
670 }
671 if (!is_game_running(&gamestate)) {
672 addstr("Game has ended. Use -S to analyze it.\n");
673 return;
674 }
675 curcol = opponent_color(last_move(&gamestate).piece&COLOR_MASK);
676 } else {
677 printw("Can't read PGN file (%s)\n", strerror(errno));
678 return;
679 }
680 }
681
682 bool running;
683 do {
684 clear();
685 uint8_t perspective = settings->disableflip ? WHITE : curcol;
686 draw_board(&gamestate, perspective, settings->unicode);
687 running = !domove_singlemachine(&gamestate,
688 &(settings->gameinfo), curcol);
689 curcol = opponent_color(curcol);
690 } while (running);
691
692 game_review(settings, &gamestate);
693 gamestate_cleanup(&gamestate);
694 }
695
696 void game_play(Settings *settings, GameState *gamestate, int opponent) {
697 inputy = getmaxy(stdscr) - 6;
698
699 uint8_t mycolor = settings->gameinfo.servercolor;
700 if (!settings->ishost) {
701 mycolor = opponent_color(mycolor);
702 }
703
704 bool myturn = (gamestate->movecount > 0 ?
705 (last_move(gamestate).piece & COLOR_MASK) : BLACK) != mycolor;
706
707 bool running;
708 do {
709 clear();
710 draw_board(gamestate, mycolor, settings->unicode);
711 if (myturn) {
712 running = !sendmove(gamestate, &(settings->gameinfo),
713 opponent, mycolor);
714 } else {
715 running = !recvmove(gamestate, &(settings->gameinfo),
716 opponent, mycolor);
717 }
718 myturn ^= true;
719 } while (running);
720 }
721
722 void dump_gameinfo(GameInfo *gameinfo) {
723 int serverwhite = gameinfo->servercolor == WHITE;
724 attron(A_UNDERLINE);
725 printw("Game details\n");
726 attroff(A_UNDERLINE);
727 printw(" Server: %s\n Client: %s\n",
728 serverwhite?"White":"Black", serverwhite?"Black":"White"
729 );
730 if (gameinfo->timecontrol) {
731 if (gameinfo->time % 60) {
732 printw(" Time limit: %ds + %ds\n",
733 gameinfo->time, gameinfo->addtime);
734 } else {
735 printw(" Time limit: %dm + %ds\n",
736 gameinfo->time/60, gameinfo->addtime);
737 }
738 } else {
739 printw(" No time limit\n");
740 }
741 refresh();
742 }
743
744 void dump_moveinfo(GameState *gamestate) {
745 for (unsigned i = 0 ; i < gamestate->movecount ; i++) {
746 if (i % 2 == 0) {
747 printw("%d. %s", 1+i/2, gamestate->moves[i].string);
748 } else {
749 printw("%s", gamestate->moves[i].string);
750 }
751 // only five moves reliably fit into one screen row
752 if ((i+1) % 10) {
753 addch(' ');
754 } else {
755 addch('\n');
756 }
757 }
758 refresh();
759 }

mercurial