Wed, 26 Mar 2014 14:12:59 +0100
completed eval_move
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2014 Mike Becker. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ #include "game.h" #include "input.h" #include "rules/rules.h" #include <ncurses.h> #include <string.h> static const uint8_t boardx = 10, boardy = 10; static void draw_board(Board board, uint8_t mycolor) { for (uint8_t y = 0 ; y < 8 ; y++) { for (uint8_t x = 0 ; x < 8 ; x++) { uint8_t col = board[y][x] & COLOR_MASK; uint8_t piece = board[y][x] & PIECE_MASK; char piecec = ' '; switch (piece) { case PAWN: piecec = 'P'; break; case ROOK: piecec = 'R'; break; case KNIGHT: piecec = 'N'; break; case BISHOP: piecec = 'B'; break; case QUEEN: piecec = 'Q'; break; case KING: piecec = 'K'; break; } attrset((col == WHITE ? A_BOLD : A_DIM) | COLOR_PAIR((y&1)==(x&1) ? COL_WB : COL_BW)); int cy = mycolor == WHITE ? boardy-y : boardy-7+y; int cx = mycolor == WHITE ? boardx+x*3 : boardx+21-x*3; mvaddch(cy, cx, ' '); mvaddch(cy, cx+1, piecec); mvaddch(cy, cx+2, ' '); } } attrset(A_NORMAL); for (uint8_t i = 0 ; i < 8 ; i++) { int x = mycolor == WHITE ? boardx+i*3+1 : boardx+22-i*3; int y = mycolor == WHITE ? boardy-i : boardy-7+i; mvaddch(boardy+1, x, 'a'+i); mvaddch(y, boardx-2, '1'+i); } } /** * Applies a move and deletes captured pieces. * * @param board the current board state * @param move the move to apply */ static void apply_move(Board board, Move *move) { board[move->fromrow][move->fromfile] = 0; // TODO: care for en passant capture board[move->torow][move->tofile] = move->piece; /* castling */ if ((move->piece & PIECE_MASK) == KING && move->fromfile == fileidx('e')) { uint8_t color = move->piece & COLOR_MASK; if (move->tofile == fileidx('g')) { board[move->torow][fileidx('h')] = 0; board[move->torow][fileidx('f')] = color|ROOK; } else if (move->tofile == fileidx('c')) { board[move->torow][fileidx('a')] = 0; board[move->torow][fileidx('d')] = color|ROOK; } } } /** * Validates move by applying chess rules. * @param board the current board state * @param move the move to validate * @return TRUE, if the move complies to chess rules, FALSE otherwise */ static _Bool validate_move(Board board, Move *move) { _Bool result; /* validate indices (don't trust opponent) */ if (!chkidx(move)) { return FALSE; } /* does piece exist */ result = board[move->fromrow][move->fromfile] == move->piece; switch (move->piece & PIECE_MASK) { case PAWN: result = result && pawn_chkrules(board, move); result = result && !pawn_isblocked(board, move); break; case ROOK: result = result && rook_chkrules(board, move); result = result && !rook_isblocked(board, move); break; case KNIGHT: result = result && knight_chkrules(board, move); result = result && !knight_isblocked(board, move); break; case BISHOP: result = result && bishop_chkrules(board, move); result = result && !bishop_isblocked(board, move); break; case QUEEN: result = result && queen_chkrules(board, move); result = result && !queen_isblocked(board, move); break; case KING: result = result && king_chkrules(board, move); result = result && !king_isblocked(board, move); break; default: result = FALSE; } /* is piece pinned */ // TODO: make it so /* correct check and checkmate flags */ // TODO: make it so return result; } /** * Maps a character to a piece. * * Does not work for pawns, since they don't have a character. * * @param c one of R,N,B,Q,K * @return numeric value for the specified piece */ static uint8_t getpiece(char c) { switch (c) { case 'R': return ROOK; case 'N': return KNIGHT; case 'B': return BISHOP; case 'Q': return QUEEN; case 'K': return KING; default: return 0; } } /** * Guesses the location of a piece for short algebraic notation. * * @param board the current state of the board * @param move the move date to operate on * @return TRUE if the location could be retrieved, FALSE if the location is * ambiguous */ static _Bool getlocation(Board board, Move *move) { uint8_t piece = move->piece & PIECE_MASK; switch (piece) { case PAWN: return pawn_getlocation(board, move); case ROOK: return rook_getlocation(board, move); case KNIGHT: return knight_getlocation(board, move); case BISHOP: return bishop_getlocation(board, move); case QUEEN: return queen_getlocation(board, move); case KING: return king_getlocation(board, move); default: return FALSE; } } /** * Evaluates a move syntactically and stores the move data in the specified * object. * * @param board the current state of the board * @param mycolor the color of the current player * @param mstr the input string to parse * @param move a pointer to object where the move data shall be stored * @return TRUE, if the move is syntactically valid, FALSE otherwise */ static _Bool eval_move(Board board, uint8_t mycolor, char *mstr, Move *move) { memset(move, 0, sizeof(Move)); move->fromfile = POS_UNSPECIFIED; move->fromrow = POS_UNSPECIFIED; size_t len = strlen(mstr); /* evaluate check/checkmate flags */ if (mstr[len-1] == '+') { len--; mstr[len] = '\0'; move->check = TRUE; } else if (mstr[len-1] == '#') { len--; mstr[len] = '\0'; move->checkmate = TRUE; } if (len == 2) { /* pawn move (e.g. "e4") */ move->piece = PAWN; move->tofile = fileidx(mstr[0]); move->torow = rowidx(mstr[1]); } else if (len == 3) { if (strcmp(mstr, "O-O") == 0) { /* king side castling */ move->piece = KING; move->fromfile = fileidx('e'); move->tofile = fileidx('g'); move->fromrow = move->torow = mycolor == WHITE ? 0 : 7; } else { /* move (e.g. "Nf3") */ move->piece = getpiece(mstr[0]); move->tofile = fileidx(mstr[1]); move->torow = rowidx(mstr[2]); } } else if (len == 4) { move->piece = getpiece(mstr[0]); if (mstr[1] == 'x') { /* capture (e.g. "Nxf3", "dxe5") */ move->capture = TRUE; if (!move->piece) { move->piece = PAWN; move->fromfile = fileidx(mstr[0]); } } else { /* move (e.g. "Ndf3") */ move->fromfile = fileidx(mstr[1]); } move->tofile = fileidx(mstr[2]); move->torow = rowidx(mstr[3]); } else if (len == 5) { if (strcmp(mstr, "O-O-O") == 0) { /* queen side castling "O-O-O" */ move->piece = KING; move->fromfile = fileidx('e'); move->tofile = fileidx('c'); move->fromrow = move->torow = mycolor == WHITE ? 0 : 7; } else { move->piece = getpiece(mstr[0]); if (mstr[2] == 'x') { move->capture = TRUE; if (move->piece) { /* capture (e.g. "Ndxf3") */ move->fromfile = fileidx(mstr[1]); } else { /* long notation capture (e.g. "e5xf6") */ move->piece = PAWN; move->fromfile = fileidx(mstr[0]); move->fromrow = rowidx(mstr[1]); } } else { /* long notation move (e.g. "Nc5a4") */ move->fromfile = fileidx(mstr[1]); move->fromrow = rowidx(mstr[2]); } move->tofile = fileidx(mstr[3]); move->torow = rowidx(mstr[4]); } } else if (len == 6) { /* long notation capture (e.g. "Nc5xf3") */ if (mstr[3] == 'x') { move->capture = TRUE; move->piece = getpiece(mstr[0]); move->fromfile = fileidx(mstr[1]); move->fromrow = rowidx(mstr[2]); move->tofile = fileidx(mstr[4]); move->torow = rowidx(mstr[5]); } } if (move->piece) { move->piece |= mycolor; if (move->fromfile == POS_UNSPECIFIED || move->fromrow == POS_UNSPECIFIED) { return getlocation(board, move) && chkidx(move); } else { return chkidx(move); } } else { return FALSE; } // TODO: return status code to indicate the error type } static int sendmove(Board board, uint8_t mycolor, int opponent) { const size_t buflen = 8; char movestr[buflen]; _Bool remisrejected = FALSE; uint8_t code; while (1) { move(boardy+3, 0); if (remisrejected) { printw( "Use chess notation to enter your move.\n" "Remis offer rejected - type 'surr' to surrender. \n\n" "Type your move: "); } else { printw( "Use chess notation to enter your move.\n" "Or type 'surr' to surrender or 'remis' to offer remis.\n\n" "Type your move: "); } clrtoeol(); refresh(); getnstr(movestr, buflen); if (strncmp(movestr, "surr", buflen) == 0) { printw("You surrendered!"); refresh(); net_send_code(opponent, NETCODE_SURRENDER); return 1; } else if (strncmp(movestr, "remis", buflen) == 0) { if (!remisrejected) { net_send_code(opponent, NETCODE_REMIS); printw("Remis offer sent - waiting for acceptance..."); refresh(); if (net_recieve_code(opponent) == NETCODE_ACCEPT) { printw("\rRemis accepted!"); clrtoeol(); refresh(); return 1; } else { remisrejected = TRUE; } } } else { Move move; if (eval_move(board, mycolor, movestr, &move)) { net_send_code(opponent, NETCODE_MOVE); net_send_data(opponent, &move, sizeof(Move)); code = net_recieve_code(opponent); move.check = code == NETCODE_CHECK; move.checkmate = code == NETCODE_CHECKMATE; // TODO: record move if (code == NETCODE_DECLINE) { printw("Invalid move."); clrtoeol(); } else { apply_move(board, &move); if (move.checkmate) { printw("Checkmate!"); return 1; } } } else { printw("Can't interpret move - please use algebraic notation."); } } } } static int recvmove(Board board, int opponent) { while (1) { move(boardy+3, 0); printw("Awaiting opponent move..."); clrtoeol(); refresh(); // TODO: nonblocking uint32_t code = net_recieve_code(opponent); Move move; switch (code) { case NETCODE_SURRENDER: printw("\rYour opponent surrendered!"); clrtoeol(); return 1; case NETCODE_REMIS: if (prompt_yesno( "\rYour opponent offers remis - do you accept")) { printw("\rRemis accepted!"); clrtoeol(); net_send_code(opponent, NETCODE_ACCEPT); return 1; } else { net_send_code(opponent, NETCODE_DECLINE); } break; case NETCODE_MOVE: net_recieve_data(opponent, &move, sizeof(Move)); if (validate_move(board, &move)) { apply_move(board, &move); // TODO: record move if (move.check) { net_send_code(opponent, NETCODE_CHECK); } else if (move.checkmate) { net_send_code(opponent, NETCODE_CHECKMATE); } else { net_send_code(opponent, NETCODE_ACCEPT); } return 0; } else { net_send_code(opponent, NETCODE_DECLINE); } } } } void game_start(Settings *settings, int opponent) { _Bool myturn = is_server(settings) == (settings->gameinfo.servercolor == WHITE); uint8_t mycolor = myturn ? WHITE:BLACK; _Bool running; Board board = { {WROOK, WKNIGHT, WBISHOP, WQUEEN, WKING, WBISHOP, WKNIGHT, WROOK}, {WPAWN, WPAWN, WPAWN, WPAWN, WPAWN, WPAWN, WPAWN, WPAWN}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {BPAWN, BPAWN, BPAWN, BPAWN, BPAWN, BPAWN, BPAWN, BPAWN}, {BROOK, BKNIGHT, BBISHOP, BQUEEN, BKING, BBISHOP, BKNIGHT, BROOK} }; do { clear(); draw_board(board, mycolor); if (myturn) { running = !sendmove(board, mycolor, opponent); } else { running = !recvmove(board, opponent); flushinp(); // flush any input the user hacked in while waiting } myturn ^= TRUE; } while (running); mvaddstr(getmaxy(tchess_window)-1, 0, "Game has ended. Press any key to leave..."); getch(); }