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