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
--- a/src/chess/Makefile	Wed Jun 11 16:54:20 2014 +0200
+++ b/src/chess/Makefile	Mon Jun 16 13:45:31 2014 +0200
@@ -38,6 +38,7 @@
 SRC += queen.c
 SRC += king.c
 SRC += rules.c
+SRC += pgn.c
 
 OBJ = $(SRC:%.c=$(BUILDDIR)/release/%$(OBJ_EXT))
 OBJ_D = $(SRC:%.c=$(BUILDDIR)/debug/%$(OBJ_EXT))
--- a/src/chess/chess.h	Wed Jun 11 16:54:20 2014 +0200
+++ b/src/chess/chess.h	Mon Jun 16 13:45:31 2014 +0200
@@ -34,3 +34,4 @@
 #include "bishop.h"
 #include "queen.h"
 #include "king.h"
+#include "pgn.h"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/chess/pgn.c	Mon Jun 16 13:45:31 2014 +0200
@@ -0,0 +1,196 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Mike Becker. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "pgn.h"
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+int read_pgn(FILE* stream, GameState *gamestate, GameInfo *gameinfo) {
+    int c, i;
+    
+    char result[8];
+    
+    char tagkey[32];
+    char tagvalue[128];
+    
+    // read tag pairs
+    _Bool readmoves = 0;
+    while (!readmoves) {
+        while (isspace(c = fgetc(stream)));
+        if (c == '1') {
+            readmoves = 1;
+            break;
+        }
+        if (c != '[') {
+            return EXIT_FAILURE;
+        }
+        while (isspace(c = fgetc(stream)));
+        i = 0;
+        do {
+            tagkey[i++] = c;
+        } while (!isspace(c = fgetc(stream)));
+        tagkey[i] = '\0';
+        while (isspace(c = fgetc(stream)));
+        if (c != '"') {
+            return EXIT_FAILURE;
+        }
+        i = 0;
+        while ((c = fgetc(stream)) != '"') {
+            if (c == '\n') {
+                return EXIT_FAILURE;
+            }
+            tagvalue[i++] = c;
+        }
+        tagvalue[i] = '\0';
+        if (fgetc(stream) != ']') {
+            return EXIT_FAILURE;
+        }
+
+        if (strcmp("Result", tagkey) == 0) {
+            memcpy(result, tagvalue, 8);
+        }
+    }
+    
+    // read moves
+    if (fgetc(stream) != '.') {
+        return EXIT_FAILURE;
+    }
+    
+    char movestr[10];
+    Move move;
+    uint8_t curcol = WHITE;
+    
+    while (readmoves) {
+        // move
+        while (isspace(c = fgetc(stream)));
+        i = 0;
+        do {
+            movestr[i++] = c;
+            if (i >= 10) {
+                return EXIT_FAILURE;
+            }
+        } while (!isspace(c = fgetc(stream)));
+        movestr[i] = '\0';
+        if (eval_move(gamestate, movestr, &move, curcol)
+                != VALID_MOVE_SYNTAX) {
+            return EXIT_FAILURE;
+        }
+        if (validate_move(gamestate, &move) != VALID_MOVE_SEMANTICS) {
+            return EXIT_FAILURE;
+        }
+        apply_move(gamestate, &move);
+        
+        // TODO: parse comments
+        while (isspace(c = fgetc(stream)));
+        
+        // end of game data encountered
+        if (c == EOF) {
+            break;
+        }
+        if (c == '1' || c == '0') {
+            c = fgetc(stream);
+            if (c == '-') {
+                gamestate->resign = !gamestate->checkmate;
+                break;
+            } else if (c == '/') {
+                gamestate->remis = !gamestate->stalemate;
+                break;
+            } else {
+                // oops, it was a move number, go back!
+                fseek(stream, -1, SEEK_CUR);
+            }
+        }
+        
+        // we have eaten the next valuable byte, so go back
+        fseek(stream, -1, SEEK_CUR);
+        
+        // skip move number after black move
+        if (curcol == BLACK) {
+            while (isdigit(c = fgetc(stream)));
+            if (c != '.') {
+                return EXIT_FAILURE;
+            }
+        }
+        curcol = opponent_color(curcol);
+    }
+    
+    return EXIT_SUCCESS;
+}
+
+size_t write_pgn(FILE* stream, GameState *gamestate, GameInfo *gameinfo) {
+    // TODO: tag pairs
+    size_t bytes = 0;
+    
+    // Result
+    char *result;
+    if (gamestate->stalemate || gamestate->remis) {
+        result = "1/2-1/2";
+    } else if (gamestate->checkmate || gamestate->resign) {
+        if (gamestate->lastmove) {
+            result = (gamestate->lastmove->move.piece & COLOR_MASK) == WHITE ?
+                "1-0" : "0-1";
+        } else {
+            result = "0-1";
+        }
+    } else {
+        result = "*";
+    }
+    fprintf(stream, "[Result \"%s\"]\n\n", result);
+    
+    // moves
+    int i = 1;
+    for (MoveList *movelist = gamestate->movelist ;
+        movelist ; movelist = movelist->next) {
+        
+        if (++i % 2 == 0) {
+            fprintf(stream, "%d. %s", i/2, movelist->move.string);
+        } else {
+            fprintf(stream, " %s", movelist->move.string);
+        }
+        
+        // TODO: move time and maybe other comments
+        
+        // line break every 10 moves
+        if (i % 20)  {
+            fputc(' ', stream);
+        } else {
+            fputc('\n', stream);
+        }
+    }
+    
+    if (result[0] == '*') {
+        fputc('\n', stream);
+    } else {
+        fprintf(stream, "%s\n", result);
+    }
+    
+    
+    return bytes;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/chess/pgn.h	Mon Jun 16 13:45:31 2014 +0200
@@ -0,0 +1,50 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Mike Becker. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+
+#ifndef PGN_H
+#define	PGN_H
+
+#include "rules.h"
+#include <stdio.h>
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+    
+int read_pgn(FILE *stream, GameState *gamestate, GameInfo *gameinfo);
+size_t write_pgn(FILE* stream, GameState *gamestate, GameInfo *gameinfo);
+
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* PGN_H */
+
--- a/src/chess/rules.c	Wed Jun 11 16:54:20 2014 +0200
+++ b/src/chess/rules.c	Mon Jun 16 13:45:31 2014 +0200
@@ -44,6 +44,22 @@
     return simulation;
 }
 
