- [King of the Hill](https://en.wikipedia.org/wiki/King_of_the_Hill_(chess)), [Racing Kings](https://en.wikipedia.org/wiki/V._R._Parton#Racing_Kings)
- [Three-check](https://en.wikipedia.org/wiki/Three-check_chess), Five-check
- [Los Alamos](https://en.wikipedia.org/wiki/Los_Alamos_chess)
+- [Atomic](https://en.wikipedia.org/wiki/Atomic_chess) (non-standard rules)
- [Horde](https://en.wikipedia.org/wiki/Dunsany%27s_Chess#Horde_Chess)
- [Knightmate](https://www.chessvariants.com/diffobjective.dir/knightmate.html)
if (pos.extinction_value() == -VALUE_MATE)
{
Bitboard bExt = attackedBy[Us][ALL_PIECES] & pos.pieces(Them);
- while (bExt)
+ for (PieceType pt : pos.extinction_piece_types())
{
- PieceType pt = type_of(pos.piece_on(pop_lsb(&bExt)));
+ if (pt == ALL_PIECES)
+ continue;
int denom = std::max(pos.count_with_hand(Them, pt) - pos.extinction_piece_count(), 1);
- if (pos.extinction_piece_types().find(pt) != pos.extinction_piece_types().end())
- score += make_score(1000, 1000) / (denom * denom);
+ // Explosion threats
+ if (pos.blast_on_capture())
+ {
+ int evasions = popcount(((attackedBy[Them][pt] & ~pos.pieces(Them)) | pos.pieces(Them, pt)) & ~attackedBy[Us][ALL_PIECES]) * denom;
+ int attacks = popcount((attackedBy[Them][pt] | pos.pieces(Them, pt)) & attackedBy[Us][ALL_PIECES]);
+ int explosions = 0;
+
+ Bitboard bExtBlast = bExt & (attackedBy2[Us] | ~attackedBy[Us][pt]);
+ while (bExtBlast)
+ {
+ Square s = pop_lsb(&bExtBlast);
+ if (((attacks_bb<KING>(s) | s) & pos.pieces(Them, pt)) && !(attacks_bb<KING>(s) & pos.pieces(Us, pt)))
+ explosions++;
+ }
+ int danger = 20 * attacks / (evasions + 1) + 40 * explosions;
+ score += make_score(danger * (100 + danger), 0);
+ }
+ else
+ // Direct extinction threats
+ score += make_score(1000, 1000) / (denom * denom) * popcount(bExt & pos.pieces(Them, pt));
}
}
parse_attribute("mandatoryPawnPromotion", v->mandatoryPawnPromotion);
parse_attribute("mandatoryPiecePromotion", v->mandatoryPiecePromotion);
parse_attribute("pieceDemotion", v->pieceDemotion);
+ parse_attribute("blastOnCapture", v->blastOnCapture);
parse_attribute("endgameEval", v->endgameEval);
parse_attribute("doubleStep", v->doubleStep);
parse_attribute("doubleStepRank", v->doubleStepRank);
std::cerr << "pieceToCharTable - Missing piece type: " << ptu << std::endl;
}
}
+
+ // Check for limitations
+
+ // Options incompatible with royal kings
+ if (v->pieceTypes.find(KING) != v->pieceTypes.end())
+ {
+ if (v->blastOnCapture)
+ std::cerr << "Can not use kings with blastOnCapture" << std::endl;
+ if (v->flipEnclosedPieces)
+ std::cerr << "Can not use kings with flipEnclosedPieces" << std::endl;
+ }
+
}
return v;
}
if (type_of(m) == CASTLING)
{
// Non-royal pieces can not be impeded from castling
- if (type_of(piece_on(from)) != KING)
+ if (type_of(piece_on(from)) != KING && !var->extinctionPseudoRoyal)
return true;
// After castling, the rook and king final positions are the same in
if (attackers_to(s, ~us))
return false;
+ // TODO: need to consider touching kings
+ if (var->extinctionPseudoRoyal && attackers_to(from, ~us))
+ return false;
+
// Will the gate be blocked by king or rook?
Square rto = to + (to_sq(m) > from_sq(m) ? WEST : EAST);
if (is_gating(m) && (gating_square(m) == to || gating_square(m) == rto))
st->gatesBB[us] = 0;
}
+ // Remove the blast pieces
+ if (captured && blast_on_capture())
+ {
+ std::memset(st->unpromotedBycatch, 0, sizeof(st->unpromotedBycatch));
+ st->demotedBycatch = st->promotedBycatch = 0;
+ Bitboard blast = (attacks_bb<KING>(to) & (pieces() ^ pieces(PAWN))) | to;
+ while (blast)
+ {
+ Square bsq = pop_lsb(&blast);
+ Piece bpc = piece_on(bsq);
+ Color bc = color_of(bpc);
+ if (type_of(bpc) != PAWN)
+ st->nonPawnMaterial[bc] -= PieceValue[MG][bpc];
+
+ // Update board and piece lists
+ // In order to not have to store the values of both board and unpromotedBoard,
+ // demote promoted pieces, but keep promoted pawns as promoted,
+ // and store demotion/promotion bitboards to disambiguate the piece state
+ bool capturedPromoted = is_promoted(bsq);
+ Piece unpromotedCaptured = unpromoted_piece_on(bsq);
+ st->unpromotedBycatch[bsq] = unpromotedCaptured ? unpromotedCaptured : bpc;
+ if (unpromotedCaptured)
+ st->demotedBycatch |= bsq;
+ else if (capturedPromoted)
+ st->promotedBycatch |= bsq;
+ remove_piece(bsq);
+ board[bsq] = NO_PIECE;
+ if (captures_to_hand())
+ {
+ Piece pieceToHand = !capturedPromoted || drop_loop() ? ~bpc
+ : unpromotedCaptured ? ~unpromotedCaptured
+ : make_piece(~color_of(bpc), PAWN);
+ add_to_hand(pieceToHand);
+ k ^= Zobrist::inHand[pieceToHand][pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)] - 1]
+ ^ Zobrist::inHand[pieceToHand][pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)]];
+ }
+
+ // Update material hash key
+ k ^= Zobrist::psq[bpc][bsq];
+ st->materialKey ^= Zobrist::psq[bpc][pieceCount[bpc]];
+ if (type_of(bpc) == PAWN)
+ st->pawnKey ^= Zobrist::psq[bpc][bsq];
+
+ // Update castling rights if needed
+ if (st->castlingRights && castlingRightsMask[bsq])
+ {
+ int cr = castlingRightsMask[bsq];
+ k ^= Zobrist::castling[st->castlingRights & cr];
+ st->castlingRights &= ~cr;
+ }
+ }
+ }
+
// Update the key with the final value
st->key = k;
// Calculate checkers bitboard (if move gives check)
|| (is_pass(m) && pass()));
assert(type_of(st->capturedPiece) != KING);
+ // Add the blast pieces
+ if (st->capturedPiece && blast_on_capture())
+ {
+ Bitboard blast = attacks_bb<KING>(to) | to;
+ while (blast)
+ {
+ Square bsq = pop_lsb(&blast);
+ Piece unpromotedBpc = st->unpromotedBycatch[bsq];
+ Piece bpc = st->demotedBycatch & bsq ? make_piece(color_of(unpromotedBpc), promoted_piece_type(type_of(unpromotedBpc)))
+ : unpromotedBpc;
+ bool isPromoted = (st->promotedBycatch | st->demotedBycatch) & bsq;
+
+ // Update board and piece lists
+ if (bpc)
+ {
+ put_piece(bpc, bsq, isPromoted, st->demotedBycatch & bsq ? unpromotedBpc : NO_PIECE);
+ if (captures_to_hand())
+ remove_from_hand(!drop_loop() && (st->promotedBycatch & bsq) ? make_piece(~color_of(unpromotedBpc), PAWN)
+ : ~unpromotedBpc);
+ }
+ }
+ }
+
// Remove gated piece
if (is_gating(m))
{
}
+Value Position::blast_see(Move m) const {
+ assert(is_ok(m));
+
+ Square from = from_sq(m);
+ Square to = to_sq(m);
+ Color us = color_of(moved_piece(m));
+ Bitboard fromto = type_of(m) == DROP ? square_bb(to) : from | to;
+ Bitboard blast = ((attacks_bb<KING>(to) & ~pieces(PAWN)) | fromto) & pieces();
+
+ Value result = VALUE_ZERO;
+
+ // Add the least valuable attacker for quiet moves
+ if (!capture(m))
+ {
+ Bitboard attackers = attackers_to(to, pieces() ^ fromto, ~us);
+ Value minAttacker = VALUE_INFINITE;
+
+ while (attackers)
+ {
+ Square s = pop_lsb(&attackers);
+ if (extinction_piece_types().find(type_of(piece_on(s))) == extinction_piece_types().end())
+ minAttacker = std::min(minAttacker, blast & s ? VALUE_ZERO : PieceValue[MG][piece_on(s)]);
+ }
+
+ if (minAttacker == VALUE_INFINITE)
+ return VALUE_ZERO;
+
+ result += minAttacker;
+ if (type_of(m) == DROP)
+ result -= PieceValue[MG][dropped_piece_type(m)];
+ }
+
+ // Sum up blast piece values
+ while (blast)
+ {
+ Piece bpc = piece_on(pop_lsb(&blast));
+ if (extinction_piece_types().find(type_of(bpc)) != extinction_piece_types().end())
+ return color_of(bpc) == us ? extinction_value()
+ : capture(m) ? -extinction_value()
+ : VALUE_ZERO;
+ result += color_of(bpc) == us ? -PieceValue[MG][bpc] : PieceValue[MG][bpc];
+ }
+
+ return capture(m) || must_capture() ? result - 1 : std::min(result, VALUE_ZERO);
+}
+
+
/// Position::see_ge (Static Exchange Evaluation Greater or Equal) tests if the
/// SEE value of move is greater or equal to the given threshold. We'll use an
/// algorithm similar to alpha-beta pruning with a null window.
return VALUE_ZERO >= threshold;
Square from = from_sq(m), to = to_sq(m);
+
// nCheck
if (check_counting() && color_of(moved_piece(m)) == sideToMove && gives_check(m))
return true;
+ // Atomic explosion SEE
+ if (blast_on_capture())
+ return blast_see(m) >= threshold;
+
// Extinction
if ( extinction_value() != VALUE_NONE
&& piece_on(to)
&& count<ALL_PIECES>(~sideToMove) == extinction_piece_count() + 1)))
return extinction_value() < VALUE_ZERO;
+ // Do not evaluate SEE if value would be unreliable
if (must_capture() || !checking_permitted() || is_gating(m) || count<CLOBBER_PIECE>() == count<ALL_PIECES>())
return VALUE_ZERO >= threshold;
// extinction
if (extinction_value() != VALUE_NONE)
{
- for (Color c : { WHITE, BLACK })
+ for (Color c : { ~sideToMove, sideToMove })
for (PieceType pt : extinction_piece_types())
if ( count_with_hand( c, pt) <= var->extinctionPieceCount
&& count_with_hand(~c, pt) >= var->extinctionOpponentPieceCount + (extinction_claim() && c == sideToMove))
Bitboard checkersBB;
Piece capturedPiece;
Piece unpromotedCapturedPiece;
+ Piece unpromotedBycatch[SQUARE_NB];
+ Bitboard promotedBycatch;
+ Bitboard demotedBycatch;
StateInfo* previous;
Bitboard blockersForKing[COLOR_NB];
Bitboard pinners[COLOR_NB];
bool mandatory_pawn_promotion() const;
bool mandatory_piece_promotion() const;
bool piece_demotion() const;
+ bool blast_on_capture() const;
bool endgame_eval() const;
bool double_step_enabled() const;
Rank double_step_rank_max() const;
void undo_null_move();
// Static Exchange Evaluation
+ Value blast_see(Move m) const;
bool see_ge(Move m, Value threshold = VALUE_ZERO) const;
// Accessing hash keys
return var->pieceDemotion;
}
+inline bool Position::blast_on_capture() const {
+ assert(var != nullptr);
+ return var->blastOnCapture;
+}
+
inline bool Position::endgame_eval() const {
assert(var != nullptr);
return var->endgameEval && !count_in_hand(WHITE, ALL_PIECES) && !count_in_hand(BLACK, ALL_PIECES);
// Consider promotion types in pawn score
if (pt == PAWN)
+ {
score -= make_score(0, (QueenValueEg - maxPromotion) / 100);
+ if (v->blastOnCapture)
+ score += score;
+ }
// Scale slider piece values with board size
const PieceInfo* pi = pieceMap.find(pt)->second;
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->blastOnCapture)
+ 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);
// standard variants of XBoard/WinBoard
std::set<string> standard_variants = {
"normal", "nocastle", "fischerandom", "knightmate", "3check", "makruk", "shatranj",
- "asean", "seirawan", "crazyhouse", "bughouse", "suicide", "giveaway", "losers",
+ "asean", "seirawan", "crazyhouse", "bughouse", "suicide", "giveaway", "losers", "atomic",
"capablanca", "gothic", "janus", "caparandom", "grand", "shogi", "xiangqi"
};
v->extinctionPieceTypes = {ALL_PIECES};
return v;
}
+ // Atomic chess without checks (ICC rules)
+ // https://www.chessclub.com/help/atomic
+ Variant* nocheckatomic_variant() {
+ Variant* v = fairy_variant_base();
+ v->variantTemplate = "atomic";
+ v->remove_piece(KING);
+ v->add_piece(COMMONER, 'k');
+ v->extinctionValue = -VALUE_MATE;
+ v->extinctionPieceTypes = {COMMONER};
+ v->blastOnCapture = true;
+ return v;
+ }
+ // Atomic chess
+ // https://en.wikipedia.org/wiki/Atomic_chess
+ Variant* atomic_variant() {
+ Variant* v = nocheckatomic_variant();
+ // TODO: castling, check(-mate), stalemate are not yet properly implemented
+ v->extinctionPseudoRoyal = true;
+ return v;
+ }
Variant* threecheck_variant() {
Variant* v = fairy_variant_base();
v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 3+3 0 1";
add("kinglet", kinglet_variant()->conclude());
add("threekings", threekings_variant()->conclude());
add("horde", horde_variant()->conclude());
+ add("nocheckatomic", nocheckatomic_variant()->conclude());
+ add("atomic", atomic_variant()->conclude());
add("3check", threecheck_variant()->conclude());
add("5check", fivecheck_variant()->conclude());
add("crazyhouse", crazyhouse_variant()->conclude());
bool mandatoryPawnPromotion = true;
bool mandatoryPiecePromotion = false;
bool pieceDemotion = false;
+ bool blastOnCapture = false;
bool endgameEval = false;
bool doubleStep = true;
Rank doubleStepRank = RANK_2;
bool bikjangRule = false;
Value extinctionValue = VALUE_NONE;
bool extinctionClaim = false;
+ bool extinctionPseudoRoyal = false; // TODO: implementation incomplete
std::set<PieceType> extinctionPieceTypes = {};
int extinctionPieceCount = 0;
int extinctionOpponentPieceCount = 0;
# mandatoryPawnPromotion: pawn promotion is mandatory [bool] (default: true)
# mandatoryPiecePromotion: piece promotion (and demotion if enabled) is mandatory [bool] (default: false)
# pieceDemotion: enable demotion of pieces (e.g., Kyoto shogi) [bool] (default: false)
+# blastOnCapture: captures explode all adjacent non-pawn pieces (e.g., atomic chess) [bool] (default: false)
# endgameEval: enable special endgame evaluation (for very chess-like variants only) [bool] (default: false)
# doubleStep: enable pawn double step [bool] (default: true)
# doubleStepRank: relative rank from where pawn double steps are allowed [Rank] (default: 2)