namespace {
- // Table used to drive the king towards the edge of the board
+ // Used to drive the king towards the edge of the board
// in KX vs K and KQ vs KR endgames.
- constexpr int PushToEdges[SQUARE_NB] = {
- 100, 90, 80, 70, 70, 80, 90, 100,
- 90, 70, 60, 50, 50, 60, 70, 90,
- 80, 60, 40, 30, 30, 40, 60, 80,
- 70, 50, 30, 20, 20, 30, 50, 70,
- 70, 50, 30, 20, 20, 30, 50, 70,
- 80, 60, 40, 30, 30, 40, 60, 80,
- 90, 70, 60, 50, 50, 60, 70, 90,
- 100, 90, 80, 70, 70, 80, 90, 100
- };
-
- // Table used to drive the king towards a corner square of the
- // right color in KBN vs K endgames.
- constexpr int PushToCorners[SQUARE_NB] = {
- 6400, 6080, 5760, 5440, 5120, 4800, 4480, 4160,
- 6080, 5760, 5440, 5120, 4800, 4480, 4160, 4480,
- 5760, 5440, 4960, 4480, 4480, 4000, 4480, 4800,
- 5440, 5120, 4480, 3840, 3520, 4480, 4800, 5120,
- 5120, 4800, 4480, 3520, 3840, 4480, 5120, 5440,
- 4800, 4480, 4000, 4480, 4480, 4960, 5440, 5760,
- 4480, 4160, 4480, 4800, 5120, 5440, 5760, 6080,
- 4160, 4480, 4800, 5120, 5440, 5760, 6080, 6400
- };
-
- // Table used to drive the king towards the edge of the board
- inline int push_to_edge(Square s) {
- int rd = edge_distance(rank_of(s)), fd = edge_distance(file_of(s));
++ inline int push_to_edge(Square s, const Position& pos) {
++ int rd = edge_distance(rank_of(s), pos.max_rank()), fd = edge_distance(file_of(s), pos.max_file());
+ return 90 - (7 * fd * fd / 2 + 7 * rd * rd / 2);
+ }
+
+ // Used to drive the king towards A1H8 corners in KBN vs K endgames.
- inline int push_to_corner(Square s) {
- return abs(7 - rank_of(s) - file_of(s));
++ inline int push_to_corner(Square s, const Position& pos) {
++ return abs((pos.max_file() + pos.max_rank()) / 2 - rank_of(s) - file_of(s));
++ }
++
++ // Used to drive the king towards the edge of the board
+ // in KSF vs K.
- constexpr int PushToOpposingSideEdges[SQUARE_NB] = {
- 20, 10, 5, 0, 0, 5, 10, 20,
- 20, 10, 5, 0, 0, 5, 10, 20,
- 30, 20, 10, 0, 0, 10, 20, 30,
- 50, 40, 20, 10, 10, 20, 40, 50,
- 60, 50, 40, 30, 30, 40, 50, 60,
- 70, 60, 50, 40, 40, 50, 60, 70,
- 90, 70, 60, 50, 50, 60, 70, 90,
- 100, 90, 80, 70, 70, 80, 90, 100
- };
++ inline int push_to_opposing_edge(Square s, const Position& pos) {
++ int rd = rank_of(s), fd = edge_distance(file_of(s), pos.max_file());
++ return 20 - (7 * fd * fd / 2 - 7 * rd * rd / 4);
+ }
// Drive a piece close to or away from another piece
inline int push_close(Square s1, Square s2) { return 140 - 20 * distance(s1, s2); }
assert(pos.count<PAWN>(strongSide) == 1);
- if (file_of(pos.square<PAWN>(strongSide)) >= FILE_E)
- sq = flip_file(sq);
+ if (file_of(pos.square<PAWN>(strongSide)) > pos.max_file() / 2)
+ sq = flip_file(sq, pos.max_file());
- return strongSide == WHITE ? sq : flip_rank(sq);
+ return strongSide == WHITE ? sq : flip_rank(sq, pos.max_rank());
}
- // Map the square to an 8x8 board
- Square map_to_standard_board(const Position& pos, Square s) {
- File f = file_of(s) > pos.max_file() / 2 ? File(FILE_H - pos.max_file() + file_of(s)) : file_of(s);
- Rank r = rank_of(s) > pos.max_rank() / 2 ? Rank(RANK_8 - pos.max_rank() + rank_of(s)) : rank_of(s);
- return Square(r * 8 + f);
- }
-
} // namespace
Value result = pos.non_pawn_material(strongSide)
+ pos.count<PAWN>(strongSide) * PawnValueEg
- + PushToEdges[map_to_standard_board(pos, loserKSq)]
- + push_to_edge(loserKSq)
++ + push_to_edge(loserKSq, pos)
+ push_close(winnerKSq, loserKSq);
if ( pos.count<QUEEN>(strongSide)
// If our bishop does not attack A1/H8, we flip the enemy king square
// to drive to opposite corners (A8/H1).
- Value result = VALUE_KNOWN_WIN
+ Value result = (VALUE_KNOWN_WIN + 3520)
+ push_close(winnerKSq, loserKSq)
- + PushToCorners[map_to_standard_board(pos, relative_square(opposite_colors(bishopSq, SQ_A1) ? BLACK : WHITE, loserKSq, pos.max_rank()))];
- + 420 * push_to_corner(opposite_colors(bishopSq, SQ_A1) ? flip_file(loserKSq) : loserKSq);
++ + 420 * push_to_corner(opposite_colors(bishopSq, SQ_A1) ? flip_file(loserKSq, pos.max_file()) : loserKSq, pos);
assert(abs(result) < VALUE_TB_WIN_IN_MAX_PLY);
return strongSide == pos.side_to_move() ? result : -result;
assert(verify_material(pos, strongSide, RookValueMg, 0));
assert(verify_material(pos, weakSide, BishopValueMg, 0));
- Value result = Value(PushToEdges[map_to_standard_board(pos, pos.square<KING>(weakSide))]);
- Value result = Value(push_to_edge(pos.square<KING>(weakSide)));
++ Value result = Value(push_to_edge(pos.square<KING>(weakSide), pos));
return strongSide == pos.side_to_move() ? result : -result;
}
Square bksq = pos.square<KING>(weakSide);
Square bnsq = pos.square<KNIGHT>(weakSide);
- Value result = Value(PushToEdges[map_to_standard_board(pos, bksq)] + push_away(bksq, bnsq));
- Value result = Value(push_to_edge(bksq) + push_away(bksq, bnsq));
++ Value result = Value(push_to_edge(bksq, pos) + push_away(bksq, bnsq));
return strongSide == pos.side_to_move() ? result : -result;
}
Value result = QueenValueEg
- RookValueEg
- + PushToEdges[map_to_standard_board(pos, loserKSq)]
- + push_to_edge(loserKSq)
++ + push_to_edge(loserKSq, pos)
+ push_close(winnerKSq, loserKSq);
return strongSide == pos.side_to_move() ? result : -result;
assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
Value result = PawnValueEg
- + 2 * PushToEdges[map_to_standard_board(pos, pos.square<KING>(weakSide))]
- + 2 * push_to_edge(pos.square<KING>(weakSide))
++ + 2 * push_to_edge(pos.square<KING>(weakSide), pos)
- 10 * relative_rank(weakSide, pos.square<PAWN>(weakSide));
return strongSide == pos.side_to_move() ? result : -result;
template<> Value Endgame<KNNK>::operator()(const Position&) const { return VALUE_DRAW; }
+/// KFsPs vs K.
+template<>
+Value Endgame<KFsPsK>::operator()(const Position& pos) const {
+
+ assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
+
+ Square winnerKSq = pos.square<KING>(strongSide);
+ Square loserKSq = pos.square<KING>(weakSide);
+
+ Value result = pos.non_pawn_material(strongSide)
+ + pos.count<PAWN>(strongSide) * PawnValueEg
- + PushToEdges[map_to_standard_board(pos, loserKSq)]
++ + push_to_edge(loserKSq, pos)
+ + push_close(winnerKSq, loserKSq);
+
+ if ( pos.count<FERS>(strongSide) >= 3
+ && ( DarkSquares & pos.pieces(strongSide, FERS))
+ && (~DarkSquares & pos.pieces(strongSide, FERS)))
+ result = std::min(result + VALUE_KNOWN_WIN, VALUE_TB_WIN_IN_MAX_PLY - 1);
+ else if (pos.count<FERS>(strongSide) + pos.count<PAWN>(strongSide) < 3)
+ return VALUE_DRAW;
+ else
+ {
+ bool dark = DarkSquares & pos.pieces(strongSide, FERS);
+ bool light = ~DarkSquares & pos.pieces(strongSide, FERS);
+
+ // Determine the color of ferzes from promoting pawns
+ Bitboard b = pos.pieces(strongSide, PAWN);
+ while (b && (!dark || !light))
+ {
+ if (file_of(pop_lsb(&b)) % 2 != relative_rank(strongSide, pos.promotion_rank(), pos.max_rank()) % 2)
+ light = true;
+ else
+ dark = true;
+ }
+ if (!dark || !light)
+ return VALUE_DRAW; // we can not checkmate with same colored ferzes
+ }
+
+ return strongSide == pos.side_to_move() ? result : -result;
+}
+
+
+/// Mate with KNS vs K.
+template<>
+Value Endgame<KNSK>::operator()(const Position& pos) const {
+
+ assert(verify_material(pos, strongSide, KnightValueMg + SilverValueMg, 0));
+ assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
+
+ Square winnerKSq = pos.square<KING>(strongSide);
+ Square loserKSq = pos.square<KING>(weakSide);
+
+ Value result = VALUE_KNOWN_WIN
+ + push_close(winnerKSq, loserKSq)
- + PushToOpposingSideEdges[map_to_standard_board(pos, relative_square(strongSide, loserKSq, pos.max_rank()))];
++ + push_to_opposing_edge(relative_square(strongSide, loserKSq, pos.max_rank()), pos);
+
+ return strongSide == pos.side_to_move() ? result : -result;
+}
+
+
+/// KNF vs K. Can only be won if the weaker side's king
+/// is close to a corner of the same color as the fers.
+template<>
+Value Endgame<KNFK>::operator()(const Position& pos) const {
+
+ assert(verify_material(pos, strongSide, KnightValueMg + FersValueMg, 0));
+ assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
+
+ Square winnerKSq = pos.square<KING>(strongSide);
+ Square loserKSq = pos.square<KING>(weakSide);
+ Square fersSq = pos.square<FERS>(strongSide);
+
+ // tries to drive toward corners A1 or H8. If we have a
+ // fers that cannot reach the above squares, we flip the kings in order
+ // to drive the enemy toward corners A8 or H1.
+ if (opposite_colors(fersSq, SQ_A1))
+ {
+ winnerKSq = relative_square(BLACK, winnerKSq, pos.max_rank());
+ loserKSq = relative_square(BLACK, loserKSq, pos.max_rank());
+ }
+
+ Value result = Value(push_close(winnerKSq, loserKSq))
- + (PushToCorners[map_to_standard_board(pos, loserKSq)] - 3000) / 10;
++ + 50 * push_to_corner(loserKSq, pos);
+
+ return strongSide == pos.side_to_move() ? result : -result;
+}
+
+
+/// KNSFKR vs K.
+template<>
+Value Endgame<KNSFKR>::operator()(const Position& pos) const {
+
+ assert(verify_material(pos, strongSide, KnightValueMg + SilverValueMg + FersValueMg, 0));
+ assert(verify_material(pos, weakSide, RookValueMg, 0));
+
+ Square winnerKSq = pos.square<KING>(strongSide);
+ Square loserKSq = pos.square<KING>(weakSide);
+
+ Value result = KnightValueEg + SilverValueEg + FersValueEg - RookValueEg
+ + push_close(winnerKSq, loserKSq)
- + PushToOpposingSideEdges[map_to_standard_board(pos, relative_square(strongSide, loserKSq, pos.max_rank()))];
++ + push_to_opposing_edge(relative_square(strongSide, loserKSq, pos.max_rank()), pos);
+
+ return strongSide == pos.side_to_move() ? result : -result;
+}
+
+
+/// Mate with KSF vs K.
+template<>
+Value Endgame<KSFK>::operator()(const Position& pos) const {
+
+ assert(verify_material(pos, strongSide, SilverValueMg + FersValueMg, 0));
+ assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
+
+ Square winnerKSq = pos.square<KING>(strongSide);
+ Square loserKSq = pos.square<KING>(weakSide);
+
+ Value result = VALUE_KNOWN_WIN
+ + push_close(winnerKSq, loserKSq)
- + PushToOpposingSideEdges[map_to_standard_board(pos, relative_square(strongSide, loserKSq, pos.max_rank()))];
++ + push_to_opposing_edge(relative_square(strongSide, loserKSq, pos.max_rank()), pos);
+
+ return strongSide == pos.side_to_move() ? result : -result;
+}
+
+
+/// Mate with KSF vs KF.
+template<>
+Value Endgame<KSFKF>::operator()(const Position& pos) const {
+
+ assert(verify_material(pos, strongSide, SilverValueMg + FersValueMg, 0));
+ assert(verify_material(pos, weakSide, FersValueMg, 0));
+
+ Square winnerKSq = pos.square<KING>(strongSide);
+ Square loserKSq = pos.square<KING>(weakSide);
+ Square fersSq = pos.square<FERS>(weakSide);
+
+ Value result = SilverValueEg
+ + push_close(winnerKSq, loserKSq)
+ + push_away(fersSq, loserKSq)
- + PushToOpposingSideEdges[map_to_standard_board(pos, relative_square(strongSide, loserKSq, pos.max_rank()))];
++ + push_to_opposing_edge(relative_square(strongSide, loserKSq, pos.max_rank()), pos);
+
+ return strongSide == pos.side_to_move() ? result : -result;
+}
+
+
+/// KR vs KS
+template<>
+Value Endgame<KRKS>::operator()(const Position& pos) const {
+
+ assert(verify_material(pos, strongSide, RookValueMg, 0));
+ assert(verify_material(pos, weakSide, SilverValueMg, 0));
+
+ Square winnerKSq = pos.square<KING>(strongSide);
+ Square loserKSq = pos.square<KING>(weakSide);
+
+ Value result = RookValueEg
+ - SilverValueEg
- + PushToEdges[map_to_standard_board(pos, loserKSq)]
++ + push_to_edge(loserKSq, pos)
+ + push_close(winnerKSq, loserKSq);
+
+ return strongSide == pos.side_to_move() ? result : -result;
+}
+
+
/// KB and one or more pawns vs K. It checks for draws with rook pawns and
/// a bishop of the wrong color. If such a draw is detected, SCALE_FACTOR_DRAW
/// is returned. If not, the return value is SCALE_FACTOR_NONE, i.e. no scaling
#include <algorithm>
#include "types.h"
+ #include "bitboard.h"
+#include "piece.h"
+#include "variant.h"
namespace PSQT {
// init() initializes piece-square tables: the white halves of the tables are
// copied from Bonus[] adding the piece value, then the black halves of the
// tables are initialized by flipping and changing the sign of the white scores.
-void init() {
+void init(const Variant* v) {
- for (Piece pc = W_PAWN; pc <= W_KING; ++pc)
+ PieceType strongestPiece = NO_PIECE_TYPE;
+ for (PieceType pt : v->pieceTypes)
+ if (PieceValue[MG][pt] > PieceValue[MG][strongestPiece])
+ strongestPiece = pt;
+
+ Value maxPromotion = VALUE_ZERO;
+ for (PieceType pt : v->promotionPieceTypes)
+ maxPromotion = std::max(maxPromotion, PieceValue[EG][pt]);
+
+ for (PieceType pt = PAWN; pt <= KING; ++pt)
{
+ Piece pc = make_piece(WHITE, pt);
+
Score score = make_score(PieceValue[MG][pc], PieceValue[EG][pc]);
- for (Square s = SQ_A1; s <= SQ_H8; ++s)
+ // Consider promotion types in pawn score
+ if (pt == PAWN)
+ score -= make_score(0, (QueenValueEg - maxPromotion) / 100);
+
+ // Scale slider piece values with board size
+ const PieceInfo* pi = pieceMap.find(pt)->second;
+ bool isSlider = pi->sliderQuiet.size() || pi->sliderCapture.size() || pi->hopperQuiet.size() || pi->hopperCapture.size();
+ bool isPawn = !isSlider && pi->stepsQuiet.size() && !std::any_of(pi->stepsQuiet.begin(), pi->stepsQuiet.end(), [](Direction d) { return d < SOUTH / 2; });
+ bool isSlowLeaper = !isSlider && !std::any_of(pi->stepsQuiet.begin(), pi->stepsQuiet.end(), [](Direction d) { return dist(d) > 1; });
+
+ if (isSlider)
+ {
+ constexpr int lc = 5;
+ constexpr int rm = 5;
+ constexpr int r0 = rm + RANK_8;
+ int r1 = rm + (v->maxRank + v->maxFile) / 2;
+ int leaper = pi->stepsQuiet.size() + pi->stepsCapture.size();
+ int slider = pi->sliderQuiet.size() + pi->sliderCapture.size() + pi->hopperQuiet.size() + pi->hopperCapture.size();
+ score = make_score(mg_value(score) * (lc * leaper + r1 * slider) / (lc * leaper + r0 * slider),
+ eg_value(score) * (lc * leaper + r1 * slider) / (lc * leaper + r0 * slider));
+ }
+
+ // Increase leapers' value in makpong
+ if (v->makpongRule)
+ {
+ if (std::any_of(pi->stepsCapture.begin(), pi->stepsCapture.end(), [](Direction d) { return dist(d) > 1; })
+ && !pi->lameLeaper)
+ score = make_score(mg_value(score) * 4200 / (3500 + mg_value(score)),
+ eg_value(score) * 4700 / (3500 + mg_value(score)));
+ }
+
+ // For drop variants, halve the piece values
+ if (v->capturesToHand)
+ score = make_score(mg_value(score) * 3500 / (7000 + mg_value(score)),
+ eg_value(score) * 3500 / (7000 + eg_value(score)));
+ else if (!v->checking)
+ score = make_score(mg_value(score) * 2000 / (3500 + mg_value(score)),
+ eg_value(score) * 2200 / (3500 + eg_value(score)));
+ else if (v->twoBoards)
+ score = make_score(mg_value(score) * 7000 / (7000 + mg_value(score)),
+ eg_value(score) * 7000 / (7000 + eg_value(score)));
+ else if (v->checkCounting)
+ score = make_score(mg_value(score) * (40000 + mg_value(score)) / 41000,
+ eg_value(score) * (30000 + eg_value(score)) / 31000);
+ else if (pt == strongestPiece)
+ score += make_score(std::max(QueenValueMg - PieceValue[MG][pt], VALUE_ZERO) / 20,
+ std::max(QueenValueEg - PieceValue[EG][pt], VALUE_ZERO) / 20);
+
+ // For antichess variants, use negative piece values
+ if ( v->extinctionValue == VALUE_MATE
+ && v->extinctionPieceTypes.find(ALL_PIECES) != v->extinctionPieceTypes.end())
+ score = -make_score(mg_value(score) / 8, eg_value(score) / 8 / (1 + !pi->sliderCapture.size()));
+
+ for (Square s = SQ_A1; s <= SQ_MAX; ++s)
{
- File f = std::max(std::min(file_of(s), File(v->maxFile - file_of(s))), FILE_A);
- File f = edge_distance(file_of(s));
- psq[ pc][ s] = score + (type_of(pc) == PAWN ? PBonus[rank_of(s)][file_of(s)]
- : Bonus[pc][rank_of(s)][f]);
- psq[~pc][flip_rank(s)] = -psq[pc][s];
++ File f = std::max(edge_distance(file_of(s), v->maxFile), FILE_A);
+ Rank r = rank_of(s);
+ psq[ pc][ s] = score + ( pt == PAWN ? PBonus[std::min(r, RANK_8)][std::min(file_of(s), FILE_H)]
+ : pt == KING ? KingBonus[std::min(r, RANK_8)][std::min(f, FILE_D)] * (1 + v->capturesToHand)
+ : pt <= QUEEN ? Bonus[pc][std::min(r, RANK_8)][std::min(f, FILE_D)]
+ : pt == HORSE ? Bonus[KNIGHT][std::min(r, RANK_8)][std::min(f, FILE_D)]
+ : isSlider ? make_score(5, 5) * (2 * f + std::max(std::min(r, Rank(v->maxRank - r)), RANK_1) - v->maxFile - 1)
+ : isPawn ? make_score(5, 5) * (2 * f - v->maxFile)
+ : make_score(10, 10) * (1 + isSlowLeaper) * (f + std::max(std::min(r, Rank(v->maxRank - r)), RANK_1) - v->maxFile / 2));
+ if (pt == SOLDIER && r < v->soldierPromotionRank)
+ psq[pc][s] -= score * (v->soldierPromotionRank - r) / (4 + f);
+ if (v->enclosingDrop)
+ {
+ if (f == FILE_A && (r == RANK_1 || r == v->maxRank))
+ psq[pc][s] += make_score(1000, 1000);
+ }
+ psq[~pc][rank_of(s) <= v->maxRank ? flip_rank(s, v->maxRank) : s] = -psq[pc][s];
}
+ // pieces in pocket
+ psq[ pc][SQ_NONE] = score + make_score(45, 10);
+ psq[~pc][SQ_NONE] = -psq[pc][SQ_NONE];
}
}