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@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@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@23: static void draw_board(GameState *gamestate) { 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@23: int cy = gamestate->mycolor == WHITE ? boardy-y : boardy-7+y; universe@23: int cx = gamestate->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@23: int x = gamestate->mycolor == WHITE ? boardx+i*3+1 : boardx+22-i*3; universe@23: int y = gamestate->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@18: universe@18: /* move log */ universe@18: // TODO: introduce window to avoid bugs with a long move log universe@18: uint8_t logy = 0; 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@18: if (logelem) { universe@18: Move move = logelem->move; universe@25: if ((move.piece&PIECE_MASK) == KING && universe@25: abs(move.tofile-move.fromfile) == 2) { universe@25: addstr(move.tofile==fileidx('c')?"O-O-O":"O-O"); universe@25: } else { universe@25: char logstr[] = { universe@25: getpiecechr(move.piece), universe@25: filechr(move.fromfile), rowchr(move.fromrow), universe@25: move.capture ? 'x':'\0', universe@25: filechr(move.tofile), rowchr(move.torow), universe@27: move.check ? '+' : (move.promotion ? '=' : '\0'), universe@25: move.promotion ? getpiecechr(move.promotion) : '\0' universe@25: }; universe@25: for (int stri = 0 ; stri < sizeof(logstr) ; stri++) { universe@25: if (logstr[stri]) { universe@25: addch(logstr[stri]); universe@25: } universe@18: } universe@18: } universe@27: if (!logelem->next) { universe@27: if (gamestate->checkmate) { universe@28: addstr("\b#"); universe@27: } else if (gamestate->stalemate) { universe@27: addstr(" stalemate"); universe@27: } universe@27: } universe@25: addch(' '); universe@18: universe@18: logelem = logelem->next; universe@18: } 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@26: printw("Cannot find the piece that shall be moved."); 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@26: default: universe@26: printw("Can't interpret move - please use algebraic notation."); universe@26: } universe@26: } universe@26: universe@37: #define MOVESTR_BUFLEN 8 universe@30: static int domove_singlemachine(GameState *gamestate, GameInfo *gameinfo) { 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@26: "Or type 'surr' to surrender or 'remis' to end with remis.\n\n" universe@26: "Type your move: "); universe@26: clrtoeol(); universe@30: universe@37: if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) { universe@37: if (strncmp(movestr, "surr", MOVESTR_BUFLEN) == 0) { universe@30: printw("%s surrendered!", universe@30: gamestate->mycolor==WHITE?"White":"Black"); universe@30: clrtoeol(); universe@30: refresh(); universe@30: return 1; universe@37: } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) { universe@30: printw("Game ends remis."); universe@30: clrtoeol(); universe@30: refresh(); universe@30: return 1; universe@30: } else { universe@30: Move move; universe@30: int eval_result = eval_move(gamestate, movestr, &move); universe@30: switch (eval_result) { universe@30: case VALID_MOVE_SYNTAX: universe@30: if (validate_move(gamestate, &move)) { 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@30: printw("Invalid move."); universe@26: } universe@30: break; universe@30: default: universe@30: eval_move_failed_msg(eval_result); universe@26: } universe@30: clrtoeol(); universe@26: } universe@26: } universe@26: } universe@26: } universe@8: universe@33: static int sendmove(GameState *gamestate, GameInfo *gameinfo, int opponent) { 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@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@33: universe@37: if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) { universe@37: if (strncmp(movestr, "surr", MOVESTR_BUFLEN) == 0) { universe@33: printw("You surrendered!"); universe@33: clrtoeol(); universe@7: refresh(); universe@33: net_send_code(opponent, NETCODE_SURRENDER); universe@33: return 1; 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@33: if (net_recieve_code(opponent) == NETCODE_ACCEPT) { universe@33: printw("\rRemis accepted!"); universe@18: clrtoeol(); universe@33: refresh(); universe@26: return 1; universe@14: } else { universe@33: remisrejected = TRUE; universe@11: } universe@18: } universe@33: } else { universe@33: Move move; universe@33: int eval_result = eval_move(gamestate, movestr, &move); universe@33: switch (eval_result) { universe@33: case VALID_MOVE_SYNTAX: universe@33: net_send_data(opponent, NETCODE_MOVE, &move, sizeof(Move)); universe@33: code = net_recieve_code(opponent); universe@33: move.check = code == NETCODE_CHECK; universe@33: gamestate->checkmate = code == NETCODE_CHECKMATE; universe@33: gamestate->stalemate = code == NETCODE_STALEMATE; universe@33: if (code == NETCODE_DECLINE) { universe@33: printw("Invalid move."); universe@33: } else { 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@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: if (net_setnonblocking(opponent, 1)) { universe@34: printw("Cannot setup nonblocking IO on network socket"); universe@34: cbreak(); getch(); universe@34: exit(EXIT_FAILURE); universe@34: } universe@34: 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@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@34: uint32_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@34: case NETCODE_SURRENDER: universe@34: printw("\rYour opponent surrendered!"); universe@34: clrtoeol(); universe@34: return 1; universe@34: case NETCODE_REMIS: universe@34: if (prompt_yesno( universe@34: "\rYour opponent offers remis - do you accept")) { 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@34: net_recieve_data(opponent, &move, sizeof(Move)); universe@34: if (validate_move(gamestate, &move)) { universe@34: apply_move(gamestate, &move); universe@34: if (move.check) { universe@34: net_send_code(opponent, NETCODE_CHECK); universe@34: } else 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@34: } else { universe@34: net_send_code(opponent, NETCODE_ACCEPT); universe@34: } universe@34: return 0; universe@34: } else { universe@34: net_send_code(opponent, NETCODE_DECLINE); universe@34: } universe@33: } universe@7: } universe@7: } universe@7: } universe@6: universe@26: static void init_board(GameState *gamestate) { universe@26: Board initboard = { universe@26: {WROOK, WKNIGHT, WBISHOP, WQUEEN, WKING, WBISHOP, WKNIGHT, WROOK}, universe@26: {WPAWN, WPAWN, WPAWN, WPAWN, WPAWN, WPAWN, WPAWN, WPAWN}, universe@26: {0, 0, 0, 0, 0, 0, 0, 0}, universe@26: {0, 0, 0, 0, 0, 0, 0, 0}, universe@26: {0, 0, 0, 0, 0, 0, 0, 0}, universe@26: {0, 0, 0, 0, 0, 0, 0, 0}, universe@26: {BPAWN, BPAWN, BPAWN, BPAWN, BPAWN, BPAWN, BPAWN, BPAWN}, universe@26: {BROOK, BKNIGHT, BBISHOP, BQUEEN, BKING, BBISHOP, BKNIGHT, BROOK} universe@26: }; universe@26: memcpy(gamestate->board, initboard, sizeof(Board)); 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@28: memset(&gamestate, 0, sizeof(GameState)); universe@26: init_board(&gamestate); universe@26: gamestate.mycolor = WHITE; universe@30: universe@26: _Bool running; universe@26: do { universe@26: clear(); universe@26: draw_board(&gamestate); universe@30: running = !domove_singlemachine(&gamestate, &(settings->gameinfo)); universe@26: gamestate.mycolor = opponent_color(gamestate.mycolor); universe@26: } while (running); universe@28: move(0,0); universe@28: draw_board(&gamestate); universe@26: universe@26: gamestate_cleanup(&gamestate); universe@26: universe@26: mvaddstr(getmaxy(stdscr)-1, 0, universe@26: "Game has ended. Press any key to leave..."); universe@26: refresh(); universe@35: cbreak(); universe@35: flushinp(); universe@26: getch(); universe@26: } universe@26: universe@6: void game_start(Settings *settings, int opponent) { universe@33: inputy = getmaxy(stdscr) - 6; universe@33: universe@7: _Bool myturn = is_server(settings) == universe@7: (settings->gameinfo.servercolor == WHITE); universe@8: universe@23: GameState gamestate; universe@28: memset(&gamestate, 0, sizeof(GameState)); universe@26: init_board(&gamestate); universe@23: gamestate.mycolor = myturn ? WHITE:BLACK; universe@7: universe@23: _Bool running; universe@7: do { universe@7: clear(); universe@23: draw_board(&gamestate); universe@7: if (myturn) { universe@33: running = !sendmove(&gamestate, &(settings->gameinfo), opponent); universe@7: } else { universe@33: running = !recvmove(&gamestate, &(settings->gameinfo), opponent); universe@7: } universe@11: myturn ^= TRUE; universe@7: } while (running); universe@7: universe@28: move(0,0); universe@28: draw_board(&gamestate); universe@28: universe@23: gamestate_cleanup(&gamestate); universe@18: universe@23: mvaddstr(getmaxy(stdscr)-1, 0, universe@7: "Game has ended. Press any key to leave..."); universe@26: refresh(); universe@33: cbreak(); universe@33: flushinp(); universe@7: getch(); universe@6: }