Mon, 16 Jun 2014 13:45:31 +0200
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;