Files
brchess/src/position.c

473 lines
13 KiB
C

/* position.c - position management.
*
* Copyright (C) 2021-2024 Bruno Raoult ("br")
* Licensed under the GNU General Public License v3.0 or later.
* Some rights reserved. See COPYING.
*
* You should have received a copy of the GNU General Public License along with this
* program. If not, see <https://www.gnu.org/licenses/gpl-3.0-standalone.html>.
*
* SPDX-License-Identifier: GPL-3.0-or-later <https://spdx.org/licenses/GPL-3.0-or-later.html>
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include "brlib.h"
#include "bitops.h"
#include "chessdefs.h"
#include "position.h"
#include "bitboard.h"
#include "hyperbola-quintessence.h"
#include "fen.h"
#include "piece.h"
#include "util.h"
#include "board.h"
#include "attack.h"
/**
* pos_new() - allocate a new position
*
* Position is not initialized.
* If BUG_ON compilation is defined, the program will fail.
*
* @Return: The new position or NULL.
*/
pos_t *pos_new(void)
{
return safe_malloc(sizeof(pos_t));
}
/**
* pos_dup() - duplicate a position.
* @pos: &position to duplicate.
*
* Return a copy, allocated with malloc(1), of @pos.
*
* @Return: The new position.
*
* TODO: merge with pos_new - NULL for init, non null for duplicate
*/
pos_t *pos_dup(const pos_t *pos)
{
pos_t *newpos = safe_malloc(sizeof(pos_t));
*newpos = *pos;
return newpos;
}
/**
* pos_del() - delete a position.
* @pos: &position.
*/
void pos_del(pos_t *pos)
{
safe_free(pos);
}
/**
* pos_clear() - clear a position.
* @pos: &position.
*
* @return: @pos.
*/
pos_t *pos_clear(pos_t *pos)
{
# ifdef DEBUG_POS
printf("size(pos_board=%lu elt=%lu\n", sizeof(pos->board), sizeof(int));
# endif
pos->node_count = 0;
pos->turn = WHITE;
/* move_do/undo position state */
pos->en_passant = SQUARE_NONE;
pos->castle = 0;
pos->clock_50 = 0;
pos->plycount = 0;
//pos->captured = NO_PIECE;
for (square_t sq = A1; sq <= H8; ++sq)
pos->board[sq] = EMPTY;
for (color_t color = WHITE; color <= BLACK; ++color) {
for (piece_type_t piece = 0; piece <= KING; ++piece)
pos->bb[color][piece] = 0;
//pos->controlled[color] = 0;
pos->king[color] = SQUARE_NONE;
}
pos->checkers = 0;
pos->pinners = 0;
pos->blockers = 0;
//pos->moves.nmoves = 0;
return pos;
}
/**
* pos_cmp() - compare two positions..
* @pos1, @pos2: The two &position.
*
* @return: true if equal, false otherwise.
*/
bool pos_cmp(__unused const pos_t *pos1, __unused const pos_t *pos2)
{
#define _cmpf(a) (pos1->a != pos2->a)
bool ret = false;
if (warn_on(_cmpf(node_count)))
goto end;
if (warn_on(_cmpf(turn)))
goto end;
/* move_do/undo position state */
if (warn_on(_cmpf(en_passant)))
goto end;
if (warn_on(_cmpf(castle)))
goto end;
if (warn_on(_cmpf(clock_50)))
goto end;
if (warn_on(_cmpf(plycount)))
goto end;
//if (warn_on(_cmpf(captured)))
// goto end;
for (square_t sq = A1; sq <= H8; ++sq)
if (warn_on(_cmpf(board[sq])))
goto end;
for (color_t color = WHITE; color <= BLACK; ++color) {
for (piece_type_t piece = 0; piece <= KING; ++piece)
if (warn_on(_cmpf(bb[color][piece])))
goto end;
//pos->controlled[color] = 0;
if (warn_on(_cmpf(king[color])))
goto end;
}
if (warn_on(_cmpf(checkers)))
goto end;
if (warn_on(_cmpf(pinners)))
goto end;
if (warn_on(_cmpf(blockers)))
goto end;
/*
* if (warn_on(_cmpf(moves.nmoves)))
* goto end;
* for (int i = 0; i < pos1->moves.nmoves; ++i)
* if (warn_on(_cmpf(moves.move[i])))
* goto end;
*/
ret = true;
end:
return ret;
#undef _cmpf
}
/**
* pos_checkers() - find all checkers on a king.
* @pos: &position
* @color: king color
*
* Get a bitboard of all checkers on @color king.
* Just a wrapper over @sq_attackers().
*
* @return: a bitboard of attackers.
*/
bitboard_t pos_checkers(const pos_t *pos, const color_t color)
{
bitboard_t occ = pos_occ(pos);
return sq_attackers(pos, occ, pos->king[color], OPPONENT(color));
}
/**
* pos_set_checkers_pinners_blockers() - calculate checkers, pinners and blockers.
* @pos: &position
*
* Set position checkers, pinners and blockers on player-to-play king.
* It should be faster than @pos_checkers + @pos_set_pinners_blockers, as
* some calculation will be done once.
*/
void pos_set_checkers_pinners_blockers(pos_t *pos)
{
int us = pos->turn, them = OPPONENT(us);
bitboard_t occ = pos_occ(pos);
bitboard_t attackers;
bitboard_t checkers = 0, blockers = 0, pinners = 0;
bitboard_t targets, tmpcheckers, maybeblockers, tmppinners;
square_t king = pos->king[us];
int pinner;
/* bishop type - we attack with a bishop from king position */
attackers = pos->bb[them][BISHOP] | pos->bb[them][QUEEN];
/* targets is all "target" pieces if K was a bishop */
targets = hyperbola_bishop_moves(occ, king) & occ;
/* checkers = only opponent B/Q */
tmpcheckers = targets & attackers;
checkers |= tmpcheckers;
/* maybe blockers = we remove checkers, to look "behind" */
maybeblockers = targets & ~tmpcheckers;
/* we find second targets, by removing first ones (excl. checkers) */
if (maybeblockers) {
targets = hyperbola_bishop_moves(occ ^ maybeblockers, king) ^ tmpcheckers;
/* pinners = only B/Q */
tmppinners = targets & attackers;
/* blockers = we find occupied squares between pinner and king */
while (tmppinners) {
pinner = bb_next(&tmppinners);
pinners |= mask(pinner);
blockers |= bb_between[pinner][king] & maybeblockers;
}
}
/* same for rook type */
attackers = pos->bb[them][ROOK] | pos->bb[them][QUEEN];
targets = hyperbola_rook_moves(occ, king) & occ;
tmpcheckers = targets & attackers;
checkers |= tmpcheckers;
maybeblockers = targets & ~tmpcheckers;
if (maybeblockers) {
targets = hyperbola_rook_moves(occ ^ maybeblockers, king) ^ tmpcheckers;
tmppinners = targets & attackers;
while (tmppinners) {
pinner = bb_next(&tmppinners);
pinners |= mask(pinner);
blockers |= bb_between[pinner][king] & maybeblockers;
}
}
/* pawns */
checkers |= bb_pawn_attacks[us][king] & pos->bb[them][PAWN];
/* knight */
checkers |= bb_knight[king] & pos->bb[them][KNIGHT];
pos->checkers = checkers;
pos->pinners = pinners;
pos->blockers = blockers;
}
/**
* pos_set_pinners_blockers() - set position pinners and blockers.
* @pos: &position
*
* set position pinners and blockers on player-to-play king.
*/
void pos_set_pinners_blockers(pos_t *pos)
{
color_t color = pos->turn;
square_t king = pos->king[color];
bitboard_t tmp, occ = pos_occ(pos), blockers = 0;
int pinner;
pos->pinners = sq_pinners(pos, king, OPPONENT(pos->turn));
bit_for_each64(pinner, tmp, pos->pinners) {
//bitboard_t blocker =
// warn_on(popcount64(blocker) != 1);
blockers |= bb_between_excl[pinner][king] & occ;
}
pos->blockers = blockers;
return;
}
/**
* pos_king_pinners() - get the "pinners" on a king "pinners".
* @pos: &position
* @color: king color.
*
* get position "pinners" on @color king. Here, pinner means a piece separated from king
* by one piece (any color) only.
* This is just a wrapper over @sq_pinners().
*
* @return: pinners bitboard.
*/
bitboard_t pos_king_pinners(const pos_t *pos, const color_t color)
{
return sq_pinners(pos, pos->king[color], OPPONENT(pos->turn));
}
/**
* pos_king_blockers() - get the pin blockers on a king.
* @pos: &position
* @color: king color.
* @pinners: pinners bitboard.
*
* get @pinners blockers pieces on @color king.
*
* @return: blockers bitboard.
*/
bitboard_t pos_king_blockers(const pos_t *pos, const color_t color, const bitboard_t pinners)
{
bitboard_t tmp, blockers = 0, occ = pos_occ(pos);
square_t pinner, king = pos->king[color];
bit_for_each64(pinner, tmp, pinners) {
//warn_on(popcount64(blocker) != 1);
//if (popcount64(blocker) != 1) {
// printf("n blockers = %d\n", popcount64(blocker));
// bb_print("blockers", blocker);
//}
blockers |= bb_between_excl[pinner][king] & occ;
}
return blockers;
}
/**
* pos_ok() - extensive position consistency check.
* @pos: &position
* @strict: if true, call bug_on() on any error.
*
* Perform some validity check on position @pos:
* - pawns on 1st or 8th rank
* - number of pawns > 8 (per color)
* - total number of pieces > 16 or zero (per color)
* - number of kings != 1 (per color)
* - discrepancy between board and king (per color)
* - discrepancy between piece bitboards and ALL_PIECES bitboards (per color)
* - discrepancy between bitboards and board (per color)
* - side-to-move already checking opponent king
* - side-to-move in check more than twice
* - kings distance is 1
*
* In case of errors, and @strict is true, @bug_on() is called, and program will
* be terminated.
* This function should be called with @strict == false during initialization phase
* (eg after fen parsing), and with @strict == true otherwise (as we have some data
* corruption).
*
* TODO: add more checks:
* - kings attacking each other
*
* @return: (if @strict is false) return true if check is ok, false otherwise.
*/
bool pos_ok(__unused const pos_t *pos, __unused const bool strict)
{
int n, count = 0, bbcount = 0, error = 0;
__unused bitboard_t tmp;
/* pawns on 1st ot 8th rank */
tmp = (pos->bb[WHITE][PAWN] | pos->bb[BLACK][PAWN]) & (RANK_1bb | RANK_8bb);
error += warn_on(tmp);
for (color_t color = WHITE; color <= BLACK; ++color) {
/* pawn count */
n = popcount64(pos->bb[color][PAWN]);
error += warn_on(n > 8);
/* king count */
n = popcount64(pos->bb[color][KING]);
error += warn_on(n != 1);
/* king mismatch with board */
error += warn_on(PIECE(pos->board[pos->king[color]]) != KING);
/* pieces count */
n = popcount64(pos->bb[color][ALL_PIECES]);
error += warn_on(n == 0 || n > 16);
bbcount += n;
}
for (square_t sq = 0; sq < 64; ++sq) {
piece_t piece = pos->board[sq];
__unused bitboard_t match;
if (piece == EMPTY)
continue;
color_t c = COLOR(piece);
piece_type_t p = PIECE(piece);
match = pos->bb[c][p] & mask(sq);
error += warn_on(!match);
count++;
}
/* occupied occupation is different from bitboards */
error += warn_on(count != bbcount);
/* is opponent already in check ? */
error += warn_on(pos_checkers(pos, OPPONENT(pos->turn)));
/* is color to play in check more than twice ? */
error += warn_on(popcount64(pos_checkers(pos, OPPONENT(pos->turn))) > 2);
/* kings distance is less than 2 */
error += warn_on(sq_dist(pos->king[WHITE], pos->king[BLACK]) < 2);
bug_on(strict && error);
return error? false: true;
}
/**
* pos_print() - Print position and fen on stdout.
* @pos: &position
*/
void pos_print(const pos_t *pos)
{
char str[128];
board_print(pos->board);
printf("fen %s\n", pos2fen(pos, str));
printf("checkers: %s\n", pos_checkers2str(pos, str, sizeof(str)));
printf("pinners : %s\n", pos_pinners2str(pos, str, sizeof(str)));
printf("blockers: %s\n", pos_blockers2str(pos, str, sizeof(str)));
}
/**
* pos_print_mask() - Print position and fen on stdout, with highlighted squares.
* @pos: &position
* @mask: mask of highlighted squares.
*/
void pos_print_mask(const pos_t *pos, const bitboard_t mask)
{
char fen[92];
board_print_mask(pos->board, mask);
printf("fen %s\n", pos2fen(pos, fen));
}
/**
* pos_print_raw - print simple position board (octal/FEN symbol values)
* @bb: the bitboard
* @type: int, 0 for octal, 1 for fen symbol
*/
void pos_print_raw(const pos_t *pos, const int type)
{
board_print_raw(pos->board, type);
return;
}
/**
* pos_print_pieces() - Print position pieces
* @pos: &position
*/
void pos_print_pieces(const pos_t *pos)
{
int sq, count, cur;
char *pname;
u64 tmp;
bitboard_t p;
for (int color = WHITE; color <= BLACK; ++color) {
for (int piece = KING; piece >= PAWN; --piece) {
p = pos->bb[color][piece];
count = popcount64(p);
cur = 0;
pname = piece_to_char(p);
printf("%s(%d)%s", pname, count, count? ":": "");
if (count) {
bit_for_each64(sq, tmp, p) {
// char cf = sq_file(bit), cr = sq_rank(bit);
printf("%s%s", cur? ",": "", sq_to_string(sq));
cur++;
}
}
printf(" ");
}
printf("\n");
}
}