mirror of
https://github.com/lichess-org/dartchess.git
synced 2026-05-26 13:51:01 +00:00
Merge pull request #41 from lichess-org/square_extension_type
Square extension type
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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
@@ -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
@@ -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++;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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>()));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
@@ -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 .
|
||||
'''));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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'})));
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user