Files
brchess/src/position.c

321 lines
8.7 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 "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.
*
* New position is the same as source one (with duplicated pieces list),
* except:
* - moves list is empty
* - bestmove is NULL
* - nodecount is set to zero
* - eval is set to EVAL_INVALID
* - moves_generated ans moves_counted are unset
* - check is set to zero
*
* @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));
//board = new->board;
*newpos = *pos;
//new->bestmove = NULL;
newpos->node_count = 0;
//new->eval = EVAL_INVALID;
//new->moves_generated = false;
//new->moves_counted = false;
//new->check[WHITE] = new->check[BLACK] = 0;
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.
*/
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;
pos->clock_50 = 0;
pos->plycount = 0;
pos->en_passant = SQUARE_NONE;
pos->castle = 0;
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;
}
for (square_t sq = A1; sq <= H8; ++sq)
pos->board[sq] = EMPTY;
pos->moves.curmove = 0;
pos->moves.nmoves = 0;
pos->checkers = 0;
pos->pinners = 0;
return pos;
}
/**
* 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_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) {
bitboard_t blocker = bb_between_excl[pinner][king] & occ;
warn_on(popcount64(blocker) != 1);
if (popcount64(blocker) != 1) {
printf("n blockers = %d\n", popcount64(blocker));
bb_print("blockers", blocker);
}
blockers |= blocker;
}
return blockers;
}
/**
* pos_check() - 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: Number of detected error (only if @strict is false).
*/
int pos_check(const pos_t *pos, const bool strict)
{
int n, count = 0, bbcount = 0, error = 0;
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];
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;
}
/**
* 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_board_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 bit, 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_cap(piece);
printf("%s(0)%s", pname, count? ":": "");
if (count) {
bit_for_each64(bit, tmp, p) {
char cf = sq_file(bit), cr = sq_rank(bit);
printf("%s%c%c", cur? ",": "", FILE2C(cf), RANK2C(cr));
cur++;
}
}
printf(" ");
}
printf("\n");
}
}