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

Impossible en passant capture that blocks a check #19

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
31 changes: 27 additions & 4 deletions lib/src/position.dart
Original file line number Diff line number Diff line change
Expand Up @@ -844,9 +844,20 @@ abstract class Position<T extends Position<T>> {
}
if (epSquare != null && _canCaptureEp(square)) {
final pawn = epSquare! - delta;

if (ctx.checkers.isEmpty || ctx.checkers.singleSquare == pawn) {
legalEpSquare = SquareSet.fromSquare(epSquare!);
}

// en passant capture is valid when blocking a sliding attack on king
final sniper = ctx.snipers.singleSquare;
if (ctx.checkers.isNotEmpty && sniper != null) {
final b = between(ctx.king!, sniper);
final epSquareSet = SquareSet.fromSquare(epSquare!);
if ((b & board.occupied).isEmpty && b.isIntersected(epSquareSet)) {
legalEpSquare = epSquareSet;
}
}
}
} else if (piece.role == Role.bishop) {
pseudo = bishopAttacks(square, board.occupied);
Expand Down Expand Up @@ -899,24 +910,30 @@ abstract class Position<T extends Position<T>> {
isVariantEnd: isVariantEnd,
mustCapture: false,
king: king,
snipers: SquareSet.empty,
blockers: SquareSet.empty,
checkers: SquareSet.empty);
}
final snipers = _snipers(king);
return _Context(
isVariantEnd: isVariantEnd,
mustCapture: false,
king: king,
blockers: _sliderBlockers(king),
snipers: snipers,
blockers: _sliderBlockers(king, snipers),
checkers: checkers,
);
}

SquareSet _sliderBlockers(Square king) {
final snipers = rookAttacks(king, SquareSet.empty)
SquareSet _snipers(Square king) {
return rookAttacks(king, SquareSet.empty)
.intersect(board.rooksAndQueens)
.union(bishopAttacks(king, SquareSet.empty)
.intersect(board.bishopsAndQueens))
.intersect(board.bySide(turn.opposite));
}

SquareSet _sliderBlockers(Square king, SquareSet snipers) {
SquareSet blockers = SquareSet.empty;
for (final sniper in snipers.squares) {
final b = between(king, sniper) & board.occupied;
Expand Down Expand Up @@ -1754,11 +1771,13 @@ class RacingKings extends Position<RacingKings> {
return blackKing != null &&
kingAttacks(blackKing).intersect(goal).squares.where((square) {
// Check whether this king move is legal
final snipers = _snipers(blackKing);
final context = _Context(
isVariantEnd: false,
mustCapture: false,
king: blackKing,
blockers: _sliderBlockers(blackKing),
snipers: snipers,
blockers: _sliderBlockers(blackKing, snipers),
checkers: checkers,
);
final legalMoves = _legalMovesOf(blackKing, context: context);
Expand Down Expand Up @@ -2537,6 +2556,7 @@ class _Context {
const _Context({
required this.isVariantEnd,
required this.king,
required this.snipers,
required this.blockers,
required this.checkers,
required this.mustCapture,
Expand All @@ -2545,20 +2565,23 @@ class _Context {
final bool isVariantEnd;
final bool mustCapture;
final Square? king;
final SquareSet snipers;
final SquareSet blockers;
final SquareSet checkers;

_Context copyWith({
bool? isVariantEnd,
bool? mustCapture,
Square? king,
SquareSet? snipers,
SquareSet? blockers,
SquareSet? checkers,
}) {
return _Context(
isVariantEnd: isVariantEnd ?? this.isVariantEnd,
mustCapture: mustCapture ?? this.mustCapture,
king: king,
snipers: snipers ?? this.snipers,
blockers: blockers ?? this.blockers,
checkers: checkers ?? this.checkers,
);
Expand Down
71 changes: 36 additions & 35 deletions test/perft_test.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@Tags(['perft'])
import 'package:test/test.dart';
import 'dart:io';
import 'package:dartchess/dartchess.dart';
Expand Down Expand Up @@ -103,41 +104,6 @@ void main() async {
}
});

// group('Chess Tricky', () {
// final tests =
// Parser().parse(File('test/resources/tricky.perft').readAsStringSync());
// for (final perftTest in tests) {
// for (final testCase in perftTest.cases
// .takeWhile((testCase) => testCase.nodes < nodeLimit)) {
// test('${perftTest.id} ${perftTest.fen} ${testCase.depth}', () {
// final position = Chess.fromSetup(Setup.parseFen(perftTest.fen),
// ignoreImpossibleCheck:
// true); // true: otherwise there is an Impossible Check Error
// expect(perft(position, testCase.depth), testCase.nodes,
// reason:
// 'id: ${perftTest.id}\nfen: ${perftTest.fen} \ndepth: ${testCase.depth} \nnodes: ${testCase.nodes}');
// });
// }
// }
// });

group('Random', () {
final tests =
Parser().parse(File('test/resources/random.perft').readAsStringSync());
// only test 10 position in random. Test file has around 6000 positions
for (final perftTest in tests.take(50)) {
final position = Chess.fromSetup(Setup.parseFen(perftTest.fen));
for (final testCase in perftTest.cases
.takeWhile((testCase) => testCase.nodes < nodeLimit)) {
test('${perftTest.id} ${perftTest.fen} ${testCase.depth}', () {
expect(perft(position, testCase.depth), testCase.nodes,
reason:
'id: ${perftTest.id}\nfen: ${perftTest.fen} \ndepth: ${testCase.depth} \nnodes: ${testCase.nodes}');
});
}
}
});

group('Standard', () {
test('initial position', () {
const pos = Chess.initial;
Expand All @@ -147,6 +113,41 @@ void main() async {
expect(perft(pos, 3), 8902);
expect(perft(pos, 4), 197281);
});

group('random', () {
final tests = Parser()
.parse(File('test/resources/random.perft').readAsStringSync());
// only test 10 position in random. Test file has around 6000 positions
for (final perftTest in tests.take(50)) {
final position = Chess.fromSetup(Setup.parseFen(perftTest.fen));
for (final testCase in perftTest.cases
.takeWhile((testCase) => testCase.nodes < nodeLimit)) {
test('${perftTest.id} ${perftTest.fen} ${testCase.depth}', () {
expect(perft(position, testCase.depth), testCase.nodes,
reason:
'id: ${perftTest.id}\nfen: ${perftTest.fen} \ndepth: ${testCase.depth} \nnodes: ${testCase.nodes}');
});
}
}
});

group('tricky', () {
final tests = Parser()
.parse(File('test/resources/tricky.perft').readAsStringSync());
for (final perftTest in tests) {
for (final testCase in perftTest.cases
.takeWhile((testCase) => testCase.nodes < nodeLimit)) {
test('${perftTest.id} ${perftTest.fen} ${testCase.depth}', () {
final position = Chess.fromSetup(Setup.parseFen(perftTest.fen),
ignoreImpossibleCheck:
true); // true: otherwise there is an Impossible Check Error
expect(perft(position, testCase.depth), testCase.nodes,
reason:
'id: ${perftTest.id}\nfen: ${perftTest.fen} \ndepth: ${testCase.depth} \nnodes: ${testCase.nodes}');
});
}
}
});
});

group('Chess 960', () {
Expand Down