| 34 #include <string.h> |
34 #include <string.h> |
| 35 #include <sys/time.h> |
35 #include <sys/time.h> |
| 36 #include <time.h> |
36 #include <time.h> |
| 37 |
37 |
| 38 enum { |
38 enum { |
| 39 pgn_error_missing_quote = 1, |
39 pgn_no_error = 0, |
| |
40 pgn_error_missing_quote, |
| 40 pgn_error_missing_bracket, |
41 pgn_error_missing_bracket, |
| 41 pgn_error_missing_brace, |
42 pgn_error_missing_brace, |
| 42 pgn_error_missing_dot, |
43 pgn_error_missing_dot, |
| 43 pgn_error_move_syntax, |
44 pgn_error_move_syntax, |
| 44 pgn_error_move_semantics, |
45 pgn_error_move_semantics, |
| 45 pgn_error_result_syntax, |
46 pgn_error_result_syntax, |
| |
47 pgn_error_result_mismatch, |
| 46 pgn_error_unexpected_eof, |
48 pgn_error_unexpected_eof, |
| 47 }; |
49 }; |
| 48 |
50 |
| 49 static const char* pgn_error_strings[] = { |
51 static const char* pgn_error_strings[] = { |
| 50 "No Error.", |
52 "No Error.", |
| 53 "Tags must be enclosed in square brackets: '[Key \"Value\"]'.", |
55 "Tags must be enclosed in square brackets: '[Key \"Value\"]'.", |
| 54 "Move numbers must be terminated with a dot (e.g. '13.' - not '13').", |
56 "Move numbers must be terminated with a dot (e.g. '13.' - not '13').", |
| 55 "Move is syntactically incorrect.", |
57 "Move is syntactically incorrect.", |
| 56 "Move is not valid according to chess rules.", |
58 "Move is not valid according to chess rules.", |
| 57 "Result syntax is incorrect. Expected 1-0, 0-1, 1/2-1/2, or *.", |
59 "Result syntax is incorrect. Expected 1-0, 0-1, 1/2-1/2, or *.", |
| |
60 "Result at end of move text does not match result in tag roster.", |
| 58 "Unexpected end of data.", |
61 "Unexpected end of data.", |
| 59 }; |
62 }; |
| 60 |
63 |
| 61 const char* pgn_error_str(int code) { |
64 const char* pgn_error_str(int code) { |
| 62 return pgn_error_strings[code]; |
65 return pgn_error_strings[code]; |
| |
66 } |
| |
67 |
| |
68 static const char *pgn_result(GameState *gamestate) { |
| |
69 if (gamestate->stalemate || gamestate->remis) { |
| |
70 return "1/2-1/2"; |
| |
71 } else if (gamestate->wresign) { |
| |
72 return "0-1"; |
| |
73 } else if (gamestate->bresign) { |
| |
74 return "1-0"; |
| |
75 } else if (gamestate->checkmate) { |
| |
76 if (current_color(gamestate) == BLACK) { |
| |
77 return "1-0"; |
| |
78 } else { |
| |
79 return "0-1"; |
| |
80 } |
| |
81 } else { |
| |
82 return "*"; |
| |
83 } |
| 63 } |
84 } |
| 64 |
85 |
| 65 int parse_pgn(const char *pgndata, GameState *gamestate) { |
86 int parse_pgn(const char *pgndata, GameState *gamestate) { |
| 66 int i; |
87 int i; |
| 67 char c; |
88 char c; |
| 68 |
89 |
| 69 char result[8]; |
90 char result[8] = {0}; |
| 70 |
91 |
| 71 char tagkey[32]; |
92 char tagkey[32]; |
| 72 char tagvalue[128]; |
93 char tagvalue[128]; |
| 73 |
94 |
| 74 /* read tag pairs */ |
95 /* read tag pairs */ |
| 101 if (*(pgndata++) != ']') { |
122 if (*(pgndata++) != ']') { |
| 102 return pgn_error_missing_bracket; |
123 return pgn_error_missing_bracket; |
| 103 } |
124 } |
| 104 |
125 |
| 105 // TODO: read clock info |
126 // TODO: read clock info |
| |
127 |
| 106 if (strcmp("Result", tagkey) == 0) { |
128 if (strcmp("Result", tagkey) == 0) { |
| 107 // TODO: why are we parsing this? it is never used! |
|
| 108 memcpy(result, tagvalue, 8); |
129 memcpy(result, tagvalue, 8); |
| 109 } |
130 } |
| 110 } |
131 } |
| 111 |
132 |
| 112 /* read moves */ |
133 /* read moves */ |
| 130 } while (!isspace(c = *(pgndata++))); |
152 } while (!isspace(c = *(pgndata++))); |
| 131 movestr[i] = '\0'; |
153 movestr[i] = '\0'; |
| 132 if (eval_move(gamestate, movestr, &move, curcol) |
154 if (eval_move(gamestate, movestr, &move, curcol) |
| 133 != VALID_MOVE_SYNTAX) { |
155 != VALID_MOVE_SYNTAX) { |
| 134 return pgn_error_move_syntax; |
156 return pgn_error_move_syntax; |
| 135 } |
157 } |
| 136 if (validate_move(gamestate, &move) != VALID_MOVE_SEMANTICS) { |
158 if (validate_move(gamestate, &move) != VALID_MOVE_SEMANTICS) { |
| 137 return pgn_error_move_semantics; |
159 return pgn_error_move_semantics; |
| 138 } |
160 } |
| 139 apply_move(gamestate, &move); |
161 apply_move(gamestate, &move); |
| 140 |
162 |
| 172 gamestate->bresign = true; |
194 gamestate->bresign = true; |
| 173 } else { |
195 } else { |
| 174 return pgn_error_result_syntax; |
196 return pgn_error_result_syntax; |
| 175 } |
197 } |
| 176 } |
198 } |
| |
199 movetext_ends_with_result = true; |
| 177 break; |
200 break; |
| 178 } else if (c == '/') { |
201 } else if (c == '/') { |
| 179 gamestate->remis = !gamestate->stalemate; |
202 gamestate->remis = !gamestate->stalemate; |
| |
203 movetext_ends_with_result = true; |
| 180 break; |
204 break; |
| 181 } else { |
205 } else { |
| 182 /* oops, it was a move number, go back! */ |
206 /* oops, it was a move number, go back! */ |
| 183 pgndata--; |
207 pgndata--; |
| 184 } |
208 } |
| 194 return pgn_error_missing_dot; |
218 return pgn_error_missing_dot; |
| 195 } |
219 } |
| 196 } |
220 } |
| 197 curcol = opponent_color(curcol); |
221 curcol = opponent_color(curcol); |
| 198 } |
222 } |
| 199 |
223 |
| 200 return 0; |
224 /* sanity check result - if it was specified */ |
| |
225 if (movetext_ends_with_result && result[0]) { |
| |
226 if (strncmp(result, pgn_result(gamestate), 8) != 0) { |
| |
227 fprintf(stderr, "Fuck it: %s\n", result); |
| |
228 return pgn_error_result_mismatch; |
| |
229 } |
| |
230 } |
| |
231 |
| |
232 return pgn_no_error; |
| 201 } |
233 } |
| 202 |
234 |
| 203 int read_pgn(FILE *stream, GameState *gamestate) { |
235 int read_pgn(FILE *stream, GameState *gamestate) { |
| 204 fseek(stream, 0, SEEK_END); |
236 fseek(stream, 0, SEEK_END); |
| 205 long int size = ftell(stream); |
237 long int size = ftell(stream); |
| 281 fprintf(stream, "[Site \"%s\"]\n", "Somewhere on Earth"); |
313 fprintf(stream, "[Site \"%s\"]\n", "Somewhere on Earth"); |
| 282 fprintf(stream, "[Date \"%s\"]\n", pgn_date(gamestate)); |
314 fprintf(stream, "[Date \"%s\"]\n", pgn_date(gamestate)); |
| 283 fprintf(stream, "[Round \"%s\"]\n", "-"); |
315 fprintf(stream, "[Round \"%s\"]\n", "-"); |
| 284 fprintf(stream, "[White \"%s\"]\n", pgn_player_name(gamestate, WHITE)); |
316 fprintf(stream, "[White \"%s\"]\n", pgn_player_name(gamestate, WHITE)); |
| 285 fprintf(stream, "[Black \"%s\"]\n", pgn_player_name(gamestate, BLACK)); |
317 fprintf(stream, "[Black \"%s\"]\n", pgn_player_name(gamestate, BLACK)); |
| 286 |
318 const char *result = pgn_result(gamestate); |
| 287 char *result; |
|
| 288 if (gamestate->stalemate || gamestate->remis) { |
|
| 289 result = "1/2-1/2"; |
|
| 290 } else if (gamestate->wresign) { |
|
| 291 result = "0-1"; |
|
| 292 } else if (gamestate->bresign) { |
|
| 293 result = "1-0"; |
|
| 294 } else if (gamestate->checkmate) { |
|
| 295 if (current_color(gamestate) == BLACK) { |
|
| 296 result = "1-0"; |
|
| 297 } else { |
|
| 298 result = "0-1"; |
|
| 299 } |
|
| 300 } else { |
|
| 301 result = "*"; |
|
| 302 } |
|
| 303 fprintf(stream, "[Result \"%s\"]\n\n", result); |
319 fprintf(stream, "[Result \"%s\"]\n\n", result); |
| 304 |
320 |
| 305 // TODO: add optional clock info |
321 // TODO: add optional clock info |
| 306 |
322 |
| 307 /* moves */ |
323 /* moves */ |