src/chess/rules.c

changeset 220
04da225a5677
parent 219
24b866230dd4
equal deleted inserted replaced
219:24b866230dd4 220:04da225a5677
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]);

mercurial