Sat, 29 Mar 2014 16:53:58 +0100
fixed bishop + added pawn promotion + added move log
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 uint8_t getpiecechr(uint8_t piece) {
39 switch (piece & PIECE_MASK) {
40 case ROOK: return 'R';
41 case KNIGHT: return 'N';
42 case BISHOP: return 'B';
43 case QUEEN: return 'Q';
44 case KING: return 'K';
45 default: return '\0';
46 }
47 }
49 /**
50 * Maps a character to a piece.
51 *
52 * Does not work for pawns, since they don't have a character.
53 *
54 * @param c one of R,N,B,Q,K
55 * @return numeric value for the specified piece
56 */
57 static uint8_t getpiece(char c) {
58 switch (c) {
59 case 'R': return ROOK;
60 case 'N': return KNIGHT;
61 case 'B': return BISHOP;
62 case 'Q': return QUEEN;
63 case 'K': return KING;
64 default: return 0;
65 }
66 }
68 /**
69 * Guesses the location of a piece for short algebraic notation.
70 *
71 * @param board the current state of the board
72 * @param move the move date to operate on
73 * @return status code (see rules/rules.h for the codes)
74 */
75 static int getlocation(Board board, Move *move) {
76 uint8_t piece = move->piece & PIECE_MASK;
77 switch (piece) {
78 case PAWN: return pawn_getlocation(board, move);
79 case ROOK: return rook_getlocation(board, move);
80 case KNIGHT: return knight_getlocation(board, move);
81 case BISHOP: return bishop_getlocation(board, move);
82 case QUEEN: return queen_getlocation(board, move);
83 case KING: return king_getlocation(board, move);
84 default: return INVALID_MOVE_SYNTAX;
85 }
86 }
89 static void draw_board(Board board, MoveListRoot *movelist, uint8_t mycolor) {
91 for (uint8_t y = 0 ; y < 8 ; y++) {
92 for (uint8_t x = 0 ; x < 8 ; x++) {
93 uint8_t col = board[y][x] & COLOR_MASK;
94 uint8_t piece = board[y][x] & PIECE_MASK;
95 char piecec;
96 if (piece) {
97 piecec = piece == PAWN ? 'P' : getpiecechr(piece);
98 } else {
99 piecec = ' ';
100 }
102 attrset((col == WHITE ? A_BOLD : A_DIM) |
103 COLOR_PAIR((y&1)==(x&1) ? COL_WB : COL_BW));
105 int cy = mycolor == WHITE ? boardy-y : boardy-7+y;
106 int cx = mycolor == WHITE ? boardx+x*3 : boardx+21-x*3;
107 mvaddch(cy, cx, ' ');
108 mvaddch(cy, cx+1, piecec);
109 mvaddch(cy, cx+2, ' ');
110 }
111 }
113 attrset(A_NORMAL);
114 for (uint8_t i = 0 ; i < 8 ; i++) {
115 int x = mycolor == WHITE ? boardx+i*3+1 : boardx+22-i*3;
116 int y = mycolor == 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 // TODO: introduce window to avoid bugs with a long move log
123 uint8_t logy = 0;
124 const uint8_t logx = boardx + 30;
125 int logi = 1;
126 MoveList *logelem = movelist->first;
128 while (logelem) {
129 logi++;
130 if (logi % 2 == 0) {
131 if ((logi - 2) % 4 == 0) {
132 logy++;
133 wmove(tchess_window, logy, logx);
134 }
135 printw("%d. ", logi / 2);
136 }
138 if (logelem) {
139 Move move = logelem->move;
140 char logstr[] = {
141 getpiecechr(move.piece),
142 filechr(move.fromfile), rowchr(move.fromrow),
143 move.capture ? 'x':'\0',
144 filechr(move.tofile), rowchr(move.torow),
145 move.check ? '+' : (move.checkmate ? '#' :
146 (move.promotion ? '=' : '\0')),
147 move.promotion ? getpiecechr(move.promotion) : '\0',
148 ' '
149 };
150 for (int stri = 0 ; stri < sizeof(logstr) ; stri++) {
151 if (logstr[stri]) {
152 addch(logstr[stri]);
153 }
154 }
156 logelem = logelem->next;
157 }
158 }
159 }
161 /**
162 * Applies a move and deletes captured pieces.
163 *
164 * @param board the current board state
165 * @param move the move to apply
166 */
167 static void apply_move(Board board, Move *move) {
168 uint8_t piece = move->piece & PIECE_MASK;
169 uint8_t color = move->piece & COLOR_MASK;
171 /* en passant capture */
172 if (move->capture && piece == PAWN &&
173 mdst(board, move) == 0) {
174 board[move->fromrow][move->tofile] = 0;
175 }
177 /* remove old en passant threats */
178 for (uint8_t file = 0 ; file < 8 ; file++) {
179 board[3][file] &= ~ENPASSANT_THREAT;
180 board[4][file] &= ~ENPASSANT_THREAT;
181 }
183 /* add new en passant threat */
184 if (piece == PAWN && (
185 (move->fromrow == 1 && move->torow == 3) ||
186 (move->fromrow == 6 && move->torow == 4))) {
187 move->piece |= ENPASSANT_THREAT;
188 }
190 /* move (and maybe capture or promote) */
191 msrc(board, move) = 0;
192 if (move->promotion) {
193 mdst(board, move) = move->promotion;
194 } else {
195 mdst(board, move) = move->piece;
196 }
198 /* castling */
199 if (piece == KING &&
200 move->fromfile == fileidx('e')) {
202 if (move->tofile == fileidx('g')) {
203 board[move->torow][fileidx('h')] = 0;
204 board[move->torow][fileidx('f')] = color|ROOK;
205 } else if (move->tofile == fileidx('c')) {
206 board[move->torow][fileidx('a')] = 0;
207 board[move->torow][fileidx('d')] = color|ROOK;
208 }
209 }
210 }
212 /**
213 * Validates move by applying chess rules.
214 * @param board the current board state
215 * @param move the move to validate
216 * @return TRUE, if the move complies to chess rules, FALSE otherwise
217 */
218 static _Bool validate_move(Board board, Move *move) {
219 _Bool result;
221 /* validate indices (don't trust opponent) */
222 if (!chkidx(move)) {
223 return FALSE;
224 }
226 /* does piece exist */
227 result = msrc(board, move) == move->piece;
229 /* can't capture own pieces */
230 if ((mdst(board, move) & COLOR_MASK) == (move->piece & COLOR_MASK)) {
231 return FALSE;
232 }
234 /* validate individual rules */
235 switch (move->piece & PIECE_MASK) {
236 case PAWN:
237 result = result && pawn_chkrules(board, move);
238 result = result && !pawn_isblocked(board, move);
239 break;
240 case ROOK:
241 result = result && rook_chkrules(move);
242 result = result && !rook_isblocked(board, move);
243 break;
244 case KNIGHT:
245 result = result && knight_chkrules(move);
246 result = result && !knight_isblocked(board, move);
247 break;
248 case BISHOP:
249 result = result && bishop_chkrules(move);
250 result = result && !bishop_isblocked(board, move);
251 break;
252 case QUEEN:
253 result = result && queen_chkrules(move);
254 result = result && !queen_isblocked(board, move);
255 break;
256 case KING:
257 result = result && king_chkrules(board, move);
258 result = result && !king_isblocked(board, move);
259 break;
260 default:
261 result = FALSE;
262 }
264 /* is piece pinned */
265 // TODO: make it so
267 /* correct check and checkmate flags */
268 // TODO: make it so
270 return result;
271 }
273 /**
274 * Evaluates a move syntactically and stores the move data in the specified
275 * object.
276 *
277 * @param board the current state of the board
278 * @param mycolor the color of the current player
279 * @param mstr the input string to parse
280 * @param move a pointer to object where the move data shall be stored
281 * @return status code (see rules/rules.h for the list of codes)
282 */
283 static int eval_move(Board board, uint8_t mycolor, char *mstr, Move *move) {
284 memset(move, 0, sizeof(Move));
285 move->fromfile = POS_UNSPECIFIED;
286 move->fromrow = POS_UNSPECIFIED;
288 size_t len = strlen(mstr);
290 /* evaluate check/checkmate flags */
291 if (mstr[len-1] == '+') {
292 len--; mstr[len] = '\0';
293 move->check = TRUE;
294 } else if (mstr[len-1] == '#') {
295 len--; mstr[len] = '\0';
296 move->checkmate = TRUE;
297 }
299 /* evaluate promotion */
300 if (len > 3 && mstr[len-2] == '=') {
301 move->promotion = getpiece(mstr[len-1]);
302 if (!move->promotion) {
303 return INVALID_MOVE_SYNTAX;
304 } else {
305 move->promotion |= mycolor;
306 len -= 2;
307 mstr[len] = 0;
308 }
309 }
311 if (len == 2) {
312 /* pawn move (e.g. "e4") */
313 move->piece = PAWN;
314 move->tofile = fileidx(mstr[0]);
315 move->torow = rowidx(mstr[1]);
316 } else if (len == 3) {
317 if (strcmp(mstr, "O-O") == 0) {
318 /* king side castling */
319 move->piece = KING;
320 move->fromfile = fileidx('e');
321 move->tofile = fileidx('g');
322 move->fromrow = move->torow = mycolor == WHITE ? 0 : 7;
323 } else {
324 /* move (e.g. "Nf3") */
325 move->piece = getpiece(mstr[0]);
326 move->tofile = fileidx(mstr[1]);
327 move->torow = rowidx(mstr[2]);
328 }
330 } else if (len == 4) {
331 move->piece = getpiece(mstr[0]);
332 if (!move->piece) {
333 move->piece = PAWN;
334 move->fromfile = fileidx(mstr[0]);
335 }
336 if (mstr[1] == 'x') {
337 /* capture (e.g. "Nxf3", "dxe5") */
338 move->capture = TRUE;
339 } else {
340 /* move (e.g. "Ndf3", "N2c3", "e2e4") */
341 if (isfile(mstr[1])) {
342 move->fromfile = fileidx(mstr[1]);
343 if (move->piece == PAWN) {
344 move->piece = 0;
345 }
346 } else {
347 move->fromrow = rowidx(mstr[1]);
348 }
349 }
350 move->tofile = fileidx(mstr[2]);
351 move->torow = rowidx(mstr[3]);
352 } else if (len == 5) {
353 if (strcmp(mstr, "O-O-O") == 0) {
354 /* queen side castling "O-O-O" */
355 move->piece = KING;
356 move->fromfile = fileidx('e');
357 move->tofile = fileidx('c');
358 move->fromrow = move->torow = mycolor == WHITE ? 0 : 7;
359 } else {
360 move->piece = getpiece(mstr[0]);
361 if (mstr[2] == 'x') {
362 move->capture = TRUE;
363 if (move->piece) {
364 /* capture (e.g. "Ndxf3") */
365 move->fromfile = fileidx(mstr[1]);
366 } else {
367 /* long notation capture (e.g. "e5xf6") */
368 move->piece = PAWN;
369 move->fromfile = fileidx(mstr[0]);
370 move->fromrow = rowidx(mstr[1]);
371 }
372 } else {
373 /* long notation move (e.g. "Nc5a4") */
374 move->fromfile = fileidx(mstr[1]);
375 move->fromrow = rowidx(mstr[2]);
376 }
377 move->tofile = fileidx(mstr[3]);
378 move->torow = rowidx(mstr[4]);
379 }
380 } else if (len == 6) {
381 /* long notation capture (e.g. "Nc5xf3") */
382 if (mstr[3] == 'x') {
383 move->capture = TRUE;
384 move->piece = getpiece(mstr[0]);
385 move->fromfile = fileidx(mstr[1]);
386 move->fromrow = rowidx(mstr[2]);
387 move->tofile = fileidx(mstr[4]);
388 move->torow = rowidx(mstr[5]);
389 }
390 }
393 if (move->piece) {
394 if (move->piece == PAWN && move->torow == (mycolor==WHITE?7:0)
395 && !move->promotion) {
396 return NEED_PROMOTION;
397 }
399 move->piece |= mycolor;
400 if (move->fromfile == POS_UNSPECIFIED
401 || move->fromrow == POS_UNSPECIFIED) {
402 return getlocation(board, move);
403 } else {
404 return chkidx(move) ? VALID_MOVE_SYNTAX : INVALID_POSITION;
405 }
406 } else {
407 return INVALID_MOVE_SYNTAX;
408 }
409 }
411 static int sendmove(Board board, MoveListRoot *movelist,
412 uint8_t mycolor, int opponent) {
414 const size_t buflen = 8;
415 char movestr[buflen];
416 _Bool remisrejected = FALSE;
417 uint8_t code;
419 int inputy = getmaxy(tchess_window) - 6;
420 while (1) {
421 move(inputy, 0);
422 if (remisrejected) {
423 printw(
424 "Use chess notation to enter your move.\n"
425 "Remis offer rejected - type 'surr' to surrender. \n\n"
426 "Type your move: ");
427 } else {
428 printw(
429 "Use chess notation to enter your move.\n"
430 "Or type 'surr' to surrender or 'remis' to offer remis.\n\n"
431 "Type your move: ");
432 }
433 clrtoeol();
434 refresh();
435 getnstr(movestr, buflen);
437 if (strncmp(movestr, "surr", buflen) == 0) {
438 printw("You surrendered!");
439 clrtoeol();
440 refresh();
441 net_send_code(opponent, NETCODE_SURRENDER);
442 return 1;
443 } else if (strncmp(movestr, "remis", buflen) == 0) {
444 if (!remisrejected) {
445 net_send_code(opponent, NETCODE_REMIS);
446 printw("Remis offer sent - waiting for acceptance...");
447 refresh();
448 if (net_recieve_code(opponent) == NETCODE_ACCEPT) {
449 printw("\rRemis accepted!");
450 clrtoeol();
451 refresh();
452 return 1;
453 } else {
454 remisrejected = TRUE;
455 }
456 }
457 } else {
458 Move move;
459 int eval_result = eval_move(board, mycolor, movestr, &move);
460 switch (eval_result) {
461 case VALID_MOVE_SYNTAX:
462 net_send_code(opponent, NETCODE_MOVE);
463 net_send_data(opponent, &move, sizeof(Move));
464 code = net_recieve_code(opponent);
465 move.check = code == NETCODE_CHECK;
466 move.checkmate = code == NETCODE_CHECKMATE;
467 addmove(movelist, &move);
468 if (code == NETCODE_DECLINE) {
469 printw("Invalid move.");
470 } else {
471 apply_move(board, &move);
472 if (move.checkmate) {
473 printw("Checkmate!");
474 clrtoeol();
475 return 1;
476 } else {
477 return 0;
478 }
479 }
480 break;
481 case AMBIGUOUS_MOVE:
482 printw("Ambiguous move - please specify the piece to move.");
483 break;
484 case INVALID_POSITION:
485 printw("Cannot find the piece that shall be moved.");
486 break;
487 case NEED_PROMOTION:
488 printw("You need to promote the pawn (append \"=Q\" e.g.)!");
489 break;
490 default:
491 printw("Can't interpret move - please use algebraic notation.");
492 }
493 clrtoeol();
494 }
495 }
496 }
498 static int recvmove(Board board, MoveListRoot *movelist, int opponent) {
500 int inputy = getmaxy(tchess_window) - 6;
501 while (1) {
502 move(inputy, 0);
503 printw("Awaiting opponent move...");
504 clrtoeol();
505 refresh();
507 // TODO: nonblocking
508 uint32_t code = net_recieve_code(opponent);
510 Move move;
511 switch (code) {
512 case NETCODE_SURRENDER:
513 printw("\rYour opponent surrendered!");
514 clrtoeol();
515 return 1;
516 case NETCODE_REMIS:
517 if (prompt_yesno(
518 "\rYour opponent offers remis - do you accept")) {
519 printw("\rRemis accepted!");
520 clrtoeol();
521 net_send_code(opponent, NETCODE_ACCEPT);
522 return 1;
523 } else {
524 net_send_code(opponent, NETCODE_DECLINE);
525 }
526 break;
527 case NETCODE_MOVE:
528 net_recieve_data(opponent, &move, sizeof(Move));
529 if (validate_move(board, &move)) {
530 apply_move(board, &move);
531 addmove(movelist, &move);
532 if (move.check) {
533 net_send_code(opponent, NETCODE_CHECK);
534 } else if (move.checkmate) {
535 net_send_code(opponent, NETCODE_CHECKMATE);
536 } else {
537 net_send_code(opponent, NETCODE_ACCEPT);
538 }
539 return 0;
540 } else {
541 net_send_code(opponent, NETCODE_DECLINE);
542 }
543 }
544 }
545 }
547 void freemovelist(MoveListRoot* list) {
548 MoveList *elem;
549 elem = list->first;
550 while (elem) {
551 MoveList *cur = elem;
552 elem = elem->next;
553 free(cur);
554 };
555 free(list);
556 }
558 void addmove(MoveListRoot* list, Move *move) {
559 MoveList *elem = malloc(sizeof(MoveList));
560 elem->next = NULL;
561 elem->move = *move;
563 if (list->last) {
564 list->last->next = elem;
565 list->last = elem;
566 } else {
567 list->first = list->last = elem;
568 }
569 }
571 void game_start(Settings *settings, int opponent) {
572 _Bool myturn = is_server(settings) ==
573 (settings->gameinfo.servercolor == WHITE);
574 uint8_t mycolor = myturn ? WHITE:BLACK;
576 _Bool running;
578 MoveListRoot* movelist = calloc(1, sizeof(MoveListRoot));
580 Board board = {
581 {WROOK, WKNIGHT, WBISHOP, WQUEEN, WKING, WBISHOP, WKNIGHT, WROOK},
582 {WPAWN, WPAWN, WPAWN, WPAWN, WPAWN, WPAWN, WPAWN, WPAWN},
583 {0, 0, 0, 0, 0, 0, 0, 0},
584 {0, 0, 0, 0, 0, 0, 0, 0},
585 {0, 0, 0, 0, 0, 0, 0, 0},
586 {0, 0, 0, 0, 0, 0, 0, 0},
587 {BPAWN, BPAWN, BPAWN, BPAWN, BPAWN, BPAWN, BPAWN, BPAWN},
588 {BROOK, BKNIGHT, BBISHOP, BQUEEN, BKING, BBISHOP, BKNIGHT, BROOK}
589 };
591 do {
592 clear();
593 draw_board(board, movelist, mycolor);
594 if (myturn) {
595 running = !sendmove(board, movelist, mycolor, opponent);
596 } else {
597 running = !recvmove(board, movelist, opponent);
598 flushinp(); // flush any input the user hacked in while waiting
599 }
600 myturn ^= TRUE;
601 } while (running);
603 freemovelist(movelist);
605 mvaddstr(getmaxy(tchess_window)-1, 0,
606 "Game has ended. Press any key to leave...");
607 getch();
608 }