diff --git a/.gitignore b/.gitignore index 03641f0..ee55a3a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ .dart_tool/ # project-specific -builds/ \ No newline at end of file +bin/test_* +builds/ +logs/ \ No newline at end of file diff --git a/lib/classes/best_move_searcher.dart b/lib/classes/best_move_searcher.dart index fc84807..67aa75b 100644 --- a/lib/classes/best_move_searcher.dart +++ b/lib/classes/best_move_searcher.dart @@ -1,7 +1,8 @@ -import 'dart:collection'; import 'package:omnichess/classes/position.dart'; +import 'package:omnichess/constants/numbers.dart'; import 'package:omnichess/constants/piece.dart'; +import 'package:omnichess/data_structures/position_tree_node.dart'; import 'package:omnichess/functions/legal_moves.dart'; class BestMoveSearcher { @@ -9,14 +10,29 @@ class BestMoveSearcher { bool _isRunning = false; bool _isStopped = false; - Future getPositionBaseValue(Position position) async { - if (await LegalMoves.isCheckmate(position)) { + static int getPositionBaseValue(Position position) { + if (LegalMoves.isCheckmate(position)) { return position.isWhiteTurn - ? -(1 << 63) // min int (white is checkmated) - : ((1 << 63) - 1); // max int (black is checkmated) + ? Numbers.maxInteger + : Numbers.minInteger; } int value = 0; + List whitePawnFiles = List.filled(8, 0, growable: false); // isolated pawns, doubled pawns + List blackPawnFiles = List.filled(8, 0, growable: false); + List whiteBishopColors = List.filled(2, false, growable: false); // bishop pairs + List blackBishopColors = List.filled(2, false, growable: false); + int row = 0; + int column = 0; for (int i = 0; i < 64; i++) { + final int verticalDisplacementFromCenter = 7 - 2*row; + final int horizontalDisplacementFromCenter = 7 - 2*column; + final int attackingValue = (98-(verticalDisplacementFromCenter*verticalDisplacementFromCenter + horizontalDisplacementFromCenter*horizontalDisplacementFromCenter)) >> 4; + if (LegalMoves.isWhiteAttacking(position, i)) { + value += attackingValue; + } + if (LegalMoves.isBlackAttacking(position, i)) { + value -= attackingValue; + } value += switch (position.board[i]) { Piece.whiteQueen => 900, Piece.whiteRook => 500, @@ -30,40 +46,86 @@ class BestMoveSearcher { Piece.blackPawn => -100, _ => 0, }; + if (position.board[i] == Piece.whitePawn) { + whitePawnFiles[i % 8]++; + if (whitePawnFiles[i % 8] > 1) { + value -= 25; + } + } + if (position.board[i] == Piece.blackPawn) { + blackPawnFiles[i % 8]++; + if (blackPawnFiles[i % 8] > 1) { + value += 25; + } + } + if (position.board[i] == Piece.whiteBishop) { + whiteBishopColors[i%2] = true; + } + if (position.board[i] == Piece.blackBishop) { + blackBishopColors[i%2] = true; + } + column = (column + 1) % 8; + if (column == 0) { + row++; + } + } + if (whitePawnFiles[0] > 0 && whitePawnFiles[1] == 0) { + value -= 50; + } + if (whitePawnFiles[7] > 0 && whitePawnFiles[6] == 0) { + value -= 50; + } + if (blackPawnFiles[0] > 0 && blackPawnFiles[1] == 0) { + value += 50; + } + if (blackPawnFiles[7] > 0 && blackPawnFiles[6] == 0) { + value += 50; + } + for (int i = 1; i < 7; i++) { + if (whitePawnFiles[i-1] == 0 && whitePawnFiles[i] > 0 && whitePawnFiles[i+1] == 0) { + value -= 50; + } + if (blackPawnFiles[i-1] == 0 && blackPawnFiles[i] > 0 && blackPawnFiles[i+1] == 0) { + value += 50; + } + } + if (whiteBishopColors[0] && whiteBishopColors[1]){ + value += 50; + } + if (blackBishopColors[0] && blackBishopColors[1]){ + value -= 50; } return value; } - Future search(Position position) async { + Future search(Position position, int? moveTime) async { _isRunning = true; - (int, String, Position)? bestMove; - Queue<(int, String, Position)> evaluatedMoves = Queue(); - await for (final (String, Position) move in LegalMoves.getLegalMoves(position)) { - final (int, String, Position) evaluatedMove = (await getPositionBaseValue(move.$2), move.$1, move.$2); - if ( - null == bestMove || - (bestMove.$1 < evaluatedMove.$1 && position.isWhiteTurn) || - (bestMove.$1 > evaluatedMove.$1 && !position.isWhiteTurn) - ) { - bestMove = evaluatedMove; + PositionTreeNode positionTree = PositionTreeNode.fromPosition(position); + String bestLine = ""; + int? bestValue; + int depth = 0; + final int startTime = DateTime.now().millisecondsSinceEpoch; + int time = 0; + while (!_isStopped && positionTree.priority < Numbers.maxInteger) { + positionTree.computeStep(); + time = DateTime.now().millisecondsSinceEpoch - startTime; + if (null != moveTime && time >= moveTime) { + _isStopped = true; + } + if (positionTree.childrenByValue.isNotEmpty || bestValue != positionTree.value) { + final String thisBestLine = positionTree.childrenByValue.firstOrNull?.bestLine ?? ""; + if (bestLine != thisBestLine || bestValue != positionTree.value || depth != positionTree.depth) { + bestLine = thisBestLine; + bestValue = positionTree.value; + depth = positionTree.depth; + print("info depth $depth multipv 1 nodes ${positionTree.nodeCount} time $time score cp ${(position.isWhiteTurn ? 1 : -1) * (positionTree.value)} pv $bestLine"); + } } - evaluatedMoves.add(evaluatedMove); await Future.delayed(Duration.zero); - if (_isStopped) { - break; - } - // TODO evaluate checkmate } - if (evaluatedMoves.isEmpty) { - _isRunning = false; - _isStopped = false; - return null; - } - // TODO search - // IDEA implement a BFS with pruning on the ones that give suboptimal values _isRunning = false; _isStopped = false; - return bestMove?.$2; + return positionTree.childrenByValue.firstOrNull?.move; } Future stop() async { diff --git a/lib/constants/numbers.dart b/lib/constants/numbers.dart new file mode 100644 index 0000000..d9f4d58 --- /dev/null +++ b/lib/constants/numbers.dart @@ -0,0 +1,11 @@ +import 'package:meta/meta.dart'; + +@immutable +class Numbers { + + Numbers._(); + + static const int maxInteger = 0x7FFFFFFFFFFFFFFF; + static const int minInteger = -0x7FFFFFFFFFFFFFFF; + +} \ No newline at end of file diff --git a/lib/data_structures/heap.dart b/lib/data_structures/heap.dart deleted file mode 100644 index b7b9d05..0000000 --- a/lib/data_structures/heap.dart +++ /dev/null @@ -1,83 +0,0 @@ -class Heap { - - final int Function(T) getPriority; - List<(T, int)> _items; - - Heap({ - required this.getPriority - }): _items = []; - - factory Heap.fromItems( - List items, - { - required int Function(T) getPriority, - } - ) { - Heap heap = Heap(getPriority: getPriority); - for (final T item in items) { - heap.add(item); - } - return heap; - } - - T get first => _items.first.$1; - - T? get firstOrNull => _items.firstOrNull?.$1; - - int get length => _items.length; - - bool get isEmpty => _items.isEmpty; - - bool get isNotEmpty => _items.isNotEmpty; - - void add(T item) { - final (T, int) heapItem = (item, getPriority(item)); - final int thisKey = heapItem.$2; - int i = _items.length; - _items.add(heapItem); - while (i > 0 && _items[(i-1) ~/ 2].$2 > thisKey) { - _items[i] = _items[(i-1) ~/ 2]; - i = (i-1)~/2; - } - _items[i] = heapItem; - } - - T removeFirst() { - if (_items.isEmpty) { - throw StateError("No element"); - } - final T firstItem = _items[0].$1; - if (_items.length < 2) { - _items.length--; - return firstItem; - } - final (T, int) itemToMove = _items.last; - _items[0] = itemToMove; - _items.length--; - int i = 0; - int leftIndex, rightIndex; - bool stop = false; - while (!stop) { - rightIndex = (i+1) << 1; - leftIndex = rightIndex-1; - if (leftIndex < _items.length && itemToMove.$2 > _items[leftIndex].$2) { - if (rightIndex < _items.length && _items[leftIndex].$2 > _items[rightIndex].$2) { - _items[i] = _items[rightIndex]; - _items[rightIndex] = itemToMove; - i = rightIndex; - } else { - _items[i] = _items[leftIndex]; - _items[leftIndex] = itemToMove; - i = leftIndex; - } - } else { - stop = true; - } - } - return firstItem; - } - - @override - String toString() => _items.map(((T, int) item) => item.$1).toString(); - -} \ No newline at end of file diff --git a/lib/data_structures/position_tree_node.dart b/lib/data_structures/position_tree_node.dart new file mode 100644 index 0000000..4899497 --- /dev/null +++ b/lib/data_structures/position_tree_node.dart @@ -0,0 +1,96 @@ +import 'package:omnichess/classes/best_move_searcher.dart'; +import 'package:omnichess/classes/position.dart'; +import 'package:omnichess/constants/numbers.dart'; +import 'package:omnichess/data_structures/search_heap.dart'; +import 'package:omnichess/functions/legal_moves.dart'; + +class PositionTreeNode { + + int priority; + int value; + int nodeCount; + int depth; + final String move; + final Position position; + final Iterable<(String, Position)> legalMoves; + SearchHeap children; + SearchHeap childrenByValue; + + String get bestLine { + final String? childBestLine = childrenByValue.firstOrNull?.bestLine; + return "$move${null == childBestLine ? "" : " $childBestLine"}"; + } + + PositionTreeNode({ + required this.priority, + required this.value, + required this.move, + required this.position, + required this.legalMoves, + required this.children, + required this.childrenByValue, + }): + nodeCount = 0, + depth = 0; + + factory PositionTreeNode.fromPosition(Position position, [ String move = "" ]) { + final int value = BestMoveSearcher.getPositionBaseValue(position); + final Iterable<(String, Position)> legalMoves = LegalMoves.getLegalMoves(position); + return PositionTreeNode( + priority: getPositionBasePriority(position, value, legalMoves, 0), + value: value, + move: move, + position: position, + legalMoves: legalMoves, + children: SearchHeap( + getKey: (PositionTreeNode node) => node.move, + getPriority: (PositionTreeNode node) => node.priority, + ), + childrenByValue: SearchHeap( + getKey: (PositionTreeNode node) => node.move, + getPriority: position.isWhiteTurn + ? (PositionTreeNode node) => -node.value + : (PositionTreeNode node) => node.value, + ), + ); + } + + void computeStep() { + if (legalMoves.isEmpty) { + return; + } + if (children.isEmpty) { + _computeChildren(); + return; + } + final PositionTreeNode firstChild = children.first; + final int initialFirstChildNodeCount = firstChild.nodeCount; + firstChild.computeStep(); + children.updateElement(firstChild.move); + childrenByValue.updateElement(firstChild.move); + nodeCount += firstChild.nodeCount - initialFirstChildNodeCount; + if (firstChild.depth + 1 > depth) { + depth = firstChild.depth + 1; + } + priority = (children.first.priority == Numbers.maxInteger) ? children.first.priority : children.first.priority + 1; + value = childrenByValue.first.value; + } + + void _computeChildren() { + for (final (String, Position) move in legalMoves) { + final PositionTreeNode positionTreeNode = PositionTreeNode.fromPosition(move.$2, move.$1); + children.add(positionTreeNode); + childrenByValue.add(positionTreeNode); + } + nodeCount = legalMoves.length; + priority = (children.first.priority == Numbers.maxInteger) ? children.first.priority : children.first.priority + 1; + depth = 1; + value = childrenByValue.first.value; + } + + static int getPositionBasePriority(Position position, int value, Iterable<(String, Position)> legalMoves, int depth) => + legalMoves.isEmpty + ? Numbers.maxInteger + : 0; + +} \ No newline at end of file diff --git a/lib/data_structures/search_heap.dart b/lib/data_structures/search_heap.dart new file mode 100644 index 0000000..b44d87c --- /dev/null +++ b/lib/data_structures/search_heap.dart @@ -0,0 +1,135 @@ +class SearchHeap { + + final K Function(T) getKey; + final int Function(T) getPriority; + List<(T, K, int)> _items; + Map _positionByKey; + + SearchHeap({ + required this.getKey, + required this.getPriority, + }): + _items = [], + _positionByKey = {}; + + factory SearchHeap.fromItems( + List items, + { + required K Function(T) getKey, + required int Function(T) getPriority, + } + ) { + SearchHeap heap = SearchHeap(getKey: getKey, getPriority: getPriority); + for (final T item in items) { + heap.add(item); + } + return heap; + } + + T get first => _items.first.$1; + + T? get firstOrNull => _items.firstOrNull?.$1; + + int get length => _items.length; + + bool get isEmpty => _items.isEmpty; + + bool get isNotEmpty => _items.isNotEmpty; + + void add(T item) { + final (T, K, int) heapItem = (item, getKey(item), getPriority(item)); + final int thisKey = heapItem.$3; + int i = _items.length; + _items.add(heapItem); + while (i > 0 && _items[(i-1) >> 1].$3 > thisKey) { + _items[i] = _items[(i-1) >> 1]; + _positionByKey[_items[i].$2] = i; + i = (i-1) >> 1; + } + _items[i] = heapItem; + _positionByKey[heapItem.$2] = i; + } + + T removeFirst() { + if (_items.isEmpty) { + throw StateError("No element"); + } + final T firstItem = _items[0].$1; + _positionByKey.remove(_items[0].$2); + if (_items.length < 2) { + _items.length--; + return firstItem; + } + final (T, K, int) itemToMove = _items.last; + _items.length--; + int i = 0; + int leftIndex, rightIndex; + bool stop = false; + while (!stop) { + rightIndex = (i+1) << 1; + leftIndex = rightIndex-1; + if (leftIndex < _items.length && itemToMove.$3 > _items[leftIndex].$3) { + if (rightIndex < _items.length && _items[leftIndex].$3 > _items[rightIndex].$3) { + _items[i] = _items[rightIndex]; + _positionByKey[_items[i].$2] = i; + i = rightIndex; + } else { + _items[i] = _items[leftIndex]; + _positionByKey[_items[i].$2] = i; + i = leftIndex; + } + } else if (rightIndex < _items.length && _items[leftIndex].$3 > _items[rightIndex].$3) { + _items[i] = _items[rightIndex]; + _positionByKey[_items[i].$2] = i; + i = rightIndex; + } else { + stop = true; + } + } + _items[i] = itemToMove; + _positionByKey[itemToMove.$2] = i; + return firstItem; + } + + void updateElement(K key) { + int i = _positionByKey[key]!; + final (T, K, int) item = (_items[i].$1, _items[i].$2, getPriority(_items[i].$1)); + bool dontBubbleDown = false; + while (i > 0 && _items[(i-1) >> 1].$3 > item.$3) { + dontBubbleDown = true; + _items[i] = _items[(i-1) >> 1]; + _positionByKey[_items[i].$2] = i; + i = (i-1) >> 1; + } + int leftIndex, rightIndex; + while (!dontBubbleDown) { + rightIndex = (i+1) << 1; + leftIndex = rightIndex-1; + if (leftIndex < _items.length && item.$3 > _items[leftIndex].$3) { + if (rightIndex < _items.length && _items[leftIndex].$3 > _items[rightIndex].$3) { + _items[i] = _items[rightIndex]; + _positionByKey[_items[i].$2] = i; + i = rightIndex; + } else { + _items[i] = _items[leftIndex]; + _positionByKey[_items[i].$2] = i; + i = leftIndex; + } + } else if (rightIndex < _items.length && _items[leftIndex].$3 > _items[rightIndex].$3) { + _items[i] = _items[rightIndex]; + _positionByKey[_items[i].$2] = i; + i = rightIndex; + } else { + dontBubbleDown = true; + } + } + _items[i] = item; + _positionByKey[item.$2] = i; + } + + @override + String toString() => _items.map(((T, K, int) item) => item.$1).toString(); + + List get debugItems => _items.map(((T, K, int) item) => item.$1).toList(); + +} \ No newline at end of file diff --git a/lib/functions/_partials/legal_moves_black.dart b/lib/functions/_partials/legal_moves_black.dart index c479f50..0a15e27 100644 --- a/lib/functions/_partials/legal_moves_black.dart +++ b/lib/functions/_partials/legal_moves_black.dart @@ -4,9 +4,9 @@ class _LegalMovesBlack { _LegalMovesBlack._(); - static Stream<(String, Position)> _getLegalMoves(Position position) => _getValidOrCheckedMoves(position).where(((String, Position) move) => !_isBlackChecked(move.$2)); + static Iterable<(String, Position)> _getLegalMoves(Position position) => _getValidOrCheckedMoves(position).where(((String, Position) move) => !_isBlackChecked(move.$2)); - static Stream<(String, Position)> _getValidOrCheckedMoves(Position position) async* { + static Iterable<(String, Position)> _getValidOrCheckedMoves(Position position) sync* { for (int i = 0; i < 64; i++) { switch (position.board[i]) { case Piece.emptySquare: @@ -40,7 +40,7 @@ class _LegalMovesBlack { } } - static Stream<(String, Position)> _getBlackPawnMoves(Position position, int i) async* { + static Iterable<(String, Position)> _getBlackPawnMoves(Position position, int i) sync* { int j = i+8; if (Piece.emptySquare == position.board[j]) { // pawn forward if (i > 47) { @@ -87,7 +87,7 @@ class _LegalMovesBlack { } } - static Stream<(String, Position)> _getBlackKnightMoves(Position position, int i) async* { + static Iterable<(String, Position)> _getBlackKnightMoves(Position position, int i) sync* { final bool canGoUp2 = i > 15; final bool canGoUp1 = i > 7; final bool canGoDown1 = i < 56; @@ -131,7 +131,7 @@ class _LegalMovesBlack { } } - static Stream<(String, Position)> _getBlackBishopMoves(Position position, int i) async* { + static Iterable<(String, Position)> _getBlackBishopMoves(Position position, int i) sync* { final int column = i % 8; // up left bool hasEncounteredPiece = false; @@ -198,7 +198,7 @@ class _LegalMovesBlack { j+=9; } } - static Stream<(String, Position)> _getBlackRookMoves(Position position, int i) async* { + static Iterable<(String, Position)> _getBlackRookMoves(Position position, int i) sync* { final int column = i % 8; // up bool hasEncounteredPiece = false; @@ -262,7 +262,7 @@ class _LegalMovesBlack { } } - static Stream<(String, Position)> _getBlackKingMoves(Position position, int i) async* { + static Iterable<(String, Position)> _getBlackKingMoves(Position position, int i) sync* { int j = i-8; if (j >= 0 && !Piece.isBlack(position.board[j])) { yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j)); @@ -352,7 +352,7 @@ class _LegalMovesBlack { } static bool _isWhitePawnAttacking(Position position, int square) { - if (square < 8) { + if (square > 47) { return false; } final int column = square % 8; @@ -480,7 +480,7 @@ class _LegalMovesBlack { // up bool hasEncounteredPiece = false; int j = square-8; - while (!hasEncounteredPiece && j > 0) { + while (!hasEncounteredPiece && j >= 0) { final int piece = position.board[j]; if (Piece.emptySquare != piece) { hasEncounteredPiece = true; @@ -507,7 +507,7 @@ class _LegalMovesBlack { hasEncounteredPiece = false; j = square - 1; int k = column - 1; - while (!hasEncounteredPiece && k > 0) { + while (!hasEncounteredPiece && k >= 0) { final int piece = position.board[j]; if (Piece.emptySquare != piece) { hasEncounteredPiece = true; diff --git a/lib/functions/_partials/legal_moves_white.dart b/lib/functions/_partials/legal_moves_white.dart index 3b80663..460cea4 100644 --- a/lib/functions/_partials/legal_moves_white.dart +++ b/lib/functions/_partials/legal_moves_white.dart @@ -4,9 +4,9 @@ class _LegalMovesWhite { _LegalMovesWhite._(); - static Stream<(String, Position)> _getLegalMoves(Position position) => _getValidOrCheckedMoves(position).where(((String, Position) move) => !_isWhiteChecked(move.$2)); + static Iterable<(String, Position)> _getLegalMoves(Position position) => _getValidOrCheckedMoves(position).where(((String, Position) move) => !_isWhiteChecked(move.$2)); - static Stream<(String, Position)> _getValidOrCheckedMoves(Position position) async* { + static Iterable<(String, Position)> _getValidOrCheckedMoves(Position position) sync* { for (int i = 0; i < 64; i++) { switch (position.board[i]) { case Piece.emptySquare: @@ -40,7 +40,7 @@ class _LegalMovesWhite { } } - static Stream<(String, Position)> _getWhitePawnMoves(Position position, int i) async* { + static Iterable<(String, Position)> _getWhitePawnMoves(Position position, int i) sync* { int j = i-8; if (Piece.emptySquare == position.board[j]) { // pawn forward if (i < 16) { @@ -87,7 +87,7 @@ class _LegalMovesWhite { } } - static Stream<(String, Position)> _getWhiteKnightMoves(Position position, int i) async* { + static Iterable<(String, Position)> _getWhiteKnightMoves(Position position, int i) sync* { final bool canGoUp2 = i > 15; final bool canGoUp1 = i > 7; final bool canGoDown1 = i < 56; @@ -131,7 +131,7 @@ class _LegalMovesWhite { } } - static Stream<(String, Position)> _getWhiteBishopMoves(Position position, int i) async* { + static Iterable<(String, Position)> _getWhiteBishopMoves(Position position, int i) sync* { final int column = i % 8; // up left bool hasEncounteredPiece = false; @@ -198,7 +198,7 @@ class _LegalMovesWhite { j+=9; } } - static Stream<(String, Position)> _getWhiteRookMoves(Position position, int i) async* { + static Iterable<(String, Position)> _getWhiteRookMoves(Position position, int i) sync* { final int column = i % 8; // up bool hasEncounteredPiece = false; @@ -262,7 +262,7 @@ class _LegalMovesWhite { } } - static Stream<(String, Position)> _getWhiteKingMoves(Position position, int i) async* { + static Iterable<(String, Position)> _getWhiteKingMoves(Position position, int i) sync* { int j = i-8; if (j >= 0 && !Piece.isWhite(position.board[j])) { yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j)); @@ -352,7 +352,7 @@ class _LegalMovesWhite { } static bool _isBlackPawnAttacking(Position position, int square) { - if (square < 8) { + if (square < 16) { return false; } final int column = square % 8; @@ -480,7 +480,7 @@ class _LegalMovesWhite { // up bool hasEncounteredPiece = false; int j = square-8; - while (!hasEncounteredPiece && j > 0) { + while (!hasEncounteredPiece && j >= 0) { final int piece = position.board[j]; if (Piece.emptySquare != piece) { hasEncounteredPiece = true; @@ -507,7 +507,7 @@ class _LegalMovesWhite { hasEncounteredPiece = false; j = square - 1; int k = column - 1; - while (!hasEncounteredPiece && k > 0) { + while (!hasEncounteredPiece && k >= 0) { final int piece = position.board[j]; if (Piece.emptySquare != piece) { hasEncounteredPiece = true; diff --git a/lib/functions/legal_moves.dart b/lib/functions/legal_moves.dart index ef78237..1719949 100644 --- a/lib/functions/legal_moves.dart +++ b/lib/functions/legal_moves.dart @@ -8,12 +8,17 @@ part "_partials/legal_moves_white.dart"; class LegalMoves { LegalMoves._(); - static Stream<(String, Position)> getLegalMoves(Position position) => position.isWhiteTurn + static Iterable<(String, Position)> getLegalMoves(Position position) => position.isWhiteTurn ? _LegalMovesWhite._getLegalMoves(position) : _LegalMovesBlack._getLegalMoves(position); - static Future isCheckmate(Position position) async => position.isWhiteTurn - ? (_LegalMovesWhite._isWhiteChecked(position) && await getLegalMoves(position).isEmpty) - : (_LegalMovesBlack._isBlackChecked(position) && await getLegalMoves(position).isEmpty); + static bool isCheckmate(Position position) => position.isWhiteTurn + ? (_LegalMovesWhite._isWhiteChecked(position) && getLegalMoves(position).isEmpty) + : (_LegalMovesBlack._isBlackChecked(position) && getLegalMoves(position).isEmpty); + + static bool isWhiteAttacking(Position position, int square) => _LegalMovesBlack._isWhiteAttacking(position, square); + static bool isBlackAttacking(Position position, int square) => _LegalMovesWhite._isBlackAttacking(position, square); + static bool isWhiteChecked(Position position) => _LegalMovesWhite._isWhiteChecked(position); + static bool isBlackChecked(Position position) => _LegalMovesBlack._isBlackChecked(position); } \ No newline at end of file diff --git a/lib/omnichess.dart b/lib/omnichess.dart index c685b18..ad2519c 100644 --- a/lib/omnichess.dart +++ b/lib/omnichess.dart @@ -22,8 +22,15 @@ class Omnichess { } Future _goCommand(Queue inputComponents) async { + int? moveTime; + while (inputComponents.isNotEmpty) { + final String inputComponent = inputComponents.removeFirst(); + if ("movetime" == inputComponent) { + moveTime = int.tryParse(inputComponents.firstOrNull ?? ""); + } + } final Position position = Position.fromPositionCommand(positionCommand); - final String? bestMove = await bestMoveSearcher.search(position); + final String? bestMove = await bestMoveSearcher.search(position, moveTime); print("bestmove ${bestMove ?? "(none)"}"); } @@ -103,11 +110,26 @@ class Omnichess { } void loop() async { + final DateTime now = DateTime.now(); + final String logName = "${now.year}" + "${now.month.toString().padLeft(2, "0")}" + "${now.day.toString().padLeft(2, "0")}" + "-" + "${now.hour.toString().padLeft(2, "0")}" + "${now.minute.toString().padLeft(2, "0")}" + "${now.second.toString().padLeft(2, "0")}" + "${now.millisecond.toString().padLeft(3, "0")}" + ".log"; + final File log = File("logs/$logName"); + if (!log.existsSync()) { + log.createSync(); + } late StreamSubscription inputSubscription; inputSubscription = stdin .transform(utf8.decoder) .transform(LineSplitter()) .listen((String line) async { + log.writeAsStringSync("$line\n", mode: FileMode.append); final String input = line.trim(); final bool keepGoing = await elaborate(input); if (!keepGoing) {