src/game.c

Wed, 09 Apr 2014 12:07:47 +0200

author
Mike Becker <universe@uap-core.de>
date
Wed, 09 Apr 2014 12:07:47 +0200
changeset 34
c4d4b8a8f902
parent 33
866025982aa9
child 35
6c64b7a073af
permissions
-rw-r--r--

added nonblocking read for network games + minor build system fixes

     1 /*
     2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3  *
     4  * Copyright 2014 Mike Becker. All rights reserved.
     5  *
     6  * Redistribution and use in source and binary forms, with or without
     7  * modification, are permitted provided that the following conditions are met:
     8  *
     9  *   1. Redistributions of source code must retain the above copyright
    10  *      notice, this list of conditions and the following disclaimer.
    11  *
    12  *   2. Redistributions in binary form must reproduce the above copyright
    13  *      notice, this list of conditions and the following disclaimer in the
    14  *      documentation and/or other materials provided with the distribution.
    15  *
    16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    17  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    19  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    20  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    26  * POSSIBILITY OF SUCH DAMAGE.
    27  *
    28  */
    30 #include "game.h"
    31 #include "network.h"
    32 #include "input.h"
    33 #include <ncurses.h>
    34 #include <string.h>
    35 #include <inttypes.h>
    36 #include <sys/select.h>
    38 static const uint8_t boardx = 10, boardy = 10;
    39 static int inputy = 21; /* should be overridden on game startup */
    41 static int timecontrol(GameState *gamestate, GameInfo *gameinfo) {
    42     if (gameinfo->timecontrol) {
    43         uint16_t white = remaining_movetime(gameinfo, gamestate, WHITE);
    44         uint16_t black = remaining_movetime(gameinfo, gamestate, BLACK);
    45         mvprintw(boardy+4, boardx-1,
    46             "White time: %4" PRIu16 ":%02" PRIu16,
    47             white / 60, white % 60);
    48         mvprintw(boardy+5, boardx-1,
    49             "Black time: %4" PRIu16 ":%02" PRIu16,
    50             black / 60, black % 60);
    52         if (white == 0) {
    53             move(inputy, 0);
    54             printw("Time is over - Black wins!");
    55             clrtobot();
    56             refresh();
    57             return 1;
    58         }
    59         if (black == 0) {
    60             move(inputy, 0);
    61             printw("Time is over - White wins!");
    62             clrtobot();
    63             refresh();
    64             return 1;
    65         }
    66     }
    68     return 0;
    69 }
    71 static void draw_board(GameState *gamestate) {
    72     for (uint8_t y = 0 ; y < 8 ; y++) {
    73         for (uint8_t x = 0 ; x < 8 ; x++) {
    74             uint8_t col = gamestate->board[y][x] & COLOR_MASK;
    75             uint8_t piece = gamestate->board[y][x] & PIECE_MASK;
    76             char piecec;
    77             if (piece) {
    78                 piecec = piece == PAWN ? 'P' : getpiecechr(piece);
    79             } else {
    80                 piecec = ' ';
    81             }
    83             attrset((col == WHITE ? A_BOLD : A_DIM) |
    84                 COLOR_PAIR((y&1)==(x&1) ? COL_WB : COL_BW));
    86             int cy = gamestate->mycolor == WHITE ? boardy-y : boardy-7+y;
    87             int cx = gamestate->mycolor == WHITE ? boardx+x*3 : boardx+21-x*3;
    88             mvaddch(cy, cx, ' ');
    89             mvaddch(cy, cx+1, piecec);
    90             mvaddch(cy, cx+2, ' ');
    91         }
    92     }
    94     attrset(A_NORMAL);
    95     for (uint8_t i = 0 ; i < 8 ; i++) {
    96         int x = gamestate->mycolor == WHITE ? boardx+i*3+1 : boardx+22-i*3;
    97         int y = gamestate->mycolor == WHITE ? boardy-i : boardy-7+i;
    98         mvaddch(boardy+1, x, 'a'+i);
    99         mvaddch(y, boardx-2, '1'+i);
   100     }
   102     /* move log */
   103     // TODO: introduce window to avoid bugs with a long move log
   104     uint8_t logy = 0;
   105     const uint8_t logx = boardx + 30;
   106     int logi = 1;
   107     MoveList *logelem = gamestate->movelist;
   109     while (logelem) {
   110         logi++;
   111         if (logi % 2 == 0) {
   112             if ((logi - 2) % 4 == 0) {
   113                 logy++;
   114                 move(logy, logx);
   115             }
   116             printw("%d. ", logi / 2);
   117         }
   119         if (logelem) {
   120             Move move = logelem->move;
   121             if ((move.piece&PIECE_MASK) == KING &&
   122                 abs(move.tofile-move.fromfile) == 2) {
   123                 addstr(move.tofile==fileidx('c')?"O-O-O":"O-O");
   124             } else {
   125                 char logstr[] = {
   126                     getpiecechr(move.piece),
   127                     filechr(move.fromfile), rowchr(move.fromrow),
   128                     move.capture ? 'x':'\0',
   129                     filechr(move.tofile), rowchr(move.torow),
   130                     move.check ? '+' : (move.promotion ? '=' : '\0'),
   131                     move.promotion ? getpiecechr(move.promotion) : '\0'
   132                 };
   133                 for (int stri = 0 ; stri < sizeof(logstr) ; stri++) {
   134                     if (logstr[stri]) {
   135                         addch(logstr[stri]);
   136                     }
   137                 }
   138             }
   139             if (!logelem->next) {
   140                 if (gamestate->checkmate) {
   141                     addstr("\b#");
   142                 } else if (gamestate->stalemate) {
   143                     addstr(" stalemate");
   144                 }
   145             }
   146             addch(' ');
   148             logelem = logelem->next;
   149         }
   150     }
   151 }
   153 static void eval_move_failed_msg(int code) {
   154     switch (code) {
   155     case AMBIGUOUS_MOVE:
   156         printw("Ambiguous move - please specify the piece to move.");
   157         break;
   158     case INVALID_POSITION:
   159         printw("Cannot find the piece that shall be moved.");
   160         break;
   161     case NEED_PROMOTION:
   162         printw("You need to promote the pawn (append \"=Q\" e.g.)!");
   163         break;
   164     default:
   165         printw("Can't interpret move - please use algebraic notation.");
   166     }
   167 }
   169 static int domove_singlemachine(GameState *gamestate, GameInfo *gameinfo) {
   171     const size_t buflen = 8;
   172     size_t bufpos = 0;
   173     char movestr[buflen];
   175     flushinp();
   176     while (1) {
   177         if (timecontrol(gamestate, gameinfo)) {
   178             return 1;
   179         }
   181         move(inputy, 0);
   182         printw(
   183             "Use chess notation to enter your move.\n"
   184             "Or type 'surr' to surrender or 'remis' to end with remis.\n\n"
   185             "Type your move: ");
   186         clrtoeol();
   188         if (asyncgetnstr(movestr, &bufpos, buflen)) {
   189             if (strncmp(movestr, "surr", buflen) == 0) {
   190                 printw("%s surrendered!",
   191                     gamestate->mycolor==WHITE?"White":"Black");
   192                 clrtoeol();
   193                 refresh();
   194                 return 1;
   195             } else if (strncmp(movestr, "remis", buflen) == 0) {
   196                 printw("Game ends remis.");
   197                 clrtoeol();
   198                 refresh();
   199                 return 1;
   200             } else {
   201                 Move move;
   202                 int eval_result = eval_move(gamestate, movestr, &move);
   203                 switch (eval_result) {
   204                 case VALID_MOVE_SYNTAX:
   205                     if (validate_move(gamestate, &move)) {
   206                         apply_move(gamestate, &move);
   207                         if (gamestate->checkmate) {
   208                             printw("Checkmate!");
   209                             clrtoeol();
   210                             return 1;
   211                         } else if (gamestate->stalemate) {
   212                             printw("Stalemate!");
   213                             clrtoeol();
   214                             return 1;
   215                         } else {
   216                             return 0;
   217                         }
   218                     } else {
   219                         printw("Invalid move.");
   220                     }
   221                     break;
   222                 default:
   223                     eval_move_failed_msg(eval_result);
   224                 }
   225                 clrtoeol();
   226             }
   227         }
   228     }
   229 }
   231 static int sendmove(GameState *gamestate, GameInfo *gameinfo, int opponent) {
   233     const size_t buflen = 8;
   234     size_t bufpos = 0;
   235     char movestr[buflen];
   236     _Bool remisrejected = FALSE;
   237     uint8_t code;
   239     flushinp();
   240     while (1) {
   241         if (timecontrol(gamestate, gameinfo)) {
   242             net_send_code(opponent, NETCODE_TIMEOVER);
   243             return 1;
   244         }
   246         move(inputy, 0);
   247         if (remisrejected) {
   248             printw(
   249                 "Use chess notation to enter your move.\n"
   250                 "Remis offer rejected - type 'surr' to surrender.      \n\n"
   251                 "Type your move: ");
   252         } else {
   253             printw(
   254                 "Use chess notation to enter your move.\n"
   255                 "Or type 'surr' to surrender or 'remis' to offer remis.\n\n"
   256                 "Type your move: ");
   257         }
   258         clrtoeol();
   260         if (asyncgetnstr(movestr, &bufpos, buflen)) {
   261             if (strncmp(movestr, "surr", buflen) == 0) {
   262                 printw("You surrendered!");
   263                 clrtoeol();
   264                 refresh();
   265                 net_send_code(opponent, NETCODE_SURRENDER);
   266                 return 1;
   267             } else if (strncmp(movestr, "remis", buflen) == 0) {
   268                 if (!remisrejected) {
   269                     net_send_code(opponent, NETCODE_REMIS);
   270                     printw("Remis offer sent - waiting for acceptance...");
   271                     refresh();
   272                     if (net_recieve_code(opponent) == NETCODE_ACCEPT) {
   273                         printw("\rRemis accepted!");
   274                         clrtoeol();
   275                         refresh();
   276                         return 1;
   277                     } else {
   278                         remisrejected = TRUE;
   279                     }
   280                 }
   281             } else {
   282                 Move move;
   283                 int eval_result = eval_move(gamestate, movestr, &move);
   284                 switch (eval_result) {
   285                 case VALID_MOVE_SYNTAX:
   286                     net_send_data(opponent, NETCODE_MOVE, &move, sizeof(Move));
   287                     code = net_recieve_code(opponent);
   288                     move.check = code == NETCODE_CHECK;
   289                     gamestate->checkmate = code == NETCODE_CHECKMATE;
   290                     gamestate->stalemate = code == NETCODE_STALEMATE;
   291                     if (code == NETCODE_DECLINE) {
   292                         printw("Invalid move.");
   293                     } else {
   294                         apply_move(gamestate, &move);
   295                         if (gamestate->checkmate) {
   296                             printw("Checkmate!");
   297                             clrtoeol();
   298                             return 1;
   299                         } else if (gamestate->stalemate) {
   300                             printw("Stalemate!");
   301                             clrtoeol();
   302                             return 1;
   303                         } else {
   304                             return 0;
   305                         }
   306                     }
   307                     break;
   308                 default:
   309                     eval_move_failed_msg(eval_result);
   310                 }
   311                 clrtoeol();
   312             }
   313         }
   314     }
   315 }
   317 static int recvmove(GameState *gamestate, GameInfo *gameinfo, int opponent) {
   319     if (net_setnonblocking(opponent, 1)) {
   320         printw("Cannot setup nonblocking IO on network socket");
   321         cbreak(); getch();
   322         exit(EXIT_FAILURE);
   323     }
   325     struct timeval timeout;
   326     while (1) {
   327         timecontrol(gamestate, gameinfo);
   329         move(inputy, 0);
   330         printw("Awaiting opponent move...");
   331         clrtoeol();
   332         refresh();
   334         fd_set readfds;
   336         FD_ZERO(&readfds);
   337         FD_SET(opponent, &readfds);
   338         timeout.tv_sec = 0;
   339         timeout.tv_usec = 1e5;
   341         int result = select(opponent+1, &readfds, NULL, NULL, &timeout);
   342         if (result == -1) {
   343             printw("\rCannot perform asynchronous network IO");
   344             cbreak(); getch();
   345             exit(EXIT_FAILURE);
   346         }
   347         if (result > 0) {
   348             uint32_t code = net_recieve_code(opponent);
   350             Move move;
   351             switch (code) {
   352             case NETCODE_TIMEOVER:
   353                 printw("\rYour opponent's time ran out - you win!");
   354                 clrtoeol();
   355                 return 1;
   356             case NETCODE_SURRENDER:
   357                 printw("\rYour opponent surrendered!");
   358                 clrtoeol();
   359                 return 1;
   360             case NETCODE_REMIS:
   361                 if (prompt_yesno(
   362                     "\rYour opponent offers remis - do you accept")) {
   363                     printw("\rRemis accepted!");
   364                     clrtoeol();
   365                     net_send_code(opponent, NETCODE_ACCEPT);
   366                     return 1;
   367                 } else {
   368                     net_send_code(opponent, NETCODE_DECLINE);
   369                 }
   370                 break;
   371             case NETCODE_MOVE:
   372                 net_recieve_data(opponent, &move, sizeof(Move));
   373                 if (validate_move(gamestate, &move)) {
   374                     apply_move(gamestate, &move);
   375                     if (move.check) {
   376                         net_send_code(opponent, NETCODE_CHECK);
   377                     } else if (gamestate->checkmate) {
   378                         net_send_code(opponent, NETCODE_CHECKMATE);
   379                         printw("\rCheckmate!");
   380                         clrtoeol();
   381                         return 1;
   382                     } else if (gamestate->stalemate) {
   383                         net_send_code(opponent, NETCODE_STALEMATE);
   384                         printw("\rStalemate!");
   385                         clrtoeol();
   386                         return 1;
   387                     } else {
   388                         net_send_code(opponent, NETCODE_ACCEPT);
   389                     }
   390                     return 0;
   391                 } else {
   392                     net_send_code(opponent, NETCODE_DECLINE);
   393                 }
   394             }
   395         }
   396     }
   397 }
   399 static void init_board(GameState *gamestate) {
   400     Board initboard = {
   401         {WROOK, WKNIGHT, WBISHOP, WQUEEN, WKING, WBISHOP, WKNIGHT, WROOK},
   402         {WPAWN, WPAWN,   WPAWN,   WPAWN,  WPAWN, WPAWN,   WPAWN,   WPAWN},
   403         {0,     0,       0,       0,      0,     0,       0,       0},
   404         {0,     0,       0,       0,      0,     0,       0,       0},
   405         {0,     0,       0,       0,      0,     0,       0,       0},
   406         {0,     0,       0,       0,      0,     0,       0,       0},
   407         {BPAWN, BPAWN,   BPAWN,   BPAWN,  BPAWN, BPAWN,   BPAWN,   BPAWN},
   408         {BROOK, BKNIGHT, BBISHOP, BQUEEN, BKING, BBISHOP, BKNIGHT, BROOK}
   409     };
   410     memcpy(gamestate->board, initboard, sizeof(Board));
   411 }
   413 void game_start_singlemachine(Settings *settings) {
   414     inputy = getmaxy(stdscr) - 6;
   416     GameState gamestate;
   417     memset(&gamestate, 0, sizeof(GameState));
   418     init_board(&gamestate);
   419     gamestate.mycolor = WHITE;
   421     _Bool running;
   422     do {
   423         clear();
   424         draw_board(&gamestate);
   425         running = !domove_singlemachine(&gamestate, &(settings->gameinfo));
   426         gamestate.mycolor = opponent_color(gamestate.mycolor);
   427     }  while (running);
   428     move(0,0);
   429     draw_board(&gamestate);
   431     gamestate_cleanup(&gamestate);
   433     mvaddstr(getmaxy(stdscr)-1, 0,
   434         "Game has ended. Press any key to leave...");
   435     refresh();
   436     getch();
   437 }
   439 void game_start(Settings *settings, int opponent) {
   440     inputy = getmaxy(stdscr) - 6;
   442     _Bool myturn = is_server(settings) ==
   443         (settings->gameinfo.servercolor == WHITE);
   445     GameState gamestate;
   446     memset(&gamestate, 0, sizeof(GameState));
   447     init_board(&gamestate);
   448     gamestate.mycolor = myturn ? WHITE:BLACK;
   450     _Bool running;
   451     do {
   452         clear();
   453         draw_board(&gamestate);
   454         if (myturn) {
   455             running = !sendmove(&gamestate, &(settings->gameinfo), opponent);
   456         } else {
   457             running = !recvmove(&gamestate, &(settings->gameinfo), opponent);
   458         }
   459         myturn ^= TRUE;
   460     }  while (running);
   462     move(0,0);
   463     draw_board(&gamestate);
   465     gamestate_cleanup(&gamestate);
   467     mvaddstr(getmaxy(stdscr)-1, 0,
   468         "Game has ended. Press any key to leave...");
   469     refresh();
   470     cbreak();
   471     flushinp();
   472     getch();
   473 }

mercurial