Fri, 04 Apr 2014 17:36:42 +0200
NEED TESTING: implemented check and checkmate - TODO: avoid checkmate by moving another piece in between
/* * 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 "network.h" #include "input.h" #include <ncurses.h> #include <string.h> static const uint8_t boardx = 10, boardy = 10; static void draw_board(GameState *gamestate) { for (uint8_t y = 0 ; y < 8 ; y++) { for (uint8_t x = 0 ; x < 8 ; x++) { uint8_t col = gamestate->board[y][x] & COLOR_MASK; uint8_t piece = gamestate->board[y][x] & PIECE_MASK; char piecec; if (piece) { piecec = piece == PAWN ? 'P' : getpiecechr(piece); } else { piecec = ' '; } attrset((col == WHITE ? A_BOLD : A_DIM) | COLOR_PAIR((y&1)==(x&1) ? COL_WB : COL_BW)); int cy = gamestate->mycolor == WHITE ? boardy-y : boardy-7+y; int cx = gamestate->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 = gamestate->mycolor == WHITE ? boardx+i*3+1 : boardx+22-i*3; int y = gamestate->mycolor == WHITE ? boardy-i : boardy-7+i; mvaddch(boardy+1, x, 'a'+i); mvaddch(y, boardx-2, '1'+i); } /* move log */ // TODO: introduce window to avoid bugs with a long move log uint8_t logy = 0; const uint8_t logx = boardx + 30; int logi = 1; MoveList *logelem = gamestate->movelist; while (logelem) { logi++; if (logi % 2 == 0) { if ((logi - 2) % 4 == 0) { logy++; move(logy, logx); } printw("%d. ", logi / 2); } if (logelem) { Move move = logelem->move; if ((move.piece&PIECE_MASK) == KING && abs(move.tofile-move.fromfile) == 2) { addstr(move.tofile==fileidx('c')?"O-O-O":"O-O"); } else { char logstr[] = { getpiecechr(move.piece), filechr(move.fromfile), rowchr(move.fromrow), move.capture ? 'x':'\0', filechr(move.tofile), rowchr(move.torow), move.check ? '+' : (move.promotion ? '=' : '\0'), move.promotion ? getpiecechr(move.promotion) : '\0' }; for (int stri = 0 ; stri < sizeof(logstr) ; stri++) { if (logstr[stri]) { addch(logstr[stri]); } } } if (!logelem->next) { if (gamestate->checkmate) { addstr("\b#"); } else if (gamestate->stalemate) { addstr(" stalemate"); } } addch(' '); logelem = logelem->next; } } } static void eval_move_failed_msg(int code) { switch (code) { case AMBIGUOUS_MOVE: printw("Ambiguous move - please specify the piece to move."); break; case INVALID_POSITION: printw("Cannot find the piece that shall be moved."); break; case NEED_PROMOTION: printw("You need to promote the pawn (append \"=Q\" e.g.)!"); break; default: printw("Can't interpret move - please use algebraic notation."); } } static int domove_singlemachine(GameState *gamestate) { const size_t buflen = 8; char movestr[buflen]; int inputy = getmaxy(stdscr) - 6; while (1) { move(inputy, 0); printw( "Use chess notation to enter your move.\n" "Or type 'surr' to surrender or 'remis' to end with remis.\n\n" "Type your move: "); clrtoeol(); refresh(); getnstr(movestr, buflen); if (strncmp(movestr, "surr", buflen) == 0) { printw("%s surrendered!", gamestate->mycolor==WHITE?"White":"Black"); clrtoeol(); refresh(); return 1; } else if (strncmp(movestr, "remis", buflen) == 0) { printw("Game ends remis."); clrtoeol(); refresh(); return 1; } else { Move move; int eval_result = eval_move(gamestate, movestr, &move); switch (eval_result) { case VALID_MOVE_SYNTAX: if (validate_move(gamestate, &move)) { apply_move(gamestate, &move); if (gamestate->checkmate) { printw("Checkmate!"); clrtoeol(); return 1; } else if (gamestate->stalemate) { printw("Stalemate!"); clrtoeol(); return 1; } else { return 0; } } else { printw("Invalid move."); } break; default: eval_move_failed_msg(eval_result); } clrtoeol(); } } } static int sendmove(GameState *gamestate, int opponent) { const size_t buflen = 8; char movestr[buflen]; _Bool remisrejected = FALSE; uint8_t code; int inputy = getmaxy(stdscr) - 6; while (1) { move(inputy, 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!"); clrtoeol(); 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; int eval_result = eval_move(gamestate, movestr, &move); switch (eval_result) { case VALID_MOVE_SYNTAX: net_send_data(opponent, NETCODE_MOVE, &move, sizeof(Move)); code = net_recieve_code(opponent); move.check = code == NETCODE_CHECK; gamestate->checkmate = code == NETCODE_CHECKMATE; gamestate->stalemate = code == NETCODE_STALEMATE; if (code == NETCODE_DECLINE) { printw("Invalid move."); } else { apply_move(gamestate, &move); if (gamestate->checkmate) { printw("Checkmate!"); clrtoeol(); return 1; } else if (gamestate->stalemate) { printw("Stalemate!"); clrtoeol(); return 1; } else { return 0; } } break; default: eval_move_failed_msg(eval_result); } clrtoeol(); } } } static int recvmove(GameState *gamestate, int opponent) { int inputy = getmaxy(stdscr) - 6; while (1) { move(inputy, 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(gamestate, &move)) { apply_move(gamestate, &move); if (move.check) { net_send_code(opponent, NETCODE_CHECK); } else if (gamestate->checkmate) { net_send_code(opponent, NETCODE_CHECKMATE); printw("\rCheckmate!"); clrtoeol(); return 1; } else if (gamestate->stalemate) { net_send_code(opponent, NETCODE_STALEMATE); printw("\rStalemate!"); clrtoeol(); return 1; } else { net_send_code(opponent, NETCODE_ACCEPT); } return 0; } else { net_send_code(opponent, NETCODE_DECLINE); } } } } static void init_board(GameState *gamestate) { Board initboard = { {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} }; memcpy(gamestate->board, initboard, sizeof(Board)); } void game_start_singlemachine(Settings *settings) { GameState gamestate; memset(&gamestate, 0, sizeof(GameState)); init_board(&gamestate); gamestate.mycolor = WHITE; // TODO: time limit _Bool running; do { clear(); draw_board(&gamestate); running = !domove_singlemachine(&gamestate); gamestate.mycolor = opponent_color(gamestate.mycolor); } while (running); move(0,0); draw_board(&gamestate); gamestate_cleanup(&gamestate); mvaddstr(getmaxy(stdscr)-1, 0, "Game has ended. Press any key to leave..."); refresh(); getch(); } void game_start(Settings *settings, int opponent) { _Bool myturn = is_server(settings) == (settings->gameinfo.servercolor == WHITE); // TODO: time limit GameState gamestate; memset(&gamestate, 0, sizeof(GameState)); init_board(&gamestate); gamestate.mycolor = myturn ? WHITE:BLACK; _Bool running; do { clear(); draw_board(&gamestate); if (myturn) { running = !sendmove(&gamestate, opponent); } else { running = !recvmove(&gamestate, opponent); flushinp(); // flush any input the user hacked in while waiting } myturn ^= TRUE; } while (running); move(0,0); draw_board(&gamestate); gamestate_cleanup(&gamestate); mvaddstr(getmaxy(stdscr)-1, 0, "Game has ended. Press any key to leave..."); refresh(); getch(); }