added pgn parser and writer (without comment support yet) + minor refactorings

Mon, 16 Jun 2014 13:45:31 +0200

author
Mike Becker <universe@uap-core.de>
date
Mon, 16 Jun 2014 13:45:31 +0200
changeset 50
41017d0a72c5
parent 49
02c509a44e98
child 51
84f2e380a434

added pgn parser and writer (without comment support yet) + minor refactorings

src/chess/Makefile file | annotate | diff | comparison | revisions
src/chess/chess.h file | annotate | diff | comparison | revisions
src/chess/pgn.c file | annotate | diff | comparison | revisions
src/chess/pgn.h file | annotate | diff | comparison | revisions
src/chess/rules.c file | annotate | diff | comparison | revisions
src/chess/rules.h file | annotate | diff | comparison | revisions
src/game.c file | annotate | diff | comparison | revisions
src/main.c file | annotate | diff | comparison | revisions
src/terminal-chess.h file | annotate | diff | comparison | revisions
     1.1 --- a/src/chess/Makefile	Wed Jun 11 16:54:20 2014 +0200
     1.2 +++ b/src/chess/Makefile	Mon Jun 16 13:45:31 2014 +0200
     1.3 @@ -38,6 +38,7 @@
     1.4  SRC += queen.c
     1.5  SRC += king.c
     1.6  SRC += rules.c
     1.7 +SRC += pgn.c
     1.8  
     1.9  OBJ = $(SRC:%.c=$(BUILDDIR)/release/%$(OBJ_EXT))
    1.10  OBJ_D = $(SRC:%.c=$(BUILDDIR)/debug/%$(OBJ_EXT))
     2.1 --- a/src/chess/chess.h	Wed Jun 11 16:54:20 2014 +0200
     2.2 +++ b/src/chess/chess.h	Mon Jun 16 13:45:31 2014 +0200
     2.3 @@ -34,3 +34,4 @@
     2.4  #include "bishop.h"
     2.5  #include "queen.h"
     2.6  #include "king.h"
     2.7 +#include "pgn.h"
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/src/chess/pgn.c	Mon Jun 16 13:45:31 2014 +0200
     3.3 @@ -0,0 +1,196 @@
     3.4 +/*
     3.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3.6 + *
     3.7 + * Copyright 2014 Mike Becker. All rights reserved.
     3.8 + *
     3.9 + * Redistribution and use in source and binary forms, with or without
    3.10 + * modification, are permitted provided that the following conditions are met:
    3.11 + *
    3.12 + *   1. Redistributions of source code must retain the above copyright
    3.13 + *      notice, this list of conditions and the following disclaimer.
    3.14 + *
    3.15 + *   2. Redistributions in binary form must reproduce the above copyright
    3.16 + *      notice, this list of conditions and the following disclaimer in the
    3.17 + *      documentation and/or other materials provided with the distribution.
    3.18 + *
    3.19 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    3.20 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    3.21 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    3.22 + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    3.23 + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    3.24 + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    3.25 + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    3.26 + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    3.27 + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    3.28 + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    3.29 + * POSSIBILITY OF SUCH DAMAGE.
    3.30 + *
    3.31 + */
    3.32 +
    3.33 +#include "pgn.h"
    3.34 +#include <ctype.h>
    3.35 +#include <stdlib.h>
    3.36 +#include <string.h>
    3.37 +
    3.38 +int read_pgn(FILE* stream, GameState *gamestate, GameInfo *gameinfo) {
    3.39 +    int c, i;
    3.40 +    
    3.41 +    char result[8];
    3.42 +    
    3.43 +    char tagkey[32];
    3.44 +    char tagvalue[128];
    3.45 +    
    3.46 +    // read tag pairs
    3.47 +    _Bool readmoves = 0;
    3.48 +    while (!readmoves) {
    3.49 +        while (isspace(c = fgetc(stream)));
    3.50 +        if (c == '1') {
    3.51 +            readmoves = 1;
    3.52 +            break;
    3.53 +        }
    3.54 +        if (c != '[') {
    3.55 +            return EXIT_FAILURE;
    3.56 +        }
    3.57 +        while (isspace(c = fgetc(stream)));
    3.58 +        i = 0;
    3.59 +        do {
    3.60 +            tagkey[i++] = c;
    3.61 +        } while (!isspace(c = fgetc(stream)));
    3.62 +        tagkey[i] = '\0';
    3.63 +        while (isspace(c = fgetc(stream)));
    3.64 +        if (c != '"') {
    3.65 +            return EXIT_FAILURE;
    3.66 +        }
    3.67 +        i = 0;
    3.68 +        while ((c = fgetc(stream)) != '"') {
    3.69 +            if (c == '\n') {
    3.70 +                return EXIT_FAILURE;
    3.71 +            }
    3.72 +            tagvalue[i++] = c;
    3.73 +        }
    3.74 +        tagvalue[i] = '\0';
    3.75 +        if (fgetc(stream) != ']') {
    3.76 +            return EXIT_FAILURE;
    3.77 +        }
    3.78 +
    3.79 +        if (strcmp("Result", tagkey) == 0) {
    3.80 +            memcpy(result, tagvalue, 8);
    3.81 +        }
    3.82 +    }
    3.83 +    
    3.84 +    // read moves
    3.85 +    if (fgetc(stream) != '.') {
    3.86 +        return EXIT_FAILURE;
    3.87 +    }
    3.88 +    
    3.89 +    char movestr[10];
    3.90 +    Move move;
    3.91 +    uint8_t curcol = WHITE;
    3.92 +    
    3.93 +    while (readmoves) {
    3.94 +        // move
    3.95 +        while (isspace(c = fgetc(stream)));
    3.96 +        i = 0;
    3.97 +        do {
    3.98 +            movestr[i++] = c;
    3.99 +            if (i >= 10) {
   3.100 +                return EXIT_FAILURE;
   3.101 +            }
   3.102 +        } while (!isspace(c = fgetc(stream)));
   3.103 +        movestr[i] = '\0';
   3.104 +        if (eval_move(gamestate, movestr, &move, curcol)
   3.105 +                != VALID_MOVE_SYNTAX) {
   3.106 +            return EXIT_FAILURE;
   3.107 +        }
   3.108 +        if (validate_move(gamestate, &move) != VALID_MOVE_SEMANTICS) {
   3.109 +            return EXIT_FAILURE;
   3.110 +        }
   3.111 +        apply_move(gamestate, &move);
   3.112 +        
   3.113 +        // TODO: parse comments
   3.114 +        while (isspace(c = fgetc(stream)));
   3.115 +        
   3.116 +        // end of game data encountered
   3.117 +        if (c == EOF) {
   3.118 +            break;
   3.119 +        }
   3.120 +        if (c == '1' || c == '0') {
   3.121 +            c = fgetc(stream);
   3.122 +            if (c == '-') {
   3.123 +                gamestate->resign = !gamestate->checkmate;
   3.124 +                break;
   3.125 +            } else if (c == '/') {
   3.126 +                gamestate->remis = !gamestate->stalemate;
   3.127 +                break;
   3.128 +            } else {
   3.129 +                // oops, it was a move number, go back!
   3.130 +                fseek(stream, -1, SEEK_CUR);
   3.131 +            }
   3.132 +        }
   3.133 +        
   3.134 +        // we have eaten the next valuable byte, so go back
   3.135 +        fseek(stream, -1, SEEK_CUR);
   3.136 +        
   3.137 +        // skip move number after black move
   3.138 +        if (curcol == BLACK) {
   3.139 +            while (isdigit(c = fgetc(stream)));
   3.140 +            if (c != '.') {
   3.141 +                return EXIT_FAILURE;
   3.142 +            }
   3.143 +        }
   3.144 +        curcol = opponent_color(curcol);
   3.145 +    }
   3.146 +    
   3.147 +    return EXIT_SUCCESS;
   3.148 +}
   3.149 +
   3.150 +size_t write_pgn(FILE* stream, GameState *gamestate, GameInfo *gameinfo) {
   3.151 +    // TODO: tag pairs
   3.152 +    size_t bytes = 0;
   3.153 +    
   3.154 +    // Result
   3.155 +    char *result;
   3.156 +    if (gamestate->stalemate || gamestate->remis) {
   3.157 +        result = "1/2-1/2";
   3.158 +    } else if (gamestate->checkmate || gamestate->resign) {
   3.159 +        if (gamestate->lastmove) {
   3.160 +            result = (gamestate->lastmove->move.piece & COLOR_MASK) == WHITE ?
   3.161 +                "1-0" : "0-1";
   3.162 +        } else {
   3.163 +            result = "0-1";
   3.164 +        }
   3.165 +    } else {
   3.166 +        result = "*";
   3.167 +    }
   3.168 +    fprintf(stream, "[Result \"%s\"]\n\n", result);
   3.169 +    
   3.170 +    // moves
   3.171 +    int i = 1;
   3.172 +    for (MoveList *movelist = gamestate->movelist ;
   3.173 +        movelist ; movelist = movelist->next) {
   3.174 +        
   3.175 +        if (++i % 2 == 0) {
   3.176 +            fprintf(stream, "%d. %s", i/2, movelist->move.string);
   3.177 +        } else {
   3.178 +            fprintf(stream, " %s", movelist->move.string);
   3.179 +        }
   3.180 +        
   3.181 +        // TODO: move time and maybe other comments
   3.182 +        
   3.183 +        // line break every 10 moves
   3.184 +        if (i % 20)  {
   3.185 +            fputc(' ', stream);
   3.186 +        } else {
   3.187 +            fputc('\n', stream);
   3.188 +        }
   3.189 +    }
   3.190 +    
   3.191 +    if (result[0] == '*') {
   3.192 +        fputc('\n', stream);
   3.193 +    } else {
   3.194 +        fprintf(stream, "%s\n", result);
   3.195 +    }
   3.196 +    
   3.197 +    
   3.198 +    return bytes;
   3.199 +}
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/src/chess/pgn.h	Mon Jun 16 13:45:31 2014 +0200
     4.3 @@ -0,0 +1,50 @@
     4.4 +/*
     4.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     4.6 + *
     4.7 + * Copyright 2014 Mike Becker. All rights reserved.
     4.8 + *
     4.9 + * Redistribution and use in source and binary forms, with or without
    4.10 + * modification, are permitted provided that the following conditions are met:
    4.11 + *
    4.12 + *   1. Redistributions of source code must retain the above copyright
    4.13 + *      notice, this list of conditions and the following disclaimer.
    4.14 + *
    4.15 + *   2. Redistributions in binary form must reproduce the above copyright
    4.16 + *      notice, this list of conditions and the following disclaimer in the
    4.17 + *      documentation and/or other materials provided with the distribution.
    4.18 + *
    4.19 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    4.20 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    4.21 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    4.22 + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    4.23 + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    4.24 + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    4.25 + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    4.26 + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    4.27 + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    4.28 + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    4.29 + * POSSIBILITY OF SUCH DAMAGE.
    4.30 + *
    4.31 + */
    4.32 +
    4.33 +
    4.34 +#ifndef PGN_H
    4.35 +#define	PGN_H
    4.36 +
    4.37 +#include "rules.h"
    4.38 +#include <stdio.h>
    4.39 +
    4.40 +#ifdef	__cplusplus
    4.41 +extern "C" {
    4.42 +#endif
    4.43 +    
    4.44 +int read_pgn(FILE *stream, GameState *gamestate, GameInfo *gameinfo);
    4.45 +size_t write_pgn(FILE* stream, GameState *gamestate, GameInfo *gameinfo);
    4.46 +
    4.47 +
    4.48 +#ifdef	__cplusplus
    4.49 +}
    4.50 +#endif
    4.51 +
    4.52 +#endif	/* PGN_H */
    4.53 +
     5.1 --- a/src/chess/rules.c	Wed Jun 11 16:54:20 2014 +0200
     5.2 +++ b/src/chess/rules.c	Mon Jun 16 13:45:31 2014 +0200
     5.3 @@ -44,6 +44,22 @@
     5.4      return simulation;
     5.5  }
     5.6  
     5.7 +void gamestate_init(GameState *gamestate) {
     5.8 +    memset(gamestate, 0, sizeof(GameState));
     5.9 +    
    5.10 +    Board initboard = {
    5.11 +        {WROOK, WKNIGHT, WBISHOP, WQUEEN, WKING, WBISHOP, WKNIGHT, WROOK},
    5.12 +        {WPAWN, WPAWN,   WPAWN,   WPAWN,  WPAWN, WPAWN,   WPAWN,   WPAWN},
    5.13 +        {0,     0,       0,       0,      0,     0,       0,       0},
    5.14 +        {0,     0,       0,       0,      0,     0,       0,       0},
    5.15 +        {0,     0,       0,       0,      0,     0,       0,       0},
    5.16 +        {0,     0,       0,       0,      0,     0,       0,       0},
    5.17 +        {BPAWN, BPAWN,   BPAWN,   BPAWN,  BPAWN, BPAWN,   BPAWN,   BPAWN},
    5.18 +        {BROOK, BKNIGHT, BBISHOP, BQUEEN, BKING, BBISHOP, BKNIGHT, BROOK}
    5.19 +    };
    5.20 +    memcpy(gamestate->board, initboard, sizeof(Board));
    5.21 +}
    5.22 +
    5.23  void gamestate_cleanup(GameState *gamestate) {
    5.24      MoveList *elem;
    5.25      elem = gamestate->movelist;
    5.26 @@ -587,7 +603,7 @@
    5.27      }
    5.28  }
    5.29  
    5.30 -int eval_move(GameState *gamestate, char *mstr, Move *move) {
    5.31 +int eval_move(GameState *gamestate, char *mstr, Move *move, uint8_t color) {
    5.32      memset(move, 0, sizeof(Move));
    5.33      move->fromfile = POS_UNSPECIFIED;
    5.34      move->fromrow = POS_UNSPECIFIED;
    5.35 @@ -612,7 +628,7 @@
    5.36          if (!move->promotion) {
    5.37              return INVALID_MOVE_SYNTAX;
    5.38          } else {
    5.39 -            move->promotion |= gamestate->mycolor;
    5.40 +            move->promotion |= color;
    5.41              len -= 2;
    5.42              mstr[len] = 0;
    5.43          }
    5.44 @@ -629,7 +645,7 @@
    5.45              move->piece = KING;
    5.46              move->fromfile = fileidx('e');
    5.47              move->tofile = fileidx('g');
    5.48 -            move->fromrow = move->torow = gamestate->mycolor == WHITE ? 0 : 7;
    5.49 +            move->fromrow = move->torow = color == WHITE ? 0 : 7;
    5.50          } else {
    5.51              /* move (e.g. "Nf3") */
    5.52              move->piece = getpiece(mstr[0]);
    5.53 @@ -664,7 +680,7 @@
    5.54              move->piece = KING;
    5.55              move->fromfile = fileidx('e');
    5.56              move->tofile = fileidx('c');
    5.57 -            move->fromrow = move->torow = gamestate->mycolor == WHITE ? 0 : 7;
    5.58 +            move->fromrow = move->torow = color == WHITE ? 0 : 7;
    5.59          } else {
    5.60              move->piece = getpiece(mstr[0]);
    5.61              if (mstr[2] == 'x') {
    5.62 @@ -701,12 +717,12 @@
    5.63      
    5.64      if (move->piece) {
    5.65          if (move->piece == PAWN
    5.66 -            && move->torow == (gamestate->mycolor==WHITE?7:0)
    5.67 +            && move->torow == (color==WHITE?7:0)
    5.68              && !move->promotion) {
    5.69              return NEED_PROMOTION;
    5.70          }
    5.71          
    5.72 -        move->piece |= gamestate->mycolor;
    5.73 +        move->piece |= color;
    5.74          if (move->fromfile == POS_UNSPECIFIED
    5.75              || move->fromrow == POS_UNSPECIFIED) {
    5.76              return getlocation(gamestate, move);
     6.1 --- a/src/chess/rules.h	Wed Jun 11 16:54:20 2014 +0200
     6.2 +++ b/src/chess/rules.h	Mon Jun 16 13:45:31 2014 +0200
     6.3 @@ -110,11 +110,12 @@
     6.4  
     6.5  typedef struct {
     6.6      Board board;
     6.7 -    uint8_t mycolor;
     6.8      MoveList* movelist;
     6.9      MoveList* lastmove;
    6.10      _Bool checkmate;
    6.11      _Bool stalemate;
    6.12 +    _Bool remis;
    6.13 +    _Bool resign;
    6.14  } GameState;
    6.15  
    6.16  #define opponent_color(color) ((color)==WHITE?BLACK:WHITE)
    6.17 @@ -141,6 +142,16 @@
    6.18  #define fileidx_s(c) (isfile(c)?fileidx(c):POS_UNSPECIFIED)
    6.19  #define rowidx_s(c) (isrow(c)?rowidx(c):POS_UNSPECIFIED)
    6.20  
    6.21 +#define is_game_running(gamestate) !((gamestate)->checkmate || \
    6.22 +    (gamestate)->resign || (gamestate)->stalemate || (gamestate)->remis)
    6.23 +
    6.24 +
    6.25 +/**
    6.26 + * Initializes a game state and prepares the chess board.
    6.27 + * @param gamestate the game state to initialize
    6.28 + */
    6.29 +void gamestate_init(GameState *gamestate);
    6.30 +
    6.31  /**
    6.32   * Cleans up a game state and frees the memory for the movement list.
    6.33   * @param gamestate the game state to clean up
    6.34 @@ -276,9 +287,10 @@
    6.35   * @param gamestate the current game state
    6.36   * @param mstr the input string to parse
    6.37   * @param move a pointer to object where the move data shall be stored
    6.38 + * @param color the color of the player to evaluate the move for
    6.39   * @return status code (see macros in this file for the list of codes)
    6.40   */
    6.41 -int eval_move(GameState *gamestate, char *mstr, Move *move);
    6.42 +int eval_move(GameState *gamestate, char *mstr, Move *move, uint8_t color);
    6.43  
    6.44  /**
    6.45   * Validates move by applying chess rules.
     7.1 --- a/src/game.c	Wed Jun 11 16:54:20 2014 +0200
     7.2 +++ b/src/game.c	Mon Jun 16 13:45:31 2014 +0200
     7.3 @@ -35,6 +35,8 @@
     7.4  #include <string.h>
     7.5  #include <inttypes.h>
     7.6  #include <sys/select.h>
     7.7 +#include <stdio.h>
     7.8 +#include <errno.h>
     7.9  
    7.10  static const uint8_t boardx = 10, boardy = 10;
    7.11  static int inputy = 21; /* should be overridden on game startup */
    7.12 @@ -69,7 +71,7 @@
    7.13      return 0;
    7.14  }
    7.15  
    7.16 -static void draw_board(GameState *gamestate) {
    7.17 +static void draw_board(GameState *gamestate, uint8_t perspective) {
    7.18      for (uint8_t y = 0 ; y < 8 ; y++) {
    7.19          for (uint8_t x = 0 ; x < 8 ; x++) {
    7.20              uint8_t col = gamestate->board[y][x] & COLOR_MASK;
    7.21 @@ -89,8 +91,8 @@
    7.22                  )
    7.23              );
    7.24              
    7.25 -            int cy = gamestate->mycolor == WHITE ? boardy-y : boardy-7+y;
    7.26 -            int cx = gamestate->mycolor == WHITE ? boardx+x*3 : boardx+21-x*3;
    7.27 +            int cy = perspective == WHITE ? boardy-y : boardy-7+y;
    7.28 +            int cx = perspective == WHITE ? boardx+x*3 : boardx+21-x*3;
    7.29              mvaddch(cy, cx, ' ');
    7.30              mvaddch(cy, cx+1, piecec);
    7.31              mvaddch(cy, cx+2, ' ');
    7.32 @@ -99,8 +101,8 @@
    7.33      
    7.34      attrset(A_NORMAL);
    7.35      for (uint8_t i = 0 ; i < 8 ; i++) {
    7.36 -        int x = gamestate->mycolor == WHITE ? boardx+i*3+1 : boardx+22-i*3;
    7.37 -        int y = gamestate->mycolor == WHITE ? boardy-i : boardy-7+i;
    7.38 +        int x = perspective == WHITE ? boardx+i*3+1 : boardx+22-i*3;
    7.39 +        int y = perspective == WHITE ? boardy-i : boardy-7+i;
    7.40          mvaddch(boardy+1, x, 'a'+i);
    7.41          mvaddch(y, boardx-2, '1'+i);
    7.42      }
    7.43 @@ -122,17 +124,15 @@
    7.44              printw("%d. ", logi / 2);
    7.45          }
    7.46  
    7.47 -        if (logelem) {
    7.48 -            addstr(logelem->move.string);
    7.49 -            if (!logelem->next) {
    7.50 -                if (gamestate->stalemate) {
    7.51 -                    addstr(" stalemate");
    7.52 -                }
    7.53 +        addstr(logelem->move.string);
    7.54 +        if (!logelem->next) {
    7.55 +            if (gamestate->stalemate) {
    7.56 +                addstr(" stalemate");
    7.57              }
    7.58 -            addch(' ');
    7.59 -            
    7.60 -            logelem = logelem->next;
    7.61          }
    7.62 +        addch(' ');
    7.63 +
    7.64 +        logelem = logelem->next;
    7.65      }
    7.66  }
    7.67  
    7.68 @@ -167,8 +167,30 @@
    7.69      }
    7.70  }
    7.71  
    7.72 -#define MOVESTR_BUFLEN 8
    7.73 -static int domove_singlemachine(GameState *gamestate, GameInfo *gameinfo) {
    7.74 +static void save_pgn(GameState *gamestate, GameInfo *gameinfo) {
    7.75 +    printw("Filename: ");
    7.76 +    clrtoeol();
    7.77 +    refresh();
    7.78 +    
    7.79 +    char filename[64];
    7.80 +    int y = getcury(stdscr);
    7.81 +    if (getnstr(filename, 64) == OK) {
    7.82 +        move(y, 0);
    7.83 +        FILE *file = fopen(filename, "w");
    7.84 +        if (file) {
    7.85 +            write_pgn(file, gamestate, gameinfo);
    7.86 +            fclose(file);
    7.87 +            printw("File saved.");
    7.88 +        } else {
    7.89 +            printw("Can't write to file (%s).", strerror(errno));
    7.90 +        }
    7.91 +        clrtoeol();
    7.92 +    }
    7.93 +}
    7.94 +
    7.95 +#define MOVESTR_BUFLEN 10
    7.96 +static int domove_singlemachine(GameState *gamestate,
    7.97 +        GameInfo *gameinfo, uint8_t curcolor) {
    7.98      
    7.99  
   7.100      size_t bufpos = 0;
   7.101 @@ -183,29 +205,33 @@
   7.102          move(inputy, 0);
   7.103          printw(
   7.104              "Use chess notation to enter your move.\n"
   7.105 -            "Or type 'resign' to resign or 'remis' to end with remis.\n\n"
   7.106 +            "Or use a command: remis, resign, savepgn\n\n"
   7.107              "Type your move: ");
   7.108          clrtoeol();
   7.109          
   7.110          if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) {
   7.111              if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) {
   7.112 +                gamestate->resign = 1;
   7.113                  printw("%s resigned!",
   7.114 -                    gamestate->mycolor==WHITE?"White":"Black");
   7.115 -                clrtoeol();
   7.116 +                    curcolor==WHITE?"White":"Black");
   7.117 +                clrtobot();
   7.118                  refresh();
   7.119                  return 1;
   7.120              } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
   7.121 +                gamestate->remis = 1;
   7.122                  printw("Game ends remis.");
   7.123 -                clrtoeol();
   7.124 +                clrtobot();
   7.125                  refresh();
   7.126                  return 1;
   7.127 +            } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) {
   7.128 +                save_pgn(gamestate, gameinfo);
   7.129              } else {
   7.130                  Move move;
   7.131 -                int eval_result = eval_move(gamestate, movestr, &move);
   7.132 -                switch (eval_result) {
   7.133 +                int result = eval_move(gamestate, movestr, &move, curcolor);
   7.134 +                switch (result) {
   7.135                  case VALID_MOVE_SYNTAX:
   7.136 -                    eval_result = validate_move(gamestate, &move);
   7.137 -                    if (eval_result == VALID_MOVE_SEMANTICS) {
   7.138 +                    result = validate_move(gamestate, &move);
   7.139 +                    if (result == VALID_MOVE_SEMANTICS) {
   7.140                          apply_move(gamestate, &move);
   7.141                          if (gamestate->checkmate) {
   7.142                              printw("Checkmate!");
   7.143 @@ -219,11 +245,11 @@
   7.144                              return 0;
   7.145                          }
   7.146                      } else {
   7.147 -                        eval_move_failed_msg(eval_result);
   7.148 +                        eval_move_failed_msg(result);
   7.149                      }
   7.150                      break;
   7.151                  default:
   7.152 -                    eval_move_failed_msg(eval_result);
   7.153 +                    eval_move_failed_msg(result);
   7.154                  }
   7.155                  clrtoeol();
   7.156              }
   7.157 @@ -231,7 +257,8 @@
   7.158      }
   7.159  }
   7.160  
   7.161 -static int sendmove(GameState *gamestate, GameInfo *gameinfo, int opponent) {
   7.162 +static int sendmove(GameState *gamestate, GameInfo *gameinfo,
   7.163 +        int opponent, uint8_t mycolor) {
   7.164      
   7.165      size_t bufpos = 0;
   7.166      char movestr[MOVESTR_BUFLEN];
   7.167 @@ -249,23 +276,26 @@
   7.168          if (remisrejected) {
   7.169              printw(
   7.170                  "Use chess notation to enter your move.\n"
   7.171 -                "Remis offer rejected - type 'resign' to resign.      \n\n"
   7.172 +                "Remis offer rejected                    \n\n"
   7.173                  "Type your move: ");
   7.174          } else {
   7.175              printw(
   7.176                  "Use chess notation to enter your move.\n"
   7.177 -                "Or type 'resign' to resign or 'remis' to offer remis.\n\n"
   7.178 +                "Or use a command: remis, resign, savepgn\n\n"
   7.179                  "Type your move: ");
   7.180          }
   7.181          clrtoeol();
   7.182          
   7.183          if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) {
   7.184              if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) {
   7.185 +                gamestate->resign = 1;
   7.186                  printw("You resigned!");
   7.187                  clrtoeol();
   7.188                  refresh();
   7.189                  net_send_code(opponent, NETCODE_RESIGN);
   7.190                  return 1;
   7.191 +            } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) {
   7.192 +                save_pgn(gamestate, gameinfo);
   7.193              } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
   7.194                  if (!remisrejected) {
   7.195                      net_send_code(opponent, NETCODE_REMIS);
   7.196 @@ -273,6 +303,7 @@
   7.197                      refresh();
   7.198                      code = net_recieve_code(opponent);
   7.199                      if (code == NETCODE_ACCEPT) {
   7.200 +                        gamestate->remis = 1;
   7.201                          printw("\rRemis accepted!");
   7.202                          clrtoeol();
   7.203                          refresh();
   7.204 @@ -288,7 +319,7 @@
   7.205                  }
   7.206              } else {
   7.207                  Move move;
   7.208 -                int eval_result = eval_move(gamestate, movestr, &move);
   7.209 +                int eval_result = eval_move(gamestate, movestr, &move, mycolor);
   7.210                  switch (eval_result) {
   7.211                  case VALID_MOVE_SYNTAX:
   7.212                      net_send_data(opponent, NETCODE_MOVE, &move,
   7.213 @@ -353,6 +384,8 @@
   7.214          timeout.tv_sec = 0;
   7.215          timeout.tv_usec = 1e5;
   7.216          
   7.217 +        // TODO: allow commands
   7.218 +        
   7.219          int result = select(opponent+1, &readfds, NULL, NULL, &timeout);
   7.220          if (result == -1) {
   7.221              printw("\rCannot perform asynchronous network IO");
   7.222 @@ -369,6 +402,7 @@
   7.223                  clrtoeol();
   7.224                  return 1;
   7.225              case NETCODE_RESIGN:
   7.226 +                gamestate->resign = 1;
   7.227                  printw("\rYour opponent resigned!");
   7.228                  clrtoeol();
   7.229                  return 1;
   7.230 @@ -379,6 +413,7 @@
   7.231              case NETCODE_REMIS:
   7.232                  if (prompt_yesno(
   7.233                      "\rYour opponent offers remis - do you accept")) {
   7.234 +                    gamestate->remis = 1;
   7.235                      printw("\rRemis accepted!");
   7.236                      clrtoeol();
   7.237                      net_send_code(opponent, NETCODE_ACCEPT);
   7.238 @@ -421,39 +456,71 @@
   7.239      }
   7.240  }
   7.241  
   7.242 -static void init_board(GameState *gamestate) {
   7.243 -    Board initboard = {
   7.244 -        {WROOK, WKNIGHT, WBISHOP, WQUEEN, WKING, WBISHOP, WKNIGHT, WROOK},
   7.245 -        {WPAWN, WPAWN,   WPAWN,   WPAWN,  WPAWN, WPAWN,   WPAWN,   WPAWN},
   7.246 -        {0,     0,       0,       0,      0,     0,       0,       0},
   7.247 -        {0,     0,       0,       0,      0,     0,       0,       0},
   7.248 -        {0,     0,       0,       0,      0,     0,       0,       0},
   7.249 -        {0,     0,       0,       0,      0,     0,       0,       0},
   7.250 -        {BPAWN, BPAWN,   BPAWN,   BPAWN,  BPAWN, BPAWN,   BPAWN,   BPAWN},
   7.251 -        {BROOK, BKNIGHT, BBISHOP, BQUEEN, BKING, BBISHOP, BKNIGHT, BROOK}
   7.252 -    };
   7.253 -    memcpy(gamestate->board, initboard, sizeof(Board));
   7.254 +static void post_game(GameState *gamestate, GameInfo *gameinfo) {
   7.255 +    move(0,0);
   7.256 +    draw_board(gamestate, WHITE);
   7.257 +    
   7.258 +    // TODO: network connection is still open here - think about it!
   7.259 +    
   7.260 +    mvaddstr(getmaxy(stdscr)-1, 0,
   7.261 +        "Press 'q' to quit or 's' to save a PGN file...");
   7.262 +    refresh();
   7.263 +    flushinp();
   7.264 +    
   7.265 +    noecho();
   7.266 +    int c;
   7.267 +    do {
   7.268 +        c = getch();
   7.269 +        if (c == 's') {
   7.270 +            addch('\r');
   7.271 +            echo();
   7.272 +            save_pgn(gamestate, gameinfo);
   7.273 +            addstr(" Press 'q' to quit...");
   7.274 +            noecho();
   7.275 +        }
   7.276 +    } while (c != 'q');
   7.277 +    echo();
   7.278 +    
   7.279 +    gamestate_cleanup(gamestate);
   7.280  }
   7.281  
   7.282  void game_start_singlemachine(Settings *settings) {
   7.283      inputy = getmaxy(stdscr) - 6;
   7.284      
   7.285      GameState gamestate;
   7.286 -    memset(&gamestate, 0, sizeof(GameState));
   7.287 -    init_board(&gamestate);
   7.288 -    gamestate.mycolor = WHITE;
   7.289 +    gamestate_init(&gamestate);
   7.290 +    uint8_t curcol = WHITE;
   7.291 +    
   7.292 +    if (settings->continuepgn) {
   7.293 +        FILE *pgnfile = fopen(settings->continuepgn, "r");
   7.294 +        if (pgnfile) {
   7.295 +            int result = read_pgn(pgnfile, &gamestate, &(settings->gameinfo));
   7.296 +            fclose(pgnfile);
   7.297 +            if (result != EXIT_SUCCESS) {
   7.298 +                addstr("Invalid PGN file content.\n");
   7.299 +                return;
   7.300 +            }
   7.301 +            if (!is_game_running(&gamestate)) {
   7.302 +                addstr("Game has ended. Use -S to analyze it.\n");
   7.303 +                return;
   7.304 +            }
   7.305 +            curcol = opponent_color(gamestate.lastmove->move.piece&COLOR_MASK);
   7.306 +        } else {
   7.307 +            printw("Can't read PGN file (%s)\n", strerror(errno));
   7.308 +            return;
   7.309 +        }
   7.310 +    }
   7.311  
   7.312      _Bool running;
   7.313      do {
   7.314          clear();
   7.315 -        draw_board(&gamestate);
   7.316 -        running = !domove_singlemachine(&gamestate, &(settings->gameinfo));
   7.317 -        gamestate.mycolor = opponent_color(gamestate.mycolor);
   7.318 +        draw_board(&gamestate, curcol);
   7.319 +        running = !domove_singlemachine(&gamestate,
   7.320 +            &(settings->gameinfo), curcol);
   7.321 +        curcol = opponent_color(curcol);
   7.322      }  while (running);
   7.323 -    move(0,0);
   7.324 -    draw_board(&gamestate);
   7.325      
   7.326 -    gamestate_cleanup(&gamestate);
   7.327 +    post_game(&gamestate, &(settings->gameinfo));
   7.328  }
   7.329  
   7.330  void game_start(Settings *settings, int opponent) {
   7.331 @@ -461,26 +528,23 @@
   7.332      
   7.333      _Bool myturn = is_server(settings) ==
   7.334          (settings->gameinfo.servercolor == WHITE);
   7.335 +    uint8_t mycolor = myturn ? WHITE : BLACK;
   7.336      
   7.337      GameState gamestate;
   7.338 -    memset(&gamestate, 0, sizeof(GameState));
   7.339 -    init_board(&gamestate);
   7.340 -    gamestate.mycolor = myturn ? WHITE:BLACK;
   7.341 +    gamestate_init(&gamestate);
   7.342      
   7.343      _Bool running;
   7.344      do {
   7.345          clear();
   7.346 -        draw_board(&gamestate);
   7.347 +        draw_board(&gamestate, mycolor);
   7.348          if (myturn) {
   7.349 -            running = !sendmove(&gamestate, &(settings->gameinfo), opponent);
   7.350 +            running = !sendmove(&gamestate, &(settings->gameinfo),
   7.351 +                opponent, mycolor);
   7.352          } else {
   7.353              running = !recvmove(&gamestate, &(settings->gameinfo), opponent);
   7.354          }
   7.355          myturn ^= TRUE;
   7.356      }  while (running);
   7.357      
   7.358 -    move(0,0);
   7.359 -    draw_board(&gamestate);
   7.360 -    
   7.361 -    gamestate_cleanup(&gamestate);
   7.362 +    post_game(&gamestate, &(settings->gameinfo));
   7.363  }
     8.1 --- a/src/main.c	Wed Jun 11 16:54:20 2014 +0200
     8.2 +++ b/src/main.c	Mon Jun 16 13:45:31 2014 +0200
     8.3 @@ -41,7 +41,7 @@
     8.4      uint8_t timeunit = 60;
     8.5      size_t len;
     8.6      
     8.7 -    for (int opt ; (opt = getopt(argc, argv, "a:bc:hp:rst:")) != -1 ;) {
     8.8 +    for (int opt ; (opt = getopt(argc, argv, "a:bc:hp:rsS:t:")) != -1 ;) {
     8.9          switch (opt) {
    8.10          case 'c':
    8.11              settings->continuepgn = optarg;
    8.12 @@ -55,6 +55,9 @@
    8.13          case 's':
    8.14              settings->singlemachine = 1;
    8.15              break;
    8.16 +        case 'S':
    8.17 +            settings->analyzepgn = optarg;
    8.18 +            break;
    8.19          case 't':
    8.20          case 'a':
    8.21              len = strlen(optarg);
    8.22 @@ -103,6 +106,24 @@
    8.23          return 1;
    8.24      }
    8.25      
    8.26 +        
    8.27 +    if (settings->continuepgn) {
    8.28 +        if (settings->serverhost) {
    8.29 +            fprintf(stderr, "Can't continue a game when joining a server.\n");
    8.30 +            return 1;
    8.31 +        }
    8.32 +        if (settings->analyzepgn) {
    8.33 +            fprintf(stderr, "The options -c and -S are mutually exclusive\n");
    8.34 +            return 1;
    8.35 +        }
    8.36 +        // TODO: implement
    8.37 +        if (!settings->singlemachine) {
    8.38 +            fprintf(stderr, "Game continuation currently not supported for "
    8.39 +                "network games.\n");
    8.40 +            return 1;
    8.41 +        }
    8.42 +    }
    8.43 +    
    8.44      return 0;
    8.45  }
    8.46  
    8.47 @@ -111,7 +132,6 @@
    8.48      memset(&settings, 0, sizeof(Settings));
    8.49      settings.gameinfo.servercolor = WHITE;
    8.50      settings.port = "27015";
    8.51 -    settings.continuepgn = NULL;
    8.52      return settings;
    8.53  }
    8.54  
    8.55 @@ -154,17 +174,16 @@
    8.56  "Usage: terminal-chess [OPTION]... [HOST]\n"
    8.57  "Starts/joins a network chess game\n"
    8.58  "\nGeneral options\n"
    8.59 +"  -c <PGN file> Continue the specified game\n"
    8.60  "  -h            This help page\n"
    8.61  "  -p            TCP port to use (default: 27015)\n"
    8.62 +// TODO: implement and activate feature
    8.63 +//"  -S <PGN file> Compute and print statistics for the specified game\n"
    8.64  "\nServer options\n"
    8.65  "  -a <time>     Specifies the time to add after each move\n"
    8.66  "  -b            Server plays black pieces (default: white)\n"
    8.67 -// TODO: implement and activate feature
    8.68 -//"  -c <PGN file> Continue the specified game\n"
    8.69  "  -r            Distribute color randomly\n"
    8.70  "  -s            Single machine mode\n"
    8.71 -// TODO: implement and activate feature
    8.72 -//"  -S <PGN file> Compute and print statistics for the specified game\n"
    8.73  "  -t <time>     Specifies time limit (default: no limit)\n"
    8.74  "\nNotes\n"
    8.75  "The time unit for -a is seconds and for -t minutes by default. To "
    8.76 @@ -192,6 +211,9 @@
    8.77      if (settings.singlemachine) {
    8.78          game_start_singlemachine(&settings);
    8.79          exitcode = EXIT_SUCCESS;
    8.80 +    } else if (settings.analyzepgn) {
    8.81 +        printw("Not implemented yet.\n");
    8.82 +        exitcode = EXIT_SUCCESS;
    8.83      } else {
    8.84          exitcode = is_server(&settings) ?
    8.85              server_run(&settings) : client_run(&settings);
    8.86 @@ -199,6 +221,7 @@
    8.87      
    8.88      mvaddstr(getmaxy(stdscr)-1, 0,
    8.89          "Game has ended. Press any key to leave...");
    8.90 +    clrtoeol();
    8.91      refresh();
    8.92      cbreak();
    8.93      flushinp();
     9.1 --- a/src/terminal-chess.h	Wed Jun 11 16:54:20 2014 +0200
     9.2 +++ b/src/terminal-chess.h	Mon Jun 16 13:45:31 2014 +0200
     9.3 @@ -45,6 +45,7 @@
     9.4      char* port;
     9.5      char* serverhost; /* NULL, if we are about to start a server */
     9.6      char* continuepgn;
     9.7 +    char* analyzepgn;
     9.8      _Bool printhelp;
     9.9      _Bool singlemachine;
    9.10  } Settings;

mercurial