Merge pull request #41 from lichess-org/square_extension_type

Square extension type
This commit is contained in:
Vincent Velociter
2024-08-01 14:15:58 +02:00
committed by GitHub
24 changed files with 1009 additions and 605 deletions
+6
View File
@@ -1,3 +1,9 @@
## 0.8.0
### Breaking changes:
- `Square` is now an extension type.
- Introduce `File` and `Rank` types.
## 0.7.1
- Add Piece.kind, Role.letter and Role.uppercaseLetter getters.
+3 -7
View File
@@ -1,6 +1,6 @@
import 'package:benchmark/benchmark.dart';
import 'package:dartchess/dartchess.dart';
import 'dart:io';
import 'dart:io' as io;
void main() {
benchmark('make fen from initial position', () {
@@ -37,19 +37,15 @@ void main() {
legalMovesPos.legalMoves.length;
});
benchmark('algebraic legal moves', () {
algebraicLegalMoves(legalMovesPos);
});
benchmark('parsePgn - kasparov-deep-blue', () {
final String data =
File('./data/kasparov-deep-blue-1997.pgn').readAsStringSync();
io.File('./data/kasparov-deep-blue-1997.pgn').readAsStringSync();
PgnGame.parseMultiGamePgn(data);
});
final game = PgnGame.parsePgn(
File('./data/lichess-bullet-game.pgn').readAsStringSync());
io.File('./data/lichess-bullet-game.pgn').readAsStringSync());
benchmark('makePgn', () {
game.makePgn();
});
+3 -5
View File
@@ -6,12 +6,10 @@ 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 = algebraicLegalMoves(pos);
// Generate legal moves
assert(pos.legalMoves.length == 16);
assert(legalMoves['e2']!.length == 2);
const move = NormalMove(from: 12, to: 28);
const move = NormalMove(from: Square.e2, to: Square.e4);
assert(pos.isLegal(move));
+7 -13
View File
@@ -1,22 +1,18 @@
import './square_set.dart';
import './utils.dart';
import './models.dart';
/// Gets squares attacked or defended by a king on [Square].
SquareSet kingAttacks(Square square) {
assert(square >= 0 && square < 64);
return _kingAttacks[square];
}
/// Gets squares attacked or defended by a knight on [Square].
SquareSet knightAttacks(Square square) {
assert(square >= 0 && square < 64);
return _knightAttacks[square];
}
/// Gets squares attacked or defended by a pawn of the given [Side] on [Square].
SquareSet pawnAttacks(Side side, Square square) {
assert(square >= 0 && square < 64);
return _pawnAttacks[side]![square];
}
@@ -89,10 +85,8 @@ SquareSet _computeRange(Square square, List<int> deltas) {
SquareSet range = SquareSet.empty;
for (final delta in deltas) {
final sq = square + delta;
if (0 <= sq &&
sq < 64 &&
(squareFile(square) - squareFile(sq)).abs() <= 2) {
range = range.withSquare(sq);
if (0 <= sq && sq < 64 && (square.file - Square(sq).file).abs() <= 2) {
range = range.withSquare(Square(sq));
}
}
return range;
@@ -100,7 +94,7 @@ SquareSet _computeRange(Square square, List<int> deltas) {
List<T> _tabulate<T>(T Function(Square square) f) {
final List<T> table = [];
for (Square square = 0; square < 64; square++) {
for (final square in Square.values) {
table.insert(square, f(square));
}
return table;
@@ -116,18 +110,18 @@ final _pawnAttacks = {
};
final _fileRange =
_tabulate((sq) => SquareSet.fromFile(squareFile(sq)).withoutSquare(sq));
_tabulate((sq) => SquareSet.fromFile(sq.file).withoutSquare(sq));
final _rankRange =
_tabulate((sq) => SquareSet.fromRank(squareRank(sq)).withoutSquare(sq));
_tabulate((sq) => SquareSet.fromRank(sq.rank).withoutSquare(sq));
final _diagRange = _tabulate((sq) {
final shift = 8 * (squareRank(sq) - squareFile(sq));
final shift = 8 * (sq.rank - sq.file);
return (shift >= 0
? SquareSet.diagonal.shl(shift)
: SquareSet.diagonal.shr(-shift))
.withoutSquare(sq);
});
final _antiDiagRange = _tabulate((sq) {
final shift = 8 * (squareRank(sq) + squareFile(sq) - 7);
final shift = 8 * (sq.rank + sq.file - 7);
return (shift >= 0
? SquareSet.antidiagonal.shl(shift)
: SquareSet.antidiagonal.shr(-shift))
+2 -2
View File
@@ -124,7 +124,7 @@ class Board {
file += code - 48;
} else {
if (file >= 8 || rank < 0) throw const FenError('ERR_BOARD');
final square = file + rank * 8;
final square = Square(file + rank * 8);
final promoted = i + 1 < boardFen.length && boardFen[i + 1] == '~';
final piece = _charToPiece(c, promoted);
if (piece == null) throw const FenError('ERR_BOARD');
@@ -147,7 +147,7 @@ class Board {
int empty = 0;
for (int rank = 7; rank >= 0; rank--) {
for (int file = 0; file < 8; file++) {
final square = file + rank * 8;
final square = Square(file + rank * 8);
final piece = pieceAt(square);
if (piece == null) {
empty++;
-3
View File
@@ -1,6 +1,3 @@
const kFileNames = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
const kRankNames = ['1', '2', '3', '4', '5', '6', '7', '8'];
/// The board part of the initial position in the FEN format.
const kInitialBoardFEN = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR';
+4 -5
View File
@@ -2,7 +2,6 @@ import './board.dart';
import './models.dart';
import './position.dart';
import './square_set.dart';
import './utils.dart';
/// Takes a string and returns a SquareSet. Useful for debugging/testing purposes.
///
@@ -34,7 +33,7 @@ SquareSet makeSquareSet(String rep) {
for (int x = 0; x < 8; x++) {
final repSq = table[y][x];
if (repSq == '1') {
ret = ret.withSquare(x + y * 8);
ret = ret.withSquare(Square(x + y * 8));
}
}
}
@@ -46,7 +45,7 @@ String humanReadableSquareSet(SquareSet sq) {
final buffer = StringBuffer();
for (int y = 7; y >= 0; y--) {
for (int x = 0; x < 8; x++) {
final square = x + y * 8;
final square = Square(x + y * 8);
buffer.write(sq.has(square) ? '1' : '.');
buffer.write(x < 7 ? ' ' : '\n');
}
@@ -59,7 +58,7 @@ String humanReadableBoard(Board board) {
final buffer = StringBuffer();
for (int y = 7; y >= 0; y--) {
for (int x = 0; x < 8; x++) {
final square = x + y * 8;
final square = Square(x + y * 8);
final p = board.pieceAt(square);
final col = p != null ? p.fenChar : '.';
buffer.write(col);
@@ -100,7 +99,7 @@ int perft(Position pos, int depth, {bool shouldLog = false}) {
for (final entry in pos.legalMoves.entries) {
final from = entry.key;
final dests = entry.value;
final promotions = squareRank(from) == (pos.turn == Side.white ? 6 : 1) &&
final promotions = from.rank == (pos.turn == Side.white ? 6 : 1) &&
pos.board.pawns.has(from)
? promotionRoles
: [null];
+373 -84
View File
@@ -1,6 +1,6 @@
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.
enum Side {
@@ -55,9 +55,6 @@ enum Role {
}
}
@Deprecated('Use `letter` instead.')
String get char => letter;
/// Gets the role letter in lowercase (as for black piece in FEN notation).
String get letter => switch (this) {
Role.pawn => 'p',
@@ -79,77 +76,317 @@ enum Role {
};
}
/// Number between 0 and 63 included representing a square on the board.
///
/// See [SquareSet] to see how the mapping looks like.
typedef Square = int;
/// A file of the chessboard.
extension type const File._(int value) implements int {
/// Gets the chessboard [File] from a file index between 0 and 7.
const File(this.value) : assert(value >= 0 && value < 8);
/// All the squares on the board.
abstract class Squares {
static const a1 = 0;
static const b1 = 1;
static const c1 = 2;
static const d1 = 3;
static const e1 = 4;
static const f1 = 5;
static const g1 = 6;
static const h1 = 7;
static const a2 = 8;
static const b2 = 9;
static const c2 = 10;
static const d2 = 11;
static const e2 = 12;
static const f2 = 13;
static const g2 = 14;
static const h2 = 15;
static const a3 = 16;
static const b3 = 17;
static const c3 = 18;
static const d3 = 19;
static const e3 = 20;
static const f3 = 21;
static const g3 = 22;
static const h3 = 23;
static const a4 = 24;
static const b4 = 25;
static const c4 = 26;
static const d4 = 27;
static const e4 = 28;
static const f4 = 29;
static const g4 = 30;
static const h4 = 31;
static const a5 = 32;
static const b5 = 33;
static const c5 = 34;
static const d5 = 35;
static const e5 = 36;
static const f5 = 37;
static const g5 = 38;
static const h5 = 39;
static const a6 = 40;
static const b6 = 41;
static const c6 = 42;
static const d6 = 43;
static const e6 = 44;
static const f6 = 45;
static const g6 = 46;
static const h6 = 47;
static const a7 = 48;
static const b7 = 49;
static const c7 = 50;
static const d7 = 51;
static const e7 = 52;
static const f7 = 53;
static const g7 = 54;
static const h7 = 55;
static const a8 = 56;
static const b8 = 57;
static const c8 = 58;
static const d8 = 59;
static const e8 = 60;
static const f8 = 61;
static const g8 = 62;
static const h8 = 63;
/// Gets a [File] from its name in algebraic notation.
///
/// Throws a [FormatException] if the algebraic notation is invalid.
factory File.fromName(String algebraic) {
final file = algebraic.codeUnitAt(0) - 97;
if (file < 0 || file > 7) {
throw FormatException('Invalid algebraic notation: $algebraic');
}
return File(file);
}
static const a = File(0);
static const b = File(1);
static const c = File(2);
static const d = File(3);
static const e = File(4);
static const f = File(5);
static const g = File(6);
static const h = File(7);
/// All files in ascending order.
static const values = [a, b, c, d, e, f, g, h];
static const _names = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
/// The name of the file, such as 'a', 'b', 'c', etc.
String get name => _names[value];
/// Returns the file offset by [delta].
///
/// Returns `null` if the resulting file is out of bounds.
File? offset(int delta) {
assert(delta >= -7 && delta <= 7);
final newFile = value + delta;
if (newFile < 0 || newFile > 7) {
return null;
}
return File(newFile);
}
}
/// A rank of the chessboard.
extension type const Rank._(int value) implements int {
/// Gets the chessboard [Rank] from a rank index between 0 and 7.
const Rank(this.value) : assert(value >= 0 && value < 8);
/// Gets a [Rank] from its name in algebraic notation.
///
/// Throws a [FormatException] if the algebraic notation is invalid.
factory Rank.fromName(String algebraic) {
final rank = algebraic.codeUnitAt(0) - 49;
if (rank < 0 || rank > 7) {
throw FormatException('Invalid algebraic notation: $algebraic');
}
return Rank(rank);
}
static const first = Rank(0);
static const second = Rank(1);
static const third = Rank(2);
static const fourth = Rank(3);
static const fifth = Rank(4);
static const sixth = Rank(5);
static const seventh = Rank(6);
static const eighth = Rank(7);
/// All ranks in ascending order.
static const values = [
first,
second,
third,
fourth,
fifth,
sixth,
seventh,
eighth
];
static const _names = ['1', '2', '3', '4', '5', '6', '7', '8'];
/// The name of the rank, such as '1', '2', '3', etc.
String get name => _names[value];
/// Returns the rank offset by [delta].
///
/// Returns `null` if the resulting rank is out of bounds.
Rank? offset(int delta) {
assert(delta >= -7 && delta <= 7);
final newRank = value + delta;
if (newRank < 0 || newRank > 7) {
return null;
}
return Rank(newRank);
}
}
/// A square of the chessboard.
///
/// The square is represented with an integer ranging from 0 to 63, using a
/// little-endian rank-file mapping (LERF):
/// ```
/// 8 | 56 57 58 59 60 61 62 63
/// 7 | 48 49 50 51 52 53 54 55
/// 6 | 40 41 42 43 44 45 46 47
/// 5 | 32 33 34 35 36 37 38 39
/// 4 | 24 25 26 27 28 29 30 31
/// 3 | 16 17 18 19 20 21 22 23
/// 2 | 8 9 10 11 12 13 14 15
/// 1 | 0 1 2 3 4 5 6 7
/// -------------------------
/// a b c d e f g h
/// ```
///
/// See also:
/// - [File]
/// - [Rank]
/// - [SquareSet] for the manipulation of sets of squares.
extension type const Square._(int value) implements int {
/// Gets the chessboard [Square] from a square index between 0 and 63.
const Square(this.value) : assert(value >= 0 && value < 64);
/// Gets a [Square] from its name in algebraic notation.
///
/// 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) {
throw FormatException('Invalid algebraic notation: $algebraic');
}
return Square(file | (rank << 3));
}
/// Gets a [Square] from its file and rank.
factory Square.fromCoords(File file, Rank rank) => Square(file | (rank << 3));
/// Parses a square name in algebraic notation.
///
/// 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;
return Square(file | (rank << 3));
}
/// The file of the square on the board.
File get file => File(value & 0x7);
/// The rank of the square on the board.
Rank get rank => Rank(value >> 3);
/// Unique identifier of the square, using pure algebraic notation.
String get name => file.name + rank.name;
/// Returns the square offset by [delta].
///
/// Returns `null` if the resulting square is out of bounds.
Square? offset(int delta) {
assert(delta >= -63 && delta <= 63);
final newSquare = value + delta;
if (newSquare < 0 || newSquare > 63) {
return null;
}
return Square(newSquare);
}
/// Return the bitwise XOR of the numeric square representation.
Square xor(Square other) => Square(value ^ other.value);
static const a1 = Square(0);
static const b1 = Square(1);
static const c1 = Square(2);
static const d1 = Square(3);
static const e1 = Square(4);
static const f1 = Square(5);
static const g1 = Square(6);
static const h1 = Square(7);
static const a2 = Square(8);
static const b2 = Square(9);
static const c2 = Square(10);
static const d2 = Square(11);
static const e2 = Square(12);
static const f2 = Square(13);
static const g2 = Square(14);
static const h2 = Square(15);
static const a3 = Square(16);
static const b3 = Square(17);
static const c3 = Square(18);
static const d3 = Square(19);
static const e3 = Square(20);
static const f3 = Square(21);
static const g3 = Square(22);
static const h3 = Square(23);
static const a4 = Square(24);
static const b4 = Square(25);
static const c4 = Square(26);
static const d4 = Square(27);
static const e4 = Square(28);
static const f4 = Square(29);
static const g4 = Square(30);
static const h4 = Square(31);
static const a5 = Square(32);
static const b5 = Square(33);
static const c5 = Square(34);
static const d5 = Square(35);
static const e5 = Square(36);
static const f5 = Square(37);
static const g5 = Square(38);
static const h5 = Square(39);
static const a6 = Square(40);
static const b6 = Square(41);
static const c6 = Square(42);
static const d6 = Square(43);
static const e6 = Square(44);
static const f6 = Square(45);
static const g6 = Square(46);
static const h6 = Square(47);
static const a7 = Square(48);
static const b7 = Square(49);
static const c7 = Square(50);
static const d7 = Square(51);
static const e7 = Square(52);
static const f7 = Square(53);
static const g7 = Square(54);
static const h7 = Square(55);
static const a8 = Square(56);
static const b8 = Square(57);
static const c8 = Square(58);
static const d8 = Square(59);
static const e8 = Square(60);
static const f8 = Square(61);
static const g8 = Square(62);
static const h8 = Square(63);
/// All squares on the chessboard, from a1 to h8.
static const values = [
a1,
b1,
c1,
d1,
e1,
f1,
g1,
h1,
a2,
b2,
c2,
d2,
e2,
f2,
g2,
h2,
a3,
b3,
c3,
d3,
e3,
f3,
g3,
h3,
a4,
b4,
c4,
d4,
e4,
f4,
g4,
h4,
a5,
b5,
c5,
d5,
e5,
f5,
g5,
h5,
a6,
b6,
c6,
d6,
e6,
f6,
g6,
h6,
a7,
b7,
c7,
d7,
e7,
f7,
g7,
h7,
a8,
b8,
c8,
d8,
e8,
f8,
g8,
h8
];
}
typedef BySide<T> = IMap<Side, T>;
@@ -254,17 +491,19 @@ sealed class Move {
/// Gets the UCI notation of this move.
String get uci;
/// Constructs a [Move] from an UCI string.
/// Parses a UCI string into a move.
///
/// Will return a [NormalMove] or a [DropMove] depending on the UCI string.
///
/// Returns `null` if UCI string is not valid.
static Move? fromUci(String str) {
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]);
@@ -279,13 +518,19 @@ sealed class Move {
return null;
}
/// Returns `true` if [square] is a square of this move.
bool hasSquare(Square square);
/// Returns an iterable of all squares involved in this move.
Iterable<Square> get squares;
@override
String toString() {
return 'Move($uci)';
}
}
/// Represents a chess move, possibly a promotion.
/// Represents a chess move, which is possibly a promotion.
@immutable
class NormalMove extends Move {
const NormalMove({
@@ -294,18 +539,42 @@ class NormalMove extends Move {
this.promotion,
});
/// Constructs a [NormalMove] from a UCI string.
///
/// Throws a [FormatException] if the UCI string is invalid.
factory NormalMove.fromUci(String uci) {
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]);
}
if (from != null && to != null) {
return NormalMove(from: from, to: to, promotion: promotion);
}
throw FormatException('Invalid UCI notation: $uci');
}
/// The origin square of this move.
final Square from;
/// The role of the promoted piece, if any.
final Role? promotion;
@override
bool hasSquare(Square square) => square == from || square == to;
@override
Iterable<Square> get squares => [from, to];
/// Returns a copy of this move with a [promotion] role.
NormalMove withPromotion(Role? promotion) =>
NormalMove(from: from, to: to, promotion: promotion);
/// Gets UCI notation, like `g1f3` for a normal move, `a7a8q` for promotion to a queen.
@override
String get uci =>
toAlgebraic(from) +
toAlgebraic(to) +
(promotion != null ? promotion!.letter : '');
from.name + to.name + (promotion != null ? promotion!.letter : '');
@override
bool operator ==(Object other) {
@@ -320,16 +589,36 @@ class NormalMove extends Move {
/// Represents a drop move.
@immutable
class DropMove extends Move {
/// Constructs a [DropMove] from a target square and a role.
const DropMove({
required super.to,
required this.role,
});
/// Constructs a [DropMove] from a UCI string.
///
/// Throws a [FormatException] if the UCI string is invalid.
factory DropMove.fromUci(String uci) {
final role = Role.fromChar(uci[0]);
final to = Square.parse(uci.substring(2));
if (role != null && to != null) {
return DropMove(to: to, role: role);
}
throw FormatException('Invalid UCI notation: $uci');
}
/// The [Role] of the dropped piece.
final Role role;
@override
bool hasSquare(Square square) => square == to;
@override
Iterable<Square> get squares => [to];
/// Gets UCI notation of the drop, like `Q@f7`.
@override
String get uci => '${role.uppercaseLetter}@${toAlgebraic(to)}';
String get uci => '${role.uppercaseLetter}@${to.name}';
@override
bool operator ==(Object other) {
+4 -5
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>;
@@ -397,19 +396,19 @@ class PgnCommentShape {
@override
String toString() {
return to == from
? '${color.string[0]}${toAlgebraic(to)}'
: '${color.string[0]}${toAlgebraic(from)}${toAlgebraic(to)}';
? '${color.string[0]}${to.name}'
: '${color.string[0]}${from.name}${to.name}';
}
/// 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);
}
+56 -56
View File
@@ -1,7 +1,6 @@
import 'package:meta/meta.dart';
import 'dart:math' as math;
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import './constants.dart';
import './square_set.dart';
import './attacks.dart';
import './models.dart';
@@ -306,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;
}
@@ -344,9 +343,9 @@ abstract class Position<T extends Position<T>> {
final isPromotion = san.contains('=');
final isCapturing = san.contains('x');
int? pawnRank;
Rank? pawnRank;
if (oneIndex <= san.codeUnits[0] && san.codeUnits[0] <= eightIndex) {
pawnRank = san.codeUnits[0] - oneIndex;
pawnRank = Rank(san.codeUnits[0] - oneIndex);
san = san.substring(1);
}
final isPawnMove = aIndex <= san.codeUnits[0] && san.codeUnits[0] <= hIndex;
@@ -369,7 +368,7 @@ abstract class Position<T extends Position<T>> {
return null;
}
final sourceFile = sourceFileCharacter - aIndex;
final sourceFile = File(sourceFileCharacter - aIndex);
final sourceFileFilter = SquareSet.fromFile(sourceFile);
filter = filter.intersect(sourceFileFilter);
@@ -403,18 +402,18 @@ abstract class Position<T extends Position<T>> {
return null;
}
final destination = parseSquare(san);
final destination = Square.parse(san);
if (destination == null) {
return null;
}
// There may be many pawns in the corresponding file
// The corect choice will always be the pawn behind the destination square that is furthest down the board
for (int rank = 0; rank < 8; rank++) {
for (final rank in Rank.values) {
final rankFilter = SquareSet.fromRank(rank).complement();
// If the square is behind or on this rank, the rank it will not contain the source pawn
if (turn == Side.white && rank >= squareRank(destination) ||
turn == Side.black && rank <= squareRank(destination)) {
if (turn == Side.white && rank >= destination.rank ||
turn == Side.black && rank <= destination.rank) {
filter = filter.intersect(rankFilter);
}
}
@@ -440,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;
}
@@ -473,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;
}
@@ -483,11 +482,11 @@ abstract class Position<T extends Position<T>> {
if (san.length == 1) {
final sourceCharacter = san.codeUnits[0];
if (oneIndex <= sourceCharacter && sourceCharacter <= eightIndex) {
final rank = sourceCharacter - oneIndex;
final rank = Rank(sourceCharacter - oneIndex);
final rankFilter = SquareSet.fromRank(rank);
filter = filter.intersect(rankFilter);
} else if (aIndex <= sourceCharacter && sourceCharacter <= hIndex) {
final file = sourceCharacter - aIndex;
final file = File(sourceCharacter - aIndex);
final fileFilter = SquareSet.fromFile(file);
filter = filter.intersect(fileFilter);
} else {
@@ -536,17 +535,18 @@ abstract class Position<T extends Position<T>> {
return _copyWith();
}
final castlingSide = _getCastlingSide(move);
final epCaptureTarget = to + (turn == Side.white ? -8 : 8);
Square? epCaptureTarget;
Square? newEpSquare;
Board newBoard = board.removePieceAt(from);
Castles newCastles = castles;
if (piece.role == Role.pawn) {
if (to == epSquare) {
epCaptureTarget = Square(to + (turn == Side.white ? -8 : 8));
newBoard = newBoard.removePieceAt(epCaptureTarget);
}
final delta = from - to;
if (delta.abs() == 16 && from >= 8 && from <= 55) {
newEpSquare = (from + to) >>> 1;
if (delta.abs() == 16 && from >= Square.a2 && from <= Square.h7) {
newEpSquare = Square((from + to) >>> 1);
}
} else if (piece.role == Role.rook) {
newCastles = newCastles.discardRookAt(from);
@@ -576,7 +576,7 @@ abstract class Position<T extends Position<T>> {
final capturedPiece = castlingSide == null
? board.pieceAt(to)
: to == epSquare
: to == epSquare && epCaptureTarget != null
? board.pieceAt(epCaptureTarget)
: null;
final isCapture = capturedPiece != null;
@@ -742,8 +742,8 @@ abstract class Position<T extends Position<T>> {
if (epSquare != null) {
// The pushed pawn must be the only checker, or it has uncovered
// check by a single sliding piece.
final pushedTo = epSquare! ^ 8;
final pushedFrom = epSquare! ^ 24;
final pushedTo = epSquare!.xor(Square.a2);
final pushedFrom = epSquare!.xor(Square.a4);
if (checkers.moreThanOne ||
(checkers.first != pushedTo &&
board
@@ -776,7 +776,7 @@ abstract class Position<T extends Position<T>> {
san = to > from ? 'O-O' : 'O-O-O';
} else {
final capture = board.occupied.has(to) ||
(role == Role.pawn && squareFile(from) != squareFile(to));
(role == Role.pawn && from.file != to.file);
if (role != Role.pawn) {
san = role.uppercaseLetter;
@@ -805,34 +805,33 @@ abstract class Position<T extends Position<T>> {
if (others.isNotEmpty) {
bool row = false;
bool column =
others.isIntersected(SquareSet.fromRank(squareRank(from)));
if (others
.isIntersected(SquareSet.fromFile(squareFile(from)))) {
others.isIntersected(SquareSet.fromRank(from.rank));
if (others.isIntersected(SquareSet.fromFile(from.file))) {
row = true;
} else {
column = true;
}
if (column) {
san += kFileNames[squareFile(from)];
san += from.file.name;
}
if (row) {
san += kRankNames[squareRank(from)];
san += from.rank.name;
}
}
}
} else if (capture) {
san = kFileNames[squareFile(from)];
san = from.file.name;
}
if (capture) san += 'x';
san += toAlgebraic(to);
san += to.name;
if (prom != null) {
san += '=${prom.uppercaseLetter}';
}
}
case DropMove(role: final role, to: final to):
if (role != Role.pawn) san = role.uppercaseLetter;
san += '@${toAlgebraic(to)}';
san += '@${to.name}';
}
return san;
}
@@ -853,13 +852,13 @@ abstract class Position<T extends Position<T>> {
pseudo = pawnAttacks(turn, square) & board.bySide(turn.opposite);
final delta = turn == Side.white ? 8 : -8;
final step = square + delta;
if (0 <= step && step < 64 && !board.occupied.has(step)) {
pseudo = pseudo.withSquare(step);
if (0 <= step && step < 64 && !board.occupied.has(Square(step))) {
pseudo = pseudo.withSquare(Square(step));
final canDoubleStep =
turn == Side.white ? square < 16 : square >= 64 - 16;
turn == Side.white ? square < Square.a3 : square >= Square.a7;
final doubleStep = step + delta;
if (canDoubleStep && !board.occupied.has(doubleStep)) {
pseudo = pseudo.withSquare(doubleStep);
if (canDoubleStep && !board.occupied.has(Square(doubleStep))) {
pseudo = pseudo.withSquare(Square(doubleStep));
}
}
if (epSquare != null && _canCaptureEp(square)) {
@@ -980,7 +979,7 @@ abstract class Position<T extends Position<T>> {
if (!pawnAttacks(turn, pawn).has(epSquare!)) return false;
final king = board.kingOf(turn);
if (king == null) return true;
final captured = epSquare! + (turn == Side.white ? -8 : 8);
final captured = Square(epSquare! + (turn == Side.white ? -8 : 8));
final occupied = board.occupied
.toggleSquare(pawn)
.toggleSquare(epSquare!)
@@ -1787,7 +1786,7 @@ class RacingKings extends Position<RacingKings> {
fullmoves: 1);
static const initial = RacingKings._initial();
static const goal = SquareSet.fromRank(7);
static const goal = SquareSet.fromRank(Rank.eighth);
bool get blackCanReachGoal {
final blackKing = board.kingOf(Side.black);
@@ -2359,10 +2358,10 @@ class Castles {
static const standard = Castles(
unmovedRooks: SquareSet.corners,
whiteRookQueenSide: Squares.a1,
whiteRookKingSide: Squares.h1,
blackRookQueenSide: Squares.a8,
blackRookKingSide: Squares.h8,
whiteRookQueenSide: Square.a1,
whiteRookKingSide: Square.h1,
blackRookQueenSide: Square.a8,
blackRookKingSide: Square.h8,
whitePathQueenSide: SquareSet(0x000000000000000e),
whitePathKingSide: SquareSet(0x0000000000000060),
blackPathQueenSide: SquareSet(0x0e00000000000000),
@@ -2385,8 +2384,8 @@ class Castles {
unmovedRooks: SquareSet(0x8100000000000000),
whiteRookKingSide: null,
whiteRookQueenSide: null,
blackRookKingSide: Squares.h8,
blackRookQueenSide: Squares.a8,
blackRookKingSide: Square.h8,
blackRookQueenSide: Square.a8,
whitePathKingSide: SquareSet.empty,
whitePathQueenSide: SquareSet.empty,
blackPathQueenSide: SquareSet(0x0e00000000000000),
@@ -2612,27 +2611,29 @@ class _Context {
Square _rookCastlesTo(Side side, CastlingSide cs) {
return side == Side.white
? (cs == CastlingSide.queen ? Squares.d1 : Squares.f1)
? (cs == CastlingSide.queen ? Square.d1 : Square.f1)
: cs == CastlingSide.queen
? Squares.d8
: Squares.f8;
? Square.d8
: Square.f8;
}
Square _kingCastlesTo(Side side, CastlingSide cs) {
return side == Side.white
? (cs == CastlingSide.queen ? Squares.c1 : Squares.g1)
? (cs == CastlingSide.queen ? Square.c1 : Square.g1)
: cs == CastlingSide.queen
? Squares.c8
: Squares.g8;
? Square.c8
: Square.g8;
}
Square? _validEpSquare(Setup setup) {
if (setup.epSquare == null) return null;
final epRank = setup.turn == Side.white ? 5 : 2;
final forward = setup.turn == Side.white ? 8 : -8;
if (squareRank(setup.epSquare!) != epRank) return null;
if (setup.board.occupied.has(setup.epSquare! + forward)) return null;
final pawn = setup.epSquare! - forward;
if (setup.epSquare!.rank != epRank) return null;
if (setup.board.occupied.has(Square(setup.epSquare!.value + forward))) {
return null;
}
final pawn = Square(setup.epSquare!.value - forward);
if (!setup.board.pawns.has(pawn) ||
!setup.board.bySide(setup.turn.opposite).has(pawn)) {
return null;
@@ -2654,13 +2655,12 @@ SquareSet _pseudoLegalMoves(Position pos, Square square, _Context context) {
pseudo = pseudo & captureTargets;
final delta = pos.turn == Side.white ? 8 : -8;
final step = square + delta;
if (0 <= step && step < 64 && !pos.board.occupied.has(step)) {
pseudo = pseudo.withSquare(step);
if (0 <= step && step < 64 && !pos.board.occupied.has(Square(step))) {
pseudo = pseudo.withSquare(Square(step));
final canDoubleStep =
pos.turn == Side.white ? square < 16 : square >= 64 - 16;
final doubleStep = step + delta;
if (canDoubleStep && !pos.board.occupied.has(doubleStep)) {
pseudo = pseudo.withSquare(doubleStep);
pos.turn == Side.white ? square < Square.a3 : square >= Square.a7;
if (canDoubleStep && !pos.board.occupied.has(Square(step + delta))) {
pseudo = pseudo.withSquare(Square(step + delta));
}
}
return pseudo;
+6 -8
View File
@@ -4,8 +4,6 @@ import 'dart:math' as math;
import './square_set.dart';
import './models.dart';
import './board.dart';
import './utils.dart';
import './constants.dart';
/// A not necessarily legal position.
@immutable
@@ -121,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');
}
}
@@ -178,7 +176,7 @@ class Setup {
board.fen + (pockets != null ? _makePockets(pockets!) : ''),
turnLetter,
_makeCastlingFen(board, unmovedRooks),
if (epSquare != null) toAlgebraic(epSquare!) else '-',
if (epSquare != null) epSquare!.name else '-',
if (remainingChecks != null) _makeRemainingChecks(remainingChecks!),
math.max(0, math.min(halfmoves, 9999)),
math.max(1, math.min(fullmoves, 9999)),
@@ -325,7 +323,7 @@ SquareSet _parseCastlingFen(Board board, String castlingPart) {
candidates = backrank.squaresReversed;
} else if ('a'.compareTo(lower) <= 0 && lower.compareTo('h') <= 0) {
candidates =
(SquareSet.fromFile(lower.codeUnitAt(0) - 'a'.codeUnitAt(0)) &
(SquareSet.fromFile(File(lower.codeUnitAt(0) - 'a'.codeUnitAt(0))) &
backrank)
.squares;
} else {
@@ -339,8 +337,8 @@ SquareSet _parseCastlingFen(Board board, String castlingPart) {
}
}
}
if ((const SquareSet.fromRank(0) & unmovedRooks).size > 2 ||
(const SquareSet.fromRank(7) & unmovedRooks).size > 2) {
if ((const SquareSet.fromRank(Rank.first) & unmovedRooks).size > 2 ||
(const SquareSet.fromRank(Rank.eighth) & unmovedRooks).size > 2) {
throw const FenError('ERR_CASTLING');
}
return unmovedRooks;
@@ -371,7 +369,7 @@ String _makeCastlingFen(Board board, SquareSet unmovedRooks) {
} else if (rook == candidates.last && king != null && king < rook) {
buffer.write(color == Side.white ? 'K' : 'k');
} else {
final file = kFileNames[squareFile(rook)];
final file = rook.file.name;
buffer.write(color == Side.white ? file.toUpperCase() : file);
}
}
+52 -32
View File
@@ -1,25 +1,15 @@
import './models.dart';
/// A set of squares represented by a 64 bit integer mask, using little endian
/// rank-file (LERF) mapping.
/// A finite set of all squares on a chessboard.
///
/// ```
/// 8 | 56 57 58 59 60 61 62 63
/// 7 | 48 49 50 51 52 53 54 55
/// 6 | 40 41 42 43 44 45 46 47
/// 5 | 32 33 34 35 36 37 38 39
/// 4 | 24 25 26 27 28 29 30 31
/// 3 | 16 17 18 19 20 21 22 23
/// 2 | 8 9 10 11 12 13 14 15
/// 1 | 0 1 2 3 4 5 6 7
/// -------------------------
/// a b c d e f g h
/// ```
/// All the squares are represented by a single 64-bit integer, where each bit
/// corresponds to a square, using a little-endian rank-file mapping.
/// See also [Square].
///
/// The set operations are implemented as bitwise operations on the integer.
extension type const SquareSet(int value) {
/// Creates a [SquareSet] with a single [Square].
const SquareSet.fromSquare(Square square)
: value = 1 << square,
assert(square >= 0 && square < 64);
const SquareSet.fromSquare(Square square) : value = 1 << square;
/// Creates a [SquareSet] from several [Square]s.
SquareSet.fromSquares(Iterable<Square> squares)
@@ -28,12 +18,12 @@ extension type const SquareSet(int value) {
.fold(0, (left, right) => left | right);
/// Create a [SquareSet] containing all squares of the given rank.
const SquareSet.fromRank(int rank)
const SquareSet.fromRank(Rank rank)
: value = 0xff << (8 * rank),
assert(rank >= 0 && rank < 8);
/// Create a [SquareSet] containing all squares of the given file.
const SquareSet.fromFile(int file)
const SquareSet.fromFile(File file)
: value = 0x0101010101010101 << file,
assert(file >= 0 && file < 8);
@@ -50,6 +40,10 @@ extension type const SquareSet(int value) {
static const corners = SquareSet(0x8100000000000081);
static const center = SquareSet(0x0000001818000000);
static const backranks = SquareSet(0xff000000000000ff);
static const firstRank = SquareSet(0xff);
static const eighthRank = SquareSet(0xff00000000000000);
static const aFile = SquareSet(0x0101010101010101);
static const hFile = SquareSet(0x8080808080808080);
/// Bitwise right shift
SquareSet shr(int shift) {
@@ -65,22 +59,29 @@ extension type const SquareSet(int value) {
return this;
}
/// Returns a new [SquareSet] with a bitwise XOR of this set and [other].
SquareSet xor(SquareSet other) => SquareSet(value ^ other.value);
SquareSet operator ^(SquareSet other) => SquareSet(value ^ other.value);
/// Returns a new [SquareSet] with the squares that are in either this set or [other].
SquareSet union(SquareSet other) => SquareSet(value | other.value);
SquareSet operator |(SquareSet other) => SquareSet(value | other.value);
/// Returns a new [SquareSet] with the squares that are in both this set and [other].
SquareSet intersect(SquareSet other) => SquareSet(value & other.value);
SquareSet operator &(SquareSet other) => SquareSet(value & other.value);
/// Returns a new [SquareSet] with the [other] squares removed from this set.
SquareSet minus(SquareSet other) => SquareSet(value - other.value);
SquareSet operator -(SquareSet other) => SquareSet(value - other.value);
/// Returns the set complement of this set.
SquareSet complement() => SquareSet(~value);
/// Returns the set difference of this set and [other].
SquareSet diff(SquareSet other) => SquareSet(value & ~other.value);
/// Flips the set vertically.
SquareSet flipVertical() {
const k1 = 0x00FF00FF00FF00FF;
const k2 = 0x0000FFFF0000FFFF;
@@ -90,6 +91,7 @@ extension type const SquareSet(int value) {
return SquareSet(x);
}
/// Flips the set horizontally.
SquareSet mirrorHorizontal() {
const k1 = 0x5555555555555555;
const k2 = 0x3333333333333333;
@@ -100,42 +102,60 @@ extension type const SquareSet(int value) {
return SquareSet(x);
}
/// Returns the number of squares in the set.
int get size => _popcnt64(value);
/// Returns true if the set is empty.
bool get isEmpty => value == 0;
/// Returns true if the set is not empty.
bool get isNotEmpty => value != 0;
int? get first => _getFirstSquare(value);
int? get last => _getLastSquare(value);
/// Returns the first square in the set, or null if the set is empty.
Square? get first => _getFirstSquare(value);
/// Returns the last square in the set, or null if the set is empty.
Square? get last => _getLastSquare(value);
/// Returns the squares in the set as an iterable.
Iterable<Square> get squares => _iterateSquares();
/// Returns the squares in the set as an iterable in reverse order.
Iterable<Square> get squaresReversed => _iterateSquaresReversed();
/// Returns true if the set contains more than one square.
bool get moreThanOne => isNotEmpty && size > 1;
/// Returns square if it is single, otherwise returns null.
int? get singleSquare => moreThanOne ? null : last;
Square? get singleSquare => moreThanOne ? null : last;
/// Returns true if the [SquareSet] contains the given [square].
bool has(Square square) {
assert(square >= 0 && square < 64);
return value & (1 << square) != 0;
}
/// Returns true if the square set has any square in the [other] square set.
bool isIntersected(SquareSet other) => intersect(other).isNotEmpty;
/// Returns true if the square set is disjoint from the [other] square set.
bool isDisjoint(SquareSet other) => intersect(other).isEmpty;
/// Returns a new [SquareSet] with the given [square] added.
SquareSet withSquare(Square square) {
assert(square >= 0 && square < 64);
return SquareSet(value | (1 << square));
}
/// Returns a new [SquareSet] with the given [square] removed.
SquareSet withoutSquare(Square square) {
assert(square >= 0 && square < 64);
return SquareSet(value & ~(1 << square));
}
/// Removes [Square] if present, or put it if absent.
SquareSet toggleSquare(Square square) {
assert(square >= 0 && square < 64);
return SquareSet(value ^ (1 << square));
}
/// Returns a new [SquareSet] with its first [Square] removed.
SquareSet withoutFirst() {
final f = first;
return f != null ? withoutSquare(f) : empty;
@@ -144,8 +164,8 @@ extension type const SquareSet(int value) {
/// Returns the hexadecimal string representation of the bitboard value.
String toHexString() {
final buffer = StringBuffer();
for (Square square = 63; square >= 0; square--) {
buffer.write(has(square) ? '1' : '0');
for (int square = 63; square >= 0; square--) {
buffer.write(has(Square(square)) ? '1' : '0');
}
final b = buffer.toString();
final first = int.parse(b.substring(0, 32), radix: 2)
@@ -181,14 +201,14 @@ extension type const SquareSet(int value) {
}
}
int? _getFirstSquare(int bitboard) {
Square? _getFirstSquare(int bitboard) {
final ntz = _ntz64(bitboard);
return ntz >= 0 && ntz < 64 ? ntz : null;
return ntz >= 0 && ntz < 64 ? Square(ntz) : null;
}
int? _getLastSquare(int bitboard) {
Square? _getLastSquare(int bitboard) {
if (bitboard == 0) return null;
return 63 - _nlz64(bitboard);
return Square(63 - _nlz64(bitboard));
}
}
-56
View File
@@ -1,59 +1,3 @@
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import './models.dart';
import './constants.dart';
import './position.dart';
/// Gets the rank of that square.
Square squareRank(Square square) => square >> 3;
/// Gets the file of that square.
Square squareFile(Square square) => square & 0x7;
/// 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 file + 8 * rank;
}
/// Returns the algebraic coordinate notation of the [Square].
String toAlgebraic(Square square) =>
kFileNames[squareFile(square)] + kRankNames[squareRank(square)];
/// Gets all the legal moves of this position in the algebraic coordinate notation.
///
/// Includes both possible representations of castling moves (unless `chess960` is true).
IMap<String, ISet<String>> algebraicLegalMoves(Position pos,
{bool isChess960 = false}) {
final Map<String, ISet<String>> result = {};
for (final entry in pos.legalMoves.entries) {
final dests = entry.value.squares;
if (dests.isNotEmpty) {
final from = entry.key;
final destSet = dests.map((e) => toAlgebraic(e)).toSet();
if (!isChess960 &&
from == pos.board.kingOf(pos.turn) &&
squareFile(entry.key) == 4) {
if (dests.contains(0)) {
destSet.add('c1');
} else if (dests.contains(56)) {
destSet.add('c8');
}
if (dests.contains(7)) {
destSet.add('g1');
} else if (dests.contains(63)) {
destSet.add('g8');
}
}
result[toAlgebraic(from)] = ISet(destSet);
}
}
return IMap(result);
}
/// Utility for nullable fields in copyWith methods
class Box<T> {
const Box(this.value);
+1 -1
View File
@@ -1,7 +1,7 @@
name: dartchess
description: Provides chess and chess variants rules and operations including chess move generation, read and write FEN, read and write PGN.
repository: https://github.com/lichess-org/dartchess
version: 0.7.1
version: 0.8.0
platforms:
android:
ios:
+23 -23
View File
@@ -13,7 +13,7 @@ void main() {
. . . . 1 1 1 .
. . . . . . . .
''');
expect(kingAttacks(21), attacks);
expect(kingAttacks(Square.f3), attacks);
});
test('King attacks near edges', () {
@@ -27,7 +27,7 @@ void main() {
. . . . . . 1 1
. . . . . . 1 .
''');
expect(kingAttacks(7), attacks);
expect(kingAttacks(Square.h1), attacks);
});
test('Knight attacks', () {
@@ -41,7 +41,7 @@ void main() {
. . . . . . . .
. . . . . . . .
''');
expect(knightAttacks(35), attacks);
expect(knightAttacks(Square.d5), attacks);
});
test('Knight attacks near edges', () {
@@ -55,7 +55,7 @@ void main() {
. . . . . . . .
. . . . 1 . . .
''');
expect(knightAttacks(14), attacks);
expect(knightAttacks(Square.g2), attacks);
});
test('White pawn attacks', () {
@@ -69,7 +69,7 @@ void main() {
. . . . . . . .
. . . . . . . .
''');
expect(pawnAttacks(Side.white, 11), attacks);
expect(pawnAttacks(Side.white, Square.d2), attacks);
});
test('White pawn attacks near edges', () {
@@ -83,7 +83,7 @@ void main() {
. . . . . . . .
. . . . . . . .
''');
expect(pawnAttacks(Side.white, 8), attacks);
expect(pawnAttacks(Side.white, Square.a2), attacks);
});
test('Black pawn attacks', () {
@@ -97,7 +97,7 @@ void main() {
. . . . . . . .
. . . . . . . .
''');
expect(pawnAttacks(Side.black, 36), attacks);
expect(pawnAttacks(Side.black, Square.e5), attacks);
});
test('Black pawn attacks near edges', () {
@@ -111,11 +111,11 @@ void main() {
. . . . . . . .
. . . . . . . .
''');
expect(pawnAttacks(Side.black, 39), attacks);
expect(pawnAttacks(Side.black, Square.h5), attacks);
});
test('Bishop attacks, empty board', () {
expect(bishopAttacks(27, SquareSet.empty), makeSquareSet('''
expect(bishopAttacks(Square.d4, SquareSet.empty), makeSquareSet('''
. . . . . . . 1
1 . . . . . 1 .
. 1 . . . 1 . .
@@ -138,7 +138,7 @@ void main() {
. . . . . . . .
. . . . . . . .
''');
expect(bishopAttacks(0, occupied), makeSquareSet('''
expect(bishopAttacks(Square.a1, occupied), makeSquareSet('''
. . . . . . . .
. . . . . . . .
. . . . . 1 . .
@@ -161,7 +161,7 @@ void main() {
. . . . . . . .
. . . . . . . .
''');
expect(bishopAttacks(36, occupied), makeSquareSet('''
expect(bishopAttacks(Square.e5, occupied), makeSquareSet('''
. . . . . . . .
. . . . . . . .
. . . 1 . 1 . .
@@ -174,7 +174,7 @@ void main() {
});
test('Rook attacks, empty board', () {
expect(rookAttacks(10, SquareSet.empty), makeSquareSet('''
expect(rookAttacks(Square.c2, SquareSet.empty), makeSquareSet('''
. . 1 . . . . .
. . 1 . . . . .
. . 1 . . . . .
@@ -197,7 +197,7 @@ void main() {
. . 1 . . . . .
. . . . . . . .
''');
expect(rookAttacks(42, occupied), makeSquareSet('''
expect(rookAttacks(Square.c6, occupied), makeSquareSet('''
. . 1 . . . . .
. . 1 . . . . .
1 1 . 1 1 1 . .
@@ -220,7 +220,7 @@ void main() {
. . . . . . . .
. . . . . . . .
''');
expect(rookAttacks(36, occupied), makeSquareSet('''
expect(rookAttacks(Square.e5, occupied), makeSquareSet('''
. . . . . . . .
. . . . . . . .
. . . . 1 . . .
@@ -233,7 +233,7 @@ void main() {
});
test('Queen attacks, empty board', () {
expect(queenAttacks(37, SquareSet.empty), makeSquareSet('''
expect(queenAttacks(Square.f5, SquareSet.empty), makeSquareSet('''
. . 1 . . 1 . .
. . . 1 . 1 . 1
. . . . 1 1 1 .
@@ -256,7 +256,7 @@ void main() {
. . 1 . . . . .
. . . . . . . .
''');
expect(queenAttacks(42, occupied), makeSquareSet('''
expect(queenAttacks(Square.c6, occupied), makeSquareSet('''
1 . 1 . 1 . . .
. 1 1 1 . . . .
. 1 . 1 1 1 1 1
@@ -279,7 +279,7 @@ void main() {
. . . . . . . .
. . . . . . . .
''');
expect(queenAttacks(36, occupied), makeSquareSet('''
expect(queenAttacks(Square.e5, occupied), makeSquareSet('''
. . . . . . . .
. . . . . . . .
. . . 1 1 1 . .
@@ -296,27 +296,27 @@ void main() {
const emptySquareSet = SquareSet.empty;
expect(() {
kingAttacks(illegalBoardPosition);
kingAttacks(Square(illegalBoardPosition));
}, throwsA(isA<AssertionError>()));
expect(() {
pawnAttacks(Side.white, illegalBoardPosition);
pawnAttacks(Side.white, Square(illegalBoardPosition));
}, throwsA(isA<AssertionError>()));
expect(() {
knightAttacks(illegalBoardPosition);
knightAttacks(Square(illegalBoardPosition));
}, throwsA(isA<AssertionError>()));
expect(() {
bishopAttacks(illegalBoardPosition, emptySquareSet);
bishopAttacks(Square(illegalBoardPosition), emptySquareSet);
}, throwsA(isA<AssertionError>()));
expect(() {
rookAttacks(illegalBoardPosition, emptySquareSet);
rookAttacks(Square(illegalBoardPosition), emptySquareSet);
}, throwsA(isA<AssertionError>()));
expect(() {
queenAttacks(illegalBoardPosition, emptySquareSet);
queenAttacks(Square(illegalBoardPosition), emptySquareSet);
}, throwsA(isA<AssertionError>()));
});
}
+7 -7
View File
@@ -12,7 +12,7 @@ void main() {
test('empty board', () {
expect(Board.empty.pieces.isEmpty, true);
expect(Board.empty.pieceAt(0), null);
expect(Board.empty.pieceAt(Square.a1), null);
});
test('standard board', () {
@@ -21,13 +21,13 @@ void main() {
test('setPieceAt', () {
const piece = Piece.whiteKing;
final board = Board.empty.setPieceAt(0, piece);
final board = Board.empty.setPieceAt(Square.a1, piece);
expect(board.occupied, const SquareSet(0x0000000000000001));
expect(board.pieces.length, 1);
expect(board.pieceAt(0), piece);
expect(board.pieceAt(Square.a1), piece);
final board2 = Board.standard.setPieceAt(60, piece);
expect(board2.pieceAt(60), piece);
final board2 = Board.standard.setPieceAt(Square.e8, piece);
expect(board2.pieceAt(Square.e8), piece);
expect(board2.white, const SquareSet(0x100000000000FFFF));
expect(board2.black, const SquareSet(0xEFFF000000000000));
@@ -40,8 +40,8 @@ void main() {
});
test('removePieceAt', () {
final board = Board.empty.setPieceAt(10, Piece.whiteKing);
expect(board.removePieceAt(10), Board.empty);
final board = Board.empty.setPieceAt(Square.c2, Piece.whiteKing);
expect(board.removePieceAt(Square.c2), Board.empty);
});
test('parse board fen', () {
+15 -15
View File
@@ -2,14 +2,14 @@
library;
import 'package:test/test.dart';
import 'dart:io';
import 'dart:io' as io;
import 'package:dartchess/dartchess.dart';
import 'perft_parser.dart';
void main() async {
group('Three Check', () {
final tests =
Parser().parse(File('test/resources/3check.perft').readAsStringSync());
final tests = Parser()
.parse(io.File('test/resources/3check.perft').readAsStringSync());
for (final perftTest in tests) {
final position = ThreeCheck.fromSetup(Setup.parseFen(perftTest.fen));
for (final testCase in perftTest.cases) {
@@ -24,7 +24,7 @@ void main() async {
group('Antichess', () {
final tests = Parser()
.parse(File('test/resources/antichess.perft').readAsStringSync());
.parse(io.File('test/resources/antichess.perft').readAsStringSync());
for (final perftTest in tests) {
final position = Antichess.fromSetup(Setup.parseFen(perftTest.fen));
for (final testCase in perftTest.cases) {
@@ -38,8 +38,8 @@ void main() async {
});
group('Atomic', () {
final tests =
Parser().parse(File('test/resources/atomic.perft').readAsStringSync());
final tests = Parser()
.parse(io.File('test/resources/atomic.perft').readAsStringSync());
for (final perftTest in tests) {
final position = Atomic.fromSetup(Setup.parseFen(perftTest.fen));
for (final testCase in perftTest.cases) {
@@ -54,7 +54,7 @@ void main() async {
group('Crazyhouse', () {
final tests = Parser()
.parse(File('test/resources/crazyhouse.perft').readAsStringSync());
.parse(io.File('test/resources/crazyhouse.perft').readAsStringSync());
for (final perftTest in tests) {
final position = Crazyhouse.fromSetup(Setup.parseFen(perftTest.fen));
for (final testCase in perftTest.cases) {
@@ -68,8 +68,8 @@ void main() async {
});
group('Horde', () {
final tests =
Parser().parse(File('test/resources/horde.perft').readAsStringSync());
final tests = Parser()
.parse(io.File('test/resources/horde.perft').readAsStringSync());
for (final perftTest in tests) {
final position = Horde.fromSetup(Setup.parseFen(perftTest.fen));
for (final testCase in perftTest.cases) {
@@ -84,7 +84,7 @@ void main() async {
group('Racing Kings', () {
final tests = Parser()
.parse(File('test/resources/racingkings.perft').readAsStringSync());
.parse(io.File('test/resources/racingkings.perft').readAsStringSync());
for (final perftTest in tests) {
final position = RacingKings.fromSetup(Setup.parseFen(perftTest.fen));
for (final testCase in perftTest.cases) {
@@ -98,8 +98,8 @@ void main() async {
});
group('Chess Tricky', () {
final tests =
Parser().parse(File('test/resources/tricky.perft').readAsStringSync());
final tests = Parser()
.parse(io.File('test/resources/tricky.perft').readAsStringSync());
for (final perftTest in tests) {
for (final testCase in perftTest.cases) {
test('${perftTest.id} ${perftTest.fen} ${testCase.depth}', () {
@@ -115,8 +115,8 @@ void main() async {
});
group('Random', () {
final tests =
Parser().parse(File('test/resources/random.perft').readAsStringSync());
final tests = Parser()
.parse(io.File('test/resources/random.perft').readAsStringSync());
// only test 25 position in random. Test file has around 6000 positions
for (final perftTest in tests.take(25)) {
final position = Chess.fromSetup(Setup.parseFen(perftTest.fen));
@@ -132,7 +132,7 @@ void main() async {
group('Chess 960', () {
final tests = Parser()
.parse(File('test/resources/chess960.perft').readAsStringSync());
.parse(io.File('test/resources/chess960.perft').readAsStringSync());
for (final perftTest in tests) {
final position = Chess.fromSetup(Setup.parseFen(perftTest.fen));
for (final testCase in perftTest.cases) {
+108 -8
View File
@@ -2,18 +2,118 @@ import 'package:dartchess/dartchess.dart';
import 'package:test/test.dart';
void main() {
group('File', () {
test('File.values', () {
expect(File.values.length, 8);
});
test('fromName', () {
expect(File.fromName('a'), File.a);
expect(File.fromName('h'), File.h);
expect(() => File.fromName('i'), throwsFormatException);
});
test('offset', () {
expect(File.a.offset(1), File.b);
expect(File.h.offset(-1), File.g);
expect(File.h.offset(1), null);
});
});
group('Rank', () {
test('Rank.values', () {
expect(Rank.values.length, 8);
});
test('fromName', () {
expect(Rank.fromName('1'), Rank.first);
expect(Rank.fromName('8'), Rank.eighth);
expect(() => Rank.fromName('9'), throwsFormatException);
});
test('offset', () {
expect(Rank.first.offset(1), Rank.second);
expect(Rank.eighth.offset(-1), Rank.seventh);
expect(Rank.eighth.offset(1), null);
});
});
group('Square', () {
test('Square.values', () {
expect(Square.values.length, 64);
});
test('fromName', () {
expect(Square.fromName('a1'), Square.a1);
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('fromCoords', () {
expect(Square.fromCoords(const File(0), const Rank(0)), Square.a1);
expect(Square.fromCoords(const File(2), const Rank(5)), Square.c6);
expect(Square.fromCoords(const File(7), const Rank(7)), Square.h8);
});
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', () {
expect(Square.a1.offset(8), Square.a2);
expect(Square.h8.offset(-8), Square.h7);
expect(Square.h8.offset(1), null);
});
test('name', () {
expect(Square.a1.name, 'a1');
expect(Square.h8.name, 'h8');
});
});
group('Move', () {
test('fromUci', () {
expect(Move.fromUci('a1a2'), const NormalMove(from: 0, to: 8));
expect(Move.fromUci('h7h8q'),
const NormalMove(from: 55, to: 63, promotion: Role.queen));
expect(Move.fromUci('P@h1'), const DropMove(role: Role.pawn, to: 7));
test('parse', () {
expect(
Move.parse('a1a2'), const NormalMove(from: Square.a1, to: Square.a2));
expect(
Move.parse('h7h8q'),
const NormalMove(
from: Square.h7, to: Square.h8, promotion: Role.queen));
expect(
Move.parse('P@h1'), const DropMove(role: Role.pawn, to: Square.h1));
});
test('NormalMove.fromUci', () {
expect(NormalMove.fromUci('a1a2'),
const NormalMove(from: Square.a1, to: Square.a2));
expect(
NormalMove.fromUci('h7h8q'),
const NormalMove(
from: Square.h7, to: Square.h8, promotion: Role.queen));
expect(() => NormalMove.fromUci('P@h1'), throwsFormatException);
});
test('DropMove.fromUci', () {
expect(DropMove.fromUci('P@h1'),
const DropMove(role: Role.pawn, to: Square.h1));
expect(() => DropMove.fromUci('a1a2'), throwsFormatException);
});
test('uci', () {
expect(const DropMove(role: Role.queen, to: 1).uci, 'Q@b1');
expect(const NormalMove(from: 2, to: 3).uci, 'c1d1');
expect(const NormalMove(from: 0, to: 0, promotion: Role.knight).uci,
expect(const DropMove(role: Role.queen, to: Square.b1).uci, 'Q@b1');
expect(const NormalMove(from: Square.c1, to: Square.d1).uci, 'c1d1');
expect(
const NormalMove(
from: Square.a1, to: Square.a1, promotion: Role.knight)
.uci,
'a1a1n');
});
});
+12 -11
View File
@@ -1,5 +1,5 @@
import 'package:test/test.dart';
import 'dart:io';
import 'dart:io' as io;
import 'package:dartchess/dartchess.dart';
import 'perft_parser.dart';
@@ -18,7 +18,7 @@ void main() async {
group('tricky', () {
final tests = Parser()
.parse(File('test/resources/tricky.perft').readAsStringSync());
.parse(io.File('test/resources/tricky.perft').readAsStringSync());
for (final perftTest in tests) {
for (final testCase in perftTest.cases
.takeWhile((testCase) => testCase.nodes < nodeLimit)) {
@@ -34,7 +34,8 @@ void main() async {
group('impossible checker', () {
final tests = Parser().parse(
File('test/resources/impossible-checker.perft').readAsStringSync());
io.File('test/resources/impossible-checker.perft')
.readAsStringSync());
for (final perftTest in tests) {
for (final testCase in perftTest.cases
.takeWhile((testCase) => testCase.nodes < nodeLimit)) {
@@ -51,7 +52,7 @@ void main() async {
group('random', () {
final tests = Parser()
.parse(File('test/resources/random.perft').readAsStringSync());
.parse(io.File('test/resources/random.perft').readAsStringSync());
// only test 10 position in random. Test file has around 6000 positions
for (final perftTest in tests.take(50)) {
final position = Chess.fromSetup(Setup.parseFen(perftTest.fen));
@@ -68,8 +69,8 @@ void main() async {
});
group('Three Check', () {
final tests =
Parser().parse(File('test/resources/3check.perft').readAsStringSync());
final tests = Parser()
.parse(io.File('test/resources/3check.perft').readAsStringSync());
for (final perftTest in tests) {
final position = ThreeCheck.fromSetup(Setup.parseFen(perftTest.fen));
for (final testCase in perftTest.cases
@@ -85,7 +86,7 @@ void main() async {
group('Antichess', () {
final tests = Parser()
.parse(File('test/resources/antichess.perft').readAsStringSync());
.parse(io.File('test/resources/antichess.perft').readAsStringSync());
for (final perftTest in tests) {
final position = Antichess.fromSetup(Setup.parseFen(perftTest.fen));
for (final testCase in perftTest.cases
@@ -133,8 +134,8 @@ void main() async {
});
*/
group('Horde', () {
final tests =
Parser().parse(File('test/resources/horde.perft').readAsStringSync());
final tests = Parser()
.parse(io.File('test/resources/horde.perft').readAsStringSync());
for (final perftTest in tests) {
final position = Horde.fromSetup(Setup.parseFen(perftTest.fen));
for (final testCase in perftTest.cases
@@ -150,7 +151,7 @@ void main() async {
group('Racing Kings', () {
final tests = Parser()
.parse(File('test/resources/racingkings.perft').readAsStringSync());
.parse(io.File('test/resources/racingkings.perft').readAsStringSync());
for (final perftTest in tests) {
final position = RacingKings.fromSetup(Setup.parseFen(perftTest.fen));
for (final testCase in perftTest.cases
@@ -166,7 +167,7 @@ void main() async {
group('Chess 960', () {
final tests = Parser()
.parse(File('test/resources/chess960.perft').readAsStringSync());
.parse(io.File('test/resources/chess960.perft').readAsStringSync());
for (final perftTest in tests.take(50)) {
final position = Chess.fromSetup(Setup.parseFen(perftTest.fen));
for (final testCase in perftTest.cases
+30 -9
View File
@@ -1,4 +1,4 @@
import 'package:dartchess/dartchess.dart';
import 'package:dartchess/dartchess.dart' hide File;
import 'package:test/test.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'dart:io';
@@ -113,11 +113,21 @@ void main() {
text: 'commentary',
shapes: IListConst([
PgnCommentShape(
color: CommentShapeColor.yellow, from: 0, to: 0),
PgnCommentShape(color: CommentShapeColor.red, from: 0, to: 0),
PgnCommentShape(color: CommentShapeColor.blue, from: 4, to: 12),
color: CommentShapeColor.yellow,
from: Square.a1,
to: Square.a1),
PgnCommentShape(
color: CommentShapeColor.green, from: 63, to: 63)
color: CommentShapeColor.red,
from: Square.a1,
to: Square.a1),
PgnCommentShape(
color: CommentShapeColor.blue,
from: Square.e1,
to: Square.e2),
PgnCommentShape(
color: CommentShapeColor.green,
from: Square.h8,
to: Square.h8)
])));
expect(
@@ -138,7 +148,10 @@ void main() {
const PgnComment(
text: 'foo',
shapes: IListConst([
PgnCommentShape(color: CommentShapeColor.green, from: 0, to: 0)
PgnCommentShape(
color: CommentShapeColor.green,
from: Square.a1,
to: Square.a1)
])));
expect(
@@ -158,9 +171,17 @@ void main() {
clock: Duration(seconds: 1),
shapes: IListConst([
PgnCommentShape(
color: CommentShapeColor.yellow, from: 0, to: 0),
PgnCommentShape(color: CommentShapeColor.red, from: 0, to: 1),
PgnCommentShape(color: CommentShapeColor.red, from: 0, to: 2)
color: CommentShapeColor.yellow,
from: Square.a1,
to: Square.a1),
PgnCommentShape(
color: CommentShapeColor.red,
from: Square.a1,
to: Square.b1),
PgnCommentShape(
color: CommentShapeColor.red,
from: Square.a1,
to: Square.c1)
])).makeComment(),
'text [%csl Ya1] [%cal Ra1b1,Ra1c1] [%eval 10.00] [%emt 1:02:03.4] [%clk 0:00:01]');
+132 -103
View File
@@ -7,7 +7,8 @@ void main() {
test('implements hashCode/==', () {
expect(Chess.initial, Chess.initial);
expect(Chess.initial, isNot(Antichess.initial));
expect(Chess.initial, isNot(Chess.initial.play(Move.fromUci('e2e4')!)));
expect(
Chess.initial, isNot(Chess.initial.play(NormalMove.fromUci('e2e4'))));
});
test('Chess.toString()', () {
@@ -22,18 +23,22 @@ void main() {
test('ply', () {
expect(Chess.initial.ply, 0);
expect(Chess.initial.play(const NormalMove(from: 12, to: 28)).ply, 1);
expect(
Chess.initial
.play(const NormalMove(from: 12, to: 28))
.play(const NormalMove(from: 52, to: 36))
.play(const NormalMove(from: Square.e2, to: Square.e4))
.ply,
1);
expect(
Chess.initial
.play(const NormalMove(from: Square.e2, to: Square.e4))
.play(const NormalMove(from: Square.e7, to: Square.e5))
.ply,
2);
expect(
Chess.initial
.play(const NormalMove(from: 12, to: 28))
.play(const NormalMove(from: 52, to: 36))
.play(const NormalMove(from: 5, to: 26))
.play(const NormalMove(from: Square.e2, to: Square.e4))
.play(const NormalMove(from: Square.e7, to: Square.e5))
.play(const NormalMove(from: Square.f1, to: Square.c4))
.ply,
3);
});
@@ -57,27 +62,26 @@ void main() {
expect(castles.unmovedRooks, SquareSet.corners);
expect(castles, Castles.standard);
expect(castles.rookOf(Side.white, CastlingSide.queen), 0);
expect(castles.rookOf(Side.white, CastlingSide.king), 7);
expect(castles.rookOf(Side.black, CastlingSide.queen), 56);
expect(castles.rookOf(Side.black, CastlingSide.king), 63);
expect(castles.rookOf(Side.white, CastlingSide.queen), Square.a1);
expect(castles.rookOf(Side.white, CastlingSide.king), Square.h1);
expect(castles.rookOf(Side.black, CastlingSide.queen), Square.a8);
expect(castles.rookOf(Side.black, CastlingSide.king), Square.h8);
expect(castles.pathOf(Side.white, CastlingSide.queen).squares,
equals([1, 2, 3]));
equals([Square.b1, Square.c1, Square.d1]));
expect(castles.pathOf(Side.white, CastlingSide.king).squares,
equals([5, 6]));
equals([Square.f1, Square.g1]));
expect(castles.pathOf(Side.black, CastlingSide.queen).squares,
equals([57, 58, 59]));
equals([Square.b8, Square.c8, Square.d8]));
expect(castles.pathOf(Side.black, CastlingSide.king).squares,
equals([61, 62]));
equals([Square.f8, Square.g8]));
});
test('discard rook', () {
expect(Castles.standard.discardRookAt(24), Castles.standard);
expect(Castles.standard.discardRookAt(Square.a4), Castles.standard);
expect(
Castles.standard.discardRookAt(7).rooksPositions[Side.white],
IMap(
const {CastlingSide.queen: Squares.a1, CastlingSide.king: null}));
Castles.standard.discardRookAt(Square.h1).rooksPositions[Side.white],
IMap(const {CastlingSide.queen: Square.a1, CastlingSide.king: null}));
});
test('discard side', () {
@@ -88,15 +92,20 @@ void main() {
const {CastlingSide.queen: null, CastlingSide.king: null},
),
Side.black: ByCastlingSide(
const {CastlingSide.queen: 56, CastlingSide.king: 63},
const {
CastlingSide.queen: Square.a8,
CastlingSide.king: Square.h8,
},
)
})));
expect(
Castles.standard.discardSide(Side.black).rooksPositions,
equals(BySide({
Side.white: ByCastlingSide(
const {CastlingSide.queen: 0, CastlingSide.king: 7}),
Side.white: ByCastlingSide(const {
CastlingSide.queen: Square.a1,
CastlingSide.king: Square.h1,
}),
Side.black: ByCastlingSide(
const {CastlingSide.queen: null, CastlingSide.king: null})
})));
@@ -107,7 +116,7 @@ void main() {
test('makeSan en passant', () {
final setup = Setup.parseFen('6bk/7b/8/3pP3/8/8/8/Q3K3 w - d6 0 2');
final pos = Chess.fromSetup(setup);
final move = Move.fromUci('e5d6')!;
final move = NormalMove.fromUci('e5d6');
final (newPos, san) = pos.makeSan(move);
expect(san, 'exd6#');
expect(newPos.fen, '6bk/7b/3P4/8/8/8/8/Q3K3 b - - 0 2');
@@ -115,13 +124,13 @@ void main() {
test('makeSan with scholar mate', () {
const moves = [
NormalMove(from: 12, to: 28),
NormalMove(from: 52, to: 36),
NormalMove(from: 5, to: 26),
NormalMove(from: 57, to: 42),
NormalMove(from: 3, to: 21),
NormalMove(from: 51, to: 43),
NormalMove(from: 21, to: 53),
NormalMove(from: Square.e2, to: Square.e4),
NormalMove(from: Square.e7, to: Square.e5),
NormalMove(from: Square.f1, to: Square.c4),
NormalMove(from: Square.b8, to: Square.c6),
NormalMove(from: Square.d1, to: Square.f3),
NormalMove(from: Square.d7, to: Square.d6),
NormalMove(from: Square.f3, to: Square.f7),
];
final (_, sans) = moves
.fold<(Position<Chess>, List<String>)>((Chess.initial, []), (acc, e) {
@@ -134,10 +143,10 @@ void main() {
test('parse basic san', () {
const position = Chess.initial;
expect(
position.parseSan('e4'), equals(const NormalMove(from: 12, to: 28)));
expect(
position.parseSan('Nf3'), equals(const NormalMove(from: 6, to: 21)));
expect(position.parseSan('e4'),
equals(const NormalMove(from: Square.e2, to: Square.e4)));
expect(position.parseSan('Nf3'),
equals(const NormalMove(from: Square.g1, to: Square.f3)));
expect(position.parseSan('Nf6'), null);
expect(position.parseSan('Ke2'), null);
expect(position.parseSan('O-O'), null);
@@ -155,7 +164,8 @@ void main() {
final pos2 = Chess.fromSetup(
Setup.parseFen('r4br1/pp1Npkp1/2P4p/5P2/6P1/5KnP/PP6/R1B5 b - -'));
expect(pos2.parseSan('bxc6'), equals(const NormalMove(from: 49, to: 42)));
expect(pos2.parseSan('bxc6'),
equals(const NormalMove(from: Square.b7, to: Square.c6)));
final pos3 = Chess.fromSetup(Setup.parseFen(
'2rq1rk1/pb2bppp/1p2p3/n1ppPn2/2PP4/PP3N2/1B1NQPPP/RB3RK1 b - -'));
@@ -265,8 +275,8 @@ void main() {
test('overspecified pawn move', () {
const position = Chess.initial;
expect(
position.parseSan('2e4'), equals(const NormalMove(from: 12, to: 28)));
expect(position.parseSan('2e4'),
equals(const NormalMove(from: Square.e2, to: Square.e4)));
});
test('chess960 parseSan castle moves', () {
@@ -375,22 +385,22 @@ void main() {
test('standard position legal moves', () {
final moves = IMap({
0: SquareSet.empty,
1: const SquareSet.fromSquare(16).withSquare(18),
2: SquareSet.empty,
3: SquareSet.empty,
4: SquareSet.empty,
5: SquareSet.empty,
6: const SquareSet.fromSquare(21).withSquare(23),
7: SquareSet.empty,
8: const SquareSet.fromSquare(16).withSquare(24),
9: const SquareSet.fromSquare(17).withSquare(25),
10: const SquareSet.fromSquare(18).withSquare(26),
11: const SquareSet.fromSquare(19).withSquare(27),
12: const SquareSet.fromSquare(20).withSquare(28),
13: const SquareSet.fromSquare(21).withSquare(29),
14: const SquareSet.fromSquare(22).withSquare(30),
15: const SquareSet.fromSquare(23).withSquare(31),
Square.a1: SquareSet.empty,
Square.b1: const SquareSet.fromSquare(Square.a3).withSquare(Square.c3),
Square.c1: SquareSet.empty,
Square.d1: SquareSet.empty,
Square.e1: SquareSet.empty,
Square.f1: SquareSet.empty,
Square.g1: const SquareSet.fromSquare(Square.f3).withSquare(Square.h3),
Square.h1: SquareSet.empty,
Square.a2: const SquareSet.fromSquare(Square.a3).withSquare(Square.a4),
Square.b2: const SquareSet.fromSquare(Square.b3).withSquare(Square.b4),
Square.c2: const SquareSet.fromSquare(Square.c3).withSquare(Square.c4),
Square.d2: const SquareSet.fromSquare(Square.d3).withSquare(Square.d4),
Square.e2: const SquareSet.fromSquare(Square.e3).withSquare(Square.e4),
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));
});
@@ -408,7 +418,7 @@ void main() {
test('castling legal moves', () {
final pos = Chess.fromSetup(Setup.parseFen(
'r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1'));
expect(pos.legalMovesOf(4), const SquareSet(0x00000000000000A9));
expect(pos.legalMovesOf(Square.e1), const SquareSet(0x00000000000000A9));
});
test('isCheck', () {
@@ -466,50 +476,59 @@ void main() {
});
test('isLegal', () {
expect(Chess.initial.isLegal(const NormalMove(from: 12, to: 28)), true);
expect(Chess.initial.isLegal(const NormalMove(from: 12, to: 29)), false);
expect(
Chess.initial
.isLegal(const NormalMove(from: Square.e2, to: Square.e4)),
true);
expect(
Chess.initial
.isLegal(const NormalMove(from: Square.e4, to: Square.f4)),
false);
final promPos = Chess.fromSetup(
Setup.parseFen('8/5P2/2RK2P1/8/4k3/8/8/7r w - - 0 1'));
expect(
promPos.isLegal(
const NormalMove(from: 53, to: 61, promotion: Role.king)),
promPos.isLegal(const NormalMove(
from: Square.f7, to: Square.f8, promotion: Role.king)),
false);
expect(
promPos.isLegal(
const NormalMove(from: 42, to: 58, promotion: Role.queen)),
promPos.isLegal(const NormalMove(
from: Square.c6, to: Square.c8, promotion: Role.queen)),
false);
expect(
promPos.isLegal(
const NormalMove(from: 46, to: 54, promotion: Role.queen)),
promPos.isLegal(const NormalMove(
from: Square.g6, to: Square.g7, promotion: Role.queen)),
false);
expect(
promPos.isLegal(
const NormalMove(from: 53, to: 61, promotion: Role.queen)),
promPos.isLegal(const NormalMove(
from: Square.f7, to: Square.f8, promotion: Role.queen)),
true);
});
group('play', () {
test('a move not valid', () {
expect(() => Chess.initial.play(const NormalMove(from: 12, to: 44)),
expect(
() => Chess.initial
.play(const NormalMove(from: Square.e4, to: Square.e6)),
throwsA(const TypeMatcher<PlayError>()));
});
test('e2 e4 on standard position', () {
final pos = Chess.initial.play(const NormalMove(from: 12, to: 28));
expect(pos.board.pieceAt(28), Piece.whitePawn);
expect(pos.board.pieceAt(12), null);
final pos = Chess.initial
.play(const NormalMove(from: Square.e2, to: Square.e4));
expect(pos.board.pieceAt(Square.e4), Piece.whitePawn);
expect(pos.board.pieceAt(Square.e2), null);
expect(pos.turn, Side.black);
});
test('scholar mate', () {
final pos = Chess.initial
.play(const NormalMove(from: 12, to: 28))
.play(const NormalMove(from: 52, to: 36))
.play(const NormalMove(from: 5, to: 26))
.play(const NormalMove(from: 57, to: 42))
.play(const NormalMove(from: 3, to: 21))
.play(const NormalMove(from: 51, to: 43))
.play(const NormalMove(from: 21, to: 53));
.play(const NormalMove(from: Square.e2, to: Square.e4))
.play(const NormalMove(from: Square.e7, to: Square.e5))
.play(const NormalMove(from: Square.f1, to: Square.c4))
.play(const NormalMove(from: Square.b8, to: Square.c6))
.play(const NormalMove(from: Square.d1, to: Square.f3))
.play(const NormalMove(from: Square.d7, to: Square.d6))
.play(const NormalMove(from: Square.f3, to: Square.f7));
expect(pos.isCheckmate, true);
expect(pos.turn, Side.black);
@@ -521,68 +540,77 @@ void main() {
test('halfmoves increment', () {
// pawn move
expect(Chess.initial.play(const NormalMove(from: 12, to: 28)).halfmoves,
expect(
Chess.initial
.play(const NormalMove(from: Square.e2, to: Square.e4))
.halfmoves,
0);
// piece move
final pos = Chess.fromSetup(Setup.parseFen(
'r2qr2k/5Qpp/2R1nn2/3p4/3P4/1B3P2/PB4PP/4R1K1 b - - 0 29'))
.play(const NormalMove(from: 44, to: 38));
.play(const NormalMove(from: Square.e6, to: Square.g5));
expect(pos.halfmoves, 1);
// capture move
final pos2 = Chess.fromSetup(Setup.parseFen(
'r2qr2k/5Qpp/2R2n2/3p2n1/3P4/1B3P2/PB4PP/4R1K1 w - - 1 30'))
.play(const NormalMove(from: 17, to: 35));
.play(const NormalMove(from: Square.b3, to: Square.d5));
expect(pos2.halfmoves, 0);
});
test('fullmoves increment', () {
final pos = Chess.initial.play(const NormalMove(from: 12, to: 28));
final pos = Chess.initial
.play(const NormalMove(from: Square.e2, to: Square.e4));
expect(pos.fullmoves, 1);
expect(pos.play(const NormalMove(from: 52, to: 36)).fullmoves, 2);
expect(
pos
.play(const NormalMove(from: Square.e7, to: Square.e5))
.fullmoves,
2);
});
test('epSquare is correctly set after a double push move', () {
final pos = Chess.initial.play(const NormalMove(from: 12, to: 28));
expect(pos.epSquare, 20);
final pos = Chess.initial
.play(const NormalMove(from: Square.e2, to: Square.e4));
expect(pos.epSquare, Square.e3);
});
test('en passant capture', () {
final pos = Chess.fromSetup(Setup.parseFen(
'r1bqkbnr/ppppp1pp/2n5/4Pp2/8/8/PPPP1PPP/RNBQKBNR w KQkq f6 0 3'))
.play(const NormalMove(from: 36, to: 45));
expect(pos.board.pieceAt(45), Piece.whitePawn);
expect(pos.board.pieceAt(37), null);
.play(const NormalMove(from: Square.e5, to: Square.f6));
expect(pos.board.pieceAt(Square.f6), Piece.whitePawn);
expect(pos.board.pieceAt(Square.f5), null);
expect(pos.epSquare, null);
});
test('rook move removes castling right', () {
final pos = Chess.fromSetup(Setup.parseFen(
'r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 4 4'))
.play(const NormalMove(from: 7, to: 5));
.play(const NormalMove(from: Square.h1, to: Square.f1));
expect(
pos.castles.rooksPositions[Side.white],
equals(IMap(const {
CastlingSide.queen: Squares.a1,
CastlingSide.queen: Square.a1,
CastlingSide.king: null
})));
expect(pos.castles.unmovedRooks.has(7), false);
expect(pos.castles.unmovedRooks.has(Square.h1), false);
});
test('capturing a rook removes castling right', () {
final pos = Chess.fromSetup(Setup.parseFen(
'r1bqk1nr/pppp1pbp/2n1p1p1/8/2B1P3/1P3N2/P1PP1PPP/RNBQK2R b KQkq - 4 4'))
.play(const NormalMove(from: 54, to: 0));
.play(const NormalMove(from: Square.g7, to: Square.a1));
expect(pos.castles.rookOf(Side.white, CastlingSide.queen), isNull);
expect(pos.castles.rookOf(Side.white, CastlingSide.king), Squares.h1);
expect(pos.castles.unmovedRooks.has(0), false);
expect(pos.castles.rookOf(Side.white, CastlingSide.king), Square.h1);
expect(pos.castles.unmovedRooks.has(Square.a1), false);
});
test('king captures unmoved rook', () {
final pos = Chess.fromSetup(
Setup.parseFen('8/8/8/B2p3Q/2qPp1P1/b7/2P2PkP/4K2R b K - 0 1'));
const move = NormalMove(from: 14, to: 7);
const move = NormalMove(from: Square.g2, to: Square.h1);
expect(pos.isLegal(move), true);
final pos2 = pos.play(move);
expect(pos2.fen, '8/8/8/B2p3Q/2qPp1P1/b7/2P2P1P/4K2k w - - 0 2');
@@ -597,18 +625,19 @@ void main() {
e is PositionError &&
e.cause == IllegalSetup.impossibleCheck)));
final pos = Chess.fromSetup(setup, ignoreImpossibleCheck: true);
const enPassant = NormalMove(from: 35, to: 42);
const enPassant = NormalMove(from: Square.d5, to: Square.c6);
expect(pos.isLegal(enPassant), false);
});
test('castling move', () {
final pos = Chess.fromSetup(Setup.parseFen(
'r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 4 4'))
.play(const NormalMove(from: 4, to: 6));
expect(pos.board.pieceAt(6), Piece.whiteKing);
expect(pos.board.pieceAt(5), Piece.whiteRook);
.play(const NormalMove(from: Square.e1, to: Square.g1));
expect(pos.board.pieceAt(Square.g1), Piece.whiteKing);
expect(pos.board.pieceAt(Square.f1), Piece.whiteRook);
expect(
pos.castles.unmovedRooks.isIntersected(const SquareSet.fromRank(0)),
pos.castles.unmovedRooks
.isIntersected(const SquareSet.fromRank(Rank.first)),
false);
expect(pos.castles.rookOf(Side.white, CastlingSide.king), isNull);
expect(pos.castles.rookOf(Side.white, CastlingSide.queen), isNull);
@@ -617,19 +646,19 @@ void main() {
test('castling moves', () {
final pos =
Chess.fromSetup(Setup.parseFen('2r5/8/8/8/8/8/6PP/k2KR3 w K -'));
const move = NormalMove(from: 3, to: 4);
const move = NormalMove(from: Square.d1, to: Square.e1);
expect(pos.play(move).fen, '2r5/8/8/8/8/8/6PP/k4RK1 b - - 1 1');
final pos2 = Chess.fromSetup(Setup.parseFen(
'r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1'));
const move2 = NormalMove(from: 4, to: 0);
const move2 = NormalMove(from: Square.e1, to: Square.a1);
expect(pos2.play(move2).fen,
'r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/2KR3R b kq - 1 1');
final pos3 = Chess.fromSetup(Setup.parseFen(
'1r2k2r/p1b1n1pp/1q3p2/1p2pPQ1/4P3/2P4P/1B2B1P1/R3K2R w KQk - 0 20'));
const queenSide = NormalMove(from: 4, to: 0);
const altQueenSide = NormalMove(from: 4, to: 2);
const queenSide = NormalMove(from: Square.e1, to: Square.a1);
const altQueenSide = NormalMove(from: Square.e1, to: Square.c1);
expect(pos3.normalizeMove(queenSide), queenSide);
expect(pos3.normalizeMove(altQueenSide), queenSide);
expect(pos3.play(altQueenSide).fen,
@@ -697,7 +726,7 @@ void main() {
final setup = Setup.parseFen(
'r1bqkbn1/p1ppp3/2n4p/6p1/1Pp5/4P3/P2P1PP1/R1B1K3 b - b3 0 11');
final pos = Antichess.fromSetup(setup);
final move = Move.fromUci('c4b3')!;
final move = NormalMove.fromUci('c4b3');
expect(pos.isLegal(move), isTrue);
final sanMove = pos.parseSan('cxb3');
@@ -1000,7 +1029,7 @@ void main() {
test('remaining checks', () {
final pos = ThreeCheck.fromSetup(Setup.parseFen(
'rnbqkbnr/ppp1pppp/3p4/8/8/4P3/PPPP1PPP/RNBQKBNR w KQkq - 3+3 0 2'));
expect(pos.play(Move.fromUci('f1b5')!).fen,
expect(pos.play(NormalMove.fromUci('f1b5')).fen,
'rnbqkbnr/ppp1pppp/3p4/1B6/8/4P3/PPPP1PPP/RNBQK1NR b KQkq - 2+3 1 2');
});
});
+1 -1
View File
@@ -44,7 +44,7 @@ void main() {
Setup.parseFen(
'r1bqkbnr/ppppp1pp/2n5/4Pp2/8/8/PPPP1PPP/RNBQKBNR w KQkq f6')
.epSquare,
45);
Square.f6);
});
test('parse initial fen', () {
+164 -116
View File
@@ -2,117 +2,156 @@ import 'package:dartchess/dartchess.dart';
import 'package:test/test.dart';
void main() {
test('toHexString', () {
expect(SquareSet.empty.toHexString(), '0');
expect(SquareSet.full.toHexString(), '0xFFFFFFFFFFFFFFFF');
expect(SquareSet.lightSquares.toHexString(), '0x55AA55AA55AA55AA');
expect(SquareSet.darkSquares.toHexString(), '0xAA55AA55AA55AA55');
expect(SquareSet.diagonal.toHexString(), '0x8040201008040201');
expect(SquareSet.antidiagonal.toHexString(), '0x0102040810204080');
expect(SquareSet.corners.toHexString(), '0x8100000000000081');
expect(SquareSet.backranks.toHexString(), '0xFF000000000000FF');
expect(const SquareSet(0x0000000000000001).toHexString(),
'0x0000000000000001');
expect(const SquareSet(0xf).toHexString(), '0x000000000000000F');
});
group('SquareSet', () {
test('toHexString', () {
expect(SquareSet.empty.toHexString(), '0');
expect(SquareSet.full.toHexString(), '0xFFFFFFFFFFFFFFFF');
expect(SquareSet.lightSquares.toHexString(), '0x55AA55AA55AA55AA');
expect(SquareSet.darkSquares.toHexString(), '0xAA55AA55AA55AA55');
expect(SquareSet.diagonal.toHexString(), '0x8040201008040201');
expect(SquareSet.antidiagonal.toHexString(), '0x0102040810204080');
expect(SquareSet.corners.toHexString(), '0x8100000000000081');
expect(SquareSet.backranks.toHexString(), '0xFF000000000000FF');
expect(const SquareSet(0x0000000000000001).toHexString(),
'0x0000000000000001');
expect(const SquareSet(0xf).toHexString(), '0x000000000000000F');
});
test('full set has all', () {
for (Square square = 0; square < 64; square++) {
expect(SquareSet.full.has(square), true);
}
});
test('full set has all', () {
for (int i = 0; i < 64; i++) {
expect(SquareSet.full.has(Square(i)), true);
}
});
test('size', () {
SquareSet squares = SquareSet.empty;
for (int i = 0; i < 64; i++) {
expect(squares.size, i);
squares = squares.withSquare(i);
}
});
test('size', () {
SquareSet squares = SquareSet.empty;
for (int i = 0; i < 64; i++) {
expect(squares.size, i);
squares = squares.withSquare(Square(i));
}
});
test('shr', () {
const r = SquareSet(0xe0a12221e222212);
expect(r.shr(0), r);
expect(r.shr(1), const SquareSet(0x70509110f111109));
expect(r.shr(3), const SquareSet(0x1c1424443c44442));
expect(r.shr(48), const SquareSet(0xe0a));
expect(r.shr(62), SquareSet.empty);
});
test('shr', () {
const r = SquareSet(0xe0a12221e222212);
expect(r.shr(0), r);
expect(r.shr(1), const SquareSet(0x70509110f111109));
expect(r.shr(3), const SquareSet(0x1c1424443c44442));
expect(r.shr(48), const SquareSet(0xe0a));
expect(r.shr(62), SquareSet.empty);
});
test('shl', () {
const r = SquareSet(0xe0a12221e222212);
expect(r.shl(0), r);
expect(r.shl(1), const SquareSet(0x1c1424443c444424));
expect(r.shl(3), const SquareSet(0x70509110f1111090));
expect(r.shl(10), const SquareSet(0x2848887888884800));
expect(r.shl(32), const SquareSet(0x1e22221200000000));
expect(r.shl(48), const SquareSet(0x2212000000000000));
expect(r.shl(62), const SquareSet(0x8000000000000000));
expect(r.shl(63), SquareSet.empty);
});
test('shl', () {
const r = SquareSet(0xe0a12221e222212);
expect(r.shl(0), r);
expect(r.shl(1), const SquareSet(0x1c1424443c444424));
expect(r.shl(3), const SquareSet(0x70509110f1111090));
expect(r.shl(10), const SquareSet(0x2848887888884800));
expect(r.shl(32), const SquareSet(0x1e22221200000000));
expect(r.shl(48), const SquareSet(0x2212000000000000));
expect(r.shl(62), const SquareSet(0x8000000000000000));
expect(r.shl(63), SquareSet.empty);
});
test('first', () {
for (Square square = 0; square < 64; square++) {
expect(SquareSet.fromSquare(square).first, square);
}
expect(SquareSet.full.first, 0);
expect(SquareSet.empty.first, null);
for (int rank = 0; rank < 8; rank++) {
expect(SquareSet.fromRank(rank).first, rank * 8);
}
});
test('first', () {
for (int i = 0; i < 64; i++) {
expect(SquareSet.fromSquare(Square(i)).first, Square(i));
}
expect(SquareSet.full.first, Square.a1);
expect(SquareSet.empty.first, null);
for (int rank = 0; rank < 8; rank++) {
expect(SquareSet.fromRank(Rank(rank)).first, Square(rank * 8));
}
});
test('last', () {
for (Square square = 0; square < 64; square++) {
expect(SquareSet.fromSquare(square).last, square);
}
expect(SquareSet.full.last, 63);
expect(SquareSet.empty.last, null);
for (int rank = 0; rank < 8; rank++) {
expect(SquareSet.fromRank(rank).last, rank * 8 + 7);
}
});
test('last', () {
for (int i = 0; i < 64; i++) {
expect(SquareSet.fromSquare(Square(i)).last, Square(i));
}
expect(SquareSet.full.last, Square.h8);
expect(SquareSet.empty.last, null);
for (int rank = 0; rank < 8; rank++) {
expect(SquareSet.fromRank(Rank(rank)).last, Square(rank * 8 + 7));
}
});
test('more that one', () {
expect(SquareSet.empty.moreThanOne, false);
expect(SquareSet.full.moreThanOne, true);
expect(const SquareSet.fromSquare(4).moreThanOne, false);
expect(const SquareSet.fromSquare(4).withSquare(5).moreThanOne, true);
});
test('more that one', () {
expect(SquareSet.empty.moreThanOne, false);
expect(SquareSet.full.moreThanOne, true);
expect(const SquareSet.fromSquare(Square.e1).moreThanOne, false);
expect(
const SquareSet.fromSquare(Square.e1)
.withSquare(Square.f1)
.moreThanOne,
true);
});
test('singleSquare', () {
expect(SquareSet.empty.singleSquare, null);
expect(SquareSet.full.singleSquare, null);
expect(const SquareSet.fromSquare(4).singleSquare, 4);
expect(const SquareSet.fromSquare(4).withSquare(5).singleSquare, null);
});
test('singleSquare', () {
expect(SquareSet.empty.singleSquare, null);
expect(SquareSet.full.singleSquare, null);
expect(const SquareSet.fromSquare(Square.e1).singleSquare, Square.e1);
expect(
const SquareSet.fromSquare(Square.e1)
.withSquare(Square.f1)
.singleSquare,
null);
});
test('squares', () {
expect(SquareSet.empty.squares.toList(), List<Square>.empty());
expect(SquareSet.full.squares.toList(), [for (int i = 0; i < 64; i++) i]);
expect(SquareSet.diagonal.squares, equals([0, 9, 18, 27, 36, 45, 54, 63]));
});
test('squares', () {
expect(SquareSet.empty.squares.toList(), List<Square>.empty());
expect(
SquareSet.full.squares.toList(),
[for (int i = 0; i < 64; i++) Square(i)],
);
expect(
SquareSet.diagonal.squares,
equals([
Square.a1,
Square.b2,
Square.c3,
Square.d4,
Square.e5,
Square.f6,
Square.g7,
Square.h8,
]));
});
test('squaresReversed', () {
expect(SquareSet.empty.squaresReversed.toList(), List<Square>.empty());
expect(SquareSet.full.squaresReversed.toList(),
[for (int i = 63; i >= 0; i--) i]);
expect(SquareSet.diagonal.squaresReversed,
equals([63, 54, 45, 36, 27, 18, 9, 0]));
});
test('squaresReversed', () {
expect(SquareSet.empty.squaresReversed.toList(), List<Square>.empty());
expect(
SquareSet.full.squaresReversed.toList(),
[for (int i = 63; i >= 0; i--) Square(i)],
);
expect(
SquareSet.diagonal.squaresReversed,
equals([
Square.h8,
Square.g7,
Square.f6,
Square.e5,
Square.d4,
Square.c3,
Square.b2,
Square.a1,
]));
});
test('from file', () {
expect(const SquareSet.fromFile(0), const SquareSet(0x0101010101010101));
expect(const SquareSet.fromFile(7), const SquareSet(0x8080808080808080));
});
test('from file', () {
expect(const SquareSet.fromFile(File(0)),
const SquareSet(0x0101010101010101));
expect(const SquareSet.fromFile(File(7)),
const SquareSet(0x8080808080808080));
});
test('from rank', () {
expect(const SquareSet.fromRank(0), const SquareSet(0x00000000000000FF));
expect(const SquareSet.fromRank(7), const SquareSet(0xFF00000000000000));
});
test('from rank', () {
expect(const SquareSet.fromRank(Rank(0)),
const SquareSet(0x00000000000000FF));
expect(const SquareSet.fromRank(Rank(7)),
const SquareSet(0xFF00000000000000));
});
test('from square', () {
expect(const SquareSet.fromSquare(42), makeSquareSet('''
test('from square', () {
expect(const SquareSet.fromSquare(Square.c6), makeSquareSet('''
. . . . . . . .
. . . . . . . .
. . 1 . . . . .
@@ -122,10 +161,17 @@ void main() {
. . . . . . . .
. . . . . . . .
'''));
});
test('from squares', () {
expect(SquareSet.fromSquares(const [42, 44, 26, 28]), makeSquareSet('''
expect(const SquareSet.fromSquare(Square.a1), const SquareSet(1));
expect(const SquareSet.fromSquare(Square.h8),
const SquareSet(0x8000000000000000));
});
test('from squares', () {
expect(
SquareSet.fromSquares(
const [Square.c6, Square.e6, Square.c4, Square.e4]),
makeSquareSet('''
. . . . . . . .
. . . . . . . .
. . 1 . 1 . . .
@@ -135,10 +181,10 @@ void main() {
. . . . . . . .
. . . . . . . .
'''));
});
});
test('with square', () {
expect(SquareSet.center.withSquare(43), makeSquareSet('''
test('with square', () {
expect(SquareSet.center.withSquare(Square.d6), makeSquareSet('''
. . . . . . . .
. . . . . . . .
. . . 1 . . . .
@@ -148,10 +194,10 @@ void main() {
. . . . . . . .
. . . . . . . .
'''));
});
});
test('without square', () {
expect(SquareSet.center.withoutSquare(27), makeSquareSet('''
test('without square', () {
expect(SquareSet.center.withoutSquare(Square.d4), makeSquareSet('''
. . . . . . . .
. . . . . . . .
. . . . . . . .
@@ -161,10 +207,11 @@ void main() {
. . . . . . . .
. . . . . . . .
'''));
});
});
test('toggle square', () {
expect(SquareSet.center.toggleSquare(35).toggleSquare(43), makeSquareSet('''
test('toggle square', () {
expect(SquareSet.center.toggleSquare(Square.d5).toggleSquare(Square.d6),
makeSquareSet('''
. . . . . . . .
. . . . . . . .
. . . 1 . . . .
@@ -174,10 +221,10 @@ void main() {
. . . . . . . .
. . . . . . . .
'''));
});
});
test('flip vertical', () {
expect(makeSquareSet('''
test('flip vertical', () {
expect(makeSquareSet('''
. 1 1 1 1 . . .
. 1 . . . 1 . .
. 1 . . . 1 . .
@@ -196,10 +243,10 @@ void main() {
. 1 . . . 1 . .
. 1 1 1 1 . . .
'''));
});
});
test('mirror horizontal', () {
expect(makeSquareSet('''
test('mirror horizontal', () {
expect(makeSquareSet('''
. 1 1 1 1 . . .
. 1 . . . 1 . .
. 1 . . . 1 . .
@@ -218,5 +265,6 @@ void main() {
. . . 1 . . 1 .
. . 1 . . . 1 .
'''));
});
});
}
-35
View File
@@ -1,35 +0,0 @@
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:dartchess/dartchess.dart';
import 'package:test/test.dart';
void main() {
test('algebraicLegalMoves 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 = algebraicLegalMoves(pos);
expect(moves['g7'], contains('h8'));
expect(moves['g7'], isNot(contains('g8')));
});
test('algebraicLegalMoves with regular castle', () {
final wtm =
Chess.fromSetup(Setup.parseFen('r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1'));
expect(algebraicLegalMoves(wtm)['e1'],
equals({'a1', 'c1', 'd1', 'd2', 'e2', 'f1', 'f2', 'g1', 'h1'}));
expect(algebraicLegalMoves(wtm)['e8'], null);
final btm =
Chess.fromSetup(Setup.parseFen('r3k2r/8/8/8/8/8/8/R3K2R b KQkq - 0 1'));
expect(algebraicLegalMoves(btm)['e8'],
equals({'a8', 'c8', 'd7', 'd8', 'e7', 'f7', 'f8', 'g8', 'h8'}));
expect(algebraicLegalMoves(btm)['e1'], null);
});
test('algebraicLegalMoves with chess960 castle', () {
final pos = Chess.fromSetup(Setup.parseFen(
'rk2r3/pppbnppp/3p2n1/P2Pp3/4P2q/R5NP/1PP2PP1/1KNQRB2 b Kkq - 0 1'));
expect(algebraicLegalMoves(pos, isChess960: true)['b8'],
equals(ISet(const {'a8', 'c8', 'e8'})));
});
}