Skip to content

Commit

Permalink
enpassant in the editor
Browse files Browse the repository at this point in the history
  • Loading branch information
HaonRekcef committed Aug 30, 2024
1 parent 7dcd9d7 commit 92adc76
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 0 deletions.
53 changes: 53 additions & 0 deletions lib/src/model/board_editor/board_editor_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class BoardEditorController extends _$BoardEditorController {
pieces: readFen(initialFen ?? kInitialFEN).lock,
unmovedRooks: SquareSet.corners,
editorPointerMode: EditorPointerMode.drag,
enPassantSquare: null,
pieceToAddOnEdit: null,
);
}
Expand Down Expand Up @@ -65,6 +66,56 @@ class BoardEditorController extends _$BoardEditorController {
_updatePosition(readFen(fen).lock);
}

/// Calculates the squares where an en passant capture could be possible.
SquareSet calculateEnPassantOptions() {
final side = state.sideToPlay;
final pieces = state.pieces;
SquareSet enPassantSquares = SquareSet.empty;
final boardFen = writeFen(pieces.unlock);
final board = Board.parseFen(boardFen);

/// For en passant to be possible, there needs to be an adjacent pawn which has moved two squares forward.
/// So the two squares behind must be empty
void checkEnPassant(Square square, int fileOffset) {
final adjacentSquare =
Square.fromCoords(square.file.offset(fileOffset)!, square.rank);
final targetSquare = Square.fromCoords(
square.file.offset(fileOffset)!,
square.rank.offset(side == Side.white ? 1 : -1)!,
);
final originSquare = Square.fromCoords(
square.file.offset(fileOffset)!,
square.rank.offset(side == Side.white ? 2 : -2)!,
);

if (board.sideAt(adjacentSquare) == side.opposite &&
board.roleAt(adjacentSquare) == Role.pawn &&
board.sideAt(targetSquare) == null &&
board.sideAt(originSquare) == null) {
enPassantSquares =
enPassantSquares.union(SquareSet.fromSquare(targetSquare));
}
}

pieces.forEach((square, piece) {
if (piece.color == side && piece.role == Role.pawn) {
if ((side == Side.white && square.rank == Rank.fifth) ||
(side == Side.black && square.rank == Rank.fourth)) {
if (square.file != File.a) checkEnPassant(square, -1);
if (square.file != File.h) checkEnPassant(square, 1);
}
}
});

return enPassantSquares;
}

void toggleEnPassantSquare(Square square) {
state = state.copyWith(
enPassantSquare: state.enPassantSquare == square ? null : square,
);
}

void _updatePosition(IMap<Square, Piece> pieces) {
state = state.copyWith(pieces: pieces);
}
Expand Down Expand Up @@ -105,6 +156,7 @@ class BoardEditorState with _$BoardEditorState {
required IMap<Square, Piece> pieces,
required SquareSet unmovedRooks,
required EditorPointerMode editorPointerMode,
required Square? enPassantSquare,

/// When null, clears squares when in edit mode. Has no effect in drag mode.
required Piece? pieceToAddOnEdit,
Expand All @@ -129,6 +181,7 @@ class BoardEditorState with _$BoardEditorState {
board: board,
unmovedRooks: unmovedRooks,
turn: sideToPlay == Side.white ? Side.white : Side.black,
epSquare: enPassantSquare,
halfmoves: 0,
fullmoves: 1,
);
Expand Down
23 changes: 23 additions & 0 deletions lib/src/view/board_editor/board_editor_menu.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ class BoardEditorMenu extends ConsumerWidget {
final editorController = boardEditorControllerProvider(initialFen);
final editorState = ref.watch(editorController);

final enPassantSquares =
ref.read(editorController.notifier).calculateEnPassantOptions();

return SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 8.0),
Expand Down Expand Up @@ -80,6 +83,26 @@ class BoardEditorMenu extends ConsumerWidget {
),
);
}),
if (enPassantSquares.isNotEmpty) ...[
Padding(
padding: Styles.bodySectionPadding,
child: const Text('En passant', style: Styles.subtitle),
),
Wrap(
spacing: 8.0,
children: enPassantSquares.squares.map((square) {
return ChoiceChip(
label: Text(square.name),
selected: editorState.enPassantSquare == square,
onSelected: (selected) {
ref
.read(editorController.notifier)
.toggleEnPassantSquare(square);
},
);
}).toList(),
),
],
],
),
),
Expand Down
38 changes: 38 additions & 0 deletions test/view/board_editor/board_editor_screen_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,44 @@ void main() {
);
});

testWidgets('Possible en passant squares are calculated correctly',
(tester) async {
final app = await buildTestApp(
tester,
home: const BoardEditorScreen(),
);
await tester.pumpWidget(app);

final container = ProviderScope.containerOf(
tester.element(find.byType(ChessboardEditor)),
);
final controllerProvider = boardEditorControllerProvider(null);
container
.read(controllerProvider.notifier)
.loadFen('1nbqkbn1/pppppppp/8/8/8/8/PPPPPPPP/1NBQKBN1');

expect(
container.read(controllerProvider.notifier).calculateEnPassantOptions(),
SquareSet.empty,
);

container.read(controllerProvider.notifier).loadFen(
'r1bqkbnr/4p1p1/3n4/pPppPppP/8/8/P1PP1P2/RNBQKBNR w KQkq - 0 1',
);
expect(
container.read(controllerProvider.notifier).calculateEnPassantOptions(),
SquareSet.fromSquares([Square.a6, Square.c6, Square.f6]),
);
container.read(controllerProvider.notifier).loadFen(
'rnbqkbnr/pp1p1p1p/8/8/PpPpPQpP/8/NPRP1PP1/2B1KBNR b Kkq - 0 1',
);
container.read(controllerProvider.notifier).setSideToPlay(Side.black);
expect(
container.read(controllerProvider.notifier).calculateEnPassantOptions(),
SquareSet.fromSquares([Square.e3, Square.h3]),
);
});

testWidgets('Can drag pieces to new squares', (tester) async {
final app = await buildTestApp(
tester,
Expand Down

0 comments on commit 92adc76

Please sign in to comment.