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 { bool _isRunning = false; bool _isStopped = false; static int getPositionBaseValue(Position position) { if (LegalMoves.isCheckmate(position)) { return position.isWhiteTurn ? 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, Piece.whiteBishop => 300, Piece.whiteKnight => 300, Piece.whitePawn => 100, Piece.blackQueen => -900, Piece.blackRook => -500, Piece.blackBishop => -300, Piece.blackKnight => -300, 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, int? moveTime) async { _isRunning = true; 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"); } } await Future.delayed(Duration.zero); } _isRunning = false; _isStopped = false; return positionTree.childrenByValue.firstOrNull?.move; } Future stop() async { if (!_isRunning) { return; } _isStopped = true; } }