mirror of
https://github.com/HChaZZY/Stockfish.git
synced 2025-12-18 08:07:08 +08:00
Compare commits
44 Commits
sf_1.1a
...
sf_1.2_opt
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc3c1dc25a | ||
|
|
11763d2b7f | ||
|
|
d99a95df29 | ||
|
|
0da1d6a846 | ||
|
|
5d94305af3 | ||
|
|
e4fd9a2df7 | ||
|
|
aedc6c6f1f | ||
|
|
dae4e7df07 | ||
|
|
96a32eec69 | ||
|
|
e46d3670fd | ||
|
|
8cd5cb930d | ||
|
|
2feb9d5100 | ||
|
|
d11426c777 | ||
|
|
e96f56adfa | ||
|
|
e3b03f13b3 | ||
|
|
54b7da120f | ||
|
|
3fafc9768a | ||
|
|
23490bd825 | ||
|
|
61c6a3d5a0 | ||
|
|
31d4f0b734 | ||
|
|
f178f0a291 | ||
|
|
72ca727b38 | ||
|
|
2d0146fe1d | ||
|
|
96d0501735 | ||
|
|
b58ad355ca | ||
|
|
17000d1ea0 | ||
|
|
b09cbaebb9 | ||
|
|
725c504a5f | ||
|
|
bfbfc24d07 | ||
|
|
a55b06d3c9 | ||
|
|
ecc19381b4 | ||
|
|
dae2f600d6 | ||
|
|
4c294932e7 | ||
|
|
c831b00544 | ||
|
|
5b853c9be6 | ||
|
|
5f8f83bc05 | ||
|
|
8ee3124487 | ||
|
|
07b45151d2 | ||
|
|
9e3ab9099f | ||
|
|
f09884d72f | ||
|
|
ab29d8df67 | ||
|
|
389dc0e83b | ||
|
|
67aac4889e | ||
|
|
aaad48464b |
@@ -250,8 +250,8 @@ Bitboard BMask[64];
|
||||
int BAttackIndex[64];
|
||||
Bitboard BAttacks[0x1480];
|
||||
|
||||
Bitboard SetMaskBB[64];
|
||||
Bitboard ClearMaskBB[64];
|
||||
Bitboard SetMaskBB[65];
|
||||
Bitboard ClearMaskBB[65];
|
||||
|
||||
Bitboard StepAttackBB[16][64];
|
||||
Bitboard RayBB[64][8];
|
||||
@@ -433,6 +433,8 @@ namespace {
|
||||
// be necessary to touch any of them.
|
||||
|
||||
void init_masks() {
|
||||
SetMaskBB[SQ_NONE] = 0ULL;
|
||||
ClearMaskBB[SQ_NONE] = ~SetMaskBB[SQ_NONE];
|
||||
for(Square s = SQ_A1; s <= SQ_H8; s++) {
|
||||
SetMaskBB[s] = (1ULL << s);
|
||||
ClearMaskBB[s] = ~SetMaskBB[s];
|
||||
|
||||
@@ -113,8 +113,8 @@ extern const Bitboard RankBB[8];
|
||||
extern const Bitboard RelativeRankBB[2][8];
|
||||
extern const Bitboard InFrontBB[2][8];
|
||||
|
||||
extern Bitboard SetMaskBB[64];
|
||||
extern Bitboard ClearMaskBB[64];
|
||||
extern Bitboard SetMaskBB[65];
|
||||
extern Bitboard ClearMaskBB[65];
|
||||
|
||||
extern Bitboard StepAttackBB[16][64];
|
||||
extern Bitboard RayBB[64][8];
|
||||
@@ -236,6 +236,19 @@ inline Bitboard in_front_bb(Color c, Square s) {
|
||||
}
|
||||
|
||||
|
||||
/// behind_bb() takes a color and a rank or square as input, and returns a
|
||||
/// bitboard representing all the squares on all ranks behind of the rank
|
||||
/// (or square), from the given color's point of view.
|
||||
|
||||
inline Bitboard behind_bb(Color c, Rank r) {
|
||||
return InFrontBB[opposite_color(c)][r];
|
||||
}
|
||||
|
||||
inline Bitboard behind_bb(Color c, Square s) {
|
||||
return in_front_bb(opposite_color(c), square_rank(s));
|
||||
}
|
||||
|
||||
|
||||
/// ray_bb() gives a bitboard representing all squares along the ray in a
|
||||
/// given direction from a given square.
|
||||
|
||||
|
||||
@@ -62,6 +62,13 @@ KRKNEvaluationFunction EvaluateKNKR = KRKNEvaluationFunction(BLACK);
|
||||
KQKREvaluationFunction EvaluateKQKR = KQKREvaluationFunction(WHITE);
|
||||
KQKREvaluationFunction EvaluateKRKQ = KQKREvaluationFunction(BLACK);
|
||||
|
||||
// KBB vs KN:
|
||||
KBBKNEvaluationFunction EvaluateKBBKN = KBBKNEvaluationFunction(WHITE);
|
||||
KBBKNEvaluationFunction EvaluateKNKBB = KBBKNEvaluationFunction(BLACK);
|
||||
|
||||
// K and two minors vs K and one or two minors:
|
||||
KmmKmEvaluationFunction EvaluateKmmKm = KmmKmEvaluationFunction(WHITE);
|
||||
|
||||
|
||||
/// Scaling functions
|
||||
|
||||
@@ -187,6 +194,8 @@ KRKPEvaluationFunction::KRKPEvaluationFunction(Color c) : EndgameEvaluationFunct
|
||||
KRKBEvaluationFunction::KRKBEvaluationFunction(Color c) : EndgameEvaluationFunction(c) { }
|
||||
KRKNEvaluationFunction::KRKNEvaluationFunction(Color c) : EndgameEvaluationFunction(c) { }
|
||||
KQKREvaluationFunction::KQKREvaluationFunction(Color c) : EndgameEvaluationFunction(c) { }
|
||||
KBBKNEvaluationFunction::KBBKNEvaluationFunction(Color c) : EndgameEvaluationFunction(c) { }
|
||||
KmmKmEvaluationFunction::KmmKmEvaluationFunction(Color c) : EndgameEvaluationFunction(c) { }
|
||||
|
||||
|
||||
ScalingFunction::ScalingFunction(Color c) {
|
||||
@@ -420,6 +429,36 @@ Value KQKREvaluationFunction::apply(const Position &pos) {
|
||||
}
|
||||
|
||||
|
||||
Value KBBKNEvaluationFunction::apply(const Position &pos) {
|
||||
assert(pos.piece_count(strongerSide, BISHOP) == 2);
|
||||
assert(pos.non_pawn_material(strongerSide) == 2*BishopValueMidgame);
|
||||
assert(pos.piece_count(weakerSide, KNIGHT) == 1);
|
||||
assert(pos.non_pawn_material(weakerSide) == KnightValueMidgame);
|
||||
assert(pos.pawns() == EmptyBoardBB);
|
||||
|
||||
Value result = BishopValueEndgame;
|
||||
Square wksq = pos.king_square(strongerSide);
|
||||
Square bksq = pos.king_square(weakerSide);
|
||||
Square nsq = pos.piece_list(weakerSide, KNIGHT, 0);
|
||||
|
||||
// Bonus for attacking king close to defending king
|
||||
result += distance_bonus(square_distance(wksq, bksq));
|
||||
|
||||
// Bonus for driving the defending king and knight apart
|
||||
result += Value(square_distance(bksq, nsq) * 32);
|
||||
|
||||
// Bonus for restricting the knight's mobility
|
||||
result += Value((8 - count_1s_max_15(pos.piece_attacks<KNIGHT>(nsq))) * 8);
|
||||
|
||||
return (strongerSide == pos.side_to_move())? result : -result;
|
||||
}
|
||||
|
||||
|
||||
Value KmmKmEvaluationFunction::apply(const Position &pos) {
|
||||
return Value(0);
|
||||
}
|
||||
|
||||
|
||||
/// KBPKScalingFunction scales endgames where the stronger side has king,
|
||||
/// bishop and one or more pawns. It checks for draws with rook pawns and a
|
||||
/// bishop of the wrong color. If such a draw is detected, ScaleFactor(0) is
|
||||
@@ -604,6 +643,16 @@ ScaleFactor KRPKRScalingFunction::apply(const Position &pos) {
|
||||
- (8 * square_distance(wpsq, queeningSq) +
|
||||
2 * square_distance(wksq, queeningSq)));
|
||||
|
||||
// If the pawn is not far advanced, and the defending king is somewhere in
|
||||
// the pawn's path, it's probably a draw:
|
||||
if(r <= RANK_4 && bksq > wpsq) {
|
||||
if(square_file(bksq) == square_file(wpsq))
|
||||
return ScaleFactor(10);
|
||||
if(abs(square_file(bksq) - square_file(wpsq)) == 1
|
||||
&& square_distance(wksq, bksq) > 2)
|
||||
return ScaleFactor(24 - 2 * square_distance(wksq, bksq));
|
||||
}
|
||||
|
||||
return SCALE_FACTOR_NONE;
|
||||
}
|
||||
|
||||
|
||||
@@ -99,6 +99,20 @@ public:
|
||||
Value apply(const Position &pos);
|
||||
};
|
||||
|
||||
// KBB vs KN:
|
||||
class KBBKNEvaluationFunction : public EndgameEvaluationFunction {
|
||||
public:
|
||||
KBBKNEvaluationFunction(Color C);
|
||||
Value apply(const Position &pos);
|
||||
};
|
||||
|
||||
// K and two minors vs K and one or two minors:
|
||||
class KmmKmEvaluationFunction : public EndgameEvaluationFunction {
|
||||
public:
|
||||
KmmKmEvaluationFunction(Color c);
|
||||
Value apply(const Position &pos);
|
||||
};
|
||||
|
||||
|
||||
/// Abstract base class for all evaluation scaling functions:
|
||||
|
||||
@@ -205,6 +219,12 @@ extern KRKNEvaluationFunction EvaluateKRKN, EvaluateKNKR;
|
||||
// KQ vs KR:
|
||||
extern KQKREvaluationFunction EvaluateKQKR, EvaluateKRKQ;
|
||||
|
||||
// KBB vs KN:
|
||||
extern KBBKNEvaluationFunction EvaluateKBBKN, EvaluateKNKBB;
|
||||
|
||||
// K and two minors vs K and one or two minors:
|
||||
extern KmmKmEvaluationFunction EvaluateKmmKm;
|
||||
|
||||
// KBP vs K:
|
||||
extern KBPKScalingFunction ScaleKBPK, ScaleKKBP;
|
||||
|
||||
|
||||
117
src/evaluate.cpp
117
src/evaluate.cpp
@@ -52,6 +52,7 @@ namespace {
|
||||
int WeightPassedPawnsMidgame = 0x100;
|
||||
int WeightPassedPawnsEndgame = 0x100;
|
||||
int WeightKingSafety[2] = { 0x100, 0x100 };
|
||||
int WeightSpace;
|
||||
|
||||
// Internal evaluation weights. These are applied on top of the evaluation
|
||||
// weights read from UCI parameters. The purpose is to be able to change
|
||||
@@ -63,8 +64,9 @@ namespace {
|
||||
const int WeightPawnStructureEndgameInternal = 0x100;
|
||||
const int WeightPassedPawnsMidgameInternal = 0x100;
|
||||
const int WeightPassedPawnsEndgameInternal = 0x100;
|
||||
const int WeightKingSafetyInternal = 0x100;
|
||||
const int WeightKingOppSafetyInternal = 0x100;
|
||||
const int WeightKingSafetyInternal = 0x110;
|
||||
const int WeightKingOppSafetyInternal = 0x110;
|
||||
const int WeightSpaceInternal = 0x30;
|
||||
|
||||
// Visually better to define tables constants
|
||||
typedef Value V;
|
||||
@@ -203,6 +205,19 @@ namespace {
|
||||
((1ULL << SQ_A8) | (1ULL << SQ_H8))
|
||||
};
|
||||
|
||||
// The SpaceMask[color] contains area of the board which is consdered by
|
||||
// the space evaluation. In the middle game, each side is given a bonus
|
||||
// based on how many squares inside this area are safe and available for
|
||||
// friendly minor pieces.
|
||||
const Bitboard SpaceMask[2] = {
|
||||
(1ULL<<SQ_C2) | (1ULL<<SQ_D2) | (1ULL<<SQ_E2) | (1ULL<<SQ_F2) |
|
||||
(1ULL<<SQ_C3) | (1ULL<<SQ_D3) | (1ULL<<SQ_E3) | (1ULL<<SQ_F3) |
|
||||
(1ULL<<SQ_C4) | (1ULL<<SQ_D4) | (1ULL<<SQ_E4) | (1ULL<<SQ_F4),
|
||||
(1ULL<<SQ_C7) | (1ULL<<SQ_D7) | (1ULL<<SQ_E7) | (1ULL<<SQ_F7) |
|
||||
(1ULL<<SQ_C6) | (1ULL<<SQ_D6) | (1ULL<<SQ_E6) | (1ULL<<SQ_F6) |
|
||||
(1ULL<<SQ_C5) | (1ULL<<SQ_D5) | (1ULL<<SQ_E5) | (1ULL<<SQ_F5)
|
||||
};
|
||||
|
||||
/// King safety constants and variables. The king safety scores are taken
|
||||
/// from the array SafetyTable[]. Various little "meta-bonuses" measuring
|
||||
/// the strength of the attack are added up into an integer, which is used
|
||||
@@ -215,8 +230,7 @@ namespace {
|
||||
const int KnightAttackWeight = 2;
|
||||
|
||||
// Bonuses for safe checks for each piece type.
|
||||
int QueenContactCheckBonus = 4;
|
||||
int RookContactCheckBonus = 2;
|
||||
int QueenContactCheckBonus = 3;
|
||||
int QueenCheckBonus = 2;
|
||||
int RookCheckBonus = 1;
|
||||
int BishopCheckBonus = 1;
|
||||
@@ -246,18 +260,18 @@ namespace {
|
||||
// in init_safety().
|
||||
Value SafetyTable[100];
|
||||
|
||||
// Pawn and material hash tables, indexed by the current thread id:
|
||||
// Pawn and material hash tables, indexed by the current thread id
|
||||
PawnInfoTable *PawnTable[8] = {0, 0, 0, 0, 0, 0, 0, 0};
|
||||
MaterialInfoTable *MaterialTable[8] = {0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
// Sizes of pawn and material hash tables:
|
||||
// Sizes of pawn and material hash tables
|
||||
const int PawnTableSize = 16384;
|
||||
const int MaterialTableSize = 1024;
|
||||
|
||||
// Array which gives the number of nonzero bits in an 8-bit integer:
|
||||
uint8_t BitCount8Bit[256];
|
||||
|
||||
// Function prototypes:
|
||||
// Function prototypes
|
||||
void evaluate_knight(const Position &p, Square s, Color us, EvalInfo &ei);
|
||||
void evaluate_bishop(const Position &p, Square s, Color us, EvalInfo &ei);
|
||||
void evaluate_rook(const Position &p, Square s, Color us, EvalInfo &ei);
|
||||
@@ -270,6 +284,7 @@ namespace {
|
||||
void evaluate_trapped_bishop_a1h1(const Position &pos, Square s, Color us,
|
||||
EvalInfo &ei);
|
||||
|
||||
void evaluate_space(const Position &p, Color us, EvalInfo &ei);
|
||||
inline Value apply_weight(Value v, int w);
|
||||
Value scale_by_game_phase(Value mv, Value ev, Phase ph, const ScaleFactor sf[]);
|
||||
|
||||
@@ -407,6 +422,13 @@ Value evaluate(const Position &pos, EvalInfo &ei, int threadID) {
|
||||
|
||||
ei.mgValue += ei.pi->kingside_storm_value(WHITE)
|
||||
- ei.pi->queenside_storm_value(BLACK);
|
||||
|
||||
// Evaluate space for both sides
|
||||
if (ei.mi->space_weight() > 0)
|
||||
{
|
||||
evaluate_space(pos, WHITE, ei);
|
||||
evaluate_space(pos, BLACK, ei);
|
||||
}
|
||||
}
|
||||
|
||||
// Mobility
|
||||
@@ -414,7 +436,7 @@ Value evaluate(const Position &pos, EvalInfo &ei, int threadID) {
|
||||
ei.egValue += apply_weight(ei.egMobility, WeightMobilityEndgame);
|
||||
|
||||
// If we don't already have an unusual scale factor, check for opposite
|
||||
// colored bishop endgames, and use a lower scale for those:
|
||||
// colored bishop endgames, and use a lower scale for those
|
||||
if ( phase < PHASE_MIDGAME
|
||||
&& pos.opposite_colored_bishops()
|
||||
&& ( (factor[WHITE] == SCALE_FACTOR_NORMAL && ei.egValue > Value(0))
|
||||
@@ -527,6 +549,7 @@ void read_weights(Color us) {
|
||||
|
||||
WeightKingSafety[us] = weight_option("Cowardice", WeightKingSafetyInternal);
|
||||
WeightKingSafety[them] = weight_option("Aggressiveness", WeightKingOppSafetyInternal);
|
||||
WeightSpace = weight_option("Space", WeightSpaceInternal);
|
||||
|
||||
init_safety();
|
||||
}
|
||||
@@ -613,7 +636,6 @@ namespace {
|
||||
|
||||
void evaluate_rook(const Position &p, Square s, Color us, EvalInfo &ei) {
|
||||
|
||||
//Bitboard b = p.rook_attacks(s);
|
||||
Bitboard b = rook_attacks_bb(s, p.occupied_squares() & ~p.rooks_and_queens(us));
|
||||
ei.attackedBy[us][ROOK] |= b;
|
||||
|
||||
@@ -744,14 +766,14 @@ namespace {
|
||||
Bitboard occ = p.occupied_squares(), b, b2;
|
||||
|
||||
// Initialize the 'attackUnits' variable, which is used later on as an
|
||||
// index to the SafetyTable[] array. The initial is based on the number
|
||||
// and types of the attacking pieces, the number of attacked and
|
||||
// index to the SafetyTable[] array. The initial value is based on the
|
||||
// number and types of the attacking pieces, the number of attacked and
|
||||
// undefended squares around the king, the square of the king, and the
|
||||
// quality of the pawn shelter.
|
||||
int attackUnits =
|
||||
Min((ei.kingAttackersCount[them] * ei.kingAttackersWeight[them]) / 2, 25)
|
||||
+ (ei.kingAdjacentZoneAttacksCount[them] + count_1s_max_15(undefended)) * 3
|
||||
+ InitKingDanger[relative_square(us, s)] - shelter / 32;
|
||||
+ InitKingDanger[relative_square(us, s)] - (shelter >> 5);
|
||||
|
||||
// Analyse safe queen contact checks
|
||||
b = undefended & ei.attacked_by(them, QUEEN) & ~p.pieces_of_color(them);
|
||||
@@ -797,25 +819,8 @@ namespace {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Analyse safe rook contact checks:
|
||||
if (RookContactCheckBonus)
|
||||
{
|
||||
b = undefended & ei.attacked_by(them, ROOK) & ~p.pieces_of_color(them);
|
||||
if (b)
|
||||
{
|
||||
Bitboard attackedByOthers =
|
||||
ei.attacked_by(them, PAWN) | ei.attacked_by(them, KNIGHT)
|
||||
| ei.attacked_by(them, BISHOP) | ei.attacked_by(them, QUEEN);
|
||||
|
||||
b &= attackedByOthers;
|
||||
if (b)
|
||||
{
|
||||
int count = count_1s_max_15(b);
|
||||
attackUnits += (RookContactCheckBonus * count * (sente? 2 : 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Analyse safe distance checks:
|
||||
// Analyse safe distance checks
|
||||
if (QueenCheckBonus > 0 || RookCheckBonus > 0)
|
||||
{
|
||||
b = p.piece_attacks<ROOK>(s) & ~p.pieces_of_color(them) & ~ei.attacked_by(us);
|
||||
@@ -931,6 +936,13 @@ namespace {
|
||||
b2 = squares_in_front_of(us, s);
|
||||
b3 = b2 & ei.attacked_by(them);
|
||||
b4 = b2 & ei.attacked_by(us);
|
||||
|
||||
// If there is an enemy rook or queen attacking the pawn from behind,
|
||||
// add all X-ray attacks by the rook or queen.
|
||||
if(bit_is_set(ei.attacked_by(them,ROOK)|ei.attacked_by(them,QUEEN),s)
|
||||
&& squares_behind(us, s) & pos.rooks_and_queens(them))
|
||||
b3 = b2;
|
||||
|
||||
if((b2 & pos.pieces_of_color(them)) == EmptyBoardBB) {
|
||||
// There are no enemy pieces in the pawn's path! Are any of the
|
||||
// squares in the pawn's path attacked by the enemy?
|
||||
@@ -1101,14 +1113,54 @@ namespace {
|
||||
}
|
||||
|
||||
|
||||
// apply_weight applies an evaluation weight to a value.
|
||||
// evaluate_space() computes the space evaluation for a given side. The
|
||||
// space evaluation is a simple bonus based on the number of safe squares
|
||||
// available for minor pieces on the central four files on ranks 2--4. Safe
|
||||
// squares one, two or three squares behind a friendly pawn are counted
|
||||
// twice. Finally, the space bonus is scaled by a weight taken from the
|
||||
// material hash table.
|
||||
|
||||
void evaluate_space(const Position &pos, Color us, EvalInfo &ei) {
|
||||
|
||||
Color them = opposite_color(us);
|
||||
|
||||
// Find the safe squares for our pieces inside the area defined by
|
||||
// SpaceMask[us]. A square is unsafe it is attacked by an enemy
|
||||
// pawn, or if it is undefended and attacked by an enemy piece.
|
||||
|
||||
Bitboard safeSquares =
|
||||
SpaceMask[us] & ~pos.pawns(us) & ~ei.attacked_by(them, PAWN)
|
||||
& ~(~ei.attacked_by(us) & ei.attacked_by(them));
|
||||
|
||||
// Find all squares which are at most three squares behind some friendly
|
||||
// pawn.
|
||||
Bitboard behindFriendlyPawns = pos.pawns(us);
|
||||
if(us == WHITE) {
|
||||
behindFriendlyPawns |= (behindFriendlyPawns >> 8);
|
||||
behindFriendlyPawns |= (behindFriendlyPawns >> 16);
|
||||
}
|
||||
else {
|
||||
behindFriendlyPawns |= (behindFriendlyPawns << 8);
|
||||
behindFriendlyPawns |= (behindFriendlyPawns << 16);
|
||||
}
|
||||
|
||||
int space =
|
||||
count_1s_max_15(safeSquares)
|
||||
+ count_1s_max_15(behindFriendlyPawns & safeSquares);
|
||||
|
||||
ei.mgValue += Sign[us] *
|
||||
apply_weight(Value(space * ei.mi->space_weight()), WeightSpace);
|
||||
}
|
||||
|
||||
|
||||
// apply_weight() applies an evaluation weight to a value
|
||||
|
||||
inline Value apply_weight(Value v, int w) {
|
||||
return (v*w) / 0x100;
|
||||
}
|
||||
|
||||
|
||||
// scale_by_game_phase interpolates between a middle game and an endgame
|
||||
// scale_by_game_phase() interpolates between a middle game and an endgame
|
||||
// score, based on game phase. It also scales the return value by a
|
||||
// ScaleFactor array.
|
||||
|
||||
@@ -1156,7 +1208,6 @@ namespace {
|
||||
void init_safety() {
|
||||
|
||||
QueenContactCheckBonus = get_option_value_int("Queen Contact Check Bonus");
|
||||
RookContactCheckBonus = get_option_value_int("Rook Contact Check Bonus");
|
||||
QueenCheckBonus = get_option_value_int("Queen Check Bonus");
|
||||
RookCheckBonus = get_option_value_int("Rook Check Bonus");
|
||||
BishopCheckBonus = get_option_value_int("Bishop Check Bonus");
|
||||
|
||||
@@ -161,6 +161,22 @@ MaterialInfo* MaterialInfoTable::get_material_info(const Position& pos) {
|
||||
mi->evaluationFunction = &EvaluateKKX;
|
||||
return mi;
|
||||
}
|
||||
else if ( pos.pawns() == EmptyBoardBB
|
||||
&& pos.rooks() == EmptyBoardBB
|
||||
&& pos.queens() == EmptyBoardBB)
|
||||
{
|
||||
// Minor piece endgame with at least one minor piece per side,
|
||||
// and no pawns.
|
||||
assert(pos.knights(WHITE) | pos.bishops(WHITE));
|
||||
assert(pos.knights(BLACK) | pos.bishops(BLACK));
|
||||
|
||||
if ( pos.piece_count(WHITE, BISHOP) + pos.piece_count(WHITE, KNIGHT) <= 2
|
||||
&& pos.piece_count(BLACK, BISHOP) + pos.piece_count(BLACK, KNIGHT) <= 2)
|
||||
{
|
||||
mi->evaluationFunction = &EvaluateKmmKm;
|
||||
return mi;
|
||||
}
|
||||
}
|
||||
|
||||
// OK, we didn't find any special evaluation function for the current
|
||||
// material configuration. Is there a suitable scaling function?
|
||||
@@ -221,6 +237,18 @@ MaterialInfo* MaterialInfoTable::get_material_info(const Position& pos) {
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the space weight
|
||||
if (pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK) >=
|
||||
2*QueenValueMidgame + 4*RookValueMidgame + 2*KnightValueMidgame)
|
||||
{
|
||||
int minorPieceCount = pos.piece_count(WHITE, KNIGHT)
|
||||
+ pos.piece_count(BLACK, KNIGHT)
|
||||
+ pos.piece_count(WHITE, BISHOP)
|
||||
+ pos.piece_count(BLACK, BISHOP);
|
||||
|
||||
mi->spaceWeight = minorPieceCount * minorPieceCount;
|
||||
}
|
||||
|
||||
// Evaluate the material balance
|
||||
|
||||
int sign;
|
||||
@@ -309,6 +337,8 @@ EndgameFunctions::EndgameFunctions() {
|
||||
add(z[W][KNIGHT][1] ^ z[B][ROOK][1], &EvaluateKNKR);
|
||||
add(z[W][QUEEN][1] ^ z[B][ROOK][1], &EvaluateKQKR);
|
||||
add(z[W][ROOK][1] ^ z[B][QUEEN][1], &EvaluateKRKQ);
|
||||
add(z[W][BISHOP][2] ^ z[B][KNIGHT][1], &EvaluateKBBKN);
|
||||
add(z[W][KNIGHT][1] ^ z[B][BISHOP][2], &EvaluateKNKBB);
|
||||
|
||||
add(z[W][KNIGHT][1] ^ z[W][PAWN][1], W, &ScaleKNPK);
|
||||
add(z[B][KNIGHT][1] ^ z[B][PAWN][1], B, &ScaleKKNP);
|
||||
|
||||
@@ -52,6 +52,7 @@ public:
|
||||
Value mg_value() const;
|
||||
Value eg_value() const;
|
||||
ScaleFactor scale_factor(const Position& pos, Color c) const;
|
||||
int space_weight() const;
|
||||
bool specialized_eval_exists() const;
|
||||
Value evaluate(const Position& pos) const;
|
||||
|
||||
@@ -64,6 +65,7 @@ private:
|
||||
uint8_t factor[2];
|
||||
EndgameEvaluationFunction* evaluationFunction;
|
||||
ScalingFunction* scalingFunction[2];
|
||||
uint8_t spaceWeight;
|
||||
};
|
||||
|
||||
|
||||
@@ -120,6 +122,7 @@ inline void MaterialInfo::clear() {
|
||||
|
||||
mgValue = egValue = 0;
|
||||
factor[WHITE] = factor[BLACK] = uint8_t(SCALE_FACTOR_NORMAL);
|
||||
spaceWeight = 0;
|
||||
evaluationFunction = NULL;
|
||||
scalingFunction[WHITE] = scalingFunction[BLACK] = NULL;
|
||||
}
|
||||
@@ -144,6 +147,15 @@ inline ScaleFactor MaterialInfo::scale_factor(const Position& pos, Color c) cons
|
||||
}
|
||||
|
||||
|
||||
/// MaterialInfo::space_weight() simply returns the weight for the space
|
||||
/// evaluation for this material configuration.
|
||||
|
||||
inline int MaterialInfo::space_weight() const {
|
||||
|
||||
return spaceWeight;
|
||||
}
|
||||
|
||||
|
||||
/// MaterialInfo::specialized_eval_exists decides whether there is a
|
||||
/// specialized evaluation function for the current material configuration,
|
||||
/// or if the normal evaluation function should be used.
|
||||
|
||||
57
src/misc.cpp
57
src/misc.cpp
@@ -37,6 +37,7 @@ int gettimeofday(struct timeval * tp, struct timezone * tzp);
|
||||
|
||||
#endif
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
@@ -49,6 +50,9 @@ int gettimeofday(struct timeval * tp, struct timezone * tzp);
|
||||
//// Variables
|
||||
////
|
||||
|
||||
static const std::string AppName = "Stockfish";
|
||||
static const std::string AppTag = "";
|
||||
|
||||
long dbg_cnt0 = 0;
|
||||
long dbg_cnt1 = 0;
|
||||
|
||||
@@ -60,6 +64,42 @@ bool dbg_show_hit_rate = false;
|
||||
//// Functions
|
||||
////
|
||||
|
||||
void dbg_hit_on(bool b) {
|
||||
|
||||
assert(!dbg_show_mean);
|
||||
dbg_show_hit_rate = true;
|
||||
dbg_cnt0++;
|
||||
if (b)
|
||||
dbg_cnt1++;
|
||||
}
|
||||
|
||||
void dbg_hit_on_c(bool c, bool b) {
|
||||
|
||||
if (c)
|
||||
dbg_hit_on(b);
|
||||
}
|
||||
|
||||
void dbg_before() {
|
||||
|
||||
assert(!dbg_show_mean);
|
||||
dbg_show_hit_rate = true;
|
||||
dbg_cnt0++;
|
||||
}
|
||||
|
||||
void dbg_after() {
|
||||
|
||||
assert(!dbg_show_mean);
|
||||
dbg_show_hit_rate = true;
|
||||
dbg_cnt1++;
|
||||
}
|
||||
|
||||
void dbg_mean_of(int v) {
|
||||
|
||||
assert(!dbg_show_hit_rate);
|
||||
dbg_cnt0++;
|
||||
dbg_cnt1 += v;
|
||||
}
|
||||
|
||||
void dbg_print_hit_rate() {
|
||||
|
||||
std::cout << "Total " << dbg_cnt0 << " Hit " << dbg_cnt1
|
||||
@@ -73,6 +113,19 @@ void dbg_print_mean() {
|
||||
<< (float)dbg_cnt1 / (dbg_cnt0 ? dbg_cnt0 : 1) << std::endl;
|
||||
}
|
||||
|
||||
void dbg_print_hit_rate(std::ofstream& logFile) {
|
||||
|
||||
logFile << "Total " << dbg_cnt0 << " Hit " << dbg_cnt1
|
||||
<< " hit rate (%) " << (dbg_cnt1*100)/(dbg_cnt0 ? dbg_cnt0 : 1)
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
void dbg_print_mean(std::ofstream& logFile) {
|
||||
|
||||
logFile << "Total " << dbg_cnt0 << " Mean "
|
||||
<< (float)dbg_cnt1 / (dbg_cnt0 ? dbg_cnt0 : 1) << std::endl;
|
||||
}
|
||||
|
||||
/// engine_name() returns the full name of the current Stockfish version.
|
||||
/// This will be either "Stockfish YYMMDD" (where YYMMDD is the date when the
|
||||
/// program was compiled) or "Stockfish <version number>", depending on whether
|
||||
@@ -90,7 +143,9 @@ const std::string engine_name() {
|
||||
std::stringstream s;
|
||||
std::string day = (date[4] == ' ' ? date.substr(5, 1) : date.substr(4, 2));
|
||||
|
||||
s << "Stockfish " << date.substr(date.length() - 2) << std::setfill('0')
|
||||
std::string name = AppName + " " + AppTag + " ";
|
||||
|
||||
s << name << date.substr(date.length() - 2) << std::setfill('0')
|
||||
<< std::setw(2) << mon << std::setw(2) << day;
|
||||
|
||||
return s.str();
|
||||
|
||||
19
src/misc.h
19
src/misc.h
@@ -26,6 +26,7 @@
|
||||
//// Includes
|
||||
////
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
|
||||
@@ -37,7 +38,7 @@
|
||||
/// Version number. If this is left empty, the current date (in the format
|
||||
/// YYMMDD) is used as a version number.
|
||||
|
||||
const std::string EngineVersion = "1.1a";
|
||||
const std::string EngineVersion = "1.2";
|
||||
|
||||
|
||||
////
|
||||
@@ -60,21 +61,21 @@ extern int Bioskey();
|
||||
////
|
||||
//// Debug
|
||||
////
|
||||
|
||||
extern bool dbg_show_mean;
|
||||
extern bool dbg_show_hit_rate;
|
||||
|
||||
extern long dbg_cnt0;
|
||||
extern long dbg_cnt1;
|
||||
|
||||
inline void dbg_hit_on(bool b) { dbg_show_hit_rate = true; dbg_cnt0++; if (b) dbg_cnt1++; }
|
||||
inline void dbg_hit_on_c(bool c, bool b) { if (c) dbg_hit_on(b); }
|
||||
|
||||
inline void dbg_before() { dbg_show_hit_rate = true; dbg_cnt0++; }
|
||||
inline void dbg_after() { dbg_show_hit_rate = true; dbg_cnt1++; }
|
||||
|
||||
inline void dbg_mean_of(int v) { dbg_cnt0++; dbg_cnt1 += v; }
|
||||
|
||||
extern void dbg_hit_on(bool b);
|
||||
extern void dbg_hit_on_c(bool c, bool b);
|
||||
extern void dbg_before();
|
||||
extern void dbg_after();
|
||||
extern void dbg_mean_of(int v);
|
||||
extern void dbg_print_hit_rate();
|
||||
extern void dbg_print_mean();
|
||||
extern void dbg_print_hit_rate(std::ofstream& logFile);
|
||||
extern void dbg_print_mean(std::ofstream& logFile);
|
||||
|
||||
#endif // !defined(MISC_H_INCLUDED)
|
||||
|
||||
399
src/pawns.cpp
399
src/pawns.cpp
@@ -86,40 +86,37 @@ namespace {
|
||||
|
||||
// Candidate passed pawn bonus by rank, middle game.
|
||||
const Value CandidateMidgameBonus[8] = {
|
||||
Value(0), Value(12), Value(12), Value(20),
|
||||
Value(40), Value(90), Value(0), Value(0)
|
||||
Value( 0), Value(12), Value(12), Value(20),
|
||||
Value(40), Value(90), Value( 0), Value( 0)
|
||||
};
|
||||
|
||||
// Candidate passed pawn bonus by rank, endgame.
|
||||
const Value CandidateEndgameBonus[8] = {
|
||||
Value(0), Value(24), Value(24), Value(40),
|
||||
Value(80), Value(180), Value(0), Value(0)
|
||||
Value( 0), Value(24), Value(24), Value(40),
|
||||
Value(80), Value(180), Value(0), Value( 0)
|
||||
};
|
||||
|
||||
// Evaluate pawn storms?
|
||||
const bool EvaluatePawnStorms = true;
|
||||
|
||||
// Pawn storm tables for positions with opposite castling:
|
||||
const int QStormTable[64] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
-22, -22, -22, -13, -4, 0, 0, 0,
|
||||
-4, -9, -9, -9, -4, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
-22,-22,-22,-13,-4, 0, 0, 0,
|
||||
-4, -9, -9, -9,-4, 0, 0, 0,
|
||||
9, 18, 22, 18, 9, 0, 0, 0,
|
||||
22, 31, 31, 22, 0, 0, 0, 0,
|
||||
31, 40, 40, 31, 0, 0, 0, 0,
|
||||
31, 40, 40, 31, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0
|
||||
22, 31, 31, 22, 0, 0, 0, 0,
|
||||
31, 40, 40, 31, 0, 0, 0, 0,
|
||||
31, 40, 40, 31, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0
|
||||
};
|
||||
|
||||
const int KStormTable[64] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, -4, -13, -22, -27, -27,
|
||||
0, 0, 0, -4, -9, -13, -18, -18,
|
||||
0, 0, 0, 0, 9, 9, 9, 9,
|
||||
0, 0, 0, 0, 9, 18, 27, 27,
|
||||
0, 0, 0, 0, 9, 27, 40, 36,
|
||||
0, 0, 0, 0, 0, 31, 40, 31,
|
||||
0, 0, 0, 0, 0, 0, 0, 0
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0,-4,-13,-22,-27,-27,
|
||||
0, 0, 0,-4, -9,-13,-18,-18,
|
||||
0, 0, 0, 0, 9, 9, 9, 9,
|
||||
0, 0, 0, 0, 9, 18, 27, 27,
|
||||
0, 0, 0, 0, 9, 27, 40, 36,
|
||||
0, 0, 0, 0, 0, 31, 40, 31,
|
||||
0, 0, 0, 0, 0, 0, 0, 0
|
||||
};
|
||||
|
||||
// Pawn storm open file bonuses by file:
|
||||
@@ -141,14 +138,16 @@ namespace {
|
||||
/// Constructor
|
||||
|
||||
PawnInfoTable::PawnInfoTable(unsigned numOfEntries) {
|
||||
|
||||
size = numOfEntries;
|
||||
entries = new PawnInfo[size];
|
||||
if(entries == NULL) {
|
||||
std::cerr << "Failed to allocate " << (numOfEntries * sizeof(PawnInfo))
|
||||
<< " bytes for pawn hash table." << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
if (entries == NULL)
|
||||
{
|
||||
std::cerr << "Failed to allocate " << (numOfEntries * sizeof(PawnInfo))
|
||||
<< " bytes for pawn hash table." << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
this->clear();
|
||||
clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -173,6 +172,7 @@ void PawnInfoTable::clear() {
|
||||
/// the same pawn structure occurs again.
|
||||
|
||||
PawnInfo *PawnInfoTable::get_pawn_info(const Position &pos) {
|
||||
|
||||
assert(pos.is_ok());
|
||||
|
||||
Key key = pos.get_pawn_key();
|
||||
@@ -181,207 +181,204 @@ PawnInfo *PawnInfoTable::get_pawn_info(const Position &pos) {
|
||||
|
||||
// If pi->key matches the position's pawn hash key, it means that we
|
||||
// have analysed this pawn structure before, and we can simply return the
|
||||
// information we found the last time instead of recomputing it:
|
||||
if(pi->key == key)
|
||||
return pi;
|
||||
// information we found the last time instead of recomputing it
|
||||
if (pi->key == key)
|
||||
return pi;
|
||||
|
||||
// Clear the PawnInfo object, and set the key:
|
||||
// Clear the PawnInfo object, and set the key
|
||||
pi->clear();
|
||||
pi->key = key;
|
||||
|
||||
Value mgValue[2] = {Value(0), Value(0)};
|
||||
Value egValue[2] = {Value(0), Value(0)};
|
||||
|
||||
// Loop through the pawns for both colors:
|
||||
for(Color us = WHITE; us <= BLACK; us++) {
|
||||
// Loop through the pawns for both colors
|
||||
for (Color us = WHITE; us <= BLACK; us++)
|
||||
{
|
||||
Color them = opposite_color(us);
|
||||
Bitboard ourPawns = pos.pawns(us);
|
||||
Bitboard theirPawns = pos.pawns(them);
|
||||
Bitboard pawns = ourPawns;
|
||||
|
||||
// Initialize pawn storm scores by giving bonuses for open files:
|
||||
if(EvaluatePawnStorms)
|
||||
for(File f = FILE_A; f <= FILE_H; f++)
|
||||
if(pos.file_is_half_open(us, f)) {
|
||||
pi->ksStormValue[us] += KStormOpenFileBonus[f];
|
||||
pi->qsStormValue[us] += QStormOpenFileBonus[f];
|
||||
// Initialize pawn storm scores by giving bonuses for open files
|
||||
for (File f = FILE_A; f <= FILE_H; f++)
|
||||
if (pos.file_is_half_open(us, f))
|
||||
{
|
||||
pi->ksStormValue[us] += KStormOpenFileBonus[f];
|
||||
pi->qsStormValue[us] += QStormOpenFileBonus[f];
|
||||
}
|
||||
|
||||
// Loop through all pawns of the current color and score each pawn:
|
||||
while(pawns) {
|
||||
Square s = pop_1st_bit(&pawns);
|
||||
File f = square_file(s);
|
||||
Rank r = square_rank(s);
|
||||
bool passed, doubled, isolated, backward, chain, candidate;
|
||||
int bonus;
|
||||
// Loop through all pawns of the current color and score each pawn
|
||||
while (pawns)
|
||||
{
|
||||
bool passed, doubled, isolated, backward, chain, candidate;
|
||||
Square s = pop_1st_bit(&pawns);
|
||||
File f = square_file(s);
|
||||
Rank r = square_rank(s);
|
||||
|
||||
assert(pos.piece_on(s) == pawn_of_color(us));
|
||||
assert(pos.piece_on(s) == pawn_of_color(us));
|
||||
|
||||
// The file containing the pawn is not half open:
|
||||
pi->halfOpenFiles[us] &= ~(1 << f);
|
||||
// The file containing the pawn is not half open
|
||||
pi->halfOpenFiles[us] &= ~(1 << f);
|
||||
|
||||
// Passed, isolated or doubled pawn?
|
||||
passed = pos.pawn_is_passed(us, s);
|
||||
isolated = pos.pawn_is_isolated(us, s);
|
||||
doubled = pos.pawn_is_doubled(us, s);
|
||||
// Passed, isolated or doubled pawn?
|
||||
passed = pos.pawn_is_passed(us, s);
|
||||
isolated = pos.pawn_is_isolated(us, s);
|
||||
doubled = pos.pawn_is_doubled(us, s);
|
||||
|
||||
if(EvaluatePawnStorms) {
|
||||
// We calculate kingside and queenside pawn storm
|
||||
// scores for both colors. These are used when evaluating
|
||||
// middle game positions with opposite side castling.
|
||||
// We calculate kingside and queenside pawn storm scores
|
||||
// for both colors. These are used when evaluating middle
|
||||
// game positions with opposite side castling.
|
||||
//
|
||||
// Each pawn is given a base score given by a piece square table
|
||||
// (KStormTable[] or QStormTable[]). This score is increased if
|
||||
// (KStormTable[] or QStormTable[]). This score is increased if
|
||||
// there are enemy pawns on adjacent files in front of the pawn.
|
||||
// This is because we want to be able to open files against the
|
||||
// enemy king, and to avoid blocking the pawn structure (e.g. white
|
||||
// pawns on h6, g5, black pawns on h7, g6, f7).
|
||||
|
||||
// Kingside and queenside pawn storms
|
||||
int KBonus = KStormTable[relative_square(us, s)];
|
||||
int QBonus = QStormTable[relative_square(us, s)];
|
||||
bool outPostFlag = (KBonus > 0 && (outpost_mask(us, s) & theirPawns));
|
||||
bool passedFlag = (QBonus > 0 && (passed_pawn_mask(us, s) & theirPawns));
|
||||
|
||||
switch (f) {
|
||||
|
||||
case FILE_A:
|
||||
QBonus += passedFlag * QBonus / 2;
|
||||
break;
|
||||
|
||||
case FILE_B:
|
||||
QBonus += passedFlag * (QBonus / 2 + QBonus / 4);
|
||||
break;
|
||||
|
||||
case FILE_C:
|
||||
QBonus += passedFlag * QBonus / 2;
|
||||
break;
|
||||
|
||||
case FILE_F:
|
||||
KBonus += outPostFlag * KBonus / 4;
|
||||
break;
|
||||
|
||||
case FILE_G:
|
||||
KBonus += outPostFlag * (KBonus / 2 + KBonus / 4);
|
||||
break;
|
||||
|
||||
case FILE_H:
|
||||
KBonus += outPostFlag * KBonus / 2;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
pi->ksStormValue[us] += KBonus;
|
||||
pi->qsStormValue[us] += QBonus;
|
||||
|
||||
// Member of a pawn chain (but not the backward one)? We could speed up
|
||||
// the test a little by introducing an array of masks indexed by color
|
||||
// and square for doing the test, but because everything is hashed,
|
||||
// it probably won't make any noticable difference.
|
||||
chain = ourPawns
|
||||
& neighboring_files_bb(f)
|
||||
& (rank_bb(r) | rank_bb(r - (us == WHITE ? 1 : -1)));
|
||||
|
||||
// Test for backward pawn
|
||||
//
|
||||
// If the pawn is passed, isolated, or member of a pawn chain
|
||||
// it cannot be backward. If can capture an enemy pawn or if
|
||||
// there are friendly pawns behind on neighboring files it cannot
|
||||
// be backward either.
|
||||
if ( passed
|
||||
|| isolated
|
||||
|| chain
|
||||
|| (pos.pawn_attacks(us, s) & theirPawns)
|
||||
|| (ourPawns & behind_bb(us, r) & neighboring_files_bb(f)))
|
||||
backward = false;
|
||||
else
|
||||
{
|
||||
// We now know that there are no friendly pawns beside or behind this
|
||||
// pawn on neighboring files. We now check whether the pawn is
|
||||
// backward by looking in the forward direction on the neighboring
|
||||
// files, and seeing whether we meet a friendly or an enemy pawn first.
|
||||
Bitboard b;
|
||||
if (us == WHITE)
|
||||
{
|
||||
for (b = pos.pawn_attacks(us, s); !(b & (ourPawns | theirPawns)); b <<= 8);
|
||||
backward = (b | (b << 8)) & theirPawns;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (b = pos.pawn_attacks(us, s); !(b & (ourPawns | theirPawns)); b >>= 8);
|
||||
backward = (b | (b >> 8)) & theirPawns;
|
||||
}
|
||||
}
|
||||
|
||||
// Test for candidate passed pawn
|
||||
candidate = !passed
|
||||
&& pos.file_is_half_open(them, f)
|
||||
&& ( count_1s_max_15(neighboring_files_bb(f) & (behind_bb(us, r) | rank_bb(r)) & ourPawns)
|
||||
- count_1s_max_15(neighboring_files_bb(f) & in_front_bb(us, r) & theirPawns)
|
||||
>= 0);
|
||||
|
||||
// In order to prevent doubled passed pawns from receiving a too big
|
||||
// bonus, only the frontmost passed pawn on each file is considered as
|
||||
// a true passed pawn.
|
||||
if (passed && (ourPawns & squares_in_front_of(us, s)))
|
||||
{
|
||||
// candidate = true;
|
||||
passed = false;
|
||||
}
|
||||
|
||||
// Score this pawn
|
||||
Value mv = Value(0), ev = Value(0);
|
||||
if (isolated)
|
||||
{
|
||||
mv -= IsolatedPawnMidgamePenalty[f];
|
||||
ev -= IsolatedPawnEndgamePenalty[f];
|
||||
if (pos.file_is_half_open(them, f))
|
||||
{
|
||||
mv -= IsolatedPawnMidgamePenalty[f] / 2;
|
||||
ev -= IsolatedPawnEndgamePenalty[f] / 2;
|
||||
}
|
||||
}
|
||||
if (doubled)
|
||||
{
|
||||
mv -= DoubledPawnMidgamePenalty[f];
|
||||
ev -= DoubledPawnEndgamePenalty[f];
|
||||
}
|
||||
if (backward)
|
||||
{
|
||||
mv -= BackwardPawnMidgamePenalty[f];
|
||||
ev -= BackwardPawnEndgamePenalty[f];
|
||||
if (pos.file_is_half_open(them, f))
|
||||
{
|
||||
mv -= BackwardPawnMidgamePenalty[f] / 2;
|
||||
ev -= BackwardPawnEndgamePenalty[f] / 2;
|
||||
}
|
||||
}
|
||||
if (chain)
|
||||
{
|
||||
mv += ChainMidgameBonus[f];
|
||||
ev += ChainEndgameBonus[f];
|
||||
}
|
||||
if (candidate)
|
||||
{
|
||||
mv += CandidateMidgameBonus[relative_rank(us, s)];
|
||||
ev += CandidateEndgameBonus[relative_rank(us, s)];
|
||||
}
|
||||
|
||||
mgValue[us] += mv;
|
||||
egValue[us] += ev;
|
||||
|
||||
// Kingside pawn storms:
|
||||
bonus = KStormTable[relative_square(us, s)];
|
||||
if(bonus > 0 && outpost_mask(us, s) & theirPawns) {
|
||||
switch(f) {
|
||||
|
||||
case FILE_F:
|
||||
bonus += bonus / 4;
|
||||
break;
|
||||
|
||||
case FILE_G:
|
||||
bonus += bonus / 2 + bonus / 4;
|
||||
break;
|
||||
|
||||
case FILE_H:
|
||||
bonus += bonus / 2;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
pi->ksStormValue[us] += bonus;
|
||||
|
||||
// Queenside pawn storms:
|
||||
bonus = QStormTable[relative_square(us, s)];
|
||||
if(bonus > 0 && passed_pawn_mask(us, s) & theirPawns) {
|
||||
switch(f) {
|
||||
|
||||
case FILE_A:
|
||||
bonus += bonus / 2;
|
||||
break;
|
||||
|
||||
case FILE_B:
|
||||
bonus += bonus / 2 + bonus / 4;
|
||||
break;
|
||||
|
||||
case FILE_C:
|
||||
bonus += bonus / 2;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
pi->qsStormValue[us] += bonus;
|
||||
}
|
||||
|
||||
// Member of a pawn chain? We could speed up the test a little by
|
||||
// introducing an array of masks indexed by color and square for doing
|
||||
// the test, but because everything is hashed, it probably won't make
|
||||
// any noticable difference.
|
||||
chain = (us == WHITE)?
|
||||
(ourPawns & neighboring_files_bb(f) & (rank_bb(r) | rank_bb(r-1))) :
|
||||
(ourPawns & neighboring_files_bb(f) & (rank_bb(r) | rank_bb(r+1)));
|
||||
|
||||
|
||||
// Test for backward pawn.
|
||||
|
||||
// If the pawn is isolated, passed, or member of a pawn chain, it cannot
|
||||
// be backward:
|
||||
if(passed || isolated || chain)
|
||||
backward = false;
|
||||
// If the pawn can capture an enemy pawn, it's not backward:
|
||||
else if(pos.pawn_attacks(us, s) & theirPawns)
|
||||
backward = false;
|
||||
// Check for friendly pawns behind on neighboring files:
|
||||
else if(ourPawns & in_front_bb(them, r) & neighboring_files_bb(f))
|
||||
backward = false;
|
||||
else {
|
||||
// We now know that there is no friendly pawns beside or behind this
|
||||
// pawn on neighboring files. We now check whether the pawn is
|
||||
// backward by looking in the forward direction on the neighboring
|
||||
// files, and seeing whether we meet a friendly or an enemy pawn first.
|
||||
Bitboard b;
|
||||
if(us == WHITE) {
|
||||
for(b=pos.pawn_attacks(us, s); !(b&(ourPawns|theirPawns)); b<<=8);
|
||||
backward = (b | (b << 8)) & theirPawns;
|
||||
}
|
||||
else {
|
||||
for(b=pos.pawn_attacks(us, s); !(b&(ourPawns|theirPawns)); b>>=8);
|
||||
backward = (b | (b >> 8)) & theirPawns;
|
||||
}
|
||||
}
|
||||
|
||||
// Test for candidate passed pawn.
|
||||
candidate =
|
||||
(!passed && pos.file_is_half_open(them, f) &&
|
||||
count_1s_max_15(neighboring_files_bb(f)
|
||||
& (in_front_bb(them, r) | rank_bb(r))
|
||||
& ourPawns)
|
||||
- count_1s_max_15(neighboring_files_bb(f) & in_front_bb(us, r)
|
||||
& theirPawns)
|
||||
>= 0);
|
||||
|
||||
// In order to prevent doubled passed pawns from receiving a too big
|
||||
// bonus, only the frontmost passed pawn on each file is considered as
|
||||
// a true passed pawn.
|
||||
if(passed && (ourPawns & squares_in_front_of(us, s))) {
|
||||
// candidate = true;
|
||||
passed = false;
|
||||
}
|
||||
|
||||
// Score this pawn:
|
||||
Value mv = Value(0), ev = Value(0);
|
||||
if(isolated) {
|
||||
mv -= IsolatedPawnMidgamePenalty[f];
|
||||
ev -= IsolatedPawnEndgamePenalty[f];
|
||||
if(pos.file_is_half_open(them, f)) {
|
||||
mv -= IsolatedPawnMidgamePenalty[f] / 2;
|
||||
ev -= IsolatedPawnEndgamePenalty[f] / 2;
|
||||
}
|
||||
}
|
||||
if(doubled) {
|
||||
mv -= DoubledPawnMidgamePenalty[f];
|
||||
ev -= DoubledPawnEndgamePenalty[f];
|
||||
}
|
||||
if(backward) {
|
||||
mv -= BackwardPawnMidgamePenalty[f];
|
||||
ev -= BackwardPawnEndgamePenalty[f];
|
||||
if(pos.file_is_half_open(them, f)) {
|
||||
mv -= BackwardPawnMidgamePenalty[f] / 2;
|
||||
ev -= BackwardPawnEndgamePenalty[f] / 2;
|
||||
}
|
||||
}
|
||||
if(chain) {
|
||||
mv += ChainMidgameBonus[f];
|
||||
ev += ChainEndgameBonus[f];
|
||||
}
|
||||
if(candidate) {
|
||||
mv += CandidateMidgameBonus[relative_rank(us, s)];
|
||||
ev += CandidateEndgameBonus[relative_rank(us, s)];
|
||||
}
|
||||
|
||||
mgValue[us] += mv;
|
||||
egValue[us] += ev;
|
||||
|
||||
// If the pawn is passed, set the square of the pawn in the passedPawns
|
||||
// bitboard:
|
||||
if(passed)
|
||||
set_bit(&(pi->passedPawns), s);
|
||||
}
|
||||
}
|
||||
// If the pawn is passed, set the square of the pawn in the passedPawns
|
||||
// bitboard
|
||||
if (passed)
|
||||
set_bit(&(pi->passedPawns), s);
|
||||
} // while(pawns)
|
||||
} // for(colors)
|
||||
|
||||
pi->mgValue = int16_t(mgValue[WHITE] - mgValue[BLACK]);
|
||||
pi->egValue = int16_t(egValue[WHITE] - egValue[BLACK]);
|
||||
|
||||
return pi;
|
||||
}
|
||||
|
||||
@@ -120,10 +120,10 @@ inline bool PawnInfo::has_open_file_to_right(Color c, File f) const {
|
||||
}
|
||||
|
||||
inline void PawnInfo::clear() {
|
||||
mgValue = egValue = 0;
|
||||
passedPawns = EmptyBoardBB;
|
||||
ksStormValue[WHITE] = ksStormValue[BLACK] = 0;
|
||||
qsStormValue[WHITE] = qsStormValue[BLACK] = 0;
|
||||
|
||||
Key k = key;
|
||||
memset(this, 0, sizeof(PawnInfo));
|
||||
key = k;
|
||||
halfOpenFiles[WHITE] = halfOpenFiles[BLACK] = 0xFF;
|
||||
}
|
||||
|
||||
|
||||
@@ -1582,10 +1582,16 @@ void Position::undo_null_move(const UndoInfo &u) {
|
||||
|
||||
|
||||
/// Position::see() is a static exchange evaluator: It tries to estimate the
|
||||
/// material gain or loss resulting from a move. There are two versions of
|
||||
/// this function: One which takes a move as input, and one which takes a
|
||||
/// 'from' and a 'to' square. The function does not yet understand promotions
|
||||
/// or en passant captures.
|
||||
/// 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 or en passant captures.
|
||||
|
||||
int Position::see(Square to) const {
|
||||
|
||||
assert(square_is_ok(to));
|
||||
return see(SQ_NONE, to);
|
||||
}
|
||||
|
||||
int Position::see(Move m) const {
|
||||
|
||||
@@ -1595,18 +1601,22 @@ int Position::see(Move m) const {
|
||||
|
||||
int Position::see(Square from, Square to) const {
|
||||
|
||||
// Approximate material values, with pawn = 1
|
||||
// Material values
|
||||
static const int seeValues[18] = {
|
||||
0, 1, 3, 3, 5, 10, 100, 0, 0, 1, 3, 3, 5, 10, 100, 0, 0, 0
|
||||
0, PawnValueMidgame, KnightValueMidgame, BishopValueMidgame,
|
||||
RookValueMidgame, QueenValueMidgame, QueenValueMidgame*10, 0,
|
||||
0, PawnValueMidgame, KnightValueMidgame, BishopValueMidgame,
|
||||
RookValueMidgame, QueenValueMidgame, QueenValueMidgame*10, 0,
|
||||
0, 0
|
||||
};
|
||||
|
||||
Bitboard attackers, occ, b;
|
||||
|
||||
assert(square_is_ok(from));
|
||||
assert(square_is_ok(from) || from == SQ_NONE);
|
||||
assert(square_is_ok(to));
|
||||
|
||||
// Initialize colors
|
||||
Color us = color_of_piece_on(from);
|
||||
Color us = (from != SQ_NONE ? color_of_piece_on(from) : opposite_color(color_of_piece_on(to)));
|
||||
Color them = opposite_color(us);
|
||||
|
||||
// Initialize pieces
|
||||
@@ -1616,15 +1626,49 @@ int Position::see(Square from, Square to) const {
|
||||
// Find all attackers to the destination square, with the moving piece
|
||||
// removed, but possibly an X-ray attacker added behind it.
|
||||
occ = occupied_squares();
|
||||
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 the opponent has no attackers, we are finished
|
||||
// Handle enpassant moves
|
||||
if (ep_square() == 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
|
||||
if ((attackers & pieces_of_color(them)) == EmptyBoardBB)
|
||||
return seeValues[capture];
|
||||
|
||||
|
||||
@@ -253,6 +253,7 @@ public:
|
||||
// Static exchange evaluation
|
||||
int see(Square from, Square to) const;
|
||||
int see(Move m) const;
|
||||
int see(Square to) const;
|
||||
|
||||
// Accessing hash keys
|
||||
Key get_key() const;
|
||||
|
||||
289
src/search.cpp
289
src/search.cpp
@@ -47,6 +47,23 @@ namespace {
|
||||
|
||||
/// Types
|
||||
|
||||
// The BetaCounterType class is used to order moves at ply one.
|
||||
// Apart for the first one that has its score, following moves
|
||||
// normally have score -VALUE_INFINITE, so are ordered according
|
||||
// to the number of beta cutoffs occurred under their subtree during
|
||||
// the last iteration.
|
||||
|
||||
struct BetaCounterType {
|
||||
|
||||
BetaCounterType();
|
||||
void clear();
|
||||
void add(Color us, Depth d, int threadID);
|
||||
void read(Color us, int64_t& our, int64_t& their);
|
||||
|
||||
int64_t hits[THREAD_MAX][2];
|
||||
};
|
||||
|
||||
|
||||
// The RootMove class is used for moves at the root at the tree. For each
|
||||
// root move, we store a score, a node count, and a PV (really a refutation
|
||||
// in the case of moves which fail low).
|
||||
@@ -60,6 +77,7 @@ namespace {
|
||||
Value score;
|
||||
int64_t nodes, cumulativeNodes;
|
||||
Move pv[PLY_MAX_PLUS_2];
|
||||
int64_t ourBeta, theirBeta;
|
||||
};
|
||||
|
||||
|
||||
@@ -74,6 +92,7 @@ namespace {
|
||||
inline Value get_move_score(int moveNum) const;
|
||||
inline void set_move_score(int moveNum, Value score);
|
||||
inline void set_move_nodes(int moveNum, int64_t nodes);
|
||||
inline void set_beta_counters(int moveNum, int64_t our, int64_t their);
|
||||
void set_move_pv(int moveNum, const Move pv[]);
|
||||
inline Move get_move_pv(int moveNum, int i) const;
|
||||
inline int64_t get_move_cumulative_nodes(int moveNum) const;
|
||||
@@ -107,16 +126,13 @@ namespace {
|
||||
const bool UseIIDAtNonPVNodes = false;
|
||||
|
||||
// Use null move driven internal iterative deepening?
|
||||
bool UseNullDrivenIID = true;
|
||||
bool UseNullDrivenIID = false;
|
||||
|
||||
// Internal iterative deepening margin. At Non-PV moves, when
|
||||
// UseIIDAtNonPVNodes is true, we do an internal iterative deepening search
|
||||
// when the static evaluation is at most IIDMargin below beta.
|
||||
const Value IIDMargin = Value(0x100);
|
||||
|
||||
// Use easy moves?
|
||||
const bool UseEasyMove = true;
|
||||
|
||||
// Easy move margin. An easy move candidate must be at least this much
|
||||
// better than the second best move.
|
||||
const Value EasyMoveMargin = Value(0x200);
|
||||
@@ -146,13 +162,14 @@ namespace {
|
||||
bool UseQSearchFutilityPruning = true;
|
||||
bool UseFutilityPruning = true;
|
||||
|
||||
// Margins for futility pruning in the quiescence search, at frontier
|
||||
// nodes, and at pre-frontier nodes
|
||||
Value FutilityMargin0 = Value(0x80);
|
||||
Value FutilityMargin1 = Value(0x100);
|
||||
Value FutilityMargin2 = Value(0x300);
|
||||
// Margins for futility pruning in the quiescence search, and at frontier
|
||||
// and near frontier nodes
|
||||
Value FutilityMarginQS = Value(0x80);
|
||||
Value FutilityMargins[6] = { Value(0x100), Value(0x200), Value(0x250),
|
||||
Value(0x2A0), Value(0x340), Value(0x3A0) };
|
||||
|
||||
// Razoring
|
||||
const bool RazorAtDepthOne = false;
|
||||
Depth RazorDepth = 4*OnePly;
|
||||
Value RazorMargin = Value(0x300);
|
||||
|
||||
@@ -177,9 +194,10 @@ namespace {
|
||||
int NodesSincePoll;
|
||||
int NodesBetweenPolls = 30000;
|
||||
|
||||
// Iteration counter
|
||||
// Iteration counters
|
||||
int Iteration;
|
||||
bool LastIterations;
|
||||
BetaCounterType BetaCounter;
|
||||
|
||||
// Scores and number of times the best move changed for each iteration:
|
||||
Value ValueByIteration[PLY_MAX_PLUS_2];
|
||||
@@ -247,6 +265,7 @@ namespace {
|
||||
void update_pv(SearchStack ss[], int ply);
|
||||
void sp_update_pv(SearchStack *pss, SearchStack ss[], int ply);
|
||||
bool connected_moves(const Position &pos, Move m1, Move m2);
|
||||
bool value_is_mate(Value value);
|
||||
bool move_is_killer(Move m, const SearchStack& ss);
|
||||
Depth extension(const Position &pos, Move m, bool pvNode, bool check, bool singleReply, bool mateThreat, bool* dangerous);
|
||||
bool ok_to_do_nullmove(const Position &pos);
|
||||
@@ -397,9 +416,10 @@ void think(const Position &pos, bool infinite, bool ponder, int side_to_move,
|
||||
UseQSearchFutilityPruning = get_option_value_bool("Futility Pruning (Quiescence Search)");
|
||||
UseFutilityPruning = get_option_value_bool("Futility Pruning (Main Search)");
|
||||
|
||||
FutilityMargin0 = value_from_centipawns(get_option_value_int("Futility Margin 0"));
|
||||
FutilityMargin1 = value_from_centipawns(get_option_value_int("Futility Margin 1"));
|
||||
FutilityMargin2 = value_from_centipawns(get_option_value_int("Futility Margin 2"));
|
||||
FutilityMarginQS = value_from_centipawns(get_option_value_int("Futility Margin (Quiescence Search)"));
|
||||
int fmScale = get_option_value_int("Futility Margin Scale Factor (Main Search)");
|
||||
for (int i = 0; i < 6; i++)
|
||||
FutilityMargins[i] = (FutilityMargins[i] * fmScale) / 100;
|
||||
|
||||
RazorDepth = (get_option_value_int("Maximum Razoring Depth") + 1) * OnePly;
|
||||
RazorMargin = value_from_centipawns(get_option_value_int("Razoring Margin"));
|
||||
@@ -429,7 +449,6 @@ void think(const Position &pos, bool infinite, bool ponder, int side_to_move,
|
||||
// Set thinking time:
|
||||
int myTime = time[side_to_move];
|
||||
int myIncrement = increment[side_to_move];
|
||||
int oppTime = time[1 - side_to_move];
|
||||
|
||||
if (!movesToGo) // Sudden death time control
|
||||
{
|
||||
@@ -729,6 +748,12 @@ namespace {
|
||||
|
||||
if (UseLogFile)
|
||||
{
|
||||
if (dbg_show_mean)
|
||||
dbg_print_mean(LogFile);
|
||||
|
||||
if (dbg_show_hit_rate)
|
||||
dbg_print_hit_rate(LogFile);
|
||||
|
||||
UndoInfo u;
|
||||
LogFile << "Nodes: " << nodes_searched() << std::endl
|
||||
<< "Nodes/second: " << nps() << std::endl
|
||||
@@ -768,6 +793,9 @@ namespace {
|
||||
// are used to sort the root moves at the next iteration.
|
||||
nodes = nodes_searched();
|
||||
|
||||
// Reset beta cut-off counters
|
||||
BetaCounter.clear();
|
||||
|
||||
// Pick the next root move, and print the move and the move number to
|
||||
// the standard output.
|
||||
move = ss[0].currentMove = rml.get_move(i);
|
||||
@@ -823,6 +851,11 @@ namespace {
|
||||
// sort the root moves at the next iteration.
|
||||
rml.set_move_nodes(i, nodes_searched() - nodes);
|
||||
|
||||
// Remember the beta-cutoff statistics
|
||||
int64_t our, their;
|
||||
BetaCounter.read(pos.side_to_move(), our, their);
|
||||
rml.set_beta_counters(i, our, their);
|
||||
|
||||
assert(value >= -VALUE_INFINITE && value <= VALUE_INFINITE);
|
||||
|
||||
if (value <= alpha && i >= MultiPV)
|
||||
@@ -905,16 +938,17 @@ namespace {
|
||||
assert(ply >= 0 && ply < PLY_MAX);
|
||||
assert(threadID >= 0 && threadID < ActiveThreads);
|
||||
|
||||
// Initialize, and make an early exit in case of an aborted search,
|
||||
// an instant draw, maximum ply reached, etc.
|
||||
if (AbortSearch || thread_should_stop(threadID))
|
||||
return Value(0);
|
||||
|
||||
if (depth < OnePly)
|
||||
return qsearch(pos, ss, alpha, beta, Depth(0), ply, threadID);
|
||||
|
||||
// Initialize, and make an early exit in case of an aborted search,
|
||||
// an instant draw, maximum ply reached, etc.
|
||||
init_node(pos, ss, ply, threadID);
|
||||
|
||||
// After init_node() that calls poll()
|
||||
if (AbortSearch || thread_should_stop(threadID))
|
||||
return Value(0);
|
||||
|
||||
if (pos.is_draw())
|
||||
return VALUE_DRAW;
|
||||
|
||||
@@ -968,9 +1002,8 @@ namespace {
|
||||
movesSearched[moveCount++] = ss[ply].currentMove = move;
|
||||
|
||||
if (moveIsCapture)
|
||||
ss[ply].currentMoveCaptureValue = pos.midgame_value_of_piece_on(move_to(move));
|
||||
else if (move_is_ep(move))
|
||||
ss[ply].currentMoveCaptureValue = PawnValueMidgame;
|
||||
ss[ply].currentMoveCaptureValue =
|
||||
move_is_ep(move)? PawnValueMidgame : pos.midgame_value_of_piece_on(move_to(move));
|
||||
else
|
||||
ss[ply].currentMoveCaptureValue = Value(0);
|
||||
|
||||
@@ -1003,7 +1036,7 @@ namespace {
|
||||
else
|
||||
value = alpha + 1; // Just to trigger next condition
|
||||
|
||||
if (value > alpha) // Go with full depth pv search
|
||||
if (value > alpha) // Go with full depth non-pv search
|
||||
{
|
||||
ss[ply].reduction = Depth(0);
|
||||
value = -search(pos, ss, -alpha, newDepth, ply+1, true, threadID);
|
||||
@@ -1041,7 +1074,9 @@ namespace {
|
||||
// If we are at ply 1, and we are searching the first root move at
|
||||
// ply 0, set the 'Problem' variable if the score has dropped a lot
|
||||
// (from the computer's point of view) since the previous iteration:
|
||||
if (Iteration >= 2 && -value <= ValueByIteration[Iteration-1] - ProblemMargin)
|
||||
if ( ply == 1
|
||||
&& Iteration >= 2
|
||||
&& -value <= ValueByIteration[Iteration-1] - ProblemMargin)
|
||||
Problem = true;
|
||||
}
|
||||
|
||||
@@ -1073,6 +1108,7 @@ namespace {
|
||||
|
||||
else if (bestValue >= beta)
|
||||
{
|
||||
BetaCounter.add(pos.side_to_move(), depth, threadID);
|
||||
Move m = ss[ply].pv[ply];
|
||||
if (ok_to_history(pos, m)) // Only non capture moves are considered
|
||||
{
|
||||
@@ -1097,21 +1133,22 @@ namespace {
|
||||
assert(ply >= 0 && ply < PLY_MAX);
|
||||
assert(threadID >= 0 && threadID < ActiveThreads);
|
||||
|
||||
EvalInfo ei;
|
||||
|
||||
// Initialize, and make an early exit in case of an aborted search,
|
||||
// an instant draw, maximum ply reached, etc.
|
||||
if (AbortSearch || thread_should_stop(threadID))
|
||||
return Value(0);
|
||||
|
||||
if (depth < OnePly)
|
||||
return qsearch(pos, ss, beta-1, beta, Depth(0), ply, threadID);
|
||||
|
||||
// Initialize, and make an early exit in case of an aborted search,
|
||||
// an instant draw, maximum ply reached, etc.
|
||||
init_node(pos, ss, ply, threadID);
|
||||
|
||||
// After init_node() that calls poll()
|
||||
if (AbortSearch || thread_should_stop(threadID))
|
||||
return Value(0);
|
||||
|
||||
if (pos.is_draw())
|
||||
return VALUE_DRAW;
|
||||
|
||||
EvalInfo ei;
|
||||
|
||||
if (ply >= PLY_MAX - 1)
|
||||
return evaluate(pos, ei, threadID);
|
||||
|
||||
@@ -1139,7 +1176,9 @@ namespace {
|
||||
|
||||
// Null move search
|
||||
if ( allowNullmove
|
||||
&& depth > OnePly
|
||||
&& !isCheck
|
||||
&& !value_is_mate(beta)
|
||||
&& ok_to_do_nullmove(pos)
|
||||
&& approximateEval >= beta - NullMoveMargin)
|
||||
{
|
||||
@@ -1147,25 +1186,30 @@ namespace {
|
||||
|
||||
UndoInfo u;
|
||||
pos.do_null_move(u);
|
||||
int R = (depth > 7 ? 4 : 3);
|
||||
int R = (depth >= 4 * OnePly ? 4 : 3); // Null move dynamic reduction
|
||||
|
||||
Value nullValue = -search(pos, ss, -(beta-1), depth-R*OnePly, ply+1, false, threadID);
|
||||
|
||||
// Check for a null capture artifact, if the value without the null capture
|
||||
// is above beta then there is a good possibility that this is a cut-node.
|
||||
// We will do an IID later to find a ttMove.
|
||||
// is above beta then mark the node as a suspicious failed low. We will verify
|
||||
// later if we are really under threat.
|
||||
if ( UseNullDrivenIID
|
||||
&& nullValue < beta
|
||||
&& depth > 6 * OnePly
|
||||
&&!value_is_mate(nullValue)
|
||||
&& ttMove == MOVE_NONE
|
||||
&& ss[ply + 1].currentMove != MOVE_NONE
|
||||
&& pos.move_is_capture(ss[ply + 1].currentMove)
|
||||
&& pos.see(ss[ply + 1].currentMove) * PawnValueMidgame + nullValue > beta - IIDMargin)
|
||||
&& pos.see(ss[ply + 1].currentMove) + nullValue >= beta)
|
||||
nullDrivenIID = true;
|
||||
|
||||
pos.undo_null_move(u);
|
||||
|
||||
if (nullValue >= beta)
|
||||
if (value_is_mate(nullValue))
|
||||
{
|
||||
/* Do not return unproven mates */
|
||||
}
|
||||
else if (nullValue >= beta)
|
||||
{
|
||||
if (depth < 6 * OnePly)
|
||||
return beta;
|
||||
@@ -1176,9 +1220,9 @@ namespace {
|
||||
return beta;
|
||||
} else {
|
||||
// The null move failed low, which means that we may be faced with
|
||||
// some kind of threat. If the previous move was reduced, check if
|
||||
// some kind of threat. If the previous move was reduced, check if
|
||||
// the move that refuted the null move was somehow connected to the
|
||||
// move which was reduced. If a connection is found, return a fail
|
||||
// move which was reduced. If a connection is found, return a fail
|
||||
// low score (which will cause the reduced move to fail high in the
|
||||
// parent node, which will trigger a re-search with full depth).
|
||||
if (nullValue == value_mated_in(ply + 2))
|
||||
@@ -1194,11 +1238,17 @@ namespace {
|
||||
}
|
||||
}
|
||||
// Null move search not allowed, try razoring
|
||||
else if ( (approximateEval < beta - RazorMargin && depth < RazorDepth)
|
||||
||(approximateEval < beta - PawnValueMidgame && depth <= OnePly))
|
||||
else if ( !value_is_mate(beta)
|
||||
&& approximateEval < beta - RazorMargin
|
||||
&& depth < RazorDepth
|
||||
&& (RazorAtDepthOne || depth > OnePly)
|
||||
&& ttMove == MOVE_NONE
|
||||
&& !pos.has_pawn_on_7th(pos.side_to_move()))
|
||||
{
|
||||
Value v = qsearch(pos, ss, beta-1, beta, Depth(0), ply, threadID);
|
||||
if (v < beta)
|
||||
if ( (v < beta - RazorMargin - RazorMargin / 4)
|
||||
|| (depth <= 2*OnePly && v < beta - RazorMargin)
|
||||
|| (depth <= OnePly && v < beta - RazorMargin / 2))
|
||||
return v;
|
||||
}
|
||||
|
||||
@@ -1213,12 +1263,15 @@ namespace {
|
||||
{
|
||||
// The null move failed low due to a suspicious capture. Perhaps we
|
||||
// are facing a null capture artifact due to the side to move change
|
||||
// and this is a cut-node. So it's a good time to search for a ttMove.
|
||||
// and this position should fail high. So do a normal search with a
|
||||
// reduced depth to get a good ttMove to use in the following full
|
||||
// depth search.
|
||||
Move tm = ss[ply].threatMove;
|
||||
|
||||
assert(tm != MOVE_NONE);
|
||||
assert(ttMove == MOVE_NONE);
|
||||
|
||||
search(pos, ss, beta, Min(depth/2, depth-3*OnePly), ply, false, threadID);
|
||||
search(pos, ss, beta, depth/2, ply, false, threadID);
|
||||
ttMove = ss[ply].pv[ply];
|
||||
ss[ply].threatMove = tm;
|
||||
}
|
||||
@@ -1261,15 +1314,18 @@ namespace {
|
||||
&& !moveIsCapture
|
||||
&& !move_promotion(move))
|
||||
{
|
||||
// History pruning. See ok_to_prune() definition
|
||||
if ( moveCount >= 2 + int(depth)
|
||||
&& ok_to_prune(pos, move, ss[ply].threatMove, depth))
|
||||
continue;
|
||||
|
||||
if (depth < 3 * OnePly && approximateEval < beta)
|
||||
// Value based pruning
|
||||
if (depth < 7 * OnePly && approximateEval < beta)
|
||||
{
|
||||
if (futilityValue == VALUE_NONE)
|
||||
futilityValue = evaluate(pos, ei, threadID)
|
||||
+ (depth < 2 * OnePly ? FutilityMargin1 : FutilityMargin2);
|
||||
+ FutilityMargins[int(depth)/2 - 1]
|
||||
+ 32 * (depth & 1);
|
||||
|
||||
if (futilityValue < beta)
|
||||
{
|
||||
@@ -1347,6 +1403,7 @@ namespace {
|
||||
TT.store(pos, value_to_tt(bestValue, ply), depth, MOVE_NONE, VALUE_TYPE_UPPER);
|
||||
else
|
||||
{
|
||||
BetaCounter.add(pos.side_to_move(), depth, threadID);
|
||||
Move m = ss[ply].pv[ply];
|
||||
if (ok_to_history(pos, m)) // Only non capture moves are considered
|
||||
{
|
||||
@@ -1372,15 +1429,14 @@ namespace {
|
||||
assert(ply >= 0 && ply < PLY_MAX);
|
||||
assert(threadID >= 0 && threadID < ActiveThreads);
|
||||
|
||||
EvalInfo ei;
|
||||
|
||||
// Initialize, and make an early exit in case of an aborted search,
|
||||
// an instant draw, maximum ply reached, etc.
|
||||
init_node(pos, ss, ply, threadID);
|
||||
|
||||
// After init_node() that calls poll()
|
||||
if (AbortSearch || thread_should_stop(threadID))
|
||||
return Value(0);
|
||||
|
||||
init_node(pos, ss, ply, threadID);
|
||||
|
||||
if (pos.is_draw())
|
||||
return VALUE_DRAW;
|
||||
|
||||
@@ -1390,14 +1446,16 @@ namespace {
|
||||
return value_from_tt(tte->value(), ply);
|
||||
|
||||
// Evaluate the position statically
|
||||
Value staticValue = evaluate(pos, ei, threadID);
|
||||
EvalInfo ei;
|
||||
bool isCheck = pos.is_check();
|
||||
Value staticValue = (isCheck ? -VALUE_INFINITE : evaluate(pos, ei, threadID));
|
||||
|
||||
if (ply == PLY_MAX - 1)
|
||||
return staticValue;
|
||||
return evaluate(pos, ei, threadID);
|
||||
|
||||
// Initialize "stand pat score", and return it immediately if it is
|
||||
// at least beta.
|
||||
Value bestValue = (pos.is_check() ? -VALUE_INFINITE : staticValue);
|
||||
Value bestValue = staticValue;
|
||||
|
||||
if (bestValue >= beta)
|
||||
return bestValue;
|
||||
@@ -1408,12 +1466,11 @@ namespace {
|
||||
// Initialize a MovePicker object for the current position, and prepare
|
||||
// to search the moves. Because the depth is <= 0 here, only captures,
|
||||
// queen promotions and checks (only if depth == 0) will be generated.
|
||||
MovePicker mp = MovePicker(pos, false, MOVE_NONE, EmptySearchStack, depth, &ei);
|
||||
bool pvNode = (beta - alpha != 1);
|
||||
MovePicker mp = MovePicker(pos, pvNode, MOVE_NONE, EmptySearchStack, depth, isCheck ? NULL : &ei);
|
||||
Move move;
|
||||
int moveCount = 0;
|
||||
Bitboard dcCandidates = mp.discovered_check_candidates();
|
||||
bool isCheck = pos.is_check();
|
||||
bool pvNode = (beta - alpha != 1);
|
||||
bool enoughMaterial = pos.non_pawn_material(pos.side_to_move()) > RookValueMidgame;
|
||||
|
||||
// Loop through the moves until no moves remain or a beta cutoff
|
||||
@@ -1438,7 +1495,8 @@ namespace {
|
||||
Value futilityValue = staticValue
|
||||
+ Max(pos.midgame_value_of_piece_on(move_to(move)),
|
||||
pos.endgame_value_of_piece_on(move_to(move)))
|
||||
+ FutilityMargin0
|
||||
+ (move_is_ep(move) ? PawnValueEndgame : Value(0))
|
||||
+ FutilityMarginQS
|
||||
+ ei.futilityMargin;
|
||||
|
||||
if (futilityValue < alpha)
|
||||
@@ -1641,8 +1699,11 @@ namespace {
|
||||
|
||||
assert(move_is_ok(move));
|
||||
|
||||
ss[sp->ply].currentMoveCaptureValue = move_is_ep(move)?
|
||||
PawnValueMidgame : pos.midgame_value_of_piece_on(move_to(move));
|
||||
if (moveIsCapture)
|
||||
ss[sp->ply].currentMoveCaptureValue =
|
||||
move_is_ep(move)? PawnValueMidgame : pos.midgame_value_of_piece_on(move_to(move));
|
||||
else
|
||||
ss[sp->ply].currentMoveCaptureValue = Value(0);
|
||||
|
||||
lock_grab(&(sp->lock));
|
||||
int moveCount = ++sp->moves;
|
||||
@@ -1723,8 +1784,10 @@ namespace {
|
||||
}
|
||||
// If we are at ply 1, and we are searching the first root move at
|
||||
// ply 0, set the 'Problem' variable if the score has dropped a lot
|
||||
// (from the computer's point of view) since the previous iteration:
|
||||
if (Iteration >= 2 && -value <= ValueByIteration[Iteration-1] - ProblemMargin)
|
||||
// (from the computer's point of view) since the previous iteration.
|
||||
if ( sp->ply == 1
|
||||
&& Iteration >= 2
|
||||
&& -value <= ValueByIteration[Iteration-1] - ProblemMargin)
|
||||
Problem = true;
|
||||
}
|
||||
lock_release(&(sp->lock));
|
||||
@@ -1733,7 +1796,7 @@ namespace {
|
||||
lock_grab(&(sp->lock));
|
||||
|
||||
// If this is the master thread and we have been asked to stop because of
|
||||
// a beta cutoff higher up in the tree, stop all slave threads:
|
||||
// a beta cutoff higher up in the tree, stop all slave threads.
|
||||
if (sp->master == threadID && thread_should_stop(threadID))
|
||||
for (int i = 0; i < ActiveThreads; i++)
|
||||
if (sp->slaves[i])
|
||||
@@ -1745,6 +1808,32 @@ namespace {
|
||||
lock_release(&(sp->lock));
|
||||
}
|
||||
|
||||
/// The BetaCounterType class
|
||||
|
||||
BetaCounterType::BetaCounterType() { clear(); }
|
||||
|
||||
void BetaCounterType::clear() {
|
||||
|
||||
for (int i = 0; i < THREAD_MAX; i++)
|
||||
hits[i][WHITE] = hits[i][BLACK] = 0ULL;
|
||||
}
|
||||
|
||||
void BetaCounterType::add(Color us, Depth d, int threadID) {
|
||||
|
||||
// Weighted count based on depth
|
||||
hits[threadID][us] += int(d);
|
||||
}
|
||||
|
||||
void BetaCounterType::read(Color us, int64_t& our, int64_t& their) {
|
||||
|
||||
our = their = 0UL;
|
||||
for (int i = 0; i < THREAD_MAX; i++)
|
||||
{
|
||||
our += hits[i][us];
|
||||
their += hits[i][opposite_color(us)];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The RootMove class
|
||||
|
||||
@@ -1764,7 +1853,7 @@ namespace {
|
||||
if (score != m.score)
|
||||
return (score < m.score);
|
||||
|
||||
return nodes <= m.nodes;
|
||||
return theirBeta <= m.theirBeta;
|
||||
}
|
||||
|
||||
/// The RootMoveList class
|
||||
@@ -1827,6 +1916,11 @@ namespace {
|
||||
moves[moveNum].cumulativeNodes += nodes;
|
||||
}
|
||||
|
||||
inline void RootMoveList::set_beta_counters(int moveNum, int64_t our, int64_t their) {
|
||||
moves[moveNum].ourBeta = our;
|
||||
moves[moveNum].theirBeta = their;
|
||||
}
|
||||
|
||||
void RootMoveList::set_move_pv(int moveNum, const Move pv[]) {
|
||||
int j;
|
||||
for(j = 0; pv[j] != MOVE_NONE; j++)
|
||||
@@ -2051,6 +2145,18 @@ namespace {
|
||||
}
|
||||
|
||||
|
||||
// value_is_mate() checks if the given value is a mate one
|
||||
// eventually compensated for the ply.
|
||||
|
||||
bool value_is_mate(Value value) {
|
||||
|
||||
assert(abs(value) <= VALUE_INFINITE);
|
||||
|
||||
return value <= value_mated_in(PLY_MAX)
|
||||
|| value >= value_mate_in(PLY_MAX);
|
||||
}
|
||||
|
||||
|
||||
// move_is_killer() checks if the given move is among the
|
||||
// killer moves of that ply.
|
||||
|
||||
@@ -2075,6 +2181,8 @@ namespace {
|
||||
Depth extension(const Position &pos, Move m, bool pvNode, bool check,
|
||||
bool singleReply, bool mateThreat, bool* dangerous) {
|
||||
|
||||
assert(m != MOVE_NONE);
|
||||
|
||||
Depth result = Depth(0);
|
||||
*dangerous = check || singleReply || mateThreat;
|
||||
|
||||
@@ -2087,21 +2195,26 @@ namespace {
|
||||
if (mateThreat)
|
||||
result += MateThreatExtension[pvNode];
|
||||
|
||||
if (pos.move_is_pawn_push_to_7th(m))
|
||||
if (pos.type_of_piece_on(move_from(m)) == PAWN)
|
||||
{
|
||||
result += PawnPushTo7thExtension[pvNode];
|
||||
*dangerous = true;
|
||||
}
|
||||
if (pos.move_is_passed_pawn_push(m))
|
||||
{
|
||||
result += PassedPawnExtension[pvNode];
|
||||
*dangerous = true;
|
||||
if (pos.move_is_pawn_push_to_7th(m))
|
||||
{
|
||||
result += PawnPushTo7thExtension[pvNode];
|
||||
*dangerous = true;
|
||||
}
|
||||
if (pos.move_is_passed_pawn_push(m))
|
||||
{
|
||||
result += PassedPawnExtension[pvNode];
|
||||
*dangerous = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( pos.midgame_value_of_piece_on(move_to(m)) >= RookValueMidgame
|
||||
if ( pos.move_is_capture(m)
|
||||
&& pos.type_of_piece_on(move_to(m)) != PAWN
|
||||
&& ( pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK)
|
||||
- pos.midgame_value_of_piece_on(move_to(m)) == Value(0))
|
||||
&& !move_promotion(m))
|
||||
&& !move_promotion(m)
|
||||
&& !move_is_ep(m))
|
||||
{
|
||||
result += PawnEndgameExtension[pvNode];
|
||||
*dangerous = true;
|
||||
@@ -2156,31 +2269,35 @@ namespace {
|
||||
tto = move_to(threat);
|
||||
|
||||
// Case 1: Castling moves are never pruned.
|
||||
if(move_is_castle(m))
|
||||
return false;
|
||||
if (move_is_castle(m))
|
||||
return false;
|
||||
|
||||
// Case 2: Don't prune moves which move the threatened piece
|
||||
if(!PruneEscapeMoves && threat != MOVE_NONE && mfrom == tto)
|
||||
return false;
|
||||
if (!PruneEscapeMoves && threat != MOVE_NONE && mfrom == tto)
|
||||
return false;
|
||||
|
||||
// Case 3: If the threatened piece has value less than or equal to the
|
||||
// value of the threatening piece, don't prune move which defend it.
|
||||
if(!PruneDefendingMoves && threat != MOVE_NONE
|
||||
&& (piece_value_midgame(pos.piece_on(tfrom))
|
||||
>= piece_value_midgame(pos.piece_on(tto)))
|
||||
&& pos.move_attacks_square(m, tto))
|
||||
if ( !PruneDefendingMoves
|
||||
&& threat != MOVE_NONE
|
||||
&& pos.move_is_capture(threat)
|
||||
&& ( pos.midgame_value_of_piece_on(tfrom) >= pos.midgame_value_of_piece_on(tto)
|
||||
|| pos.type_of_piece_on(tfrom) == KING)
|
||||
&& pos.move_attacks_square(m, tto))
|
||||
return false;
|
||||
|
||||
// Case 4: Don't prune moves with good history.
|
||||
if(!H.ok_to_prune(pos.piece_on(move_from(m)), m, d))
|
||||
return false;
|
||||
if (!H.ok_to_prune(pos.piece_on(move_from(m)), m, d))
|
||||
return false;
|
||||
|
||||
// Case 5: If the moving piece in the threatened move is a slider, don't
|
||||
// prune safe moves which block its ray.
|
||||
if(!PruneBlockingMoves && threat != MOVE_NONE
|
||||
&& piece_is_slider(pos.piece_on(tfrom))
|
||||
&& bit_is_set(squares_between(tfrom, tto), mto) && pos.see(m) >= 0)
|
||||
return false;
|
||||
if ( !PruneBlockingMoves
|
||||
&& threat != MOVE_NONE
|
||||
&& piece_is_slider(pos.piece_on(tfrom))
|
||||
&& bit_is_set(squares_between(tfrom, tto), mto)
|
||||
&& pos.see(m) >= 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
//// Includes
|
||||
////
|
||||
|
||||
#include "types.h"
|
||||
#include "depth.h"
|
||||
#include "history.h"
|
||||
#include "lock.h"
|
||||
|
||||
@@ -85,6 +85,7 @@ namespace {
|
||||
o.push_back(Option("Pawn Structure (Endgame)", 100, 0, 200));
|
||||
o.push_back(Option("Passed Pawns (Middle Game)", 100, 0, 200));
|
||||
o.push_back(Option("Passed Pawns (Endgame)", 100, 0, 200));
|
||||
o.push_back(Option("Space", 100, 0, 200));
|
||||
o.push_back(Option("Aggressiveness", 100, 0, 200));
|
||||
o.push_back(Option("Cowardice", 100, 0, 200));
|
||||
o.push_back(Option("King Safety Curve", "Quadratic", COMBO));
|
||||
@@ -96,8 +97,7 @@ namespace {
|
||||
o.push_back(Option("King Safety X Intercept", 0, 0, 20));
|
||||
o.push_back(Option("King Safety Max Slope", 30, 10, 100));
|
||||
o.push_back(Option("King Safety Max Value", 500, 100, 1000));
|
||||
o.push_back(Option("Queen Contact Check Bonus", 4, 0, 8));
|
||||
o.push_back(Option("Rook Contact Check Bonus", 2, 0, 4));
|
||||
o.push_back(Option("Queen Contact Check Bonus", 3, 0, 8));
|
||||
o.push_back(Option("Queen Check Bonus", 2, 0, 4));
|
||||
o.push_back(Option("Rook Check Bonus", 1, 0, 4));
|
||||
o.push_back(Option("Bishop Check Bonus", 1, 0, 4));
|
||||
@@ -120,12 +120,11 @@ namespace {
|
||||
o.push_back(Option("Full Depth Moves (non-PV nodes)", 3, 1, 100));
|
||||
o.push_back(Option("Threat Depth", 5, 0, 100));
|
||||
o.push_back(Option("Selective Plies", 7, 0, 10));
|
||||
o.push_back(Option("Null driven IID", true));
|
||||
o.push_back(Option("Null driven IID", false));
|
||||
o.push_back(Option("Futility Pruning (Main Search)", true));
|
||||
o.push_back(Option("Futility Pruning (Quiescence Search)", true));
|
||||
o.push_back(Option("Futility Margin 0", 50, 0, 1000));
|
||||
o.push_back(Option("Futility Margin 1", 100, 0, 1000));
|
||||
o.push_back(Option("Futility Margin 2", 300, 0, 1000));
|
||||
o.push_back(Option("Futility Margin (Quiescence Search)", 50, 0, 1000));
|
||||
o.push_back(Option("Futility Margin Scale Factor (Main Search)", 100, 0, 1000));
|
||||
o.push_back(Option("Maximum Razoring Depth", 3, 0, 4));
|
||||
o.push_back(Option("Razoring Margin", 300, 150, 600));
|
||||
o.push_back(Option("LSN filtering", false));
|
||||
@@ -184,7 +183,9 @@ namespace {
|
||||
{
|
||||
std::istringstream ss(it->currentValue);
|
||||
ss >> ret;
|
||||
}
|
||||
} else
|
||||
assert(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user