| 70 } while (rank-- > 0); |
71 } while (rank-- > 0); |
| 71 |
72 |
| 72 return i; |
73 return i; |
| 73 } |
74 } |
| 74 |
75 |
| 75 static size_t fen_color(char *str, GameState *gamestate) { |
76 static size_t fen_color(char *str, const GameState *gamestate) { |
| 76 str[0] = current_color(gamestate) == WHITE ? 'w' : 'b'; |
77 str[0] = current_color(gamestate) == WHITE ? 'w' : 'b'; |
| 77 return 1; |
78 return 1; |
| 78 } |
79 } |
| 79 |
80 |
| 80 static size_t fen_castling(char *str, GameState *gamestate) { |
81 static size_t fen_castling(char *str, const GameState *gamestate) { |
| 81 size_t i = 0; |
82 size_t i = 0; |
| 82 if (!gamestate->castling.K) str[i++] = 'K'; |
83 if (!gamestate->castling.K) str[i++] = 'K'; |
| 83 if (!gamestate->castling.Q) str[i++] = 'Q'; |
84 if (!gamestate->castling.Q) str[i++] = 'Q'; |
| 84 if (!gamestate->castling.k) str[i++] = 'k'; |
85 if (!gamestate->castling.k) str[i++] = 'k'; |
| 85 if (!gamestate->castling.q) str[i++] = 'q'; |
86 if (!gamestate->castling.q) str[i++] = 'q'; |
| 86 if (!i) str[i++] = '-'; |
87 if (!i) str[i++] = '-'; |
| 87 |
88 |
| 88 return i; |
89 return i; |
| 89 } |
90 } |
| 90 |
91 |
| 91 static size_t fen_enpassant(char *str, GameState *gamestate) { |
92 static size_t fen_enpassant(char *str, const GameState *gamestate) { |
| 92 |
93 |
| 93 str[0] = '-'; |
94 str[0] = '-'; |
| 94 |
95 |
| 95 for (int file = 0 ; file < 8 ; file++) { |
96 for (int file = 0 ; file < 8 ; file++) { |
| 96 if (enpassant_threat_exists(gamestate, file, 3)) { |
97 if (enpassant_threat_exists(gamestate, file, 3)) { |
| 118 } |
120 } |
| 119 |
121 |
| 120 return sprintf(str, "%u", hm); |
122 return sprintf(str, "%u", hm); |
| 121 } |
123 } |
| 122 |
124 |
| 123 static size_t fen_movenr(char *str, GameState *gamestate) { |
125 static size_t fen_movenr(char *str, const GameState *gamestate) { |
| 124 return sprintf(str, "%u", 1 + gamestate->movecount / 2); |
126 unsigned mc = gamestate->movecount + gamestate->move_start; |
| |
127 return sprintf(str, "%u", 1 + mc / 2); |
| 125 } |
128 } |
| 126 |
129 |
| 127 static size_t fen_space(char *str) { |
130 static size_t fen_space(char *str) { |
| 128 *str = ' '; |
131 *str = ' '; |
| 129 return 1; |
132 return 1; |
| 130 } |
133 } |
| 131 |
134 |
| 132 void fen_compute(char *str, GameState *gamestate) { |
135 void fen_compute(char *str, const GameState *gamestate) { |
| 133 str += fen_pieces(str, gamestate); |
136 str += fen_pieces(str, gamestate); |
| 134 str += fen_space(str); |
137 str += fen_space(str); |
| 135 str += fen_color(str, gamestate); |
138 str += fen_color(str, gamestate); |
| 136 str += fen_space(str); |
139 str += fen_space(str); |
| 137 str += fen_castling(str, gamestate); |
140 str += fen_castling(str, gamestate); |
| 141 str += fen_halfmove(str, gamestate); |
144 str += fen_halfmove(str, gamestate); |
| 142 str += fen_space(str); |
145 str += fen_space(str); |
| 143 str += fen_movenr(str, gamestate); |
146 str += fen_movenr(str, gamestate); |
| 144 *str = '\0'; |
147 *str = '\0'; |
| 145 } |
148 } |
| |
149 |
| |
150 static unsigned fen_parse_number(const char *str, unsigned *target) { |
| |
151 unsigned l = 0; |
| |
152 *target = 0; |
| |
153 while (str[l] >= '0' && str[l] <= '9') { |
| |
154 unsigned n = str[l] - '0'; |
| |
155 *target *= 10; |
| |
156 *target += n; |
| |
157 l++; |
| |
158 } |
| |
159 /* safety precaution - reject unreasonable high numbers */ |
| |
160 if (l > 5) return 0; |
| |
161 return l; |
| |
162 } |
| |
163 |
| |
164 int fen_parse(const char *str, GameState *gamestate) { |
| |
165 const char * const fen_start = str; |
| |
166 // TODO: think about error reporting that is as good as for PGNs |
| |
167 |
| |
168 if (str == NULL) return 1; |
| |
169 |
| |
170 /* zero-initialize the game state */ |
| |
171 memset(gamestate, 0, sizeof(GameState)); |
| |
172 |
| |
173 /* parse the board (FEN starts top-left at "a8") */ |
| |
174 Rank r = 7; |
| |
175 File f = 0; |
| |
176 while (true) { |
| |
177 switch (*str) { |
| |
178 case 'K': gamestate->board[r][f] = WKING; break; |
| |
179 case 'Q': gamestate->board[r][f] = WQUEEN; break; |
| |
180 case 'B': gamestate->board[r][f] = WBISHOP; break; |
| |
181 case 'N': gamestate->board[r][f] = WKNIGHT; break; |
| |
182 case 'R': gamestate->board[r][f] = WROOK; break; |
| |
183 case 'P': gamestate->board[r][f] = WPAWN; break; |
| |
184 case 'k': gamestate->board[r][f] = BKING; break; |
| |
185 case 'q': gamestate->board[r][f] = BQUEEN; break; |
| |
186 case 'b': gamestate->board[r][f] = BBISHOP; break; |
| |
187 case 'n': gamestate->board[r][f] = BKNIGHT; break; |
| |
188 case 'r': gamestate->board[r][f] = BROOK; break; |
| |
189 case 'p': gamestate->board[r][f] = BPAWN; break; |
| |
190 case '1': break; |
| |
191 case '2': f += 1; break; |
| |
192 case '3': f += 2; break; |
| |
193 case '4': f += 3; break; |
| |
194 case '5': f += 4; break; |
| |
195 case '6': f += 5; break; |
| |
196 case '7': f += 6; break; |
| |
197 case '8': f += 7; break; |
| |
198 default: return 1; |
| |
199 } |
| |
200 f++; |
| |
201 str++; |
| |
202 if (f == 8) { |
| |
203 /* rank complete - test for separator or ending space */ |
| |
204 if (r > 0) { |
| |
205 if (*str != '/') return 1; |
| |
206 str++; |
| |
207 f = 0; |
| |
208 r--; |
| |
209 } else { |
| |
210 if (*str != ' ') return 1; |
| |
211 str++; |
| |
212 break; |
| |
213 } |
| |
214 } |
| |
215 } |
| |
216 |
| |
217 /* whose turn is it? */ |
| |
218 bool white_to_move; |
| |
219 if (str[0] == 'w') { |
| |
220 white_to_move = true; |
| |
221 } else if (str[0] == 'b') { |
| |
222 white_to_move = false; |
| |
223 } else { |
| |
224 return 1; |
| |
225 } |
| |
226 if (str[1] != ' ') return 1; |
| |
227 str += 2; |
| |
228 |
| |
229 /* castling rights */ |
| |
230 gamestate->castling.K = gamestate->castling.Q = true; |
| |
231 gamestate->castling.k = gamestate->castling.q = true; |
| |
232 if (*str == '-') { |
| |
233 str++; |
| |
234 } else { |
| |
235 char cstl[5] = "KQkq"; |
| |
236 bool found = false; |
| |
237 for (unsigned i = 0 ; i < 4 ; i++) { |
| |
238 if (*str == cstl[i]) { |
| |
239 found = true; |
| |
240 switch (i) { |
| |
241 case 0: gamestate->castling.K = false; break; |
| |
242 case 1: gamestate->castling.Q = false; break; |
| |
243 case 2: gamestate->castling.k = false; break; |
| |
244 case 3: gamestate->castling.q = false; break; |
| |
245 } |
| |
246 str++; |
| |
247 } |
| |
248 } |
| |
249 if (!found) return 1; /* no castling info found */ |
| |
250 } |
| |
251 if (*str != ' ') return 1; |
| |
252 str++; |
| |
253 |
| |
254 /* is there an en-passant threat? */ |
| |
255 if (*str == '-') { |
| |
256 str++; |
| |
257 } else { |
| |
258 if (isfile(str[0]) && isrank(str[1])) { |
| |
259 f = fileidx(str[0]); |
| |
260 r = rankidx(str[1]); |
| |
261 if (r == 2) { |
| |
262 r = 3; |
| |
263 } else if (r == 5) { |
| |
264 r = 4; |
| |
265 } else { |
| |
266 return 1; |
| |
267 } |
| |
268 /* the threat is applied to the pawn, not the field it passed */ |
| |
269 enpassant_threat_add(gamestate, f, r); |
| |
270 } else { |
| |
271 return 1; |
| |
272 } |
| |
273 } |
| |
274 if (*str != ' ') return 1; |
| |
275 str++; |
| |
276 |
| |
277 /* fifty-moves counter */ |
| |
278 unsigned mnr; |
| |
279 unsigned mlen; |
| |
280 mlen = fen_parse_number(str, &mnr); |
| |
281 if (mlen == 0) return 1; |
| |
282 if (str[mlen] != ' ') return 1; |
| |
283 str += mlen+1; |
| |
284 gamestate->fifty_cntr_start = mnr; |
| |
285 |
| |
286 /* move number */ |
| |
287 mlen = fen_parse_number(str, &mnr); |
| |
288 if (mlen == 0) return 1; |
| |
289 if (mnr == 0) return 1; |
| |
290 if (str[mlen] != '\0') return 1; |
| |
291 str += mlen+1; |
| |
292 gamestate->move_start = 2*mnr - 1; |
| |
293 if (white_to_move) gamestate->move_start--; |
| |
294 |
| |
295 /* only copy the fen string if everything is a success */ |
| |
296 gamestate->fen_start = strdup(fen_start); |
| |
297 return 0; |
| |
298 } |