+void gamestate_init(GameState *gamestate) {
+    memset(gamestate, 0, sizeof(GameState));
+    
+    Board initboard = {
+        {WROOK, WKNIGHT, WBISHOP, WQUEEN, WKING, WBISHOP, WKNIGHT, WROOK},
+        {WPAWN, WPAWN,   WPAWN,   WPAWN,  WPAWN, WPAWN,   WPAWN,   WPAWN},
+        {0,     0,       0,       0,      0,     0,       0,       0},
+        {0,     0,       0,       0,      0,     0,       0,       0},
+        {0,     0,       0,       0,      0,     0,       0,       0},
+        {0,     0,       0,       0,      0,     0,       0,       0},
+        {BPAWN, BPAWN,   BPAWN,   BPAWN,  BPAWN, BPAWN,   BPAWN,   BPAWN},
+        {BROOK, BKNIGHT, BBISHOP, BQUEEN, BKING, BBISHOP, BKNIGHT, BROOK}
+    };
+    memcpy(gamestate->board, initboard, sizeof(Board));
+}
+
 void gamestate_cleanup(GameState *gamestate) {
     MoveList *elem;
     elem = gamestate->movelist;
@@ -587,7 +603,7 @@
     }
 }
 
-int eval_move(GameState *gamestate, char *mstr, Move *move) {
+int eval_move(GameState *gamestate, char *mstr, Move *move, uint8_t color) {
     memset(move, 0, sizeof(Move));
     move->fromfile = POS_UNSPECIFIED;
     move->fromrow = POS_UNSPECIFIED;
@@ -612,7 +628,7 @@
         if (!move->promotion) {
             return INVALID_MOVE_SYNTAX;
         } else {
-            move->promotion |= gamestate->mycolor;
+            move->promotion |= color;
             len -= 2;
             mstr[len] = 0;
         }
@@ -629,7 +645,7 @@
             move->piece = KING;
             move->fromfile = fileidx('e');
             move->tofile = fileidx('g');
-            move->fromrow = move->torow = gamestate->mycolor == WHITE ? 0 : 7;
+            move->fromrow = move->torow = color == WHITE ? 0 : 7;
         } else {
             /* move (e.g. "Nf3") */
             move->piece = getpiece(mstr[0]);
@@ -664,7 +680,7 @@
             move->piece = KING;
             move->fromfile = fileidx('e');
             move->tofile = fileidx('c');
-            move->fromrow = move->torow = gamestate->mycolor == WHITE ? 0 : 7;
+            move->fromrow = move->torow = color == WHITE ? 0 : 7;
         } else {
             move->piece = getpiece(mstr[0]);
             if (mstr[2] == 'x') {
@@ -701,12 +717,12 @@
     
     if (move->piece) {
         if (move->piece == PAWN
-            && move->torow == (gamestate->mycolor==WHITE?7:0)
+            && move->torow == (color==WHITE?7:0)
             && !move->promotion) {
             return NEED_PROMOTION;
         }
         
-        move->piece |= gamestate->mycolor;
+        move->piece |= color;
         if (move->fromfile == POS_UNSPECIFIED
             || move->fromrow == POS_UNSPECIFIED) {
             return getlocation(gamestate, move);
--- a/src/chess/rules.h	Wed Jun 11 16:54:20 2014 +0200
+++ b/src/chess/rules.h	Mon Jun 16 13:45:31 2014 +0200
@@ -110,11 +110,12 @@
 
 typedef struct {
     Board board;
-    uint8_t mycolor;
     MoveList* movelist;
     MoveList* lastmove;
     _Bool checkmate;
     _Bool stalemate;
+    _Bool remis;
+    _Bool resign;
 } GameState;
 
 #define opponent_color(color) ((color)==WHITE?BLACK:WHITE)
@@ -141,6 +142,16 @@
 #define fileidx_s(c) (isfile(c)?fileidx(c):POS_UNSPECIFIED)
 #define rowidx_s(c) (isrow(c)?rowidx(c):POS_UNSPECIFIED)
 
+#define is_game_running(gamestate) !((gamestate)->checkmate || \
+    (gamestate)->resign || (gamestate)->stalemate || (gamestate)->remis)
+
+
+/**
+ * Initializes a game state and prepares the chess board.
+ * @param gamestate the game state to initialize
+ */
+void gamestate_init(GameState *gamestate);
+
 /**
  * Cleans up a game state and frees the memory for the movement list.
  * @param gamestate the game state to clean up
@@ -276,9 +287,10 @@
  * @param gamestate the current game state
  * @param mstr the input string to parse
  * @param move a pointer to object where the move data shall be stored
+ * @param color the color of the player to evaluate the move for
  * @return status code (see macros in this file for the list of codes)
  */
-int eval_move(GameState *gamestate, char *mstr, Move *move);
+int eval_move(GameState *gamestate, char *mstr, Move *move, uint8_t color);
 
 /**
  * Validates move by applying chess rules.
--- a/src/game.c	Wed Jun 11 16:54:20 2014 +0200
+++ b/src/game.c	Mon Jun 16 13:45:31 2014 +0200
@@ -35,6 +35,8 @@
 #include <string.h>
 #include <inttypes.h>
 #include <sys/select.h>
+#include <stdio.h>
+#include <errno.h>
 
 static const uint8_t boardx = 10, boardy = 10;
 static int inputy = 21; /* should be overridden on game startup */
@@ -69,7 +71,7 @@
     return 0;
 }
 
-static void draw_board(GameState *gamestate) {
+static void draw_board(GameState *gamestate, uint8_t perspective) {
     for (uint8_t y = 0 ; y < 8 ; y++) {
         for (uint8_t x = 0 ; x < 8 ; x++) {
             uint8_t col = gamestate->board[y][x] & COLOR_MASK;
@@ -89,8 +91,8 @@
                 )
             );
             
-            int cy = gamestate->mycolor == WHITE ? boardy-y : boardy-7+y;
-            int cx = gamestate->mycolor == WHITE ? boardx+x*3 : boardx+21-x*3;
+            int cy = perspective == WHITE ? boardy-y : boardy-7+y;
+            int cx = perspective == WHITE ? boardx+x*3 : boardx+21-x*3;
             mvaddch(cy, cx, ' ');
             mvaddch(cy, cx+1, piecec);
             mvaddch(cy, cx+2, ' ');
@@ -99,8 +101,8 @@
     
     attrset(A_NORMAL);
     for (uint8_t i = 0 ; i < 8 ; i++) {
-        int x = gamestate->mycolor == WHITE ? boardx+i*3+1 : boardx+22-i*3;
-        int y = gamestate->mycolor == WHITE ? boardy-i : boardy-7+i;
+        int x = perspective == WHITE ? boardx+i*3+1 : boardx+22-i*3;
+        int y = perspective == WHITE ? boardy-i : boardy-7+i;
         mvaddch(boardy+1, x, 'a'+i);
         mvaddch(y, boardx-2, '1'+i);
     }
@@ -122,17 +124,15 @@
             printw("%d. ", logi / 2);
         }
 
-        if (logelem) {
-            addstr(logelem->move.string);
-            if (!logelem->next) {
-                if (gamestate->stalemate) {
-                    addstr(" stalemate");
-                }
+        addstr(logelem->move.string);
+        if (!logelem->next) {
+            if (gamestate->stalemate) {
+                addstr(" stalemate");
             }
-            addch(' ');
-            
-            logelem = logelem->next;
         }
+        addch(' ');
+
+        logelem = logelem->next;
     }
 }
 
@@ -167,8 +167,30 @@
     }
 }
 
-#define MOVESTR_BUFLEN 8
-static int domove_singlemachine(GameState *gamestate, GameInfo *gameinfo) {
+static void save_pgn(GameState *gamestate, GameInfo *gameinfo) {
+    printw("Filename: ");
+    clrtoeol();
+    refresh();
+    
+    char filename[64];
+    int y = getcury(stdscr);
+    if (getnstr(filename, 64) == OK) {
+        move(y, 0);
+        FILE *file = fopen(filename, "w");
+        if (file) {
+            write_pgn(file, gamestate, gameinfo);
+            fclose(file);
+            printw("File saved.");
+        } else {
+            printw("Can't write to file (%s).", strerror(errno));
+        }
+        clrtoeol();
+    }
+}
+
+#define MOVESTR_BUFLEN 10
+static int domove_singlemachine(GameState *gamestate,
+        GameInfo *gameinfo, uint8_t curcolor) {
     
 
     size_t bufpos = 0;
@@ -183,29 +205,33 @@
         move(inputy, 0);
         printw(
             "Use chess notation to enter your move.\n"
-            "Or type 'resign' to resign or 'remis' to end with remis.\n\n"
+            "Or use a command: remis, resign, savepgn\n\n"
             "Type your move: ");
         clrtoeol();
         
         if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) {
             if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) {
+                gamestate->resign = 1;
                 printw("%s resigned!",
-                    gamestate->mycolor==WHITE?"White":"Black");
-                clrtoeol();
+                    curcolor==WHITE?"White":"Black");
+                clrtobot();
                 refresh();
                 return 1;
             } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
+                gamestate->remis = 1;
                 printw("Game ends remis.");
-                clrtoeol();
+                clrtobot();
                 refresh();
                 return 1;
+            } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) {
+                save_pgn(gamestate, gameinfo);
             } else {
                 Move move;
-                int eval_result = eval_move(gamestate, movestr, &move);
-                switch (eval_result) {
+                int result = eval_move(gamestate, movestr, &move, curcolor);
+                switch (result) {
                 case VALID_MOVE_SYNTAX:
-                    eval_result = validate_move(gamestate, &move);
-                    if (eval_result == VALID_MOVE_SEMANTICS) {
+                    result = validate_move(gamestate, &move);
+                    if (result == VALID_MOVE_SEMANTICS) {
                         apply_move(gamestate, &move);
                         if (gamestate->checkmate) {
                             printw("Checkmate!");
@@ -219,11 +245,11 @@
                             return 0;
                         }
                     } else {
-                        eval_move_failed_msg(eval_result);
+                        eval_move_failed_msg(result);
                     }
                     break;
                 default:
-                    eval_move_failed_msg(eval_result);
+                    eval_move_failed_msg(result);
                 }
                 clrtoeol();
             }
@@ -231,7 +257,8 @@
     }
 }
 
