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

Crazyhouse en passant bug with DropMove #2

Merged
merged 1 commit into from
Apr 17, 2023
Merged
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
6 changes: 5 additions & 1 deletion lib/src/debug.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import './models.dart';
import './position.dart';
import './square_set.dart';
import './utils.dart';
import './setup.dart';

/// Takes a string and returns a SquareSet. Useful for debugging/testing purposes.
///
Expand Down Expand Up @@ -55,8 +56,11 @@ String humanReadableSquareSet(SquareSet sq) {
}

/// Prints the board as a human readable string format
String humanReadableBoard(Board board) {
String humanReadableBoard(Board board, [Pockets? pockets]) {
final buffer = StringBuffer();
if (pockets != null) {
buffer.write('Pockets: $pockets\n');
}
for (int y = 7; y >= 0; y--) {
for (int x = 0; x < 8; x++) {
final square = x + y * 8;
Expand Down
13 changes: 12 additions & 1 deletion lib/src/position.dart
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,17 @@ abstract class Position<T extends Position<T>> {
}
}

/// Plays a move from a Standard Algebraic Notation string.
///
/// Throws a [PlayError] if the move is not legal.
Position<T> playSan(String san) {
final move = parseSan(san);
if (move == null) {
throw PlayError('Invalid SAN $san');
}
return play(move);
}

/// Plays a move without checking if the move is legal.
Position<T> playUnchecked(Move move) {
assert(move is NormalMove || move is DropMove);
Expand Down Expand Up @@ -1526,7 +1537,7 @@ class Crazyhouse extends Position<Crazyhouse> {
pockets: pockets != null ? pockets.value : this.pockets,
turn: turn ?? this.turn,
castles: castles ?? this.castles,
epSquare: epSquare != null ? epSquare.value : this.epSquare,
epSquare: epSquare?.value,
halfmoves: halfmoves ?? this.halfmoves,
fullmoves: fullmoves ?? this.fullmoves,
);
Expand Down
5 changes: 5 additions & 0 deletions lib/src/setup.dart
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,11 @@ class Pockets {

@override
int get hashCode => value.hashCode;

@override
String toString() {
return _makePockets(this);
}
}

Pockets _parsePockets(String pocketPart) {
Expand Down
48 changes: 48 additions & 0 deletions test/crazyhouse_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import 'package:dartchess/dartchess.dart';
import 'package:test/test.dart';
import 'db_testing_lib.dart';

void main() {
test('Crazyhouse - issue #23: Crazyhouse en passant bug with DropMove', () {
Position a = Crazyhouse.initial;
a = a.playSan('d4');
a = a.playSan('e5');
a = a.playSan('Nf3');
a = a.playSan('Qg5');
a = a.playSan('Nxg5');
a = a.playSan('Be7');
a = a.playSan('dxe5');
a = a.playSan('Bd8');

a = a.playSan('Nc3');
printBoard(a, printLegalMoves: true);

a = a.playSan('f5'); // creates epSquare at f6 for White
printBoard(a, printLegalMoves: true);

a = a.playSan(
'Q@f7'); // Bug: copyWith() is given null for epSquare, which then copies White's epSquare of f6 forward to Black
final List<Move> legalMoves = printBoard(a, printLegalMoves: true);
const MyExpectations myExpectations = MyExpectations(
legalMoves: 0,
legalDrops: 0,
legalDropZone: DropZone.anywhere,
rolesThatCanDrop: [],
rolesThatCantDrop: [
Role.king,
Role.queen,
Role.rook,
Role.bishop,
Role.knight,
Role.pawn,
],
);

expect(myExpectations.testLegalMoves(legalMoves), '');

expect(a.outcome, Outcome.whiteWins);

// a = a.playSan('gxf6'); // captures the Queen at f7!
// printBoard(a, printLegalMoves: true);
});
}
164 changes: 164 additions & 0 deletions test/db_testing_lib.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import 'package:dartchess/dartchess.dart';

const verbosePrinting = false;
bool printedNotice = false;

void conditionalPrint(Object? a) {
if (!printedNotice) {
printedNotice = true;
print('=' * 60);
print('${'=\tVERBOSE PRINTING'.padRight(53)}=');
if (verbosePrinting) {
print('${'=\tverbosePrinting is ON'.padRight(53)}=');
print('${'=\t'.padRight(53)}=');
print('${'=\tTo disable, set the "verbosePrinting"'.padRight(53)}=');
print('${'=\tconstant to false'.padRight(53)}=');
} else {
print('${'=\tverbosePrinting is OFF'.padRight(53)}=');
print('${'=\t'.padRight(53)}=');
print(
'${'=\tSet the "verbosePrinting" constant to true to help'.padRight(53)}=');
print('${'=\twith debugging these tests'.padRight(53)}=');
}
print('${'=\t(line 3 of \\test\\db_testing_lib.dart)'.padRight(53)}=');
print('=' * 60);
}
if (verbosePrinting) {
print(a);
}
}

enum DropZone { none, whiteHomeRow, blackHomeRow, anywhere }

const noDrops = <int>[];
const whiteHomeRow = <int>[
0,
1,
2,
3,
4,
5,
6,
7,
];
const blackHomeRow = <int>[
56,
57,
58,
59,
60,
61,
62,
63,
];

class MyExpectations {
final int legalDrops;
final int legalMoves;
final DropZone legalDropZone;
final List<Role> rolesThatCanDrop;
final List<Role> rolesThatCantDrop;

const MyExpectations(
{required this.legalMoves,
required this.legalDrops,
required this.legalDropZone,
required this.rolesThatCanDrop,
required this.rolesThatCantDrop});

String testLegalMoves(List<Move> a) {
if (a.whereType<NormalMove>().length != legalMoves) {
return 'Expected $legalMoves legal moves, got ${a.whereType<NormalMove>().length}';
}
if (a.whereType<DropMove>().length != legalDrops) {
return 'Expected $legalDrops legal drops, got ${a.whereType<DropMove>().length}';
}
for (final move in a) {
if (move is DropMove) {
if (rolesThatCantDrop.contains(move.role)) {
return '${move.role} is listed in rolesThatCantDrop';
}
if (!rolesThatCanDrop.contains(move.role)) {
return '${move.role} is not listed in rolesThatCanDrop';
}
if (legalDropZone == DropZone.anywhere &&
move.role == Role.pawn &&
(whiteHomeRow.contains(move.to) ||
blackHomeRow.contains(move.to))) {
return 'Drop zone is anywhere, but a pawn cannot be dropped in rows 1 or 8';
} else if (legalDropZone == DropZone.whiteHomeRow &&
!whiteHomeRow.contains(move.to)) {
return 'Drop zone is whiteHomeRow, but ${move.to} is not in whiteHomeRow';
} else if (legalDropZone == DropZone.blackHomeRow &&
!blackHomeRow.contains(move.to)) {
return 'Drop zone is blackHomeRow, but ${move.to} is not in whiteHomeRow';
} else if (legalDropZone == DropZone.none &&
!noDrops.contains(move.to)) {
return 'Drop zone is none, but ${move.to} is not in noDrops';
}
}
}
return '';
}
}

List<DropMove> dropTestEachSquare(Position position) {
final legalDrops = <DropMove>[];
final allRoles = <Role>[
Role.pawn,
Role.knight,
Role.bishop,
Role.rook,
Role.queen,
Role.king
];
for (final pieceRole in allRoles) {
for (int a = 0; a < 64; a++) {
if (position.isLegal(DropMove(role: pieceRole, to: a))) {
legalDrops.add(DropMove(role: pieceRole, to: a));
}
}
}
return legalDrops;
}

List<NormalMove> moveTestEachSquare(Position position) {
final legalMoves = <NormalMove>[];
for (int a = 0; a < 64; a++) {
for (int b = 0; b < 64; b++) {
if (position.isLegal(NormalMove(from: a, to: b))) {
legalMoves.add(NormalMove(from: a, to: b));
}
}
}
return legalMoves;
}

List<Move> printBoard(Position a, {bool printLegalMoves = false}) {
final z = StringBuffer();
final y = StringBuffer();
String x = '';
int moves = 0;
int drops = 0;

final legalMoves = <Move>[];
if (printLegalMoves) {
for (final move in moveTestEachSquare(a)) {
z.write('${move.uci}, ');
legalMoves.add(move);
moves++;
}
x = 'Legal Moves ($moves):\n$z\n';
for (final drop in dropTestEachSquare(a)) {
y.write('${drop.uci}, ');
legalMoves.add(drop);
drops++;
}
x += 'Legal Drops ($drops):\n$y';
}

conditionalPrint('${humanReadableBoard(a.board, a.pockets)}$x');
conditionalPrint(
'------------------------------------------------------------');
return legalMoves;
}