Wed, 09 Apr 2014 12:07:47 +0200
added nonblocking read for network games + minor build system fixes
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 <ncurses.h>
34 #include <string.h>
35 #include <inttypes.h>
36 #include <sys/select.h>
38 static const uint8_t boardx = 10, boardy = 10;
39 static int inputy = 21; /* should be overridden on game startup */
41 static int timecontrol(GameState *gamestate, GameInfo *gameinfo) {
42 if (gameinfo->timecontrol) {
43 uint16_t white = remaining_movetime(gameinfo, gamestate, WHITE);
44 uint16_t black = remaining_movetime(gameinfo, gamestate, BLACK);
45 mvprintw(boardy+4, boardx-1,
46 "White time: %4" PRIu16 ":%02" PRIu16,
47 white / 60, white % 60);
48 mvprintw(boardy+5, boardx-1,
49 "Black time: %4" PRIu16 ":%02" PRIu16,
50 black / 60, black % 60);
52 if (white == 0) {
53 move(inputy, 0);
54 printw("Time is over - Black wins!");
55 clrtobot();
56 refresh();
57 return 1;
58 }
59 if (black == 0) {
60 move(inputy, 0);
61 printw("Time is over - White wins!");
62 clrtobot();
63 refresh();
64 return 1;
65 }
66 }
68 return 0;
69 }
71 static void draw_board(GameState *gamestate) {
72 for (uint8_t y = 0 ; y < 8 ; y++) {
73 for (uint8_t x = 0 ; x < 8 ; x++) {
74 uint8_t col = gamestate->board[y][x] & COLOR_MASK;
75 uint8_t piece = gamestate->board[y][x] & PIECE_MASK;
76 char piecec;
77 if (piece) {
78 piecec = piece == PAWN ? 'P' : getpiecechr(piece);
79 } else {
80 piecec = ' ';
81 }
83 attrset((col == WHITE ? A_BOLD : A_DIM) |
84 COLOR_PAIR((y&1)==(x&1) ? COL_WB : COL_BW));
86 int cy = gamestate->mycolor == WHITE ? boardy-y : boardy-7+y;
87 int cx = gamestate->mycolor == WHITE ? boardx+x*3 : boardx+21-x*3;
88 mvaddch(cy, cx, ' ');
89 mvaddch(cy, cx+1, piecec);
90 mvaddch(cy, cx+2, ' ');
91 }
92 }
94 attrset(A_NORMAL);
95 for (uint8_t i = 0 ; i < 8 ; i++) {
96 int x = gamestate->mycolor == WHITE ? boardx+i*3+1 : boardx+22-i*3;
97 int y = gamestate->mycolor == WHITE ? boardy-i : boardy-7+i;
98 mvaddch(boardy+1, x, 'a'+i);
99 mvaddch(y, boardx-2, '1'+i);
100 }
102 /* move log */
103 // TODO: introduce window to avoid bugs with a long move log
104 uint8_t logy = 0;
105 const uint8_t logx = boardx + 30;
106 int logi = 1;
107 MoveList *logelem = gamestate->movelist;
109 while (logelem) {
110 logi++;
111 if (logi % 2 == 0) {
112 if ((logi - 2) % 4 == 0) {
113 logy++;
114 move(logy, logx);
115 }
116 printw("%d. ", logi / 2);
117 }
119 if (logelem) {
120 Move move = logelem->move;
121 if ((move.piece&PIECE_MASK) == KING &&
122 abs(move.tofile-move.fromfile) == 2) {
123 addstr(move.tofile==fileidx('c')?"O-O-O":"O-O");
124 } else {
125 char logstr[] = {
126 getpiecechr(move.piece),
127 filechr(move.fromfile), rowchr(move.fromrow),
128 move.capture ? 'x':'\0',
129 filechr(move.tofile), rowchr(move.torow),
130 move.check ? '+' : (move.promotion ? '=' : '\0'),
131 move.promotion ? getpiecechr(move.promotion) : '\0'
132 };
133 for (int stri = 0 ; stri < sizeof(logstr) ; stri++) {
134 if (logstr[stri]) {
135 addch(logstr[stri]);
136 }
137 }
138 }
139 if (!logelem->next) {
140 if (gamestate->checkmate) {
141 addstr("\b#");
142 } else if (gamestate->stalemate) {
143 addstr(" stalemate");
144 }
145 }
146 addch(' ');
148 logelem = logelem->next;
149 }
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("Cannot find the piece that shall be moved.");
160 break;
161 case NEED_PROMOTION:
162 printw("You need to promote the pawn (append \"=Q\" e.g.)!");
163 break;
164 default:
165 printw("Can't interpret move - please use algebraic notation.");
166 }
167 }
169 static int domove_singlemachine(GameState *gamestate, GameInfo *gameinfo) {
171 const size_t buflen = 8;
172 size_t bufpos = 0;
173 char movestr[buflen];
175 flushinp();
176 while (1) {
177 if (timecontrol(gamestate, gameinfo)) {
178 return 1;
179 }
181 move(inputy, 0);
182 printw(
183 "Use chess notation to enter your move.\n"
184 "Or type 'surr' to surrender or 'remis' to end with remis.\n\n"
185 "Type your move: ");
186 clrtoeol();
188 if (asyncgetnstr(movestr, &bufpos, buflen)) {
189 if (strncmp(movestr, "surr", buflen) == 0) {
190 printw("%s surrendered!",
191 gamestate->mycolor==WHITE?"White":"Black");
192 clrtoeol();
193 refresh();
194 return 1;
195 } else if (strncmp(movestr, "remis", buflen) == 0) {
196 printw("Game ends remis.");
197 clrtoeol();
198 refresh();
199 return 1;
200 } else {
201 Move move;
202 int eval_result = eval_move(gamestate, movestr, &move);
203 switch (eval_result) {
204 case VALID_MOVE_SYNTAX:
205 if (validate_move(gamestate, &move)) {
206 apply_move(gamestate, &move);
207 if (gamestate->checkmate) {
208 printw("Checkmate!");
209 clrtoeol();
210 return 1;
211 } else if (gamestate->stalemate) {
212 printw("Stalemate!");
213 clrtoeol();
214 return 1;
215 } else {
216 return 0;
217 }
218 } else {
219 printw("Invalid move.");
220 }
221 break;
222 default:
223 eval_move_failed_msg(eval_result);
224 }
225 clrtoeol();
226 }
227 }
228 }
229 }
231 static int sendmove(GameState *gamestate, GameInfo *gameinfo, int opponent) {
233 const size_t buflen = 8;
234 size_t bufpos = 0;
235 char movestr[buflen];
236 _Bool remisrejected = FALSE;
237 uint8_t code;
239 flushinp();
240 while (1) {
241 if (timecontrol(gamestate, gameinfo)) {
242 net_send_code(opponent, NETCODE_TIMEOVER);
243 return 1;
244 }
246 move(inputy, 0);
247 if (remisrejected) {
248 printw(
249 "Use chess notation to enter your move.\n"
250 "Remis offer rejected - type 'surr' to surrender. \n\n"
251 "Type your move: ");
252 } else {
253 printw(
254 "Use chess notation to enter your move.\n"
255 "Or type 'surr' to surrender or 'remis' to offer remis.\n\n"
256 "Type your move: ");
257 }
258 clrtoeol();
260 if (asyncgetnstr(movestr, &bufpos, buflen)) {
261 if (strncmp(movestr, "surr", buflen) == 0) {
262 printw("You surrendered!");
263 clrtoeol();
264 refresh();
265 net_send_code(opponent, NETCODE_SURRENDER);
266 return 1;
267 } else if (strncmp(movestr, "remis", buflen) == 0) {
268 if (!remisrejected) {
269 net_send_code(opponent, NETCODE_REMIS);
270 printw("Remis offer sent - waiting for acceptance...");
271 refresh();
272 if (net_recieve_code(opponent) == NETCODE_ACCEPT) {
273 printw("\rRemis accepted!");
274 clrtoeol();
275 refresh();
276 return 1;
277 } else {
278 remisrejected = TRUE;
279 }
280 }
281 } else {
282 Move move;
283 int eval_result = eval_move(gamestate, movestr, &move);
284 switch (eval_result) {
285 case VALID_MOVE_SYNTAX:
286 net_send_data(opponent, NETCODE_MOVE, &move, sizeof(Move));
287 code = net_recieve_code(opponent);
288 move.check = code == NETCODE_CHECK;
289 gamestate->checkmate = code == NETCODE_CHECKMATE;
290 gamestate->stalemate = code == NETCODE_STALEMATE;
291 if (code == NETCODE_DECLINE) {
292 printw("Invalid move.");
293 } else {
294 apply_move(gamestate, &move);
295 if (gamestate->checkmate) {
296 printw("Checkmate!");
297 clrtoeol();
298 return 1;
299 } else if (gamestate->stalemate) {
300 printw("Stalemate!");
301 clrtoeol();
302 return 1;
303 } else {
304 return 0;
305 }
306 }
307 break;
308 default:
309 eval_move_failed_msg(eval_result);
310 }
311 clrtoeol();
312 }
313 }
314 }
315 }
317 static int recvmove(GameState *gamestate, GameInfo *gameinfo, int opponent) {
319 if (net_setnonblocking(opponent, 1)) {
320 printw("Cannot setup nonblocking IO on network socket");
321 cbreak(); getch();
322 exit(EXIT_FAILURE);
323 }
325 struct timeval timeout;
326 while (1) {
327 timecontrol(gamestate, gameinfo);
329 move(inputy, 0);
330 printw("Awaiting opponent move...");
331 clrtoeol();
332 refresh();
334 fd_set readfds;
336 FD_ZERO(&readfds);
337 FD_SET(opponent, &readfds);
338 timeout.tv_sec = 0;
339 timeout.tv_usec = 1e5;
341 int result = select(opponent+1, &readfds, NULL, NULL, &timeout);
342 if (result == -1) {
343 printw("\rCannot perform asynchronous network IO");
344 cbreak(); getch();
345 exit(EXIT_FAILURE);
346 }
347 if (result > 0) {
348 uint32_t code = net_recieve_code(opponent);
350 Move move;
351 switch (code) {
352 case NETCODE_TIMEOVER:
353 printw("\rYour opponent's time ran out - you win!");
354 clrtoeol();
355 return 1;
356 case NETCODE_SURRENDER:
357 printw("\rYour opponent surrendered!");
358 clrtoeol();
359 return 1;
360 case NETCODE_REMIS:
361 if (prompt_yesno(
362 "\rYour opponent offers remis - do you accept")) {
363 printw("\rRemis accepted!");
364 clrtoeol();
365 net_send_code(opponent, NETCODE_ACCEPT);
366 return 1;
367 } else {
368 net_send_code(opponent, NETCODE_DECLINE);
369 }
370 break;
371 case NETCODE_MOVE:
372 net_recieve_data(opponent, &move, sizeof(Move));
373 if (validate_move(gamestate, &move)) {
374 apply_move(gamestate, &move);
375 if (move.check) {
376 net_send_code(opponent, NETCODE_CHECK);
377 } else if (gamestate->checkmate) {
378 net_send_code(opponent, NETCODE_CHECKMATE);
379 printw("\rCheckmate!");
380 clrtoeol();
381 return 1;
382 } else if (gamestate->stalemate) {
383 net_send_code(opponent, NETCODE_STALEMATE);
384 printw("\rStalemate!");
385 clrtoeol();
386 return 1;
387 } else {
388 net_send_code(opponent, NETCODE_ACCEPT);
389 }
390 return 0;
391 } else {
392 net_send_code(opponent, NETCODE_DECLINE);
393 }
394 }
395 }
396 }
397 }
399 static void init_board(GameState *gamestate) {
400 Board initboard = {
401 {WROOK, WKNIGHT, WBISHOP, WQUEEN, WKING, WBISHOP, WKNIGHT, WROOK},
402 {WPAWN, WPAWN, WPAWN, WPAWN, WPAWN, WPAWN, WPAWN, WPAWN},
403 {0, 0, 0, 0, 0, 0, 0, 0},
404 {0, 0, 0, 0, 0, 0, 0, 0},
405 {0, 0, 0, 0, 0, 0, 0, 0},
406 {0, 0, 0, 0, 0, 0, 0, 0},
407 {BPAWN, BPAWN, BPAWN, BPAWN, BPAWN, BPAWN, BPAWN, BPAWN},
408 {BROOK, BKNIGHT, BBISHOP, BQUEEN, BKING, BBISHOP, BKNIGHT, BROOK}
409 };
410 memcpy(gamestate->board, initboard, sizeof(Board));
411 }
413 void game_start_singlemachine(Settings *settings) {
414 inputy = getmaxy(stdscr) - 6;
416 GameState gamestate;
417 memset(&gamestate, 0, sizeof(GameState));
418 init_board(&gamestate);
419 gamestate.mycolor = WHITE;
421 _Bool running;
422 do {
423 clear();
424 draw_board(&gamestate);
425 running = !domove_singlemachine(&gamestate, &(settings->gameinfo));
426 gamestate.mycolor = opponent_color(gamestate.mycolor);
427 } while (running);
428 move(0,0);
429 draw_board(&gamestate);
431 gamestate_cleanup(&gamestate);
433 mvaddstr(getmaxy(stdscr)-1, 0,
434 "Game has ended. Press any key to leave...");
435 refresh();
436 getch();
437 }
439 void game_start(Settings *settings, int opponent) {
440 inputy = getmaxy(stdscr) - 6;
442 _Bool myturn = is_server(settings) ==
443 (settings->gameinfo.servercolor == WHITE);
445 GameState gamestate;
446 memset(&gamestate, 0, sizeof(GameState));
447 init_board(&gamestate);
448 gamestate.mycolor = myturn ? WHITE:BLACK;
450 _Bool running;
451 do {
452 clear();
453 draw_board(&gamestate);
454 if (myturn) {
455 running = !sendmove(&gamestate, &(settings->gameinfo), opponent);
456 } else {
457 running = !recvmove(&gamestate, &(settings->gameinfo), opponent);
458 }
459 myturn ^= TRUE;
460 } while (running);
462 move(0,0);
463 draw_board(&gamestate);
465 gamestate_cleanup(&gamestate);
467 mvaddstr(getmaxy(stdscr)-1, 0,
468 "Game has ended. Press any key to leave...");
469 refresh();
470 cbreak();
471 flushinp();
472 getch();
473 }