Remove fast immutable collections

This commit is contained in:
Vincent Velociter
2026-05-17 14:16:19 +02:00
parent a8d833ed91
commit 8243acdb38
12 changed files with 78 additions and 84 deletions
+4 -4
View File
@@ -5,8 +5,8 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Commands
```bash
# Run all tests
dart test
# Run all tests (exclude slow full_perft tests)
dart test -x full_perft
# Run a single test file
dart test test/position_test.dart
@@ -51,7 +51,7 @@ This is a pure Dart chess rules library (`package:dartchess`) supporting standar
**`position.dart`** — the heart of the library. `Position` is an immutable abstract base class; each variant subclasses it: `Chess`, `Antichess`, `Atomic`, `Crazyhouse`, `KingOfTheHill`, `ThreeCheck`, `RacingKings`, `Horde`. Concrete private implementations (`_Chess`, etc.) are returned by `fromSetup`. Key API:
- `Position.setupPosition(rule, setup)` — variant-aware factory
- `pos.legalMoves``IMap<Square, SquareSet>` (king-to-rook encoding for castling)
- `pos.legalMoves``Map<Square, SquareSet>` (king-to-rook encoding for castling)
- `pos.play(move)` / `pos.playUnchecked(move)` — returns new position
- `pos.parseSan(san)` / `pos.makeSan(move)` — SAN I/O
- `pos.isCheckmate`, `pos.isStalemate`, `pos.isGameOver`, `pos.outcome`
@@ -65,7 +65,7 @@ This is a pure Dart chess rules library (`package:dartchess`) supporting standar
### Immutability
All `Position`, `Board`, `Setup`, `Castles` instances are immutable (`@immutable`). The library uses `fast_immutable_collections` (`IMap`, `ISet`) for collections stored in positions.
All `Position`, `Board`, `Setup`, `Castles` instances are immutable (`@immutable`). Standard Dart collections are used throughout; immutability is enforced by the library's internal discipline rather than FIC types.
### Chess960
+1 -2
View File
@@ -1,5 +1,4 @@
import 'package:meta/meta.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import './square_set.dart';
import './models.dart';
import './attacks.dart';
@@ -185,7 +184,7 @@ class Board {
}
/// Gets the number of pieces of each [Role] for the given [Side].
IMap<Role, int> materialCount(Side side) => IMap.fromEntries(
ByRole<int> materialCount(Side side) => Map.fromEntries(
Role.values.map((role) => MapEntry(role, piecesOf(side, role).size)));
/// A [SquareSet] of all the pieces matching this [Side] and [Role].
+12 -12
View File
@@ -105,30 +105,30 @@ abstract class Castles {
/// Gets rooks positions by side and castling side.
BySide<ByCastlingSide<Square?>> get rooksPositions {
return BySide({
Side.white: ByCastlingSide({
return {
Side.white: {
CastlingSide.queen: _whiteRookQueenSide,
CastlingSide.king: _whiteRookKingSide,
}),
Side.black: ByCastlingSide({
},
Side.black: {
CastlingSide.queen: _blackRookQueenSide,
CastlingSide.king: _blackRookKingSide,
}),
});
},
};
}
/// Gets rooks paths by side and castling side.
BySide<ByCastlingSide<SquareSet>> get paths {
return BySide({
Side.white: ByCastlingSide({
return {
Side.white: {
CastlingSide.queen: _whitePathQueenSide,
CastlingSide.king: _whitePathKingSide,
}),
Side.black: ByCastlingSide({
},
Side.black: {
CastlingSide.queen: _blackPathQueenSide,
CastlingSide.king: _blackPathKingSide,
}),
});
},
};
}
/// Gets the rook [Square] by side and castling side.
+3 -4
View File
@@ -1,5 +1,4 @@
import 'package:meta/meta.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import './square_set.dart';
/// The chessboard side, white or black.
@@ -337,9 +336,9 @@ extension type const Square._(int value) implements int {
static const h8 = Square(63);
}
typedef BySide<T> = IMap<Side, T>;
typedef ByRole<T> = IMap<Role, T>;
typedef ByCastlingSide<T> = IMap<CastlingSide, T>;
typedef BySide<T> = Map<Side, T>;
typedef ByRole<T> = Map<Role, T>;
typedef ByCastlingSide<T> = Map<CastlingSide, T>;
/// Describes a chess piece kind by its color and role.
enum PieceKind {
+12 -12
View File
@@ -1,5 +1,4 @@
import 'dart:math' as math;
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:meta/meta.dart';
import './setup.dart';
@@ -494,7 +493,7 @@ class PgnEvaluation {
class PgnComment {
const PgnComment(
{this.text,
this.shapes = const IListConst([]),
this.shapes = const [],
this.clock,
this.emt,
this.eval})
@@ -504,7 +503,7 @@ class PgnComment {
final String? text;
/// List of comment shapes.
final IList<PgnCommentShape> shapes;
final List<PgnCommentShape> shapes;
/// Player's remaining time.
final Duration? clock;
@@ -571,7 +570,7 @@ class PgnComment {
return PgnComment(
text: text.isNotEmpty ? text : null,
shapes: IList(shapes),
shapes: List.unmodifiable(shapes),
emt: emt,
clock: clock,
eval: eval);
@@ -601,17 +600,18 @@ class PgnComment {
@override
bool operator ==(Object other) {
return identical(this, other) ||
other is PgnComment &&
text == other.text &&
shapes == other.shapes &&
clock == other.clock &&
emt == other.emt &&
eval == other.eval;
if (identical(this, other)) return true;
if (other is! PgnComment) return false;
if (text != other.text || clock != other.clock || emt != other.emt || eval != other.eval) return false;
if (shapes.length != other.shapes.length) return false;
for (var i = 0; i < shapes.length; i++) {
if (shapes[i] != other.shapes[i]) return false;
}
return true;
}
@override
int get hashCode => Object.hash(text, shapes, clock, emt, eval);
int get hashCode => Object.hash(text, Object.hashAll(shapes), clock, emt, eval);
}
/// A frame used for parsing a line
+4 -5
View File
@@ -1,6 +1,5 @@
import 'package:meta/meta.dart';
import 'dart:math' as math;
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'attacks.dart';
import 'castles.dart';
import 'models.dart';
@@ -191,13 +190,13 @@ abstract class Position {
///
/// Use the [makeLegalMoves] helper to get all the legal moves including alternative
/// castling moves.
IMap<Square, SquareSet> get legalMoves {
Map<Square, SquareSet> get legalMoves {
final context = _makeContext();
if (context.isVariantEnd) return IMap(const {});
return IMap({
if (context.isVariantEnd) return const {};
return {
for (final s in board.bySide(turn).squares)
s: _legalMovesOf(s, context: context)
});
};
}
/// Gets all the legal drops of this position.
+18 -11
View File
@@ -1,5 +1,4 @@
import 'package:meta/meta.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'dart:math' as math;
import './square_set.dart';
import './models.dart';
@@ -255,23 +254,31 @@ class Pockets {
/// Increments the number of pieces in the pocket of that [Side] and [Role].
Pockets increment(Side side, Role role) {
final newPocket = value[side]!.add(role, of(side, role) + 1);
return Pockets(value: value.add(side, newPocket));
final newPocket = {...value[side]!, role: of(side, role) + 1};
return Pockets(value: {...value, side: newPocket});
}
/// Decrements the number of pieces in the pocket of that [Side] and [Role].
Pockets decrement(Side side, Role role) {
final newPocket = value[side]!.add(role, of(side, role) - 1);
return Pockets(value: value.add(side, newPocket));
final newPocket = {...value[side]!, role: of(side, role) - 1};
return Pockets(value: {...value, side: newPocket});
}
@override
bool operator ==(Object other) {
return identical(this, other) || other is Pockets && other.value == value;
if (identical(this, other)) return true;
if (other is! Pockets) return false;
for (final side in Side.values) {
for (final role in Role.values) {
if (of(side, role) != other.of(side, role)) return false;
}
}
return true;
}
@override
int get hashCode => value.hashCode;
int get hashCode => Object.hashAll(
Side.values.expand((s) => Role.values.map((r) => of(s, r))));
}
Pockets _parsePockets(String pocketPart) {
@@ -398,16 +405,16 @@ int _nthIndexOf(String haystack, String needle, int nth) {
return index;
}
const ByRole<int> _emptyPocket = IMapConst({
const ByRole<int> _emptyPocket = {
Role.pawn: 0,
Role.knight: 0,
Role.bishop: 0,
Role.rook: 0,
Role.queen: 0,
Role.king: 0,
});
};
const BySide<ByRole<int>> _emptyPocketsBySide = IMapConst({
const BySide<ByRole<int>> _emptyPocketsBySide = {
Side.white: _emptyPocket,
Side.black: _emptyPocket,
});
};
-1
View File
@@ -15,7 +15,6 @@ environment:
sdk: ">=3.3.0 <4.0.0"
dependencies:
fast_immutable_collections: ^11.0.0
meta: ^1.8.0
dev_dependencies:
+13 -19
View File
@@ -1,4 +1,3 @@
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:dartchess/dartchess.dart';
import 'package:test/test.dart';
@@ -40,34 +39,29 @@ void main() {
expect(Castles.standard.discardRookAt(Square.a4), Castles.standard);
expect(
Castles.standard.discardRookAt(Square.h1).rooksPositions[Side.white],
IMap(const {CastlingSide.queen: Square.a1, CastlingSide.king: null}));
const {CastlingSide.queen: Square.a1, CastlingSide.king: null});
});
test('discard side', () {
expect(
Castles.standard.discardSide(Side.white).rooksPositions,
equals(BySide({
Side.white: ByCastlingSide(
const {CastlingSide.queen: null, CastlingSide.king: null},
),
Side.black: ByCastlingSide(
const {
CastlingSide.queen: Square.a8,
CastlingSide.king: Square.h8,
},
)
})));
equals({
Side.white: const {CastlingSide.queen: null, CastlingSide.king: null},
Side.black: const {
CastlingSide.queen: Square.a8,
CastlingSide.king: Square.h8,
},
}));
expect(
Castles.standard.discardSide(Side.black).rooksPositions,
equals(BySide({
Side.white: ByCastlingSide(const {
equals({
Side.white: const {
CastlingSide.queen: Square.a1,
CastlingSide.king: Square.h1,
}),
Side.black: ByCastlingSide(
const {CastlingSide.queen: null, CastlingSide.king: null})
})));
},
Side.black: const {CastlingSide.queen: null, CastlingSide.king: null},
}));
});
});
}
+6 -7
View File
@@ -1,6 +1,5 @@
import 'package:dartchess/dartchess.dart' hide File;
import 'package:test/test.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'dart:io';
import 'pgn_fixtures.dart';
@@ -111,7 +110,7 @@ void main() {
'[%csl Ya1][%cal Ra1a1,Be1e2]commentary [%csl Gh8]'),
const PgnComment(
text: 'commentary',
shapes: IListConst([
shapes: [
PgnCommentShape(
color: CommentShapeColor.yellow,
from: Square.a1,
@@ -128,7 +127,7 @@ void main() {
color: CommentShapeColor.green,
from: Square.h8,
to: Square.h8)
])));
]));
expect(
PgnComment.fromPgn('prefix [%eval .99,23]'),
@@ -147,12 +146,12 @@ void main() {
PgnComment.fromPgn('[%csl Ga1]foo'),
const PgnComment(
text: 'foo',
shapes: IListConst([
shapes: [
PgnCommentShape(
color: CommentShapeColor.green,
from: Square.a1,
to: Square.a1)
])));
]));
expect(
PgnComment.fromPgn(
@@ -169,7 +168,7 @@ void main() {
Duration(hours: 1, minutes: 2, seconds: 3, milliseconds: 400),
eval: PgnEvaluation.pawns(pawns: 10),
clock: Duration(seconds: 1),
shapes: IListConst([
shapes: [
PgnCommentShape(
color: CommentShapeColor.yellow,
from: Square.a1,
@@ -182,7 +181,7 @@ void main() {
color: CommentShapeColor.red,
from: Square.a1,
to: Square.c1)
])).makeComment(),
]).makeComment(),
'text [%csl Ya1] [%cal Ra1b1,Ra1c1] [%eval 10.00] [%emt 1:02:03.4] [%clk 0:00:01]');
expect(
+4 -5
View File
@@ -1,4 +1,3 @@
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:dartchess/dartchess.dart';
import 'package:test/test.dart';
import 'dart:io' as io;
@@ -361,7 +360,7 @@ void main() {
});
test('standard position legal moves', () {
final moves = IMap({
final moves = {
Square.a1: SquareSet.empty,
Square.b1: const SquareSet.fromSquare(Square.a3).withSquare(Square.c3),
Square.c1: SquareSet.empty,
@@ -378,7 +377,7 @@ void main() {
Square.f2: const SquareSet.fromSquare(Square.f3).withSquare(Square.f4),
Square.g2: const SquareSet.fromSquare(Square.g3).withSquare(Square.g4),
Square.h2: const SquareSet.fromSquare(Square.h3).withSquare(Square.h4),
});
};
expect(Chess.initial.legalMoves, equals(moves));
});
@@ -665,10 +664,10 @@ void main() {
.play(const NormalMove(from: Square.h1, to: Square.f1));
expect(
pos.castles.rooksPositions[Side.white],
equals(IMap(const {
equals(const {
CastlingSide.queen: Square.a1,
CastlingSide.king: null
})));
}));
expect(pos.castles.castlingRights.has(Square.h1), false);
});
+1 -2
View File
@@ -1,5 +1,4 @@
import 'package:dartchess/dartchess.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:test/test.dart';
void main() {
@@ -61,7 +60,7 @@ void main() {
);
expect(
makeLegalMoves(pos, includeAlternateCastlingMoves: false)[Square.b8],
equals(ISet(const {Square.a8, Square.c8, Square.e8})),
equals(const {Square.a8, Square.c8, Square.e8}),
);
});
}