universe@6: /* universe@6: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. universe@6: * universe@55: * Copyright 2016 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@19: #include "network.h" universe@7: #include "input.h" universe@35: #include "colors.h" universe@7: #include universe@7: #include universe@30: #include universe@34: #include universe@50: #include universe@50: #include universe@7: universe@7: static const uint8_t boardx = 10, boardy = 10; universe@33: static int inputy = 21; /* should be overridden on game startup */ universe@7: universe@33: static int timecontrol(GameState *gamestate, GameInfo *gameinfo) { universe@30: if (gameinfo->timecontrol) { universe@33: uint16_t white = remaining_movetime(gameinfo, gamestate, WHITE); universe@33: uint16_t black = remaining_movetime(gameinfo, gamestate, BLACK); universe@30: mvprintw(boardy+4, boardx-1, universe@33: "White time: %4" PRIu16 ":%02" PRIu16, universe@33: white / 60, white % 60); universe@30: mvprintw(boardy+5, boardx-1, universe@33: "Black time: %4" PRIu16 ":%02" PRIu16, universe@33: black / 60, black % 60); universe@33: universe@33: if (white == 0) { universe@33: move(inputy, 0); universe@33: printw("Time is over - Black wins!"); universe@33: clrtobot(); universe@33: refresh(); universe@33: return 1; universe@33: } universe@33: if (black == 0) { universe@33: move(inputy, 0); universe@33: printw("Time is over - White wins!"); universe@33: clrtobot(); universe@33: refresh(); universe@33: return 1; universe@33: } universe@30: } universe@33: universe@33: return 0; universe@30: } universe@30: universe@50: static void draw_board(GameState *gamestate, uint8_t perspective) { universe@54: char fen[90]; universe@54: compute_fen(fen, gamestate); universe@54: mvaddstr(0, 0, fen); universe@54: universe@7: for (uint8_t y = 0 ; y < 8 ; y++) { universe@7: for (uint8_t x = 0 ; x < 8 ; x++) { universe@23: uint8_t col = gamestate->board[y][x] & COLOR_MASK; universe@23: uint8_t piece = gamestate->board[y][x] & PIECE_MASK; universe@18: char piecec; universe@18: if (piece) { universe@18: piecec = piece == PAWN ? 'P' : getpiecechr(piece); universe@18: } else { universe@18: piecec = ' '; universe@7: } universe@7: universe@35: _Bool boardblack = (y&1)==(x&1); universe@35: attrset((col==WHITE ? A_BOLD : A_DIM)| universe@35: COLOR_PAIR(col == WHITE ? universe@35: (boardblack ? COL_WB : COL_WW) : universe@35: (boardblack ? COL_BB : COL_BW) universe@35: ) universe@35: ); universe@7: universe@50: int cy = perspective == WHITE ? boardy-y : boardy-7+y; universe@50: int cx = perspective == 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@50: int x = perspective == WHITE ? boardx+i*3+1 : boardx+22-i*3; universe@50: int y = perspective == 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@18: universe@18: /* move log */ universe@18: // TODO: introduce window to avoid bugs with a long move log universe@54: uint8_t logy = 1; universe@18: const uint8_t logx = boardx + 30; universe@18: int logi = 1; universe@23: MoveList *logelem = gamestate->movelist; universe@18: universe@18: while (logelem) { universe@18: logi++; universe@18: if (logi % 2 == 0) { universe@18: if ((logi - 2) % 4 == 0) { universe@18: logy++; universe@23: move(logy, logx); universe@18: } universe@18: printw("%d. ", logi / 2); universe@18: } universe@18: universe@50: addstr(logelem->move.string); universe@50: if (!logelem->next) { universe@50: if (gamestate->stalemate) { universe@50: addstr(" stalemate"); universe@27: } universe@18: } universe@50: addch(' '); universe@50: universe@50: logelem = logelem->next; universe@18: } universe@7: } universe@7: universe@26: static void eval_move_failed_msg(int code) { universe@26: switch (code) { universe@26: case AMBIGUOUS_MOVE: universe@26: printw("Ambiguous move - please specify the piece to move."); universe@26: break; universe@26: case INVALID_POSITION: universe@48: printw("No piece can be moved this way."); universe@26: break; universe@26: case NEED_PROMOTION: universe@26: printw("You need to promote the pawn (append \"=Q\" e.g.)!"); universe@26: break; universe@47: case KING_IN_CHECK: universe@47: printw("Your king is in check!"); universe@47: break; universe@47: case PIECE_PINNED: universe@47: printw("This piece is pinned!"); universe@47: break; universe@47: case INVALID_MOVE_SYNTAX: universe@47: printw("Can't interpret move - please use algebraic notation."); universe@47: break; universe@48: case RULES_VIOLATED: universe@48: printw("Move does not comply chess rules."); universe@48: break; universe@48: case KING_MOVES_INTO_CHECK: universe@48: printw("Can't move the king into a check position."); universe@48: break; universe@26: default: universe@47: printw("Unknown move parser error."); universe@26: } universe@26: } universe@26: universe@50: static void save_pgn(GameState *gamestate, GameInfo *gameinfo) { universe@50: printw("Filename: "); universe@50: clrtoeol(); universe@50: refresh(); universe@50: universe@50: char filename[64]; universe@50: int y = getcury(stdscr); universe@51: if (getnstr(filename, 64) == OK && filename[0] != '\0') { universe@50: move(y, 0); universe@50: FILE *file = fopen(filename, "w"); universe@50: if (file) { universe@50: write_pgn(file, gamestate, gameinfo); universe@50: fclose(file); universe@50: printw("File saved."); universe@50: } else { universe@50: printw("Can't write to file (%s).", strerror(errno)); universe@50: } universe@50: clrtoeol(); universe@50: } universe@50: } universe@50: universe@50: #define MOVESTR_BUFLEN 10 universe@50: static int domove_singlemachine(GameState *gamestate, universe@50: GameInfo *gameinfo, uint8_t curcolor) { universe@26: universe@37: universe@32: size_t bufpos = 0; universe@37: char movestr[MOVESTR_BUFLEN]; universe@26: universe@33: flushinp(); universe@26: while (1) { universe@33: if (timecontrol(gamestate, gameinfo)) { universe@33: return 1; universe@33: } universe@33: universe@26: move(inputy, 0); universe@26: printw( universe@26: "Use chess notation to enter your move.\n" universe@50: "Or use a command: remis, resign, savepgn\n\n" universe@26: "Type your move: "); universe@26: clrtoeol(); universe@30: universe@37: if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) { universe@44: if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) { universe@50: gamestate->resign = 1; universe@44: printw("%s resigned!", universe@50: curcolor==WHITE?"White":"Black"); universe@50: clrtobot(); universe@30: refresh(); universe@30: return 1; universe@37: } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) { universe@50: gamestate->remis = 1; universe@30: printw("Game ends remis."); universe@50: clrtobot(); universe@30: refresh(); universe@30: return 1; universe@50: } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) { universe@50: save_pgn(gamestate, gameinfo); universe@30: } else { universe@30: Move move; universe@50: int result = eval_move(gamestate, movestr, &move, curcolor); universe@50: switch (result) { universe@30: case VALID_MOVE_SYNTAX: universe@50: result = validate_move(gamestate, &move); universe@50: if (result == VALID_MOVE_SEMANTICS) { universe@30: apply_move(gamestate, &move); universe@30: if (gamestate->checkmate) { universe@30: printw("Checkmate!"); universe@30: clrtoeol(); universe@30: return 1; universe@30: } else if (gamestate->stalemate) { universe@30: printw("Stalemate!"); universe@30: clrtoeol(); universe@30: return 1; universe@30: } else { universe@30: return 0; universe@30: } universe@26: } else { universe@50: eval_move_failed_msg(result); universe@26: } universe@30: break; universe@30: default: universe@50: eval_move_failed_msg(result); universe@26: } universe@30: clrtoeol(); universe@26: } universe@26: } universe@26: } universe@26: } universe@8: universe@50: static int sendmove(GameState *gamestate, GameInfo *gameinfo, universe@50: int opponent, uint8_t mycolor) { universe@18: universe@33: size_t bufpos = 0; universe@37: char movestr[MOVESTR_BUFLEN]; universe@7: _Bool remisrejected = FALSE; universe@11: uint8_t code; universe@7: universe@33: flushinp(); universe@7: while (1) { universe@33: if (timecontrol(gamestate, gameinfo)) { universe@33: net_send_code(opponent, NETCODE_TIMEOVER); universe@33: return 1; universe@33: } universe@33: universe@18: move(inputy, 0); universe@7: if (remisrejected) { universe@7: printw( universe@7: "Use chess notation to enter your move.\n" universe@50: "Remis offer rejected \n\n" universe@7: "Type your move: "); universe@7: } else { universe@7: printw( universe@7: "Use chess notation to enter your move.\n" universe@50: "Or use a command: remis, resign, savepgn\n\n" universe@7: "Type your move: "); universe@7: } universe@7: clrtoeol(); universe@33: universe@37: if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) { universe@44: if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) { universe@50: gamestate->resign = 1; universe@44: printw("You resigned!"); universe@33: clrtoeol(); universe@7: refresh(); universe@44: net_send_code(opponent, NETCODE_RESIGN); universe@33: return 1; universe@50: } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) { universe@50: save_pgn(gamestate, gameinfo); universe@37: } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) { universe@33: if (!remisrejected) { universe@33: net_send_code(opponent, NETCODE_REMIS); universe@33: printw("Remis offer sent - waiting for acceptance..."); universe@8: refresh(); universe@46: code = net_recieve_code(opponent); universe@46: if (code == NETCODE_ACCEPT) { universe@50: gamestate->remis = 1; universe@33: printw("\rRemis accepted!"); universe@18: clrtoeol(); universe@33: refresh(); universe@26: return 1; universe@46: } else if (code == NETCODE_CONNLOST) { universe@46: printw("\rYour opponent left the game."); universe@46: clrtoeol(); universe@46: refresh(); universe@46: return 1; universe@14: } else { universe@33: remisrejected = TRUE; universe@11: } universe@18: } universe@33: } else { universe@33: Move move; universe@50: int eval_result = eval_move(gamestate, movestr, &move, mycolor); universe@33: switch (eval_result) { universe@33: case VALID_MOVE_SYNTAX: universe@51: net_send_data(opponent, NETCODE_MOVE, &move, sizeof(Move)); universe@33: code = net_recieve_code(opponent); universe@48: move.check = code == NETCODE_CHECK || universe@48: code == NETCODE_CHECKMATE; universe@33: gamestate->checkmate = code == NETCODE_CHECKMATE; universe@33: gamestate->stalemate = code == NETCODE_STALEMATE; universe@33: if (code == NETCODE_DECLINE) { universe@48: uint32_t reason; universe@48: net_recieve_data(opponent, &reason, sizeof(uint32_t)); universe@48: reason = ntohl(reason); universe@48: eval_move_failed_msg(reason); universe@42: } else if (code == NETCODE_ACCEPT universe@42: || code == NETCODE_CHECK universe@42: || code == NETCODE_CHECKMATE universe@42: || code == NETCODE_STALEMATE) { universe@33: apply_move(gamestate, &move); universe@33: if (gamestate->checkmate) { universe@33: printw("Checkmate!"); universe@33: clrtoeol(); universe@33: return 1; universe@33: } else if (gamestate->stalemate) { universe@33: printw("Stalemate!"); universe@33: clrtoeol(); universe@33: return 1; universe@33: } else { universe@33: return 0; universe@33: } universe@46: } else if (code == NETCODE_CONNLOST) { universe@46: printw("Your opponent left the game."); universe@46: return 1; universe@42: } else { universe@42: printw("Invalid network response."); universe@33: } universe@33: break; universe@33: default: universe@33: eval_move_failed_msg(eval_result); universe@33: } universe@33: clrtoeol(); universe@8: } universe@7: } universe@7: } universe@7: } universe@7: universe@33: static int recvmove(GameState *gamestate, GameInfo *gameinfo, int opponent) { universe@7: universe@34: struct timeval timeout; universe@7: while (1) { universe@33: timecontrol(gamestate, gameinfo); universe@33: universe@18: move(inputy, 0); universe@7: printw("Awaiting opponent move..."); universe@7: clrtoeol(); universe@7: refresh(); universe@7: universe@34: fd_set readfds; universe@8: universe@34: FD_ZERO(&readfds); universe@34: FD_SET(opponent, &readfds); universe@34: timeout.tv_sec = 0; universe@34: timeout.tv_usec = 1e5; universe@34: universe@50: // TODO: allow commands universe@50: universe@34: int result = select(opponent+1, &readfds, NULL, NULL, &timeout); universe@34: if (result == -1) { universe@34: printw("\rCannot perform asynchronous network IO"); universe@34: cbreak(); getch(); universe@34: exit(EXIT_FAILURE); universe@34: } universe@34: if (result > 0) { universe@43: uint8_t code = net_recieve_code(opponent); universe@34: universe@34: Move move; universe@34: switch (code) { universe@34: case NETCODE_TIMEOVER: universe@34: printw("\rYour opponent's time ran out - you win!"); universe@7: clrtoeol(); universe@7: return 1; universe@44: case NETCODE_RESIGN: universe@50: gamestate->resign = 1; universe@44: printw("\rYour opponent resigned!"); universe@34: clrtoeol(); universe@34: return 1; universe@46: case NETCODE_CONNLOST: universe@46: printw("\rYour opponent has left the game."); universe@46: clrtoeol(); universe@46: return 1; universe@34: case NETCODE_REMIS: universe@34: if (prompt_yesno( universe@34: "\rYour opponent offers remis - do you accept")) { universe@50: gamestate->remis = 1; universe@34: printw("\rRemis accepted!"); universe@7: clrtoeol(); universe@34: net_send_code(opponent, NETCODE_ACCEPT); universe@7: return 1; universe@7: } else { universe@34: net_send_code(opponent, NETCODE_DECLINE); universe@7: } universe@34: break; universe@34: case NETCODE_MOVE: universe@51: net_recieve_data(opponent, &move, sizeof(Move)); universe@48: code = validate_move(gamestate, &move); universe@48: if (code == VALID_MOVE_SEMANTICS) { universe@34: apply_move(gamestate, &move); universe@51: if (gamestate->checkmate) { universe@34: net_send_code(opponent, NETCODE_CHECKMATE); universe@34: printw("\rCheckmate!"); universe@34: clrtoeol(); universe@34: return 1; universe@34: } else if (gamestate->stalemate) { universe@34: net_send_code(opponent, NETCODE_STALEMATE); universe@34: printw("\rStalemate!"); universe@34: clrtoeol(); universe@34: return 1; universe@51: } else if (move.check) { universe@51: net_send_code(opponent, NETCODE_CHECK); universe@34: } else { universe@34: net_send_code(opponent, NETCODE_ACCEPT); universe@34: } universe@34: return 0; universe@34: } else { universe@48: uint32_t reason = htonl(code); universe@48: net_send_data(opponent, NETCODE_DECLINE, universe@48: &reason, sizeof(uint32_t)); universe@34: } universe@45: break; universe@45: default: universe@45: printw("\nInvalid network request."); universe@33: } universe@7: } universe@7: } universe@7: } universe@6: universe@50: static void post_game(GameState *gamestate, GameInfo *gameinfo) { universe@50: move(0,0); universe@50: draw_board(gamestate, WHITE); universe@50: universe@50: // TODO: network connection is still open here - think about it! universe@50: universe@50: mvaddstr(getmaxy(stdscr)-1, 0, universe@50: "Press 'q' to quit or 's' to save a PGN file..."); universe@50: refresh(); universe@50: flushinp(); universe@50: universe@50: noecho(); universe@50: int c; universe@50: do { universe@50: c = getch(); universe@50: if (c == 's') { universe@50: addch('\r'); universe@50: echo(); universe@50: save_pgn(gamestate, gameinfo); universe@50: addstr(" Press 'q' to quit..."); universe@50: noecho(); universe@50: } universe@50: } while (c != 'q'); universe@50: echo(); universe@50: universe@50: gamestate_cleanup(gamestate); universe@26: } universe@26: universe@26: void game_start_singlemachine(Settings *settings) { universe@33: inputy = getmaxy(stdscr) - 6; universe@33: universe@26: GameState gamestate; universe@50: gamestate_init(&gamestate); universe@50: uint8_t curcol = WHITE; universe@50: universe@50: if (settings->continuepgn) { universe@50: FILE *pgnfile = fopen(settings->continuepgn, "r"); universe@50: if (pgnfile) { universe@50: int result = read_pgn(pgnfile, &gamestate, &(settings->gameinfo)); universe@60: long position = ftell(pgnfile); universe@50: fclose(pgnfile); universe@59: if (result) { universe@60: printw("Invalid PGN file content at position %ld:\n%s\n", universe@60: position, pgn_error_str(result)); universe@50: return; universe@50: } universe@50: if (!is_game_running(&gamestate)) { universe@50: addstr("Game has ended. Use -S to analyze it.\n"); universe@50: return; universe@50: } universe@50: curcol = opponent_color(gamestate.lastmove->move.piece&COLOR_MASK); universe@50: } else { universe@50: printw("Can't read PGN file (%s)\n", strerror(errno)); universe@50: return; universe@50: } universe@50: } universe@30: universe@26: _Bool running; universe@26: do { universe@26: clear(); universe@50: draw_board(&gamestate, curcol); universe@50: running = !domove_singlemachine(&gamestate, universe@50: &(settings->gameinfo), curcol); universe@50: curcol = opponent_color(curcol); universe@26: } while (running); universe@26: universe@50: post_game(&gamestate, &(settings->gameinfo)); universe@26: } universe@26: universe@51: void game_continue(Settings *settings, int opponent, GameState *gamestate) { universe@33: inputy = getmaxy(stdscr) - 6; universe@33: universe@51: uint8_t mycolor = is_server(settings) ? settings->gameinfo.servercolor : universe@51: opponent_color(settings->gameinfo.servercolor); universe@8: universe@51: _Bool myturn = (gamestate->lastmove ? universe@53: (gamestate->lastmove->move.piece & COLOR_MASK) : BLACK) != mycolor; universe@7: universe@23: _Bool running; universe@7: do { universe@7: clear(); universe@51: draw_board(gamestate, mycolor); universe@7: if (myturn) { universe@51: running = !sendmove(gamestate, &(settings->gameinfo), universe@50: opponent, mycolor); universe@7: } else { universe@51: running = !recvmove(gamestate, &(settings->gameinfo), opponent); universe@7: } universe@11: myturn ^= TRUE; universe@7: } while (running); universe@7: universe@51: post_game(gamestate, &(settings->gameinfo)); universe@6: } universe@51: universe@51: void game_start(Settings *settings, int opponent) { universe@51: GameState gamestate; universe@51: gamestate_init(&gamestate); universe@51: universe@51: game_continue(settings, opponent, &gamestate); universe@51: }