From 2091e40213b3d30b902bd56e9a25bb77df7ad419 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Thu, 30 May 2024 19:03:59 +0700 Subject: [PATCH] Non-royal castling pieces (#82) --- lib/src/actions/actions/checks.dart | 5 +++- lib/src/constants.dart | 1 + lib/src/fen.dart | 25 ++++++++++-------- lib/src/game/game.dart | 17 +++++++----- lib/src/game/game_movement.dart | 40 +++++++++++++++++++---------- lib/src/game/game_outputs.dart | 5 ++-- lib/src/game/game_result.dart | 9 +++++-- lib/src/piece_type.dart | 16 ++++++++++-- lib/src/state/masked_state.dart | 5 ++++ lib/src/state/state.dart | 7 +++++ lib/src/variant/built_variant.dart | 15 ++++++++--- lib/src/variant/variants/misc.dart | 30 +++++++++++++++++++--- test/divide.dart | 2 +- test/misc_test.dart | 9 +++++++ 14 files changed, 141 insertions(+), 45 deletions(-) diff --git a/lib/src/actions/actions/checks.dart b/lib/src/actions/actions/checks.dart index 78b968b..3d28c58 100644 --- a/lib/src/actions/actions/checks.dart +++ b/lib/src/actions/actions/checks.dart @@ -104,7 +104,10 @@ class ActionCheckPieceCount extends Action { } return [ EffectSetGameResult( - WonGameElimination(winner: white ? Bishop.white : Bishop.black), + WonGameElimination( + winner: white ? Bishop.white : Bishop.black, + pieceType: pieceType, + ), ), ]; }, diff --git a/lib/src/constants.dart b/lib/src/constants.dart index d88d1af..0f19798 100644 --- a/lib/src/constants.dart +++ b/lib/src/constants.dart @@ -208,6 +208,7 @@ enum Variants { dobutsu(Dobutsu.dobutsu, alt: 'Dobutsu Shogi'), spawn(MiscVariants.spawn, alt: 'Spawn Chess'), kinglet(MiscVariants.kinglet, alt: 'Kinglet Chess'), + extinction(MiscVariants.extinction), threeKings(MiscVariants.threeKings, alt: 'Three Kings Chess'), domination(MiscVariants.domination), dart(MiscVariants.dart), diff --git a/lib/src/fen.dart b/lib/src/fen.dart index f777dbc..748b28c 100644 --- a/lib/src/fen.dart +++ b/lib/src/fen.dart @@ -120,6 +120,7 @@ ParseFenResult parseFen({ int sq = 0; int emptySquares = 0; List royalSquares = List.filled(Bishop.numPlayers, Bishop.invalid); + List castlingSquares = List.filled(Bishop.numPlayers, Bishop.invalid); for (String c in boardSymbols) { if (c == '~') { @@ -154,6 +155,9 @@ ParseFenResult parseFen({ if (variant.pieces[pieceIndex].type.royal) { royalSquares[colour] = sq; } + if (variant.pieces[pieceIndex].type.castling) { + castlingSquares[colour] = sq; + } sq++; } } @@ -191,7 +195,7 @@ ParseFenResult parseFen({ final castling = variant.castling ? setupCastling( castlingString: castlingStr, - royalSquares: royalSquares, + castlingSquares: castlingSquares, board: board, variant: variant, ) @@ -204,6 +208,7 @@ ParseFenResult parseFen({ epSquare: ep, castlingRights: castling.castlingRights, royalSquares: royalSquares, + castlingSquares: castlingSquares, virginFiles: virginFiles, hands: hands, gates: gates, @@ -222,14 +227,14 @@ class ParseFenResult { class CastlingSetup { final int castlingRights; - final int? royalFile; + final int? castlingFile; final int? castlingTargetK; final int? castlingTargetQ; final List? castlingFileSymbols; const CastlingSetup({ required this.castlingRights, - this.royalFile, + this.castlingFile, this.castlingTargetK, this.castlingTargetQ, this.castlingFileSymbols, @@ -240,7 +245,7 @@ class CastlingSetup { CastlingSetup setupCastling({ required String castlingString, - required List royalSquares, + required List castlingSquares, required List board, required BuiltVariant variant, }) { @@ -252,7 +257,7 @@ CastlingSetup setupCastling({ throw ('Invalid castling string'); } List? castlingFileSymbols; - int? royalFile; + int? castlingFile; int? castlingTargetK; int? castlingTargetQ; final size = variant.boardSize; @@ -260,12 +265,12 @@ CastlingSetup setupCastling({ for (String c in castlingString.split('')) { // there is probably a better way to do all of this bool white = c == c.toUpperCase(); - royalFile = size.file(royalSquares[white ? 0 : 1]); + castlingFile = size.file(castlingSquares[white ? 0 : 1]); if (Castling.symbols.containsKey(c)) { cr += Castling.symbols[c]!; } else { int cFile = fileFromSymbol(c); - bool kingside = cFile > size.file(royalSquares[white ? 0 : 1]); + bool kingside = cFile > size.file(castlingSquares[white ? 0 : 1]); if (kingside) { castlingTargetK = cFile; cr += white ? Castling.k : Castling.bk; @@ -285,9 +290,9 @@ CastlingSetup setupCastling({ bool kingside = false; for (int j = 0; j < size.h; j++) { int piece = board[r + j].type; - if (piece == variant.royalPiece) { + if (piece == variant.castlingPiece) { kingside = true; - } else if (piece == variant.castlingPiece) { + } else if (piece == variant.rookPiece) { if (kingside) { castlingTargetK = j; } else { @@ -306,7 +311,7 @@ CastlingSetup setupCastling({ } return CastlingSetup( castlingRights: cr, - royalFile: royalFile, + castlingFile: castlingFile, castlingTargetK: castlingTargetK, castlingTargetQ: castlingTargetQ, castlingFileSymbols: castlingFileSymbols, diff --git a/lib/src/game/game.dart b/lib/src/game/game.dart index fac2f3e..387981e 100644 --- a/lib/src/game/game.dart +++ b/lib/src/game/game.dart @@ -32,7 +32,7 @@ class Game { int? castlingTargetK; int? castlingTargetQ; - int? royalFile; + int? castlingFile; List castlingFileSymbols = ['K', 'Q', 'k', 'q']; late MoveGenParams royalCaptureOptions; @@ -81,7 +81,7 @@ class Game { final newState = result.state.copyWith(hash: zobrist.compute(result.state)); zobrist.incrementHash(newState.hash); history.add(newState); - royalFile = result.castling.royalFile; + castlingFile = result.castling.castlingFile; castlingTargetK = result.castling.castlingTargetK; castlingTargetQ = result.castling.castlingTargetQ; castlingFileSymbols = @@ -384,7 +384,10 @@ class Game { } // Generate castling - if (variant.castling && options.castling && pieceType.royal && !inCheck) { + if (variant.castling && + options.castling && + pieceType.castling && + !inCheck) { bool kingside = colour == Bishop.white ? state.castlingRights.wk : state.castlingRights.bk; @@ -425,7 +428,7 @@ class Game { if (board[targetSq].isNotEmpty && targetSq != rookSq && targetSq != square) continue; - int numMidSqs = (targetFile - royalFile!).abs(); + int numMidSqs = (targetFile - castlingFile!).abs(); bool valid = true; if (!options.ignorePieces) { int numRookMidSquares = (targetFile - rookFile).abs(); @@ -433,14 +436,14 @@ class Game { for (int j = 1; j <= numRookMidSquares; j++) { int midFile = rookFile + (i == 0 ? -j : j); int midSq = size.square(midFile, royalRank); - if (board[midSq].isNotEmpty && midFile != royalFile) { + if (board[midSq].isNotEmpty && midFile != castlingFile) { valid = false; break; } } } for (int j = 1; j <= numMidSqs; j++) { - int midFile = royalFile! + (i == 0 ? j : -j); + int midFile = castlingFile! + (i == 0 ? j : -j); // For some Chess960 positions. // See also https://github.com/alexobviously/bishop/issues/11 @@ -456,7 +459,7 @@ class Game { break; } - if (midFile == targetFile && targetFile == royalFile) { + if (midFile == targetFile && targetFile == castlingFile) { continue; } // king starting on target diff --git a/lib/src/game/game_movement.dart b/lib/src/game/game_movement.dart index e2936ff..466bd83 100644 --- a/lib/src/game/game_movement.dart +++ b/lib/src/game/game_movement.dart @@ -233,6 +233,7 @@ extension GameMovement on Game { int castlingRights = state.castlingRights; List royalSquares = List.from(state.royalSquares); + List castlingSquares = List.from(state.castlingSquares); if (move.enPassant) { // Remove the captured ep piece @@ -265,29 +266,37 @@ extension GameMovement on Game { : variant.castlingOptions.qTarget!; int rookFile = kingside ? castlingFile - 1 : castlingFile + 1; int rookSq = size.square(rookFile, fromRank); - int kingSq = size.square(castlingFile, fromRank); + int castlingSq = size.square(castlingFile, fromRank); int rook = board[move.castlingPieceSquare!]; hash ^= zobrist.table[move.castlingPieceSquare!][rook.piece]; - if (board[kingSq].isNotEmpty) { - hash ^= zobrist.table[kingSq][board[kingSq].piece]; + if (board[castlingSq].isNotEmpty) { + hash ^= zobrist.table[castlingSq][board[castlingSq].piece]; } - hash ^= zobrist.table[kingSq][fromSq.piece]; + hash ^= zobrist.table[castlingSq][fromSq.piece]; if (board[rookSq].isNotEmpty) { hash ^= zobrist.table[rookSq][board[rookSq].piece]; } hash ^= zobrist.table[rookSq][rook.piece]; board[move.castlingPieceSquare!] = Bishop.empty; - board[kingSq] = fromSq.setInitialState(false); + board[castlingSq] = fromSq.setInitialState(false); board[rookSq] = rook; castlingRights = castlingRights.remove(colour); - royalSquares[colour] = kingSq; - } else if (fromPiece.royal) { - // king moved - castlingRights = castlingRights.remove(colour); - royalSquares[colour] = move.to; + castlingSquares[colour] = castlingSq; + if (fromPiece.royal) { + royalSquares[colour] = castlingSq; + } + } else if (fromPiece.royal || fromPiece.castling) { + // Royal or castling piece moved, but it wasn't castling. + if (fromPiece.castling) { + castlingRights = castlingRights.remove(colour); + castlingSquares[colour] = move.to; + } + if (fromPiece.royal) { + royalSquares[colour] = move.to; + } } else { // If the player's rook moved, remove relevant castling rights - if (fromSq.type == variant.castlingPiece) { + if (fromSq.type == variant.rookPiece) { int fromFile = size.file(move.from); bool onFirstRank = size.rank(move.from) == size.firstRank(colour); int ks = colour == Bishop.white ? Castling.k : Castling.bk; @@ -302,8 +311,8 @@ extension GameMovement on Game { castlingRights = castlingRights.flip(qs); } } - // If the opponent's rook was captured, remove relevant castling rights - if (move.capture && move.capturedPiece!.type == variant.castlingPiece) { + // If the opponent's rook was captured, remove relevant castling rights. + if (move.capture && move.capturedPiece!.type == variant.rookPiece) { // rook captured int toFile = size.file(move.to); int opponent = colour.opponent; @@ -321,6 +330,10 @@ extension GameMovement on Game { } } } + // If the opponent's castling piece was captured. + if (move.capture && move.capturedPiece!.type == variant.castlingPiece) { + castlingRights = castlingRights.remove(colour.opponent); + } if (castlingRights != state.castlingRights) { hash ^= zobrist.table[zobrist.castling][state.castlingRights]; hash ^= zobrist.table[zobrist.castling][castlingRights]; @@ -341,6 +354,7 @@ extension GameMovement on Game { state.turn == Bishop.black ? state.fullMoves + 1 : state.fullMoves, castlingRights: castlingRights, royalSquares: royalSquares, + castlingSquares: castlingSquares, virginFiles: virginFiles, epSquare: epSquare, hash: hash, diff --git a/lib/src/game/game_outputs.dart b/lib/src/game/game_outputs.dart index 6b45f9d..4aa654d 100644 --- a/lib/src/game/game_outputs.dart +++ b/lib/src/game/game_outputs.dart @@ -218,10 +218,11 @@ extension GameOutputs on Game { int firstTurn = history.first.turn; int turn = firstTurn; String pgn = ''; - for (int i = 0; i < moves.length; i++) { - if (i == 0 || turn == Bishop.white) + for (int i = 0; i < moves.length; i++) { + if (i == 0 || turn == Bishop.white) { pgn = '$pgn${firstMove + i ~/ 2}${(i == 0 && turn == Bishop.black) ? "" : ". "}'; + } if (i == 0 && turn == Bishop.black) { pgn = '$pgn... '; firstMove++; diff --git a/lib/src/game/game_result.dart b/lib/src/game/game_result.dart index a635210..69c6952 100644 --- a/lib/src/game/game_result.dart +++ b/lib/src/game/game_result.dart @@ -107,13 +107,18 @@ class WonGameRoyalDead extends WonGame { } class WonGameElimination extends WonGame { - const WonGameElimination({required super.winner}); + final String? pieceType; + const WonGameElimination({required super.winner, this.pieceType}); @override String toString() => 'WonGameElimination($winner)'; @override - String get readable => '${super.readable} by elimination'; + String get readable => [ + super.readable, + 'by elimination', + if (pieceType != null) '($pieceType)', + ].join(' '); } class WonGameStalemate extends WonGame { diff --git a/lib/src/piece_type.dart b/lib/src/piece_type.dart index 5c7214a..db088fb 100644 --- a/lib/src/piece_type.dart +++ b/lib/src/piece_type.dart @@ -9,9 +9,14 @@ class PieceType { /// All of the different move groups this piece can make. final List moves; - /// Royal pieces can be checkmated, and can castle. + /// Royal pieces can be checkmated, and can castle, + /// unless [castling] is false. final bool royal; + /// Whether this is a castling piece. + /// If not specified, it will be the same as [royal]. + final bool castling; + /// Defines the promotion behaviour of this piece type. final PiecePromoOptions promoOptions; @@ -51,6 +56,7 @@ class PieceType { this.betza, required this.moves, this.royal = false, + bool? castling, this.promoOptions = PiecePromoOptions.promoPiece, this.enPassantable = false, this.noSanSymbol = false, @@ -58,7 +64,7 @@ class PieceType { this.regionEffects = const [], this.actions = const [], this.optimisationData, - }); + }) : castling = castling ?? royal; factory PieceType.fromJson( Map json, { @@ -68,6 +74,7 @@ class PieceType { return PieceType.fromBetza( json['betza'], royal: json['royal'] ?? false, + castling: json['castling'], promoOptions: json.containsKey('promoOptions') ? PiecePromoOptions.fromJson(json['promoOptions']) : PiecePromoOptions.promoPiece, @@ -97,6 +104,7 @@ class PieceType { return { 'betza': betza, if (verbose || royal) 'royal': royal, + if (verbose || castling != royal) 'castling': castling, if (verbose || promoOptions != PiecePromoOptions.promoPiece) 'promoOptions': promoOptions.toJson(), if (verbose || enPassantable) 'enPassantable': enPassantable, @@ -119,6 +127,7 @@ class PieceType { String? betza, List? moves, bool? royal, + bool? castling, PiecePromoOptions? promoOptions, bool? enPassantable, bool? noSanSymbol, @@ -131,6 +140,7 @@ class PieceType { betza: betza ?? this.betza, moves: moves ?? this.moves, royal: royal ?? this.royal, + castling: castling ?? this.castling, promoOptions: promoOptions ?? this.promoOptions, enPassantable: enPassantable ?? this.enPassantable, noSanSymbol: noSanSymbol ?? this.noSanSymbol, @@ -194,6 +204,7 @@ class PieceType { factory PieceType.fromBetza( String betza, { bool royal = false, + bool? castling, PiecePromoOptions promoOptions = PiecePromoOptions.promoPiece, bool enPassantable = false, bool noSanSymbol = false, @@ -209,6 +220,7 @@ class PieceType { betza: betza, moves: moves, royal: royal, + castling: castling, promoOptions: promoOptions, enPassantable: enPassantable, noSanSymbol: noSanSymbol, diff --git a/lib/src/state/masked_state.dart b/lib/src/state/masked_state.dart index 7a8581c..443e66e 100644 --- a/lib/src/state/masked_state.dart +++ b/lib/src/state/masked_state.dart @@ -23,6 +23,7 @@ class MaskedState extends BishopState { required super.castlingRights, super.epSquare, required super.royalSquares, + required super.castlingSquares, required super.virginFiles, super.hands, super.gates, @@ -49,6 +50,7 @@ class MaskedState extends BishopState { castlingRights: state.castlingRights, epSquare: state.epSquare, royalSquares: state.royalSquares, + castlingSquares: state.castlingSquares, virginFiles: state.virginFiles, hands: state.hands, gates: state.gates, @@ -78,6 +80,7 @@ class MaskedState extends BishopState { castlingRights: state.castlingRights, epSquare: state.epSquare, royalSquares: state.royalSquares, + castlingSquares: state.castlingSquares, virginFiles: state.virginFiles, hands: hands ?? state.hands, gates: gates ?? state.gates, @@ -100,6 +103,7 @@ class MaskedState extends BishopState { CastlingRights? castlingRights, int? epSquare, List? royalSquares, + List? castlingSquares, List>? virginFiles, List? hands, List? gates, @@ -119,6 +123,7 @@ class MaskedState extends BishopState { castlingRights: castlingRights ?? this.castlingRights, epSquare: epSquare ?? this.epSquare, royalSquares: royalSquares ?? this.royalSquares, + castlingSquares: castlingSquares ?? this.castlingSquares, virginFiles: virginFiles ?? this.virginFiles, hands: hands ?? this.hands, gates: gates ?? this.gates, diff --git a/lib/src/state/state.dart b/lib/src/state/state.dart index 6dc9c47..ae4ed90 100644 --- a/lib/src/state/state.dart +++ b/lib/src/state/state.dart @@ -38,6 +38,10 @@ class BishopState { /// Index 0 - white, index 1 - black. final List royalSquares; + /// The squares that the castling pieces currently reside on. + /// This will usually be equivalent to [royalSquares]. + final List castlingSquares; + /// A list of files that have been untouched for each player. /// For use with e.g. Seirawan chess, where pieces can only be gated on files /// that haven't had their pieces move yet. @@ -99,6 +103,7 @@ class BishopState { required this.castlingRights, this.epSquare, required this.royalSquares, + required this.castlingSquares, required this.virginFiles, this.hands, this.gates, @@ -118,6 +123,7 @@ class BishopState { CastlingRights? castlingRights, int? epSquare, List? royalSquares, + List? castlingSquares, List>? virginFiles, List? hands, List? gates, @@ -136,6 +142,7 @@ class BishopState { castlingRights: castlingRights ?? this.castlingRights, epSquare: epSquare ?? this.epSquare, royalSquares: royalSquares ?? this.royalSquares, + castlingSquares: castlingSquares ?? this.castlingSquares, virginFiles: virginFiles ?? this.virginFiles, hands: hands ?? this.hands, gates: gates ?? this.gates, diff --git a/lib/src/variant/built_variant.dart b/lib/src/variant/built_variant.dart index ebdd6f4..212ffd7 100644 --- a/lib/src/variant/built_variant.dart +++ b/lib/src/variant/built_variant.dart @@ -14,9 +14,10 @@ class BuiltVariant { final DropBuilderFunction? dropBuilder; final MoveChecker? passChecker; final PieceMoveChecker? firstMoveChecker; + final int royalPiece; final int epPiece; final int castlingPiece; - final int royalPiece; + final int rookPiece; final MaterialConditions materialConditions; final Map regions; final Map> winRegions; @@ -43,6 +44,7 @@ class BuiltVariant { this.firstMoveChecker, required this.epPiece, required this.castlingPiece, + required this.rookPiece, required this.royalPiece, required this.materialConditions, required this.regions, @@ -69,9 +71,10 @@ class BuiltVariant { DropBuilderFunction? dropBuilder, MoveChecker? passChecker, PieceMoveChecker? firstMoveChecker, + int? royalPiece, int? epPiece, int? castlingPiece, - int? royalPiece, + int? rookPiece, MaterialConditions? materialConditions, Map? regions, Map>? winRegions, @@ -96,9 +99,10 @@ class BuiltVariant { dropBuilder: dropBuilder ?? this.dropBuilder, passChecker: passChecker ?? this.passChecker, firstMoveChecker: firstMoveChecker ?? this.firstMoveChecker, + royalPiece: royalPiece ?? this.royalPiece, epPiece: epPiece ?? this.epPiece, castlingPiece: castlingPiece ?? this.castlingPiece, - royalPiece: royalPiece ?? this.royalPiece, + rookPiece: rookPiece ?? this.rookPiece, materialConditions: materialConditions ?? this.materialConditions, regions: regions ?? this.regions, winRegions: winRegions ?? this.winRegions, @@ -184,13 +188,16 @@ class BuiltVariant { promoLimits: data.promotionOptions.pieceLimits ?.map((k, v) => MapEntry(pieceIndexLookup[k]!, v)), promoMap: promoMap, + royalPiece: pieces.indexWhere((p) => p.type.royal), epPiece: data.enPassant ? pieces.indexWhere((p) => p.type.enPassantable) : Bishop.invalid, castlingPiece: data.castling + ? pieces.indexWhere((p) => p.type.castling) + : Bishop.invalid, + rookPiece: data.castling ? pieces.indexWhere((p) => p.symbol == data.castlingOptions.rookPiece) : Bishop.invalid, - royalPiece: pieces.indexWhere((p) => p.type.royal), materialConditions: data.materialConditions.convert(pieces), regions: data.regions.map((k, v) => MapEntry(k, v.build(data.boardSize))), winRegions: winRegions, diff --git a/lib/src/variant/variants/misc.dart b/lib/src/variant/variants/misc.dart index ecf70c0..f70cc37 100644 --- a/lib/src/variant/variants/misc.dart +++ b/lib/src/variant/variants/misc.dart @@ -21,6 +21,7 @@ class MiscVariants { }, ); + /// https://www.chessvariants.org/winning.dir/kinglet.html static Variant kinglet() => Variant.standard().copyWith( name: 'Kinglet Chess', description: @@ -31,15 +32,38 @@ class MiscVariants { 'B': PieceType.bishop().withNoPromotion(), 'R': PieceType.rook().withNoPromotion(), 'Q': PieceType.queen().withNoPromotion(), - 'K': PieceType.fromBetza('K'), + 'K': PieceType.fromBetza('K', castling: true), }, actions: [ActionCheckPieceCount(pieceType: 'P')], ); + /// https://www.chessvariants.org/winning.dir/extinction.html + static Variant extinction() => Variant.standard().copyWith( + name: 'Extinction Chess', + description: 'The first player that does not have pieces of all ' + 'types loses the game.', + pieceTypes: { + 'P': PieceType.pawn(), + 'N': PieceType.knight(), + 'B': PieceType.bishop(), + 'R': PieceType.rook(), + 'Q': PieceType.queen(), + 'K': PieceType.fromBetza('K', castling: true), + }, + actions: [ + ActionCheckPieceCount(pieceType: 'P'), + ActionCheckPieceCount(pieceType: 'N'), + ActionCheckPieceCount(pieceType: 'B'), + ActionCheckPieceCount(pieceType: 'R'), + ActionCheckPieceCount(pieceType: 'Q'), + ActionCheckPieceCount(pieceType: 'K'), + ], + ); + static Variant threeKings() => Variant.standard().copyWith( name: 'Three Kings Chess', - description: - 'Each player has three kings, but only one has to be captured for them to win.', + description: 'Each player has three kings, but only one has to ' + 'be captured for them to win.', startPosition: 'knbqkbnk/pppppppp/8/8/8/8/PPPPPPPP/KNBQKBNK w - - 0 1', castlingOptions: CastlingOptions.none, actions: [ActionCheckPieceCount(pieceType: 'K', count: 3)], diff --git a/test/divide.dart b/test/divide.dart index dde35c3..5d660a1 100644 --- a/test/divide.dart +++ b/test/divide.dart @@ -34,7 +34,7 @@ void main(List args) async { if (value != stockfish[key]) { int diff = stockfish[key] - value; print( - 'Error on $key - $value (us) vs ${stockfish[key]} (stockfish) [${(diff > 0) ? "+$diff" : diff}]', + 'Error on $key - $value (bishop) vs ${stockfish[key]} (stockfish) [${(diff > 0) ? "+$diff" : diff}]', ); } else if (showCorrect) { print('$key correct ($value)'); diff --git a/test/misc_test.dart b/test/misc_test.dart index 21bffce..3428ac0 100644 --- a/test/misc_test.dart +++ b/test/misc_test.dart @@ -93,4 +93,13 @@ void main() { ), ); }); + test('Kinglet Castling & Win Condition', () { + final g = Game( + variant: MiscVariants.kinglet(), + fen: 'rnbqkbnr/p7/1P6/8/8/8/P7/RNBQK2R w KQkq - 0 1', + ); + g.makeMultipleMoves(['e1g1', 'h8h1', 'b1a3', 'h1g1', 'b6a7']); + expect(g.result, isA()); + expect(g.winner, Bishop.white); + }); }