Merge pull request #52 from lichess-org/explosionSquares

Explosion squares
This commit is contained in:
Vincent Velociter
2026-03-03 13:10:08 +01:00
committed by GitHub
4 changed files with 44 additions and 6 deletions
-5
View File
@@ -1,8 +1,3 @@
/// Dart chess library for native platforms.
///
/// All classes are immutable except [PgnNode] and [PgnChildNode].
library dartchess;
export 'src/constants.dart';
export 'src/models.dart';
export 'src/square_set.dart';
+1 -1
View File
@@ -185,7 +185,7 @@ extension type const Rank._(int value) implements int {
///
/// The square is represented with an integer ranging from 0 to 63, using a
/// little-endian rank-file mapping (LERF):
/// ```
/// ```txt
/// 8 | 56 57 58 59 60 61 62 63
/// 7 | 48 49 50 51 52 53 54 55
/// 6 | 40 41 42 43 44 45 46 47
+18
View File
@@ -1318,6 +1318,24 @@ abstract class Atomic extends Position {
}
}
/// Returns the set of squares that explode when [move] is played.
///
/// The explosion center ([move].to) and all surrounding non-pawn occupied
/// squares are included. Returns an empty set if the move is not a capture.
SquareSet explosionSquares(Move move) {
if (move is! NormalMove) return SquareSet.empty;
if (_getCastlingSide(move) != null) return SquareSet.empty;
final capturedPiece = board.pieceAt(move.to);
final isEnPassant =
move.to == epSquare && board.pieceAt(move.from)?.role == Role.pawn;
if (capturedPiece == null && !isEnPassant) return SquareSet.empty;
final surrounding = kingAttacks(move.to)
.intersect(board.occupied)
.diff(board.pawns)
.withoutSquare(move.from);
return surrounding.withSquare(move.to);
}
/// Plays a move without checking if the move is legal.
///
/// In addition to standard rules, all captures cause an explosion by which
+25
View File
@@ -773,6 +773,31 @@ void main() {
expect(pos.hasInsufficientMaterial(Side.black), test[2]);
}
});
test('explosionSquares', () {
// Giuoco Piano: Bxf7+ explodes f7 (center), f6 (knight), e8 (king), f8 (bishop)
// g7 is a pawn and is not included
final pos = Atomic.fromSetup(Setup.parseFen(
'r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 4 4'));
expect(
pos.explosionSquares(NormalMove.fromUci('c4f7')),
SquareSet.fromSquares([Square.f7, Square.f6, Square.e8, Square.f8]),
);
// Non-capture move returns empty set
expect(
pos.explosionSquares(NormalMove.fromUci('d2d3')),
SquareSet.empty,
);
// En passant: explosion centered on the destination square
final epPos = Atomic.fromSetup(Setup.parseFen(
'rnbqkbnr/ppp1p1pp/8/3pPp2/8/8/PPPP1PPP/RNBQKBNR w KQkq f6 0 3'));
expect(
epPos.explosionSquares(NormalMove.fromUci('e5f6')),
const SquareSet.fromSquare(Square.f6),
);
});
});
group('Antichess', () {