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