mirror of
https://github.com/lichess-org/dartchess.git
synced 2026-05-26 13:51:01 +00:00
Remove fast immutable collections
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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
@@ -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
@@ -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(
|
||||
|
||||
@@ -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
@@ -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}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user