Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ The games currently supported besides chess are listed below. Fairy-Stockfish ca
- [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, [Dragon Chess](https://www.edami.com/dragonchess/)
- [Musketeer](https://www.musketeerchess.net)
- [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)
Expand Down
3 changes: 2 additions & 1 deletion src/parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,8 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
parse_attribute("wallingRegion", v->wallingRegion[WHITE]);
parse_attribute("wallingRegion", v->wallingRegion[BLACK]);
parse_attribute("wallOrMove", v->wallOrMove);
parse_attribute("seirawanGating", v->seirawanGating);
parse_attribute("seirawanGating", v->seirawanGating);
parse_attribute("commitGates", v->commitGates);
parse_attribute("cambodianMoves", v->cambodianMoves);
parse_attribute("diagonalLines", v->diagonalLines);
parse_attribute("pass", v->pass[WHITE]);
Expand Down
82 changes: 69 additions & 13 deletions src/position.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -267,10 +267,13 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960,
Rank r = max_rank();
Square sq = SQ_A1 + r * NORTH;

int commitFile = 0;
int rank = 0;

// 1. Piece placement
while ((ss >> token) && !isspace(token))
{
if (isdigit(token))
{
if (isdigit(token) && (!commit_gates() || (rank != 0 && rank != max_rank() + 2)))
{
#ifdef LARGEBOARDS
if (isdigit(ss.peek()))
Expand All @@ -283,12 +286,14 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960,
}

else if (token == '/')
{
sq = SQ_A1 + --r * NORTH;
{
if(!commit_gates() || (rank != 0 && rank <= max_rank())) sq += 2 * SOUTH + (FILE_MAX - max_file()) * EAST;
++rank;
commitFile = 0;
if (!is_ok(sq))
break;
}

else if(token == '*') ++commitFile;
// Stop before pieces in hand
else if (token == '[')
break;
Expand All @@ -309,8 +314,15 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960,
{
if (ss.peek() == '~')
ss >> token;
put_piece(Piece(idx), sq, token == '~');
++sq;

if(v->commitGates && (rank == 0 || rank == max_rank() + 2)){
commit_piece(Piece(idx), File(commitFile));
++commitFile;
}
else{
put_piece(Piece(idx), sq, token == '~');
++sq;
}
}

// Promoted shogi pieces
Expand All @@ -319,6 +331,14 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960,
ss >> token;
put_piece(make_piece(color_of(Piece(idx)), promoted_piece_type(type_of(Piece(idx)))), sq, true, Piece(idx));
++sq;
if(v->commitGates && (rank == 0 || rank == max_rank() + 2)){
commit_piece(Piece(idx), File(commitFile));
++commitFile;
}
else {
put_piece(make_piece(color_of(Piece(idx)), promoted_piece_type(type_of(Piece(idx)))), sq, true, Piece(idx));
++sq;
}
}
}
// Pieces in hand
Expand Down Expand Up @@ -377,7 +397,7 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960,
}

// Set gates (and skip castling rights)
if (gating())
if (gating() && !commit_gates())
{
st->gatesBB[c] |= rsq;
if (token == 'K' || token == 'Q')
Expand Down Expand Up @@ -604,6 +624,7 @@ void Position::set_state(StateInfo* si) const {
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;
si->removedGatingType = NO_PIECE_TYPE;

set_check_info(si);

Expand Down Expand Up @@ -680,6 +701,13 @@ string Position::fen(bool sfen, bool showPromoted, int countStarted, std::string

int emptyCnt;
std::ostringstream ss;
if(commit_gates()){
for(File f = FILE_A; f < max_file(); ++f){
if(has_committed_piece(BLACK, f)) ss << piece_to_char()[make_piece(BLACK, committedGates[BLACK][f])];
else ss << "*";
}
ss << "/";
}

for (Rank r = max_rank(); r >= RANK_1; --r)
{
Expand Down Expand Up @@ -713,7 +741,13 @@ string Position::fen(bool sfen, bool showPromoted, int countStarted, std::string
if (r > RANK_1)
ss << '/';
}

if(commit_gates()){
ss << "/";
for(File f = FILE_A; f <= max_file(); ++f){
if(has_committed_piece(WHITE, f)) ss << piece_to_char()[make_piece(WHITE, committedGates[WHITE][f])];
else ss << "*";
}
}
// SFEN
if (sfen)
{
Expand All @@ -733,7 +767,7 @@ string Position::fen(bool sfen, bool showPromoted, int countStarted, std::string
}

// pieces in hand
if (!variant()->freeDrops && (piece_drops() || seirawan_gating()))
if (!variant()->freeDrops && (piece_drops() || seirawan_gating()) && !commit_gates())
{
ss << '[';
if (holdings != "-")
Expand All @@ -760,7 +794,7 @@ string Position::fen(bool sfen, bool showPromoted, int countStarted, std::string
if (can_castle(WHITE_OOO))
ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OOO))) : 'Q');

if (gating() && gates(WHITE) && (!seirawan_gating() || count_in_hand(WHITE, ALL_PIECES) > 0 || captures_to_hand()))
if (gating() && !commit_gates() && gates(WHITE) && (!seirawan_gating() || count_in_hand(WHITE, ALL_PIECES) > 0 || captures_to_hand()))
for (File f = FILE_A; f <= max_file(); ++f)
if ( (gates(WHITE) & file_bb(f))
// skip gating flags redundant with castling flags
Expand Down Expand Up @@ -788,8 +822,7 @@ string Position::fen(bool sfen, bool showPromoted, int countStarted, std::string
&& !(can_castle(BLACK_OOO) && f == file_of(castling_rook_square(BLACK_OOO))))
ss << char('a' + f);

if (!can_castle(ANY_CASTLING) && !(gating() && (gates(WHITE) | gates(BLACK))))
ss << '-';
if (!can_castle(ANY_CASTLING) && !(gating() && !commit_gates() && (gates(WHITE) | gates(BLACK))))

// Counting limit or ep-square
if (st->countingLimit)
Expand Down Expand Up @@ -1944,7 +1977,20 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
st->materialKey ^= Zobrist::psq[gating_piece][pieceCount[gating_piece]];
st->nonPawnMaterial[us] += PieceValue[MG][gating_piece];
}
if(st->removedGatingType > NO_PIECE_TYPE){
commit_piece(piece_on(from), file_of(from));
}

// Musketeer gating
if(commit_gates()){
Rank r = rank_of(from);
if(r == RANK_1 && has_committed_piece(WHITE, file_of(from))){
st->removedGatingType = drop_committed_piece(WHITE, file_of(from));
} else if(r == max_rank() && has_committed_piece(BLACK, file_of(from))){
st->removedGatingType = drop_committed_piece(BLACK, file_of(from));
}
else st->removedGatingType = NO_PIECE_TYPE;
}
// Remove gates
if (gating())
{
Expand Down Expand Up @@ -2169,6 +2215,10 @@ void Position::undo_move(Move m) {
st->gatesBB[us] |= gating_square(m);
}

if(st->removedGatingType > NO_PIECE_TYPE){
commit_piece(piece_on(from), file_of(from));
}

if (type_of(m) == PROMOTION)
{
assert((promotion_zone(us) & to) || sittuyin_promotion());
Expand Down Expand Up @@ -2262,6 +2312,12 @@ void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Squ
Piece castlingKingPiece = piece_on(Do ? from : to);
Piece castlingRookPiece = piece_on(Do ? rfrom : rto);

if(commit_gates()){
if(has_committed_piece(us, file_of(rfrom))){
drop_committed_piece(us, file_of(rfrom));
}
}

if (Do && Eval::useNNUE)
{
auto& dp = st->dirtyPiece;
Expand Down
35 changes: 35 additions & 0 deletions src/position.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ struct StateInfo {
bool pass;
Move move;
int repetition;
PieceType removedGatingType;

// Used by NNUE
Eval::NNUE::Accumulator accumulator;
Expand Down Expand Up @@ -180,6 +181,7 @@ class Position {
bool walling() const;
WallingRule walling_rule() const;
bool seirawan_gating() const;
bool commit_gates() const;
bool cambodian_moves() const;
Bitboard diagonal_lines() const;
bool pass(Color c) const;
Expand Down Expand Up @@ -369,12 +371,17 @@ class Position {
bool tsumeMode;
bool chess960;
int pieceCountInHand[COLOR_NB][PIECE_TYPE_NB];
PieceType committedGates[COLOR_NB][FILE_NB];
int virtualPieces;
Bitboard promotedPieces;
void add_to_hand(Piece pc);
void remove_from_hand(Piece pc);
void drop_piece(Piece pc_hand, Piece pc_drop, Square s);
void undrop_piece(Piece pc_hand, Square s);
void commit_piece(Piece pc, File fl);
void uncommit_piece(Color cl, File fl);
bool has_committed_piece(Color cl, File fl) const;
PieceType drop_committed_piece(Color cl, File fl);
Bitboard find_drop_region(Direction dir, Square s, Bitboard occupied) const;
};

Expand Down Expand Up @@ -799,6 +806,11 @@ inline WallingRule Position::walling_rule() const {
return var->wallingRule;
}

inline bool Position::commit_gates() const {
assert(var != nullptr);
return var->commitGates;
}

inline bool Position::seirawan_gating() const {
assert(var != nullptr);
return var->seirawanGating;
Expand Down Expand Up @@ -1561,6 +1573,29 @@ inline bool Position::can_drop(Color c, PieceType pt) const {
return variant()->freeDrops || count_in_hand(c, pt) > 0;
}

inline void Position::commit_piece(Piece pc, File fl){
committedGates[color_of(pc)][fl] = type_of(pc);
}

inline void Position::uncommit_piece(Color cl, File fl){
committedGates[cl][fl] = NO_PIECE_TYPE;
}

inline bool Position::has_committed_piece(Color cl, File fl) const {
return committedGates[cl][fl] > NO_PIECE_TYPE;
}

inline PieceType Position::drop_committed_piece(Color cl, File fl){
if(has_committed_piece(cl, fl)){
Square dropSquare = make_square(fl, (cl == WHITE)? RANK_1 : max_rank());
PieceType committedPieceType = committedGates[cl][fl];
put_piece(make_piece(cl, committedPieceType), dropSquare, false, NO_PIECE);
uncommit_piece(cl, fl);
return committedPieceType;
}
else return NO_PIECE_TYPE;
}

} // namespace Stockfish

#endif // #ifndef POSITION_H_INCLUDED
31 changes: 31 additions & 0 deletions src/variant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1207,6 +1207,36 @@ namespace {
return v;
}
#ifdef LARGEBOARDS
// Musketeer Chess
// https://musketeerchess.net
// A Seirawan-inspired variant with unique gating mechanics.
// Pieces are introduced to predefined squares, chosen before game start, this is named Gating Selection = Where the chosen piece is going to be gated
// Gating of the additional pieces is activated when first-rank pieces move for the first time. Only the additional piece waiting to be gated on that specific square can be introduced.
// Features a variety of new pieces, thus there is a piece selection step where both players must agree to chose the additional piece combination.
// In Fairy Stockfish the Piece Selection is determined at the PieceToCharTable, this default combination can be changed in variant.ini
Variant* musketeer_variant() {
Variant* v = chess_variant();
v->variantTemplate = "seirawan";
v->pieceToCharTable = "PNBRQ.C..........LO..Kpnbrq.c..........lo..k"; // The default piece combo in Musketeer Chess is Leopard L and Musketeer Cannon O
v->add_piece(ARCHBISHOP, 'a');
v->add_piece(CHANCELLOR, 'c');
v->add_piece(AMAZON, 'd'); // also called Dragon in Musketeer, but Amazon is the most accurate
v->add_piece(CUSTOM_PIECE_1, 'l', "F2N"); // Leopard
v->add_piece(CUSTOM_PIECE_2, 'h', "ADGH"); // Hawk
v->add_piece(CUSTOM_PIECE_3, 'u', "NC"); // Unicorn
V->add_piece(CUSTOM_PIECE_4, 's', "B2ND"); // Spider
v->add_piece(CUSTOM_PIECE_5, 'f', "B3vND"); // Fortress
v->add_piece(CUSTOM_PIECE_6, 'e', "FWDA"); // Musketeer Elephant
v->add_piece(CUSTOM_PIECE_7, 'o', "FWDsN"); // Musketeer Cannon

//"********/rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR/******** w KQkq - 0 1"
v->startFen = "********/rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR/******** w KQkq - 0 1";
v->gating = true;
v->commitGates = true;
v->promotionPieceTypes[BLACK] = piece_set(CUSTOM_PIECE_1) | CUSTOM_PIECE_7 | QUEEN | ROOK | BISHOP | KNIGHT;
v->promotionPieceTypes[WHITE] = piece_set(CUSTOM_PIECE_1) | CUSTOM_PIECE_7 | QUEEN | ROOK | BISHOP | KNIGHT;
return v;
}
// Shogi (Japanese chess)
// https://en.wikipedia.org/wiki/Shogi
Variant* shogi_variant() {
Expand Down Expand Up @@ -1878,6 +1908,7 @@ void VariantMap::init() {
add("minixiangqi", minixiangqi_variant());
add("raazuvaa", raazuvaa_variant());
#ifdef LARGEBOARDS
add("musketeer", musketeer_variant());
add("shogi", shogi_variant());
add("shoshogi", shoshogi_variant());
add("yarishogi", yarishogi_variant());
Expand Down
1 change: 1 addition & 0 deletions src/variant.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ struct Variant {
Bitboard wallingRegion[COLOR_NB] = {AllSquares, AllSquares};
bool wallOrMove = false;
bool seirawanGating = false;
bool commitGates = false;
bool cambodianMoves = false;
Bitboard diagonalLines = 0;
bool pass[COLOR_NB] = {false, false};
Expand Down
1 change: 1 addition & 0 deletions src/variants.ini
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@
# wallingRegionBlack: mask where wall squares (including duck) can be placed by black [Bitboard] (default: all squares)
# wallOrMove: can wall or move, but not both [bool] (default: false)
# seirawanGating: allow gating of pieces in hand like in S-Chess, requires "gating = true" [bool] (default: false)
# commitGates: gating pieces are committed to gating squares like in Musketeer chess [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]
# pass: allow passing [bool] (default: false)
Expand Down