Compare commits
2 Commits
637cb053c4
...
0b88775b0c
| Author | SHA1 | Date |
|---|---|---|
|
|
0b88775b0c | |
|
|
576a42b670 |
|
|
@ -3,4 +3,6 @@
|
|||
.dart_tool/
|
||||
|
||||
# project-specific
|
||||
bin/test_*
|
||||
builds/
|
||||
logs/
|
||||
|
|
@ -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<int> 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<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,
|
||||
|
|
@ -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<String?> search(Position position) async {
|
||||
Future<String?> 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<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;
|
||||
|
||||
}
|
||||
|
|
@ -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._();
|
||||
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<bool> 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);
|
||||
|
||||
}
|
||||
|
|
@ -22,8 +22,15 @@ class Omnichess {
|
|||
}
|
||||
|
||||
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 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<String> 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) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue