diff --git a/lib/src/position.dart b/lib/src/position.dart index d62473e..67ac885 100644 --- a/lib/src/position.dart +++ b/lib/src/position.dart @@ -844,9 +844,20 @@ abstract class Position> { } 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); @@ -899,24 +910,30 @@ abstract class Position> { 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; @@ -1754,11 +1771,13 @@ class RacingKings extends Position { 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); @@ -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, @@ -2545,6 +2565,7 @@ class _Context { final bool isVariantEnd; final bool mustCapture; final Square? king; + final SquareSet snipers; final SquareSet blockers; final SquareSet checkers; @@ -2552,6 +2573,7 @@ class _Context { bool? isVariantEnd, bool? mustCapture, Square? king, + SquareSet? snipers, SquareSet? blockers, SquareSet? checkers, }) { @@ -2559,6 +2581,7 @@ class _Context { isVariantEnd: isVariantEnd ?? this.isVariantEnd, mustCapture: mustCapture ?? this.mustCapture, king: king, + snipers: snipers ?? this.snipers, blockers: blockers ?? this.blockers, checkers: checkers ?? this.checkers, ); diff --git a/test/perft_test.dart b/test/perft_test.dart index ab4ae1e..6f23272 100644 --- a/test/perft_test.dart +++ b/test/perft_test.dart @@ -1,3 +1,4 @@ +@Tags(['perft']) import 'package:test/test.dart'; import 'dart:io'; import 'package:dartchess/dartchess.dart'; @@ -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; @@ -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', () {