More refactoring: remove utils

This commit is contained in:
Vincent Velociter
2024-08-01 10:52:16 +02:00
parent 485131f6c0
commit 7adc5f9889
9 changed files with 32 additions and 129 deletions
-4
View File
@@ -37,10 +37,6 @@ void main() {
legalMovesPos.legalMoves.length;
});
benchmark('algebraic legal moves', () {
legalMovesOf(legalMovesPos);
});
benchmark('parsePgn - kasparov-deep-blue', () {
final String data =
io.File('./data/kasparov-deep-blue-1997.pgn').readAsStringSync();
+2 -4
View File
@@ -6,10 +6,8 @@ void main() {
'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1');
final pos = Chess.fromSetup(setup);
// Generate legal moves in algebraic notation
final legalMoves = legalMovesOf(pos);
assert(legalMoves[Square.e2]!.length == 2);
// Generate legal moves
assert(pos.legalMoves.length == 16);
const move = NormalMove(from: Square.e2, to: Square.e4);
+12 -10
View File
@@ -1,6 +1,5 @@
import 'package:meta/meta.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import './utils.dart';
import './square_set.dart';
/// The chessboard side, white or black.
@@ -207,6 +206,9 @@ extension type const Square._(int value) implements int {
///
/// Throws a [FormatException] if the algebraic notation is invalid.
factory Square.fromName(String algebraic) {
if (algebraic.length != 2) {
throw FormatException('Invalid algebraic notation: $algebraic');
}
final file = algebraic.codeUnitAt(0) - 97;
final rank = algebraic.codeUnitAt(1) - 49;
if (file < 0 || file > 7 || rank < 0 || rank > 7) {
@@ -219,11 +221,10 @@ extension type const Square._(int value) implements int {
///
/// Returns either a [Square] or `null` if the algebraic notation is invalid.
static Square? parse(String algebraic) {
if (algebraic.length != 2) return null;
final file = algebraic.codeUnitAt(0) - 97;
final rank = algebraic.codeUnitAt(1) - 49;
if (file < 0 || file > 7 || rank < 0 || rank > 7) {
return null;
}
if (file < 0 || file > 7 || rank < 0 || rank > 7) return null;
return Square(rank * 8 + file);
}
@@ -319,6 +320,7 @@ extension type const Square._(int value) implements int {
static const g8 = Square(62);
static const h8 = Square(63);
/// All squares on the chessboard, from a1 to h8.
static const values = [
a1,
b1,
@@ -648,11 +650,11 @@ sealed class Move {
static Move? parse(String str) {
if (str[1] == '@' && str.length == 4) {
final role = Role.fromChar(str[0]);
final to = parseSquare(str.substring(2));
final to = Square.parse(str.substring(2));
if (role != null && to != null) return DropMove(to: to, role: role);
} else if (str.length == 4 || str.length == 5) {
final from = parseSquare(str.substring(0, 2));
final to = parseSquare(str.substring(2, 4));
final from = Square.parse(str.substring(0, 2));
final to = Square.parse(str.substring(2, 4));
Role? promotion;
if (str.length == 5) {
promotion = Role.fromChar(str[4]);
@@ -692,8 +694,8 @@ class NormalMove extends Move {
///
/// Throws a [FormatException] if the UCI string is invalid.
factory NormalMove.fromUci(String uci) {
final from = parseSquare(uci.substring(0, 2));
final to = parseSquare(uci.substring(2, 4));
final from = Square.parse(uci.substring(0, 2));
final to = Square.parse(uci.substring(2, 4));
Role? promotion;
if (uci.length == 5) {
promotion = Role.fromChar(uci[4]);
@@ -749,7 +751,7 @@ class DropMove extends Move {
/// Throws a [FormatException] if the UCI string is invalid.
factory DropMove.fromUci(String uci) {
final role = Role.fromChar(uci[0]);
final to = parseSquare(uci.substring(2));
final to = Square.parse(uci.substring(2));
if (role != null && to != null) {
return DropMove(to: to, role: role);
}
+2 -3
View File
@@ -5,7 +5,6 @@ import 'package:meta/meta.dart';
import './setup.dart';
import './models.dart';
import './position.dart';
import './utils.dart';
typedef PgnHeaders = Map<String, String>;
@@ -404,12 +403,12 @@ class PgnCommentShape {
/// Parse the PGN for any comment or return null.
static PgnCommentShape? fromPgn(String str) {
final color = CommentShapeColor.parseShapeColor(str.substring(0, 1));
final from = parseSquare(str.substring(1, 3));
final from = Square.parse(str.substring(1, 3));
if (color == null || from == null) return null;
if (str.length == 3) {
return PgnCommentShape(color: color, from: from, to: from);
}
final to = parseSquare(str.substring(3, 5));
final to = Square.parse(str.substring(3, 5));
if (str.length == 5 && to != null) {
return PgnCommentShape(color: color, from: from, to: to);
}
+4 -4
View File
@@ -305,7 +305,7 @@ abstract class Position<T extends Position<T>> {
} else {
return null;
}
final destination = parseSquare(san.substring(san.length - 2));
final destination = Square.parse(san.substring(san.length - 2));
if (destination == null) {
return null;
}
@@ -402,7 +402,7 @@ abstract class Position<T extends Position<T>> {
return null;
}
final destination = parseSquare(san);
final destination = Square.parse(san);
if (destination == null) {
return null;
}
@@ -439,7 +439,7 @@ abstract class Position<T extends Position<T>> {
}
// The final two moves define the destination
final destination = parseSquare(san.substring(san.length - 2));
final destination = Square.parse(san.substring(san.length - 2));
if (destination == null) {
return null;
}
@@ -472,7 +472,7 @@ abstract class Position<T extends Position<T>> {
return null;
}
if (san.length == 2) {
final sourceSquare = parseSquare(san);
final sourceSquare = Square.parse(san);
if (sourceSquare == null) {
return null;
}
+1 -2
View File
@@ -4,7 +4,6 @@ import 'dart:math' as math;
import './square_set.dart';
import './models.dart';
import './board.dart';
import './utils.dart';
/// A not necessarily legal position.
@immutable
@@ -120,7 +119,7 @@ class Setup {
if (parts.isNotEmpty) {
final epPart = parts.removeAt(0);
if (epPart != '-') {
epSquare = parseSquare(epPart);
epSquare = Square.parse(epPart);
if (epSquare == null) throw const FenError('ERR_EP_SQUARE');
}
}
-45
View File
@@ -1,48 +1,3 @@
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import './models.dart';
import './position.dart';
/// Parses a string like 'a1', 'a2', etc. and returns a [Square] or `null` if the square
/// doesn't exist.
Square? parseSquare(String str) {
if (str.length != 2) return null;
final file = str.codeUnitAt(0) - 'a'.codeUnitAt(0);
final rank = str.codeUnitAt(1) - '1'.codeUnitAt(0);
if (file < 0 || file >= 8 || rank < 0 || rank >= 8) return null;
return Square(file + 8 * rank);
}
/// Gets all the legal moves of this position as a map from the origin square to the set of destination squares.
///
/// Includes both possible representations of castling moves (unless `chess960` is true).
IMap<Square, ISet<Square>> legalMovesOf(Position pos,
{bool isChess960 = false}) {
final Map<Square, ISet<Square>> result = {};
for (final entry in pos.legalMoves.entries) {
final dests = entry.value.squares;
if (dests.isNotEmpty) {
final from = entry.key;
final destSet = dests.toSet();
if (!isChess960 &&
from == pos.board.kingOf(pos.turn) &&
entry.key.file == 4) {
if (dests.contains(Square.a1)) {
destSet.add(Square.c1);
} else if (dests.contains(Square.a8)) {
destSet.add(Square.c8);
}
if (dests.contains(Square.h1)) {
destSet.add(Square.g1);
} else if (dests.contains(Square.h8)) {
destSet.add(Square.g8);
}
}
result[from] = ISet(destSet);
}
}
return IMap(result);
}
/// Utility for nullable fields in copyWith methods
class Box<T> {
const Box(this.value);
+11
View File
@@ -48,6 +48,17 @@ void main() {
expect(Square.fromName('h8'), Square.h8);
expect(Square.fromName('e4'), Square.e4);
expect(() => Square.fromName('i1'), throwsFormatException);
expect(() => Square.fromName('a9'), throwsFormatException);
expect(() => Square.fromName('a11'), throwsFormatException);
});
test('parse', () {
expect(Square.parse('a1'), Square.a1);
expect(Square.parse('h8'), Square.h8);
expect(Square.parse('e4'), Square.e4);
expect(Square.parse('a9'), isNull);
expect(Square.parse('i1'), isNull);
expect(Square.parse('a11'), isNull);
});
test('offset', () {
-57
View File
@@ -1,57 +0,0 @@
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:dartchess/dartchess.dart';
import 'package:test/test.dart';
void main() {
test('legalMovesOf with Kh8', () {
final setup = Setup.parseFen(
'r1bq1r2/3n2k1/p1p1pp2/3pP2P/8/PPNB2Q1/2P2P2/R3K3 b Q - 1 22');
final pos = Chess.fromSetup(setup);
final moves = legalMovesOf(pos);
expect(moves[Square.g7], contains(Square.h8));
expect(moves[Square.g7], isNot(contains(Square.g8)));
});
test('legalMovesOf with regular castle', () {
final wtm =
Chess.fromSetup(Setup.parseFen('r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1'));
expect(
legalMovesOf(wtm)[Square.e1],
equals({
Square.a1,
Square.c1,
Square.d1,
Square.d2,
Square.e2,
Square.f1,
Square.f2,
Square.g1,
Square.h1
}));
expect(legalMovesOf(wtm)[Square.e8], null);
final btm =
Chess.fromSetup(Setup.parseFen('r3k2r/8/8/8/8/8/8/R3K2R b KQkq - 0 1'));
expect(
legalMovesOf(btm)[Square.e8],
equals({
Square.a8,
Square.c8,
Square.d7,
Square.d8,
Square.e7,
Square.f7,
Square.f8,
Square.g8,
Square.h8
}));
expect(legalMovesOf(btm)[Square.e1], null);
});
test('legalMovesOf with chess960 castle', () {
final pos = Chess.fromSetup(Setup.parseFen(
'rk2r3/pppbnppp/3p2n1/P2Pp3/4P2q/R5NP/1PP2PP1/1KNQRB2 b Kkq - 0 1'));
expect(legalMovesOf(pos, isChess960: true)[Square.b8],
equals(ISet(const {Square.a8, Square.c8, Square.e8})));
});
}