| 142 default: |
142 default: |
| 143 return 0; |
143 return 0; |
| 144 } |
144 } |
| 145 } |
145 } |
| 146 |
146 |
| 147 static bool check_stalemate(const GameState *gamestate) { |
147 |
| |
148 /* applies the move without recalculating gamestate flags */ |
| |
149 static void apply_move_internal(GameState *gamestate, Move *move) { |
| |
150 /* en passant capture */ |
| |
151 if (move->capture && piece_type(move->piece) == PAWN && |
| |
152 piece_at(gamestate, move->tofile, move->torank) == 0) { |
| |
153 piece_remove(gamestate, move->tofile, move->fromrank); |
| |
154 } |
| |
155 |
| |
156 /* remove old en passant threats */ |
| |
157 for (File file = 0 ; file < 8 ; file++) { |
| |
158 enpassant_threat_remove(gamestate, file, 3); |
| |
159 enpassant_threat_remove(gamestate, file, 4); |
| |
160 } |
| |
161 |
| |
162 /* move (and maybe capture or promote) */ |
| |
163 piece_remove(gamestate, move->fromfile, move->fromrank); |
| |
164 if (move->promotion) { |
| |
165 piece_set(gamestate, move->tofile, move->torank, move->promotion); |
| |
166 } else { |
| |
167 piece_set(gamestate, move->tofile, move->torank, move->piece); |
| |
168 } |
| |
169 |
| |
170 /* add new en passant threat */ |
| |
171 if (piece_type(move->piece) == PAWN && ( |
| |
172 (move->fromrank == 1 && move->torank == 3) || |
| |
173 (move->fromrank == 6 && move->torank == 4))) { |
| |
174 enpassant_threat_add(gamestate, move->tofile, move->torank); |
| |
175 } |
| |
176 |
| |
177 /* castling */ |
| |
178 bool castling_happend = false; |
| |
179 if (piece_type(move->piece) == KING && move->fromfile == fileidx('e')) { |
| |
180 const Color color = piece_color(move->piece); |
| |
181 if (move->tofile == fileidx('g')) { |
| |
182 gamestate->board[move->torank][fileidx('h')] = 0; |
| |
183 gamestate->board[move->torank][fileidx('f')] = mkpiece(ROOK, color); |
| |
184 castling_happend = true; |
| |
185 } else if (move->tofile == fileidx('c')) { |
| |
186 gamestate->board[move->torank][fileidx('a')] = 0; |
| |
187 gamestate->board[move->torank][fileidx('d')] = mkpiece(ROOK, color); |
| |
188 castling_happend = true; |
| |
189 } |
| |
190 } |
| |
191 if (castling_happend) { |
| |
192 if (piece_color(move->piece) == WHITE) { |
| |
193 gamestate->castling.K = true; |
| |
194 gamestate->castling.Q = true; |
| |
195 } else { |
| |
196 gamestate->castling.k = true; |
| |
197 gamestate->castling.q = true; |
| |
198 } |
| |
199 } else { |
| |
200 if (piece_color(move->piece) == WHITE) { |
| |
201 if (move->fromrank == rankidx('1')) { |
| |
202 if (move->fromfile == fileidx('e')) { |
| |
203 gamestate->castling.K = gamestate->castling.Q = true; |
| |
204 } else if (move->fromfile == fileidx('h')) { |
| |
205 gamestate->castling.K = true; |
| |
206 } else if (move->fromfile == fileidx('a')) { |
| |
207 gamestate->castling.Q = true; |
| |
208 } |
| |
209 } |
| |
210 } else { |
| |
211 if (move->fromrank == rankidx('8')) { |
| |
212 if (move->fromfile == fileidx('e')) { |
| |
213 gamestate->castling.k = gamestate->castling.q = true; |
| |
214 } else if (move->fromfile == fileidx('h')) { |
| |
215 gamestate->castling.k = true; |
| |
216 } else if (move->fromfile == fileidx('a')) { |
| |
217 gamestate->castling.q = true; |
| |
218 } |
| |
219 } |
| |
220 } |
| |
221 } |
| |
222 |
| |
223 /* add move to the moves array and the new position to the FEN array */ |
| |
224 if (gamestate->movecount == gamestate->movecapacity) { |
| |
225 gamestate->movecapacity += 64; /* 32 more full moves */ |
| |
226 gamestate->moves = realloc(gamestate->moves, |
| |
227 gamestate->movecapacity * sizeof(Move)); |
| |
228 gamestate->fen = realloc(gamestate->fen, |
| |
229 gamestate->movecapacity * sizeof(char*)); |
| |
230 } |
| |
231 |
| |
232 /* copy the move data into the game's move array */ |
| |
233 Move *melem = &gamestate->moves[gamestate->movecount]; |
| |
234 *melem = *move; |
| |
235 calc_movetime(gamestate, melem); |
| |
236 |
| |
237 /* important: only "add" the move after calculating the time! */ |
| |
238 gamestate->movecount++; |
| |
239 |
| |
240 /* calculate the FEN of the new position and store it in the FEN array */ |
| |
241 char fen[FEN_MAX_LENGTH]; |
| |
242 fen_compute(fen, gamestate); |
| |
243 gamestate->fen[gamestate->movecount - 1] = strdup(fen); |
| |
244 } |
| |
245 |
| |
246 /* return 0 = no check, 1 = check, 2 = checkmate */ |
| |
247 static int determine_check_or_checkmate( |
| |
248 const GameState *gamestate, const Move *move) { |
| |
249 |
| |
250 /* either simulate one more move or check for current state */ |
| |
251 Color piececolor, oppcolor; |
| |
252 GameState simulation = gamestate_copy_sim(gamestate); |
| |
253 if (move != NULL) { |
| |
254 piececolor = piece_color(move->piece); |
| |
255 oppcolor = opponent_color(piececolor); |
| |
256 Move simmove = *move; |
| |
257 apply_move_internal(&simulation, &simmove); |
| |
258 } else { |
| |
259 oppcolor = current_color(gamestate); |
| |
260 piececolor = opponent_color(oppcolor); |
| |
261 } |
| |
262 |
| |
263 /* find the opposing king */ |
| |
264 File opkingfile = 0; |
| |
265 Rank opkingrank = 0; |
| |
266 for (Rank rank = 0 ; rank < 8 ; rank++) { |
| |
267 for (File file = 0 ; file < 8 ; file++) { |
| |
268 Piece p = piece_at(&simulation, file, rank); |
| |
269 if (p == mkpiece(KING, oppcolor)) { |
| |
270 opkingfile = file; |
| |
271 opkingrank = rank; |
| |
272 } |
| |
273 } |
| |
274 } |
| |
275 |
| |
276 /* determine if the opposing king is now threatened */ |
| |
277 Move threats[16]; |
| |
278 size_t threatcount; |
| |
279 bool incheck = get_threats(&simulation, opkingfile, opkingrank, |
| |
280 piececolor, threats, &threatcount); |
| |
281 |
| |
282 if (!incheck) { |
| |
283 gamestate_cleanup(&simulation); |
| |
284 return 0; |
| |
285 } |
| |
286 |
| |
287 /* determine possible escape fields */ |
| |
288 bool canescape = false; |
| |
289 for (int dr = -1 ; dr <= 1 && !canescape ; dr++) { |
| |
290 for (int df = -1 ; df <= 1 && !canescape ; df++) { |
| |
291 if (dr == 0 && df == 0) continue; |
| |
292 Rank er = opkingrank + dr; |
| |
293 File ef = opkingfile + df; |
| |
294 if (!isidx(er) || !isidx(ef)) continue; |
| |
295 |
| |
296 /* check if piece of the king's color blocks the field */ |
| |
297 if (piece_color(simulation.board[er][ef]) == oppcolor) |
| |
298 continue; |
| |
299 |
| |
300 /* check if escape field is already covered (threatened) */ |
| |
301 if (is_covered(&simulation, ef, er, piececolor)) |
| |
302 continue; |
| |
303 |
| |
304 /* check if an attacking piece blocks the field */ |
| |
305 if (piece_color(simulation.board[er][ef]) == piececolor) { |
| |
306 /* test if the king can fight back */ |
| |
307 GameState sim_retaliate = gamestate_copy_sim(&simulation); |
| |
308 Move move_retaliate = {0}; |
| |
309 move_retaliate.piece = mkpiece(KING, oppcolor); |
| |
310 move_retaliate.fromrank = opkingrank; |
| |
311 move_retaliate.fromfile = opkingfile; |
| |
312 move_retaliate.torank = er; |
| |
313 move_retaliate.tofile = ef; |
| |
314 move_retaliate.capture = true; |
| |
315 apply_move_internal(&sim_retaliate, &move_retaliate); |
| |
316 canescape = !is_covered(&sim_retaliate, ef, er, piececolor); |
| |
317 gamestate_cleanup(&sim_retaliate); |
| |
318 continue; |
| |
319 } |
| |
320 |
| |
321 /* the field is not covered and unoccupied */ |
| |
322 canescape = true; |
| |
323 } |
| |
324 } |
| |
325 |
| |
326 /* can't escape, can the king be rescued? */ |
| |
327 if (!canescape && threatcount == 1) { |
| |
328 canescape = is_protected(&simulation, |
| |
329 threats[0].fromfile, threats[0].fromrank, oppcolor); |
| |
330 } |
| |
331 |
| |
332 /* can't capture, can he block? */ |
| |
333 if (!canescape && threatcount == 1) { |
| |
334 Move *threat = &(threats[0]); |
| |
335 unsigned tptype = piece_type(threat->piece); |
| |
336 |
| |
337 /* knight, pawns and the king cannot be blocked */ |
| |
338 if (tptype == BISHOP || tptype == ROOK || tptype == QUEEN) { |
| |
339 if (threat->fromrank == threat->torank) { |
| |
340 /* rook aspect (on rank) */ |
| |
341 int d = threat->tofile > threat->fromfile ? 1 : -1; |
| |
342 File file = threat->fromfile; |
| |
343 while (!canescape && file != threat->tofile - d) { |
| |
344 file += d; |
| |
345 canescape |= is_protected(&simulation, |
| |
346 file, threat->torank, oppcolor); |
| |
347 } |
| |
348 } else if (threat->fromfile == threat->tofile) { |
| |
349 /* rook aspect (on file) */ |
| |
350 int d = threat->torank > threat->fromrank ? 1 : -1; |
| |
351 Rank rank = threat->fromrank; |
| |
352 while (!canescape && rank != threat->torank - d) { |
| |
353 rank += d; |
| |
354 canescape |= is_protected(&simulation, |
| |
355 threat->tofile, rank, oppcolor); |
| |
356 } |
| |
357 } else { |
| |
358 /* bishop aspect */ |
| |
359 int dr = threat->torank > threat->fromrank ? 1 : -1; |
| |
360 int df = threat->tofile > threat->fromfile ? 1 : -1; |
| |
361 |
| |
362 Rank rank = threat->fromrank; |
| |
363 File file = threat->fromfile; |
| |
364 while (!canescape && file != threat->tofile - df |
| |
365 && rank != threat->torank - dr) { |
| |
366 rank += dr; |
| |
367 file += df; |
| |
368 canescape |= is_protected(&simulation, |
| |
369 file, rank, oppcolor); |
| |
370 } |
| |
371 } |
| |
372 } |
| |
373 } |
| |
374 gamestate_cleanup(&simulation); |
| |
375 return canescape ? 1 : 2; |
| |
376 } |
| |
377 |
| |
378 bool check_checkmate(const GameState *gamestate) { |
| |
379 return determine_check_or_checkmate(gamestate, NULL) == 2; |
| |
380 } |
| |
381 |
| |
382 bool check_stalemate(const GameState *gamestate) { |
| 148 Color next_player = current_color(gamestate); |
383 Color next_player = current_color(gamestate); |
| 149 |
384 |
| 150 /* scan the board for pieces of the next player's color */ |
385 /* scan the board for pieces of the next player's color */ |
| 151 Move moves[QUEEN_MOVES_MAX]; |
386 Move moves[QUEEN_MOVES_MAX]; |
| 152 for (Rank r = 0; r < 8; r++) { |
387 for (Rank r = 0; r < 8; r++) { |
| 311 case 'K': return mkpiece(KING, color); |
546 case 'K': return mkpiece(KING, color); |
| 312 default: return 0; |
547 default: return 0; |
| 313 } |
548 } |
| 314 } |
549 } |
| 315 |
550 |
| 316 /* applies the move without recalculating gamestate flags */ |
|
| 317 static void apply_move_internal(GameState *gamestate, Move *move) { |
|
| 318 /* en passant capture */ |
|
| 319 if (move->capture && piece_type(move->piece) == PAWN && |
|
| 320 piece_at(gamestate, move->tofile, move->torank) == 0) { |
|
| 321 piece_remove(gamestate, move->tofile, move->fromrank); |
|
| 322 } |
|
| 323 |
|
| 324 /* remove old en passant threats */ |
|
| 325 for (File file = 0 ; file < 8 ; file++) { |
|
| 326 enpassant_threat_remove(gamestate, file, 3); |
|
| 327 enpassant_threat_remove(gamestate, file, 4); |
|
| 328 } |
|
| 329 |
|
| 330 /* move (and maybe capture or promote) */ |
|
| 331 piece_remove(gamestate, move->fromfile, move->fromrank); |
|
| 332 if (move->promotion) { |
|
| 333 piece_set(gamestate, move->tofile, move->torank, move->promotion); |
|
| 334 } else { |
|
| 335 piece_set(gamestate, move->tofile, move->torank, move->piece); |
|
| 336 } |
|
| 337 |
|
| 338 /* add new en passant threat */ |
|
| 339 if (piece_type(move->piece) == PAWN && ( |
|
| 340 (move->fromrank == 1 && move->torank == 3) || |
|
| 341 (move->fromrank == 6 && move->torank == 4))) { |
|
| 342 enpassant_threat_add(gamestate, move->tofile, move->torank); |
|
| 343 } |
|
| 344 |
|
| 345 /* castling */ |
|
| 346 bool castling_happend = false; |
|
| 347 if (piece_type(move->piece) == KING && move->fromfile == fileidx('e')) { |
|
| 348 const Color color = piece_color(move->piece); |
|
| 349 if (move->tofile == fileidx('g')) { |
|
| 350 gamestate->board[move->torank][fileidx('h')] = 0; |
|
| 351 gamestate->board[move->torank][fileidx('f')] = mkpiece(ROOK, color); |
|
| 352 castling_happend = true; |
|
| 353 } else if (move->tofile == fileidx('c')) { |
|
| 354 gamestate->board[move->torank][fileidx('a')] = 0; |
|
| 355 gamestate->board[move->torank][fileidx('d')] = mkpiece(ROOK, color); |
|
| 356 castling_happend = true; |
|
| 357 } |
|
| 358 } |
|
| 359 if (castling_happend) { |
|
| 360 if (piece_color(move->piece) == WHITE) { |
|
| 361 gamestate->castling.K = true; |
|
| 362 gamestate->castling.Q = true; |
|
| 363 } else { |
|
| 364 gamestate->castling.k = true; |
|
| 365 gamestate->castling.q = true; |
|
| 366 } |
|
| 367 } else { |
|
| 368 if (piece_color(move->piece) == WHITE) { |
|
| 369 if (move->fromrank == rankidx('1')) { |
|
| 370 if (move->fromfile == fileidx('e')) { |
|
| 371 gamestate->castling.K = gamestate->castling.Q = true; |
|
| 372 } else if (move->fromfile == fileidx('h')) { |
|
| 373 gamestate->castling.K = true; |
|
| 374 } else if (move->fromfile == fileidx('a')) { |
|
| 375 gamestate->castling.Q = true; |
|
| 376 } |
|
| 377 } |
|
| 378 } else { |
|
| 379 if (move->fromrank == rankidx('8')) { |
|
| 380 if (move->fromfile == fileidx('e')) { |
|
| 381 gamestate->castling.k = gamestate->castling.q = true; |
|
| 382 } else if (move->fromfile == fileidx('h')) { |
|
| 383 gamestate->castling.k = true; |
|
| 384 } else if (move->fromfile == fileidx('a')) { |
|
| 385 gamestate->castling.q = true; |
|
| 386 } |
|
| 387 } |
|
| 388 } |
|
| 389 } |
|
| 390 |
|
| 391 /* add move to the moves array and the new position to the FEN array */ |
|
| 392 if (gamestate->movecount == gamestate->movecapacity) { |
|
| 393 gamestate->movecapacity += 64; /* 32 more full moves */ |
|
| 394 gamestate->moves = realloc(gamestate->moves, |
|
| 395 gamestate->movecapacity * sizeof(Move)); |
|
| 396 gamestate->fen = realloc(gamestate->fen, |
|
| 397 gamestate->movecapacity * sizeof(char*)); |
|
| 398 } |
|
| 399 |
|
| 400 /* copy the move data into the game's move array */ |
|
| 401 Move *melem = &gamestate->moves[gamestate->movecount]; |
|
| 402 *melem = *move; |
|
| 403 calc_movetime(gamestate, melem); |
|
| 404 |
|
| 405 /* important: only "add" the move after calculating the time! */ |
|
| 406 gamestate->movecount++; |
|
| 407 |
|
| 408 /* calculate the FEN of the new position and store it in the FEN array */ |
|
| 409 char fen[FEN_MAX_LENGTH]; |
|
| 410 fen_compute(fen, gamestate); |
|
| 411 gamestate->fen[gamestate->movecount - 1] = strdup(fen); |
|
| 412 } |
|
| 413 |
|
| 414 void apply_move(GameState *gamestate, Move *move) { |
551 void apply_move(GameState *gamestate, Move *move) { |
| 415 apply_move_internal(gamestate, move); |
552 apply_move_internal(gamestate, move); |
| 416 |
553 |
| 417 /* calculate gamestate flags in order of efficiency */ |
554 /* calculate gamestate flags in order of efficiency */ |
| 418 if (move->checkmate) { |
555 if (move->checkmate) { |
| 436 move_number = gamestate->movecount; |
573 move_number = gamestate->movecount; |
| 437 } |
574 } |
| 438 for (unsigned i = 0 ; i < move_number ; i++) { |
575 for (unsigned i = 0 ; i < move_number ; i++) { |
| 439 apply_move(replay, &(gamestate->moves[i])); |
576 apply_move(replay, &(gamestate->moves[i])); |
| 440 } |
577 } |
| 441 } |
|
| 442 |
|
| 443 /* return 0 = no check, 1 = check, 2 = checkmate */ |
|
| 444 static int determine_check_or_checkmate( |
|
| 445 const GameState *gamestate, const Move *move) { |
|
| 446 |
|
| 447 /* simulate the move */ |
|
| 448 GameState simulation = gamestate_copy_sim(gamestate); |
|
| 449 Move simmove = *move; |
|
| 450 apply_move_internal(&simulation, &simmove); |
|
| 451 |
|
| 452 /* find the opposing king */ |
|
| 453 Color piececolor = piece_color(move->piece); |
|
| 454 Color oppcolor = opponent_color(piececolor); |
|
| 455 File opkingfile = 0; |
|
| 456 Rank opkingrank = 0; |
|
| 457 for (Rank rank = 0 ; rank < 8 ; rank++) { |
|
| 458 for (File file = 0 ; file < 8 ; file++) { |
|
| 459 Piece p = piece_at(&simulation, file, rank); |
|
| 460 if (p == mkpiece(KING, oppcolor)) { |
|
| 461 opkingfile = file; |
|
| 462 opkingrank = rank; |
|
| 463 } |
|
| 464 } |
|
| 465 } |
|
| 466 |
|
| 467 /* determine if the opposing king is now threatened */ |
|
| 468 Move threats[16]; |
|
| 469 size_t threatcount; |
|
| 470 bool incheck = get_threats(&simulation, opkingfile, opkingrank, |
|
| 471 piececolor, threats, &threatcount); |
|
| 472 |
|
| 473 if (!incheck) { |
|
| 474 gamestate_cleanup(&simulation); |
|
| 475 return 0; |
|
| 476 } |
|
| 477 |
|
| 478 /* determine possible escape fields */ |
|
| 479 bool canescape = false; |
|
| 480 for (int dr = -1 ; dr <= 1 && !canescape ; dr++) { |
|
| 481 for (int df = -1 ; df <= 1 && !canescape ; df++) { |
|
| 482 if (dr == 0 && df == 0) continue; |
|
| 483 Rank er = opkingrank + dr; |
|
| 484 File ef = opkingfile + df; |
|
| 485 if (!isidx(er) || !isidx(ef)) continue; |
|
| 486 |
|
| 487 /* check if piece of the king's color blocks the field */ |
|
| 488 if (piece_color(simulation.board[er][ef]) == oppcolor) |
|
| 489 continue; |
|
| 490 |
|
| 491 /* check if escape field is already covered (threatened) */ |
|
| 492 if (is_covered(&simulation, ef, er, piececolor)) |
|
| 493 continue; |
|
| 494 |
|
| 495 /* check if an attacking piece blocks the field */ |
|
| 496 if (piece_color(simulation.board[er][ef]) == piececolor) { |
|
| 497 /* test if the king can fight back */ |
|
| 498 GameState sim_retaliate = gamestate_copy_sim(&simulation); |
|
| 499 Move move_retaliate = {0}; |
|
| 500 move_retaliate.piece = mkpiece(KING, oppcolor); |
|
| 501 move_retaliate.fromrank = opkingrank; |
|
| 502 move_retaliate.fromfile = opkingfile; |
|
| 503 move_retaliate.torank = er; |
|
| 504 move_retaliate.tofile = ef; |
|
| 505 move_retaliate.capture = true; |
|
| 506 apply_move_internal(&sim_retaliate, &move_retaliate); |
|
| 507 canescape = !is_covered(&sim_retaliate, ef, er, piececolor); |
|
| 508 gamestate_cleanup(&sim_retaliate); |
|
| 509 continue; |
|
| 510 } |
|
| 511 |
|
| 512 /* the field is not covered and unoccupied */ |
|
| 513 canescape = true; |
|
| 514 } |
|
| 515 } |
|
| 516 |
|
| 517 /* can't escape, can the king be rescued? */ |
|
| 518 if (!canescape && threatcount == 1) { |
|
| 519 canescape = is_protected(&simulation, |
|
| 520 threats[0].fromfile, threats[0].fromrank, oppcolor); |
|
| 521 } |
|
| 522 |
|
| 523 /* can't capture, can he block? */ |
|
| 524 if (!canescape && threatcount == 1) { |
|
| 525 Move *threat = &(threats[0]); |
|
| 526 unsigned tptype = piece_type(threat->piece); |
|
| 527 |
|
| 528 /* knight, pawns and the king cannot be blocked */ |
|
| 529 if (tptype == BISHOP || tptype == ROOK || tptype == QUEEN) { |
|
| 530 if (threat->fromrank == threat->torank) { |
|
| 531 /* rook aspect (on rank) */ |
|
| 532 int d = threat->tofile > threat->fromfile ? 1 : -1; |
|
| 533 File file = threat->fromfile; |
|
| 534 while (!canescape && file != threat->tofile - d) { |
|
| 535 file += d; |
|
| 536 canescape |= is_protected(&simulation, |
|
| 537 file, threat->torank, oppcolor); |
|
| 538 } |
|
| 539 } else if (threat->fromfile == threat->tofile) { |
|
| 540 /* rook aspect (on file) */ |
|
| 541 int d = threat->torank > threat->fromrank ? 1 : -1; |
|
| 542 Rank rank = threat->fromrank; |
|
| 543 while (!canescape && rank != threat->torank - d) { |
|
| 544 rank += d; |
|
| 545 canescape |= is_protected(&simulation, |
|
| 546 threat->tofile, rank, oppcolor); |
|
| 547 } |
|
| 548 } else { |
|
| 549 /* bishop aspect */ |
|
| 550 int dr = threat->torank > threat->fromrank ? 1 : -1; |
|
| 551 int df = threat->tofile > threat->fromfile ? 1 : -1; |
|
| 552 |
|
| 553 Rank rank = threat->fromrank; |
|
| 554 File file = threat->fromfile; |
|
| 555 while (!canescape && file != threat->tofile - df |
|
| 556 && rank != threat->torank - dr) { |
|
| 557 rank += dr; |
|
| 558 file += df; |
|
| 559 canescape |= is_protected(&simulation, |
|
| 560 file, rank, oppcolor); |
|
| 561 } |
|
| 562 } |
|
| 563 } |
|
| 564 } |
|
| 565 gamestate_cleanup(&simulation); |
|
| 566 return canescape ? 1 : 2; |
|
| 567 } |
578 } |
| 568 |
579 |
| 569 |
580 |
| 570 void format_move(const GameState *gamestate, Move *move) { |
581 void format_move(const GameState *gamestate, Move *move) { |
| 571 char *string = &(move->string[0]); |
582 char *string = &(move->string[0]); |