One thing that would be cool to consider in the future is perhaps releasing the version of SF with the tunable eval (not necessarily the eval tuner itself). I realize it's extra work for you, but it would be really fun to experiment on such a strong program.
Anyhow, congrats on another strong release .
Btw, I ported a slightly faster SEE over, which only gives boolean yes/no to Stockfish. The idea is from Strelka originally, but the code is original.
Code: Select all
// This version of SEE does not calculate the exact material imbalance, it just returns true = winning or equal, false = losing
int Position::see_sign(Move m) const {
assert(move_is_ok(m));
static const int seeValues[18] = {
0, PawnValueMidgame, BishopValueMidgame, BishopValueMidgame,
RookValueMidgame, QueenValueMidgame, QueenValueMidgame*10, 0,
0, PawnValueMidgame, BishopValueMidgame, BishopValueMidgame,
RookValueMidgame, QueenValueMidgame, QueenValueMidgame*10, 0,
0, 0
};
const Square from = move_from(m);
const Square to = move_to(m);
const Color us = (from != SQ_NONE ? color_of_piece_on(from) : opposite_color(color_of_piece_on(to)));
const Color them = opposite_color(us);
assert(piece_on(from) != NO_PIECE);
assert(color_of_piece_on(from) == us);
assert(piece_on(to) == NO_PIECE ||
(color_of_piece_on(from) != color_of_piece_on(to)) ||
move_is_castle(m));
const int fromValue = seeValues[piece_on(from)];
const int toValue = seeValues[piece_on(to)];
if (fromValue <= toValue)
{
return 1;
}
if (st->epSquare == to && type_of_piece_on(from) == PAWN)
{
// e.p. captures are always pxp which is winning or equal.
return 1;
}
if (move_is_castle(m))
{
// TODO: castles are always "good".
// We would have to change the destination square to be the final location for the rook.
return 1;
}
// We should only be making initially losing captures here
//assert(fromValue > seeValues[PAWN] || GetMoveType(move) == MoveTypePromotion || toValue == 0);
const Bitboard enemyPieces = pieces_of_color(them);
// Pawn attacks
// If any opponent pawns can capture back, this capture is probably not worthwhile (as we must be using knight or above).
if (attacks_from<PAWN>(to, us) & pieces(PAWN) & enemyPieces)
{
return -1;
}
// Knight attacks
Bitboard attackingPieces = attacks_from<KNIGHT>(to) & pieces(KNIGHT);
const int captureDeficit = fromValue - toValue;
// If any opponent knights can capture back, and the deficit we have to make up is greater than the knights value,
// it's not worth it. We can capture on this square again, and the opponent doesn't have to capture back.
if (captureDeficit > seeValues[KNIGHT] && (attackingPieces & enemyPieces))
{
return -1;
}
// Bishop attacks
Bitboard allPieces = pieces_of_color(us) | enemyPieces;
clear_bit(&allPieces, from);
attackingPieces |= bishop_attacks_bb(to, allPieces) & (pieces(BISHOP) | pieces(QUEEN));
if (captureDeficit > seeValues[BISHOP] && (attackingPieces & pieces(BISHOP) & enemyPieces))
{
return -1;
}
// Pawn defenses
// At this point, we are sure we are making a "losing" capture. The opponent can not capture back with a
// pawn. They cannot capture back with a bishop/knight and stand pat either. So, if we can capture with
// a pawn, it's got to be a winning or equal capture.
if (attacks_from<PAWN>(to, them) & pieces(PAWN) & pieces_of_color(us))
{
return 1;
}
// Rook attacks
attackingPieces |= rook_attacks_bb(to, allPieces) & (pieces(ROOK) | pieces(QUEEN));
if (captureDeficit > seeValues[ROOK] && (attackingPieces & pieces(ROOK) & enemyPieces))
{
return -1;
}
// King attacks
attackingPieces |= attacks_from<KING>(to) & pieces(KING);
// Make sure our original attacking piece is not included in the set of attacking pieces
attackingPieces &= allPieces;
if (!attackingPieces)
{
// No pieces attacking us, we have won
return 1;
}
// At this point, we have built a bitboard of all the pieces attacking the "to" square. This includes
// potential x-ray attacks from removing the "from" square piece, which we used to do the capture.
// We are currently winning the amount of material of the captured piece, time to see if the opponent
// can get it back somehow. We assume the opponent can capture our current piece in this score, which
// simplifies the later code considerably.
int seeValue = toValue - fromValue;
for (;;)
{
int capturingPieceValue = -1;
// Find the least valuable piece of the opponent that can attack the square
for (PieceType capturingPiece = KNIGHT; capturingPiece <= KING; capturingPiece++)
{
const Bitboard attacks = pieces(capturingPiece) & attackingPieces & pieces_of_color(them);
if (attacks)
{
// They can capture with a piece
capturingPieceValue = seeValues[capturingPiece];
const Square attackingSquare = first_1(attacks);
clear_bit(&attackingPieces, attackingSquare);
clear_bit(&allPieces, attackingSquare);
break;
}
}
if (capturingPieceValue == -1)
{
// The opponent can't capture back - we win
return 1;
}
// Now, if seeValue < 0, the opponent is winning. If even after we take their piece,
// we can't bring it back to 0, then we have lost this battle.
seeValue += capturingPieceValue;
if (seeValue < 0)
{
return -1;
}
// Add any x-ray attackers
attackingPieces |=
((bishop_attacks_bb(to, allPieces) & (pieces(BISHOP) | pieces(QUEEN))) |
(rook_attacks_bb(to, allPieces) & (pieces(ROOK) | pieces(QUEEN)))) & allPieces;
// Our turn to capture
capturingPieceValue = -1;
// Find the least valuable piece of the opponent that can attack the square
for (PieceType capturingPiece = KNIGHT; capturingPiece <= KING; capturingPiece++)
{
const Bitboard attacks = pieces(capturingPiece) & attackingPieces & pieces_of_color(us);
if (attacks)
{
// We can capture with a piece
capturingPieceValue = seeValues[capturingPiece];
const Square attackingSquare = first_1(attacks);
clear_bit(&attackingPieces, attackingSquare);
clear_bit(&allPieces, attackingSquare);
break;
}
}
if (capturingPieceValue == -1)
{
// We can't capture back, we lose :(
return -1;
}
// Assume our opponent can capture us back, and if we are still winning, we can stand-pat
// here, and assume we've won.
seeValue -= capturingPieceValue;
if (seeValue >= 0)
{
return 1;
}
// Add any x-ray attackers
attackingPieces |=
((bishop_attacks_bb(to, allPieces) & (pieces(BISHOP) | pieces(QUEEN))) |
(rook_attacks_bb(to, allPieces) & (pieces(ROOK) | pieces(QUEEN)))) & allPieces;
}
}
Code: Select all
#include <cassert>
void CheckSee(const std::string &fen, const std::string &move, int expected)
{
Position position(fen);
Move captureMove = move_from_string(position, move);
assert(move_is_ok(captureMove));
assert(position.see_sign(captureMove) == expected);
Position flipped;
flipped.flipped_copy(position);
std::string flippedMove(move);
flippedMove[1] = '1' + ('8' - flippedMove[1]);
flippedMove[3] = '1' + ('8' - flippedMove[3]);
captureMove = move_from_string(flipped, flippedMove);
assert(move_is_ok(captureMove));
assert(flipped.see_sign(captureMove) == expected);
}
void SeeTests()
{
// Winning pawn capture on rook
CheckSee("2r3k1/2r4p/1PNqb1p1/3p1p2/4p3/2Q1P2P/5PP1/1R4K1 w - - 0 37", "b6c7", 1);
// Winning rook/queen capture on pawn
CheckSee("2r3k1/2P4p/2Nqb1p1/3p1p2/4p3/2Q1P2P/5PP1/1R4K1 b - - 0 37", "c8c7", 1);
CheckSee("2r3k1/2P4p/2Nqb1p1/3p1p2/4p3/2Q1P2P/5PP1/1R4K1 b - - 0 37", "d6c7", 1);
// Winning rook/queen capture on knight
CheckSee("6k1/2r4p/2Nqb1p1/3p1p2/4p3/2Q1P2P/5PP1/1R4K1 b - - 0 38", "c7c6", 1);
CheckSee("6k1/2r4p/2Nqb1p1/3p1p2/4p3/2Q1P2P/5PP1/1R4K1 b - - 0 38", "d6c6", 1);
// Losing rook/queen capture on knight (revealed rook attack)
CheckSee("6k1/2r4p/2Nqb1p1/3p1p2/4p3/2Q1P2P/5PP1/2R3K1 b - - 0 38", "c7c6", -1);
CheckSee("6k1/2r4p/2Nqb1p1/3p1p2/4p3/2Q1P2P/5PP1/2R3K1 b - - 0 38", "d6c6", -1);
// Winning rook/queen capture on knight (revealed bishop attack)
CheckSee("4b1k1/2rq3p/2N3p1/3p1p2/4p3/2Q1P2P/5PP1/2R3K1 b - - 0 38", "c7c6", 1);
CheckSee("4b1k1/2rq3p/2N3p1/3p1p2/4p3/2Q1P2P/5PP1/2R3K1 b - - 0 38", "d7c6", 1);
// Winning pawn capture on pawn
CheckSee("2r3k1/2pq3p/3P2p1/b4p2/4p3/2R1P2P/5PP1/2R3K1 w - - 0 38", "d6c7", 1);
// Losing rook capture on pawn
CheckSee("2r3k1/2pq3p/3P2p1/b4p2/4p3/2R1P2P/5PP1/2R3K1 w - - 0 38", "c3c7", -1);
// Losing queen capture on rook
CheckSee("2r3k1/2p4p/3P2p1/q4p2/4p3/2R1P2P/5PP1/2R3K1 b - - 0 38", "a5c3", -1);
// Losing rook capture on pawn
CheckSee("1br3k1/2p4p/3P2p1/q4p2/4p3/2R1P2P/5PP1/2R3K1 w - - 0 38", "c3c7", -1);
// Winning Q promotion (non-capture)
CheckSee("4rrk1/2P4p/6p1/5p2/4p3/2R1P2P/5PP1/2R3K1 w - - 0 38", "c7c8q", 1);
// Losing Q promotion (non-capture)
CheckSee("r3rrk1/2P4p/6p1/5p2/4p3/2R1P2P/5PP1/2R3K1 w - - 0 38", "c7c8q", -1);
// Knight capturing pawn defended by pawn
CheckSee("K7/8/2p5/3p4/8/4N3/8/7k w - - 0 1", "e3d5", -1);
// Knight capturing undefended pawn
CheckSee("K7/8/8/3p4/8/4N3/8/7k w - - 0 1", "e3d5", 1);
// Rook capturing pawn defended by knight
CheckSee("K7/4n3/8/3p4/8/3R4/3R4/7k w - - 0 1", "d3d5", -1);
// Rook capturing pawn defended by bishop
CheckSee("K7/5b2/8/3p4/8/3R4/3R4/7k w - - 0 1", "d3d5", -1);
// Rook capturing knight defended by bishop
CheckSee("K7/5b2/8/3n4/8/3R4/3R4/7k w - - 0 1", "d3d5", 1);
// Rook capturing rook defended by bishop
CheckSee("K7/5b2/8/3r4/8/3R4/3R4/7k w - - 0 1", "d3d5", 1);
}