| 58 |
58 |
| 59 const char* pgn_error_str(int code) { |
59 const char* pgn_error_str(int code) { |
| 60 return pgn_error_strings[code]; |
60 return pgn_error_strings[code]; |
| 61 } |
61 } |
| 62 |
62 |
| 63 int read_pgn(FILE* stream, GameState *gamestate) { |
63 struct pgn_input { |
| |
64 union { |
| |
65 FILE *f; |
| |
66 const char *s; |
| |
67 } data; |
| |
68 union { |
| |
69 int (*f)(FILE*); |
| |
70 int (*s)(const char*); |
| |
71 } getc; |
| |
72 void (*backc)(struct pgn_input *); |
| |
73 }; |
| |
74 |
| |
75 static int pgngetc(struct pgn_input *pgndata) { |
| |
76 return pgndata->getc.f(pgndata->data.f); |
| |
77 } |
| |
78 |
| |
79 static void pgnbackc(struct pgn_input *pgndata) { |
| |
80 pgndata->backc(pgndata); |
| |
81 } |
| |
82 |
| |
83 static void fbackc(struct pgn_input *pgn) { |
| |
84 fseek(pgn->data.f, -1, SEEK_CUR); |
| |
85 } |
| |
86 |
| |
87 static void sbackc(struct pgn_input *pgn) { |
| |
88 --(pgn->data.s); |
| |
89 } |
| |
90 |
| |
91 static int sgetc(const char *s) { |
| |
92 return *s; |
| |
93 } |
| |
94 |
| |
95 static int read_pgn_(struct pgn_input pgndata, GameState *gamestate) { |
| 64 int c, i; |
96 int c, i; |
| 65 |
97 |
| 66 char result[8]; |
98 char result[8]; |
| 67 |
99 |
| 68 char tagkey[32]; |
100 char tagkey[32]; |
| 69 char tagvalue[128]; |
101 char tagvalue[128]; |
| 70 |
102 |
| 71 /* read tag pairs */ |
103 /* read tag pairs */ |
| 72 while (true) { |
104 while (true) { |
| 73 while (isspace(c = fgetc(stream))); |
105 while (isspace(c = pgngetc(&pgndata))); |
| 74 if (c == '1') { |
106 if (c == '1') { |
| 75 break; |
107 break; |
| 76 } |
108 } |
| 77 if (c != '[') { |
109 if (c != '[') { |
| 78 return pgn_error_missing_bracket; |
110 return pgn_error_missing_bracket; |
| 79 } |
111 } |
| 80 while (isspace(c = fgetc(stream))); |
112 while (isspace(c = pgngetc(&pgndata))); |
| 81 i = 0; |
113 i = 0; |
| 82 do { |
114 do { |
| 83 tagkey[i++] = c; |
115 tagkey[i++] = c; |
| 84 } while (!isspace(c = fgetc(stream))); |
116 } while (!isspace(c = pgngetc(&pgndata))); |
| 85 tagkey[i] = '\0'; |
117 tagkey[i] = '\0'; |
| 86 while (isspace(c = fgetc(stream))); |
118 while (isspace(c = pgngetc(&pgndata))); |
| 87 if (c != '"') { |
119 if (c != '"') { |
| 88 return pgn_error_missing_quote; |
120 return pgn_error_missing_quote; |
| 89 } |
121 } |
| 90 i = 0; |
122 i = 0; |
| 91 while ((c = fgetc(stream)) != '"') { |
123 while ((c = pgngetc(&pgndata)) != '"') { |
| 92 if (c == '\n' || c == EOF) { |
124 if (c == '\n' || c == EOF) { |
| 93 return pgn_error_missing_quote; |
125 return pgn_error_missing_quote; |
| 94 } |
126 } |
| 95 tagvalue[i++] = c; |
127 tagvalue[i++] = c; |
| 96 } |
128 } |
| 97 tagvalue[i] = '\0'; |
129 tagvalue[i] = '\0'; |
| 98 if (fgetc(stream) != ']') { |
130 if (pgngetc(&pgndata) != ']') { |
| 99 return pgn_error_missing_bracket; |
131 return pgn_error_missing_bracket; |
| 100 } |
132 } |
| 101 |
133 |
| 102 // TODO: read clock info |
134 // TODO: read clock info |
| 103 if (strcmp("Result", tagkey) == 0) { |
135 if (strcmp("Result", tagkey) == 0) { |
| 105 memcpy(result, tagvalue, 8); |
137 memcpy(result, tagvalue, 8); |
| 106 } |
138 } |
| 107 } |
139 } |
| 108 |
140 |
| 109 /* read moves */ |
141 /* read moves */ |
| 110 if (fgetc(stream) != '.') { |
142 if (pgngetc(&pgndata) != '.') { |
| 111 return pgn_error_missing_dot; |
143 return pgn_error_missing_dot; |
| 112 } |
144 } |
| 113 |
145 |
| 114 char movestr[10]; |
146 char movestr[10]; |
| 115 Move move; |
147 Move move; |
| 116 uint8_t curcol = WHITE; |
148 uint8_t curcol = WHITE; |
| 117 |
149 |
| 118 while (true) { |
150 while (true) { |
| 119 /* move */ |
151 /* move */ |
| 120 while (isspace(c = fgetc(stream))); |
152 while (isspace(c = pgngetc(&pgndata))); |
| 121 i = 0; |
153 i = 0; |
| 122 do { |
154 do { |
| 123 movestr[i++] = c; |
155 movestr[i++] = c; |
| 124 if (i >= 10) { |
156 if (i >= 10) { |
| 125 return pgn_error_move_syntax; |
157 return pgn_error_move_syntax; |
| 126 } |
158 } |
| 127 } while (!isspace(c = fgetc(stream))); |
159 } while (!isspace(c = pgngetc(&pgndata))); |
| 128 movestr[i] = '\0'; |
160 movestr[i] = '\0'; |
| 129 if (eval_move(gamestate, movestr, &move, curcol) |
161 if (eval_move(gamestate, movestr, &move, curcol) |
| 130 != VALID_MOVE_SYNTAX) { |
162 != VALID_MOVE_SYNTAX) { |
| 131 return pgn_error_move_syntax; |
163 return pgn_error_move_syntax; |
| 132 } |
164 } |
| 134 return pgn_error_move_semantics; |
166 return pgn_error_move_semantics; |
| 135 } |
167 } |
| 136 apply_move(gamestate, &move); |
168 apply_move(gamestate, &move); |
| 137 |
169 |
| 138 /* skip spaces */ |
170 /* skip spaces */ |
| 139 while (isspace(c = fgetc(stream))); |
171 while (isspace(c = pgngetc(&pgndata))); |
| 140 |
172 |
| 141 /* parse possible comment */ |
173 /* parse possible comment */ |
| 142 if (c == '{') { |
174 if (c == '{') { |
| 143 // TODO: interpret comment (may contain clock info) |
175 // TODO: interpret comment (may contain clock info) |
| 144 do { |
176 do { |
| 145 c = fgetc(stream); |
177 c = pgngetc(&pgndata); |
| 146 } while (c != '}' && c != EOF); |
178 } while (c != '}' && c != EOF); |
| 147 if (c == EOF) { |
179 if (c == EOF) { |
| 148 return pgn_error_missing_brace; |
180 return pgn_error_missing_brace; |
| 149 } |
181 } |
| 150 /* skip spaces */ |
182 /* skip spaces */ |
| 151 while (isspace(c = fgetc(stream))); |
183 while (isspace(c = pgngetc(&pgndata))); |
| 152 } |
184 } |
| 153 |
185 |
| 154 /* end of game data encountered */ |
186 /* end of game data encountered */ |
| 155 if (c == EOF) { |
187 if (c == EOF) { |
| 156 break; |
188 break; |
| 157 } |
189 } |
| 158 if (c == '1' || c == '0') { |
190 if (c == '1' || c == '0') { |
| 159 c = fgetc(stream); |
191 c = pgngetc(&pgndata); |
| 160 // TODO: #842 - allow games to be continued if possible |
192 // TODO: #842 - allow games to be continued if possible |
| 161 if (c == '-') { |
193 if (c == '-') { |
| 162 if (!gamestate->checkmate) { |
194 if (!gamestate->checkmate) { |
| 163 // TODO: maybe we should not parse this here; |
195 // TODO: maybe we should not parse this here; |
| 164 // there is an unused result string from the STR |
196 // there is an unused result string from the STR |
| 165 c = fgetc(stream); |
197 c = pgngetc(&pgndata); |
| 166 if (c == '1') { |
198 if (c == '1') { |
| 167 gamestate->wresign = true; |
199 gamestate->wresign = true; |
| 168 } else if (c == '0') { |
200 } else if (c == '0') { |
| 169 gamestate->bresign = true; |
201 gamestate->bresign = true; |
| 170 } else { |
202 } else { |
| 175 } else if (c == '/') { |
207 } else if (c == '/') { |
| 176 gamestate->remis = !gamestate->stalemate; |
208 gamestate->remis = !gamestate->stalemate; |
| 177 break; |
209 break; |
| 178 } else { |
210 } else { |
| 179 /* oops, it was a move number, go back! */ |
211 /* oops, it was a move number, go back! */ |
| 180 fseek(stream, -1, SEEK_CUR); |
212 pgnbackc(&pgndata); |
| 181 } |
213 } |
| 182 } |
214 } |
| 183 |
215 |
| 184 /* we have eaten the next valuable byte, so go back */ |
216 /* we have eaten the next valuable byte, so go back */ |
| 185 fseek(stream, -1, SEEK_CUR); |
217 pgnbackc(&pgndata); |
| 186 |
218 |
| 187 /* skip move number after black move */ |
219 /* skip move number after black move */ |
| 188 if (curcol == BLACK) { |
220 if (curcol == BLACK) { |
| 189 while (isdigit(c = fgetc(stream))); |
221 while (isdigit(c = pgngetc(&pgndata))); |
| 190 if (c != '.') { |
222 if (c != '.') { |
| 191 return pgn_error_missing_dot; |
223 return pgn_error_missing_dot; |
| 192 } |
224 } |
| 193 } |
225 } |
| 194 curcol = opponent_color(curcol); |
226 curcol = opponent_color(curcol); |
| 195 } |
227 } |
| 196 |
228 |
| 197 return 0; |
229 return 0; |
| |
230 } |
| |
231 |
| |
232 int read_pgn(FILE *stream, GameState *gamestate) { |
| |
233 return read_pgn_( |
| |
234 (struct pgn_input){ |
| |
235 {.f = stream}, |
| |
236 {.f = fgetc}, |
| |
237 fbackc |
| |
238 }, gamestate); |
| |
239 } |
| |
240 |
| |
241 int parse_pgn(const char *data, GameState *gamestate) { |
| |
242 return read_pgn_( |
| |
243 (struct pgn_input){ |
| |
244 {.s = data}, |
| |
245 {.s = sgetc}, |
| |
246 sbackc |
| |
247 }, gamestate); |
| 198 } |
248 } |
| 199 |
249 |
| 200 static void pgn_insert_newlines(char *block) { |
250 static void pgn_insert_newlines(char *block) { |
| 201 size_t pos = 0; |
251 size_t pos = 0; |
| 202 size_t last_space_pos = 0; |
252 size_t last_space_pos = 0; |