-static int sendmove(GameState *gamestate, GameInfo *gameinfo, int opponent) {
+static int sendmove(GameState *gamestate, GameInfo *gameinfo,
+        int opponent, uint8_t mycolor) {
     
     size_t bufpos = 0;
     char movestr[MOVESTR_BUFLEN];
@@ -249,23 +276,26 @@
         if (remisrejected) {
             printw(
                 "Use chess notation to enter your move.\n"
-                "Remis offer rejected - type 'resign' to resign.      \n\n"
+                "Remis offer rejected                    \n\n"
                 "Type your move: ");
         } else {
             printw(
                 "Use chess notation to enter your move.\n"
-                "Or type 'resign' to resign or 'remis' to offer remis.\n\n"
+                "Or use a command: remis, resign, savepgn\n\n"
                 "Type your move: ");
         }
         clrtoeol();
         
         if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) {
             if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) {
+                gamestate->resign = 1;
                 printw("You resigned!");
                 clrtoeol();
                 refresh();
                 net_send_code(opponent, NETCODE_RESIGN);
                 return 1;
+            } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) {
+                save_pgn(gamestate, gameinfo);
             } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
                 if (!remisrejected) {
                     net_send_code(opponent, NETCODE_REMIS);
@@ -273,6 +303,7 @@
                     refresh();
                     code = net_recieve_code(opponent);
                     if (code == NETCODE_ACCEPT) {
+                        gamestate->remis = 1;
                         printw("\rRemis accepted!");
                         clrtoeol();
                         refresh();
@@ -288,7 +319,7 @@
                 }
             } else {
                 Move move;
-                int eval_result = eval_move(gamestate, movestr, &move);
+                int eval_result = eval_move(gamestate, movestr, &move, mycolor);
                 switch (eval_result) {
                 case VALID_MOVE_SYNTAX:
                     net_send_data(opponent, NETCODE_MOVE, &move,
@@ -353,6 +384,8 @@
         timeout.tv_sec = 0;
         timeout.tv_usec = 1e5;
         
+        // TODO: allow commands
+        
         int result = select(opponent+1, &readfds, NULL, NULL, &timeout);
         if (result == -1) {
             printw("\rCannot perform asynchronous network IO");
@@ -369,6 +402,7 @@
                 clrtoeol();
                 return 1;
             case NETCODE_RESIGN:
+                gamestate->resign = 1;
                 printw("\rYour opponent resigned!");
                 clrtoeol();
                 return 1;
@@ -379,6 +413,7 @@
             case NETCODE_REMIS:
                 if (prompt_yesno(
                     "\rYour opponent offers remis - do you accept")) {
+                    gamestate->remis = 1;
                     printw("\rRemis accepted!");
                     clrtoeol();
                     net_send_code(opponent, NETCODE_ACCEPT);
@@ -421,39 +456,71 @@
     }
 }
 
-static void init_board(GameState *gamestate) {
-    Board initboard = {
-        {WROOK, WKNIGHT, WBISHOP, WQUEEN, WKING, WBISHOP, WKNIGHT, WROOK},
-        {WPAWN, WPAWN,   WPAWN,   WPAWN,  WPAWN, WPAWN,   WPAWN,   WPAWN},
-        {0,     0,       0,       0,      0,     0,       0,       0},
-        {0,     0,       0,       0,      0,     0,       0,       0},
-        {0,     0,       0,       0,      0,     0,       0,       0},
-        {0,     0,       0,       0,      0,     0,       0,       0},
-        {BPAWN, BPAWN,   BPAWN,   BPAWN,  BPAWN, BPAWN,   BPAWN,   BPAWN},
-        {BROOK, BKNIGHT, BBISHOP, BQUEEN, BKING, BBISHOP, BKNIGHT, BROOK}
-    };
-    memcpy(gamestate->board, initboard, sizeof(Board));
+static void post_game(GameState *gamestate, GameInfo *gameinfo) {
+    move(0,0);
+    draw_board(gamestate, WHITE);
+    
+    // TODO: network connection is still open here - think about it!
+    
+    mvaddstr(getmaxy(stdscr)-1, 0,
+        "Press 'q' to quit or 's' to save a PGN file...");
+    refresh();
+    flushinp();
+    
+    noecho();
+    int c;
+    do {
+        c = getch();
+        if (c == 's') {
+            addch('\r');
+            echo();
+            save_pgn(gamestate, gameinfo);
+            addstr(" Press 'q' to quit...");
+            noecho();
+        }
+    } while (c != 'q');
+    echo();
+    
+    gamestate_cleanup(gamestate);
 }
 
 void game_start_singlemachine(Settings *settings) {
     inputy = getmaxy(stdscr) - 6;
     
     GameState gamestate;
-    memset(&gamestate, 0, sizeof(GameState));
-    init_board(&gamestate);
-    gamestate.mycolor = WHITE;
+    gamestate_init(&gamestate);
+    uint8_t curcol = WHITE;
+    
+    if (settings->continuepgn) {
+        FILE *pgnfile = fopen(settings->continuepgn, "r");
+        if (pgnfile) {
+            int result = read_pgn(pgnfile, &gamestate, &(settings->gameinfo));
+            fclose(pgnfile);
+            if (result != EXIT_SUCCESS) {
+                addstr("Invalid PGN file content.\n");
+                return;
+            }
+            if (!is_game_running(&gamestate)) {
+                addstr("Game has ended. Use -S to analyze it.\n");
+                return;
+            }
+            curcol = opponent_color(gamestate.lastmove->move.piece&COLOR_MASK);
+        } else {
+            printw("Can't read PGN file (%s)\n", strerror(errno));
+            return;
+        }
+    }
 
     _Bool running;
     do {
         clear();
-        draw_board(&gamestate);
-        running = !domove_singlemachine(&gamestate, &(settings->gameinfo));
-        gamestate.mycolor = opponent_color(gamestate.mycolor);
+        draw_board(&gamestate, curcol);
+        running = !domove_singlemachine(&gamestate,
+            &(settings->gameinfo), curcol);
+        curcol = opponent_color(curcol);
     }  while (running);
-    move(0,0);
-    draw_board(&gamestate);
     
-    gamestate_cleanup(&gamestate);
+    post_game(&gamestate, &(settings->gameinfo));
 }
 
 void game_start(Settings *settings, int opponent) {
@@ -461,26 +528,23 @@
     
     _Bool myturn = is_server(settings) ==
         (settings->gameinfo.servercolor == WHITE);
+    uint8_t mycolor = myturn ? WHITE : BLACK;
     
     GameState gamestate;
-    memset(&gamestate, 0, sizeof(GameState));
-    init_board(&gamestate);
-    gamestate.mycolor = myturn ? WHITE:BLACK;
+    gamestate_init(&gamestate);
     
     _Bool running;
     do {
         clear();
-        draw_board(&gamestate);
+        draw_board(&gamestate, mycolor);
         if (myturn) {
-            running = !sendmove(&gamestate, &(settings->gameinfo), opponent);
+            running = !sendmove(&gamestate, &(settings->gameinfo),
+                opponent, mycolor);
         } else {
             running = !recvmove(&gamestate, &(settings->gameinfo), opponent);
         }
         myturn ^= TRUE;
     }  while (running);
     
-    move(0,0);
-    draw_board(&gamestate);
-    
-    gamestate_cleanup(&gamestate);
+    post_game(&gamestate, &(settings->gameinfo));
 }
--- a/src/main.c	Wed Jun 11 16:54:20 2014 +0200
+++ b/src/main.c	Mon Jun 16 13:45:31 2014 +0200
@@ -41,7 +41,7 @@
     uint8_t timeunit = 60;
     size_t len;
     
-    for (int opt ; (opt = getopt(argc, argv, "a:bc:hp:rst:")) != -1 ;) {
+    for (int opt ; (opt = getopt(argc, argv, "a:bc:hp:rsS:t:")) != -1 ;) {
         switch (opt) {
         case 'c':
             settings->continuepgn = optarg;
@@ -55,6 +55,9 @@
         case 's':
             settings->singlemachine = 1;
             break;
+        case 'S':
+            settings->analyzepgn = optarg;
+            break;
         case 't':
         case 'a':
             len = strlen(optarg);
@@ -103,6 +106,24 @@
         return 1;
     }
     
+        
+    if (settings->continuepgn) {
+        if (settings->serverhost) {
+            fprintf(stderr, "Can't continue a game when joining a server.\n");
+            return 1;
+        }
+        if (settings->analyzepgn) {
+            fprintf(stderr, "The options -c and -S are mutually exclusive\n");
+            return 1;
+        }
+        // TODO: implement
+        if (!settings->singlemachine) {
+            fprintf(stderr, "Game continuation currently not supported for "
+                "network games.\n");
+            return 1;
+        }
+    }
+    
     return 0;
 }
 
@@ -111,7 +132,6 @@
     memset(&settings, 0, sizeof(Settings));
     settings.gameinfo.servercolor = WHITE;
     settings.port = "27015";
-    settings.continuepgn = NULL;
     return settings;
 }
 
@@ -154,17 +174,16 @@
 "Usage: terminal-chess [OPTION]... [HOST]\n"
 "Starts/joins a network chess game\n"
 "\nGeneral options\n"
+"  -c <PGN file> Continue the specified game\n"
 "  -h            This help page\n"
 "  -p            TCP port to use (default: 27015)\n"
+// TODO: implement and activate feature
+//"  -S <PGN file> Compute and print statistics for the specified game\n"
 "\nServer options\n"
 "  -a <time>     Specifies the time to add after each move\n"
 "  -b            Server plays black pieces (default: white)\n"
-// TODO: implement and activate feature
-//"  -c <PGN file> Continue the specified game\n"
 "  -r            Distribute color randomly\n"
 "  -s            Single machine mode\n"
-// TODO: implement and activate feature
-//"  -S <PGN file> Compute and print statistics for the specified game\n"
 "  -t <time>     Specifies time limit (default: no limit)\n"
 "\nNotes\n"
 "The time unit for -a is seconds and for -t minutes by default. To "
@@ -192,6 +211,9 @@
     if (settings.singlemachine) {
         game_start_singlemachine(&settings);
         exitcode = EXIT_SUCCESS;
+    } else if (settings.analyzepgn) {
+        printw("Not implemented yet.\n");
+        exitcode = EXIT_SUCCESS;
     } else {
         exitcode = is_server(&settings) ?
             server_run(&settings) : client_run(&settings);
@@ -199,6 +221,7 @@
     
     mvaddstr(getmaxy(stdscr)-1, 0,
         "Game has ended. Press any key to leave...");
+    clrtoeol();
     refresh();
     cbreak();
     flushinp();
--- a/src/terminal-chess.h	Wed Jun 11 16:54:20 2014 +0200
+++ b/src/terminal-chess.h	Mon Jun 16 13:45:31 2014 +0200
@@ -45,6 +45,7 @@
     char* port;
     char* serverhost; /* NULL, if we are about to start a server */
     char* continuepgn;
+    char* analyzepgn;
     _Bool printhelp;
     _Bool singlemachine;
 } Settings;

mercurial