Skip to content

Commit

Permalink
Merge remote-tracking branch 'fairy/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
ianfab committed Apr 29, 2022
2 parents efa360b + 9022a70 commit 092be2c
Show file tree
Hide file tree
Showing 10 changed files with 309 additions and 11 deletions.
5 changes: 5 additions & 0 deletions src/bitboard.h
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,11 @@ constexpr bool more_than_one(Bitboard b) {
return b & (b - 1);
}


inline Bitboard undo_move_board(Bitboard b, Move m) {
return (from_sq(m) != SQ_NONE && (b & to_sq(m))) ? (b ^ to_sq(m)) | from_sq(m) : b;
}

/// board_size_bb() returns a bitboard representing all the squares
/// on a board with given size.

Expand Down
9 changes: 9 additions & 0 deletions src/parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ namespace {
return value == "makruk" || value == "asean" || value == "none";
}

template <> bool set(const std::string& value, ChasingRule& target) {
target = value == "axf" ? AXF_CHASING
: NO_CHASING;
return value == "axf" || value == "none";
}

template <> bool set(const std::string& value, EnclosingRule& target) {
target = value == "reversi" ? REVERSI
: value == "ataxx" ? ATAXX
Expand Down Expand Up @@ -129,6 +135,8 @@ template <class T> void VariantParser<DoCheck>::parse_attribute(const std::strin
: std::is_same<T, Value>() ? "Value"
: std::is_same<T, MaterialCounting>() ? "MaterialCounting"
: std::is_same<T, CountingRule>() ? "CountingRule"
: std::is_same<T, ChasingRule>() ? "ChasingRule"
: std::is_same<T, EnclosingRule>() ? "EnclosingRule"
: std::is_same<T, Bitboard>() ? "Bitboard"
: typeid(T).name();
std::cerr << key << " - Invalid value " << it->second << " for type " << typeName << std::endl;
Expand Down Expand Up @@ -335,6 +343,7 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
parse_attribute("nFoldValueAbsolute", v->nFoldValueAbsolute);
parse_attribute("perpetualCheckIllegal", v->perpetualCheckIllegal);
parse_attribute("moveRepetitionIllegal", v->moveRepetitionIllegal);
parse_attribute("chasingRule", v->chasingRule);
parse_attribute("stalemateValue", v->stalemateValue);
parse_attribute("stalematePieceCount", v->stalematePieceCount);
parse_attribute("checkmateValue", v->checkmateValue);
Expand Down
151 changes: 146 additions & 5 deletions src/position.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) {
for (Bitboard b = pos.checkers(); b; )
os << UCI::square(pos, pop_lsb(b)) << " ";

os << "\nChased: ";
for (Bitboard b = pos.state()->chased; b; )
os << UCI::square(pos, pop_lsb(b)) << " ";

if ( int(Tablebases::MaxCardinality) >= popcount(pos.pieces())
&& Options["UCI_Variant"] == "chess"
&& !pos.can_castle(ANY_CASTLING))
Expand Down Expand Up @@ -538,6 +542,7 @@ void Position::set_check_info(StateInfo* si) const {
}
si->shak = si->checkersBB & (byTypeBB[KNIGHT] | byTypeBB[ROOK] | byTypeBB[BERS]);
si->bikjang = var->bikjangRule && ksq != SQ_NONE ? bool(attacks_bb(sideToMove, ROOK, ksq, pieces()) & pieces(sideToMove, KING)) : false;
si->chased = var->chasingRule ? chased() : Bitboard(0);
si->legalCapture = NO_VALUE;
if (var->extinctionPseudoRoyal)
{
Expand All @@ -564,6 +569,7 @@ void Position::set_state(StateInfo* si) const {
si->pawnKey = Zobrist::noPawns;
si->nonPawnMaterial[WHITE] = si->nonPawnMaterial[BLACK] = VALUE_ZERO;
si->checkersBB = count<KING>(sideToMove) ? attackers_to(square<KING>(sideToMove), ~sideToMove) : Bitboard(0);
si->move = MOVE_NONE;

set_check_info(si);

Expand Down Expand Up @@ -784,10 +790,14 @@ Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners

// Snipers are sliders that attack 's' when a piece and other snipers are removed
Bitboard snipers = 0;
Bitboard slidingSnipers = 0;

if (var->fastAttacks)
{
snipers = ( (attacks_bb< ROOK>(s) & pieces(c, QUEEN, ROOK, CHANCELLOR))
| (attacks_bb<BISHOP>(s) & pieces(c, QUEEN, BISHOP, ARCHBISHOP))) & sliders;
slidingSnipers = snipers;
}
else
for (PieceType pt : piece_types())
{
Expand All @@ -807,16 +817,19 @@ Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners
}
else
snipers |= b & ~attacks_bb(~c, pt, s, pieces());
if (AttackRiderTypes[pt] & ~HOPPING_RIDERS)
slidingSnipers |= snipers & pieces(pt);
}
}
Bitboard occupancy = pieces() ^ snipers;
Bitboard occupancy = pieces() ^ slidingSnipers;

while (snipers)
{
Square sniperSq = pop_lsb(snipers);
Bitboard b = between_bb(s, sniperSq, type_of(piece_on(sniperSq))) & occupancy;
bool isHopper = AttackRiderTypes[type_of(piece_on(sniperSq))] & HOPPING_RIDERS;
Bitboard b = between_bb(s, sniperSq, type_of(piece_on(sniperSq))) & (isHopper ? (pieces() ^ sniperSq) : occupancy);

if (b && (!more_than_one(b) || ((AttackRiderTypes[type_of(piece_on(sniperSq))] & HOPPING_RIDERS) && popcount(b) == 2)))
if (b && (!more_than_one(b) || (isHopper && popcount(b) == 2)))
{
// Janggi cannons block each other
if ((pieces(JANGGI_CANNON) & sniperSq) && (pieces(JANGGI_CANNON) & b))
Expand Down Expand Up @@ -2316,6 +2329,8 @@ bool Position::is_optional_game_end(Value& result, int ply, int countStarted) co
int cnt = 0;
bool perpetualThem = st->checkersBB && stp->checkersBB;
bool perpetualUs = st->previous->checkersBB && stp->previous->checkersBB;
Bitboard chaseThem = undo_move_board(st->chased, st->previous->move) & stp->chased;
Bitboard chaseUs = undo_move_board(st->previous->chased, stp->move) & stp->previous->chased;
int moveRepetition = var->moveRepetitionIllegal
&& type_of(st->move) == NORMAL
&& !st->previous->checkersBB && !stp->previous->checkersBB
Expand Down Expand Up @@ -2348,6 +2363,9 @@ bool Position::is_optional_game_end(Value& result, int ply, int countStarted) co
moveRepetition = 0;
}
}
// Chased pieces are empty when there is no previous move
if (i != st->pliesFromNull)
chaseThem = undo_move_board(chaseThem, stp->previous->move) & stp->previous->previous->chased;
stp = stp->previous->previous;
perpetualThem &= bool(stp->checkersBB);

Expand All @@ -2356,8 +2374,8 @@ bool Position::is_optional_game_end(Value& result, int ply, int countStarted) co
if ( stp->key == st->key
&& ++cnt + 1 == (ply > i && !var->moveRepetitionIllegal ? 2 : n_fold_rule()))
{
result = convert_mate_value( var->perpetualCheckIllegal && perpetualThem ? VALUE_MATE
: var->perpetualCheckIllegal && perpetualUs ? -VALUE_MATE
result = convert_mate_value( var->perpetualCheckIllegal && (perpetualThem || perpetualUs) ? (!perpetualUs ? VALUE_MATE : !perpetualThem ? -VALUE_MATE : VALUE_DRAW)
: var->chasingRule && (chaseThem || chaseUs) ? (!chaseUs ? VALUE_MATE : !chaseThem ? -VALUE_MATE : VALUE_DRAW)
: var->nFoldValueAbsolute && sideToMove == BLACK ? -var->nFoldValue
: var->nFoldValue, ply);
if (result == VALUE_DRAW && var->materialCounting)
Expand All @@ -2366,7 +2384,10 @@ bool Position::is_optional_game_end(Value& result, int ply, int countStarted) co
}

if (i + 1 <= end)
{
perpetualUs &= bool(stp->previous->checkersBB);
chaseUs = undo_move_board(chaseUs, stp->move) & stp->previous->chased;
}
}
}
}
Expand Down Expand Up @@ -2507,6 +2528,126 @@ bool Position::is_immediate_game_end(Value& result, int ply) const {
return false;
}

