src/game.c

Sat, 29 Mar 2014 16:53:58 +0100

author
Mike Becker <universe@uap-core.de>
date
Sat, 29 Mar 2014 16:53:58 +0100
changeset 18
6008840b859e
parent 17
2aed5418e142
child 19
6a26114297a1
permissions
-rw-r--r--

fixed bishop + added pawn promotion + added move log

     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 "input.h"
    32 #include "rules/rules.h"
    33 #include <ncurses.h>
    34 #include <string.h>
    36 static const uint8_t boardx = 10, boardy = 10;
    38 static uint8_t getpiecechr(uint8_t piece) {
    39     switch (piece & PIECE_MASK) {
    40     case ROOK: return 'R';
    41     case KNIGHT: return 'N';
    42     case BISHOP: return 'B';
    43     case QUEEN: return 'Q';
    44     case KING: return 'K';
    45     default: return '\0';
    46     }
    47 }
    49 /**
    50  * Maps a character to a piece.
    51  * 
    52  * Does not work for pawns, since they don't have a character.
    53  * 
    54  * @param c one of R,N,B,Q,K
    55  * @return numeric value for the specified piece
    56  */
    57 static uint8_t getpiece(char c) {
    58     switch (c) {
    59         case 'R': return ROOK;
    60         case 'N': return KNIGHT;
    61         case 'B': return BISHOP;
    62         case 'Q': return QUEEN;
    63         case 'K': return KING;
    64         default: return 0;
    65     }
    66 }
    68 /**
    69  * Guesses the location of a piece for short algebraic notation.
    70  * 
    71  * @param board the current state of the board
    72  * @param move the move date to operate on
    73  * @return status code (see rules/rules.h for the codes)
    74  */
    75 static int getlocation(Board board, Move *move) {   
    76     uint8_t piece = move->piece & PIECE_MASK;
    77     switch (piece) {
    78         case PAWN: return pawn_getlocation(board, move);
    79         case ROOK: return rook_getlocation(board, move);
    80         case KNIGHT: return knight_getlocation(board, move);
    81         case BISHOP: return bishop_getlocation(board, move);
    82         case QUEEN: return queen_getlocation(board, move);
    83         case KING: return king_getlocation(board, move);
    84         default: return INVALID_MOVE_SYNTAX;
    85     }
    86 }
    89 static void draw_board(Board board, MoveListRoot *movelist, uint8_t mycolor) {
    91     for (uint8_t y = 0 ; y < 8 ; y++) {
    92         for (uint8_t x = 0 ; x < 8 ; x++) {
    93             uint8_t col = board[y][x] & COLOR_MASK;
    94             uint8_t piece = board[y][x] & PIECE_MASK;
    95             char piecec;
    96             if (piece) {
    97                 piecec = piece == PAWN ? 'P' : getpiecechr(piece);
    98             } else {
    99                 piecec = ' ';
   100             }
   102             attrset((col == WHITE ? A_BOLD : A_DIM) |
   103                 COLOR_PAIR((y&1)==(x&1) ? COL_WB : COL_BW));
   105             int cy = mycolor == WHITE ? boardy-y : boardy-7+y;
   106             int cx = mycolor == WHITE ? boardx+x*3 : boardx+21-x*3;
   107             mvaddch(cy, cx, ' ');
   108             mvaddch(cy, cx+1, piecec);
   109             mvaddch(cy, cx+2, ' ');
   110         }
   111     }
   113     attrset(A_NORMAL);
   114     for (uint8_t i = 0 ; i < 8 ; i++) {
   115         int x = mycolor == WHITE ? boardx+i*3+1 : boardx+22-i*3;
   116         int y = mycolor == WHITE ? boardy-i : boardy-7+i;
   117         mvaddch(boardy+1, x, 'a'+i);
   118         mvaddch(y, boardx-2, '1'+i);
   119     }
   121     /* move log */
   122     // TODO: introduce window to avoid bugs with a long move log
   123     uint8_t logy = 0;
   124     const uint8_t logx = boardx + 30;
   125     int logi = 1;
   126     MoveList *logelem = movelist->first;
   128     while (logelem) {
   129         logi++;
   130         if (logi % 2 == 0) {
   131             if ((logi - 2) % 4 == 0) {
   132                 logy++;
   133                 wmove(tchess_window, logy, logx);
   134             }
   135             printw("%d. ", logi / 2);
   136         }
   138         if (logelem) {
   139             Move move = logelem->move;
   140             char logstr[] = {
   141                 getpiecechr(move.piece),
   142                 filechr(move.fromfile), rowchr(move.fromrow),
   143                 move.capture ? 'x':'\0',
   144                 filechr(move.tofile), rowchr(move.torow),
   145                 move.check ? '+' : (move.checkmate ? '#' : 
   146                     (move.promotion ? '=' : '\0')),
   147                 move.promotion ? getpiecechr(move.promotion) : '\0',
   148                 ' '
   149             };
   150             for (int stri = 0 ; stri < sizeof(logstr) ; stri++) {
   151                 if (logstr[stri]) {
   152                     addch(logstr[stri]);
   153                 }
   154             }
   156             logelem = logelem->next;
   157         }
   158     }
   159 }
   161 /**
   162  * Applies a move and deletes captured pieces.
   163  * 
   164  * @param board the current board state
   165  * @param move the move to apply
   166  */
   167 static void apply_move(Board board, Move *move) {
   168     uint8_t piece = move->piece & PIECE_MASK;
   169     uint8_t color = move->piece & COLOR_MASK;
   171     /* en passant capture */
   172     if (move->capture && piece == PAWN &&
   173         mdst(board, move) == 0) {
   174         board[move->fromrow][move->tofile] = 0;
   175     }
   177     /* remove old en passant threats */
   178     for (uint8_t file = 0 ; file < 8 ; file++) {
   179         board[3][file] &= ~ENPASSANT_THREAT;
   180         board[4][file] &= ~ENPASSANT_THREAT;
   181     }
   183     /* add new en passant threat */
   184     if (piece == PAWN && (
   185         (move->fromrow == 1 && move->torow == 3) ||
   186         (move->fromrow == 6 && move->torow == 4))) {
   187         move->piece |= ENPASSANT_THREAT;
   188     }
   190     /* move (and maybe capture or promote) */
   191     msrc(board, move) = 0;
   192     if (move->promotion) {
   193         mdst(board, move) = move->promotion;
   194     } else {
   195         mdst(board, move) = move->piece;
   196     }
   198     /* castling */
   199     if (piece == KING &&
   200         move->fromfile == fileidx('e')) {
   202         if (move->tofile == fileidx('g')) {
   203             board[move->torow][fileidx('h')] = 0;
   204             board[move->torow][fileidx('f')] = color|ROOK;
   205         } else if (move->tofile == fileidx('c')) {
   206             board[move->torow][fileidx('a')] = 0;
   207             board[move->torow][fileidx('d')] = color|ROOK;
   208         }
   209     }
   210 }
   212 /**
   213  * Validates move by applying chess rules.
   214  * @param board the current board state
   215  * @param move the move to validate
   216  * @return TRUE, if the move complies to chess rules, FALSE otherwise
   217  */
   218 static _Bool validate_move(Board board, Move *move) {
   219     _Bool result;
   221     /* validate indices (don't trust opponent) */
   222     if (!chkidx(move)) {
   223         return FALSE;
   224     }
   226     /* does piece exist */
   227     result = msrc(board, move) == move->piece;
   229     /* can't capture own pieces */
   230     if ((mdst(board, move) & COLOR_MASK) == (move->piece & COLOR_MASK)) {
   231         return FALSE;
   232     }
   234     /* validate individual rules */
   235     switch (move->piece & PIECE_MASK) {
   236     case PAWN:
   237         result = result && pawn_chkrules(board, move);
   238         result = result && !pawn_isblocked(board, move);
   239         break;
   240     case ROOK:
   241         result = result && rook_chkrules(move);
   242         result = result && !rook_isblocked(board, move);
   243         break;
   244     case KNIGHT:
   245         result = result && knight_chkrules(move);
   246         result = result && !knight_isblocked(board, move);
   247         break;
   248     case BISHOP:
   249         result = result && bishop_chkrules(move);
   250         result = result && !bishop_isblocked(board, move);
   251         break;
   252     case QUEEN:
   253         result = result && queen_chkrules(move);
   254         result = result && !queen_isblocked(board, move);
   255         break;
   256     case KING:
   257         result = result && king_chkrules(board, move);
   258         result = result && !king_isblocked(board, move);
   259         break;
   260     default:
   261         result = FALSE;
   262     }
   264     /* is piece pinned */
   265     // TODO: make it so
   267     /* correct check and checkmate flags */
   268     // TODO: make it so
   270     return result;
   271 }
   273 /**
   274  * Evaluates a move syntactically and stores the move data in the specified
   275  * object.
   276  * 
   277  * @param board the current state of the board
   278  * @param mycolor the color of the current player
   279  * @param mstr the input string to parse
   280  * @param move a pointer to object where the move data shall be stored
   281  * @return status code (see rules/rules.h for the list of codes)
   282  */
   283 static int eval_move(Board board, uint8_t mycolor, char *mstr, Move *move) {
   284     memset(move, 0, sizeof(Move));
   285     move->fromfile = POS_UNSPECIFIED;
   286     move->fromrow = POS_UNSPECIFIED;
   288     size_t len = strlen(mstr);
   290     /* evaluate check/checkmate flags */
   291     if (mstr[len-1] == '+') {
   292         len--; mstr[len] = '\0';
   293         move->check = TRUE;
   294     } else if (mstr[len-1] == '#') {
   295         len--; mstr[len] = '\0';
   296         move->checkmate = TRUE;
   297     }
   299     /* evaluate promotion */
   300     if (len > 3 && mstr[len-2] == '=') {
   301         move->promotion = getpiece(mstr[len-1]);
   302         if (!move->promotion) {
   303             return INVALID_MOVE_SYNTAX;
   304         } else {
   305             move->promotion |= mycolor;
   306             len -= 2;
   307             mstr[len] = 0;
   308         }
   309     }
   311     if (len == 2) {
   312         /* pawn move (e.g. "e4") */
   313         move->piece = PAWN;
   314         move->tofile = fileidx(mstr[0]);
   315         move->torow = rowidx(mstr[1]);
   316     } else if (len == 3) {
   317         if (strcmp(mstr, "O-O") == 0) {
   318             /* king side castling */
   319             move->piece = KING;
   320             move->fromfile = fileidx('e');
   321             move->tofile = fileidx('g');
   322             move->fromrow = move->torow = mycolor == WHITE ? 0 : 7;
   323         } else {
   324             /* move (e.g. "Nf3") */
   325             move->piece = getpiece(mstr[0]);
   326             move->tofile = fileidx(mstr[1]);
   327             move->torow = rowidx(mstr[2]);
   328         }
   330     } else if (len == 4) {
   331         move->piece = getpiece(mstr[0]);
   332         if (!move->piece) {
   333             move->piece = PAWN;
   334             move->fromfile = fileidx(mstr[0]);
   335         }
   336         if (mstr[1] == 'x') {
   337             /* capture (e.g. "Nxf3", "dxe5") */
   338             move->capture = TRUE;
   339         } else {
   340             /* move (e.g. "Ndf3", "N2c3", "e2e4") */
   341             if (isfile(mstr[1])) {
   342                 move->fromfile = fileidx(mstr[1]);
   343                 if (move->piece == PAWN) {
   344                     move->piece = 0;
   345                 }
   346             } else {
   347                 move->fromrow = rowidx(mstr[1]);
   348             }
   349         }
   350         move->tofile = fileidx(mstr[2]);
   351         move->torow = rowidx(mstr[3]);
   352     } else if (len == 5) {
   353         if (strcmp(mstr, "O-O-O") == 0) {
   354             /* queen side castling "O-O-O" */
   355             move->piece = KING;
   356             move->fromfile = fileidx('e');
   357             move->tofile = fileidx('c');
   358             move->fromrow = move->torow = mycolor == WHITE ? 0 : 7;
   359         } else {
   360             move->piece = getpiece(mstr[0]);
   361             if (mstr[2] == 'x') {
   362                 move->capture = TRUE;
   363                 if (move->piece) {
   364                     /* capture (e.g. "Ndxf3") */
   365                     move->fromfile = fileidx(mstr[1]);
   366                 } else {
   367                     /* long notation capture (e.g. "e5xf6") */
   368                     move->piece = PAWN;
   369                     move->fromfile = fileidx(mstr[0]);
   370                     move->fromrow = rowidx(mstr[1]);
   371                 }
   372             } else {
   373                 /* long notation move (e.g. "Nc5a4") */
   374                 move->fromfile = fileidx(mstr[1]);
   375                 move->fromrow = rowidx(mstr[2]);
   376             }
   377             move->tofile = fileidx(mstr[3]);
   378             move->torow = rowidx(mstr[4]);
   379         }
   380     } else if (len == 6) {
   381         /* long notation capture (e.g. "Nc5xf3") */
   382         if (mstr[3] == 'x') {
   383             move->capture = TRUE;
   384             move->piece = getpiece(mstr[0]);
   385             move->fromfile = fileidx(mstr[1]);
   386             move->fromrow = rowidx(mstr[2]);
   387             move->tofile = fileidx(mstr[4]);
   388             move->torow = rowidx(mstr[5]);
   389         }
   390     }
   393     if (move->piece) {
   394         if (move->piece == PAWN && move->torow == (mycolor==WHITE?7:0)
   395             && !move->promotion) {
   396             return NEED_PROMOTION;
   397         }
   399         move->piece |= mycolor;
   400         if (move->fromfile == POS_UNSPECIFIED
   401             || move->fromrow == POS_UNSPECIFIED) {
   402             return getlocation(board, move);
   403         } else {
   404             return chkidx(move) ? VALID_MOVE_SYNTAX : INVALID_POSITION;
   405         }
   406     } else {
   407         return INVALID_MOVE_SYNTAX;
   408     }
   409 }
   411 static int sendmove(Board board, MoveListRoot *movelist,
   412     uint8_t mycolor, int opponent) {
   414     const size_t buflen = 8;
   415     char movestr[buflen];
   416     _Bool remisrejected = FALSE;
   417     uint8_t code;
   419     int inputy = getmaxy(tchess_window) - 6;
   420     while (1) {
   421         move(inputy, 0);
   422         if (remisrejected) {
   423             printw(
   424                 "Use chess notation to enter your move.\n"
   425                 "Remis offer rejected - type 'surr' to surrender.      \n\n"
   426                 "Type your move: ");
   427         } else {
   428             printw(
   429                 "Use chess notation to enter your move.\n"
   430                 "Or type 'surr' to surrender or 'remis' to offer remis.\n\n"
   431                 "Type your move: ");
   432         }
   433         clrtoeol();
   434         refresh();
   435         getnstr(movestr, buflen);
   437         if (strncmp(movestr, "surr", buflen) == 0) {
   438             printw("You surrendered!");
   439             clrtoeol();
   440             refresh();
   441             net_send_code(opponent, NETCODE_SURRENDER);
   442             return 1;
   443         } else if (strncmp(movestr, "remis", buflen) == 0) {
   444             if (!remisrejected) {
   445                 net_send_code(opponent, NETCODE_REMIS);
   446                 printw("Remis offer sent - waiting for acceptance...");
   447                 refresh();
   448                 if (net_recieve_code(opponent) == NETCODE_ACCEPT) {
   449                     printw("\rRemis accepted!");
   450                     clrtoeol();
   451                     refresh();
   452                     return 1;
   453                 } else {
   454                     remisrejected = TRUE;
   455                 }
   456             }
   457         } else {
   458             Move move;
   459             int eval_result = eval_move(board, mycolor, movestr, &move);
   460             switch (eval_result) {
   461             case VALID_MOVE_SYNTAX:
   462                 net_send_code(opponent, NETCODE_MOVE);
   463                 net_send_data(opponent, &move, sizeof(Move));
   464                 code = net_recieve_code(opponent);
   465                 move.check = code == NETCODE_CHECK;
   466                 move.checkmate = code == NETCODE_CHECKMATE;
   467                 addmove(movelist, &move);
   468                 if (code == NETCODE_DECLINE) {
   469                     printw("Invalid move.");
   470                 } else {
   471                     apply_move(board, &move);
   472                     if (move.checkmate) {
   473                         printw("Checkmate!");
   474                         clrtoeol();
   475                         return 1;
   476                     } else {
   477                         return 0;
   478                     }
   479                 }
   480                 break;
   481             case AMBIGUOUS_MOVE:
   482                 printw("Ambiguous move - please specify the piece to move.");
   483                 break;
   484             case INVALID_POSITION:
   485                 printw("Cannot find the piece that shall be moved.");
   486                 break;
   487             case NEED_PROMOTION:
   488                 printw("You need to promote the pawn (append \"=Q\" e.g.)!");
   489                 break;
   490             default:
   491                 printw("Can't interpret move - please use algebraic notation.");
   492             }
   493             clrtoeol();
   494         }
   495     }
   496 }
   498 static int recvmove(Board board, MoveListRoot *movelist, int opponent) {
   500     int inputy = getmaxy(tchess_window) - 6;
   501     while (1) {
   502         move(inputy, 0);
   503         printw("Awaiting opponent move...");
   504         clrtoeol();
   505         refresh();
   507         // TODO: nonblocking
   508         uint32_t code = net_recieve_code(opponent);
   510         Move move;
   511         switch (code) {
   512             case NETCODE_SURRENDER:
   513                 printw("\rYour opponent surrendered!");
   514                 clrtoeol();
   515                 return 1;
   516             case NETCODE_REMIS:
   517                 if (prompt_yesno(
   518                     "\rYour opponent offers remis - do you accept")) {
   519                     printw("\rRemis accepted!");
   520                     clrtoeol();
   521                     net_send_code(opponent, NETCODE_ACCEPT);
   522                     return 1;
   523                 } else {
   524                     net_send_code(opponent, NETCODE_DECLINE);
   525                 }
   526                 break;
   527             case NETCODE_MOVE:
   528                 net_recieve_data(opponent, &move, sizeof(Move));
   529                 if (validate_move(board, &move)) {
   530                     apply_move(board, &move);
   531                     addmove(movelist, &move);
   532                     if (move.check) {
   533                         net_send_code(opponent, NETCODE_CHECK);
   534                     } else if (move.checkmate) {
   535                         net_send_code(opponent, NETCODE_CHECKMATE);
   536                     } else {
   537                         net_send_code(opponent, NETCODE_ACCEPT);
   538                     }
   539                     return 0;
   540                 } else {
   541                     net_send_code(opponent, NETCODE_DECLINE);
   542                 }
   543         }
   544     }
   545 }
   547 void freemovelist(MoveListRoot* list) {
   548     MoveList *elem;
   549     elem = list->first;
   550     while (elem) {
   551         MoveList *cur = elem;
   552         elem = elem->next;
   553         free(cur);
   554     };
   555     free(list);
   556 }
   558 void addmove(MoveListRoot* list, Move *move) {
   559     MoveList *elem = malloc(sizeof(MoveList));
   560     elem->next = NULL;
   561     elem->move = *move;
   563     if (list->last) {
   564         list->last->next = elem;
   565         list->last = elem;
   566     } else {
   567         list->first = list->last = elem;
   568     }
   569 }
   571 void game_start(Settings *settings, int opponent) {
   572     _Bool myturn = is_server(settings) ==
   573         (settings->gameinfo.servercolor == WHITE);
   574     uint8_t mycolor = myturn ? WHITE:BLACK;
   576     _Bool running;
   578     MoveListRoot* movelist = calloc(1, sizeof(MoveListRoot));
   580     Board board = {
   581         {WROOK, WKNIGHT, WBISHOP, WQUEEN, WKING, WBISHOP, WKNIGHT, WROOK},
   582         {WPAWN, WPAWN,   WPAWN,   WPAWN,  WPAWN, WPAWN,   WPAWN,   WPAWN},
   583         {0,     0,       0,       0,      0,     0,       0,       0},
   584         {0,     0,       0,       0,      0,     0,       0,       0},
   585         {0,     0,       0,       0,      0,     0,       0,       0},
   586         {0,     0,       0,       0,      0,     0,       0,       0},
   587         {BPAWN, BPAWN,   BPAWN,   BPAWN,  BPAWN, BPAWN,   BPAWN,   BPAWN},
   588         {BROOK, BKNIGHT, BBISHOP, BQUEEN, BKING, BBISHOP, BKNIGHT, BROOK}
   589     };
   591     do {
   592         clear();
   593         draw_board(board, movelist, mycolor);
   594         if (myturn) {
   595             running = !sendmove(board, movelist, mycolor, opponent);
   596         } else {
   597             running = !recvmove(board, movelist, opponent);
   598             flushinp(); // flush any input the user hacked in while waiting
   599         }
   600         myturn ^= TRUE;
   601     }  while (running);
   603     freemovelist(movelist);
   605     mvaddstr(getmaxy(tchess_window)-1, 0,
   606         "Game has ended. Press any key to leave...");
   607     getch();
   608 }

mercurial