mirror of
https://github.com/sockspls/badfish
synced 2025-04-30 08:43:09 +00:00

Array index[] and pieceList[] are not guaranteed to be invariant to a do_move() + undo_move() sequence when a capture move is involved. The reason is that the captured piece is removed form the list and substituted with the last one in do_move() while in undo_move() is added again but at the end of the list. Because index[] and pieceList[] are used in move generation to scan the pieces it means that moves will be generated in a different order before and after a do_move() + undo_move() sequence as, for instance, the one in Position::has_mate_threat() After latest patches, move generation could now be invoked also by MovePicker c'tor and this explains why order of picked moves is different if MovePicker object is istantiated before or after a Position::has_mate_threat() call. Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2033 lines
60 KiB
C++
2033 lines
60 KiB
C++
/*
|
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
|
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
|
Copyright (C) 2008-2009 Marco Costalba
|
|
|
|
Stockfish is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Stockfish is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
////
|
|
//// Includes
|
|
////
|
|
|
|
#include <cassert>
|
|
#include <cstring>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
|
|
#include "bitcount.h"
|
|
#include "mersenne.h"
|
|
#include "movegen.h"
|
|
#include "movepick.h"
|
|
#include "position.h"
|
|
#include "psqtab.h"
|
|
#include "san.h"
|
|
#include "tt.h"
|
|
#include "ucioption.h"
|
|
|
|
using std::string;
|
|
|
|
|
|
////
|
|
//// Variables
|
|
////
|
|
|
|
int Position::castleRightsMask[64];
|
|
|
|
Key Position::zobrist[2][8][64];
|
|
Key Position::zobEp[64];
|
|
Key Position::zobCastle[16];
|
|
Key Position::zobMaterial[2][8][16];
|
|
Key Position::zobSideToMove;
|
|
|
|
Value Position::MgPieceSquareTable[16][64];
|
|
Value Position::EgPieceSquareTable[16][64];
|
|
|
|
static bool RequestPending = false;
|
|
|
|
////
|
|
//// Functions
|
|
////
|
|
|
|
/// Constructors
|
|
|
|
Position::Position(const Position& pos) {
|
|
copy(pos);
|
|
}
|
|
|
|
Position::Position(const string& fen) {
|
|
from_fen(fen);
|
|
}
|
|
|
|
|
|
/// Position::from_fen() initializes the position object with the given FEN
|
|
/// string. This function is not very robust - make sure that input FENs are
|
|
/// correct (this is assumed to be the responsibility of the GUI).
|
|
|
|
void Position::from_fen(const string& fen) {
|
|
|
|
static const string pieceLetters = "KQRBNPkqrbnp";
|
|
static const Piece pieces[] = { WK, WQ, WR, WB, WN, WP, BK, BQ, BR, BB, BN, BP };
|
|
|
|
clear();
|
|
|
|
// Board
|
|
Rank rank = RANK_8;
|
|
File file = FILE_A;
|
|
size_t i = 0;
|
|
for ( ; fen[i] != ' '; i++)
|
|
{
|
|
if (isdigit(fen[i]))
|
|
{
|
|
// Skip the given number of files
|
|
file += (fen[i] - '1' + 1);
|
|
continue;
|
|
}
|
|
else if (fen[i] == '/')
|
|
{
|
|
file = FILE_A;
|
|
rank--;
|
|
continue;
|
|
}
|
|
size_t idx = pieceLetters.find(fen[i]);
|
|
if (idx == string::npos)
|
|
{
|
|
std::cout << "Error in FEN at character " << i << std::endl;
|
|
return;
|
|
}
|
|
Square square = make_square(file, rank);
|
|
put_piece(pieces[idx], square);
|
|
file++;
|
|
}
|
|
|
|
// Side to move
|
|
i++;
|
|
if (fen[i] != 'w' && fen[i] != 'b')
|
|
{
|
|
std::cout << "Error in FEN at character " << i << std::endl;
|
|
return;
|
|
}
|
|
sideToMove = (fen[i] == 'w' ? WHITE : BLACK);
|
|
|
|
// Castling rights
|
|
i++;
|
|
if (fen[i] != ' ')
|
|
{
|
|
std::cout << "Error in FEN at character " << i << std::endl;
|
|
return;
|
|
}
|
|
|
|
i++;
|
|
while(strchr("KQkqabcdefghABCDEFGH-", fen[i])) {
|
|
if (fen[i] == '-')
|
|
{
|
|
i++;
|
|
break;
|
|
}
|
|
else if(fen[i] == 'K') allow_oo(WHITE);
|
|
else if(fen[i] == 'Q') allow_ooo(WHITE);
|
|
else if(fen[i] == 'k') allow_oo(BLACK);
|
|
else if(fen[i] == 'q') allow_ooo(BLACK);
|
|
else if(fen[i] >= 'A' && fen[i] <= 'H') {
|
|
File rookFile, kingFile = FILE_NONE;
|
|
for(Square square = SQ_B1; square <= SQ_G1; square++)
|
|
if(piece_on(square) == WK)
|
|
kingFile = square_file(square);
|
|
if(kingFile == FILE_NONE) {
|
|
std::cout << "Error in FEN at character " << i << std::endl;
|
|
return;
|
|
}
|
|
initialKFile = kingFile;
|
|
rookFile = File(fen[i] - 'A') + FILE_A;
|
|
if(rookFile < initialKFile) {
|
|
allow_ooo(WHITE);
|
|
initialQRFile = rookFile;
|
|
}
|
|
else {
|
|
allow_oo(WHITE);
|
|
initialKRFile = rookFile;
|
|
}
|
|
}
|
|
else if(fen[i] >= 'a' && fen[i] <= 'h') {
|
|
File rookFile, kingFile = FILE_NONE;
|
|
for(Square square = SQ_B8; square <= SQ_G8; square++)
|
|
if(piece_on(square) == BK)
|
|
kingFile = square_file(square);
|
|
if(kingFile == FILE_NONE) {
|
|
std::cout << "Error in FEN at character " << i << std::endl;
|
|
return;
|
|
}
|
|
initialKFile = kingFile;
|
|
rookFile = File(fen[i] - 'a') + FILE_A;
|
|
if(rookFile < initialKFile) {
|
|
allow_ooo(BLACK);
|
|
initialQRFile = rookFile;
|
|
}
|
|
else {
|
|
allow_oo(BLACK);
|
|
initialKRFile = rookFile;
|
|
}
|
|
}
|
|
else {
|
|
std::cout << "Error in FEN at character " << i << std::endl;
|
|
return;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
// Skip blanks
|
|
while (fen[i] == ' ')
|
|
i++;
|
|
|
|
// En passant square
|
|
if ( i <= fen.length() - 2
|
|
&& (fen[i] >= 'a' && fen[i] <= 'h')
|
|
&& (fen[i+1] == '3' || fen[i+1] == '6'))
|
|
st->epSquare = square_from_string(fen.substr(i, 2));
|
|
|
|
// Various initialisation
|
|
for (Square sq = SQ_A1; sq <= SQ_H8; sq++)
|
|
castleRightsMask[sq] = ALL_CASTLES;
|
|
|
|
castleRightsMask[make_square(initialKFile, RANK_1)] ^= (WHITE_OO|WHITE_OOO);
|
|
castleRightsMask[make_square(initialKFile, RANK_8)] ^= (BLACK_OO|BLACK_OOO);
|
|
castleRightsMask[make_square(initialKRFile, RANK_1)] ^= WHITE_OO;
|
|
castleRightsMask[make_square(initialKRFile, RANK_8)] ^= BLACK_OO;
|
|
castleRightsMask[make_square(initialQRFile, RANK_1)] ^= WHITE_OOO;
|
|
castleRightsMask[make_square(initialQRFile, RANK_8)] ^= BLACK_OOO;
|
|
|
|
find_checkers();
|
|
|
|
st->key = compute_key();
|
|
st->pawnKey = compute_pawn_key();
|
|
st->materialKey = compute_material_key();
|
|
st->mgValue = compute_value<MidGame>();
|
|
st->egValue = compute_value<EndGame>();
|
|
st->npMaterial[WHITE] = compute_non_pawn_material(WHITE);
|
|
st->npMaterial[BLACK] = compute_non_pawn_material(BLACK);
|
|
}
|
|
|
|
|
|
/// Position::to_fen() converts the position object to a FEN string. This is
|
|
/// probably only useful for debugging.
|
|
|
|
const string Position::to_fen() const {
|
|
|
|
static const string pieceLetters = " PNBRQK pnbrqk";
|
|
string fen;
|
|
int skip;
|
|
|
|
for (Rank rank = RANK_8; rank >= RANK_1; rank--)
|
|
{
|
|
skip = 0;
|
|
for (File file = FILE_A; file <= FILE_H; file++)
|
|
{
|
|
Square sq = make_square(file, rank);
|
|
if (!square_is_occupied(sq))
|
|
{ skip++;
|
|
continue;
|
|
}
|
|
if (skip > 0)
|
|
{
|
|
fen += (char)skip + '0';
|
|
skip = 0;
|
|
}
|
|
fen += pieceLetters[piece_on(sq)];
|
|
}
|
|
if (skip > 0)
|
|
fen += (char)skip + '0';
|
|
|
|
fen += (rank > RANK_1 ? '/' : ' ');
|
|
}
|
|
fen += (sideToMove == WHITE ? "w " : "b ");
|
|
if (st->castleRights != NO_CASTLES)
|
|
{
|
|
if (can_castle_kingside(WHITE)) fen += 'K';
|
|
if (can_castle_queenside(WHITE)) fen += 'Q';
|
|
if (can_castle_kingside(BLACK)) fen += 'k';
|
|
if (can_castle_queenside(BLACK)) fen += 'q';
|
|
} else
|
|
fen += '-';
|
|
|
|
fen += ' ';
|
|
if (ep_square() != SQ_NONE)
|
|
fen += square_to_string(ep_square());
|
|
else
|
|
fen += '-';
|
|
|
|
return fen;
|
|
}
|
|
|
|
|
|
/// Position::print() prints an ASCII representation of the position to
|
|
/// the standard output. If a move is given then also the san is print.
|
|
|
|
void Position::print(Move m) const {
|
|
|
|
static const string pieceLetters = " PNBRQK PNBRQK .";
|
|
|
|
// Check for reentrancy, as example when called from inside
|
|
// MovePicker that is used also here in move_to_san()
|
|
if (RequestPending)
|
|
return;
|
|
|
|
RequestPending = true;
|
|
|
|
std::cout << std::endl;
|
|
if (m != MOVE_NONE)
|
|
{
|
|
string col = (color_of_piece_on(move_from(m)) == BLACK ? ".." : "");
|
|
std::cout << "Move is: " << col << move_to_san(*this, m) << std::endl;
|
|
}
|
|
for (Rank rank = RANK_8; rank >= RANK_1; rank--)
|
|
{
|
|
std::cout << "+---+---+---+---+---+---+---+---+" << std::endl;
|
|
for (File file = FILE_A; file <= FILE_H; file++)
|
|
{
|
|
Square sq = make_square(file, rank);
|
|
Piece piece = piece_on(sq);
|
|
if (piece == EMPTY && square_color(sq) == WHITE)
|
|
piece = NO_PIECE;
|
|
|
|
char col = (color_of_piece_on(sq) == BLACK ? '=' : ' ');
|
|
std::cout << '|' << col << pieceLetters[piece] << col;
|
|
}
|
|
std::cout << '|' << std::endl;
|
|
}
|
|
std::cout << "+---+---+---+---+---+---+---+---+" << std::endl
|
|
<< "Fen is: " << to_fen() << std::endl
|
|
<< "Key is: " << st->key << std::endl;
|
|
|
|
RequestPending = false;
|
|
}
|
|
|
|
|
|
/// Position::copy() creates a copy of the input position.
|
|
|
|
void Position::copy(const Position& pos) {
|
|
|
|
memcpy(this, &pos, sizeof(Position));
|
|
saveState(); // detach and copy state info
|
|
}
|
|
|
|
|
|
/// Position:hidden_checkers<>() returns a bitboard of all pinned (against the
|
|
/// king) pieces for the given color and for the given pinner type. Or, when
|
|
/// template parameter FindPinned is false, the pieces of the given color
|
|
/// candidate for a discovery check against the enemy king.
|
|
/// Note that checkersBB bitboard must be already updated.
|
|
|
|
template<bool FindPinned>
|
|
Bitboard Position::hidden_checkers(Color c) const {
|
|
|
|
Bitboard pinners, result = EmptyBoardBB;
|
|
|
|
// Pinned pieces protect our king, dicovery checks attack
|
|
// the enemy king.
|
|
Square ksq = king_square(FindPinned ? c : opposite_color(c));
|
|
|
|
// Pinners are sliders, not checkers, that give check when
|
|
// candidate pinned is removed.
|
|
pinners = (rooks_and_queens(FindPinned ? opposite_color(c) : c) & RookPseudoAttacks[ksq])
|
|
| (bishops_and_queens(FindPinned ? opposite_color(c) : c) & BishopPseudoAttacks[ksq]);
|
|
|
|
if (FindPinned && pinners)
|
|
pinners &= ~st->checkersBB;
|
|
|
|
while (pinners)
|
|
{
|
|
Square s = pop_1st_bit(&pinners);
|
|
Bitboard b = squares_between(s, ksq) & occupied_squares();
|
|
|
|
assert(b);
|
|
|
|
if ( !(b & (b - 1)) // Only one bit set?
|
|
&& (b & pieces_of_color(c))) // Is an our piece?
|
|
result |= b;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/// Position:pinned_pieces() returns a bitboard of all pinned (against the
|
|
/// king) pieces for the given color.
|
|
|
|
Bitboard Position::pinned_pieces(Color c) const {
|
|
|
|
return hidden_checkers<true>(c);
|
|
}
|
|
|
|
|
|
/// Position:discovered_check_candidates() returns a bitboard containing all
|
|
/// pieces for the given side which are candidates for giving a discovered
|
|
/// check.
|
|
|
|
Bitboard Position::discovered_check_candidates(Color c) const {
|
|
|
|
return hidden_checkers<false>(c);
|
|
}
|
|
|
|
/// Position::attacks_to() computes a bitboard containing all pieces which
|
|
/// attacks a given square.
|
|
|
|
Bitboard Position::attacks_to(Square s) const {
|
|
|
|
return (pawn_attacks(BLACK, s) & pawns(WHITE))
|
|
| (pawn_attacks(WHITE, s) & pawns(BLACK))
|
|
| (piece_attacks<KNIGHT>(s) & pieces_of_type(KNIGHT))
|
|
| (piece_attacks<ROOK>(s) & rooks_and_queens())
|
|
| (piece_attacks<BISHOP>(s) & bishops_and_queens())
|
|
| (piece_attacks<KING>(s) & pieces_of_type(KING));
|
|
}
|
|
|
|
/// Position::piece_attacks_square() tests whether the piece on square f
|
|
/// attacks square t.
|
|
|
|
bool Position::piece_attacks_square(Piece p, Square f, Square t) const {
|
|
|
|
assert(square_is_ok(f));
|
|
assert(square_is_ok(t));
|
|
|
|
switch (p)
|
|
{
|
|
case WP: return pawn_attacks_square(WHITE, f, t);
|
|
case BP: return pawn_attacks_square(BLACK, f, t);
|
|
case WN: case BN: return piece_attacks_square<KNIGHT>(f, t);
|
|
case WB: case BB: return piece_attacks_square<BISHOP>(f, t);
|
|
case WR: case BR: return piece_attacks_square<ROOK>(f, t);
|
|
case WQ: case BQ: return piece_attacks_square<QUEEN>(f, t);
|
|
case WK: case BK: return piece_attacks_square<KING>(f, t);
|
|
default: break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/// Position::move_attacks_square() tests whether a move from the current
|
|
/// position attacks a given square.
|
|
|
|
bool Position::move_attacks_square(Move m, Square s) const {
|
|
|
|
assert(move_is_ok(m));
|
|
assert(square_is_ok(s));
|
|
|
|
Square f = move_from(m), t = move_to(m);
|
|
|
|
assert(square_is_occupied(f));
|
|
|
|
if (piece_attacks_square(piece_on(f), t, s))
|
|
return true;
|
|
|
|
// Move the piece and scan for X-ray attacks behind it
|
|
Bitboard occ = occupied_squares();
|
|
Color us = color_of_piece_on(f);
|
|
clear_bit(&occ, f);
|
|
set_bit(&occ, t);
|
|
Bitboard xray = ( (rook_attacks_bb(s, occ) & rooks_and_queens())
|
|
|(bishop_attacks_bb(s, occ) & bishops_and_queens())) & pieces_of_color(us);
|
|
|
|
// If we have attacks we need to verify that are caused by our move
|
|
// and are not already existent ones.
|
|
return xray && (xray ^ (xray & piece_attacks<QUEEN>(s)));
|
|
}
|
|
|
|
|
|
/// Position::find_checkers() computes the checkersBB bitboard, which
|
|
/// contains a nonzero bit for each checking piece (0, 1 or 2). It
|
|
/// currently works by calling Position::attacks_to, which is probably
|
|
/// inefficient. Consider rewriting this function to use the last move
|
|
/// played, like in non-bitboard versions of Glaurung.
|
|
|
|
void Position::find_checkers() {
|
|
|
|
Color us = side_to_move();
|
|
st->checkersBB = attacks_to(king_square(us), opposite_color(us));
|
|
}
|
|
|
|
|
|
/// Position::pl_move_is_legal() tests whether a pseudo-legal move is legal
|
|
|
|
bool Position::pl_move_is_legal(Move m) const {
|
|
|
|
// If we're in check, all pseudo-legal moves are legal, because our
|
|
// check evasion generator only generates true legal moves.
|
|
return is_check() || pl_move_is_legal(m, pinned_pieces(side_to_move()));
|
|
}
|
|
|
|
bool Position::pl_move_is_legal(Move m, Bitboard pinned) const {
|
|
|
|
assert(is_ok());
|
|
assert(move_is_ok(m));
|
|
assert(pinned == pinned_pieces(side_to_move()));
|
|
assert(!is_check());
|
|
|
|
// Castling moves are checked for legality during move generation.
|
|
if (move_is_castle(m))
|
|
return true;
|
|
|
|
Color us = side_to_move();
|
|
Square from = move_from(m);
|
|
Square ksq = king_square(us);
|
|
|
|
assert(color_of_piece_on(from) == us);
|
|
assert(piece_on(ksq) == piece_of_color_and_type(us, KING));
|
|
|
|
// En passant captures are a tricky special case. Because they are
|
|
// rather uncommon, we do it simply by testing whether the king is attacked
|
|
// after the move is made
|
|
if (move_is_ep(m))
|
|
{
|
|
Color them = opposite_color(us);
|
|
Square to = move_to(m);
|
|
Square capsq = make_square(square_file(to), square_rank(from));
|
|
Bitboard b = occupied_squares();
|
|
|
|
assert(to == ep_square());
|
|
assert(piece_on(from) == piece_of_color_and_type(us, PAWN));
|
|
assert(piece_on(capsq) == piece_of_color_and_type(them, PAWN));
|
|
assert(piece_on(to) == EMPTY);
|
|
|
|
clear_bit(&b, from);
|
|
clear_bit(&b, capsq);
|
|
set_bit(&b, to);
|
|
|
|
return !(rook_attacks_bb(ksq, b) & rooks_and_queens(them))
|
|
&& !(bishop_attacks_bb(ksq, b) & bishops_and_queens(them));
|
|
}
|
|
|
|
// If the moving piece is a king, check whether the destination
|
|
// square is attacked by the opponent.
|
|
if (from == ksq)
|
|
return !(square_is_attacked(move_to(m), opposite_color(us)));
|
|
|
|
// A non-king move is legal if and only if it is not pinned or it
|
|
// is moving along the ray towards or away from the king.
|
|
return ( !pinned
|
|
|| !bit_is_set(pinned, from)
|
|
|| (direction_between_squares(from, ksq) == direction_between_squares(move_to(m), ksq)));
|
|
}
|
|
|
|
|
|
/// Position::move_is_check() tests whether a pseudo-legal move is a check
|
|
|
|
bool Position::move_is_check(Move m) const {
|
|
|
|
Bitboard dc = discovered_check_candidates(side_to_move());
|
|
return move_is_check(m, dc);
|
|
}
|
|
|
|
bool Position::move_is_check(Move m, Bitboard dcCandidates) const {
|
|
|
|
assert(is_ok());
|
|
assert(move_is_ok(m));
|
|
assert(dcCandidates == discovered_check_candidates(side_to_move()));
|
|
|
|
Color us = side_to_move();
|
|
Color them = opposite_color(us);
|
|
Square from = move_from(m);
|
|
Square to = move_to(m);
|
|
Square ksq = king_square(them);
|
|
|
|
assert(color_of_piece_on(from) == us);
|
|
assert(piece_on(ksq) == piece_of_color_and_type(them, KING));
|
|
|
|
// Proceed according to the type of the moving piece
|
|
switch (type_of_piece_on(from))
|
|
{
|
|
case PAWN:
|
|
|
|
if (bit_is_set(pawn_attacks(them, ksq), to)) // Normal check?
|
|
return true;
|
|
|
|
if ( dcCandidates // Discovered check?
|
|
&& bit_is_set(dcCandidates, from)
|
|
&& (direction_between_squares(from, ksq) != direction_between_squares(to, ksq)))
|
|
return true;
|
|
|
|
if (move_is_promotion(m)) // Promotion with check?
|
|
{
|
|
Bitboard b = occupied_squares();
|
|
clear_bit(&b, from);
|
|
|
|
switch (move_promotion_piece(m))
|
|
{
|
|
case KNIGHT:
|
|
return bit_is_set(piece_attacks<KNIGHT>(to), ksq);
|
|
case BISHOP:
|
|
return bit_is_set(bishop_attacks_bb(to, b), ksq);
|
|
case ROOK:
|
|
return bit_is_set(rook_attacks_bb(to, b), ksq);
|
|
case QUEEN:
|
|
return bit_is_set(queen_attacks_bb(to, b), ksq);
|
|
default:
|
|
assert(false);
|
|
}
|
|
}
|
|
// En passant capture with check? We have already handled the case
|
|
// of direct checks and ordinary discovered check, the only case we
|
|
// need to handle is the unusual case of a discovered check through the
|
|
// captured pawn.
|
|
else if (move_is_ep(m))
|
|
{
|
|
Square capsq = make_square(square_file(to), square_rank(from));
|
|
Bitboard b = occupied_squares();
|
|
clear_bit(&b, from);
|
|
clear_bit(&b, capsq);
|
|
set_bit(&b, to);
|
|
return (rook_attacks_bb(ksq, b) & rooks_and_queens(us))
|
|
||(bishop_attacks_bb(ksq, b) & bishops_and_queens(us));
|
|
}
|
|
return false;
|
|
|
|
// Test discovered check and normal check according to piece type
|
|
case KNIGHT:
|
|
return (dcCandidates && bit_is_set(dcCandidates, from))
|
|
|| bit_is_set(piece_attacks<KNIGHT>(ksq), to);
|
|
|
|
case BISHOP:
|
|
return (dcCandidates && bit_is_set(dcCandidates, from))
|
|
|| (direction_is_diagonal(ksq, to) && bit_is_set(piece_attacks<BISHOP>(ksq), to));
|
|
|
|
case ROOK:
|
|
return (dcCandidates && bit_is_set(dcCandidates, from))
|
|
|| (direction_is_straight(ksq, to) && bit_is_set(piece_attacks<ROOK>(ksq), to));
|
|
|
|
case QUEEN:
|
|
// Discovered checks are impossible!
|
|
assert(!bit_is_set(dcCandidates, from));
|
|
return ( (direction_is_straight(ksq, to) && bit_is_set(piece_attacks<ROOK>(ksq), to))
|
|
|| (direction_is_diagonal(ksq, to) && bit_is_set(piece_attacks<BISHOP>(ksq), to)));
|
|
|
|
case KING:
|
|
// Discovered check?
|
|
if ( bit_is_set(dcCandidates, from)
|
|
&& (direction_between_squares(from, ksq) != direction_between_squares(to, ksq)))
|
|
return true;
|
|
|
|
// Castling with check?
|
|
if (move_is_castle(m))
|
|
{
|
|
Square kfrom, kto, rfrom, rto;
|
|
Bitboard b = occupied_squares();
|
|
kfrom = from;
|
|
rfrom = to;
|
|
|
|
if (rfrom > kfrom)
|
|
{
|
|
kto = relative_square(us, SQ_G1);
|
|
rto = relative_square(us, SQ_F1);
|
|
} else {
|
|
kto = relative_square(us, SQ_C1);
|
|
rto = relative_square(us, SQ_D1);
|
|
}
|
|
clear_bit(&b, kfrom);
|
|
clear_bit(&b, rfrom);
|
|
set_bit(&b, rto);
|
|
set_bit(&b, kto);
|
|
return bit_is_set(rook_attacks_bb(rto, b), ksq);
|
|
}
|
|
return false;
|
|
|
|
default: // NO_PIECE_TYPE
|
|
break;
|
|
}
|
|
assert(false);
|
|
return false;
|
|
}
|
|
|
|
|
|
/// Position::update_checkers() udpates chekers info given the move. It is called
|
|
/// in do_move() and is faster then find_checkers().
|
|
|
|
template<PieceType Piece>
|
|
inline void Position::update_checkers(Bitboard* pCheckersBB, Square ksq, Square from,
|
|
Square to, Bitboard dcCandidates) {
|
|
|
|
const bool Bishop = (Piece == QUEEN || Piece == BISHOP);
|
|
const bool Rook = (Piece == QUEEN || Piece == ROOK);
|
|
const bool Slider = Bishop || Rook;
|
|
|
|
// Direct checks
|
|
if ( ( (Bishop && bit_is_set(BishopPseudoAttacks[ksq], to))
|
|
|| (Rook && bit_is_set(RookPseudoAttacks[ksq], to)))
|
|
&& bit_is_set(piece_attacks<Piece>(ksq), to)) // slow, try to early skip
|
|
set_bit(pCheckersBB, to);
|
|
|
|
else if ( Piece != KING
|
|
&& !Slider
|
|
&& bit_is_set(piece_attacks<Piece>(ksq), to))
|
|
set_bit(pCheckersBB, to);
|
|
|
|
// Discovery checks
|
|
if (Piece != QUEEN && bit_is_set(dcCandidates, from))
|
|
{
|
|
if (Piece != ROOK)
|
|
(*pCheckersBB) |= (piece_attacks<ROOK>(ksq) & rooks_and_queens(side_to_move()));
|
|
|
|
if (Piece != BISHOP)
|
|
(*pCheckersBB) |= (piece_attacks<BISHOP>(ksq) & bishops_and_queens(side_to_move()));
|
|
}
|
|
}
|
|
|
|
|
|
/// Position::do_move() makes a move, and saves all information necessary
|
|
/// to a StateInfo object. The move is assumed to be legal.
|
|
/// Pseudo-legal moves should be filtered out before this function is called.
|
|
|
|
void Position::do_move(Move m, StateInfo& newSt) {
|
|
|
|
do_move(m, newSt, discovered_check_candidates(side_to_move()));
|
|
}
|
|
|
|
void Position::do_move(Move m, StateInfo& newSt, Bitboard dcCandidates) {
|
|
|
|
assert(is_ok());
|
|
assert(move_is_ok(m));
|
|
|
|
Bitboard key = st->key;
|
|
|
|
// Copy some fields of old state to our new StateInfo object except the
|
|
// ones which are recalculated from scratch anyway, then switch our state
|
|
// pointer to point to the new, ready to be updated, state.
|
|
struct ReducedStateInfo {
|
|
Key key, pawnKey, materialKey;
|
|
int castleRights, rule50;
|
|
Square epSquare;
|
|
Value mgValue, egValue;
|
|
Value npMaterial[2];
|
|
};
|
|
|
|
memcpy(&newSt, st, sizeof(ReducedStateInfo));
|
|
newSt.previous = st;
|
|
st = &newSt;
|
|
|
|
// Save the current key to the history[] array, in order to be able to
|
|
// detect repetition draws.
|
|
history[gamePly] = key;
|
|
gamePly++;
|
|
|
|
// Update side to move
|
|
key ^= zobSideToMove;
|
|
|
|
// Increment the 50 moves rule draw counter. Resetting it to zero in the
|
|
// case of non-reversible moves is taken care of later.
|
|
st->rule50++;
|
|
|
|
if (move_is_castle(m))
|
|
{
|
|
st->key = key;
|
|
do_castle_move(m);
|
|
return;
|
|
}
|
|
|
|
Color us = side_to_move();
|
|
Color them = opposite_color(us);
|
|
Square from = move_from(m);
|
|
Square to = move_to(m);
|
|
bool ep = move_is_ep(m);
|
|
bool pm = move_is_promotion(m);
|
|
|
|
Piece piece = piece_on(from);
|
|
PieceType pt = type_of_piece(piece);
|
|
|
|
assert(color_of_piece_on(from) == us);
|
|
assert(color_of_piece_on(to) == them || square_is_empty(to));
|
|
assert(!(ep || pm) || piece == piece_of_color_and_type(us, PAWN));
|
|
assert(!pm || relative_rank(us, to) == RANK_8);
|
|
|
|
st->capture = ep ? PAWN : type_of_piece_on(to);
|
|
|
|
if (st->capture)
|
|
do_capture_move(key, st->capture, them, to, ep);
|
|
|
|
// Update hash key
|
|
key ^= zobrist[us][pt][from] ^ zobrist[us][pt][to];
|
|
|
|
// Reset en passant square
|
|
if (st->epSquare != SQ_NONE)
|
|
{
|
|
key ^= zobEp[st->epSquare];
|
|
st->epSquare = SQ_NONE;
|
|
}
|
|
|
|
// Update castle rights, try to shortcut a common case
|
|
int cm = castleRightsMask[from] & castleRightsMask[to];
|
|
if (cm != ALL_CASTLES && ((cm & st->castleRights) != st->castleRights))
|
|
{
|
|
key ^= zobCastle[st->castleRights];
|
|
st->castleRights &= castleRightsMask[from];
|
|
st->castleRights &= castleRightsMask[to];
|
|
key ^= zobCastle[st->castleRights];
|
|
}
|
|
|
|
// Prefetch TT access as soon as we know key is updated
|
|
TT.prefetch(key);
|
|
|
|
// Move the piece
|
|
Bitboard move_bb = make_move_bb(from, to);
|
|
do_move_bb(&(byColorBB[us]), move_bb);
|
|
do_move_bb(&(byTypeBB[pt]), move_bb);
|
|
do_move_bb(&(byTypeBB[0]), move_bb); // HACK: byTypeBB[0] == occupied squares
|
|
|
|
board[to] = board[from];
|
|
board[from] = EMPTY;
|
|
|
|
// If the moving piece was a king, update the king square
|
|
if (pt == KING)
|
|
kingSquare[us] = to;
|
|
|
|
// Update piece lists, note that index[from] is not updated and
|
|
// becomes stale. This works as long as index[] is accessed just
|
|
// by known occupied squares.
|
|
index[to] = index[from];
|
|
pieceList[us][pt][index[to]] = to;
|
|
|
|
// If the moving piece was a pawn do some special extra work
|
|
if (pt == PAWN)
|
|
{
|
|
// Reset rule 50 draw counter
|
|
st->rule50 = 0;
|
|
|
|
// Update pawn hash key
|
|
st->pawnKey ^= zobrist[us][PAWN][from] ^ zobrist[us][PAWN][to];
|
|
|
|
// Set en passant square, only if moved pawn can be captured
|
|
if (abs(int(to) - int(from)) == 16)
|
|
{
|
|
if (pawn_attacks(us, from + (us == WHITE ? DELTA_N : DELTA_S)) & pawns(them))
|
|
{
|
|
st->epSquare = Square((int(from) + int(to)) / 2);
|
|
key ^= zobEp[st->epSquare];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update incremental scores
|
|
st->mgValue += pst_delta<MidGame>(piece, from, to);
|
|
st->egValue += pst_delta<EndGame>(piece, from, to);
|
|
|
|
if (pm) // promotion ?
|
|
{
|
|
PieceType promotion = move_promotion_piece(m);
|
|
|
|
assert(promotion >= KNIGHT && promotion <= QUEEN);
|
|
|
|
// Insert promoted piece instead of pawn
|
|
clear_bit(&(byTypeBB[PAWN]), to);
|
|
set_bit(&(byTypeBB[promotion]), to);
|
|
board[to] = piece_of_color_and_type(us, promotion);
|
|
|
|
// Update material key
|
|
st->materialKey ^= zobMaterial[us][PAWN][pieceCount[us][PAWN]];
|
|
st->materialKey ^= zobMaterial[us][promotion][pieceCount[us][promotion]+1];
|
|
|
|
// Update piece counts
|
|
pieceCount[us][PAWN]--;
|
|
pieceCount[us][promotion]++;
|
|
|
|
// Update piece lists, move the last pawn at index[to] position
|
|
// and shrink the list. Add a new promotion piece to the list.
|
|
Square lastPawnSquare = pieceList[us][PAWN][pieceCount[us][PAWN]];
|
|
index[lastPawnSquare] = index[to];
|
|
pieceList[us][PAWN][index[lastPawnSquare]] = lastPawnSquare;
|
|
index[to] = pieceCount[us][promotion] - 1;
|
|
pieceList[us][promotion][index[to]] = to;
|
|
|
|
// Partially revert hash keys update
|
|
key ^= zobrist[us][PAWN][to] ^ zobrist[us][promotion][to];
|
|
st->pawnKey ^= zobrist[us][PAWN][to];
|
|
|
|
// Partially revert and update incremental scores
|
|
st->mgValue -= pst<MidGame>(us, PAWN, to);
|
|
st->mgValue += pst<MidGame>(us, promotion, to);
|
|
st->egValue -= pst<EndGame>(us, PAWN, to);
|
|
st->egValue += pst<EndGame>(us, promotion, to);
|
|
|
|
// Update material
|
|
st->npMaterial[us] += piece_value_midgame(promotion);
|
|
}
|
|
|
|
// Update the key with the final value
|
|
st->key = key;
|
|
|
|
// Update checkers bitboard, piece must be already moved
|
|
if (ep | pm)
|
|
st->checkersBB = attacks_to(king_square(them), us);
|
|
else
|
|
{
|
|
st->checkersBB = EmptyBoardBB;
|
|
Square ksq = king_square(them);
|
|
switch (pt)
|
|
{
|
|
case PAWN: update_checkers<PAWN>(&(st->checkersBB), ksq, from, to, dcCandidates); break;
|
|
case KNIGHT: update_checkers<KNIGHT>(&(st->checkersBB), ksq, from, to, dcCandidates); break;
|
|
case BISHOP: update_checkers<BISHOP>(&(st->checkersBB), ksq, from, to, dcCandidates); break;
|
|
case ROOK: update_checkers<ROOK>(&(st->checkersBB), ksq, from, to, dcCandidates); break;
|
|
case QUEEN: update_checkers<QUEEN>(&(st->checkersBB), ksq, from, to, dcCandidates); break;
|
|
case KING: update_checkers<KING>(&(st->checkersBB), ksq, from, to, dcCandidates); break;
|
|
default: assert(false); break;
|
|
}
|
|
}
|
|
|
|
// Finish
|
|
sideToMove = opposite_color(sideToMove);
|
|
|
|
st->mgValue += (sideToMove == WHITE)? TempoValueMidgame : -TempoValueMidgame;
|
|
st->egValue += (sideToMove == WHITE)? TempoValueEndgame : -TempoValueEndgame;
|
|
|
|
assert(is_ok());
|
|
}
|
|
|
|
|
|
/// Position::do_capture_move() is a private method used to update captured
|
|
/// piece info. It is called from the main Position::do_move function.
|
|
|
|
void Position::do_capture_move(Bitboard& key, PieceType capture, Color them, Square to, bool ep) {
|
|
|
|
assert(capture != KING);
|
|
|
|
Square capsq = to;
|
|
|
|
if (ep) // en passant ?
|
|
{
|
|
capsq = (them == BLACK)? (to - DELTA_N) : (to - DELTA_S);
|
|
|
|
assert(to == st->epSquare);
|
|
assert(relative_rank(opposite_color(them), to) == RANK_6);
|
|
assert(piece_on(to) == EMPTY);
|
|
assert(piece_on(capsq) == piece_of_color_and_type(them, PAWN));
|
|
|
|
board[capsq] = EMPTY;
|
|
}
|
|
|
|
// Remove captured piece
|
|
clear_bit(&(byColorBB[them]), capsq);
|
|
clear_bit(&(byTypeBB[capture]), capsq);
|
|
clear_bit(&(byTypeBB[0]), capsq);
|
|
|
|
// Update hash key
|
|
key ^= zobrist[them][capture][capsq];
|
|
|
|
// If the captured piece was a pawn, update pawn hash key
|
|
if (capture == PAWN)
|
|
st->pawnKey ^= zobrist[them][PAWN][capsq];
|
|
|
|
// Update incremental scores
|
|
st->mgValue -= pst<MidGame>(them, capture, capsq);
|
|
st->egValue -= pst<EndGame>(them, capture, capsq);
|
|
|
|
// Update material
|
|
if (capture != PAWN)
|
|
st->npMaterial[them] -= piece_value_midgame(capture);
|
|
|
|
// Update material hash key
|
|
st->materialKey ^= zobMaterial[them][capture][pieceCount[them][capture]];
|
|
|
|
// Update piece count
|
|
pieceCount[them][capture]--;
|
|
|
|
// Update piece list, move the last piece at index[capsq] position
|
|
//
|
|
// WARNING: this is a not perfectly revresible operation. When we
|
|
// will reinsert the captured piece in undo_move() we will put it
|
|
// at the end of the list and not in its original place, it means
|
|
// index[] and pieceList[] are not guaranteed to be invariant to a
|
|
// do_move() + undo_move() sequence.
|
|
Square lastPieceSquare = pieceList[them][capture][pieceCount[them][capture]];
|
|
index[lastPieceSquare] = index[capsq];
|
|
pieceList[them][capture][index[lastPieceSquare]] = lastPieceSquare;
|
|
|
|
// Reset rule 50 counter
|
|
st->rule50 = 0;
|
|
}
|
|
|
|
|
|
/// Position::do_castle_move() is a private method used to make a castling
|
|
/// move. It is called from the main Position::do_move function. Note that
|
|
/// castling moves are encoded as "king captures friendly rook" moves, for
|
|
/// instance white short castling in a non-Chess960 game is encoded as e1h1.
|
|
|
|
void Position::do_castle_move(Move m) {
|
|
|
|
assert(move_is_ok(m));
|
|
assert(move_is_castle(m));
|
|
|
|
Color us = side_to_move();
|
|
Color them = opposite_color(us);
|
|
|
|
// Reset capture field
|
|
st->capture = NO_PIECE_TYPE;
|
|
|
|
// Find source squares for king and rook
|
|
Square kfrom = move_from(m);
|
|
Square rfrom = move_to(m); // HACK: See comment at beginning of function
|
|
Square kto, rto;
|
|
|
|
assert(piece_on(kfrom) == piece_of_color_and_type(us, KING));
|
|
assert(piece_on(rfrom) == piece_of_color_and_type(us, ROOK));
|
|
|
|
// Find destination squares for king and rook
|
|
if (rfrom > kfrom) // O-O
|
|
{
|
|
kto = relative_square(us, SQ_G1);
|
|
rto = relative_square(us, SQ_F1);
|
|
} else { // O-O-O
|
|
kto = relative_square(us, SQ_C1);
|
|
rto = relative_square(us, SQ_D1);
|
|
}
|
|
|
|
// Move the pieces
|
|
Bitboard kmove_bb = make_move_bb(kfrom, kto);
|
|
do_move_bb(&(byColorBB[us]), kmove_bb);
|
|
do_move_bb(&(byTypeBB[KING]), kmove_bb);
|
|
do_move_bb(&(byTypeBB[0]), kmove_bb); // HACK: byTypeBB[0] == occupied squares
|
|
|
|
Bitboard rmove_bb = make_move_bb(rfrom, rto);
|
|
do_move_bb(&(byColorBB[us]), rmove_bb);
|
|
do_move_bb(&(byTypeBB[ROOK]), rmove_bb);
|
|
do_move_bb(&(byTypeBB[0]), rmove_bb); // HACK: byTypeBB[0] == occupied squares
|
|
|
|
// Update board array
|
|
Piece king = piece_of_color_and_type(us, KING);
|
|
Piece rook = piece_of_color_and_type(us, ROOK);
|
|
board[kfrom] = board[rfrom] = EMPTY;
|
|
board[kto] = king;
|
|
board[rto] = rook;
|
|
|
|
// Update king square
|
|
kingSquare[us] = kto;
|
|
|
|
// Update piece lists
|
|
pieceList[us][KING][index[kfrom]] = kto;
|
|
pieceList[us][ROOK][index[rfrom]] = rto;
|
|
int tmp = index[rfrom]; // In Chess960 could be rto == kfrom
|
|
index[kto] = index[kfrom];
|
|
index[rto] = tmp;
|
|
|
|
// Update incremental scores
|
|
st->mgValue += pst_delta<MidGame>(king, kfrom, kto);
|
|
st->egValue += pst_delta<EndGame>(king, kfrom, kto);
|
|
st->mgValue += pst_delta<MidGame>(rook, rfrom, rto);
|
|
st->egValue += pst_delta<EndGame>(rook, rfrom, rto);
|
|
|
|
// Update hash key
|
|
st->key ^= zobrist[us][KING][kfrom] ^ zobrist[us][KING][kto];
|
|
st->key ^= zobrist[us][ROOK][rfrom] ^ zobrist[us][ROOK][rto];
|
|
|
|
// Clear en passant square
|
|
if (st->epSquare != SQ_NONE)
|
|
{
|
|
st->key ^= zobEp[st->epSquare];
|
|
st->epSquare = SQ_NONE;
|
|
}
|
|
|
|
// Update castling rights
|
|
st->key ^= zobCastle[st->castleRights];
|
|
st->castleRights &= castleRightsMask[kfrom];
|
|
st->key ^= zobCastle[st->castleRights];
|
|
|
|
// Reset rule 50 counter
|
|
st->rule50 = 0;
|
|
|
|
// Update checkers BB
|
|
st->checkersBB = attacks_to(king_square(them), us);
|
|
|
|
// Finish
|
|
sideToMove = opposite_color(sideToMove);
|
|
|
|
st->mgValue += (sideToMove == WHITE)? TempoValueMidgame : -TempoValueMidgame;
|
|
st->egValue += (sideToMove == WHITE)? TempoValueEndgame : -TempoValueEndgame;
|
|
|
|
assert(is_ok());
|
|
}
|
|
|
|
|
|
/// Position::undo_move() unmakes a move. When it returns, the position should
|
|
/// be restored to exactly the same state as before the move was made.
|
|
|
|
void Position::undo_move(Move m) {
|
|
|
|
assert(is_ok());
|
|
assert(move_is_ok(m));
|
|
|
|
gamePly--;
|
|
sideToMove = opposite_color(sideToMove);
|
|
|
|
if (move_is_castle(m))
|
|
{
|
|
undo_castle_move(m);
|
|
return;
|
|
}
|
|
|
|
Color us = side_to_move();
|
|
Color them = opposite_color(us);
|
|
Square from = move_from(m);
|
|
Square to = move_to(m);
|
|
bool ep = move_is_ep(m);
|
|
bool pm = move_is_promotion(m);
|
|
|
|
PieceType pt = type_of_piece_on(to);
|
|
|
|
assert(square_is_empty(from));
|
|
assert(color_of_piece_on(to) == us);
|
|
assert(!pm || relative_rank(us, to) == RANK_8);
|
|
assert(!ep || to == st->previous->epSquare);
|
|
assert(!ep || relative_rank(us, to) == RANK_6);
|
|
assert(!ep || piece_on(to) == piece_of_color_and_type(us, PAWN));
|
|
|
|
if (pm) // promotion ?
|
|
{
|
|
PieceType promotion = move_promotion_piece(m);
|
|
pt = PAWN;
|
|
|
|
assert(promotion >= KNIGHT && promotion <= QUEEN);
|
|
assert(piece_on(to) == piece_of_color_and_type(us, promotion));
|
|
|
|
// Replace promoted piece with a pawn
|
|
clear_bit(&(byTypeBB[promotion]), to);
|
|
set_bit(&(byTypeBB[PAWN]), to);
|
|
|
|
// Update piece counts
|
|
pieceCount[us][promotion]--;
|
|
pieceCount[us][PAWN]++;
|
|
|
|
// Update piece list replacing promotion piece with a pawn
|
|
Square lastPromotionSquare = pieceList[us][promotion][pieceCount[us][promotion]];
|
|
index[lastPromotionSquare] = index[to];
|
|
pieceList[us][promotion][index[lastPromotionSquare]] = lastPromotionSquare;
|
|
index[to] = pieceCount[us][PAWN] - 1;
|
|
pieceList[us][PAWN][index[to]] = to;
|
|
}
|
|
|
|
// Put the piece back at the source square
|
|
Bitboard move_bb = make_move_bb(to, from);
|
|
do_move_bb(&(byColorBB[us]), move_bb);
|
|
do_move_bb(&(byTypeBB[pt]), move_bb);
|
|
do_move_bb(&(byTypeBB[0]), move_bb); // HACK: byTypeBB[0] == occupied squares
|
|
|
|
board[from] = piece_of_color_and_type(us, pt);
|
|
board[to] = EMPTY;
|
|
|
|
// If the moving piece was a king, update the king square
|
|
if (pt == KING)
|
|
kingSquare[us] = from;
|
|
|
|
// Update piece list
|
|
index[from] = index[to];
|
|
pieceList[us][pt][index[from]] = from;
|
|
|
|
if (st->capture)
|
|
{
|
|
Square capsq = to;
|
|
|
|
if (ep)
|
|
capsq = (us == WHITE)? (to - DELTA_N) : (to - DELTA_S);
|
|
|
|
assert(st->capture != KING);
|
|
assert(!ep || square_is_empty(capsq));
|
|
|
|
// Restore the captured piece
|
|
set_bit(&(byColorBB[them]), capsq);
|
|
set_bit(&(byTypeBB[st->capture]), capsq);
|
|
set_bit(&(byTypeBB[0]), capsq);
|
|
|
|
board[capsq] = piece_of_color_and_type(them, st->capture);
|
|
|
|
// Update piece count
|
|
pieceCount[them][st->capture]++;
|
|
|
|
// Update piece list, add a new captured piece in capsq square
|
|
index[capsq] = pieceCount[them][st->capture] - 1;
|
|
pieceList[them][st->capture][index[capsq]] = capsq;
|
|
}
|
|
|
|
// Finally point our state pointer back to the previous state
|
|
st = st->previous;
|
|
|
|
assert(is_ok());
|
|
}
|
|
|
|
|
|
/// Position::undo_castle_move() is a private method used to unmake a castling
|
|
/// move. It is called from the main Position::undo_move function. Note that
|
|
/// castling moves are encoded as "king captures friendly rook" moves, for
|
|
/// instance white short castling in a non-Chess960 game is encoded as e1h1.
|
|
|
|
void Position::undo_castle_move(Move m) {
|
|
|
|
assert(move_is_ok(m));
|
|
assert(move_is_castle(m));
|
|
|
|
// When we have arrived here, some work has already been done by
|
|
// Position::undo_move. In particular, the side to move has been switched,
|
|
// so the code below is correct.
|
|
Color us = side_to_move();
|
|
|
|
// Find source squares for king and rook
|
|
Square kfrom = move_from(m);
|
|
Square rfrom = move_to(m); // HACK: See comment at beginning of function
|
|
Square kto, rto;
|
|
|
|
// Find destination squares for king and rook
|
|
if (rfrom > kfrom) // O-O
|
|
{
|
|
kto = relative_square(us, SQ_G1);
|
|
rto = relative_square(us, SQ_F1);
|
|
} else { // O-O-O
|
|
kto = relative_square(us, SQ_C1);
|
|
rto = relative_square(us, SQ_D1);
|
|
}
|
|
|
|
assert(piece_on(kto) == piece_of_color_and_type(us, KING));
|
|
assert(piece_on(rto) == piece_of_color_and_type(us, ROOK));
|
|
|
|
// Put the pieces back at the source square
|
|
Bitboard kmove_bb = make_move_bb(kto, kfrom);
|
|
do_move_bb(&(byColorBB[us]), kmove_bb);
|
|
do_move_bb(&(byTypeBB[KING]), kmove_bb);
|
|
do_move_bb(&(byTypeBB[0]), kmove_bb); // HACK: byTypeBB[0] == occupied squares
|
|
|
|
Bitboard rmove_bb = make_move_bb(rto, rfrom);
|
|
do_move_bb(&(byColorBB[us]), rmove_bb);
|
|
do_move_bb(&(byTypeBB[ROOK]), rmove_bb);
|
|
do_move_bb(&(byTypeBB[0]), rmove_bb); // HACK: byTypeBB[0] == occupied squares
|
|
|
|
// Update board
|
|
board[rto] = board[kto] = EMPTY;
|
|
board[rfrom] = piece_of_color_and_type(us, ROOK);
|
|
board[kfrom] = piece_of_color_and_type(us, KING);
|
|
|
|
// Update king square
|
|
kingSquare[us] = kfrom;
|
|
|
|
// Update piece lists
|
|
pieceList[us][KING][index[kto]] = kfrom;
|
|
pieceList[us][ROOK][index[rto]] = rfrom;
|
|
int tmp = index[rto]; // In Chess960 could be rto == kfrom
|
|
index[kfrom] = index[kto];
|
|
index[rfrom] = tmp;
|
|
|
|
// Finally point our state pointer back to the previous state
|
|
st = st->previous;
|
|
|
|
assert(is_ok());
|
|
}
|
|
|
|
|
|
/// Position::do_null_move makes() a "null move": It switches the side to move
|
|
/// and updates the hash key without executing any move on the board.
|
|
|
|
void Position::do_null_move(StateInfo& backupSt) {
|
|
|
|
assert(is_ok());
|
|
assert(!is_check());
|
|
|
|
// Back up the information necessary to undo the null move to the supplied
|
|
// StateInfo object.
|
|
// Note that differently from normal case here backupSt is actually used as
|
|
// a backup storage not as a new state to be used.
|
|
backupSt.key = st->key;
|
|
backupSt.epSquare = st->epSquare;
|
|
backupSt.mgValue = st->mgValue;
|
|
backupSt.egValue = st->egValue;
|
|
backupSt.previous = st->previous;
|
|
st->previous = &backupSt;
|
|
|
|
// Save the current key to the history[] array, in order to be able to
|
|
// detect repetition draws.
|
|
history[gamePly] = st->key;
|
|
|
|
// Update the necessary information
|
|
if (st->epSquare != SQ_NONE)
|
|
st->key ^= zobEp[st->epSquare];
|
|
|
|
st->key ^= zobSideToMove;
|
|
TT.prefetch(st->key);
|
|
|
|
sideToMove = opposite_color(sideToMove);
|
|
st->epSquare = SQ_NONE;
|
|
st->rule50++;
|
|
gamePly++;
|
|
|
|
st->mgValue += (sideToMove == WHITE)? TempoValueMidgame : -TempoValueMidgame;
|
|
st->egValue += (sideToMove == WHITE)? TempoValueEndgame : -TempoValueEndgame;
|
|
}
|
|
|
|
|
|
/// Position::undo_null_move() unmakes a "null move".
|
|
|
|
void Position::undo_null_move() {
|
|
|
|
assert(is_ok());
|
|
assert(!is_check());
|
|
|
|
// Restore information from the our backup StateInfo object
|
|
StateInfo* backupSt = st->previous;
|
|
st->key = backupSt->key;
|
|
st->epSquare = backupSt->epSquare;
|
|
st->mgValue = backupSt->mgValue;
|
|
st->egValue = backupSt->egValue;
|
|
st->previous = backupSt->previous;
|
|
|
|
// Update the necessary information
|
|
sideToMove = opposite_color(sideToMove);
|
|
st->rule50--;
|
|
gamePly--;
|
|
}
|
|
|
|
|
|
/// Position::see() is a static exchange evaluator: It tries to estimate the
|
|
/// material gain or loss resulting from a move. There are three versions of
|
|
/// this function: One which takes a destination square as input, one takes a
|
|
/// move, and one which takes a 'from' and a 'to' square. The function does
|
|
/// not yet understand promotions captures.
|
|
|
|
int Position::see(Square to) const {
|
|
|
|
assert(square_is_ok(to));
|
|
return see(SQ_NONE, to);
|
|
}
|
|
|
|
int Position::see(Move m) const {
|
|
|
|
assert(move_is_ok(m));
|
|
return see(move_from(m), move_to(m));
|
|
}
|
|
|
|
int Position::see_sign(Move m) const {
|
|
|
|
assert(move_is_ok(m));
|
|
|
|
Square from = move_from(m);
|
|
Square to = move_to(m);
|
|
|
|
// Early return if SEE cannot be negative because capturing piece value
|
|
// is not bigger then captured one.
|
|
if ( midgame_value_of_piece_on(from) <= midgame_value_of_piece_on(to)
|
|
&& type_of_piece_on(from) != KING)
|
|
return 1;
|
|
|
|
return see(from, to);
|
|
}
|
|
|
|
int Position::see(Square from, Square to) const {
|
|
|
|
// Material values
|
|
static const int seeValues[18] = {
|
|
0, PawnValueMidgame, KnightValueMidgame, BishopValueMidgame,
|
|
RookValueMidgame, QueenValueMidgame, QueenValueMidgame*10, 0,
|
|
0, PawnValueMidgame, KnightValueMidgame, BishopValueMidgame,
|
|
RookValueMidgame, QueenValueMidgame, QueenValueMidgame*10, 0,
|
|
0, 0
|
|
};
|
|
|
|
Bitboard attackers, stmAttackers, occ, b;
|
|
|
|
assert(square_is_ok(from) || from == SQ_NONE);
|
|
assert(square_is_ok(to));
|
|
|
|
// Initialize colors
|
|
Color us = (from != SQ_NONE ? color_of_piece_on(from) : opposite_color(color_of_piece_on(to)));
|
|
Color them = opposite_color(us);
|
|
|
|
// Initialize pieces
|
|
Piece piece = piece_on(from);
|
|
Piece capture = piece_on(to);
|
|
|
|
// Find all attackers to the destination square, with the moving piece
|
|
// removed, but possibly an X-ray attacker added behind it.
|
|
occ = occupied_squares();
|
|
|
|
// Handle en passant moves
|
|
if (st->epSquare == to && type_of_piece_on(from) == PAWN)
|
|
{
|
|
assert(capture == EMPTY);
|
|
|
|
Square capQq = (side_to_move() == WHITE)? (to - DELTA_N) : (to - DELTA_S);
|
|
capture = piece_on(capQq);
|
|
assert(type_of_piece_on(capQq) == PAWN);
|
|
|
|
// Remove the captured pawn
|
|
clear_bit(&occ, capQq);
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
clear_bit(&occ, from);
|
|
attackers = (rook_attacks_bb(to, occ) & rooks_and_queens())
|
|
| (bishop_attacks_bb(to, occ) & bishops_and_queens())
|
|
| (piece_attacks<KNIGHT>(to) & knights())
|
|
| (piece_attacks<KING>(to) & kings())
|
|
| (pawn_attacks(WHITE, to) & pawns(BLACK))
|
|
| (pawn_attacks(BLACK, to) & pawns(WHITE));
|
|
|
|
if (from != SQ_NONE)
|
|
break;
|
|
|
|
// If we don't have any attacker we are finished
|
|
if ((attackers & pieces_of_color(us)) == EmptyBoardBB)
|
|
return 0;
|
|
|
|
// Locate the least valuable attacker to the destination square
|
|
// and use it to initialize from square.
|
|
PieceType pt;
|
|
for (pt = PAWN; !(attackers & pieces_of_color_and_type(us, pt)); pt++)
|
|
assert(pt < KING);
|
|
|
|
from = first_1(attackers & pieces_of_color_and_type(us, pt));
|
|
piece = piece_on(from);
|
|
}
|
|
|
|
// If the opponent has no attackers we are finished
|
|
stmAttackers = attackers & pieces_of_color(them);
|
|
if (!stmAttackers)
|
|
return seeValues[capture];
|
|
|
|
attackers &= occ; // Remove the moving piece
|
|
|
|
// The destination square is defended, which makes things rather more
|
|
// difficult to compute. We proceed by building up a "swap list" containing
|
|
// the material gain or loss at each stop in a sequence of captures to the
|
|
// destination square, where the sides alternately capture, and always
|
|
// capture with the least valuable piece. After each capture, we look for
|
|
// new X-ray attacks from behind the capturing piece.
|
|
int lastCapturingPieceValue = seeValues[piece];
|
|
int swapList[32], n = 1;
|
|
Color c = them;
|
|
PieceType pt;
|
|
|
|
swapList[0] = seeValues[capture];
|
|
|
|
do {
|
|
// Locate the least valuable attacker for the side to move. The loop
|
|
// below looks like it is potentially infinite, but it isn't. We know
|
|
// that the side to move still has at least one attacker left.
|
|
for (pt = PAWN; !(stmAttackers & pieces_of_type(pt)); pt++)
|
|
assert(pt < KING);
|
|
|
|
// Remove the attacker we just found from the 'attackers' bitboard,
|
|
// and scan for new X-ray attacks behind the attacker.
|
|
b = stmAttackers & pieces_of_type(pt);
|
|
occ ^= (b & (~b + 1));
|
|
attackers |= (rook_attacks_bb(to, occ) & rooks_and_queens())
|
|
| (bishop_attacks_bb(to, occ) & bishops_and_queens());
|
|
|
|
attackers &= occ;
|
|
|
|
// Add the new entry to the swap list
|
|
assert(n < 32);
|
|
swapList[n] = -swapList[n - 1] + lastCapturingPieceValue;
|
|
n++;
|
|
|
|
// Remember the value of the capturing piece, and change the side to move
|
|
// before beginning the next iteration
|
|
lastCapturingPieceValue = seeValues[pt];
|
|
c = opposite_color(c);
|
|
stmAttackers = attackers & pieces_of_color(c);
|
|
|
|
// Stop after a king capture
|
|
if (pt == KING && stmAttackers)
|
|
{
|
|
assert(n < 32);
|
|
swapList[n++] = QueenValueMidgame*10;
|
|
break;
|
|
}
|
|
} while (stmAttackers);
|
|
|
|
// Having built the swap list, we negamax through it to find the best
|
|
// achievable score from the point of view of the side to move
|
|
while (--n)
|
|
swapList[n-1] = Min(-swapList[n], swapList[n-1]);
|
|
|
|
return swapList[0];
|
|
}
|
|
|
|
|
|
/// Position::saveState() copies the content of the current state
|
|
/// inside startState and makes st point to it. This is needed
|
|
/// when the st pointee could become stale, as example because
|
|
/// the caller is about to going out of scope.
|
|
|
|
void Position::saveState() {
|
|
|
|
startState = *st;
|
|
st = &startState;
|
|
st->previous = NULL; // as a safe guard
|
|
}
|
|
|
|
|
|
/// Position::clear() erases the position object to a pristine state, with an
|
|
/// empty board, white to move, and no castling rights.
|
|
|
|
void Position::clear() {
|
|
|
|
st = &startState;
|
|
memset(st, 0, sizeof(StateInfo));
|
|
st->epSquare = SQ_NONE;
|
|
|
|
memset(byColorBB, 0, sizeof(Bitboard) * 2);
|
|
memset(byTypeBB, 0, sizeof(Bitboard) * 8);
|
|
memset(pieceCount, 0, sizeof(int) * 2 * 8);
|
|
memset(index, 0, sizeof(int) * 64);
|
|
|
|
for (int i = 0; i < 64; i++)
|
|
board[i] = EMPTY;
|
|
|
|
for (int i = 0; i < 7; i++)
|
|
for (int j = 0; j < 8; j++)
|
|
pieceList[0][i][j] = pieceList[1][i][j] = SQ_NONE;
|
|
|
|
sideToMove = WHITE;
|
|
gamePly = 0;
|
|
initialKFile = FILE_E;
|
|
initialKRFile = FILE_H;
|
|
initialQRFile = FILE_A;
|
|
}
|
|
|
|
|
|
/// Position::reset_game_ply() simply sets gamePly to 0. It is used from the
|
|
/// UCI interface code, whenever a non-reversible move is made in a
|
|
/// 'position fen <fen> moves m1 m2 ...' command. This makes it possible
|
|
/// for the program to handle games of arbitrary length, as long as the GUI
|
|
/// handles draws by the 50 move rule correctly.
|
|
|
|
void Position::reset_game_ply() {
|
|
|
|
gamePly = 0;
|
|
}
|
|
|
|
|
|
/// Position::put_piece() puts a piece on the given square of the board,
|
|
/// updating the board array, bitboards, and piece counts.
|
|
|
|
void Position::put_piece(Piece p, Square s) {
|
|
|
|
Color c = color_of_piece(p);
|
|
PieceType pt = type_of_piece(p);
|
|
|
|
board[s] = p;
|
|
index[s] = pieceCount[c][pt];
|
|
pieceList[c][pt][index[s]] = s;
|
|
|
|
set_bit(&(byTypeBB[pt]), s);
|
|
set_bit(&(byColorBB[c]), s);
|
|
set_bit(&byTypeBB[0], s); // HACK: byTypeBB[0] contains all occupied squares.
|
|
|
|
pieceCount[c][pt]++;
|
|
|
|
if (pt == KING)
|
|
kingSquare[c] = s;
|
|
}
|
|
|
|
|
|
/// Position::allow_oo() gives the given side the right to castle kingside.
|
|
/// Used when setting castling rights during parsing of FEN strings.
|
|
|
|
void Position::allow_oo(Color c) {
|
|
|
|
st->castleRights |= (1 + int(c));
|
|
}
|
|
|
|
|
|
/// Position::allow_ooo() gives the given side the right to castle queenside.
|
|
/// Used when setting castling rights during parsing of FEN strings.
|
|
|
|
void Position::allow_ooo(Color c) {
|
|
|
|
st->castleRights |= (4 + 4*int(c));
|
|
}
|
|
|
|
|
|
/// Position::compute_key() computes the hash key of the position. The hash
|
|
/// key is usually updated incrementally as moves are made and unmade, the
|
|
/// compute_key() function is only used when a new position is set up, and
|
|
/// to verify the correctness of the hash key when running in debug mode.
|
|
|
|
Key Position::compute_key() const {
|
|
|
|
Key result = Key(0ULL);
|
|
|
|
for (Square s = SQ_A1; s <= SQ_H8; s++)
|
|
if (square_is_occupied(s))
|
|
result ^= zobrist[color_of_piece_on(s)][type_of_piece_on(s)][s];
|
|
|
|
if (ep_square() != SQ_NONE)
|
|
result ^= zobEp[ep_square()];
|
|
|
|
result ^= zobCastle[st->castleRights];
|
|
if (side_to_move() == BLACK)
|
|
result ^= zobSideToMove;
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/// Position::compute_pawn_key() computes the hash key of the position. The
|
|
/// hash key is usually updated incrementally as moves are made and unmade,
|
|
/// the compute_pawn_key() function is only used when a new position is set
|
|
/// up, and to verify the correctness of the pawn hash key when running in
|
|
/// debug mode.
|
|
|
|
Key Position::compute_pawn_key() const {
|
|
|
|
Key result = Key(0ULL);
|
|
Bitboard b;
|
|
Square s;
|
|
|
|
for (Color c = WHITE; c <= BLACK; c++)
|
|
{
|
|
b = pawns(c);
|
|
while(b)
|
|
{
|
|
s = pop_1st_bit(&b);
|
|
result ^= zobrist[c][PAWN][s];
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/// Position::compute_material_key() computes the hash key of the position.
|
|
/// The hash key is usually updated incrementally as moves are made and unmade,
|
|
/// the compute_material_key() function is only used when a new position is set
|
|
/// up, and to verify the correctness of the material hash key when running in
|
|
/// debug mode.
|
|
|
|
Key Position::compute_material_key() const {
|
|
|
|
Key result = Key(0ULL);
|
|
for (Color c = WHITE; c <= BLACK; c++)
|
|
for (PieceType pt = PAWN; pt <= QUEEN; pt++)
|
|
{
|
|
int count = piece_count(c, pt);
|
|
for (int i = 0; i <= count; i++)
|
|
result ^= zobMaterial[c][pt][i];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/// Position::compute_value() compute the incremental scores for the middle
|
|
/// game and the endgame. These functions are used to initialize the incremental
|
|
/// scores when a new position is set up, and to verify that the scores are correctly
|
|
/// updated by do_move and undo_move when the program is running in debug mode.
|
|
template<Position::GamePhase Phase>
|
|
Value Position::compute_value() const {
|
|
|
|
Value result = Value(0);
|
|
Bitboard b;
|
|
Square s;
|
|
|
|
for (Color c = WHITE; c <= BLACK; c++)
|
|
for (PieceType pt = PAWN; pt <= KING; pt++)
|
|
{
|
|
b = pieces_of_color_and_type(c, pt);
|
|
while(b)
|
|
{
|
|
s = pop_1st_bit(&b);
|
|
assert(piece_on(s) == piece_of_color_and_type(c, pt));
|
|
result += pst<Phase>(c, pt, s);
|
|
}
|
|
}
|
|
|
|
const Value TempoValue = (Phase == MidGame ? TempoValueMidgame : TempoValueEndgame);
|
|
result += (side_to_move() == WHITE)? TempoValue / 2 : -TempoValue / 2;
|
|
return result;
|
|
}
|
|
|
|
|
|
/// Position::compute_non_pawn_material() computes the total non-pawn middle
|
|
/// game material score for the given side. Material scores are updated
|
|
/// incrementally during the search, this function is only used while
|
|
/// initializing a new Position object.
|
|
|
|
Value Position::compute_non_pawn_material(Color c) const {
|
|
|
|
Value result = Value(0);
|
|
|
|
for (PieceType pt = KNIGHT; pt <= QUEEN; pt++)
|
|
{
|
|
Bitboard b = pieces_of_color_and_type(c, pt);
|
|
while (b)
|
|
{
|
|
assert(piece_on(first_1(b)) == piece_of_color_and_type(c, pt));
|
|
pop_1st_bit(&b);
|
|
result += piece_value_midgame(pt);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/// Position::is_draw() tests whether the position is drawn by material,
|
|
/// repetition, or the 50 moves rule. It does not detect stalemates, this
|
|
/// must be done by the search.
|
|
|
|
bool Position::is_draw() const {
|
|
|
|
// Draw by material?
|
|
if ( !pawns()
|
|
&& (non_pawn_material(WHITE) + non_pawn_material(BLACK) <= BishopValueMidgame))
|
|
return true;
|
|
|
|
// Draw by the 50 moves rule?
|
|
if (st->rule50 > 100 || (st->rule50 == 100 && !is_check()))
|
|
return true;
|
|
|
|
// Draw by repetition?
|
|
for (int i = 2; i < Min(gamePly, st->rule50); i += 2)
|
|
if (history[gamePly - i] == st->key)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/// Position::is_mate() returns true or false depending on whether the
|
|
/// side to move is checkmated.
|
|
|
|
bool Position::is_mate() const {
|
|
|
|
MoveStack moves[256];
|
|
|
|
return is_check() && (generate_evasions(*this, moves, pinned_pieces(sideToMove)) == moves);
|
|
}
|
|
|
|
|
|
/// Position::has_mate_threat() tests whether a given color has a mate in one
|
|
/// from the current position.
|
|
|
|
bool Position::has_mate_threat(Color c) {
|
|
|
|
StateInfo st1, st2;
|
|
Color stm = side_to_move();
|
|
|
|
if (is_check())
|
|
return false;
|
|
|
|
// If the input color is not equal to the side to move, do a null move
|
|
if (c != stm)
|
|
do_null_move(st1);
|
|
|
|
MoveStack mlist[120];
|
|
bool result = false;
|
|
Bitboard dc = discovered_check_candidates(sideToMove);
|
|
Bitboard pinned = pinned_pieces(sideToMove);
|
|
|
|
// Generate pseudo-legal non-capture and capture check moves
|
|
MoveStack* last = generate_non_capture_checks(*this, mlist, dc);
|
|
last = generate_captures(*this, last);
|
|
|
|
// Loop through the moves, and see if one of them is mate
|
|
for (MoveStack* cur = mlist; cur != last; cur++)
|
|
{
|
|
Move move = cur->move;
|
|
if (!pl_move_is_legal(move, pinned))
|
|
continue;
|
|
|
|
do_move(move, st2);
|
|
if (is_mate())
|
|
result = true;
|
|
|
|
undo_move(move);
|
|
}
|
|
|
|
// Undo null move, if necessary
|
|
if (c != stm)
|
|
undo_null_move();
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/// Position::init_zobrist() is a static member function which initializes the
|
|
/// various arrays used to compute hash keys.
|
|
|
|
void Position::init_zobrist() {
|
|
|
|
for (int i = 0; i < 2; i++)
|
|
for (int j = 0; j < 8; j++)
|
|
for (int k = 0; k < 64; k++)
|
|
zobrist[i][j][k] = Key(genrand_int64());
|
|
|
|
for (int i = 0; i < 64; i++)
|
|
zobEp[i] = Key(genrand_int64());
|
|
|
|
for (int i = 0; i < 16; i++)
|
|
zobCastle[i] = genrand_int64();
|
|
|
|
zobSideToMove = genrand_int64();
|
|
|
|
for (int i = 0; i < 2; i++)
|
|
for (int j = 0; j < 8; j++)
|
|
for (int k = 0; k < 16; k++)
|
|
zobMaterial[i][j][k] = (k > 0)? Key(genrand_int64()) : Key(0LL);
|
|
|
|
for (int i = 0; i < 16; i++)
|
|
zobMaterial[0][KING][i] = zobMaterial[1][KING][i] = Key(0ULL);
|
|
}
|
|
|
|
|
|
/// Position::init_piece_square_tables() initializes the piece square tables.
|
|
/// This is a two-step operation: First, the white halves of the tables are
|
|
/// copied from the MgPST[][] and EgPST[][] arrays, with a small random number
|
|
/// added to each entry if the "Randomness" UCI parameter is non-zero.
|
|
/// Second, the black halves of the tables are initialized by mirroring
|
|
/// and changing the sign of the corresponding white scores.
|
|
|
|
void Position::init_piece_square_tables() {
|
|
|
|
int r = get_option_value_int("Randomness"), i;
|
|
for (Square s = SQ_A1; s <= SQ_H8; s++)
|
|
for (Piece p = WP; p <= WK; p++)
|
|
{
|
|
i = (r == 0)? 0 : (genrand_int32() % (r*2) - r);
|
|
MgPieceSquareTable[p][s] = Value(MgPST[p][s] + i);
|
|
EgPieceSquareTable[p][s] = Value(EgPST[p][s] + i);
|
|
}
|
|
|
|
for (Square s = SQ_A1; s <= SQ_H8; s++)
|
|
for (Piece p = BP; p <= BK; p++)
|
|
{
|
|
MgPieceSquareTable[p][s] = -MgPieceSquareTable[p-8][flip_square(s)];
|
|
EgPieceSquareTable[p][s] = -EgPieceSquareTable[p-8][flip_square(s)];
|
|
}
|
|
}
|
|
|
|
|
|
/// Position::flipped_copy() makes a copy of the input position, but with
|
|
/// the white and black sides reversed. This is only useful for debugging,
|
|
/// especially for finding evaluation symmetry bugs.
|
|
|
|
void Position::flipped_copy(const Position& pos) {
|
|
|
|
assert(pos.is_ok());
|
|
|
|
clear();
|
|
|
|
// Board
|
|
for (Square s = SQ_A1; s <= SQ_H8; s++)
|
|
if (!pos.square_is_empty(s))
|
|
put_piece(Piece(int(pos.piece_on(s)) ^ 8), flip_square(s));
|
|
|
|
// Side to move
|
|
sideToMove = opposite_color(pos.side_to_move());
|
|
|
|
// Castling rights
|
|
if (pos.can_castle_kingside(WHITE)) allow_oo(BLACK);
|
|
if (pos.can_castle_queenside(WHITE)) allow_ooo(BLACK);
|
|
if (pos.can_castle_kingside(BLACK)) allow_oo(WHITE);
|
|
if (pos.can_castle_queenside(BLACK)) allow_ooo(WHITE);
|
|
|
|
initialKFile = pos.initialKFile;
|
|
initialKRFile = pos.initialKRFile;
|
|
initialQRFile = pos.initialQRFile;
|
|
|
|
for (Square sq = SQ_A1; sq <= SQ_H8; sq++)
|
|
castleRightsMask[sq] = ALL_CASTLES;
|
|
|
|
castleRightsMask[make_square(initialKFile, RANK_1)] ^= (WHITE_OO | WHITE_OOO);
|
|
castleRightsMask[make_square(initialKFile, RANK_8)] ^= (BLACK_OO | BLACK_OOO);
|
|
castleRightsMask[make_square(initialKRFile, RANK_1)] ^= WHITE_OO;
|
|
castleRightsMask[make_square(initialKRFile, RANK_8)] ^= BLACK_OO;
|
|
castleRightsMask[make_square(initialQRFile, RANK_1)] ^= WHITE_OOO;
|
|
castleRightsMask[make_square(initialQRFile, RANK_8)] ^= BLACK_OOO;
|
|
|
|
// En passant square
|
|
if (pos.st->epSquare != SQ_NONE)
|
|
st->epSquare = flip_square(pos.st->epSquare);
|
|
|
|
// Checkers
|
|
find_checkers();
|
|
|
|
// Hash keys
|
|
st->key = compute_key();
|
|
st->pawnKey = compute_pawn_key();
|
|
st->materialKey = compute_material_key();
|
|
|
|
// Incremental scores
|
|
st->mgValue = compute_value<MidGame>();
|
|
st->egValue = compute_value<EndGame>();
|
|
|
|
// Material
|
|
st->npMaterial[WHITE] = compute_non_pawn_material(WHITE);
|
|
st->npMaterial[BLACK] = compute_non_pawn_material(BLACK);
|
|
|
|
assert(is_ok());
|
|
}
|
|
|
|
|
|
/// Position::is_ok() performs some consitency checks for the position object.
|
|
/// This is meant to be helpful when debugging.
|
|
|
|
bool Position::is_ok(int* failedStep) const {
|
|
|
|
// What features of the position should be verified?
|
|
static const bool debugBitboards = false;
|
|
static const bool debugKingCount = false;
|
|
static const bool debugKingCapture = false;
|
|
static const bool debugCheckerCount = false;
|
|
static const bool debugKey = false;
|
|
static const bool debugMaterialKey = false;
|
|
static const bool debugPawnKey = false;
|
|
static const bool debugIncrementalEval = false;
|
|
static const bool debugNonPawnMaterial = false;
|
|
static const bool debugPieceCounts = false;
|
|
static const bool debugPieceList = false;
|
|
|
|
if (failedStep) *failedStep = 1;
|
|
|
|
// Side to move OK?
|
|
if (!color_is_ok(side_to_move()))
|
|
return false;
|
|
|
|
// Are the king squares in the position correct?
|
|
if (failedStep) (*failedStep)++;
|
|
if (piece_on(king_square(WHITE)) != WK)
|
|
return false;
|
|
|
|
if (failedStep) (*failedStep)++;
|
|
if (piece_on(king_square(BLACK)) != BK)
|
|
return false;
|
|
|
|
// Castle files OK?
|
|
if (failedStep) (*failedStep)++;
|
|
if (!file_is_ok(initialKRFile))
|
|
return false;
|
|
|
|
if (!file_is_ok(initialQRFile))
|
|
return false;
|
|
|
|
// Do both sides have exactly one king?
|
|
if (failedStep) (*failedStep)++;
|
|
if (debugKingCount)
|
|
{
|
|
int kingCount[2] = {0, 0};
|
|
for (Square s = SQ_A1; s <= SQ_H8; s++)
|
|
if (type_of_piece_on(s) == KING)
|
|
kingCount[color_of_piece_on(s)]++;
|
|
|
|
if (kingCount[0] != 1 || kingCount[1] != 1)
|
|
return false;
|
|
}
|
|
|
|
// Can the side to move capture the opponent's king?
|
|
if (failedStep) (*failedStep)++;
|
|
if (debugKingCapture)
|
|
{
|
|
Color us = side_to_move();
|
|
Color them = opposite_color(us);
|
|
Square ksq = king_square(them);
|
|
if (square_is_attacked(ksq, us))
|
|
return false;
|
|
}
|
|
|
|
// Is there more than 2 checkers?
|
|
if (failedStep) (*failedStep)++;
|
|
if (debugCheckerCount && count_1s(st->checkersBB) > 2)
|
|
return false;
|
|
|
|
// Bitboards OK?
|
|
if (failedStep) (*failedStep)++;
|
|
if (debugBitboards)
|
|
{
|
|
// The intersection of the white and black pieces must be empty
|
|
if ((pieces_of_color(WHITE) & pieces_of_color(BLACK)) != EmptyBoardBB)
|
|
return false;
|
|
|
|
// The union of the white and black pieces must be equal to all
|
|
// occupied squares
|
|
if ((pieces_of_color(WHITE) | pieces_of_color(BLACK)) != occupied_squares())
|
|
return false;
|
|
|
|
// Separate piece type bitboards must have empty intersections
|
|
for (PieceType p1 = PAWN; p1 <= KING; p1++)
|
|
for (PieceType p2 = PAWN; p2 <= KING; p2++)
|
|
if (p1 != p2 && (pieces_of_type(p1) & pieces_of_type(p2)))
|
|
return false;
|
|
}
|
|
|
|
// En passant square OK?
|
|
if (failedStep) (*failedStep)++;
|
|
if (ep_square() != SQ_NONE)
|
|
{
|
|
// The en passant square must be on rank 6, from the point of view of the
|
|
// side to move.
|
|
if (relative_rank(side_to_move(), ep_square()) != RANK_6)
|
|
return false;
|
|
}
|
|
|
|
// Hash key OK?
|
|
if (failedStep) (*failedStep)++;
|
|
if (debugKey && st->key != compute_key())
|
|
return false;
|
|
|
|
// Pawn hash key OK?
|
|
if (failedStep) (*failedStep)++;
|
|
if (debugPawnKey && st->pawnKey != compute_pawn_key())
|
|
return false;
|
|
|
|
// Material hash key OK?
|
|
if (failedStep) (*failedStep)++;
|
|
if (debugMaterialKey && st->materialKey != compute_material_key())
|
|
return false;
|
|
|
|
// Incremental eval OK?
|
|
if (failedStep) (*failedStep)++;
|
|
if (debugIncrementalEval)
|
|
{
|
|
if (st->mgValue != compute_value<MidGame>())
|
|
return false;
|
|
|
|
if (st->egValue != compute_value<EndGame>())
|
|
return false;
|
|
}
|
|
|
|
// Non-pawn material OK?
|
|
if (failedStep) (*failedStep)++;
|
|
if (debugNonPawnMaterial)
|
|
{
|
|
if (st->npMaterial[WHITE] != compute_non_pawn_material(WHITE))
|
|
return false;
|
|
|
|
if (st->npMaterial[BLACK] != compute_non_pawn_material(BLACK))
|
|
return false;
|
|
}
|
|
|
|
// Piece counts OK?
|
|
if (failedStep) (*failedStep)++;
|
|
if (debugPieceCounts)
|
|
for (Color c = WHITE; c <= BLACK; c++)
|
|
for (PieceType pt = PAWN; pt <= KING; pt++)
|
|
if (pieceCount[c][pt] != count_1s(pieces_of_color_and_type(c, pt)))
|
|
return false;
|
|
|
|
if (failedStep) (*failedStep)++;
|
|
if (debugPieceList)
|
|
{
|
|
for(Color c = WHITE; c <= BLACK; c++)
|
|
for(PieceType pt = PAWN; pt <= KING; pt++)
|
|
for(int i = 0; i < pieceCount[c][pt]; i++)
|
|
{
|
|
if (piece_on(piece_list(c, pt, i)) != piece_of_color_and_type(c, pt))
|
|
return false;
|
|
|
|
if (index[piece_list(c, pt, i)] != i)
|
|
return false;
|
|
}
|
|
}
|
|
if (failedStep) *failedStep = 0;
|
|
return true;
|
|
}
|