Basic move searcher, priority only based on depth (resulting in BFS), position base value takes into account isolated pawns, doubled pawns and bishop pairs
parent
576a42b670
commit
0b88775b0c
|
|
@ -3,4 +3,6 @@
|
||||||
.dart_tool/
|
.dart_tool/
|
||||||
|
|
||||||
# project-specific
|
# project-specific
|
||||||
|
bin/test_*
|
||||||
builds/
|
builds/
|
||||||
|
logs/
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import 'dart:collection';
|
|
||||||
|
|
||||||
import 'package:omnichess/classes/position.dart';
|
import 'package:omnichess/classes/position.dart';
|
||||||
|
import 'package:omnichess/constants/numbers.dart';
|
||||||
import 'package:omnichess/constants/piece.dart';
|
import 'package:omnichess/constants/piece.dart';
|
||||||
|
import 'package:omnichess/data_structures/position_tree_node.dart';
|
||||||
import 'package:omnichess/functions/legal_moves.dart';
|
import 'package:omnichess/functions/legal_moves.dart';
|
||||||
|
|
||||||
class BestMoveSearcher {
|
class BestMoveSearcher {
|
||||||
|
|
@ -9,14 +10,29 @@ class BestMoveSearcher {
|
||||||
bool _isRunning = false;
|
bool _isRunning = false;
|
||||||
bool _isStopped = false;
|
bool _isStopped = false;
|
||||||
|
|
||||||
Future<int> getPositionBaseValue(Position position) async {
|
static int getPositionBaseValue(Position position) {
|
||||||
if (await LegalMoves.isCheckmate(position)) {
|
if (LegalMoves.isCheckmate(position)) {
|
||||||
return position.isWhiteTurn
|
return position.isWhiteTurn
|
||||||
? -(1 << 63) // min int (white is checkmated)
|
? Numbers.maxInteger
|
||||||
: ((1 << 63) - 1); // max int (black is checkmated)
|
: Numbers.minInteger;
|
||||||
}
|
}
|
||||||
int value = 0;
|
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++) {
|
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]) {
|
value += switch (position.board[i]) {
|
||||||
Piece.whiteQueen => 900,
|
Piece.whiteQueen => 900,
|
||||||
Piece.whiteRook => 500,
|
Piece.whiteRook => 500,
|
||||||
|
|
@ -30,40 +46,86 @@ class BestMoveSearcher {
|
||||||
Piece.blackPawn => -100,
|
Piece.blackPawn => -100,
|
||||||
_ => 0,
|
_ => 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;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> search(Position position) async {
|
Future<String?> search(Position position, int? moveTime) async {
|
||||||
_isRunning = true;
|
_isRunning = true;
|
||||||
(int, String, Position)? bestMove;
|
PositionTreeNode positionTree = PositionTreeNode.fromPosition(position);
|
||||||
Queue<(int, String, Position)> evaluatedMoves = Queue();
|
String bestLine = "";
|
||||||
await for (final (String, Position) move in LegalMoves.getLegalMoves(position)) {
|
int? bestValue;
|
||||||
final (int, String, Position) evaluatedMove = (await getPositionBaseValue(move.$2), move.$1, move.$2);
|
int depth = 0;
|
||||||
if (
|
final int startTime = DateTime.now().millisecondsSinceEpoch;
|
||||||
null == bestMove ||
|
int time = 0;
|
||||||
(bestMove.$1 < evaluatedMove.$1 && position.isWhiteTurn) ||
|
while (!_isStopped && positionTree.priority < Numbers.maxInteger) {
|
||||||
(bestMove.$1 > evaluatedMove.$1 && !position.isWhiteTurn)
|
positionTree.computeStep();
|
||||||
) {
|
time = DateTime.now().millisecondsSinceEpoch - startTime;
|
||||||
bestMove = evaluatedMove;
|
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);
|
await Future.delayed(Duration.zero);
|
||||||
if (_isStopped) {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
// TODO evaluate checkmate
|
|
||||||
}
|
|
||||||
if (evaluatedMoves.isEmpty) {
|
|
||||||
_isRunning = false;
|
_isRunning = false;
|
||||||
_isStopped = false;
|
_isStopped = false;
|
||||||
return null;
|
return positionTree.childrenByValue.firstOrNull?.move;
|
||||||
}
|
|
||||||
// TODO search
|
|
||||||
// IDEA implement a BFS with pruning on the ones that give suboptimal values
|
|
||||||
_isRunning = false;
|
|
||||||
_isStopped = false;
|
|
||||||
return bestMove?.$2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> stop() async {
|
Future<void> stop() async {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class Numbers {
|
||||||
|
|
||||||
|
Numbers._();
|
||||||
|
|
||||||
|
static const int maxInteger = 0x7FFFFFFFFFFFFFFF;
|
||||||
|
static const int minInteger = -0x7FFFFFFFFFFFFFFF;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
class Heap<T> {
|
|
||||||
|
|
||||||
final int Function(T) getPriority;
|
|
||||||
List<(T, int)> _items;
|
|
||||||
|
|
||||||
Heap({
|
|
||||||
required this.getPriority
|
|
||||||
}): _items = [];
|
|
||||||
|
|
||||||
factory Heap.fromItems(
|
|
||||||
List<T> items,
|
|
||||||
{
|
|
||||||
required int Function(T) getPriority,
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Heap<T> 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();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -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<String, PositionTreeNode> children;
|
||||||
|
SearchHeap<String, PositionTreeNode> 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<String, PositionTreeNode>(
|
||||||
|
getKey: (PositionTreeNode node) => node.move,
|
||||||
|
getPriority: (PositionTreeNode node) => node.priority,
|
||||||
|
),
|
||||||
|
childrenByValue: SearchHeap<String, PositionTreeNode>(
|
||||||
|
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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
class SearchHeap<K, T> {
|
||||||
|
|
||||||
|
final K Function(T) getKey;
|
||||||
|
final int Function(T) getPriority;
|
||||||
|
List<(T, K, int)> _items;
|
||||||
|
Map<K, int> _positionByKey;
|
||||||
|
|
||||||
|
SearchHeap({
|
||||||
|
required this.getKey,
|
||||||
|
required this.getPriority,
|
||||||
|
}):
|
||||||
|
_items = [],
|
||||||
|
_positionByKey = {};
|
||||||
|
|
||||||
|
factory SearchHeap.fromItems(
|
||||||
|
List<T> items,
|
||||||
|
{
|
||||||
|
required K Function(T) getKey,
|
||||||
|
required int Function(T) getPriority,
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
SearchHeap<K, T> 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<T> get debugItems => _items.map(((T, K, int) item) => item.$1).toList();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -4,9 +4,9 @@ class _LegalMovesBlack {
|
||||||
|
|
||||||
_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++) {
|
for (int i = 0; i < 64; i++) {
|
||||||
switch (position.board[i]) {
|
switch (position.board[i]) {
|
||||||
case Piece.emptySquare:
|
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;
|
int j = i+8;
|
||||||
if (Piece.emptySquare == position.board[j]) { // pawn forward
|
if (Piece.emptySquare == position.board[j]) { // pawn forward
|
||||||
if (i > 47) {
|
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 canGoUp2 = i > 15;
|
||||||
final bool canGoUp1 = i > 7;
|
final bool canGoUp1 = i > 7;
|
||||||
final bool canGoDown1 = i < 56;
|
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;
|
final int column = i % 8;
|
||||||
// up left
|
// up left
|
||||||
bool hasEncounteredPiece = false;
|
bool hasEncounteredPiece = false;
|
||||||
|
|
@ -198,7 +198,7 @@ class _LegalMovesBlack {
|
||||||
j+=9;
|
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;
|
final int column = i % 8;
|
||||||
// up
|
// up
|
||||||
bool hasEncounteredPiece = false;
|
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;
|
int j = i-8;
|
||||||
if (j >= 0 && !Piece.isBlack(position.board[j])) {
|
if (j >= 0 && !Piece.isBlack(position.board[j])) {
|
||||||
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, 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) {
|
static bool _isWhitePawnAttacking(Position position, int square) {
|
||||||
if (square < 8) {
|
if (square > 47) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final int column = square % 8;
|
final int column = square % 8;
|
||||||
|
|
@ -480,7 +480,7 @@ class _LegalMovesBlack {
|
||||||
// up
|
// up
|
||||||
bool hasEncounteredPiece = false;
|
bool hasEncounteredPiece = false;
|
||||||
int j = square-8;
|
int j = square-8;
|
||||||
while (!hasEncounteredPiece && j > 0) {
|
while (!hasEncounteredPiece && j >= 0) {
|
||||||
final int piece = position.board[j];
|
final int piece = position.board[j];
|
||||||
if (Piece.emptySquare != piece) {
|
if (Piece.emptySquare != piece) {
|
||||||
hasEncounteredPiece = true;
|
hasEncounteredPiece = true;
|
||||||
|
|
@ -507,7 +507,7 @@ class _LegalMovesBlack {
|
||||||
hasEncounteredPiece = false;
|
hasEncounteredPiece = false;
|
||||||
j = square - 1;
|
j = square - 1;
|
||||||
int k = column - 1;
|
int k = column - 1;
|
||||||
while (!hasEncounteredPiece && k > 0) {
|
while (!hasEncounteredPiece && k >= 0) {
|
||||||
final int piece = position.board[j];
|
final int piece = position.board[j];
|
||||||
if (Piece.emptySquare != piece) {
|
if (Piece.emptySquare != piece) {
|
||||||
hasEncounteredPiece = true;
|
hasEncounteredPiece = true;
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@ class _LegalMovesWhite {
|
||||||
|
|
||||||
_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++) {
|
for (int i = 0; i < 64; i++) {
|
||||||
switch (position.board[i]) {
|
switch (position.board[i]) {
|
||||||
case Piece.emptySquare:
|
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;
|
int j = i-8;
|
||||||
if (Piece.emptySquare == position.board[j]) { // pawn forward
|
if (Piece.emptySquare == position.board[j]) { // pawn forward
|
||||||
if (i < 16) {
|
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 canGoUp2 = i > 15;
|
||||||
final bool canGoUp1 = i > 7;
|
final bool canGoUp1 = i > 7;
|
||||||
final bool canGoDown1 = i < 56;
|
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;
|
final int column = i % 8;
|
||||||
// up left
|
// up left
|
||||||
bool hasEncounteredPiece = false;
|
bool hasEncounteredPiece = false;
|
||||||
|
|
@ -198,7 +198,7 @@ class _LegalMovesWhite {
|
||||||
j+=9;
|
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;
|
final int column = i % 8;
|
||||||
// up
|
// up
|
||||||
bool hasEncounteredPiece = false;
|
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;
|
int j = i-8;
|
||||||
if (j >= 0 && !Piece.isWhite(position.board[j])) {
|
if (j >= 0 && !Piece.isWhite(position.board[j])) {
|
||||||
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, 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) {
|
static bool _isBlackPawnAttacking(Position position, int square) {
|
||||||
if (square < 8) {
|
if (square < 16) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final int column = square % 8;
|
final int column = square % 8;
|
||||||
|
|
@ -480,7 +480,7 @@ class _LegalMovesWhite {
|
||||||
// up
|
// up
|
||||||
bool hasEncounteredPiece = false;
|
bool hasEncounteredPiece = false;
|
||||||
int j = square-8;
|
int j = square-8;
|
||||||
while (!hasEncounteredPiece && j > 0) {
|
while (!hasEncounteredPiece && j >= 0) {
|
||||||
final int piece = position.board[j];
|
final int piece = position.board[j];
|
||||||
if (Piece.emptySquare != piece) {
|
if (Piece.emptySquare != piece) {
|
||||||
hasEncounteredPiece = true;
|
hasEncounteredPiece = true;
|
||||||
|
|
@ -507,7 +507,7 @@ class _LegalMovesWhite {
|
||||||
hasEncounteredPiece = false;
|
hasEncounteredPiece = false;
|
||||||
j = square - 1;
|
j = square - 1;
|
||||||
int k = column - 1;
|
int k = column - 1;
|
||||||
while (!hasEncounteredPiece && k > 0) {
|
while (!hasEncounteredPiece && k >= 0) {
|
||||||
final int piece = position.board[j];
|
final int piece = position.board[j];
|
||||||
if (Piece.emptySquare != piece) {
|
if (Piece.emptySquare != piece) {
|
||||||
hasEncounteredPiece = true;
|
hasEncounteredPiece = true;
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,17 @@ part "_partials/legal_moves_white.dart";
|
||||||
class LegalMoves {
|
class LegalMoves {
|
||||||
LegalMoves._();
|
LegalMoves._();
|
||||||
|
|
||||||
static Stream<(String, Position)> getLegalMoves(Position position) => position.isWhiteTurn
|
static Iterable<(String, Position)> getLegalMoves(Position position) => position.isWhiteTurn
|
||||||
? _LegalMovesWhite._getLegalMoves(position)
|
? _LegalMovesWhite._getLegalMoves(position)
|
||||||
: _LegalMovesBlack._getLegalMoves(position);
|
: _LegalMovesBlack._getLegalMoves(position);
|
||||||
|
|
||||||
static Future<bool> isCheckmate(Position position) async => position.isWhiteTurn
|
static bool isCheckmate(Position position) => position.isWhiteTurn
|
||||||
? (_LegalMovesWhite._isWhiteChecked(position) && await getLegalMoves(position).isEmpty)
|
? (_LegalMovesWhite._isWhiteChecked(position) && getLegalMoves(position).isEmpty)
|
||||||
: (_LegalMovesBlack._isBlackChecked(position) && await 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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -22,8 +22,15 @@ class Omnichess {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _goCommand(Queue<String> inputComponents) async {
|
Future<void> _goCommand(Queue<String> 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 Position position = Position.fromPositionCommand(positionCommand);
|
||||||
final String? bestMove = await bestMoveSearcher.search(position);
|
final String? bestMove = await bestMoveSearcher.search(position, moveTime);
|
||||||
print("bestmove ${bestMove ?? "(none)"}");
|
print("bestmove ${bestMove ?? "(none)"}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,11 +110,26 @@ class Omnichess {
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() async {
|
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<String> inputSubscription;
|
late StreamSubscription<String> inputSubscription;
|
||||||
inputSubscription = stdin
|
inputSubscription = stdin
|
||||||
.transform(utf8.decoder)
|
.transform(utf8.decoder)
|
||||||
.transform(LineSplitter())
|
.transform(LineSplitter())
|
||||||
.listen((String line) async {
|
.listen((String line) async {
|
||||||
|
log.writeAsStringSync("$line\n", mode: FileMode.append);
|
||||||
final String input = line.trim();
|
final String input = line.trim();
|
||||||
final bool keepGoing = await elaborate(input);
|
final bool keepGoing = await elaborate(input);
|
||||||
if (!keepGoing) {
|
if (!keepGoing) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue