Improve Position types

This commit is contained in:
Vincent Velociter
2025-02-10 14:50:05 +01:00
parent fdefa3274d
commit ab307f5bfa
6 changed files with 21 additions and 28 deletions
+2 -2
View File
@@ -16,7 +16,7 @@ void main() {
benchmark('parse san moves', () {
const moves =
'e4 Nc6 Bc4 e6 a3 g6 Nf3 Bg7 c3 Nge7 d3 O-O Be3 Na5 Ba2 b6 Qd2 Bb7 Bh6 d5 e5 d4 Bxg7 Kxg7 Qf4 Bxf3 Qxf3 dxc3 Nxc3 Nac6 Qf6+ Kg8 Rd1 Nd4 O-O c5 Ne4 Nef5 Rd2 Qxf6 Nxf6+ Kg7 Re1 h5 h3 Rad8 b4 Nh4 Re3 Nhf5 Re1 a5 bxc5 bxc5 Bc4 Ra8 Rb1 Nh4 Rdb2 Nc6 Rb7 Nxe5 Bxe6 Kxf6 Bd5 Nf5 R7b6+ Kg7 Bxa8 Rxa8 R6b3 Nd4 Rb7 Nxd3 Rd1 Ne2+ Kh2 Ndf4 Rdd7 Rf8 Ra7 c4 Rxa5 c3 Rc5 Ne6 Rc4 Ra8 a4 Rb8 a5 Rb2 a6 c2';
Position pos = Chess.initial;
Chess pos = Chess.initial;
for (final san in moves.split(' ')) {
pos = pos.play(pos.parseSan(san)!);
}
@@ -25,7 +25,7 @@ void main() {
benchmark('parse san moves, play unchecked', () {
const moves =
'e4 Nc6 Bc4 e6 a3 g6 Nf3 Bg7 c3 Nge7 d3 O-O Be3 Na5 Ba2 b6 Qd2 Bb7 Bh6 d5 e5 d4 Bxg7 Kxg7 Qf4 Bxf3 Qxf3 dxc3 Nxc3 Nac6 Qf6+ Kg8 Rd1 Nd4 O-O c5 Ne4 Nef5 Rd2 Qxf6 Nxf6+ Kg7 Re1 h5 h3 Rad8 b4 Nh4 Re3 Nhf5 Re1 a5 bxc5 bxc5 Bc4 Ra8 Rb1 Nh4 Rdb2 Nc6 Rb7 Nxe5 Bxe6 Kxf6 Bd5 Nf5 R7b6+ Kg7 Bxa8 Rxa8 R6b3 Nd4 Rb7 Nxd3 Rd1 Ne2+ Kh2 Ndf4 Rdd7 Rf8 Ra7 c4 Rxa5 c3 Rc5 Ne6 Rc4 Ra8 a4 Rb8 a5 Rb2 a6 c2';
Position pos = Chess.initial;
Chess pos = Chess.initial;
for (final san in moves.split(' ')) {
pos = pos.playUnchecked(pos.parseSan(san)!);
}
+1 -1
View File
@@ -74,7 +74,7 @@ final _promotionRoles = [Role.queen, Role.rook, Role.knight, Role.bishop];
///
/// Computing perft numbers is useful for comparing, testing and debugging move
/// generation correctness and performance.
int perft(Position pos, int depth, {bool shouldLog = false}) {
int perft<T extends Position<T>>(T pos, int depth, {bool shouldLog = false}) {
if (depth < 1) return 1;
final promotionRoles =
+3 -3
View File
@@ -144,17 +144,17 @@ class PgnGame<T extends PgnNodeData> {
/// Headers can include an optional 'Variant' and 'Fen' key.
///
/// Throws a [PositionSetupException] if it does not meet basic validity requirements.
static Position startingPosition(PgnHeaders headers,
static T startingPosition<T extends Position<T>>(PgnHeaders headers,
{bool? ignoreImpossibleCheck}) {
final rule = Rule.fromPgn(headers['Variant']);
if (rule == null) throw PositionSetupException.variant;
if (!headers.containsKey('FEN')) {
return Position.initialPosition(rule);
return Position.initialPosition(rule) as T;
}
final fen = headers['FEN']!;
try {
return Position.setupPosition(rule, Setup.parseFen(fen),
ignoreImpossibleCheck: ignoreImpossibleCheck);
ignoreImpossibleCheck: ignoreImpossibleCheck) as T;
} catch (err) {
rethrow;
}
+7 -14
View File
@@ -49,7 +49,7 @@ abstract class Position<T extends Position<T>> {
Rule get rule;
/// Creates a copy of this position with some fields changed.
Position<T> copyWith({
T copyWith({
Board? board,
Pockets? pockets,
Side? turn,
@@ -509,7 +509,7 @@ abstract class Position<T extends Position<T>> {
/// Plays a move and returns the updated [Position].
///
/// Throws a [PlayException] if the move is not legal.
Position<T> play(Move move) {
T play(Move move) {
if (isLegal(move)) {
return playUnchecked(move);
} else {
@@ -518,7 +518,7 @@ abstract class Position<T extends Position<T>> {
}
/// Plays a move without checking if the move is legal and returns the updated [Position].
Position<T> playUnchecked(Move move) {
T playUnchecked(Move move) {
switch (move) {
case NormalMove(from: final from, to: final to, promotion: final prom):
final piece = board.pieceAt(from);
@@ -600,7 +600,7 @@ abstract class Position<T extends Position<T>> {
}
/// Returns the SAN of this [Move] and the updated [Position], without checking if the move is legal.
(Position<T>, String) makeSanUnchecked(Move move) {
(T, String) makeSanUnchecked(Move move) {
final san = _makeSanWithoutSuffix(move);
final newPos = playUnchecked(move);
final suffixed = newPos.outcome?.winner != null
@@ -614,7 +614,7 @@ abstract class Position<T extends Position<T>> {
/// Returns the SAN of this [Move] and the updated [Position].
///
/// Throws a [PlayException] if the move is not legal.
(Position<T>, String) makeSan(Move move) {
(T, String) makeSan(Move move) {
if (isLegal(move)) {
return makeSanUnchecked(move);
} else {
@@ -1336,7 +1336,7 @@ abstract class Atomic extends Position<Atomic> {
final castlingSide = _getCastlingSide(move);
final capturedPiece = castlingSide == null ? board.pieceAt(move.to) : null;
final isCapture = capturedPiece != null || move.to == epSquare;
final newPos = super.playUnchecked(move) as Atomic;
final newPos = super.playUnchecked(move);
if (isCapture) {
Castles newCastles = newPos.castles;
@@ -1753,7 +1753,7 @@ abstract class ThreeCheck extends Position<ThreeCheck> {
@override
ThreeCheck playUnchecked(Move move) {
final newPos = super.playUnchecked(move) as ThreeCheck;
final newPos = super.playUnchecked(move);
if (newPos.isCheck) {
final (whiteChecks, blackChecks) = remainingChecks;
return newPos.copyWith(
@@ -1897,10 +1897,6 @@ abstract class RacingKings extends Position<RacingKings> {
@override
bool hasInsufficientMaterial(Side side) => false;
@override
RacingKings playUnchecked(Move move) =>
super.playUnchecked(move) as RacingKings;
@override
RacingKings copyWith({
Board? board,
@@ -2233,9 +2229,6 @@ abstract class Horde extends Position<Horde> {
@override
bool get isVariantEnd => board.white.isEmpty;
@override
Horde playUnchecked(Move move) => super.playUnchecked(move) as Horde;
@override
Horde copyWith({
Board? board,
+2 -2
View File
@@ -227,8 +227,8 @@ void main() {
test('transform pgn', () {
final game = PgnGame.parsePgn('1. a4 ( 1. b4 b5 -- ) 1... a5');
final PgnNode<PgnNodeWithFen> res =
game.moves.transform<PgnNodeWithFen, Position>(
PgnGame.startingPosition(game.headers),
game.moves.transform<PgnNodeWithFen, Chess>(
PgnGame.startingPosition<Chess>(game.headers),
(pos, data, _) {
final move = pos.parseSan(data.san);
if (move != null) {
+6 -6
View File
@@ -104,7 +104,7 @@ void main() {
});
test('parse pawn capture', () {
Position pos = Chess.initial;
Chess pos = Chess.initial;
const line = ['e4', 'd5', 'c4', 'Nf6', 'exd5'];
for (final san in line) {
pos = pos.play(pos.parseSan(san)!);
@@ -124,7 +124,7 @@ void main() {
test('parse fools mate', () {
const moves = ['e4', 'e5', 'Qh5', 'Nf6', 'Bc4', 'Nc6', 'Qxf7#'];
Position position = Chess.initial;
Chess position = Chess.initial;
for (final move in moves) {
position = position.play(position.parseSan(move)!);
}
@@ -807,7 +807,7 @@ void main() {
});
test('parse san', () {
Position position = Antichess.initial;
Antichess position = Antichess.initial;
final moves = [
'g3',
'Nh6',
@@ -892,7 +892,7 @@ void main() {
'Bxh1',
'bxa8=K'
];
Position position = Antichess.initial;
Antichess position = Antichess.initial;
for (final move in moves) {
position = position.play(position.parseSan(move)!);
}
@@ -915,7 +915,7 @@ void main() {
});
test('parse san', () {
Position position = Crazyhouse.initial;
Crazyhouse position = Crazyhouse.initial;
final moves = [
'd4',
'd5',
@@ -976,7 +976,7 @@ void main() {
});
test('castle checkmates', () {
Position position = Crazyhouse.initial;
Crazyhouse position = Crazyhouse.initial;
final moves = [
'd4',
'f5',