omnichess/lib/classes/best_move_searcher.dart

138 lines
4.6 KiB
Dart

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.minInteger
: Numbers.maxInteger;
}
int value = 0;
List<int> whitePawnFiles = List.filled(8, 0, growable: false); // isolated pawns, doubled pawns
List<int> blackPawnFiles = List.filled(8, 0, growable: false);
List<bool> whiteBishopColors = List.filled(2, false, growable: false); // bishop pairs
List<bool> 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<String?> 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<void> stop() async {
if (!_isRunning) {
return;
}
_isStopped = true;
}
}