Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions include/ironphoenix/tuning.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ namespace ironphoenix {
int bishopMobility = 4;
int rookMobility = 2;
int queenMobility = 1;

// King-pressure piece weights are applied once per meaningful
// attacker that reaches usable squares around an enemy king.
// Pawn pressure starts disabled because pawn pseudo-attacks were
// especially noisy in early SPRT runs. Direct attacks on the king
// receive a smaller tempo-aware bonus only for the side to move.
int kingPressurePawn = 0;
int kingPressureKnight = 3;
int kingPressureBishop = 3;
int kingPressureRook = 4;
int kingPressureQueen = 5;
int kingPressureCheckBonus = 12;
};

[[nodiscard]] SearchParameters& search() noexcept;
Expand Down
125 changes: 125 additions & 0 deletions src/eval.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include "ironphoenix/position.hpp"
#include "ironphoenix/tuning.hpp"

#include <algorithm>

namespace ironphoenix::Eval {

namespace {
Expand Down Expand Up @@ -34,11 +36,45 @@ namespace ironphoenix::Eval {
}
}

IRONPHOENIX_FORCE_INLINE int kingPressureWeight(PieceType pt) noexcept {
const auto& params = Tuning::eval();

switch (pt) {
case PAWN: return params.kingPressurePawn;
case KNIGHT: return params.kingPressureKnight;
case BISHOP: return params.kingPressureBishop;
case ROOK: return params.kingPressureRook;
case QUEEN: return params.kingPressureQueen;
default: return 0;
}
}

IRONPHOENIX_FORCE_INLINE Bitboard teamOccupancy(const Position& pos, Color c) noexcept {
const Color partner = static_cast<Color>(static_cast<unsigned>(c) ^ 2u);
return pos.occupancy(c) | pos.occupancy(partner);
}

IRONPHOENIX_FORCE_INLINE Bitboard attacksFrom(
Color c,
PieceType pt,
Square sq,
Bitboard occ) noexcept {
switch (pt) {
case PAWN:
return Geometry::PawnAttacks[c][sq];
case KNIGHT:
return Geometry::KnightAttacks[sq];
case BISHOP:
return Geometry::bishopAttacks(sq, occ);
case ROOK:
return Geometry::rookAttacks(sq, occ);
case QUEEN:
return Geometry::queenAttacks(sq, occ);
default:
return Bitboard{};
}
}

int materialForColor(const Position& pos, Color c) noexcept {
int material = 0;

Expand Down Expand Up @@ -93,6 +129,92 @@ namespace ironphoenix::Eval {
return score;
}

int kingPressureForTeam(const Position& pos, int attackingTeam) noexcept {
const Bitboard occ = pos.occupancy();
const auto& params = Tuning::eval();
const Color sideToMove = pos.sideToMove();

int score = 0;

for (unsigned enemyIndex = 0; enemyIndex < COLOR_NB; ++enemyIndex) {
const Color enemy = static_cast<Color>(enemyIndex);
if (teamOf(enemy) == attackingTeam)
continue;

const Square king = pos.kingSquare(enemy);
if (king == SQ_NONE)
continue;

// Only score king-ring squares the defending king could at least
// potentially use. Friendly pieces around the defending king are
// shields, not escape squares, so do not reward pseudo-pressure
// against those occupied squares.
const Bitboard kingZone = Geometry::KingAttacks[king]
& ~teamOccupancy(pos, enemy);

Bitboard coveredZone{};
int attackerCount = 0;
int attackUnits = 0;
bool immediateDirectAttack = false;

// Evaluate the two partners together so overlapping pressure is
// counted once. Positional king pressure may come from either
// partner, but a direct king attack is only immediately urgent
// when it belongs to the actual side to move.
for (unsigned ci = 0; ci < COLOR_NB; ++ci) {
const Color c = static_cast<Color>(ci);
if (teamOf(c) != attackingTeam)
continue;

for (unsigned ptIndex = PAWN; ptIndex <= QUEEN; ++ptIndex) {
const PieceType pt = static_cast<PieceType>(ptIndex);
const int weight = kingPressureWeight(pt);
Bitboard pieces = pos.pieces(c, pt);

while (pieces) {
const Square sq = popLsb(pieces);
const Bitboard attacks = attacksFrom(c, pt, sq, occ);
const Bitboard zoneHits = attacks & kingZone;
const bool directAttack = attacks.test(king);

// A zero weight cleanly disables this piece type from
// the positional king-pressure term while preserving
// genuinely immediate king attacks.
if (weight != 0 && zoneHits) {
++attackerCount;
attackUnits += weight;
coveredZone |= zoneHits;
}

if (c == sideToMove && directAttack)
immediateDirectAttack = true;
}
}
}

const int coverage = coveredZone.popcount();

// Require both multiple attackers and multiple useful king-zone
// squares. This filters out single-ray and one-square pressure
// that search already handles well.
if (attackerCount >= 2 && coverage >= 2) {
const int coordinatedAttackers = std::min(attackerCount, 4);

score += attackUnits;
score += coverage;
score += coordinatedAttackers - 1;
}

// King capture is terminal, but extra checking pieces do not make
// one available king capture more terminal. Award this once per
// enemy king and only to the color that can move right now.
if (immediateDirectAttack)
score += params.kingPressureCheckBonus;
}

return score;
}

int playerScore(const Position& pos, Color c) noexcept {
return materialForColor(pos, c)
+ mobilityForColor(pos, c);
Expand All @@ -112,6 +234,9 @@ namespace ironphoenix::Eval {
team1 += score;
}

team0 += kingPressureForTeam(pos, 0);
team1 += kingPressureForTeam(pos, 1);

const int score = team0 - team1;
return teamOf(pos.sideToMove()) == 0 ? score : -score;
}
Expand Down
34 changes: 32 additions & 2 deletions src/tuning.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,13 @@ namespace ironphoenix::Tuning {
|| name == "EvalKnightMobility"
|| name == "EvalBishopMobility"
|| name == "EvalRookMobility"
|| name == "EvalQueenMobility";
|| name == "EvalQueenMobility"
|| name == "EvalKingPressurePawn"
|| name == "EvalKingPressureKnight"
|| name == "EvalKingPressureBishop"
|| name == "EvalKingPressureRook"
|| name == "EvalKingPressureQueen"
|| name == "EvalKingPressureCheckBonus";
}

bool isLmrParameter(std::string_view name) noexcept {
Expand Down Expand Up @@ -155,6 +161,18 @@ namespace ironphoenix::Tuning {
p.rookMobility = value;
else if (name == "EvalQueenMobility" && inRange(value, -100, 100))
p.queenMobility = value;
else if (name == "EvalKingPressurePawn" && inRange(value, -100, 100))
p.kingPressurePawn = value;
else if (name == "EvalKingPressureKnight" && inRange(value, -100, 100))
p.kingPressureKnight = value;
else if (name == "EvalKingPressureBishop" && inRange(value, -100, 100))
p.kingPressureBishop = value;
else if (name == "EvalKingPressureRook" && inRange(value, -100, 100))
p.kingPressureRook = value;
else if (name == "EvalKingPressureQueen" && inRange(value, -100, 100))
p.kingPressureQueen = value;
else if (name == "EvalKingPressureCheckBonus" && inRange(value, -500, 500))
p.kingPressureCheckBonus = value;
else
return false;

Expand Down Expand Up @@ -228,6 +246,12 @@ namespace ironphoenix::Tuning {
<< "option name EvalBishopMobility type spin default 4 min -100 max 100\n"
<< "option name EvalRookMobility type spin default 2 min -100 max 100\n"
<< "option name EvalQueenMobility type spin default 1 min -100 max 100\n"
<< "option name EvalKingPressurePawn type spin default 0 min -100 max 100\n"
<< "option name EvalKingPressureKnight type spin default 3 min -100 max 100\n"
<< "option name EvalKingPressureBishop type spin default 3 min -100 max 100\n"
<< "option name EvalKingPressureRook type spin default 4 min -100 max 100\n"
<< "option name EvalKingPressureQueen type spin default 5 min -100 max 100\n"
<< "option name EvalKingPressureCheckBonus type spin default 12 min -500 max 500\n"
<< "option name ResetTuning type button\n";
}

Expand Down Expand Up @@ -256,7 +280,13 @@ namespace ironphoenix::Tuning {
<< " EvalKnightMobility=" << e.knightMobility
<< " EvalBishopMobility=" << e.bishopMobility
<< " EvalRookMobility=" << e.rookMobility
<< " EvalQueenMobility=" << e.queenMobility << '\n';
<< " EvalQueenMobility=" << e.queenMobility
<< " EvalKingPressurePawn=" << e.kingPressurePawn
<< " EvalKingPressureKnight=" << e.kingPressureKnight
<< " EvalKingPressureBishop=" << e.kingPressureBishop
<< " EvalKingPressureRook=" << e.kingPressureRook
<< " EvalKingPressureQueen=" << e.kingPressureQueen
<< " EvalKingPressureCheckBonus=" << e.kingPressureCheckBonus << '\n';
}

} // namespace ironphoenix::Tuning
Loading