Tue, 18 Sep 2018 15:02:08 +0200
adds unicode support
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 */
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>
38 #include <stdio.h>
39 #include <errno.h>
41 static const uint8_t boardx = 4, boardy = 10;
42 static int inputy = 21; /* should be overridden on game startup */
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 mvprintw(boardy+4, boardx-1,
49 "White time: %4" PRIu16 ":%02" PRIu16,
50 white / 60, white % 60);
51 mvprintw(boardy+5, boardx-1,
52 "Black time: %4" PRIu16 ":%02" PRIu16,
53 black / 60, black % 60);
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 }
71 return 0;
72 }
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);
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] & PIECE_MASK;
85 unsigned char piecestr[5];
86 if (piece) {
87 if (unicode) {
88 unsigned char* uc = getpieceunicode(piece);
89 strncpy(piecestr, uc, 5);
90 } else {
91 piecestr[0] = piece == PAWN ? 'P' : getpiecechr(piece);
92 piecestr[1] = '\0';
93 }
94 } else {
95 piecestr[0] = ' ';
96 piecestr[1] = '\0';
97 }
99 _Bool boardblack = (y&1)==(x&1);
100 attrset((col==WHITE ? A_BOLD : A_DIM)|
101 COLOR_PAIR(col == WHITE ?
102 (boardblack ? COL_WB : COL_WW) :
103 (boardblack ? COL_BB : COL_BW)
104 )
105 );
107 int cy = perspective == WHITE ? boardy-y : boardy-7+y;
108 int cx = perspective == WHITE ? boardx+x*3 : boardx+21-x*3;
109 mvprintw(cy, cx, " %s ", piecestr);
110 }
111 }
113 attrset(A_NORMAL);
114 for (uint8_t i = 0 ; i < 8 ; i++) {
115 int x = perspective == WHITE ? boardx+i*3+1 : boardx+22-i*3;
116 int y = perspective == WHITE ? boardy-i : boardy-7+i;
117 mvaddch(boardy+1, x, 'a'+i);
118 mvaddch(y, boardx-2, '1'+i);
119 }
121 /* move log */
122 uint8_t logy = 2;
123 const uint8_t logx = boardx + 28;
124 move(logy, logx);
126 unsigned int logi = 0;
127 MoveList *logelem = gamestate->movelist;
129 /* wrap log after 45 moves */
130 while (gamestate->movecount/6-logi/3 >= 15) {
131 logelem = logelem->next->next;
132 logi++;
133 }
135 while (logelem) {
136 _Bool iswhite = (logelem->move.piece & COLOR_MASK) == WHITE;
137 if (iswhite) {
138 logi++;
139 printw("%d. ", logi);
140 }
142 addstr(logelem->move.string);
143 if (!iswhite && logi%3 == 0) {
144 move(++logy, logx);
145 } else {
146 addch(' ');
147 }
149 logelem = logelem->next;
150 }
151 }
153 static void eval_move_failed_msg(int code) {
154 switch (code) {
155 case AMBIGUOUS_MOVE:
156 printw("Ambiguous move - please specify the piece to move.");
157 break;
158 case INVALID_POSITION:
159 printw("No piece can be moved this way.");
160 break;
161 case NEED_PROMOTION:
162 printw("You need to promote the pawn (append \"=Q\" e.g.)!");
163 break;
164 case KING_IN_CHECK:
165 printw("Your king is in check!");
166 break;
167 case PIECE_PINNED:
168 printw("This piece is pinned!");
169 break;
170 case INVALID_MOVE_SYNTAX:
171 printw("Can't interpret move - please use algebraic notation.");
172 break;
173 case RULES_VIOLATED:
174 printw("Move does not comply chess rules.");
175 break;
176 case KING_MOVES_INTO_CHECK:
177 printw("Can't move the king into a check position.");
178 break;
179 default:
180 printw("Unknown move parser error.");
181 }
182 }
184 static void save_pgn(GameState *gamestate, GameInfo *gameinfo) {
185 printw("Filename: ");
186 clrtoeol();
187 refresh();
189 char filename[64];
190 int y = getcury(stdscr);
191 if (getnstr(filename, 64) == OK && filename[0] != '\0') {
192 move(y, 0);
193 FILE *file = fopen(filename, "w");
194 if (file) {
195 write_pgn(file, gamestate, gameinfo);
196 fclose(file);
197 printw("File saved.");
198 } else {
199 printw("Can't write to file (%s).", strerror(errno));
200 }
201 clrtoeol();
202 }
203 }
205 #define MOVESTR_BUFLEN 10
206 static int domove_singlemachine(GameState *gamestate,
207 GameInfo *gameinfo, uint8_t curcolor) {
210 size_t bufpos = 0;
211 char movestr[MOVESTR_BUFLEN];
213 flushinp();
214 while (1) {
215 if (timecontrol(gamestate, gameinfo)) {
216 return 1;
217 }
219 move(inputy, 0);
220 printw(
221 "Use chess notation to enter your move.\n"
222 "Or use a command: remis, resign, savepgn\n\n"
223 "Type your move: ");
224 clrtoeol();
226 if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) {
227 if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) {
228 gamestate->resign = 1;
229 printw("%s resigned!",
230 curcolor==WHITE?"White":"Black");
231 clrtobot();
232 refresh();
233 return 1;
234 } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
235 gamestate->remis = 1;
236 printw("Game ends remis.");
237 clrtobot();
238 refresh();
239 return 1;
240 } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) {
241 save_pgn(gamestate, gameinfo);
242 } else {
243 Move move;
244 int result = eval_move(gamestate, movestr, &move, curcolor);
245 if (result == VALID_MOVE_SYNTAX) {
246 result = validate_move(gamestate, &move);
247 if (result == VALID_MOVE_SEMANTICS) {
248 apply_move(gamestate, &move);
249 if (gamestate->checkmate) {
250 printw("Checkmate!");
251 clrtoeol();
252 return 1;
253 } else if (gamestate->stalemate) {
254 printw("Stalemate!");
255 clrtoeol();
256 return 1;
257 } else {
258 return 0;
259 }
260 } else {
261 eval_move_failed_msg(result);
262 }
263 } else {
264 eval_move_failed_msg(result);
265 }
266 clrtoeol();
267 }
268 }
269 }
270 }
272 static int sendmove(GameState *gamestate, GameInfo *gameinfo,
273 int opponent, uint8_t mycolor) {
275 size_t bufpos = 0;
276 char movestr[MOVESTR_BUFLEN];
277 _Bool remisrejected = FALSE;
278 uint8_t code;
280 flushinp();
281 while (1) {
282 if (timecontrol(gamestate, gameinfo)) {
283 net_send_code(opponent, NETCODE_TIMEOVER);
284 return 1;
285 }
287 move(inputy, 0);
288 if (remisrejected) {
289 printw(
290 "Use chess notation to enter your move.\n"
291 "Remis offer rejected \n\n"
292 "Type your move: ");
293 } else {
294 printw(
295 "Use chess notation to enter your move.\n"
296 "Or use a command: remis, resign, savepgn\n\n"
297 "Type your move: ");
298 }
299 clrtoeol();
301 if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) {
302 if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) {
303 gamestate->resign = 1;
304 printw("You resigned!");
305 clrtoeol();
306 refresh();
307 net_send_code(opponent, NETCODE_RESIGN);
308 return 1;
309 } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) {
310 save_pgn(gamestate, gameinfo);
311 } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
312 if (!remisrejected) {
313 net_send_code(opponent, NETCODE_REMIS);
314 printw("Remis offer sent - waiting for acceptance...");
315 refresh();
316 code = net_recieve_code(opponent);
317 if (code == NETCODE_ACCEPT) {
318 gamestate->remis = 1;
319 printw("\rRemis accepted!");
320 clrtoeol();
321 refresh();
322 return 1;
323 } else if (code == NETCODE_CONNLOST) {
324 printw("\rYour opponent left the game.");
325 clrtoeol();
326 refresh();
327 return 1;
328 } else {
329 remisrejected = TRUE;
330 }
331 }
332 } else {
333 Move move;
334 int eval_result = eval_move(gamestate, movestr, &move, mycolor);
335 switch (eval_result) {
336 case VALID_MOVE_SYNTAX:
337 net_send_data(opponent, NETCODE_MOVE, &move, sizeof(Move));
338 code = net_recieve_code(opponent);
339 move.check = code == NETCODE_CHECK ||
340 code == NETCODE_CHECKMATE;
341 gamestate->checkmate = code == NETCODE_CHECKMATE;
342 gamestate->stalemate = code == NETCODE_STALEMATE;
343 if (code == NETCODE_DECLINE) {
344 uint32_t reason;
345 net_recieve_data(opponent, &reason, sizeof(uint32_t));
346 reason = ntohl(reason);
347 eval_move_failed_msg(reason);
348 } else if (code == NETCODE_ACCEPT
349 || code == NETCODE_CHECK
350 || code == NETCODE_CHECKMATE
351 || code == NETCODE_STALEMATE) {
352 apply_move(gamestate, &move);
353 if (gamestate->checkmate) {
354 printw("Checkmate!");
355 clrtoeol();
356 return 1;
357 } else if (gamestate->stalemate) {
358 printw("Stalemate!");
359 clrtoeol();
360 return 1;
361 } else {
362 return 0;
363 }
364 } else if (code == NETCODE_CONNLOST) {
365 printw("Your opponent left the game.");
366 return 1;
367 } else {
368 printw("Invalid network response.");
369 }
370 break;
371 default:
372 eval_move_failed_msg(eval_result);
373 }
374 clrtoeol();
375 }
376 }
377 }
378 }
380 static int recvmove(GameState *gamestate, GameInfo *gameinfo, int opponent) {
382 struct timeval timeout;
383 while (1) {
384 timecontrol(gamestate, gameinfo);
386 move(inputy, 0);
387 printw("Awaiting opponent move...");
388 clrtoeol();
389 refresh();
391 fd_set readfds;
393 FD_ZERO(&readfds);
394 FD_SET(opponent, &readfds);
395 timeout.tv_sec = 0;
396 timeout.tv_usec = 1e5;
398 // TODO: allow commands
400 int result = select(opponent+1, &readfds, NULL, NULL, &timeout);
401 if (result == -1) {
402 printw("\rCannot perform asynchronous network IO");
403 cbreak(); getch();
404 exit(EXIT_FAILURE);
405 }
406 if (result > 0) {
407 uint8_t code = net_recieve_code(opponent);
409 Move move;
410 switch (code) {
411 case NETCODE_TIMEOVER:
412 printw("\rYour opponent's time ran out - you win!");
413 clrtoeol();
414 return 1;
415 case NETCODE_RESIGN:
416 gamestate->resign = 1;
417 printw("\rYour opponent resigned!");
418 clrtoeol();
419 return 1;
420 case NETCODE_CONNLOST:
421 printw("\rYour opponent has left the game.");
422 clrtoeol();
423 return 1;
424 case NETCODE_REMIS:
425 if (prompt_yesno(
426 "\rYour opponent offers remis - do you accept")) {
427 gamestate->remis = 1;
428 printw("\rRemis accepted!");
429 clrtoeol();
430 net_send_code(opponent, NETCODE_ACCEPT);
431 return 1;
432 } else {
433 net_send_code(opponent, NETCODE_DECLINE);
434 }
435 break;
436 case NETCODE_MOVE:
437 net_recieve_data(opponent, &move, sizeof(Move));
438 code = validate_move(gamestate, &move);
439 if (code == VALID_MOVE_SEMANTICS) {
440 apply_move(gamestate, &move);
441 if (gamestate->checkmate) {
442 net_send_code(opponent, NETCODE_CHECKMATE);
443 printw("\rCheckmate!");
444 clrtoeol();
445 return 1;
446 } else if (gamestate->stalemate) {
447 net_send_code(opponent, NETCODE_STALEMATE);
448 printw("\rStalemate!");
449 clrtoeol();
450 return 1;
451 } else if (move.check) {
452 net_send_code(opponent, NETCODE_CHECK);
453 } else {
454 net_send_code(opponent, NETCODE_ACCEPT);
455 }
456 return 0;
457 } else {
458 uint32_t reason = htonl(code);
459 net_send_data(opponent, NETCODE_DECLINE,
460 &reason, sizeof(uint32_t));
461 }
462 break;
463 default:
464 printw("\nInvalid network request.");
465 }
466 }
467 }
468 }
470 static void post_game(Settings* settings, GameState *gamestate) {
471 GameInfo *gameinfo = &(settings->gameinfo);
473 move(0,0);
474 draw_board(gamestate, WHITE, settings->unicode);
476 // TODO: network connection is still open here - think about it!
478 mvaddstr(getmaxy(stdscr)-1, 0,
479 "Press 'q' to quit or 's' to save a PGN file...");
480 refresh();
481 flushinp();
483 noecho();
484 int c;
485 do {
486 c = getch();
487 if (c == 's') {
488 addch('\r');
489 echo();
490 save_pgn(gamestate, gameinfo);
491 addstr(" Press 'q' to quit...");
492 noecho();
493 }
494 } while (c != 'q');
495 echo();
497 gamestate_cleanup(gamestate);
498 }
500 void game_start_singlemachine(Settings *settings) {
501 inputy = getmaxy(stdscr) - 6;
503 GameState gamestate;
504 gamestate_init(&gamestate);
505 uint8_t curcol = WHITE;
507 if (settings->continuepgn) {
508 FILE *pgnfile = fopen(settings->continuepgn, "r");
509 if (pgnfile) {
510 int result = read_pgn(pgnfile, &gamestate, &(settings->gameinfo));
511 long position = ftell(pgnfile);
512 fclose(pgnfile);
513 if (result) {
514 printw("Invalid PGN file content at position %ld:\n%s\n",
515 position, pgn_error_str(result));
516 return;
517 }
518 if (!is_game_running(&gamestate)) {
519 addstr("Game has ended. Use -S to analyze it.\n");
520 return;
521 }
522 curcol = opponent_color(gamestate.lastmove->move.piece&COLOR_MASK);
523 } else {
524 printw("Can't read PGN file (%s)\n", strerror(errno));
525 return;
526 }
527 }
529 _Bool running;
530 do {
531 clear();
532 draw_board(&gamestate, curcol, settings->unicode);
533 running = !domove_singlemachine(&gamestate,
534 &(settings->gameinfo), curcol);
535 curcol = opponent_color(curcol);
536 } while (running);
538 post_game(settings, &gamestate);
539 }
541 void game_continue(Settings *settings, int opponent, GameState *gamestate) {
542 inputy = getmaxy(stdscr) - 6;
544 uint8_t mycolor = is_server(settings) ? settings->gameinfo.servercolor :
545 opponent_color(settings->gameinfo.servercolor);
547 _Bool myturn = (gamestate->lastmove ?
548 (gamestate->lastmove->move.piece & COLOR_MASK) : BLACK) != mycolor;
550 _Bool running;
551 do {
552 clear();
553 draw_board(gamestate, mycolor, settings->unicode);
554 if (myturn) {
555 running = !sendmove(gamestate, &(settings->gameinfo),
556 opponent, mycolor);
557 } else {
558 running = !recvmove(gamestate, &(settings->gameinfo), opponent);
559 }
560 myturn ^= TRUE;
561 } while (running);
563 post_game(settings, gamestate);
564 }
566 void game_start(Settings *settings, int opponent) {
567 GameState gamestate;
568 gamestate_init(&gamestate);
570 game_continue(settings, opponent, &gamestate);
571 }