// Position::chased() tests whether the last move was a chase.

Bitboard Position::chased() const {
Bitboard b = 0;
if (st->move == MOVE_NONE)
return b;

Bitboard pins = blockers_for_king(sideToMove);
if (var->flyingGeneral)
{
Bitboard kingFilePieces = file_bb(file_of(square<KING>(~sideToMove))) & pieces(sideToMove);
if ((kingFilePieces & pieces(sideToMove, KING)) && !more_than_one(kingFilePieces & ~pieces(KING)))
pins |= kingFilePieces & ~pieces(KING);
}
auto addChased = [&](Square attackerSq, PieceType attackerType, Bitboard attacks) {
if (attacks & ~b)
{
// Exclude attacks on unpromoted soldiers and checks
attacks &= ~(pieces(sideToMove, KING, SOLDIER) ^ promoted_soldiers(sideToMove));
// Attacks against stronger pieces
if (attackerType == HORSE || attackerType == CANNON)
b |= attacks & pieces(sideToMove, ROOK);
if (attackerType == ELEPHANT || attackerType == FERS)
b |= attacks & pieces(sideToMove, ROOK, CANNON, HORSE);
// Exclude mutual/symmetric attacks
// Exceptions:
// - asymmetric pieces ("impaired horse")
// - pins
if (attackerType == HORSE && (PseudoAttacks[WHITE][FERS][attackerSq] & pieces()))
{
Bitboard horses = attacks & pieces(sideToMove, attackerType);
while (horses)
{
Square s = pop_lsb(horses);
if (attacks_bb(sideToMove, attackerType, s, pieces()) & attackerSq)
attacks ^= s;
}
}
else
attacks &= ~pieces(sideToMove, attackerType) | pins;
// Attacks against potentially unprotected pieces
while (attacks)
{
Square s = pop_lsb(attacks);
Bitboard roots = attackers_to(s, pieces() ^ attackerSq, sideToMove) & ~pins;
if (!roots || (var->flyingGeneral && roots == pieces(sideToMove, KING) && (attacks_bb(sideToMove, ROOK, square<KING>(~sideToMove), pieces() ^ attackerSq) & s)))
b |= s;
}
}
};

// Direct attacks
Square from = from_sq(st->move);
Square to = to_sq(st->move);
PieceType movedPiece = type_of(piece_on(to));
if (movedPiece != KING && movedPiece != SOLDIER)
{
Bitboard directAttacks = attacks_from(~sideToMove, movedPiece, to) & pieces(sideToMove);
// Only new attacks count. This avoids expensive comparison of previous and new attacks.
if (movedPiece == ROOK || movedPiece == CANNON)
directAttacks &= ~line_bb(from, to);
addChased(to, movedPiece, directAttacks);
}

// Discovered attacks
Bitboard discoveryCandidates = (PseudoAttacks[WHITE][WAZIR][from] & pieces(~sideToMove, HORSE))
| (PseudoAttacks[WHITE][FERS][from] & pieces(~sideToMove, ELEPHANT))
| (PseudoAttacks[WHITE][ROOK][from] & pieces(~sideToMove, CANNON, ROOK))
| (PseudoAttacks[WHITE][ROOK][to] & pieces(~sideToMove, CANNON));
while (discoveryCandidates)
{
Square s = pop_lsb(discoveryCandidates);
PieceType discoveryPiece = type_of(piece_on(s));
Bitboard discoveries = pieces(sideToMove)
& attacks_bb(~sideToMove, discoveryPiece, s, pieces())
& ~attacks_bb(~sideToMove, discoveryPiece, s, (captured_piece() ? pieces() : pieces() ^ to) ^ from);
addChased(s, discoveryPiece, discoveries);
}

// Changes in real roots and discovered checks
if (st->pliesFromNull > 0)
{
// Fake roots
Bitboard newPins = st->blockersForKing[sideToMove] & ~st->previous->blockersForKing[sideToMove] & pieces(sideToMove);
while (newPins)
{
Square s = pop_lsb(newPins);
PieceType pinnedPiece = type_of(piece_on(s));
Bitboard fakeRooted = pieces(sideToMove)
& ~(pieces(sideToMove, KING, SOLDIER) ^ promoted_soldiers(sideToMove))
& attacks_bb(sideToMove, pinnedPiece, s, pieces());
while (fakeRooted)
{
Square s2 = pop_lsb(fakeRooted);
if (attackers_to(s2, ~sideToMove) & ~blockers_for_king(~sideToMove))
b |= s2;
}
}
// Discovered checks
Bitboard newDiscoverers = st->blockersForKing[sideToMove] & ~st->previous->blockersForKing[sideToMove] & pieces(~sideToMove);
while (newDiscoverers)
{
Square s = pop_lsb(newDiscoverers);
PieceType discoveryPiece = type_of(piece_on(s));
Bitboard discoveryAttacks = attacks_from(~sideToMove, discoveryPiece, s) & pieces(sideToMove);
// Include all captures except where the king can pseudo-legally recapture
b |= discoveryAttacks & ~attacks_from(sideToMove, KING, square<KING>(sideToMove));
// Include captures where king can not legally recapture
discoveryAttacks &= attacks_from(sideToMove, KING, square<KING>(sideToMove));
while (discoveryAttacks)
{
Square s2 = pop_lsb(discoveryAttacks);
if (attackers_to(s2, pieces() ^ s ^ square<KING>(sideToMove), ~sideToMove) & ~square_bb(s))
b |= s2;
}
}
}

return b;
}

