| 111 |
112 |
| 112 /* at least 8 characters should be available, wipe them out */ |
113 /* at least 8 characters should be available, wipe them out */ |
| 113 memset(string, 0, 8); |
114 memset(string, 0, 8); |
| 114 |
115 |
| 115 unsigned int idx; |
116 unsigned int idx; |
| 116 if ((move->piece&PIECE_MASK) == KING && |
117 if (piece_type(move->piece) == KING && |
| 117 abs(move->tofile-move->fromfile) == 2) { |
118 abs(move->tofile-move->fromfile) == 2) { |
| 118 /* special formats for castling */ |
119 /* special formats for castling */ |
| 119 if (move->tofile==fileidx('c')) { |
120 if (move->tofile==fileidx('c')) { |
| 120 memcpy(string, "O-O-O", 5); |
121 memcpy(string, "O-O-O", 5); |
| 121 idx = 5; |
122 idx = 5; |
| 127 /* start by notating the piece character */ |
128 /* start by notating the piece character */ |
| 128 string[0] = getpiecechr(move->piece); |
129 string[0] = getpiecechr(move->piece); |
| 129 idx = string[0] ? 1 : 0; |
130 idx = string[0] ? 1 : 0; |
| 130 |
131 |
| 131 /* find out how many source information we do need */ |
132 /* find out how many source information we do need */ |
| 132 uint8_t piece = move->piece & PIECE_MASK; |
133 if (piece_type(move->piece) == PAWN) { |
| 133 if (piece == PAWN) { |
|
| 134 if (move->capture) { |
134 if (move->capture) { |
| 135 string[idx++] = filechr(move->fromfile); |
135 string[idx++] = filechr(move->fromfile); |
| 136 } |
136 } |
| 137 } else if (piece != KING) { |
137 } else if (piece_type(move->piece) != KING) { |
| 138 /* resolve ambiguities, if any */ |
138 /* resolve ambiguities, if any */ |
| 139 Move threats[16]; |
139 Move threats[16]; |
| 140 uint8_t threatcount; |
140 size_t threatcount; |
| 141 if (get_threats(gamestate, move->torow, move->tofile, |
141 if (get_threats(gamestate, move->torow, move->tofile, |
| 142 move->piece&COLOR_MASK, threats, &threatcount)) { |
142 piece_color(move->piece), threats, &threatcount)) { |
| 143 unsigned int ambrows = 0, ambfiles = 0, ambpiece = 0; |
143 unsigned int ambrows = 0, ambfiles = 0, ambpiece = 0; |
| 144 for (uint8_t i = 0 ; i < threatcount ; i++) { |
144 for (size_t i = 0 ; i < threatcount ; i++) { |
| 145 if (threats[i].piece == move->piece) { |
145 if (threats[i].piece == move->piece) { |
| 146 ambpiece++; |
146 ambpiece++; |
| 147 if (threats[i].fromrow == move->fromrow) { |
147 if (threats[i].fromrow == move->fromrow) { |
| 148 ambrows++; |
148 ambrows++; |
| 149 } |
149 } |
| 220 move->movetime.tv_usec = (int32_t) micros; |
220 move->movetime.tv_usec = (int32_t) micros; |
| 221 } |
221 } |
| 222 } |
222 } |
| 223 } |
223 } |
| 224 |
224 |
| 225 char getpiecechr(uint8_t piece) { |
225 char getpiecechr(Piece piece) { |
| 226 switch (piece & PIECE_MASK) { |
226 switch (piece_type(piece)) { |
| 227 case ROOK: return 'R'; |
227 case ROOK: return 'R'; |
| 228 case KNIGHT: return 'N'; |
228 case KNIGHT: return 'N'; |
| 229 case BISHOP: return 'B'; |
229 case BISHOP: return 'B'; |
| 230 case QUEEN: return 'Q'; |
230 case QUEEN: return 'Q'; |
| 231 case KING: return 'K'; |
231 case KING: return 'K'; |
| 232 default: return '\0'; |
232 default: return '\0'; |
| 233 } |
233 } |
| 234 } |
234 } |
| 235 |
235 |
| 236 char* getpieceunicode(uint8_t piece) { |
236 char* getpieceunicode(Piece piece) { |
| 237 if ((piece & COLOR_MASK) == WHITE) { |
237 if (piece_color(piece) == WHITE) { |
| 238 switch (piece & PIECE_MASK) { |
238 switch (piece_type(piece)) { |
| 239 case PAWN: return "\u2659"; |
239 case PAWN: return "\u2659"; |
| 240 case ROOK: return "\u2656"; |
240 case ROOK: return "\u2656"; |
| 241 case KNIGHT: return "\u2658"; |
241 case KNIGHT: return "\u2658"; |
| 242 case BISHOP: return "\u2657"; |
242 case BISHOP: return "\u2657"; |
| 243 case QUEEN: return "\u2655"; |
243 case QUEEN: return "\u2655"; |
| 244 case KING: return "\u2654"; |
244 case KING: return "\u2654"; |
| 245 default: return ""; |
245 default: return ""; |
| 246 } |
246 } |
| 247 } else { |
247 } else { |
| 248 switch (piece & PIECE_MASK) { |
248 switch (piece_type(piece)) { |
| 249 case PAWN: return "\u265f"; |
249 case PAWN: return "\u265f"; |
| 250 case ROOK: return "\u265c"; |
250 case ROOK: return "\u265c"; |
| 251 case KNIGHT: return "\u265e"; |
251 case KNIGHT: return "\u265e"; |
| 252 case BISHOP: return "\u265d"; |
252 case BISHOP: return "\u265d"; |
| 253 case QUEEN: return "\u265b"; |
253 case QUEEN: return "\u265b"; |
| 255 default: return ""; |
255 default: return ""; |
| 256 } |
256 } |
| 257 } |
257 } |
| 258 } |
258 } |
| 259 |
259 |
| 260 uint8_t getpiece(char c) { |
260 Piece getpiece(char c, Color color) { |
| 261 switch (c) { |
261 switch (c) { |
| 262 case 'R': return ROOK; |
262 case 'R': return mkpiece(ROOK, color); |
| 263 case 'N': return KNIGHT; |
263 case 'N': return mkpiece(KNIGHT, color); |
| 264 case 'B': return BISHOP; |
264 case 'B': return mkpiece(BISHOP, color); |
| 265 case 'Q': return QUEEN; |
265 case 'Q': return mkpiece(QUEEN, color); |
| 266 case 'K': return KING; |
266 case 'K': return mkpiece(KING, color); |
| 267 default: return 0; |
267 default: return 0; |
| 268 } |
268 } |
| 269 } |
269 } |
| 270 |
270 |
| 271 // TODO: check if we really need this "simulate" flag for performance |
271 // TODO: check if we really need this "simulate" flag for performance |
| 274 if (!simulate) { |
274 if (!simulate) { |
| 275 if (!move->string[0]) { |
275 if (!move->string[0]) { |
| 276 format_move(gamestate, move); |
276 format_move(gamestate, move); |
| 277 } |
277 } |
| 278 } |
278 } |
| 279 |
279 |
| 280 uint8_t piece = move->piece & PIECE_MASK; |
|
| 281 uint8_t color = move->piece & COLOR_MASK; |
|
| 282 |
|
| 283 /* en passant capture */ |
280 /* en passant capture */ |
| 284 if (move->capture && piece == PAWN && |
281 if (move->capture && piece_type(move->piece) == PAWN && |
| 285 mdst(gamestate->board, move) == 0) { |
282 piece_at(gamestate, mdst(move)) == 0) { |
| 286 gamestate->board[move->fromrow][move->tofile] = 0; |
283 piece_remove(gamestate, move->fromrow, move->tofile); |
| 287 } |
284 } |
| 288 |
285 |
| 289 /* remove old en passant threats */ |
286 /* remove old en passant threats */ |
| 290 for (uint8_t file = 0 ; file < 8 ; file++) { |
287 for (File file = 0 ; file < 8 ; file++) { |
| 291 gamestate->board[3][file] &= ~ENPASSANT_THREAT; |
288 enpassant_threat_remove(gamestate, 3, file); |
| 292 gamestate->board[4][file] &= ~ENPASSANT_THREAT; |
289 enpassant_threat_remove(gamestate, 4, file); |
| 293 } |
290 } |
| 294 |
291 |
| |
292 /* move (and maybe capture or promote) */ |
| |
293 piece_remove(gamestate, msrc(move)); |
| |
294 if (move->promotion) { |
| |
295 piece_set(gamestate, mdst(move), move->promotion); |
| |
296 } else { |
| |
297 piece_set(gamestate, mdst(move), move->piece); |
| |
298 } |
| |
299 |
| 295 /* add new en passant threat */ |
300 /* add new en passant threat */ |
| 296 if (piece == PAWN && ( |
301 if (piece_type(move->piece) == PAWN && ( |
| 297 (move->fromrow == 1 && move->torow == 3) || |
302 (move->fromrow == 1 && move->torow == 3) || |
| 298 (move->fromrow == 6 && move->torow == 4))) { |
303 (move->fromrow == 6 && move->torow == 4))) { |
| 299 move->piece |= ENPASSANT_THREAT; |
304 enpassant_threat_add(gamestate, move->torow, move->tofile); |
| 300 } |
|
| 301 |
|
| 302 /* move (and maybe capture or promote) */ |
|
| 303 msrc(gamestate->board, move) = 0; |
|
| 304 if (move->promotion) { |
|
| 305 mdst(gamestate->board, move) = move->promotion; |
|
| 306 } else { |
|
| 307 mdst(gamestate->board, move) = move->piece; |
|
| 308 } |
305 } |
| 309 |
306 |
| 310 /* castling */ |
307 /* castling */ |
| 311 if (piece == KING && move->fromfile == fileidx('e')) { |
308 if (piece_type(move->piece) == KING && move->fromfile == fileidx('e')) { |
| |
309 const Color color = piece_color(move->piece); |
| 312 if (move->tofile == fileidx('g')) { |
310 if (move->tofile == fileidx('g')) { |
| 313 gamestate->board[move->torow][fileidx('h')] = 0; |
311 gamestate->board[move->torow][fileidx('h')] = 0; |
| 314 gamestate->board[move->torow][fileidx('f')] = color|ROOK; |
312 gamestate->board[move->torow][fileidx('f')] = mkpiece(ROOK, color); |
| 315 } else if (move->tofile == fileidx('c')) { |
313 } else if (move->tofile == fileidx('c')) { |
| 316 gamestate->board[move->torow][fileidx('a')] = 0; |
314 gamestate->board[move->torow][fileidx('a')] = 0; |
| 317 gamestate->board[move->torow][fileidx('d')] = color|ROOK; |
315 gamestate->board[move->torow][fileidx('d')] = mkpiece(ROOK, color); |
| 318 } |
316 } |
| 319 } |
317 } |
| 320 |
318 |
| 321 /* add move to the moves array and the new position to the FEN array */ |
319 /* add move to the moves array and the new position to the FEN array */ |
| 322 if (gamestate->movecount == gamestate->movecapacity) { |
320 if (gamestate->movecount == gamestate->movecapacity) { |
| 361 apply_move_impl(replay, &(gamestate->moves[i]), true); |
359 apply_move_impl(replay, &(gamestate->moves[i]), true); |
| 362 } |
360 } |
| 363 } |
361 } |
| 364 |
362 |
| 365 static int validate_move_rules(GameState *gamestate, Move *move) { |
363 static int validate_move_rules(GameState *gamestate, Move *move) { |
| |
364 assert((move->piece & ~(PIECE_MASK|COLOR_MASK)) == 0); |
| |
365 |
| 366 /* validate indices (don't trust opponent) */ |
366 /* validate indices (don't trust opponent) */ |
| 367 if (!isidx(move->fromrow) || !isidx(move->fromfile) || |
367 if (!isidx(move->fromrow) || !isidx(move->fromfile) || |
| 368 !isidx(move->torow) || !isidx(move->tofile)) { |
368 !isidx(move->torow) || !isidx(move->tofile)) { |
| 369 return INVALID_MOVE_SYNTAX; |
369 return INVALID_MOVE_SYNTAX; |
| 370 } |
370 } |
| 373 if (move->fromfile == move->tofile && move->fromrow == move->torow) { |
373 if (move->fromfile == move->tofile && move->fromrow == move->torow) { |
| 374 return INVALID_MOVE_SYNTAX; |
374 return INVALID_MOVE_SYNTAX; |
| 375 } |
375 } |
| 376 |
376 |
| 377 /* does piece exist */ |
377 /* does piece exist */ |
| 378 if ((msrc(gamestate->board, move)&(PIECE_MASK|COLOR_MASK)) |
378 if (piece_at(gamestate, msrc(move)) != move->piece) { |
| 379 != (move->piece&(PIECE_MASK|COLOR_MASK))) { |
|
| 380 return PIECE_NOT_FOUND; |
379 return PIECE_NOT_FOUND; |
| 381 } |
380 } |
| |
381 |
| |
382 /* is there any piece at the destination? */ |
| |
383 Piece piece_at_dst = piece_at(gamestate, mdst(move)); |
| 382 |
384 |
| 383 /* can't capture own pieces */ |
385 /* can't capture own pieces */ |
| 384 if ((mdst(gamestate->board, move) & COLOR_MASK) |
386 if (piece_color(piece_at_dst) == piece_color(move->piece)) { |
| 385 == (move->piece & COLOR_MASK)) { |
|
| 386 return RULES_VIOLATED; |
387 return RULES_VIOLATED; |
| 387 } |
388 } |
| 388 |
389 |
| 389 /* must capture, if and only if destination is occupied... */ |
390 /* must capture, if and only if destination is occupied... */ |
| 390 if ((mdst(gamestate->board, move) == 0 && move->capture) || |
391 if (!((piece_at_dst == 0) ^ move->capture)) { |
| 391 (mdst(gamestate->board, move) != 0 && !move->capture)) { |
|
| 392 /* ... or the capture happens en passant */ |
392 /* ... or the capture happens en passant */ |
| 393 if (!move->capture || (move->piece & PIECE_MASK) != PAWN || |
393 if (!move->capture || piece_type(move->piece) != PAWN || |
| 394 !(gamestate->board[move->fromrow][move->tofile] |
394 !enpassant_threat_exists(gamestate, |
| 395 & ENPASSANT_THREAT)) { |
395 move->fromrow, move->tofile)) { |
| 396 return INVALID_MOVE_SYNTAX; |
396 return INVALID_MOVE_SYNTAX; |
| 397 } |
397 } |
| 398 } |
398 } |
| 399 |
399 |
| 400 /* validate individual rules */ |
400 /* validate individual rules */ |
| 401 bool chkrules; |
401 bool chkrules; |
| 402 switch (move->piece & PIECE_MASK) { |
402 switch (piece_type(move->piece)) { |
| 403 case PAWN: |
403 case PAWN: |
| 404 chkrules = pawn_chkrules(gamestate, move) && |
404 chkrules = pawn_chkrules(gamestate, move) && |
| 405 !pawn_isblocked(gamestate, move); |
405 !pawn_isblocked(gamestate, move); |
| 406 break; |
406 break; |
| 407 case ROOK: |
407 case ROOK: |
| 443 GameState simulation = gamestate_copy_sim(gamestate); |
443 GameState simulation = gamestate_copy_sim(gamestate); |
| 444 Move simmove = *move; |
444 Move simmove = *move; |
| 445 apply_move_impl(&simulation, &simmove, true); |
445 apply_move_impl(&simulation, &simmove, true); |
| 446 |
446 |
| 447 /* find kings for check validation */ |
447 /* find kings for check validation */ |
| 448 uint8_t piececolor = (move->piece & COLOR_MASK); |
448 Color piececolor = piece_color(move->piece); |
| 449 uint8_t oppcolor = opponent_color(piececolor); |
449 Color oppcolor = opponent_color(piececolor); |
| 450 |
450 |
| 451 uint8_t mykingfile = 0, mykingrow = 0, opkingfile = 0, opkingrow = 0; |
451 File mykingfile = 0, opkingfile = 0; |
| 452 for (uint8_t row = 0 ; row < 8 ; row++) { |
452 Row mykingrow = 0, opkingrow = 0; |
| 453 for (uint8_t file = 0 ; file < 8 ; file++) { |
453 for (Row row = 0 ; row < 8 ; row++) { |
| 454 if (simulation.board[row][file] == |
454 for (File file = 0 ; file < 8 ; file++) { |
| 455 (piececolor == WHITE?WKING:BKING)) { |
455 Piece p = piece_at(&simulation, row, file); |
| |
456 if (p == mkpiece(KING, piececolor)) { |
| 456 mykingfile = file; |
457 mykingfile = file; |
| 457 mykingrow = row; |
458 mykingrow = row; |
| 458 } else if (simulation.board[row][file] == |
459 } else if (p == mkpiece(KING, oppcolor)) { |
| 459 (piececolor == WHITE?BKING:WKING)) { |
|
| 460 opkingfile = file; |
460 opkingfile = file; |
| 461 opkingrow = row; |
461 opkingrow = row; |
| 462 } |
462 } |
| 463 } |
463 } |
| 464 } |
464 } |
| 465 |
465 |
| 466 /* don't move into or stay in check position */ |
466 /* don't move into or stay in check position */ |
| 467 if (is_covered(&simulation, mykingrow, mykingfile, oppcolor)) { |
467 if (is_covered(&simulation, mykingrow, mykingfile, oppcolor)) { |
| 468 gamestate_cleanup(&simulation); |
468 gamestate_cleanup(&simulation); |
| 469 if ((move->piece & PIECE_MASK) == KING) { |
469 if (piece_type(move->piece) == KING) { |
| 470 return KING_MOVES_INTO_CHECK; |
470 return KING_MOVES_INTO_CHECK; |
| 471 } else { |
471 } else { |
| 472 /* last move is always not null in this case */ |
472 /* last move is always not null in this case */ |
| 473 return last_move(gamestate).check ? |
473 return last_move(gamestate).check ? |
| 474 KING_IN_CHECK : PIECE_PINNED; |
474 KING_IN_CHECK : PIECE_PINNED; |
| 475 } |
475 } |
| 476 } |
476 } |
| 477 |
477 |
| 478 /* correct check and checkmate flags (move is still valid) */ |
478 /* correct check and checkmate flags (move is still valid) */ |
| 479 Move threats[16]; |
479 Move threats[16]; |
| 480 uint8_t threatcount; |
480 size_t threatcount; |
| 481 move->check = get_threats(&simulation, opkingrow, opkingfile, |
481 move->check = get_threats(&simulation, opkingrow, opkingfile, |
| 482 piececolor, threats, &threatcount); |
482 piececolor, threats, &threatcount); |
| 483 |
483 |
| 484 if (move->check) { |
484 if (move->check) { |
| 485 /* determine possible escape fields */ |
485 /* determine possible escape fields */ |
| 487 for (int dr = -1 ; dr <= 1 && !canescape ; dr++) { |
487 for (int dr = -1 ; dr <= 1 && !canescape ; dr++) { |
| 488 for (int df = -1 ; df <= 1 && !canescape ; df++) { |
488 for (int df = -1 ; df <= 1 && !canescape ; df++) { |
| 489 if (dr == 0 && df == 0) continue; |
489 if (dr == 0 && df == 0) continue; |
| 490 if (!isidx(opkingrow + dr)) continue; |
490 if (!isidx(opkingrow + dr)) continue; |
| 491 if (!isidx(opkingfile + df)) continue; |
491 if (!isidx(opkingfile + df)) continue; |
| 492 uint8_t er = opkingrow + dr; |
492 Row er = opkingrow + dr; |
| 493 uint8_t ef = opkingfile + df; |
493 File ef = opkingfile + df; |
| 494 |
494 |
| 495 /* check if escape field is already covered (threatened) */ |
495 /* check if escape field is already covered (threatened) */ |
| 496 if (is_covered(&simulation, er, ef, piececolor)) |
496 if (is_covered(&simulation, er, ef, piececolor)) |
| 497 continue; |
497 continue; |
| 498 |
498 |
| 499 /* check if piece of the king's color blocks the field */ |
499 /* check if piece of the king's color blocks the field */ |
| 500 if ((simulation.board[er][ef] & COLOR_MASK) == oppcolor) |
500 if (piece_color(simulation.board[er][ef]) == oppcolor) |
| 501 continue; |
501 continue; |
| 502 |
502 |
| 503 /* check if an attacking piece blocks the field */ |
503 /* check if an attacking piece blocks the field */ |
| 504 if ((simulation.board[er][ef] & COLOR_MASK) == piececolor) { |
504 if (piece_color(simulation.board[er][ef]) == piececolor) { |
| 505 /* test if the king can fight back */ |
505 /* test if the king can fight back */ |
| 506 GameState sim_retaliate = gamestate_copy_sim(&simulation); |
506 GameState sim_retaliate = gamestate_copy_sim(&simulation); |
| 507 Move move_retaliate = {0}; |
507 Move move_retaliate = {0}; |
| 508 move_retaliate.piece = (oppcolor|KING); |
508 move_retaliate.piece = mkpiece(KING, oppcolor); |
| 509 move_retaliate.fromrow = opkingrow; |
509 move_retaliate.fromrow = opkingrow; |
| 510 move_retaliate.fromfile = opkingfile; |
510 move_retaliate.fromfile = opkingfile; |
| 511 move_retaliate.torow = er; |
511 move_retaliate.torow = er; |
| 512 move_retaliate.tofile = ef; |
512 move_retaliate.tofile = ef; |
| 513 move_retaliate.capture = 1; |
513 move_retaliate.capture = 1; |
| 528 } |
528 } |
| 529 |
529 |
| 530 /* can't capture, can he block? */ |
530 /* can't capture, can he block? */ |
| 531 if (!canescape && threatcount == 1) { |
531 if (!canescape && threatcount == 1) { |
| 532 Move *threat = &(threats[0]); |
532 Move *threat = &(threats[0]); |
| 533 uint8_t threatpiece = threat->piece & PIECE_MASK; |
533 unsigned tptype = piece_type(threat->piece); |
| 534 |
534 |
| 535 /* knight, pawns and the king cannot be blocked */ |
535 /* knight, pawns and the king cannot be blocked */ |
| 536 if (threatpiece == BISHOP || threatpiece == ROOK |
536 if (tptype == BISHOP || tptype == ROOK || tptype == QUEEN) { |
| 537 || threatpiece == QUEEN) { |
|
| 538 if (threat->fromrow == threat->torow) { |
537 if (threat->fromrow == threat->torow) { |
| 539 /* rook aspect (on row) */ |
538 /* rook aspect (on row) */ |
| 540 int d = threat->tofile > threat->fromfile ? 1 : -1; |
539 int d = threat->tofile > threat->fromfile ? 1 : -1; |
| 541 uint8_t file = threat->fromfile; |
540 File file = threat->fromfile; |
| 542 while (!canescape && file != threat->tofile - d) { |
541 while (!canescape && file != threat->tofile - d) { |
| 543 file += d; |
542 file += d; |
| 544 canescape |= is_protected(&simulation, |
543 canescape |= is_protected(&simulation, |
| 545 threat->torow, file, oppcolor); |
544 threat->torow, file, oppcolor); |
| 546 } |
545 } |
| 547 } else if (threat->fromfile == threat->tofile) { |
546 } else if (threat->fromfile == threat->tofile) { |
| 548 /* rook aspect (on file) */ |
547 /* rook aspect (on file) */ |
| 549 int d = threat->torow > threat->fromrow ? 1 : -1; |
548 int d = threat->torow > threat->fromrow ? 1 : -1; |
| 550 uint8_t row = threat->fromrow; |
549 Row row = threat->fromrow; |
| 551 while (!canescape && row != threat->torow - d) { |
550 while (!canescape && row != threat->torow - d) { |
| 552 row += d; |
551 row += d; |
| 553 canescape |= is_protected(&simulation, |
552 canescape |= is_protected(&simulation, |
| 554 row, threat->tofile, oppcolor); |
553 row, threat->tofile, oppcolor); |
| 555 } |
554 } |
| 556 } else { |
555 } else { |
| 557 /* bishop aspect */ |
556 /* bishop aspect */ |
| 558 int dr = threat->torow > threat->fromrow ? 1 : -1; |
557 int dr = threat->torow > threat->fromrow ? 1 : -1; |
| 559 int df = threat->tofile > threat->fromfile ? 1 : -1; |
558 int df = threat->tofile > threat->fromfile ? 1 : -1; |
| 560 |
559 |
| 561 uint8_t row = threat->fromrow; |
560 Row row = threat->fromrow; |
| 562 uint8_t file = threat->fromfile; |
561 File file = threat->fromfile; |
| 563 while (!canescape && file != threat->tofile - df |
562 while (!canescape && file != threat->tofile - df |
| 564 && row != threat->torow - dr) { |
563 && row != threat->torow - dr) { |
| 565 row += dr; |
564 row += dr; |
| 566 file += df; |
565 file += df; |
| 567 canescape |= is_protected(&simulation, row, file, |
566 canescape |= is_protected(&simulation, row, file, |
| 568 oppcolor); |
567 oppcolor); |
| 569 } |
568 } |
| 579 gamestate_cleanup(&simulation); |
578 gamestate_cleanup(&simulation); |
| 580 |
579 |
| 581 return VALID_MOVE_SEMANTICS; |
580 return VALID_MOVE_SEMANTICS; |
| 582 } |
581 } |
| 583 |
582 |
| 584 bool get_threats(GameState *gamestate, uint8_t row, uint8_t file, |
583 Piece piece_at(const GameState *gamestate, Row row, File file) { |
| 585 uint8_t color, Move *threats, uint8_t *threatcount) { |
584 return gamestate->board[row][file] & (PIECE_MASK|COLOR_MASK); |
| |
585 } |
| |
586 |
| |
587 void piece_set(GameState *gamestate, Row row, File file, Piece piece) { |
| |
588 gamestate->board[row][file] = piece; |
| |
589 } |
| |
590 |
| |
591 bool get_threats(GameState *gamestate, Row row, File file, |
| |
592 Color color, Move *threats, size_t *threatcount) { |
| 586 Move candidates[32]; |
593 Move candidates[32]; |
| 587 int candidatecount = 0; |
594 size_t ccount = 0; |
| 588 for (uint8_t r = 0 ; r < 8 ; r++) { |
595 for (Row r = 0 ; r < 8 ; r++) { |
| 589 for (uint8_t f = 0 ; f < 8 ; f++) { |
596 for (File f = 0 ; f < 8 ; f++) { |
| 590 if ((gamestate->board[r][f] & COLOR_MASK) == color) { |
597 if (piece_color(gamestate->board[r][f]) == color) { |
| 591 /* non-capturing move */ |
598 /* non-capturing move */ |
| 592 memset(&(candidates[candidatecount]), 0, sizeof(Move)); |
599 memset(&(candidates[ccount]), 0, sizeof(Move)); |
| 593 candidates[candidatecount].piece = gamestate->board[r][f]; |
600 Piece p = piece_at(gamestate, r, f); |
| 594 candidates[candidatecount].fromrow = r; |
601 candidates[ccount].piece = p; |
| 595 candidates[candidatecount].fromfile = f; |
602 candidates[ccount].fromrow = r; |
| 596 candidates[candidatecount].torow = row; |
603 candidates[ccount].fromfile = f; |
| 597 candidates[candidatecount].tofile = file; |
604 candidates[ccount].torow = row; |
| 598 if ((gamestate->board[r][f]&PIECE_MASK) == PAWN |
605 candidates[ccount].tofile = file; |
| 599 && (row == 0 || row == 7)) { |
606 if (piece_type(p) == PAWN && (row == 0 || row == 7)) { |
| 600 /* the exact piece for promotion does not matter */ |
607 /* the exact piece for promotion does not matter */ |
| 601 candidates[candidatecount].promotion = color|QUEEN; |
608 candidates[ccount].promotion = mkpiece(QUEEN, color); |
| 602 } |
609 } |
| 603 candidatecount++; |
610 ccount++; |
| 604 |
611 |
| 605 /* capturing move */ |
612 /* capturing move */ |
| 606 memcpy(&(candidates[candidatecount]), |
613 memcpy(&(candidates[ccount]), |
| 607 &(candidates[candidatecount-1]), sizeof(Move)); |
614 &(candidates[ccount-1]), sizeof(Move)); |
| 608 candidates[candidatecount].capture = 1; |
615 candidates[ccount].capture = 1; |
| 609 candidatecount++; |
616 ccount++; |
| 610 } |
617 } |
| 611 } |
618 } |
| 612 } |
619 } |
| 613 |
620 |
| 614 if (threatcount) { |
621 if (threatcount) { |
| 630 |
637 |
| 631 return result; |
638 return result; |
| 632 } |
639 } |
| 633 |
640 |
| 634 bool is_pinned(GameState *gamestate, Move *move) { |
641 bool is_pinned(GameState *gamestate, Move *move) { |
| 635 uint8_t color = move->piece & COLOR_MASK; |
642 Color color = piece_color(move->piece); |
| 636 |
643 |
| 637 GameState simulation = gamestate_copy_sim(gamestate); |
644 GameState simulation = gamestate_copy_sim(gamestate); |
| 638 Move simmove = *move; |
645 Move simmove = *move; |
| 639 apply_move(&simulation, &simmove); |
646 apply_move(&simulation, &simmove); |
| 640 |
647 |
| 641 uint8_t kingfile = 0, kingrow = 0; |
648 File kingfile = 0; |
| 642 for (uint8_t row = 0 ; row < 8 ; row++) { |
649 Row kingrow = 0; |
| 643 for (uint8_t file = 0 ; file < 8 ; file++) { |
650 for (Row row = 0 ; row < 8 ; row++) { |
| |
651 for (File file = 0 ; file < 8 ; file++) { |
| 644 if (simulation.board[row][file] == (color|KING)) { |
652 if (simulation.board[row][file] == (color|KING)) { |
| 645 kingfile = file; |
653 kingfile = file; |
| 646 kingrow = row; |
654 kingrow = row; |
| 647 } |
655 } |
| 648 } |
656 } |
| 653 gamestate_cleanup(&simulation); |
661 gamestate_cleanup(&simulation); |
| 654 |
662 |
| 655 return covered; |
663 return covered; |
| 656 } |
664 } |
| 657 |
665 |
| 658 bool get_real_threats(GameState *gamestate, uint8_t row, uint8_t file, |
666 bool get_real_threats(GameState *gamestate, Row row, File file, |
| 659 uint8_t color, Move *threats, uint8_t *threatcount) { |
667 Color color, Move *threats, size_t *threatcount) { |
| 660 |
668 |
| 661 if (threatcount) { |
669 if (threatcount) { |
| 662 *threatcount = 0; |
670 *threatcount = 0; |
| 663 } |
671 } |
| 664 |
672 |
| 665 Move candidates[16]; |
673 Move candidates[16]; |
| 666 uint8_t candidatecount; |
674 size_t candidatecount; |
| 667 if (get_threats(gamestate, row, file, color, candidates, &candidatecount)) { |
675 if (get_threats(gamestate, row, file, color, candidates, &candidatecount)) { |
| 668 |
676 |
| 669 bool result = false; |
677 bool result = false; |
| 670 uint8_t kingfile = 0, kingrow = 0; |
678 File kingfile = 0; |
| 671 for (uint8_t r = 0 ; r < 8 ; r++) { |
679 Row kingrow = 0; |
| 672 for (uint8_t f = 0 ; f < 8 ; f++) { |
680 for (Row r = 0 ; r < 8 ; r++) { |
| |
681 for (File f = 0 ; f < 8 ; f++) { |
| 673 if (gamestate->board[r][f] == (color|KING)) { |
682 if (gamestate->board[r][f] == (color|KING)) { |
| 674 kingfile = f; |
683 kingfile = f; |
| 675 kingrow = r; |
684 kingrow = r; |
| 676 } |
685 } |
| 677 } |
686 } |
| 678 } |
687 } |
| 679 |
688 |
| 680 for (uint8_t i = 0 ; i < candidatecount ; i++) { |
689 for (size_t i = 0 ; i < candidatecount ; i++) { |
| 681 // TODO: check if we really need full-blown simulations here |
690 // TODO: check if we really need full-blown simulations here |
| 682 GameState simulation = gamestate_copy_sim(gamestate); |
691 GameState simulation = gamestate_copy_sim(gamestate); |
| 683 Move simmove = candidates[i]; |
692 Move simmove = candidates[i]; |
| 684 apply_move(&simulation, &simmove); |
693 apply_move(&simulation, &simmove); |
| 685 if (!is_covered(&simulation, kingrow, kingfile, |
694 if (!is_covered(&simulation, kingrow, kingfile, |
| 698 } |
707 } |
| 699 } |
708 } |
| 700 |
709 |
| 701 static int getlocation(GameState *gamestate, Move *move) { |
710 static int getlocation(GameState *gamestate, Move *move) { |
| 702 |
711 |
| 703 uint8_t color = move->piece & COLOR_MASK; |
712 Color color = piece_color(move->piece); |
| 704 uint8_t piece = move->piece & PIECE_MASK; |
|
| 705 bool incheck = gamestate->movecount > 0 ? last_move(gamestate).check:false; |
713 bool incheck = gamestate->movecount > 0 ? last_move(gamestate).check:false; |
| 706 |
714 |
| 707 Move threats[16], *threat = NULL; |
715 Move threats[16], *threat = NULL; |
| 708 uint8_t threatcount; |
716 size_t threatcount; |
| 709 |
717 |
| 710 /* determine all threats and sort out the unreal threats on our own */ |
718 /* determine all threats and sort out the unreal threats on our own */ |
| 711 if (get_threats(gamestate, move->torow, move->tofile, color, |
719 if (get_threats(gamestate, move->torow, move->tofile, color, |
| 712 threats, &threatcount)) { |
720 threats, &threatcount)) { |
| 713 |
721 |
| 714 bool found = false; |
722 bool found = false; |
| 715 |
723 |
| 716 /* find threats for the specified position */ |
724 /* find threats for the specified position */ |
| 717 for (uint8_t i = 0 ; i < threatcount ; i++) { |
725 for (size_t i = 0 ; i < threatcount ; i++) { |
| 718 if (threats[i].piece == move->piece && |
726 if (threats[i].piece == move->piece && |
| 719 (move->fromrow == POS_UNSPECIFIED || |
727 (move->fromrow == POS_UNSPECIFIED || |
| 720 move->fromrow == threats[i].fromrow) && |
728 move->fromrow == threats[i].fromrow) && |
| 721 (move->fromfile == POS_UNSPECIFIED || |
729 (move->fromfile == POS_UNSPECIFIED || |
| 722 move->fromfile == threats[i].fromfile)) { |
730 move->fromfile == threats[i].fromfile)) { |
| 758 } else { |
766 } else { |
| 759 return PIECE_NOT_FOUND; |
767 return PIECE_NOT_FOUND; |
| 760 } |
768 } |
| 761 } |
769 } |
| 762 |
770 |
| 763 static int eval_move1(const char *pstr, Move *move, uint8_t color) { |
771 static int eval_move1(const char *pstr, Move *move, Color color) { |
| 764 memset(move, 0, sizeof(Move)); |
772 memset(move, 0, sizeof(Move)); |
| 765 move->fromfile = POS_UNSPECIFIED; |
773 move->fromfile = POS_UNSPECIFIED; |
| 766 move->fromrow = POS_UNSPECIFIED; |
774 move->fromrow = POS_UNSPECIFIED; |
| 767 |
775 |
| 768 size_t len = strlen(pstr); |
776 size_t len = strlen(pstr); |
| 778 move->check = true; |
786 move->check = true; |
| 779 } |
787 } |
| 780 |
788 |
| 781 /* evaluate promotion */ |
789 /* evaluate promotion */ |
| 782 if (len > 3 && mstr[len-2] == '=') { |
790 if (len > 3 && mstr[len-2] == '=') { |
| 783 move->promotion = getpiece(mstr[len-1]); |
791 move->promotion = getpiece(mstr[len-1], color); |
| 784 if (!move->promotion) { |
792 if (!move->promotion) { |
| 785 return INVALID_MOVE_SYNTAX; |
793 return INVALID_MOVE_SYNTAX; |
| 786 } else { |
794 } else { |
| 787 move->promotion |= color; |
|
| 788 len -= 2; |
795 len -= 2; |
| 789 mstr[len] = 0; |
796 mstr[len] = 0; |
| 790 } |
797 } |
| 791 } |
798 } |
| 792 |
799 |
| 793 if (len == 2) { |
800 if (len == 2) { |
| 794 /* pawn move (e.g. "e4") */ |
801 /* pawn move (e.g. "e4") */ |
| 795 move->piece = PAWN; |
802 move->piece = mkpiece(PAWN, color); |
| 796 move->tofile = fileidx(mstr[0]); |
803 move->tofile = fileidx(mstr[0]); |
| 797 move->torow = rowidx(mstr[1]); |
804 move->torow = rowidx(mstr[1]); |
| 798 } else if (len == 3) { |
805 } else if (len == 3) { |
| 799 if (strcmp(mstr, "O-O") == 0) { |
806 if (strcmp(mstr, "O-O") == 0) { |
| 800 /* king side castling */ |
807 /* king side castling */ |
| 801 move->piece = KING; |
808 move->piece = mkpiece(KING, color); |
| 802 move->fromfile = fileidx('e'); |
809 move->fromfile = fileidx('e'); |
| 803 move->tofile = fileidx('g'); |
810 move->tofile = fileidx('g'); |
| 804 move->fromrow = move->torow = color == WHITE ? 0 : 7; |
811 move->fromrow = move->torow = color == WHITE ? 0 : 7; |
| 805 } else { |
812 } else { |
| 806 /* move (e.g. "Nf3") */ |
813 /* move (e.g. "Nf3") */ |
| 807 move->piece = getpiece(mstr[0]); |
814 move->piece = getpiece(mstr[0], color); |
| 808 move->tofile = fileidx(mstr[1]); |
815 move->tofile = fileidx(mstr[1]); |
| 809 move->torow = rowidx(mstr[2]); |
816 move->torow = rowidx(mstr[2]); |
| 810 } |
817 } |
| 811 } else if (len == 4) { |
818 } else if (len == 4) { |
| 812 move->piece = getpiece(mstr[0]); |
819 move->piece = getpiece(mstr[0], color); |
| 813 if (!move->piece) { |
820 if (move->piece == 0) { |
| 814 move->piece = PAWN; |
821 move->piece = mkpiece(PAWN, color); |
| 815 move->fromfile = fileidx(mstr[0]); |
822 move->fromfile = fileidx(mstr[0]); |
| 816 } |
823 } |
| 817 if (mstr[1] == 'x') { |
824 if (mstr[1] == 'x') { |
| 818 /* capture (e.g. "Nxf3", "dxe5") */ |
825 /* capture (e.g. "Nxf3", "dxe5") */ |
| 819 move->capture = 1; |
826 move->capture = 1; |
| 820 } else { |
827 } else { |
| 821 /* move (e.g. "Ndf3", "N2c3", "e2e4") */ |
828 /* move (e.g. "Ndf3", "N2c3", "e2e4") */ |
| 822 if (isfile(mstr[1])) { |
829 if (isfile(mstr[1])) { |
| 823 move->fromfile = fileidx(mstr[1]); |
830 move->fromfile = fileidx(mstr[1]); |
| 824 if (move->piece == PAWN) { |
831 /* when the piece is a pawn, second char cannot be a file */ |
| |
832 if (piece_type(move->piece) == PAWN) { |
| |
833 /* invalidate the result */ |
| 825 move->piece = 0; |
834 move->piece = 0; |
| 826 } |
835 } |
| 827 } else { |
836 } else { |
| 828 move->fromrow = rowidx(mstr[1]); |
837 move->fromrow = rowidx(mstr[1]); |
| 829 } |
838 } |
| 831 move->tofile = fileidx(mstr[2]); |
840 move->tofile = fileidx(mstr[2]); |
| 832 move->torow = rowidx(mstr[3]); |
841 move->torow = rowidx(mstr[3]); |
| 833 } else if (len == 5) { |
842 } else if (len == 5) { |
| 834 if (strcmp(mstr, "O-O-O") == 0) { |
843 if (strcmp(mstr, "O-O-O") == 0) { |
| 835 /* queen side castling "O-O-O" */ |
844 /* queen side castling "O-O-O" */ |
| 836 move->piece = KING; |
845 move->piece = mkpiece(KING, color); |
| 837 move->fromfile = fileidx('e'); |
846 move->fromfile = fileidx('e'); |
| 838 move->tofile = fileidx('c'); |
847 move->tofile = fileidx('c'); |
| 839 move->fromrow = move->torow = color == WHITE ? 0 : 7; |
848 move->fromrow = move->torow = color == WHITE ? 0 : 7; |
| 840 } else { |
849 } else { |
| 841 move->piece = getpiece(mstr[0]); |
850 move->piece = getpiece(mstr[0], color); |
| 842 if (mstr[2] == 'x') { |
851 if (mstr[2] == 'x') { |
| 843 move->capture = 1; |
852 move->capture = 1; |
| 844 if (move->piece) { |
853 if (move->piece) { |
| 845 /* capture (e.g. "Ndxf3" or "R1xh3") */ |
854 /* capture (e.g. "Ndxf3" or "R1xh3") */ |
| 846 if (isfile(mstr[1])) { |
855 if (isfile(mstr[1])) { |
| 866 } |
875 } |
| 867 } else if (len == 6) { |
876 } else if (len == 6) { |
| 868 /* long notation capture (e.g. "Nc5xf3") */ |
877 /* long notation capture (e.g. "Nc5xf3") */ |
| 869 if (mstr[3] == 'x') { |
878 if (mstr[3] == 'x') { |
| 870 move->capture = 1; |
879 move->capture = 1; |
| 871 move->piece = getpiece(mstr[0]); |
880 move->piece = getpiece(mstr[0], color); |
| 872 move->fromfile = fileidx(mstr[1]); |
881 move->fromfile = fileidx(mstr[1]); |
| 873 move->fromrow = rowidx(mstr[2]); |
882 move->fromrow = rowidx(mstr[2]); |
| 874 move->tofile = fileidx(mstr[4]); |
883 move->tofile = fileidx(mstr[4]); |
| 875 move->torow = rowidx(mstr[5]); |
884 move->torow = rowidx(mstr[5]); |
| 876 } |
885 } |
| 900 |
907 |
| 901 return VALID_MOVE_SYNTAX; |
908 return VALID_MOVE_SYNTAX; |
| 902 } |
909 } |
| 903 |
910 |
| 904 int eval_move(GameState *gamestate, const char *mstr, |
911 int eval_move(GameState *gamestate, const char *mstr, |
| 905 Move *move, uint8_t color) { |
912 Move *move, Color color) { |
| 906 int result = eval_move1(mstr, move, color); |
913 int result = eval_move1(mstr, move, color); |
| 907 if (result == VALID_MOVE_SYNTAX) { |
914 if (result == VALID_MOVE_SYNTAX) { |
| 908 if (move->fromfile == POS_UNSPECIFIED |
915 if (move->fromfile == POS_UNSPECIFIED |
| 909 || move->fromrow == POS_UNSPECIFIED) { |
916 || move->fromrow == POS_UNSPECIFIED) { |
| 910 return getlocation(gamestate, move); |
917 return getlocation(gamestate, move); |
| 911 } |
918 } |
| 912 } |
919 } |
| 913 return result; |
920 return result; |
| 914 } |
921 } |
| 915 |
922 |
| 916 int check_move(const char *mstr, uint8_t color) { |
923 int check_move(const char *mstr, Color color) { |
| 917 Move move; |
924 Move move; |
| 918 return eval_move1(mstr, &move, color); |
925 return eval_move1(mstr, &move, color); |
| 919 } |
926 } |
| 920 |
927 |
| 921 bool is_protected(GameState *gamestate, uint8_t row, uint8_t file, |
928 bool is_protected(GameState *gamestate, Row row, File file, Color color) { |
| 922 uint8_t color) { |
|
| 923 |
|
| 924 Move threats[16]; |
929 Move threats[16]; |
| 925 uint8_t threatcount; |
930 size_t threatcount; |
| 926 if (get_real_threats(gamestate, row, file, color, threats, &threatcount)) { |
931 if (get_real_threats(gamestate, row, file, color, threats, &threatcount)) { |
| 927 for (int i = 0 ; i < threatcount ; i++) { |
932 for (size_t i = 0 ; i < threatcount ; i++) { |
| 928 if (threats[i].piece != (color|KING)) { |
933 if (threats[i].piece != (color|KING)) { |
| 929 return true; |
934 return true; |
| 930 } |
935 } |
| 931 } |
936 } |
| 932 return false; |
937 return false; |
| 933 } else { |
938 } else { |
| 934 return false; |
939 return false; |
| 935 } |
940 } |
| 936 } |
941 } |
| 937 |
942 |
| 938 uint16_t remaining_movetime(GameState *gamestate, uint8_t color) { |
943 uint16_t remaining_movetime(GameState *gamestate, Color color) { |
| 939 unsigned move_number = gamestate->movecount; |
944 unsigned move_number = gamestate->movecount; |
| 940 if (color == BLACK) { |
945 if (color == BLACK) { |
| 941 move_number |= 1; |
946 move_number |= 1; |
| 942 } else { |
947 } else { |
| 943 move_number = (move_number + 1) & ~1; |
948 move_number = (move_number + 1) & ~1; |