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
8 changes: 8 additions & 0 deletions include/ironphoenix/tuning.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ namespace ironphoenix {
int bishopMobility = 4;
int rookMobility = 2;
int queenMobility = 1;

// Space rewards unique control of empty forward squares. Infiltration
// rewards non-pawn pieces that penetrate deeply toward the enemy side.
int spaceControl = 1;
int infiltrationKnight = 3;
int infiltrationBishop = 3;
int infiltrationRook = 2;
int infiltrationQueen = 1;
};

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

#include <algorithm>
#include <array>

namespace ironphoenix::Eval {

namespace {
Expand Down Expand Up @@ -34,11 +37,82 @@ namespace ironphoenix::Eval {
}
}

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

switch (pt) {
case KNIGHT: return params.infiltrationKnight;
case BISHOP: return params.infiltrationBishop;
case ROOK: return params.infiltrationRook;
case QUEEN: return params.infiltrationQueen;
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{};
}
}

// Normalize a square into forward progress for each seat. The pawn home
// line is progress 0 and the promotion line is progress 9 for all colors.
IRONPHOENIX_FORCE_INLINE int forwardProgress(Color c, Square sq) noexcept {
const int file = static_cast<int>(Geometry::fileOf(sq));
const int rank = static_cast<int>(Geometry::rankOf(sq));

switch (c) {
case RED: return rank - 1;
case BLUE: return file - 1;
case YELLOW: return 12 - rank;
case GREEN: return 12 - file;
}
return 0;
}

const Bitboard& spaceMask(Color c) noexcept {
// Build the four directional masks once, after Geometry::init() has
// initialized FileOf/RankOf. Progress 4..9 represents useful forward
// space from the middle of the board through the promotion line.
static const std::array<Bitboard, COLOR_NB> masks = [] {
std::array<Bitboard, COLOR_NB> result{};

for (unsigned ci = 0; ci < COLOR_NB; ++ci) {
const Color color = static_cast<Color>(ci);
for (unsigned si = 0; si < SQUARE_NB; ++si) {
const Square sq = static_cast<Square>(si);
const int progress = forwardProgress(color, sq);
if (progress >= 4 && progress <= 9)
result[ci].set(sq);
}
}

return result;
}();

return masks[static_cast<unsigned>(c)];
}

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

Expand Down Expand Up @@ -93,9 +167,59 @@ namespace ironphoenix::Eval {
return score;
}

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

// Space is unique control of empty squares in the player's forward
// half. Using a union keeps several pieces attacking the same square
// from artificially inflating the score.
Bitboard controlled{};
for (unsigned ptIndex = PAWN; ptIndex <= QUEEN; ++ptIndex) {
const PieceType pt = static_cast<PieceType>(ptIndex);
Bitboard pieces = pos.pieces(c, pt);

while (pieces) {
const Square sq = popLsb(pieces);
controlled |= attacksFrom(c, pt, sq, occ);
}
}

controlled &= spaceMask(c);
controlled &= ~occ;

int score = controlled.popcount() * params.spaceControl;

// Infiltration rewards minor/major pieces that physically penetrate
// into the opponent-facing side of the board. Pawns are intentionally
// excluded here because promotion potential belongs in a pawn term.
for (unsigned ptIndex = KNIGHT; ptIndex <= QUEEN; ++ptIndex) {
const PieceType pt = static_cast<PieceType>(ptIndex);
const int weight = infiltrationWeight(pt);
if (weight == 0)
continue;

Bitboard pieces = pos.pieces(c, pt);
while (pieces) {
const Square sq = popLsb(pieces);
const int progress = forwardProgress(c, sq);
if (progress < 6)
continue;

// Depth 1 begins at progress 6. Cap the depth component so a
// piece deep in an outer arm does not overwhelm the HCE.
const int depth = std::min(progress - 5, 6);
score += depth * weight;
}
}

return score;
}

int playerScore(const Position& pos, Color c) noexcept {
return materialForColor(pos, c)
+ mobilityForColor(pos, c);
+ mobilityForColor(pos, c)
+ spaceAndInfiltrationForColor(pos, c);
}

int handcraftedEvaluate(const Position& pos) noexcept {
Expand Down
29 changes: 27 additions & 2 deletions src/tuning.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,12 @@ namespace ironphoenix::Tuning {
|| name == "EvalKnightMobility"
|| name == "EvalBishopMobility"
|| name == "EvalRookMobility"
|| name == "EvalQueenMobility";
|| name == "EvalQueenMobility"
|| name == "EvalSpaceControl"
|| name == "EvalInfiltrationKnight"
|| name == "EvalInfiltrationBishop"
|| name == "EvalInfiltrationRook"
|| name == "EvalInfiltrationQueen";
}

bool isLmrParameter(std::string_view name) noexcept {
Expand Down Expand Up @@ -155,6 +160,16 @@ namespace ironphoenix::Tuning {
p.rookMobility = value;
else if (name == "EvalQueenMobility" && inRange(value, -100, 100))
p.queenMobility = value;
else if (name == "EvalSpaceControl" && inRange(value, -100, 100))
p.spaceControl = value;
else if (name == "EvalInfiltrationKnight" && inRange(value, -100, 100))
p.infiltrationKnight = value;
else if (name == "EvalInfiltrationBishop" && inRange(value, -100, 100))
p.infiltrationBishop = value;
else if (name == "EvalInfiltrationRook" && inRange(value, -100, 100))
p.infiltrationRook = value;
else if (name == "EvalInfiltrationQueen" && inRange(value, -100, 100))
p.infiltrationQueen = value;
else
return false;

Expand Down Expand Up @@ -228,6 +243,11 @@ 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 EvalSpaceControl type spin default 1 min -100 max 100\n"
<< "option name EvalInfiltrationKnight type spin default 3 min -100 max 100\n"
<< "option name EvalInfiltrationBishop type spin default 3 min -100 max 100\n"
<< "option name EvalInfiltrationRook type spin default 2 min -100 max 100\n"
<< "option name EvalInfiltrationQueen type spin default 1 min -100 max 100\n"
<< "option name ResetTuning type button\n";
}

Expand Down Expand Up @@ -256,7 +276,12 @@ namespace ironphoenix::Tuning {
<< " EvalKnightMobility=" << e.knightMobility
<< " EvalBishopMobility=" << e.bishopMobility
<< " EvalRookMobility=" << e.rookMobility
<< " EvalQueenMobility=" << e.queenMobility << '\n';
<< " EvalQueenMobility=" << e.queenMobility
<< " EvalSpaceControl=" << e.spaceControl
<< " EvalInfiltrationKnight=" << e.infiltrationKnight
<< " EvalInfiltrationBishop=" << e.infiltrationBishop
<< " EvalInfiltrationRook=" << e.infiltrationRook
<< " EvalInfiltrationQueen=" << e.infiltrationQueen << '\n';
}

} // namespace ironphoenix::Tuning
Loading