- [Chess960](https://en.wikipedia.org/wiki/Chess960), [Placement/Pre-Chess](https://www.chessvariants.com/link/placement-chess)
- [Crazyhouse](https://en.wikipedia.org/wiki/Crazyhouse), [Loop](https://en.wikipedia.org/wiki/Crazyhouse#Variations), [Chessgi](https://en.wikipedia.org/wiki/Crazyhouse#Variations), [Pocket Knight](http://www.chessvariants.com/other.dir/pocket.html), Capablanca-Crazyhouse
- [Bughouse](https://en.wikipedia.org/wiki/Bughouse_chess), [Koedem](http://schachclub-oetigheim.de/wp-content/uploads/2016/04/Koedem-rules.pdf)
-- [Seirawan](https://en.wikipedia.org/wiki/Seirawan_chess), Seirawan-Crazyhouse
+- [Seirawan](https://en.wikipedia.org/wiki/Seirawan_chess), Seirawan-Crazyhouse, [Dragon Chess](https://www.edami.com/dragonchess/)
- [Amazon](https://www.chessvariants.com/diffmove.dir/amazone.html), [Chigorin](https://www.chessvariants.com/diffsetup.dir/chigorin.html), [Almost chess](https://en.wikipedia.org/wiki/Almost_Chess)
- [Hoppel-Poppel](http://www.chessvariants.com/diffmove.dir/hoppel-poppel.html), New Zealand
- [Antichess](https://lichess.org/variant/antichess), [Giveaway](http://www.chessvariants.com/diffobjective.dir/giveaway.old.html), [Suicide](https://www.freechess.org/Help/HelpFiles/suicide_chess.html), [Losers](https://www.chessclub.com/help/Wild17), [Codrus](http://www.binnewirtz.com/Schlagschach1.htm)
- [Atomic](https://en.wikipedia.org/wiki/Atomic_chess)
- [Horde](https://en.wikipedia.org/wiki/Dunsany%27s_Chess#Horde_Chess), [Maharajah and the Sepoys](https://en.wikipedia.org/wiki/Maharajah_and_the_Sepoys)
- [Knightmate](https://www.chessvariants.com/diffobjective.dir/knightmate.html), [Nightrider](https://en.wikipedia.org/wiki/Nightrider_(chess)), [Grasshopper](https://en.wikipedia.org/wiki/Grasshopper_chess)
-- [Dragon Chess](https://www.edami.com/dragonchess/)
+- [Duck chess](https://duckchess.com/), [Omicron](http://www.eglebbk.dds.nl/program/chess-omicron.html), [Gustav III](https://www.chessvariants.com/play/gustav-iiis-chess)
### Shogi variants
- [Minishogi](https://en.wikipedia.org/wiki/Minishogi), [EuroShogi](https://en.wikipedia.org/wiki/EuroShogi), [Judkins shogi](https://en.wikipedia.org/wiki/Judkins_shogi)
else:
args = ["-std=c++17", "-flto", "-Wno-date-time"]
-args.extend(["-DLARGEBOARDS", "-DPRECOMPUTED_MAGICS", "-DNNUE_EMBEDDING_OFF"])
+args.extend(["-DLARGEBOARDS", "-DALLVARS", "-DPRECOMPUTED_MAGICS", "-DNNUE_EMBEDDING_OFF"])
if "64bit" in platform.architecture():
args.append("-DIS_64BIT")
CXXFLAGS += -DNNUE_EMBEDDING_OFF
endif
-# Enable all variants, even heavyweight ones like amazons
+# Enable all variants, even heavyweight ones like duck and amazons
ifneq ($(all),no)
CXXFLAGS += -DALLVARS
endif
CXXFLAGS += --bind -DNNUE_EMBEDDING_OFF -DNO_THREADS -std=c++17 -Wall
largeboards = yes
+all = yes
optimize = yes
debug = no
CXXFLAGS += -DLARGEBOARDS -DPRECOMPUTED_MAGICS -s TOTAL_MEMORY=32MB -s ALLOW_MEMORY_GROWTH=1 -s WASM_MEM_MAX=1GB
endif
+# Enable all variants, even heavyweight ones like duck and amazons
+ifneq ($(all),no)
+ CXXFLAGS += -DALLVARS
+endif
+
### Compile as ES6/ES2015 module
ifeq ($(es6),yes)
CXXFLAGS += -s ENVIRONMENT='web,worker' -s EXPORT_ES6=1 -s MODULARIZE=1 -s USE_ES6_IMPORT_META=0
san += std::string("/") + (char)toupper(pos.piece_to_char()[make_piece(us, gating_type(m))]);
}
+ // Wall square
+ if (pos.wall_gating())
+ san += "," + square(pos, gating_square(m), n);
+
// Check and checkmate
if (pos.gives_check(m) && !is_shogi(n) && n != NOTATION_XIANGQI_WXF)
{
{
if (c == ' ' || c == '[')
break;
- if (isdigit(c))
+ if (c == '*')
+ ++fileIdx;
+ else if (isdigit(c))
{
fileIdx += c - '0';
// if we have multiple digits attached we can add multiples of 9 to compute the resulting number (e.g. -> 21 = 2 + 2 * 9 + 1)
}
inline std::string get_valid_special_chars(const Variant* v) {
- std::string validSpecialCharactersFirstField = "/";
+ std::string validSpecialCharactersFirstField = "/*";
// Whether or not '-', '+', '~', '[', ']' are valid depends on the variant being played.
if (v->shogiStylePromotions)
validSpecialCharactersFirstField += '+';
if (!v->promotionPieceTypes.empty())
validSpecialCharactersFirstField += '~';
- if (!v->freeDrops && (v->pieceDrops || v->seirawanGating || v->arrowGating))
+ if (!v->freeDrops && (v->pieceDrops || v->seirawanGating))
validSpecialCharactersFirstField += "[-]";
return validSpecialCharactersFirstField;
}
// check for pocket
std::string pocket = "";
- if (v->pieceDrops || v->seirawanGating || v->arrowGating)
+ if (v->pieceDrops || v->seirawanGating)
{
if (check_pocket_info(fenParts[0], nbRanks, v, pocket) == NOK)
return FEN_INVALID_POCKET_INFO;
namespace {
template<MoveType T>
- ExtMove* make_move_and_gating(const Position& pos, ExtMove* moveList, Color us, Square from, Square to) {
+ ExtMove* make_move_and_gating(const Position& pos, ExtMove* moveList, Color us, Square from, Square to, PieceType pt = NO_PIECE_TYPE) {
- // Arrow gating moves
- if (pos.arrow_gating())
+ // Wall placing moves
+ if (pos.wall_gating())
{
- for (PieceType pt_gating : pos.piece_types())
- if (pos.can_drop(us, pt_gating))
- {
- Bitboard b = pos.drop_region(us, pt_gating) & moves_bb(us, type_of(pos.piece_on(from)), to, pos.pieces() ^ from) & ~(pos.pieces() ^ from);
- while (b)
- *moveList++ = make_gating<T>(from, to, pt_gating, pop_lsb(b));
- }
+ Bitboard b = pos.board_bb() & ~((pos.pieces() ^ from) | to);
+ if (T == CASTLING)
+ {
+ Square kto = make_square(to > from ? pos.castling_kingside_file() : pos.castling_queenside_file(), pos.castling_rank(us));
+ Direction step = kto > from ? EAST : WEST;
+ Square rto = kto - step;
+ b ^= square_bb(to) ^ kto ^ rto;
+ }
+ if (T == EN_PASSANT)
+ b ^= to - pawn_push(us);
+ if (pos.variant()->arrowGating)
+ b &= moves_bb(us, type_of(pos.piece_on(from)), to, pos.pieces() ^ from);
+ while (b)
+ *moveList++ = make_gating<T>(from, to, pt, pop_lsb(b));
return moveList;
}
- *moveList++ = make<T>(from, to);
+ *moveList++ = make<T>(from, to, pt);
// Gating moves
if (pos.seirawan_gating() && (pos.gates(us) & from))
{
for (PieceType pt : pos.promotion_piece_types())
if (!pos.promotion_limit(pt) || pos.promotion_limit(pt) > pos.count(c, pt))
- *moveList++ = make<PROMOTION>(to - D, to, pt);
+ moveList = make_move_and_gating<PROMOTION>(pos, moveList, pos.side_to_move(), to - D, to, pt);
PieceType pt = pos.promoted_piece_type(PAWN);
if (pt && !(pos.piece_promotion_on_capture() && pos.empty(to)))
- *moveList++ = make<PIECE_PROMOTION>(to - D, to);
+ moveList = make_move_and_gating<PIECE_PROMOTION>(pos, moveList, pos.side_to_move(), to - D, to);
}
return moveList;
while (b1)
{
Square to = pop_lsb(b1);
- *moveList++ = make_move(to - Up, to);
+ moveList = make_move_and_gating<NORMAL>(pos, moveList, Us, to - Up, to);
}
while (b2)
{
Square to = pop_lsb(b2);
- *moveList++ = make_move(to - Up - Up, to);
+ moveList = make_move_and_gating<NORMAL>(pos, moveList, Us, to - Up - Up, to);
}
}
while (b1)
{
Square to = pop_lsb(b1);
- *moveList++ = make_move(to - UpRight, to);
+ moveList = make_move_and_gating<NORMAL>(pos, moveList, Us, to - UpRight, to);
}
while (b2)
{
Square to = pop_lsb(b2);
- *moveList++ = make_move(to - UpLeft, to);
+ moveList = make_move_and_gating<NORMAL>(pos, moveList, Us, to - UpLeft, to);
}
if (pos.ep_square() != SQ_NONE)
assert(b1);
while (b1)
- *moveList++ = make<EN_PASSANT>(pop_lsb(b1), pos.ep_square());
+ moveList = make_move_and_gating<EN_PASSANT>(pos, moveList, Us, pop_lsb(b1), pos.ep_square());
}
}
target = pos.checkers();
}
+ // Remove inaccesible squares (outside board + wall squares)
target &= pos.board_bb();
moveList = generate_pawn_moves<Us, Type>(pos, moveList, target);
/// ordering is at the current node.
/// MovePicker constructor for the main search
-MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, const LowPlyHistory* lp,
+MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, const GateHistory* dh, const LowPlyHistory* lp,
const CapturePieceToHistory* cph, const PieceToHistory** ch, Move cm, const Move* killers, int pl)
- : pos(p), mainHistory(mh), lowPlyHistory(lp), captureHistory(cph), continuationHistory(ch),
+ : pos(p), mainHistory(mh), gateHistory(dh), lowPlyHistory(lp), captureHistory(cph), continuationHistory(ch),
ttMove(ttm), refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}}, depth(d), ply(pl) {
assert(d > 0);
}
/// MovePicker constructor for quiescence search
-MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh,
+MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, const GateHistory* dh,
const CapturePieceToHistory* cph, const PieceToHistory** ch, Square rs)
- : pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), ttMove(ttm), recaptureSquare(rs), depth(d) {
+ : pos(p), mainHistory(mh), gateHistory(dh), captureHistory(cph), continuationHistory(ch), ttMove(ttm), recaptureSquare(rs), depth(d) {
assert(d <= 0);
/// MovePicker constructor for ProbCut: we generate captures with SEE greater
/// than or equal to the given threshold.
-MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph)
- : pos(p), captureHistory(cph), ttMove(ttm), threshold(th) {
+MovePicker::MovePicker(const Position& p, Move ttm, Value th, const GateHistory* dh, const CapturePieceToHistory* cph)
+ : pos(p), gateHistory(dh), captureHistory(cph), ttMove(ttm), threshold(th) {
assert(!pos.checkers());
for (auto& m : *this)
if constexpr (Type == CAPTURES)
m.value = int(PieceValue[MG][pos.piece_on(to_sq(m))]) * 6
+ + (*gateHistory)[pos.side_to_move()][gating_square(m)]
+ (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))];
else if constexpr (Type == QUIETS)
m.value = (*mainHistory)[pos.side_to_move()][from_to(m)]
+ + (*gateHistory)[pos.side_to_move()][gating_square(m)]
+ 2 * (*continuationHistory[0])[history_slot(pos.moved_piece(m))][to_sq(m)]
+ (*continuationHistory[1])[history_slot(pos.moved_piece(m))][to_sq(m)]
+ (*continuationHistory[3])[history_slot(pos.moved_piece(m))][to_sq(m)]
/// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards
typedef Stats<int16_t, 13365, COLOR_NB, int(SQUARE_NB + 1) * int(1 << SQUARE_BITS)> ButterflyHistory;
+typedef Stats<int16_t, 13365, COLOR_NB, SQUARE_NB> GateHistory;
+
/// At higher depths LowPlyHistory records successful quiet moves near the root
/// and quiet moves which are/were in the PV (ttPv). It is cleared with each new
/// search and filled during iterative deepening.
public:
MovePicker(const MovePicker&) = delete;
MovePicker& operator=(const MovePicker&) = delete;
- MovePicker(const Position&, Move, Value, const CapturePieceToHistory*);
+ MovePicker(const Position&, Move, Value, const GateHistory*, const CapturePieceToHistory*);
MovePicker(const Position&, Move, Depth, const ButterflyHistory*,
+ const GateHistory*,
const CapturePieceToHistory*,
const PieceToHistory**,
Square);
MovePicker(const Position&, Move, Depth, const ButterflyHistory*,
+ const GateHistory*,
const LowPlyHistory*,
const CapturePieceToHistory*,
const PieceToHistory**,
const Position& pos;
const ButterflyHistory* mainHistory;
+ const GateHistory* gateHistory;
const LowPlyHistory* lowPlyHistory;
const CapturePieceToHistory* captureHistory;
const PieceToHistory** continuationHistory;
ValueListInserter<IndexType> active
) {
Square oriented_ksq = orient(perspective, pos.nnue_king_square(perspective), pos);
- Bitboard bb = pos.pieces();
+ Bitboard bb = pos.pieces(WHITE) | pos.pieces(BLACK);
while (bb)
{
Square s = pop_lsb(bb);
parse_attribute("mandatoryPiecePromotion", v->mandatoryPiecePromotion);
parse_attribute("pieceDemotion", v->pieceDemotion);
parse_attribute("blastOnCapture", v->blastOnCapture);
+ parse_attribute("petrifyOnCapture", v->petrifyOnCapture);
parse_attribute("doubleStep", v->doubleStep);
parse_attribute("doubleStepRank", v->doubleStepRank);
parse_attribute("doubleStepRankMin", v->doubleStepRankMin);
parse_attribute("immobilityIllegal", v->immobilityIllegal);
parse_attribute("gating", v->gating);
parse_attribute("arrowGating", v->arrowGating);
+ parse_attribute("duckGating", v->duckGating);
parse_attribute("seirawanGating", v->seirawanGating);
parse_attribute("cambodianMoves", v->cambodianMoves);
parse_attribute("diagonalLines", v->diagonalLines);
std::cerr << "Inconsistent settings: castlingQueensideFile > castlingKingsideFile." << std::endl;
// Check for limitations
-
+ if (v->pieceDrops && (v->arrowGating || v->duckGating))
+ std::cerr << "pieceDrops and arrowGating/duckGating are incompatible." << std::endl;
// Options incompatible with royal kings
if (v->pieceTypes.find(KING) != v->pieceTypes.end())
{
std::cerr << "Can not use kings with blastOnCapture." << std::endl;
if (v->flipEnclosedPieces)
std::cerr << "Can not use kings with flipEnclosedPieces." << std::endl;
+ if (v->duckGating)
+ std::cerr << "Can not use kings with duckGating." << std::endl;
// We can not fully check support for custom king movements at this point,
// since custom pieces are only initialized on loading of the variant.
// We will assume this is valid, but it might cause problems later if it's not.
Key side, noPawns;
Key inHand[PIECE_NB][SQUARE_NB];
Key checks[COLOR_NB][CHECKS_NB];
+ Key wall[SQUARE_NB];
}
for (Rank r = pos.max_rank(); r >= RANK_1; --r)
{
for (File f = FILE_A; f <= pos.max_file(); ++f)
- if (pos.unpromoted_piece_on(make_square(f, r)))
+ if (pos.state()->wallSquares & make_square(f, r))
+ os << " | *";
+ else if (pos.unpromoted_piece_on(make_square(f, r)))
os << " |+" << pos.piece_to_char()[pos.unpromoted_piece_on(make_square(f, r))];
else
os << " | " << pos.piece_to_char()[pos.piece_on(make_square(f, r))];
os << " *";
else
os << " ";
- if (!pos.variant()->freeDrops && (pos.piece_drops() || pos.seirawan_gating() || pos.arrow_gating()))
+ if (!pos.variant()->freeDrops && (pos.piece_drops() || pos.seirawan_gating()))
{
os << " [";
for (PieceType pt = KING; pt >= PAWN; --pt)
for (int n = 0; n < SQUARE_NB; ++n)
Zobrist::inHand[make_piece(c, pt)][n] = rng.rand<Key>();
+ for (Square s = SQ_A1; s <= SQ_MAX; ++s)
+ Zobrist::wall[s] = rng.rand<Key>();
+
// Prepare the cuckoo tables
std::memset(cuckoo, 0, sizeof(cuckoo));
std::memset(cuckooMove, 0, sizeof(cuckooMove));
break;
}
+ // Wall square
+ else if (token == '*')
+ {
+ st->wallSquares |= sq;
+ byTypeBB[ALL_PIECES] |= sq;
+ ++sq;
+ }
+
else if ((idx = piece_to_char().find(token)) != string::npos || (idx = piece_to_char_synonyms().find(token)) != string::npos)
{
if (ss.peek() == '~')
Piece pc = piece_on(s);
si->key ^= Zobrist::psq[pc][s];
- if (type_of(pc) == PAWN)
+ if (!pc)
+ si->key ^= Zobrist::wall[s];
+
+ else if (type_of(pc) == PAWN)
si->pawnKey ^= Zobrist::psq[pc][s];
else if (type_of(pc) != KING)
for (int cnt = 0; cnt < pieceCount[pc]; ++cnt)
si->materialKey ^= Zobrist::psq[pc][cnt];
- if (piece_drops() || seirawan_gating() || arrow_gating())
+ if (piece_drops() || seirawan_gating())
si->key ^= Zobrist::inHand[pc][pieceCountInHand[c][pt]];
}
{
for (File f = FILE_A; f <= max_file(); ++f)
{
- for (emptyCnt = 0; f <= max_file() && empty(make_square(f, r)); ++f)
+ for (emptyCnt = 0; f <= max_file() && !(pieces() & make_square(f, r)); ++f)
++emptyCnt;
if (emptyCnt)
if (f <= max_file())
{
- if (unpromoted_piece_on(make_square(f, r)))
+ if (empty(make_square(f, r)))
+ // Wall square
+ ss << "*";
+ else if (unpromoted_piece_on(make_square(f, r)))
// Promoted shogi pieces, e.g., +r for dragon
ss << "+" << piece_to_char()[unpromoted_piece_on(make_square(f, r))];
else
}
// pieces in hand
- if (!variant()->freeDrops && (piece_drops() || seirawan_gating() || arrow_gating()))
+ if (!variant()->freeDrops && (piece_drops() || seirawan_gating()))
{
ss << '[';
if (holdings != "-")
}
}
+ // Petrifying the king is illegal
+ if (var->petrifyOnCapture && capture(m) && type_of(moved_piece(m)) == KING)
+ return false;
+
// En passant captures are a tricky special case. Because they are rather
// uncommon, we do it simply by testing whether the king is attacked after
// the move is made.
Square to = to_sq(m);
Piece pc = moved_piece(m);
- // Illegal moves to squares outside of board
+ // Illegal moves to squares outside of board or to wall squares
if (!(board_bb() & to))
return false;
// Use a slower but simpler function for uncommon cases
// yet we skip the legality check of MoveList<LEGAL>().
- if (type_of(m) != NORMAL || is_gating(m) || arrow_gating())
+ if (type_of(m) != NORMAL || is_gating(m))
return checkers() ? MoveList< EVASIONS>(*this).contains(m)
: MoveList<NON_EVASIONS>(*this).contains(m);
+ // Illegal wall square placement
+ if (wall_gating() && !((board_bb() & ~((pieces() ^ from) | to)) & gating_square(m)))
+ return false;
+ if (var->arrowGating && !(moves_bb(us, type_of(pc), to, pieces() ^ from) & gating_square(m)))
+ return false;
+
// Handle the case where a mandatory piece promotion/demotion is not taken
if ( mandatory_piece_promotion()
&& (is_promoted(from) ? piece_demotion() : promoted_piece_type(type_of(pc)) != NO_PIECE_TYPE)
if (mandatory_pawn_promotion() && rank_of(to) == relative_rank(us, promotion_rank(), max_rank()) && !sittuyin_promotion())
return false;
- if ( !(pawn_attacks_bb(us, from) & pieces(~us) & to) // Not a capture
- && !((from + pawn_push(us) == to) && empty(to)) // Not a single push
- && !( (from + 2 * pawn_push(us) == to) // Not a double push
+ if ( !(pawn_attacks_bb(us, from) & pieces(~us) & to) // Not a capture
+ && !((from + pawn_push(us) == to) && !(pieces() & to)) // Not a single push
+ && !( (from + 2 * pawn_push(us) == to) // Not a double push
&& ( relative_rank(us, from, max_rank()) <= double_step_rank_max()
&& relative_rank(us, from, max_rank()) >= double_step_rank_min())
- && empty(to)
- && empty(to - pawn_push(us))
+ && !(pieces() & to)
+ && !(pieces() & (to - pawn_push(us)))
&& double_step_enabled()))
return false;
}
return false;
// Is there a direct check?
- if (type_of(m) != PROMOTION && type_of(m) != PIECE_PROMOTION && type_of(m) != PIECE_DEMOTION && type_of(m) != CASTLING)
+ if (type_of(m) != PROMOTION && type_of(m) != PIECE_PROMOTION && type_of(m) != PIECE_DEMOTION && type_of(m) != CASTLING
+ && !(var->petrifyOnCapture && capture(m) && type_of(moved_piece(m)) != PAWN))
{
PieceType pt = type_of(moved_piece(m));
if (AttackRiderTypes[pt] & (HOPPING_RIDERS | ASYMMETRICAL_RIDERS))
&& attacks_bb(sideToMove, gating_type(m), gating_square(m), (pieces() ^ from) | to) & square<KING>(~sideToMove))
return true;
+ // Petrified piece can't give check
+ if (var->petrifyOnCapture && capture(m) && type_of(moved_piece(m)) != PAWN)
+ return false;
+
// Is there a check by special diagonal moves?
if (more_than_one(diagonal_lines() & (to | square<KING>(~sideToMove))))
{
if ( type_of(m) != DROP
&& std::abs(int(to) - int(from)) == 2 * NORTH
&& (var->enPassantRegion & (to - pawn_push(us)))
- && (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN)))
+ && (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN))
+ && !(wall_gating() && gating_square(m) == to - pawn_push(us)))
{
st->epSquare = to - pawn_push(us);
k ^= Zobrist::enpassant[file_of(st->epSquare)];
st->gatesBB[them] ^= square<KING>(them);
// Remove the blast pieces
- if (captured && blast_on_capture())
+ if (captured && (blast_on_capture() || var->petrifyOnCapture))
{
std::memset(st->unpromotedBycatch, 0, sizeof(st->unpromotedBycatch));
st->demotedBycatch = st->promotedBycatch = 0;
- Bitboard blast = (attacks_bb<KING>(to) & (pieces() ^ pieces(PAWN))) | to;
+ Bitboard blast = blast_on_capture() ? (attacks_bb<KING>(to) & (pieces() ^ pieces(PAWN))) | to
+ : type_of(pc) != PAWN ? square_bb(to) : Bitboard(0);
while (blast)
{
Square bsq = pop_lsb(blast);
st->castlingRights &= ~castlingRightsMask[bsq];
k ^= Zobrist::castling[st->castlingRights];
}
+
+ // Make a wall square where the piece was
+ if (var->petrifyOnCapture)
+ {
+ st->wallSquares |= bsq;
+ byTypeBB[ALL_PIECES] |= bsq;
+ k ^= Zobrist::wall[bsq];
+ }
}
}
+ // Add gated wall square
+ if (wall_gating())
+ {
+ // Reset wall squares for duck gating
+ if (var->duckGating)
+ {
+ Bitboard b = st->previous->wallSquares;
+ byTypeBB[ALL_PIECES] ^= b;
+ while (b)
+ k ^= Zobrist::wall[pop_lsb(b)];
+ st->wallSquares = 0;
+ }
+ st->wallSquares |= gating_square(m);
+ byTypeBB[ALL_PIECES] |= gating_square(m);
+ k ^= Zobrist::wall[gating_square(m)];
+ }
+
// 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);
+ // Reset wall squares
+ byTypeBB[ALL_PIECES] ^= st->wallSquares ^ st->previous->wallSquares;
+
// Add the blast pieces
- if (st->capturedPiece && blast_on_capture())
+ if (st->capturedPiece && (blast_on_capture() || var->petrifyOnCapture))
{
Bitboard blast = attacks_bb<KING>(to) | to;
while (blast)
if (pinners(~stm) & occupied)
stmAttackers &= ~blockers_for_king(stm);
+ // Ignore distant sliders
+ if (var->duckGating)
+ stmAttackers &= attacks_bb<KING>(to) | ~(pieces(BISHOP, ROOK) | pieces(QUEEN));
+
if (!stmAttackers)
break;
int end = captures_to_hand() ? st->pliesFromNull : std::min(st->rule50, st->pliesFromNull);
- if (end < 3 || var->nFoldValue != VALUE_DRAW || var->perpetualCheckIllegal || var->materialCounting || var->moveRepetitionIllegal)
+ if (end < 3 || var->nFoldValue != VALUE_DRAW || var->perpetualCheckIllegal || var->materialCounting || var->moveRepetitionIllegal || var->duckGating)
return false;
Key originalKey = st->key;
CheckCount checksRemaining[COLOR_NB];
Square epSquare;
Square castlingKingSquare[COLOR_NB];
+ Bitboard wallSquares;
Bitboard gatesBB[COLOR_NB];
// Not copied when making a move (will be recomputed anyhow)
PieceType drop_no_doubled() const;
bool immobility_illegal() const;
bool gating() const;
- bool arrow_gating() const;
+ bool wall_gating() const;
bool seirawan_gating() const;
bool cambodian_moves() const;
Bitboard diagonal_lines() const;
inline Bitboard Position::board_bb() const {
assert(var != nullptr);
- return board_size_bb(var->maxFile, var->maxRank);
+ return board_size_bb(var->maxFile, var->maxRank) & ~st->wallSquares;
}
inline Bitboard Position::board_bb(Color c, PieceType pt) const {
return var->gating;
}
-inline bool Position::arrow_gating() const {
+inline bool Position::wall_gating() const {
assert(var != nullptr);
- return var->arrowGating;
+ return var->arrowGating || var->duckGating;
}
inline bool Position::seirawan_gating() const {
// Send move only when not in analyze mode and not at game end
if (!Limits.infinite && !ponder && rootMoves[0].pv[0] != MOVE_NONE && !Threads.abort.exchange(true))
{
- sync_cout << "move " << UCI::move(rootPos, bestMove) << sync_endl;
+ std::string move = UCI::move(rootPos, bestMove);
+ if (rootPos.wall_gating())
+ {
+ sync_cout << "move " << move.substr(0, move.find(",")) << "," << sync_endl;
+ sync_cout << "move " << move.substr(move.find(",") + 1) << sync_endl;
+ }
+ else
+ sync_cout << "move " << UCI::move(rootPos, bestMove) << sync_endl;
if (XBoard::stateMachine->moveAfterSearch)
{
XBoard::stateMachine->do_move(bestMove);
{
int penalty = -stat_bonus(depth);
thisThread->mainHistory[us][from_to(ttMove)] << penalty;
+ if (pos.wall_gating())
+ thisThread->gateHistory[us][gating_square(ttMove)] << penalty;
update_continuation_histories(ss, pos.moved_piece(ttMove), to_sq(ttMove), penalty);
}
}
{
assert(probCutBeta < VALUE_INFINITE);
- MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory);
+ MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &thisThread->gateHistory, &captureHistory);
int probCutCount = 0;
bool ttPv = ss->ttPv;
ss->ttPv = false;
Move countermove = thisThread->counterMoves[pos.piece_on(prevSq)][prevSq];
MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory,
+ &thisThread->gateHistory,
&thisThread->lowPlyHistory,
&captureHistory,
contHist,
continue;
// Prune moves with negative SEE (~20 Elo)
- if (!pos.see_ge(move, Value(-(30 - std::min(lmrDepth, 18) + 10 * !!pos.capture_the_flag_piece()) * lmrDepth * lmrDepth)))
+ if (!pos.variant()->duckGating && !pos.see_ge(move, Value(-(30 - std::min(lmrDepth, 18) + 10 * !!pos.capture_the_flag_piece()) * lmrDepth * lmrDepth)))
continue;
}
}
r++;
ss->statScore = thisThread->mainHistory[us][from_to(move)]
+ + thisThread->gateHistory[us][gating_square(move)] * 2
+ (*contHist[0])[history_slot(movedPiece)][to_sq(move)]
+ (*contHist[1])[history_slot(movedPiece)][to_sq(move)]
+ (*contHist[3])[history_slot(movedPiece)][to_sq(move)]
// queen promotions, and other checks (only if depth >= DEPTH_QS_CHECKS)
// will be generated.
MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory,
+ &thisThread->gateHistory,
&thisThread->captureHistory,
contHist,
to_sq((ss-1)->currentMove));
// Decrease stats for all non-best quiet moves
for (int i = 0; i < quietCount; ++i)
{
- thisThread->mainHistory[us][from_to(quietsSearched[i])] << -bonus2;
+ if (!(pos.wall_gating() && from_to(quietsSearched[i]) == from_to(bestMove)))
+ thisThread->mainHistory[us][from_to(quietsSearched[i])] << -bonus2;
+ if (pos.wall_gating())
+ thisThread->gateHistory[us][gating_square(quietsSearched[i])] << -bonus2;
update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), to_sq(quietsSearched[i]), -bonus2);
}
}
else
+ {
// Increase stats for the best move in case it was a capture move
captureHistory[moved_piece][to_sq(bestMove)][captured] << bonus1;
+ if (pos.wall_gating())
+ thisThread->gateHistory[us][gating_square(bestMove)] << bonus1;
+ }
// Extra penalty for a quiet early move that was not a TT move or
// main killer move in previous ply when it gets refuted.
{
moved_piece = pos.moved_piece(capturesSearched[i]);
captured = type_of(pos.piece_on(to_sq(capturesSearched[i])));
- captureHistory[moved_piece][to_sq(capturesSearched[i])][captured] << -bonus1;
+ if (!(pos.wall_gating() && from_to(capturesSearched[i]) == from_to(bestMove)))
+ captureHistory[moved_piece][to_sq(capturesSearched[i])][captured] << -bonus1;
+ if (pos.wall_gating())
+ thisThread->gateHistory[us][gating_square(capturesSearched[i])] << -bonus1;
}
}
Color us = pos.side_to_move();
Thread* thisThread = pos.this_thread();
thisThread->mainHistory[us][from_to(move)] << bonus;
+ if (pos.wall_gating())
+ thisThread->gateHistory[us][gating_square(move)] << bonus;
update_continuation_histories(ss, pos.moved_piece(move), to_sq(move), bonus);
// Penalty for reversed move in case of moved piece not being a pawn
counterMoves.fill(MOVE_NONE);
mainHistory.fill(0);
+ gateHistory.fill(0);
lowPlyHistory.fill(0);
captureHistory.fill(0);
Depth rootDepth, completedDepth;
CounterMoveHistory counterMoves;
ButterflyHistory mainHistory;
+ GateHistory gateHistory;
LowPlyHistory lowPlyHistory;
CapturePieceToHistory captureHistory;
ContinuationHistory continuationHistory[2][2];
#endif
#ifdef ALLVARS
-constexpr int MAX_MOVES = 4096;
+constexpr int MAX_MOVES = 8192;
#else
constexpr int MAX_MOVES = 1024;
#endif
string move = (type_of(m) == DROP ? UCI::dropped_piece(pos, m) + (CurrentProtocol == USI ? '*' : '@')
: UCI::square(pos, from)) + UCI::square(pos, to);
+ // Wall square
+ if (pos.wall_gating() && CurrentProtocol == XBOARD)
+ move += "," + UCI::square(pos, to) + UCI::square(pos, gating_square(m));
+
if (type_of(m) == PROMOTION)
move += pos.piece_to_char()[make_piece(BLACK, promotion_type(m))];
else if (type_of(m) == PIECE_PROMOTION)
move += UCI::square(pos, gating_square(m));
}
+ // Wall square
+ if (pos.wall_gating() && CurrentProtocol != XBOARD)
+ move += "," + UCI::square(pos, to) + UCI::square(pos, gating_square(m));
+
return move;
}
std::set<string> standard_variants = {
"normal", "nocastle", "fischerandom", "knightmate", "3check", "makruk", "shatranj",
"asean", "seirawan", "crazyhouse", "bughouse", "suicide", "giveaway", "losers", "atomic",
- "capablanca", "gothic", "janus", "caparandom", "grand", "shogi", "xiangqi"
+ "capablanca", "gothic", "janus", "caparandom", "grand", "shogi", "xiangqi", "duck"
};
void init_variant(const Variant* v) {
v->extinctionPseudoRoyal = true;
return v;
}
+#ifdef ALLVARS
+ // Duck chess
+ Variant* duck_variant() {
+ Variant* v = chess_variant_base()->init();
+ v->remove_piece(KING);
+ v->add_piece(COMMONER, 'k');
+ v->castlingKingPiece = COMMONER;
+ v->extinctionValue = -VALUE_MATE;
+ v->extinctionPieceTypes = {COMMONER};
+ v->duckGating = true;
+ v->stalemateValue = VALUE_MATE;
+ return v;
+ }
+#endif
// Three-check chess
// Check the king three times to win
// https://lichess.org/variant/threeCheck
v->promotionPieceTypes = {CENTAUR, QUEEN, ROOK, BISHOP, KNIGHT};
return v;
}
+ // Gustav III chess
+ // 10x8 variant with an amazon piece and wall squares
+ // https://www.chessvariants.com/play/gustav-iiis-chess
+ Variant* gustav3_variant() {
+ Variant* v = chess_variant_base()->init();
+ v->pieceToCharTable = "PNBRQ.............AKpnbrq.............ak";
+ v->maxRank = RANK_8;
+ v->maxFile = FILE_J;
+ v->castlingKingsideFile = FILE_H;
+ v->castlingQueensideFile = FILE_D;
+ v->add_piece(AMAZON, 'a');
+ v->startFen = "arnbqkbnra/*pppppppp*/*8*/*8*/*8*/*8*/*PPPPPPPP*/ARNBQKBNRA w KQkq - 0 1";
+ v->promotionPieceTypes = {AMAZON, QUEEN, ROOK, BISHOP, KNIGHT};
+ return v;
+ }
// Jeson mor
// Mongolian chess variant with knights only and a king of the hill like goal
// https://en.wikipedia.org/wiki/Jeson_Mor
v->castling = false;
return v;
}
+ // Omicron chess
+ // Omega chess on a 12x10 board
+ // http://www.eglebbk.dds.nl/program/chess-omicron.html
+ Variant* omicron_variant() {
+ Variant* v = chess_variant_base()->init();
+ v->pieceToCharTable = "PNBRQ..C.W...........Kpnbrq..c.w...........k";
+ v->maxRank = RANK_10;
+ v->maxFile = FILE_L;
+ v->startFen = "w**********w/*crnbqkbnrc*/*pppppppppp*/*10*/*10*/*10*/*10*/*PPPPPPPPPP*/*CRNBQKBNRC*/W**********W w KQkq - 0 1";
+ v->add_piece(CUSTOM_PIECES, 'c', "DAW"); // Champion
+ v->add_piece(CUSTOM_PIECES + 1, 'w', "CF"); // Wizard
+ v->castlingKingsideFile = FILE_I;
+ v->castlingQueensideFile = FILE_E;
+ v->castlingRank = RANK_2;
+ v->promotionRank = RANK_9;
+ v->promotionPieceTypes = {CUSTOM_PIECES + 1, CUSTOM_PIECES, QUEEN, ROOK, BISHOP, KNIGHT};
+ v->doubleStepRank = RANK_3;
+ v->doubleStepRankMin = RANK_3;
+ return v;
+ }
// Shako
// 10x10 variant with cannons by Jean-Louis Cazaux
// https://www.chessvariants.com/large.dir/shako.html
// https://en.wikipedia.org/wiki/Game_of_the_Amazons
Variant* amazons_variant() {
Variant* v = chess_variant_base()->init();
- v->pieceToCharTable = "P...Q.................p...q.................";
+ v->pieceToCharTable = "....Q.....................q.................";
v->maxRank = RANK_10;
v->maxFile = FILE_J;
v->reset_pieces();
v->add_piece(CUSTOM_PIECES, 'q', "mQ");
- v->add_piece(IMMOBILE_PIECE, 'p');
- v->startFen = "3q2q3/10/10/q8q/10/10/Q8Q/10/10/3Q2Q3[PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPpppppppppppppppppppppppppppppppppppppppppppppp] w - - 0 1";
+ v->startFen = "3q2q3/10/10/q8q/10/10/Q8Q/10/10/3Q2Q3 w - - 0 1";
v->stalemateValue = -VALUE_MATE;
v->arrowGating = true;
return v;
add("horde", horde_variant());
add("nocheckatomic", nocheckatomic_variant());
add("atomic", atomic_variant());
+#ifdef ALLVARS
+ add("duck", duck_variant());
+#endif
add("3check", threecheck_variant());
add("5check", fivecheck_variant());
add("crazyhouse", crazyhouse_variant());
add("chancellor", chancellor_variant());
add("embassy", embassy_variant());
add("centaur", centaur_variant());
+ add("gustav3", gustav3_variant());
add("jesonmor", jesonmor_variant());
add("courier", courier_variant());
add("grand", grand_variant());
add("opulent", opulent_variant());
add("tencubed", tencubed_variant());
+ add("omicron", omicron_variant());
add("shako", shako_variant());
add("clobber10", clobber10_variant());
add("flipello10", flipello10_variant());
bool mandatoryPiecePromotion = false;
bool pieceDemotion = false;
bool blastOnCapture = false;
+ bool petrifyOnCapture = false;
bool doubleStep = true;
Rank doubleStepRank = RANK_2;
Rank doubleStepRankMin = RANK_2;
bool immobilityIllegal = false;
bool gating = false;
bool arrowGating = false;
+ bool duckGating = false;
bool seirawanGating = false;
bool cambodianMoves = false;
Bitboard diagonalLines = 0;
nnueKing = NO_PIECE_TYPE;
}
int nnueSquares = (maxRank + 1) * (maxFile + 1);
- nnueUsePockets = (pieceDrops && (capturesToHand || (!mustDrop && !arrowGating && pieceTypes.size() != 1))) || seirawanGating;
+ nnueUsePockets = (pieceDrops && (capturesToHand || (!mustDrop && pieceTypes.size() != 1))) || seirawanGating;
int nnuePockets = nnueUsePockets ? 2 * int(maxFile + 1) : 0;
int nnueNonDropPieceIndices = (2 * pieceTypes.size() - (nnueKing != NO_PIECE_TYPE)) * nnueSquares;
int nnuePieceIndices = nnueNonDropPieceIndices + 2 * (pieceTypes.size() - (nnueKing != NO_PIECE_TYPE)) * nnuePockets;
# 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)
+# petrifyOnCapture: non-pawn pieces are turned into wall squares when capturing [bool] (default: false)
# doubleStep: enable pawn double step [bool] (default: true)
# doubleStepRank: relative rank from where pawn double steps are allowed [Rank] (default: 2)
# doubleStepRankMin: earlist relative rank from where pawn double steps are allowed [Rank] (default: 2)
# dropNoDoubledCount: specifies the count of already existing pieces for dropNoDoubled [PieceType] (default: 1)
# immobilityIllegal: pieces may not move to squares where they can never move from [bool] (default: false)
# gating: maintain squares on backrank with extra rights in castling field of FEN [bool] (default: false)
-# arrowGating: allow gating in Game of the Amazons style [bool] (default: false)
+# arrowGating: gating of wall squares in Game of the Amazons style [bool] (default: false)
+# duckGating: gating of a wall square in Duck chess style [bool] (default: false)
# seirawanGating: allow gating of pieces in hand like in S-Chess, requires "gating = true" [bool] (default: false)
# cambodianMoves: enable special moves of cambodian chess, requires "gating = true" [bool] (default: false)
# diagonalLines: enable special moves along diagonal for specific squares (Janggi) [Bitboard]
expect perft.exp opulent startpos 3 133829 > /dev/null
expect perft.exp tencubed startpos 3 68230 > /dev/null
expect perft.exp centaur startpos 3 24490 > /dev/null
+ expect perft.exp gustav3 startpos 4 331659 > /dev/null
+ expect perft.exp omicron startpos 4 967381 > /dev/null
expect perft.exp shako "fen 4kc3c/ernbq1b1re/ppp3p1pp/3p2pp2/4p5/5P4/2PN2P3/PP1PP2PPP/ER1BQKBNR1/5C3C w KQ - 0 9" 3 26325 > /dev/null
expect perft.exp shako "fen 4ncr1k1/1cr2P4/pp2p2pp1/P7PN/2Ep1p4/B3P1eN2/2P1n1P3/1B1P1K4/9p/5C2CR w - - 0 1" 3 180467 > /dev/null
expect perft.exp shako "fen r5k3/4q2c2/1ebppnp3/1pp3BeEQ/10/2PE2P3/1P3P4/5NP2P/rR3KB3/7C2 w Q - 3 35" 2 4940 > /dev/null
# special variants
if [[ $1 == "all" ]]; then
+ expect perft.exp duck startpos 1 640 > /dev/null
expect perft.exp amazons startpos 1 2176 > /dev/null
fi