Wed, 26 Mar 2014 14:53:15 +0100
fixed crucial bug where both players could move at the same time + added pawn rules (TODO: en passant)
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 "input.h"
32 #include "rules/rules.h"
33 #include <ncurses.h>
34 #include <string.h>
36 static const uint8_t boardx = 10, boardy = 10;
38 static void draw_board(Board board, uint8_t mycolor) {
40 for (uint8_t y = 0 ; y < 8 ; y++) {
41 for (uint8_t x = 0 ; x < 8 ; x++) {
42 uint8_t col = board[y][x] & COLOR_MASK;
43 uint8_t piece = board[y][x] & PIECE_MASK;
44 char piecec = ' ';
45 switch (piece) {
46 case PAWN: piecec = 'P'; break;
47 case ROOK: piecec = 'R'; break;
48 case KNIGHT: piecec = 'N'; break;
49 case BISHOP: piecec = 'B'; break;
50 case QUEEN: piecec = 'Q'; break;
51 case KING: piecec = 'K'; break;
52 }
54 attrset((col == WHITE ? A_BOLD : A_DIM) |
55 COLOR_PAIR((y&1)==(x&1) ? COL_WB : COL_BW));
57 int cy = mycolor == WHITE ? boardy-y : boardy-7+y;
58 int cx = mycolor == WHITE ? boardx+x*3 : boardx+21-x*3;
59 mvaddch(cy, cx, ' ');
60 mvaddch(cy, cx+1, piecec);
61 mvaddch(cy, cx+2, ' ');
62 }
63 }
65 attrset(A_NORMAL);
66 for (uint8_t i = 0 ; i < 8 ; i++) {
67 int x = mycolor == WHITE ? boardx+i*3+1 : boardx+22-i*3;
68 int y = mycolor == WHITE ? boardy-i : boardy-7+i;
69 mvaddch(boardy+1, x, 'a'+i);
70 mvaddch(y, boardx-2, '1'+i);
71 }
72 }
74 /**
75 * Applies a move and deletes captured pieces.
76 *
77 * @param board the current board state
78 * @param move the move to apply
79 */
80 static void apply_move(Board board, Move *move) {
81 msrc(board, move) = 0;
82 // TODO: care for en passant capture
83 mdst(board, move) = move->piece;
85 /* castling */
86 if ((move->piece & PIECE_MASK) == KING &&
87 move->fromfile == fileidx('e')) {
88 uint8_t color = move->piece & COLOR_MASK;
90 if (move->tofile == fileidx('g')) {
91 board[move->torow][fileidx('h')] = 0;
92 board[move->torow][fileidx('f')] = color|ROOK;
93 } else if (move->tofile == fileidx('c')) {
94 board[move->torow][fileidx('a')] = 0;
95 board[move->torow][fileidx('d')] = color|ROOK;
96 }
97 }
98 }
100 /**
101 * Validates move by applying chess rules.
102 * @param board the current board state
103 * @param move the move to validate
104 * @return TRUE, if the move complies to chess rules, FALSE otherwise
105 */
106 static _Bool validate_move(Board board, Move *move) {
107 _Bool result;
109 /* validate indices (don't trust opponent) */
110 if (!chkidx(move)) {
111 return FALSE;
112 }
114 /* does piece exist */
115 result = msrc(board, move) == move->piece;
117 /* can't capture own pieces */
118 if ((mdst(board, move) & COLOR_MASK) == (move->piece & COLOR_MASK)) {
119 return FALSE;
120 }
122 /* validate individual rules */
123 switch (move->piece & PIECE_MASK) {
124 case PAWN:
125 result = result && pawn_chkrules(board, move);
126 result = result && !pawn_isblocked(board, move);
127 break;
128 case ROOK:
129 result = result && rook_chkrules(board, move);
130 result = result && !rook_isblocked(board, move);
131 break;
132 case KNIGHT:
133 result = result && knight_chkrules(board, move);
134 result = result && !knight_isblocked(board, move);
135 break;
136 case BISHOP:
137 result = result && bishop_chkrules(board, move);
138 result = result && !bishop_isblocked(board, move);
139 break;
140 case QUEEN:
141 result = result && queen_chkrules(board, move);
142 result = result && !queen_isblocked(board, move);
143 break;
144 case KING:
145 result = result && king_chkrules(board, move);
146 result = result && !king_isblocked(board, move);
147 break;
148 default:
149 result = FALSE;
150 }
152 /* is piece pinned */
153 // TODO: make it so
155 /* correct check and checkmate flags */
156 // TODO: make it so
158 return result;
159 }
161 /**
162 * Maps a character to a piece.
163 *
164 * Does not work for pawns, since they don't have a character.
165 *
166 * @param c one of R,N,B,Q,K
167 * @return numeric value for the specified piece
168 */
169 static uint8_t getpiece(char c) {
170 switch (c) {
171 case 'R': return ROOK;
172 case 'N': return KNIGHT;
173 case 'B': return BISHOP;
174 case 'Q': return QUEEN;
175 case 'K': return KING;
176 default: return 0;
177 }
178 }
180 /**
181 * Guesses the location of a piece for short algebraic notation.
182 *
183 * @param board the current state of the board
184 * @param move the move date to operate on
185 * @return TRUE if the location could be retrieved, FALSE if the location is
186 * ambiguous
187 */
188 static _Bool getlocation(Board board, Move *move) {
189 uint8_t piece = move->piece & PIECE_MASK;
190 switch (piece) {
191 case PAWN: return pawn_getlocation(board, move);
192 case ROOK: return rook_getlocation(board, move);
193 case KNIGHT: return knight_getlocation(board, move);
194 case BISHOP: return bishop_getlocation(board, move);
195 case QUEEN: return queen_getlocation(board, move);
196 case KING: return king_getlocation(board, move);
197 default: return FALSE;
198 }
199 }
201 /**
202 * Evaluates a move syntactically and stores the move data in the specified
203 * object.
204 *
205 * @param board the current state of the board
206 * @param mycolor the color of the current player
207 * @param mstr the input string to parse
208 * @param move a pointer to object where the move data shall be stored
209 * @return TRUE, if the move is syntactically valid, FALSE otherwise
210 */
211 static _Bool eval_move(Board board, uint8_t mycolor, char *mstr, Move *move) {
212 memset(move, 0, sizeof(Move));
213 move->fromfile = POS_UNSPECIFIED;
214 move->fromrow = POS_UNSPECIFIED;
216 size_t len = strlen(mstr);
218 /* evaluate check/checkmate flags */
219 if (mstr[len-1] == '+') {
220 len--; mstr[len] = '\0';
221 move->check = TRUE;
222 } else if (mstr[len-1] == '#') {
223 len--; mstr[len] = '\0';
224 move->checkmate = TRUE;
225 }
227 if (len == 2) {
228 /* pawn move (e.g. "e4") */
229 move->piece = PAWN;
230 move->tofile = fileidx(mstr[0]);
231 move->torow = rowidx(mstr[1]);
232 } else if (len == 3) {
233 if (strcmp(mstr, "O-O") == 0) {
234 /* king side castling */
235 move->piece = KING;
236 move->fromfile = fileidx('e');
237 move->tofile = fileidx('g');
238 move->fromrow = move->torow = mycolor == WHITE ? 0 : 7;
239 } else {
240 /* move (e.g. "Nf3") */
241 move->piece = getpiece(mstr[0]);
242 move->tofile = fileidx(mstr[1]);
243 move->torow = rowidx(mstr[2]);
244 }
246 } else if (len == 4) {
247 move->piece = getpiece(mstr[0]);
248 if (mstr[1] == 'x') {
249 /* capture (e.g. "Nxf3", "dxe5") */
250 move->capture = TRUE;
251 if (!move->piece) {
252 move->piece = PAWN;
253 move->fromfile = fileidx(mstr[0]);
254 }
255 } else {
256 /* move (e.g. "Ndf3", "N2c3") */
257 move->fromfile = isfile(mstr[1]) ?
258 fileidx(mstr[1]) : rowidx(mstr[1]);
259 }
260 move->tofile = fileidx(mstr[2]);
261 move->torow = rowidx(mstr[3]);
262 } else if (len == 5) {
263 if (strcmp(mstr, "O-O-O") == 0) {
264 /* queen side castling "O-O-O" */
265 move->piece = KING;
266 move->fromfile = fileidx('e');
267 move->tofile = fileidx('c');
268 move->fromrow = move->torow = mycolor == WHITE ? 0 : 7;
269 } else {
270 move->piece = getpiece(mstr[0]);
271 if (mstr[2] == 'x') {
272 move->capture = TRUE;
273 if (move->piece) {
274 /* capture (e.g. "Ndxf3") */
275 move->fromfile = fileidx(mstr[1]);
276 } else {
277 /* long notation capture (e.g. "e5xf6") */
278 move->piece = PAWN;
279 move->fromfile = fileidx(mstr[0]);
280 move->fromrow = rowidx(mstr[1]);
281 }
282 } else {
283 /* long notation move (e.g. "Nc5a4") */
284 move->fromfile = fileidx(mstr[1]);
285 move->fromrow = rowidx(mstr[2]);
286 }
287 move->tofile = fileidx(mstr[3]);
288 move->torow = rowidx(mstr[4]);
289 }
290 } else if (len == 6) {
291 /* long notation capture (e.g. "Nc5xf3") */
292 if (mstr[3] == 'x') {
293 move->capture = TRUE;
294 move->piece = getpiece(mstr[0]);
295 move->fromfile = fileidx(mstr[1]);
296 move->fromrow = rowidx(mstr[2]);
297 move->tofile = fileidx(mstr[4]);
298 move->torow = rowidx(mstr[5]);
299 }
300 }
303 if (move->piece) {
304 move->piece |= mycolor;
305 if (move->fromfile == POS_UNSPECIFIED
306 || move->fromrow == POS_UNSPECIFIED) {
307 return getlocation(board, move) && chkidx(move);
308 } else {
309 return chkidx(move);
310 }
311 } else {
312 return FALSE;
313 }
314 // TODO: return status code to indicate the error type
315 }
317 static int sendmove(Board board, uint8_t mycolor, int opponent) {
318 const size_t buflen = 8;
319 char movestr[buflen];
320 _Bool remisrejected = FALSE;
321 uint8_t code;
323 while (1) {
324 move(boardy+3, 0);
325 if (remisrejected) {
326 printw(
327 "Use chess notation to enter your move.\n"
328 "Remis offer rejected - type 'surr' to surrender. \n\n"
329 "Type your move: ");
330 } else {
331 printw(
332 "Use chess notation to enter your move.\n"
333 "Or type 'surr' to surrender or 'remis' to offer remis.\n\n"
334 "Type your move: ");
335 }
336 clrtoeol();
337 refresh();
338 getnstr(movestr, buflen);
340 if (strncmp(movestr, "surr", buflen) == 0) {
341 printw("You surrendered!");
342 refresh();
343 net_send_code(opponent, NETCODE_SURRENDER);
344 return 1;
345 } else if (strncmp(movestr, "remis", buflen) == 0) {
346 if (!remisrejected) {
347 net_send_code(opponent, NETCODE_REMIS);
348 printw("Remis offer sent - waiting for acceptance...");
349 refresh();
350 if (net_recieve_code(opponent) == NETCODE_ACCEPT) {
351 printw("\rRemis accepted!");
352 clrtoeol();
353 refresh();
354 return 1;
355 } else {
356 remisrejected = TRUE;
357 }
358 }
359 } else {
360 Move move;
361 if (eval_move(board, mycolor, movestr, &move)) {
362 net_send_code(opponent, NETCODE_MOVE);
363 net_send_data(opponent, &move, sizeof(Move));
364 code = net_recieve_code(opponent);
365 move.check = code == NETCODE_CHECK;
366 move.checkmate = code == NETCODE_CHECKMATE;
367 // TODO: record move
368 if (code == NETCODE_DECLINE) {
369 printw("Invalid move.");
370 clrtoeol();
371 } else {
372 apply_move(board, &move);
373 if (move.checkmate) {
374 printw("Checkmate!");
375 return 1;
376 } else {
377 return 0;
378 }
379 }
380 } else {
381 printw("Can't interpret move - please use algebraic notation.");
382 }
383 }
384 }
385 }
387 static int recvmove(Board board, int opponent) {
389 while (1) {
390 move(boardy+3, 0);
391 printw("Awaiting opponent move...");
392 clrtoeol();
393 refresh();
395 // TODO: nonblocking
396 uint32_t code = net_recieve_code(opponent);
398 Move move;
399 switch (code) {
400 case NETCODE_SURRENDER:
401 printw("\rYour opponent surrendered!");
402 clrtoeol();
403 return 1;
404 case NETCODE_REMIS:
405 if (prompt_yesno(
406 "\rYour opponent offers remis - do you accept")) {
407 printw("\rRemis accepted!");
408 clrtoeol();
409 net_send_code(opponent, NETCODE_ACCEPT);
410 return 1;
411 } else {
412 net_send_code(opponent, NETCODE_DECLINE);
413 }
414 break;
415 case NETCODE_MOVE:
416 net_recieve_data(opponent, &move, sizeof(Move));
417 if (validate_move(board, &move)) {
418 apply_move(board, &move);
419 // TODO: record move
420 if (move.check) {
421 net_send_code(opponent, NETCODE_CHECK);
422 } else if (move.checkmate) {
423 net_send_code(opponent, NETCODE_CHECKMATE);
424 } else {
425 net_send_code(opponent, NETCODE_ACCEPT);
426 }
427 return 0;
428 } else {
429 net_send_code(opponent, NETCODE_DECLINE);
430 }
431 }
432 }
433 }
435 void game_start(Settings *settings, int opponent) {
436 _Bool myturn = is_server(settings) ==
437 (settings->gameinfo.servercolor == WHITE);
438 uint8_t mycolor = myturn ? WHITE:BLACK;
440 _Bool running;
442 Board board = {
443 {WROOK, WKNIGHT, WBISHOP, WQUEEN, WKING, WBISHOP, WKNIGHT, WROOK},
444 {WPAWN, WPAWN, WPAWN, WPAWN, WPAWN, WPAWN, WPAWN, WPAWN},
445 {0, 0, 0, 0, 0, 0, 0, 0},
446 {0, 0, 0, 0, 0, 0, 0, 0},
447 {0, 0, 0, 0, 0, 0, 0, 0},
448 {0, 0, 0, 0, 0, 0, 0, 0},
449 {BPAWN, BPAWN, BPAWN, BPAWN, BPAWN, BPAWN, BPAWN, BPAWN},
450 {BROOK, BKNIGHT, BBISHOP, BQUEEN, BKING, BBISHOP, BKNIGHT, BROOK}
451 };
453 do {
454 clear();
455 draw_board(board, mycolor);
456 if (myturn) {
457 running = !sendmove(board, mycolor, opponent);
458 } else {
459 running = !recvmove(board, opponent);
460 flushinp(); // flush any input the user hacked in while waiting
461 }
462 myturn ^= TRUE;
463 } while (running);
465 mvaddstr(getmaxy(tchess_window)-1, 0,
466 "Game has ended. Press any key to leave...");
467 getch();
468 }