Skip to content

Commit a9d42cb

Browse files
committed
Document legalMoves castling format
Re-introduce `makeLegalMoves` to export legal moves in a convenient format. Closes #33
1 parent 05af180 commit a9d42cb

File tree

4 files changed

+115
-1
lines changed

4 files changed

+115
-1
lines changed

lib/dartchess.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ export 'src/setup.dart';
1313
export 'src/position.dart';
1414
export 'src/debug.dart';
1515
export 'src/pgn.dart';
16+
export 'src/utils.dart';

lib/src/position.dart

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'models.dart';
77
import 'board.dart';
88
import 'setup.dart';
99
import 'square_set.dart';
10+
import 'utils.dart';
1011

1112
/// A base class for playable chess or chess variant positions.
1213
///
@@ -181,6 +182,14 @@ abstract class Position<T extends Position<T>> {
181182
}
182183

183184
/// Gets all the legal moves of this position.
185+
///
186+
/// Returns a [SquareSet] of all the legal moves for each [Square].
187+
///
188+
/// In order to support Chess960, the castling move format is encoded as the
189+
/// king-to-rook move only.
190+
///
191+
/// Use the [makeLegalMoves] helper to get all the legal moves including alternative
192+
/// castling moves.
184193
IMap<Square, SquareSet> get legalMoves {
185194
final context = _makeContext();
186195
if (context.isVariantEnd) return IMap(const {});
@@ -193,7 +202,7 @@ abstract class Position<T extends Position<T>> {
193202
/// Gets all the legal drops of this position.
194203
SquareSet get legalDrops => SquareSet.empty;
195204

196-
/// SquareSet of pieces giving check.
205+
/// Square set of pieces giving check.
197206
SquareSet get checkers {
198207
final king = board.kingOf(turn);
199208
return king != null ? kingAttackers(king, turn.opposite) : SquareSet.empty;

lib/src/utils.dart

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
2+
3+
import 'models.dart';
4+
import 'position.dart';
5+
6+
/// Returns all the legal moves of the [Position] in a convenient format.
7+
///
8+
/// Includes both possible representations of castling moves (unless `chess960` is true).
9+
IMap<Square, ISet<Square>> makeLegalMoves(
10+
Position pos, {
11+
bool isChess960 = false,
12+
}) {
13+
final Map<Square, ISet<Square>> result = {};
14+
for (final entry in pos.legalMoves.entries) {
15+
final dests = entry.value.squares;
16+
if (dests.isNotEmpty) {
17+
final from = entry.key;
18+
final destSet = dests.toSet();
19+
if (!isChess960 &&
20+
from == pos.board.kingOf(pos.turn) &&
21+
entry.key.file == 4) {
22+
if (dests.contains(Square.a1)) {
23+
destSet.add(Square.c1);
24+
} else if (dests.contains(Square.a8)) {
25+
destSet.add(Square.c8);
26+
}
27+
if (dests.contains(Square.h1)) {
28+
destSet.add(Square.g1);
29+
} else if (dests.contains(Square.h8)) {
30+
destSet.add(Square.g8);
31+
}
32+
}
33+
result[from] = ISet(destSet);
34+
}
35+
}
36+
return IMap(result);
37+
}

utils_test.dart

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import 'package:dartchess/dartchess.dart';
2+
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
3+
import 'package:test/test.dart';
4+
5+
void main() {
6+
test('makeLegalMoves with Kh8', () {
7+
final setup = Setup.parseFen(
8+
'r1bq1r2/3n2k1/p1p1pp2/3pP2P/8/PPNB2Q1/2P2P2/R3K3 b Q - 1 22',
9+
);
10+
final pos = Chess.fromSetup(setup);
11+
final moves = makeLegalMoves(pos);
12+
expect(moves[Square.g7], contains(Square.h8));
13+
expect(moves[Square.g7], isNot(contains(Square.g8)));
14+
});
15+
16+
test('makeLegalMoves with regular castle', () {
17+
final wtm =
18+
Chess.fromSetup(Setup.parseFen('r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1'));
19+
expect(
20+
makeLegalMoves(wtm)[Square.e1],
21+
equals(
22+
{
23+
Square.a1,
24+
Square.c1,
25+
Square.d1,
26+
Square.d2,
27+
Square.e2,
28+
Square.f1,
29+
Square.f2,
30+
Square.g1,
31+
Square.h1,
32+
},
33+
),
34+
);
35+
expect(makeLegalMoves(wtm)[Square.e8], null);
36+
37+
final btm =
38+
Chess.fromSetup(Setup.parseFen('r3k2r/8/8/8/8/8/8/R3K2R b KQkq - 0 1'));
39+
expect(
40+
makeLegalMoves(btm)[Square.e8],
41+
equals({
42+
Square.a8,
43+
Square.c8,
44+
Square.d7,
45+
Square.d8,
46+
Square.e7,
47+
Square.f7,
48+
Square.f8,
49+
Square.g8,
50+
Square.h8,
51+
}),
52+
);
53+
expect(makeLegalMoves(btm)[Square.e1], null);
54+
});
55+
56+
test('makeLegalMoves with chess960 castle', () {
57+
final pos = Chess.fromSetup(
58+
Setup.parseFen(
59+
'rk2r3/pppbnppp/3p2n1/P2Pp3/4P2q/R5NP/1PP2PP1/1KNQRB2 b Kkq - 0 1',
60+
),
61+
);
62+
expect(
63+
makeLegalMoves(pos, isChess960: true)[Square.b8],
64+
equals(ISet(const {Square.a8, Square.c8, Square.e8})),
65+
);
66+
});
67+
}

0 commit comments

Comments
 (0)