Wed, 11 Jun 2014 16:54:20 +0200
logging string representation of moves in short algebraic notation
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 addstr(logelem->move.string);
127 if (!logelem->next) {
128 if (gamestate->stalemate) {
129 addstr(" stalemate");
130 }
131 }
132 addch(' ');
134 logelem = logelem->next;
135 }
136 }
137 }
139 static void eval_move_failed_msg(int code) {
140 switch (code) {
141 case AMBIGUOUS_MOVE:
142 printw("Ambiguous move - please specify the piece to move.");
143 break;
144 case INVALID_POSITION:
145 printw("No piece can be moved this way.");
146 break;
147 case NEED_PROMOTION:
148 printw("You need to promote the pawn (append \"=Q\" e.g.)!");
149 break;
150 case KING_IN_CHECK:
151 printw("Your king is in check!");
152 break;
153 case PIECE_PINNED:
154 printw("This piece is pinned!");
155 break;
156 case INVALID_MOVE_SYNTAX:
157 printw("Can't interpret move - please use algebraic notation.");
158 break;
159 case RULES_VIOLATED:
160 printw("Move does not comply chess rules.");
161 break;
162 case KING_MOVES_INTO_CHECK:
163 printw("Can't move the king into a check position.");
164 break;
165 default:
166 printw("Unknown move parser error.");
167 }
168 }
170 #define MOVESTR_BUFLEN 8
171 static int domove_singlemachine(GameState *gamestate, GameInfo *gameinfo) {
174 size_t bufpos = 0;
175 char movestr[MOVESTR_BUFLEN];
177 flushinp();
178 while (1) {
179 if (timecontrol(gamestate, gameinfo)) {
180 return 1;
181 }
183 move(inputy, 0);
184 printw(
185 "Use chess notation to enter your move.\n"
186 "Or type 'resign' to resign or 'remis' to end with remis.\n\n"
187 "Type your move: ");
188 clrtoeol();
190 if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) {
191 if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) {
192 printw("%s resigned!",
193 gamestate->mycolor==WHITE?"White":"Black");
194 clrtoeol();
195 refresh();
196 return 1;
197 } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
198 printw("Game ends remis.");
199 clrtoeol();
200 refresh();
201 return 1;
202 } else {
203 Move move;
204 int eval_result = eval_move(gamestate, movestr, &move);
205 switch (eval_result) {
206 case VALID_MOVE_SYNTAX:
207 eval_result = validate_move(gamestate, &move);
208 if (eval_result == VALID_MOVE_SEMANTICS) {
209 apply_move(gamestate, &move);
210 if (gamestate->checkmate) {
211 printw("Checkmate!");
212 clrtoeol();
213 return 1;
214 } else if (gamestate->stalemate) {
215 printw("Stalemate!");
216 clrtoeol();
217 return 1;
218 } else {
219 return 0;
220 }
221 } else {
222 eval_move_failed_msg(eval_result);
223 }
224 break;
225 default:
226 eval_move_failed_msg(eval_result);
227 }
228 clrtoeol();
229 }
230 }
231 }
232 }
234 static int sendmove(GameState *gamestate, GameInfo *gameinfo, int opponent) {
236 size_t bufpos = 0;
237 char movestr[MOVESTR_BUFLEN];
238 _Bool remisrejected = FALSE;
239 uint8_t code;
241 flushinp();
242 while (1) {
243 if (timecontrol(gamestate, gameinfo)) {
244 net_send_code(opponent, NETCODE_TIMEOVER);
245 return 1;
246 }
248 move(inputy, 0);
249 if (remisrejected) {
250 printw(
251 "Use chess notation to enter your move.\n"
252 "Remis offer rejected - type 'resign' to resign. \n\n"
253 "Type your move: ");
254 } else {
255 printw(
256 "Use chess notation to enter your move.\n"
257 "Or type 'resign' to resign or 'remis' to offer remis.\n\n"
258 "Type your move: ");
259 }
260 clrtoeol();
262 if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) {
263 if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) {
264 printw("You resigned!");
265 clrtoeol();
266 refresh();
267 net_send_code(opponent, NETCODE_RESIGN);
268 return 1;
269 } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
270 if (!remisrejected) {
271 net_send_code(opponent, NETCODE_REMIS);
272 printw("Remis offer sent - waiting for acceptance...");
273 refresh();
274 code = net_recieve_code(opponent);
275 if (code == NETCODE_ACCEPT) {
276 printw("\rRemis accepted!");
277 clrtoeol();
278 refresh();
279 return 1;
280 } else if (code == NETCODE_CONNLOST) {
281 printw("\rYour opponent left the game.");
282 clrtoeol();
283 refresh();
284 return 1;
285 } else {
286 remisrejected = TRUE;
287 }
288 }
289 } else {
290 Move move;
291 int eval_result = eval_move(gamestate, movestr, &move);
292 switch (eval_result) {
293 case VALID_MOVE_SYNTAX:
294 net_send_data(opponent, NETCODE_MOVE, &move,
295 sizeof(Move)-8);
296 code = net_recieve_code(opponent);
297 move.check = code == NETCODE_CHECK ||
298 code == NETCODE_CHECKMATE;
299 gamestate->checkmate = code == NETCODE_CHECKMATE;
300 gamestate->stalemate = code == NETCODE_STALEMATE;
301 if (code == NETCODE_DECLINE) {
302 uint32_t reason;
303 net_recieve_data(opponent, &reason, sizeof(uint32_t));
304 reason = ntohl(reason);
305 eval_move_failed_msg(reason);
306 } else if (code == NETCODE_ACCEPT
307 || code == NETCODE_CHECK
308 || code == NETCODE_CHECKMATE
309 || code == NETCODE_STALEMATE) {
310 apply_move(gamestate, &move);
311 if (gamestate->checkmate) {
312 printw("Checkmate!");
313 clrtoeol();
314 return 1;
315 } else if (gamestate->stalemate) {
316 printw("Stalemate!");
317 clrtoeol();
318 return 1;
319 } else {
320 return 0;
321 }
322 } else if (code == NETCODE_CONNLOST) {
323 printw("Your opponent left the game.");
324 return 1;
325 } else {
326 printw("Invalid network response.");
327 }
328 break;
329 default:
330 eval_move_failed_msg(eval_result);
331 }
332 clrtoeol();
333 }
334 }
335 }
336 }
338 static int recvmove(GameState *gamestate, GameInfo *gameinfo, int opponent) {
340 struct timeval timeout;
341 while (1) {
342 timecontrol(gamestate, gameinfo);
344 move(inputy, 0);
345 printw("Awaiting opponent move...");
346 clrtoeol();
347 refresh();
349 fd_set readfds;
351 FD_ZERO(&readfds);
352 FD_SET(opponent, &readfds);
353 timeout.tv_sec = 0;
354 timeout.tv_usec = 1e5;
356 int result = select(opponent+1, &readfds, NULL, NULL, &timeout);
357 if (result == -1) {
358 printw("\rCannot perform asynchronous network IO");
359 cbreak(); getch();
360 exit(EXIT_FAILURE);
361 }
362 if (result > 0) {
363 uint8_t code = net_recieve_code(opponent);
365 Move move;
366 switch (code) {
367 case NETCODE_TIMEOVER:
368 printw("\rYour opponent's time ran out - you win!");
369 clrtoeol();
370 return 1;
371 case NETCODE_RESIGN:
372 printw("\rYour opponent resigned!");
373 clrtoeol();
374 return 1;
375 case NETCODE_CONNLOST:
376 printw("\rYour opponent has left the game.");
377 clrtoeol();
378 return 1;
379 case NETCODE_REMIS:
380 if (prompt_yesno(
381 "\rYour opponent offers remis - do you accept")) {
382 printw("\rRemis accepted!");
383 clrtoeol();
384 net_send_code(opponent, NETCODE_ACCEPT);
385 return 1;
386 } else {
387 net_send_code(opponent, NETCODE_DECLINE);
388 }
389 break;
390 case NETCODE_MOVE:
391 net_recieve_data(opponent, &move, sizeof(Move)-8);
392 code = validate_move(gamestate, &move);
393 if (code == VALID_MOVE_SEMANTICS) {
394 apply_move(gamestate, &move);
395 if (move.check) {
396 net_send_code(opponent, NETCODE_CHECK);
397 } else if (gamestate->checkmate) {
398 net_send_code(opponent, NETCODE_CHECKMATE);
399 printw("\rCheckmate!");
400 clrtoeol();
401 return 1;
402 } else if (gamestate->stalemate) {
403 net_send_code(opponent, NETCODE_STALEMATE);
404 printw("\rStalemate!");
405 clrtoeol();
406 return 1;
407 } else {
408 net_send_code(opponent, NETCODE_ACCEPT);
409 }
410 return 0;
411 } else {
412 uint32_t reason = htonl(code);
413 net_send_data(opponent, NETCODE_DECLINE,
414 &reason, sizeof(uint32_t));
415 }
416 break;
417 default:
418 printw("\nInvalid network request.");
419 }
420 }
421 }
422 }
424 static void init_board(GameState *gamestate) {
425 Board initboard = {
426 {WROOK, WKNIGHT, WBISHOP, WQUEEN, WKING, WBISHOP, WKNIGHT, WROOK},
427 {WPAWN, WPAWN, WPAWN, WPAWN, WPAWN, WPAWN, WPAWN, WPAWN},
428 {0, 0, 0, 0, 0, 0, 0, 0},
429 {0, 0, 0, 0, 0, 0, 0, 0},
430 {0, 0, 0, 0, 0, 0, 0, 0},
431 {0, 0, 0, 0, 0, 0, 0, 0},
432 {BPAWN, BPAWN, BPAWN, BPAWN, BPAWN, BPAWN, BPAWN, BPAWN},
433 {BROOK, BKNIGHT, BBISHOP, BQUEEN, BKING, BBISHOP, BKNIGHT, BROOK}
434 };
435 memcpy(gamestate->board, initboard, sizeof(Board));
436 }
438 void game_start_singlemachine(Settings *settings) {
439 inputy = getmaxy(stdscr) - 6;
441 GameState gamestate;
442 memset(&gamestate, 0, sizeof(GameState));
443 init_board(&gamestate);
444 gamestate.mycolor = WHITE;
446 _Bool running;
447 do {
448 clear();
449 draw_board(&gamestate);
450 running = !domove_singlemachine(&gamestate, &(settings->gameinfo));
451 gamestate.mycolor = opponent_color(gamestate.mycolor);
452 } while (running);
453 move(0,0);
454 draw_board(&gamestate);
456 gamestate_cleanup(&gamestate);
457 }
459 void game_start(Settings *settings, int opponent) {
460 inputy = getmaxy(stdscr) - 6;
462 _Bool myturn = is_server(settings) ==
463 (settings->gameinfo.servercolor == WHITE);
465 GameState gamestate;
466 memset(&gamestate, 0, sizeof(GameState));
467 init_board(&gamestate);
468 gamestate.mycolor = myturn ? WHITE:BLACK;
470 _Bool running;
471 do {
472 clear();
473 draw_board(&gamestate);
474 if (myturn) {
475 running = !sendmove(&gamestate, &(settings->gameinfo), opponent);
476 } else {
477 running = !recvmove(&gamestate, &(settings->gameinfo), opponent);
478 }
479 myturn ^= TRUE;
480 } while (running);
482 move(0,0);
483 draw_board(&gamestate);
485 gamestate_cleanup(&gamestate);
486 }