// Position::has_repeated() tests whether there has been at least one repetition
// of positions since the last capture or pawn move.
Expand Down
2 changes: 2 additions & 0 deletions src/position.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ struct StateInfo {
bool capturedpromoted;
bool shak;
bool bikjang;
Bitboard chased;
bool pass;
Move move;
int repetition;
Expand Down Expand Up @@ -298,6 +299,7 @@ class Position {
bool is_draw(int ply) const;
bool has_game_cycle(int ply) const;
bool has_repeated() const;
Bitboard chased() const;
int counting_limit() const;
int counting_ply(int countStarted) const;
int rule50_count() const;
Expand Down
4 changes: 4 additions & 0 deletions src/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,10 @@ enum CountingRule {
NO_COUNTING, MAKRUK_COUNTING, ASEAN_COUNTING
};

enum ChasingRule {
NO_CHASING, AXF_CHASING
};

enum EnclosingRule {
NO_ENCLOSING, REVERSI, ATAXX
};
Expand Down
16 changes: 12 additions & 4 deletions src/variant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1261,7 +1261,8 @@ namespace {
#endif
// Xiangqi (Chinese chess)
// https://en.wikipedia.org/wiki/Xiangqi
Variant* xiangqi_variant() {
// Xiangqi base variant for inheriting rules without chasing rules
Variant* xiangqi_variant_base() {
Variant* v = minixiangqi_variant()->init();
v->pieceToCharTable = "PN.R.AB..K.C..........pn.r.ab..k.c..........";
v->maxRank = RANK_10;
Expand All @@ -1278,11 +1279,16 @@ namespace {
v->soldierPromotionRank = RANK_6;
return v;
}
Variant* xiangqi_variant() {
Variant* v = xiangqi_variant_base()->init();
v->chasingRule = AXF_CHASING;
return v;
}
// Manchu/Yitong chess
// Asymmetric Xiangqi variant with a super-piece
// https://en.wikipedia.org/wiki/Manchu_chess
Variant* manchu_variant() {
Variant* v = xiangqi_variant()->init();
Variant* v = xiangqi_variant_base()->init();
v->pieceToCharTable = "PN.R.AB..K.C....M.....pn.r.ab..k.c..........";
v->add_piece(BANNER, 'm');
v->startFen = "rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/9/9/M1BAKAB2 w - - 0 1";
Expand All @@ -1291,7 +1297,7 @@ namespace {
// Supply chess
// https://en.wikipedia.org/wiki/Xiangqi#Variations
Variant* supply_variant() {
Variant* v = xiangqi_variant()->init();
Variant* v = xiangqi_variant_base()->init();
v->variantTemplate = "bughouse";
v->startFen = "rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR[] w - - 0 1";
v->twoBoards = true;
Expand All @@ -1305,7 +1311,7 @@ namespace {
// https://en.wikipedia.org/wiki/Janggi
// Official tournament rules with bikjang and material counting.
Variant* janggi_variant() {
Variant* v = xiangqi_variant()->init();
Variant* v = xiangqi_variant_base()->init();
v->variantTemplate = "janggi";
v->pieceToCharTable = ".N.R.AB.P..C.........K.n.r.ab.p..c.........k";
v->remove_piece(FERS);
Expand Down Expand Up @@ -1482,6 +1488,8 @@ void VariantMap::parse_istream(std::istream& file) {
Config attribs = {};
while (file.peek() != '[' && std::getline(file, input))
{
if (!input.empty() && input.back() == '\r')
input.pop_back();
std::stringstream ss(input);
if (ss.peek() != ';' && ss.peek() != '#')
{
Expand Down
1 change: 1 addition & 0 deletions src/variant.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ struct Variant {
bool nFoldValueAbsolute = false;
bool perpetualCheckIllegal = false;
bool moveRepetitionIllegal = false;
ChasingRule chasingRule = NO_CHASING;
Value stalemateValue = VALUE_DRAW;
bool stalematePieceCount = false; // multiply stalemate value by sign(count(~stm) - count(stm))
Value checkmateValue = -VALUE_MATE;
Expand Down
2 changes: 2 additions & 0 deletions src/variants.ini
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@
# [Value]: game result for the side to move [win, loss, draw]
# [MaterialCounting]: material couting rules for adjudication [janggi, unweighted, whitedrawodds, blackdrawodds, none]
# [CountingRule]: makruk or ASEAN counting rules [makruk, asean, none]
# [ChasingRule]: xiangqi chasing rules [axf, none]
# [EnclosingRule]: reversi or ataxx enclosing rules [reversi, ataxx, none]

### Additional options relevant for usage in Winboard/XBoard
Expand Down Expand Up @@ -206,6 +207,7 @@
# nFoldValueAbsolute: result in case of 3/n-fold repetition is from white's point of view [bool] (default: false)
# perpetualCheckIllegal: prohibit perpetual checks [bool] (default: false)
# moveRepetitionIllegal: prohibit moving back and forth with the same piece nFoldRule-1 times [bool] (default: false)
# chasingRule: enable chasing rules [ChasingRule] (default: none)
# stalemateValue: result in case of stalemate [Value] (default: draw)
# stalematePieceCount: count material in case of stalemate [bool] (default: false)
# checkmateValue: result in case of checkmate [Value] (default: loss)
Expand Down
Loading

0 comments on commit 092be2c

Please sign in to comment.