Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes for PR #762 (Musketeer variant) #771

Closed
wants to merge 14 commits into from
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
31 changes: 25 additions & 6 deletions src/apiutil.h
Original file line number Diff line number Diff line change
Expand Up @@ -564,8 +564,15 @@ inline Validation fill_char_board(CharBoard& board, const std::string& fenBoard,
{
if (c == ' ' || c == '[')
break;
if (c == '*')
++fileIdx;
if (c == '*') {
if (v->commitGates)
{
// just ignore?
}
else {
++fileIdx;
}
}
else if (isdigit(c))
{
fileIdx += c - '0';
Expand All @@ -575,14 +582,26 @@ inline Validation fill_char_board(CharBoard& board, const std::string& fenBoard,
}
else if (c == '/')
{
++rankIdx;
if (fileIdx != board.get_nb_files())
if (v->commitGates && rankIdx == 0 && fileIdx == 0)
{
std::cerr << "curRankWidth != nbFiles: " << fileIdx << " != " << board.get_nb_files() << std::endl;
return NOK;
// ignore starting '********/'
}
else {
++rankIdx;
if (fileIdx != board.get_nb_files())
{
std::cerr << "curRankWidth != nbFiles: " << fileIdx << " != " << board.get_nb_files() << std::endl;
return NOK;
}
}
if (rankIdx == board.get_nb_ranks())
{
if (v->commitGates)
{
rankIdx--; // pretend we didn't see the ending '/********'
}
break;
}
fileIdx = 0;
}
else if (!contains(validSpecialCharactersFirstField, c))
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
102 changes: 87 additions & 15 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 @@ -284,7 +287,17 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960,

else if (token == '/')
{
sq = SQ_A1 + --r * NORTH;
if(commit_gates())
{
if(rank != 0 && rank <= max_rank()){
sq += 2 * SOUTH + (FILE_MAX - max_file()) * EAST;
}
++rank;
commitFile = 0;
}
else {
sq = SQ_A1 + --r * NORTH;
}
if (!is_ok(sq))
break;
}
Expand All @@ -297,28 +310,49 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960,
else if (!is_ok(sq) || file_of(sq) > max_file() || rank_of(sq) > r)
continue;

// Wall square
else if (token == '*')
{
st->wallSquares |= sq;
byTypeBB[ALL_PIECES] |= sq;
++sq;
if(commit_gates())
{
// musketeer
++commitFile;
}
else {
// Wall square
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() == '~')
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
else if (token == '+' && (idx = piece_to_char().find(ss.peek())) != string::npos && promoted_piece_type(type_of(Piece(idx))))
{
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 +411,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 +638,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 +715,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 +755,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 +781,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 +808,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,7 +836,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))))
if (!can_castle(ANY_CASTLING) && !(gating() && !commit_gates() && (gates(WHITE) | gates(BLACK))))
ss << '-';

// Counting limit or ep-square
Expand Down Expand Up @@ -1945,6 +1993,20 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
st->nonPawnMaterial[us] += PieceValue[MG][gating_piece];
}

// Musketeer gating
if(commit_gates()){

if(st->removedGatingType > NO_PIECE_TYPE){
commit_piece(piece_on(from), file_of(from));
}
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 +2231,10 @@ void Position::undo_move(Move m) {
st->gatesBB[us] |= gating_square(m);
}

if(commit_gates() && 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 +2328,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 @@ -1209,6 +1209,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 @@ -1880,6 +1910,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
Loading