src/game.c

Thu, 10 Apr 2014 12:10:09 +0200

author
Mike Becker <universe@uap-core.de>
date
Thu, 10 Apr 2014 12:10:09 +0200
changeset 37
bcf624518909
parent 35
6c64b7a073af
child 42
21cb830efe91
permissions
-rw-r--r--

minor improvements by using macros

     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 "colors.h"
    34 #include <ncurses.h>
    35 #include <string.h>
    36 #include <inttypes.h>
    37 #include <sys/select.h>
    39 static const uint8_t boardx = 10, boardy = 10;
    40 static int inputy = 21; /* should be overridden on game startup */
    42 static int timecontrol(GameState *gamestate, GameInfo *gameinfo) {
    43     if (gameinfo->timecontrol) {
    44         uint16_t white = remaining_movetime(gameinfo, gamestate, WHITE);
    45         uint16_t black = remaining_movetime(gameinfo, gamestate, BLACK);
    46         mvprintw(boardy+4, boardx-1,
    47             "White time: %4" PRIu16 ":%02" PRIu16,
    48             white / 60, white % 60);
    49         mvprintw(boardy+5, boardx-1,
    50             "Black time: %4" PRIu16 ":%02" PRIu16,
    51             black / 60, black % 60);
    53         if (white == 0) {
    54             move(inputy, 0);
    55             printw("Time is over - Black wins!");
    56             clrtobot();
    57             refresh();
    58             return 1;
    59         }
    60         if (black == 0) {
    61             move(inputy, 0);
    62             printw("Time is over - White wins!");
    63             clrtobot();
    64             refresh();
    65             return 1;
    66         }
    67     }
    69     return 0;
    70 }
    72 static void draw_board(GameState *gamestate) {
    73     for (uint8_t y = 0 ; y < 8 ; y++) {
    74         for (uint8_t x = 0 ; x < 8 ; x++) {
    75             uint8_t col = gamestate->board[y][x] & COLOR_MASK;
    76             uint8_t piece = gamestate->board[y][x] & PIECE_MASK;
    77             char piecec;
    78             if (piece) {
    79                 piecec = piece == PAWN ? 'P' : getpiecechr(piece);
    80             } else {
    81                 piecec = ' ';
    82             }
    84             _Bool boardblack = (y&1)==(x&1);
    85             attrset((col==WHITE ? A_BOLD : A_DIM)|
    86                 COLOR_PAIR(col == WHITE ?
    87                     (boardblack ? COL_WB : COL_WW) :
    88                     (boardblack ? COL_BB : COL_BW)
    89                 )
    90             );
    92             int cy = gamestate->mycolor == WHITE ? boardy-y : boardy-7+y;
    93             int cx = gamestate->mycolor == WHITE ? boardx+x*3 : boardx+21-x*3;
    94             mvaddch(cy, cx, ' ');
    95             mvaddch(cy, cx+1, piecec);
    96             mvaddch(cy, cx+2, ' ');
    97         }
    98     }
   100     attrset(A_NORMAL);
   101     for (uint8_t i = 0 ; i < 8 ; i++) {
   102         int x = gamestate->mycolor == WHITE ? boardx+i*3+1 : boardx+22-i*3;
   103         int y = gamestate->mycolor == WHITE ? boardy-i : boardy-7+i;
   104         mvaddch(boardy+1, x, 'a'+i);
   105         mvaddch(y, boardx-2, '1'+i);
   106     }
   108     /* move log */
   109     // TODO: introduce window to avoid bugs with a long move log
   110     uint8_t logy = 0;
   111     const uint8_t logx = boardx + 30;
   112     int logi = 1;
   113     MoveList *logelem = gamestate->movelist;
   115     while (logelem) {
   116         logi++;
   117         if (logi % 2 == 0) {
   118             if ((logi - 2) % 4 == 0) {
   119                 logy++;
   120                 move(logy, logx);
   121             }
   122             printw("%d. ", logi / 2);
   123         }
   125         if (logelem) {
   126             Move move = logelem->move;
   127             if ((move.piece&PIECE_MASK) == KING &&
   128                 abs(move.tofile-move.fromfile) == 2) {
   129                 addstr(move.tofile==fileidx('c')?"O-O-O":"O-O");
   130             } else {
   131                 char logstr[] = {
   132                     getpiecechr(move.piece),
   133                     filechr(move.fromfile), rowchr(move.fromrow),
   134                     move.capture ? 'x':'\0',
   135                     filechr(move.tofile), rowchr(move.torow),
   136                     move.check ? '+' : (move.promotion ? '=' : '\0'),
   137                     move.promotion ? getpiecechr(move.promotion) : '\0'
   138                 };
   139                 for (int stri = 0 ; stri < sizeof(logstr) ; stri++) {
   140                     if (logstr[stri]) {
   141                         addch(logstr[stri]);
   142                     }
   143                 }
   144             }
   145             if (!logelem->next) {
   146                 if (gamestate->checkmate) {
   147                     addstr("\b#");
   148                 } else if (gamestate->stalemate) {
   149                     addstr(" stalemate");
   150                 }
   151             }
   152             addch(' ');
   154             logelem = logelem->next;
   155         }
   156     }
   157 }
   159 static void eval_move_failed_msg(int code) {
   160     switch (code) {
   161     case AMBIGUOUS_MOVE:
   162         printw("Ambiguous move - please specify the piece to move.");
   163         break;
   164     case INVALID_POSITION:
   165         printw("Cannot find the piece that shall be moved.");
   166         break;
   167     case NEED_PROMOTION:
   168         printw("You need to promote the pawn (append \"=Q\" e.g.)!");
   169         break;
   170     default:
   171         printw("Can't interpret move - please use algebraic notation.");
   172     }
   173 }
   175 #define MOVESTR_BUFLEN 8
   176 static int domove_singlemachine(GameState *gamestate, GameInfo *gameinfo) {
   179     size_t bufpos = 0;
   180     char movestr[MOVESTR_BUFLEN];
   182     flushinp();
   183     while (1) {
   184         if (timecontrol(gamestate, gameinfo)) {
   185             return 1;
   186         }
   188         move(inputy, 0);
   189         printw(
   190             "Use chess notation to enter your move.\n"
   191             "Or type 'surr' to surrender or 'remis' to end with remis.\n\n"
   192             "Type your move: ");
   193         clrtoeol();
   195         if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) {
   196             if (strncmp(movestr, "surr", MOVESTR_BUFLEN) == 0) {
   197                 printw("%s surrendered!",
   198                     gamestate->mycolor==WHITE?"White":"Black");
   199                 clrtoeol();
   200                 refresh();
   201                 return 1;
   202             } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
   203                 printw("Game ends remis.");
   204                 clrtoeol();
   205                 refresh();
   206                 return 1;
   207             } else {
   208                 Move move;
   209                 int eval_result = eval_move(gamestate, movestr, &move);
   210                 switch (eval_result) {
   211                 case VALID_MOVE_SYNTAX:
   212                     if (validate_move(gamestate, &move)) {
   213                         apply_move(gamestate, &move);
   214                         if (gamestate->checkmate) {
   215                             printw("Checkmate!");
   216                             clrtoeol();
   217                             return 1;
   218                         } else if (gamestate->stalemate) {
   219                             printw("Stalemate!");
   220                             clrtoeol();
   221                             return 1;
   222                         } else {
   223                             return 0;
   224                         }
   225                     } else {
   226                         printw("Invalid move.");
   227                     }
   228                     break;
   229                 default:
   230                     eval_move_failed_msg(eval_result);
   231                 }
   232                 clrtoeol();
   233             }
   234         }
   235     }
   236 }
   238 static int sendmove(GameState *gamestate, GameInfo *gameinfo, int opponent) {
   240     size_t bufpos = 0;
   241     char movestr[MOVESTR_BUFLEN];
   242     _Bool remisrejected = FALSE;
   243     uint8_t code;
   245     flushinp();
   246     while (1) {
   247         if (timecontrol(gamestate, gameinfo)) {
   248             net_send_code(opponent, NETCODE_TIMEOVER);
   249             return 1;
   250         }
   252         move(inputy, 0);
   253         if (remisrejected) {
   254             printw(
   255                 "Use chess notation to enter your move.\n"
   256                 "Remis offer rejected - type 'surr' to surrender.      \n\n"
   257                 "Type your move: ");
   258         } else {
   259             printw(
   260                 "Use chess notation to enter your move.\n"
   261                 "Or type 'surr' to surrender or 'remis' to offer remis.\n\n"
   262                 "Type your move: ");
   263         }
   264         clrtoeol();
   266         if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) {
   267             if (strncmp(movestr, "surr", MOVESTR_BUFLEN) == 0) {
   268                 printw("You surrendered!");
   269                 clrtoeol();
   270                 refresh();
   271                 net_send_code(opponent, NETCODE_SURRENDER);
   272                 return 1;
   273             } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
   274                 if (!remisrejected) {
   275                     net_send_code(opponent, NETCODE_REMIS);
   276                     printw("Remis offer sent - waiting for acceptance...");
   277                     refresh();
   278                     if (net_recieve_code(opponent) == NETCODE_ACCEPT) {
   279                         printw("\rRemis accepted!");
   280                         clrtoeol();
   281                         refresh();
   282                         return 1;
   283                     } else {
   284                         remisrejected = TRUE;
   285                     }
   286                 }
   287             } else {
   288                 Move move;
   289                 int eval_result = eval_move(gamestate, movestr, &move);
   290                 switch (eval_result) {
   291                 case VALID_MOVE_SYNTAX:
   292                     net_send_data(opponent, NETCODE_MOVE, &move, sizeof(Move));
   293                     code = net_recieve_code(opponent);
   294                     move.check = code == NETCODE_CHECK;
   295                     gamestate->checkmate = code == NETCODE_CHECKMATE;
   296                     gamestate->stalemate = code == NETCODE_STALEMATE;
   297                     if (code == NETCODE_DECLINE) {
   298                         printw("Invalid move.");
   299                     } else {
   300                         apply_move(gamestate, &move);
   301                         if (gamestate->checkmate) {
   302                             printw("Checkmate!");
   303                             clrtoeol();
   304                             return 1;
   305                         } else if (gamestate->stalemate) {
   306                             printw("Stalemate!");
   307                             clrtoeol();
   308                             return 1;
   309                         } else {
   310                             return 0;
   311                         }
   312                     }
   313                     break;
   314                 default:
   315                     eval_move_failed_msg(eval_result);
   316                 }
   317                 clrtoeol();
   318             }
   319         }
   320     }
   321 }
   323 static int recvmove(GameState *gamestate, GameInfo *gameinfo, int opponent) {
   325     if (net_setnonblocking(opponent, 1)) {
   326         printw("Cannot setup nonblocking IO on network socket");
   327         cbreak(); getch();
   328         exit(EXIT_FAILURE);
   329     }
   331     struct timeval timeout;
   332     while (1) {
   333         timecontrol(gamestate, gameinfo);
   335         move(inputy, 0);
   336         printw("Awaiting opponent move...");
   337         clrtoeol();
   338         refresh();
   340         fd_set readfds;
   342         FD_ZERO(&readfds);
   343         FD_SET(opponent, &readfds);
   344         timeout.tv_sec = 0;
   345         timeout.tv_usec = 1e5;
   347         int result = select(opponent+1, &readfds, NULL, NULL, &timeout);
   348         if (result == -1) {
   349             printw("\rCannot perform asynchronous network IO");
   350             cbreak(); getch();
   351             exit(EXIT_FAILURE);
   352         }
   353         if (result > 0) {
   354             uint32_t code = net_recieve_code(opponent);
   356             Move move;
   357             switch (code) {
   358             case NETCODE_TIMEOVER:
   359                 printw("\rYour opponent's time ran out - you win!");
   360                 clrtoeol();
   361                 return 1;
   362             case NETCODE_SURRENDER:
   363                 printw("\rYour opponent surrendered!");
   364                 clrtoeol();
   365                 return 1;
   366             case NETCODE_REMIS:
   367                 if (prompt_yesno(
   368                     "\rYour opponent offers remis - do you accept")) {
   369                     printw("\rRemis accepted!");
   370                     clrtoeol();
   371                     net_send_code(opponent, NETCODE_ACCEPT);
   372                     return 1;
   373                 } else {
   374                     net_send_code(opponent, NETCODE_DECLINE);
   375                 }
   376                 break;
   377             case NETCODE_MOVE:
   378                 net_recieve_data(opponent, &move, sizeof(Move));
   379                 if (validate_move(gamestate, &move)) {
   380                     apply_move(gamestate, &move);
   381                     if (move.check) {
   382                         net_send_code(opponent, NETCODE_CHECK);
   383                     } else if (gamestate->checkmate) {
   384                         net_send_code(opponent, NETCODE_CHECKMATE);
   385                         printw("\rCheckmate!");
   386                         clrtoeol();
   387                         return 1;
   388                     } else if (gamestate->stalemate) {
   389                         net_send_code(opponent, NETCODE_STALEMATE);
   390                         printw("\rStalemate!");
   391                         clrtoeol();
   392                         return 1;
   393                     } else {
   394                         net_send_code(opponent, NETCODE_ACCEPT);
   395                     }
   396                     return 0;
   397                 } else {
   398                     net_send_code(opponent, NETCODE_DECLINE);
   399                 }
   400             }
   401         }
   402     }
   403 }
   405 static void init_board(GameState *gamestate) {
   406     Board initboard = {
   407         {WROOK, WKNIGHT, WBISHOP, WQUEEN, WKING, WBISHOP, WKNIGHT, WROOK},
   408         {WPAWN, WPAWN,   WPAWN,   WPAWN,  WPAWN, WPAWN,   WPAWN,   WPAWN},
   409         {0,     0,       0,       0,      0,     0,       0,       0},
   410         {0,     0,       0,       0,      0,     0,       0,       0},
   411         {0,     0,       0,       0,      0,     0,       0,       0},
   412         {0,     0,       0,       0,      0,     0,       0,       0},
   413         {BPAWN, BPAWN,   BPAWN,   BPAWN,  BPAWN, BPAWN,   BPAWN,   BPAWN},
   414         {BROOK, BKNIGHT, BBISHOP, BQUEEN, BKING, BBISHOP, BKNIGHT, BROOK}
   415     };
   416     memcpy(gamestate->board, initboard, sizeof(Board));
   417 }
   419 void game_start_singlemachine(Settings *settings) {
   420     inputy = getmaxy(stdscr) - 6;
   422     GameState gamestate;
   423     memset(&gamestate, 0, sizeof(GameState));
   424     init_board(&gamestate);
   425     gamestate.mycolor = WHITE;
   427     _Bool running;
   428     do {
   429         clear();
   430         draw_board(&gamestate);
   431         running = !domove_singlemachine(&gamestate, &(settings->gameinfo));
   432         gamestate.mycolor = opponent_color(gamestate.mycolor);
   433     }  while (running);
   434     move(0,0);
   435     draw_board(&gamestate);
   437     gamestate_cleanup(&gamestate);
   439     mvaddstr(getmaxy(stdscr)-1, 0,
   440         "Game has ended. Press any key to leave...");
   441     refresh();
   442     cbreak();
   443     flushinp();
   444     getch();
   445 }
   447 void game_start(Settings *settings, int opponent) {
   448     inputy = getmaxy(stdscr) - 6;
   450     _Bool myturn = is_server(settings) ==
   451         (settings->gameinfo.servercolor == WHITE);
   453     GameState gamestate;
   454     memset(&gamestate, 0, sizeof(GameState));
   455     init_board(&gamestate);
   456     gamestate.mycolor = myturn ? WHITE:BLACK;
   458     _Bool running;
   459     do {
   460         clear();
   461         draw_board(&gamestate);
   462         if (myturn) {
   463             running = !sendmove(&gamestate, &(settings->gameinfo), opponent);
   464         } else {
   465             running = !recvmove(&gamestate, &(settings->gameinfo), opponent);
   466         }
   467         myturn ^= TRUE;
   468     }  while (running);
   470     move(0,0);
   471     draw_board(&gamestate);
   473     gamestate_cleanup(&gamestate);
   475     mvaddstr(getmaxy(stdscr)-1, 0,
   476         "Game has ended. Press any key to leave...");
   477     refresh();
   478     cbreak();
   479     flushinp();
   480     getch();
   481 }

mercurial