From e4e4edeb148b1c74badb981ea9824a55e3e54f22 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Wed, 31 Jul 2024 10:31:17 +0200 Subject: [PATCH] Add more tests --- lib/src/attacks.dart | 20 ++++++++++---------- lib/src/models.dart | 29 +++++++++++++++++++++++++++++ lib/src/position.dart | 4 ++-- lib/src/square_set.dart | 10 +++++----- test/models_test.dart | 40 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 86 insertions(+), 17 deletions(-) diff --git a/lib/src/attacks.dart b/lib/src/attacks.dart index 6ec73dd..06a03c6 100644 --- a/lib/src/attacks.dart +++ b/lib/src/attacks.dart @@ -3,25 +3,25 @@ import './models.dart'; /// Gets squares attacked or defended by a king on [Square]. SquareSet kingAttacks(Square square) { - return _kingAttacks[square.value]; + return _kingAttacks[square]; } /// Gets squares attacked or defended by a knight on [Square]. SquareSet knightAttacks(Square square) { - return _knightAttacks[square.value]; + return _knightAttacks[square]; } /// Gets squares attacked or defended by a pawn of the given [Side] on [Square]. SquareSet pawnAttacks(Side side, Square square) { - return _pawnAttacks[side]![square.value]; + return _pawnAttacks[side]![square]; } /// Gets squares attacked or defended by a bishop on [Square], given `occupied` /// squares. SquareSet bishopAttacks(Square square, SquareSet occupied) { final bit = SquareSet.fromSquare(square); - return _hyperbola(bit, _diagRange[square.value], occupied) ^ - _hyperbola(bit, _antiDiagRange[square.value], occupied); + return _hyperbola(bit, _diagRange[square], occupied) ^ + _hyperbola(bit, _antiDiagRange[square], occupied); } /// Gets squares attacked or defended by a rook on [Square], given `occupied` @@ -84,7 +84,7 @@ SquareSet between(Square a, Square b) => ray(a, b) SquareSet _computeRange(Square square, List deltas) { SquareSet range = SquareSet.empty; for (final delta in deltas) { - final sq = square.value + delta; + final sq = square + delta; if (0 <= sq && sq < 64 && (square.file - Square(sq).file).abs() <= 2) { range = range.withSquare(Square(sq)); } @@ -95,7 +95,7 @@ SquareSet _computeRange(Square square, List deltas) { List _tabulate(T Function(Square square) f) { final List table = []; for (final square in Square.values) { - table.insert(square.value, f(square)); + table.insert(square, f(square)); } return table; } @@ -137,11 +137,11 @@ SquareSet _hyperbola(SquareSet bit, SquareSet range, SquareSet occupied) { return (forward ^ reverse.flipVertical()) & range; } -SquareSet _fileAttacks(Square square, SquareSet occupied) => _hyperbola( - SquareSet.fromSquare(square), _fileRange[square.value], occupied); +SquareSet _fileAttacks(Square square, SquareSet occupied) => + _hyperbola(SquareSet.fromSquare(square), _fileRange[square], occupied); SquareSet _rankAttacks(Square square, SquareSet occupied) { - final range = _rankRange[square.value]; + final range = _rankRange[square]; final bit = SquareSet.fromSquare(square); SquareSet forward = occupied & range; SquareSet reverse = forward.mirrorHorizontal(); diff --git a/lib/src/models.dart b/lib/src/models.dart index eb6a4a3..6f2d5b6 100644 --- a/lib/src/models.dart +++ b/lib/src/models.dart @@ -84,6 +84,17 @@ 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); + /// Constructs a [File] from an algebraic notation, such as 'a', 'b', 'c', etc. + /// + /// Throws a [FormatException] if the algebraic notation is invalid. + factory File.fromAlgebraic(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); @@ -93,6 +104,7 @@ extension type const File._(int value) implements int { 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']; @@ -120,6 +132,17 @@ 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); + /// Constructs a [Rank] from an algebraic notation, such as '1', '2', '3', etc. + /// + /// Throws a [FormatException] if the algebraic notation is invalid. + factory Rank.fromAlgebraic(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); @@ -129,6 +152,7 @@ extension type const Rank._(int value) implements int { static const seventh = Rank(6); static const eighth = Rank(7); + /// All ranks in ascending order. static const values = [ first, second, @@ -167,9 +191,14 @@ extension type const Square._(int value) implements int { const Square(this.value) : assert(value >= 0 && value < 64); /// Constructs a [Square] from an algebraic notation, such as 'a1', 'b2', etc. + /// + /// Throws a [FormatException] if the algebraic notation is invalid. factory Square.fromAlgebraic(String 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(rank * 8 + file); } diff --git a/lib/src/position.dart b/lib/src/position.dart index 00ee183..cf4d9df 100644 --- a/lib/src/position.dart +++ b/lib/src/position.dart @@ -852,7 +852,7 @@ abstract class Position> { if (piece.role == Role.pawn) { pseudo = pawnAttacks(turn, square) & board.bySide(turn.opposite); final delta = turn == Side.white ? 8 : -8; - final step = square.value + delta; + final step = square + delta; if (0 <= step && step < 64 && !board.occupied.has(Square(step))) { pseudo = pseudo.withSquare(Square(step)); final canDoubleStep = @@ -995,7 +995,7 @@ abstract class Position> { /// Returns the [CastlingSide] or `null` if the move is a drop move. CastlingSide? _getCastlingSide(Move move) { if (move case NormalMove(from: final from, to: final to)) { - final delta = to.value - from.value; + final delta = to - from; if (delta.abs() != 2 && !board.bySide(turn).has(to)) { return null; } diff --git a/lib/src/square_set.dart b/lib/src/square_set.dart index 5e468e2..2667b94 100644 --- a/lib/src/square_set.dart +++ b/lib/src/square_set.dart @@ -22,7 +22,7 @@ extension type const SquareSet(int value) { /// Creates a [SquareSet] from several [Square]s. SquareSet.fromSquares(Iterable squares) : value = squares - .map((square) => 1 << square.value) + .map((square) => 1 << square) .fold(0, (left, right) => left | right); /// Create a [SquareSet] containing all squares of the given rank. @@ -135,7 +135,7 @@ extension type const SquareSet(int value) { /// Returns true if the [SquareSet] contains the given [square]. bool has(Square square) { - return value & (1 << square.value) != 0; + return value & (1 << square) != 0; } /// Returns true if the square set has any square in the [other] square set. @@ -146,17 +146,17 @@ extension type const SquareSet(int value) { /// Returns a new [SquareSet] with the given [square] added. SquareSet withSquare(Square square) { - return SquareSet(value | (1 << square.value)); + return SquareSet(value | (1 << square)); } /// Returns a new [SquareSet] with the given [square] removed. SquareSet withoutSquare(Square square) { - return SquareSet(value & ~(1 << square.value)); + return SquareSet(value & ~(1 << square)); } /// Removes [Square] if present, or put it if absent. SquareSet toggleSquare(Square square) { - return SquareSet(value ^ (1 << square.value)); + return SquareSet(value ^ (1 << square)); } /// Returns a new [SquareSet] with its first [Square] removed. diff --git a/test/models_test.dart b/test/models_test.dart index 607bbdf..07f5260 100644 --- a/test/models_test.dart +++ b/test/models_test.dart @@ -2,6 +2,46 @@ import 'package:dartchess/dartchess.dart'; import 'package:test/test.dart'; void main() { + group('File', () { + test('File.values', () { + expect(File.values.length, 8); + }); + + test('fromAlgebraic', () { + expect(File.fromAlgebraic('a'), File.a); + expect(File.fromAlgebraic('h'), File.h); + }); + + test('offset', () { + expect(File.a.offset(1), File.b); + expect(File.h.offset(-1), File.g); + }); + + test('offset throws exception if out of range', () { + expect(() => File.h.offset(1), throwsRangeError); + }); + }); + + group('Rank', () { + test('Rank.values', () { + expect(Rank.values.length, 8); + }); + + test('fromAlgebraic', () { + expect(Rank.fromAlgebraic('1'), Rank.first); + expect(Rank.fromAlgebraic('8'), Rank.eighth); + }); + + test('offset', () { + expect(Rank.first.offset(1), Rank.second); + expect(Rank.eighth.offset(-1), Rank.seventh); + }); + + test('offset throws exception if out of range', () { + expect(() => Rank.eighth.offset(1), throwsRangeError); + }); + }); + group('Square', () { test('Square.values', () { expect(Square.values.length, 64);