Support customization of n-fold repetition and n-move rule.
- Fix fourfold rule for minishogi and other shogi variants.
- Adjust n-move rule to 70 for shatranj.
- Disable n-move rule for makruk and shogi variants.
Value Evaluation<T>::value() {
assert(!pos.checkers());
- assert(!pos.is_variant_end());
+ assert(!pos.is_immediate_game_end());
// Probe the material hash table
me = Material::probe(pos);
template<>
ExtMove* generate<LEGAL>(const Position& pos, ExtMove* moveList) {
- if (pos.is_variant_end())
+ if (pos.is_immediate_game_end())
return moveList;
ExtMove* cur = moveList;
}
-/// Position::is_draw() tests whether the position is drawn by 50-move rule
-/// or by repetition. It does not detect stalemates.
+/// Position::is_optinal_game_end() tests whether the position may end the game by
+/// 50-move rule, by repetition, or a variant rule that allows a player to claim a game result.
-bool Position::is_draw(int ply) const {
+bool Position::is_optional_game_end(Value& result, int ply) const {
- if (st->rule50 > 99 && (!checkers() || MoveList<LEGAL>(*this).size()))
+ // n-move rule
+ if (n_move_rule() && st->rule50 > (2 * n_move_rule() - 1) && (!checkers() || MoveList<LEGAL>(*this).size()))
+ {
+ result = VALUE_DRAW;
return true;
+ }
- int end = captures_to_hand() ? st->pliesFromNull : std::min(st->rule50, st->pliesFromNull);
+ // n-fold repetition
+ if (n_fold_rule())
+ {
+ int end = captures_to_hand() ? st->pliesFromNull : std::min(st->rule50, st->pliesFromNull);
- if (end < 4)
- return false;
+ if (end < 4)
+ return false;
- StateInfo* stp = st->previous->previous;
- int cnt = 0;
+ StateInfo* stp = st->previous->previous;
+ int cnt = 0;
- for (int i = 4; i <= end; i += 2)
- {
- stp = stp->previous->previous;
+ for (int i = 4; i <= end; i += 2)
+ {
+ stp = stp->previous->previous;
+
+ // Return a draw score if a position repeats once earlier but strictly
+ // after the root, or repeats twice before or at the root.
+ if ( stp->key == st->key
+ && ++cnt + 1 == (ply > i ? 2 : n_fold_rule()))
+ {
+ result = convert_mate_value(var->nFoldValueAbsolute && sideToMove == BLACK ? -var->nFoldValue : var->nFoldValue, ply);
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/// Position::is_immediate_game_end() tests whether the position ends the game
+/// immediately by a variant rule, i.e., there are no more legal moves.
+/// It does not not detect stalemates.
- // Return a draw score if a position repeats once earlier but strictly
- // after the root, or repeats twice before or at the root.
- if ( stp->key == st->key
- && ++cnt + (ply > i) == 2)
- return true;
+bool Position::is_immediate_game_end(Value& result, int ply) const {
+
+ // bare king rule
+ if ( bare_king_value() != VALUE_NONE
+ && !bare_king_move()
+ && !(count<ALL_PIECES>(sideToMove) - count<KING>(sideToMove)))
+ {
+ result = bare_king_value(ply);
+ return true;
+ }
+ if ( bare_king_value() != VALUE_NONE
+ && bare_king_move()
+ && !(count<ALL_PIECES>(~sideToMove) - count<KING>(~sideToMove)))
+ {
+ result = -bare_king_value(ply);
+ return true;
+ }
+ // extinction
+ if (extinction_value() != VALUE_NONE)
+ {
+ for (PieceType pt : extinction_piece_types())
+ if (!count(WHITE, pt) || !count(BLACK, pt))
+ {
+ result = !count(sideToMove, pt) ? extinction_value(ply) : -extinction_value(ply);
+ return true;
+ }
+ }
+ // capture the flag
+ if ( capture_the_flag_piece()
+ && !flag_move()
+ && (capture_the_flag(~sideToMove) & pieces(~sideToMove, capture_the_flag_piece())))
+ {
+ result = mated_in(ply);
+ return true;
+ }
+ if ( capture_the_flag_piece()
+ && flag_move()
+ && (capture_the_flag(sideToMove) & pieces(sideToMove, capture_the_flag_piece())))
+ {
+ result = (capture_the_flag(~sideToMove) & pieces(~sideToMove, capture_the_flag_piece()))
+ && sideToMove == WHITE ? VALUE_DRAW : mate_in(ply);
+ return true;
+ }
+ // nCheck
+ if (max_check_count() && st->checksGiven[~sideToMove] == max_check_count())
+ {
+ result = mated_in(ply);
+ return true;
+ }
+ // Connect-n
+ if (connect_n() > 0)
+ {
+ Bitboard b;
+ for (Direction d : {NORTH, NORTH_EAST, EAST, SOUTH_EAST})
+ {
+ b = pieces(~sideToMove);
+ for (int i = 1; i < connect_n() && b; i++)
+ b &= shift(d, b);
+ if (b)
+ {
+ result = mated_in(ply);
+ return true;
+ }
+ }
}
return false;
int end = std::min(st->rule50, st->pliesFromNull);
- if (end < 3)
+ if (end < 3 || var->nFoldValue != VALUE_DRAW)
return false;
Key originalKey = st->key;
bool shogi_doubled_pawn() const;
bool immobility_illegal() const;
// winning conditions
+ int n_move_rule() const;
+ int n_fold_rule() const;
Value stalemate_value(int ply = 0) const;
Value checkmate_value(int ply = 0) const;
Value bare_king_value(int ply = 0) const;
CheckCount max_check_count() const;
int connect_n() const;
CheckCount checks_given(Color c) const;
- bool is_variant_end() const;
- bool is_variant_end(Value& result, int ply = 0) const;
// Variant-specific properties
int count_in_hand(Color c, PieceType pt) const;
int game_ply() const;
bool is_chess960() const;
Thread* this_thread() const;
- bool is_draw(int ply) const;
+ bool is_immediate_game_end() const;
+ bool is_game_end(Value& result, int ply = 0) const;
+ bool is_optional_game_end(Value& result, int ply = 0) const;
+ bool is_immediate_game_end(Value& result, int ply = 0) const;
bool has_game_cycle(int ply) const;
bool has_repeated() const;
int rule50_count() const;
return var->immobilityIllegal;
}
+inline int Position::n_move_rule() const {
+ assert(var != nullptr);
+ return var->nMoveRule;
+}
+
+inline int Position::n_fold_rule() const {
+ assert(var != nullptr);
+ return var->nFoldRule;
+}
+
inline Value Position::stalemate_value(int ply) const {
assert(var != nullptr);
return convert_mate_value(var->stalemateValue, ply);
return st->checksGiven[c];
}
-inline bool Position::is_variant_end() const {
+inline bool Position::is_immediate_game_end() const {
Value result;
- return is_variant_end(result);
+ return is_immediate_game_end(result);
}
-inline bool Position::is_variant_end(Value& result, int ply) const {
- // bare king rule
- if ( bare_king_value() != VALUE_NONE
- && !bare_king_move()
- && !(count<ALL_PIECES>(sideToMove) - count<KING>(sideToMove)))
- {
- result = bare_king_value(ply);
- return true;
- }
- if ( bare_king_value() != VALUE_NONE
- && bare_king_move()
- && !(count<ALL_PIECES>(~sideToMove) - count<KING>(~sideToMove)))
- {
- result = -bare_king_value(ply);
- return true;
- }
- // extinction
- if (extinction_value() != VALUE_NONE)
- {
- for (PieceType pt : extinction_piece_types())
- if (!count(WHITE, pt) || !count(BLACK, pt))
- {
- result = !count(sideToMove, pt) ? extinction_value(ply) : -extinction_value(ply);
- return true;
- }
- }
- // capture the flag
- if ( capture_the_flag_piece()
- && !flag_move()
- && (capture_the_flag(~sideToMove) & pieces(~sideToMove, capture_the_flag_piece())))
- {
- result = mated_in(ply);
- return true;
- }
- if ( capture_the_flag_piece()
- && flag_move()
- && (capture_the_flag(sideToMove) & pieces(sideToMove, capture_the_flag_piece())))
- {
- result = (capture_the_flag(~sideToMove) & pieces(~sideToMove, capture_the_flag_piece()))
- && sideToMove == WHITE ? VALUE_DRAW : mate_in(ply);
- return true;
- }
- // nCheck
- if (max_check_count() && st->checksGiven[~sideToMove] == max_check_count())
- {
- result = mated_in(ply);
- return true;
- }
- // Connect-n
- if (connect_n() > 0)
- {
- Bitboard b;
- for (Direction d : {NORTH, NORTH_EAST, EAST, SOUTH_EAST})
- {
- b = pieces(~sideToMove);
- for (int i = 1; i < connect_n() && b; i++)
- b &= shift(d, b);
- if (b)
- {
- result = mated_in(ply);
- return true;
- }
- }
- }
- return false;
+inline bool Position::is_game_end(Value& result, int ply) const {
+ return is_immediate_game_end(result, ply) || is_optional_game_end(result, ply);
}
inline Color Position::side_to_move() const {
rootMoves.emplace_back(MOVE_NONE);
Value variantResult;
sync_cout << "info depth 0 score "
- << UCI::value( rootPos.is_variant_end(variantResult) ? variantResult
+ << UCI::value( rootPos.is_game_end(variantResult) ? variantResult
: rootPos.checkers() ? rootPos.checkmate_value() : rootPos.stalemate_value())
<< sync_endl;
}
if (!rootNode)
{
Value variantResult;
- if (pos.is_variant_end(variantResult, ss->ply))
+ if (pos.is_game_end(variantResult, ss->ply))
return variantResult;
// Step 2. Check for aborted search and immediate draw
if ( Threads.stop.load(std::memory_order_relaxed)
- || pos.is_draw(ss->ply)
|| ss->ply >= MAX_PLY)
return (ss->ply >= MAX_PLY && !inCheck) ? evaluate(pos) : VALUE_DRAW;
inCheck = pos.checkers();
moveCount = 0;
- Value variantResult;
- if (pos.is_variant_end(variantResult, ss->ply))
- return variantResult;
+ Value gameResult;
+ if (pos.is_game_end(gameResult, ss->ply))
+ return gameResult;
- // Check for an immediate draw or maximum ply reached
- if ( pos.is_draw(ss->ply)
- || ss->ply >= MAX_PLY)
- return (ss->ply >= MAX_PLY && !inCheck) ? evaluate(pos) : VALUE_DRAW;
+ // Check for maximum ply reached
+ if (ss->ply >= MAX_PLY)
+ return !inCheck ? evaluate(pos) : VALUE_DRAW;
assert(0 <= ss->ply && ss->ply < MAX_PLY);
v->promotionPieceTypes = {MET};
v->doubleStep = false;
v->castling = false;
+ v->nMoveRule = 0;
return v;
}
Variant* asean_variant() {
v->bareKingValue = -VALUE_MATE;
v->bareKingMove = true;
v->stalemateValue = -VALUE_MATE;
+ v->nMoveRule = 70;
return v;
}
Variant* amazon_variant() {
v->immobilityIllegal = false;
return v;
}
- Variant* minishogi_variant() {
+ Variant* minishogi_variant_base() {
Variant* v = fairy_variant_base();
v->variantTemplate = "shogi";
- v->pocketSize = 5;
v->maxRank = RANK_5;
v->maxFile = FILE_E;
v->reset_pieces();
v->immobilityIllegal = true;
v->shogiPawnDropMateIllegal = true;
v->stalemateValue = -VALUE_MATE;
+ v->nFoldRule = 4;
+ v->nMoveRule = 0;
return v;
}
- Variant* minishogi_variant_base() {
- Variant* v = minishogi_variant();
- v->pocketSize = 0;
+ Variant* minishogi_variant() {
+ Variant* v = minishogi_variant_base();
+ v->pocketSize = 5;
+ v->nFoldValue = -VALUE_MATE;
+ v->nFoldValueAbsolute = true;
return v;
}
Variant* kyotoshogi_variant() {
bool shogiDoubledPawn = true;
bool immobilityIllegal = false;
// game end
+ int nMoveRule = 50;
+ int nFoldRule = 3;
+ Value nFoldValue = VALUE_DRAW;
+ bool nFoldValueAbsolute = false;
Value stalemateValue = VALUE_DRAW;
Value checkmateValue = -VALUE_MATE;
bool shogiPawnDropMateIllegal = false;