Initial commit
commit
637cb053c4
|
|
@ -0,0 +1,6 @@
|
||||||
|
# https://dart.dev/guides/libraries/private-files
|
||||||
|
# Created by `dart pub`
|
||||||
|
.dart_tool/
|
||||||
|
|
||||||
|
# project-specific
|
||||||
|
builds/
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Dany Thach
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
# Omnichess
|
||||||
|
|
||||||
|
Omnichess is a UCI-compatible chess engine developed in Dart. This engine was created as a fun project and is not intended for competitive use. It implements the basic functionality required by the UCI protocol so that it can be interfaced with various chess GUIs.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- UCI Protocol Support: Communicate with chess GUIs using standard UCI commands.
|
||||||
|
- Written in Dart: Leverages Dart's simplicity and ease of use.
|
||||||
|
- Fun Implementation: Designed for learning and experimenting rather than serious chess analysis.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Dart SDK (version 3.6.1 or compatible)
|
||||||
|
|
||||||
|
Ensure Dart is properly installed by running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dart --version
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running the Engine
|
||||||
|
|
||||||
|
To run the engine, execute the following command in your terminal from the root of the project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dart run
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiling the Engine
|
||||||
|
|
||||||
|
If you want better performance, you can compile the engine to get an executable that can be executed by your GUI.
|
||||||
|
|
||||||
|
To compile the engine, execute the following command from the root of the project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dart compile exe -o <output-file>
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace `<output-file>` with the name you want to give to the executable.
|
||||||
|
|
||||||
|
### UCI Setup
|
||||||
|
|
||||||
|
After starting the engine, configure your chess GUI to use the UCI protocol. You should set the engine command to either the above `dart run` command or the executable file if you choose to compile it. The engine will then communicate with the GUI for move generation and game state updates.
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
- This engine is a fun project and should not be considered a serious chess engine.
|
||||||
|
- The focus is on UCI compatibility, and while it works with standard command inputs and outputs, its analysis may not be on par with competitive-level engines.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Feel free to fork the repository and experiment. Contributions and suggestions are welcome, but please keep in mind that this is primarily a hobby project.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is provided "as is" without any warranty. Use it for personal or educational purposes, but do not expect competitive level performance.
|
||||||
|
|
||||||
|
Happy coding and enjoy playing with Omnichess!
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
# This file configures the static analysis results for your project (errors,
|
||||||
|
# warnings, and lints).
|
||||||
|
#
|
||||||
|
# This enables the 'recommended' set of lints from `package:lints`.
|
||||||
|
# This set helps identify many issues that may lead to problems when running
|
||||||
|
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
|
||||||
|
# style and format.
|
||||||
|
#
|
||||||
|
# If you want a smaller set of lints you can change this to specify
|
||||||
|
# 'package:lints/core.yaml'. These are just the most critical lints
|
||||||
|
# (the recommended set includes the core lints).
|
||||||
|
# The core lints are also what is used by pub.dev for scoring packages.
|
||||||
|
|
||||||
|
include: package:lints/recommended.yaml
|
||||||
|
|
||||||
|
# Uncomment the following section to specify additional rules.
|
||||||
|
|
||||||
|
# linter:
|
||||||
|
# rules:
|
||||||
|
# - camel_case_types
|
||||||
|
|
||||||
|
# analyzer:
|
||||||
|
# exclude:
|
||||||
|
# - path/to/excluded/files/**
|
||||||
|
|
||||||
|
# For more information about the core and recommended set of lints, see
|
||||||
|
# https://dart.dev/go/core-lints
|
||||||
|
|
||||||
|
# For additional information about configuring this file, see
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
import 'package:omnichess/omnichess.dart';
|
||||||
|
|
||||||
|
void main(List<String> arguments) {
|
||||||
|
final Omnichess engine = Omnichess();
|
||||||
|
engine.loop();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
import 'dart:collection';
|
||||||
|
|
||||||
|
import 'package:omnichess/classes/position.dart';
|
||||||
|
import 'package:omnichess/constants/piece.dart';
|
||||||
|
import 'package:omnichess/functions/legal_moves.dart';
|
||||||
|
|
||||||
|
class BestMoveSearcher {
|
||||||
|
|
||||||
|
bool _isRunning = false;
|
||||||
|
bool _isStopped = false;
|
||||||
|
|
||||||
|
Future<int> getPositionBaseValue(Position position) async {
|
||||||
|
if (await LegalMoves.isCheckmate(position)) {
|
||||||
|
return position.isWhiteTurn
|
||||||
|
? -(1 << 63) // min int (white is checkmated)
|
||||||
|
: ((1 << 63) - 1); // max int (black is checkmated)
|
||||||
|
}
|
||||||
|
int value = 0;
|
||||||
|
for (int i = 0; i < 64; i++) {
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> search(Position position) 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;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> stop() async {
|
||||||
|
if (!_isRunning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_isStopped = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,366 @@
|
||||||
|
import 'dart:collection';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:omnichess/constants/piece.dart';
|
||||||
|
|
||||||
|
class Position {
|
||||||
|
|
||||||
|
static List<int> get startingBoard => <int>[
|
||||||
|
Piece.blackRook, Piece.blackKnight, Piece.blackBishop, Piece.blackQueen, Piece.blackKing, Piece.blackBishop, Piece.blackKnight, Piece.blackRook,
|
||||||
|
Piece.blackPawn, Piece.blackPawn, Piece.blackPawn, Piece.blackPawn, Piece.blackPawn, Piece.blackPawn, Piece.blackPawn, Piece.blackPawn,
|
||||||
|
Piece.emptySquare, Piece.emptySquare, Piece.emptySquare, Piece.emptySquare, Piece.emptySquare, Piece.emptySquare, Piece.emptySquare, Piece.emptySquare,
|
||||||
|
Piece.emptySquare, Piece.emptySquare, Piece.emptySquare, Piece.emptySquare, Piece.emptySquare, Piece.emptySquare, Piece.emptySquare, Piece.emptySquare,
|
||||||
|
Piece.emptySquare, Piece.emptySquare, Piece.emptySquare, Piece.emptySquare, Piece.emptySquare, Piece.emptySquare, Piece.emptySquare, Piece.emptySquare,
|
||||||
|
Piece.emptySquare, Piece.emptySquare, Piece.emptySquare, Piece.emptySquare, Piece.emptySquare, Piece.emptySquare, Piece.emptySquare, Piece.emptySquare,
|
||||||
|
Piece.whitePawn, Piece.whitePawn, Piece.whitePawn, Piece.whitePawn, Piece.whitePawn, Piece.whitePawn, Piece.whitePawn, Piece.whitePawn,
|
||||||
|
Piece.whiteRook, Piece.whiteKnight, Piece.whiteBishop, Piece.whiteQueen, Piece.whiteKing, Piece.whiteBishop, Piece.whiteKnight, Piece.whiteRook,
|
||||||
|
];
|
||||||
|
|
||||||
|
List<int> board;
|
||||||
|
bool isWhiteTurn;
|
||||||
|
bool canWhiteCastleKingside;
|
||||||
|
bool canWhiteCastleQueenside;
|
||||||
|
bool canBlackCastleKingside;
|
||||||
|
bool canBlackCastleQueenside;
|
||||||
|
int? enPassantTargetSquare;
|
||||||
|
int halfmoveClock;
|
||||||
|
int fullmoveNumber;
|
||||||
|
|
||||||
|
String? get enPassantTargetSquareAlgebraic => null == enPassantTargetSquare ? null : String.fromCharCodes([
|
||||||
|
97 + (enPassantTargetSquare! % 8),
|
||||||
|
56 - (enPassantTargetSquare! ~/ 8)
|
||||||
|
]);
|
||||||
|
|
||||||
|
String get fen {
|
||||||
|
int i = 0;
|
||||||
|
final StringBuffer stringBuffer = StringBuffer();
|
||||||
|
while (i < 64) {
|
||||||
|
if (board[i] == 0) {
|
||||||
|
int emptyCount = 1;
|
||||||
|
i++;
|
||||||
|
while (i % 8 != 0 && board[i] == 0) {
|
||||||
|
emptyCount++;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
stringBuffer.write(emptyCount.toString());
|
||||||
|
} else {
|
||||||
|
stringBuffer.write(
|
||||||
|
Piece.fenCharacterByPiece[board[i]],
|
||||||
|
);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if (i < 64 && i % 8 == 0) {
|
||||||
|
stringBuffer.write("/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "$stringBuffer"
|
||||||
|
" "
|
||||||
|
"${isWhiteTurn ? "w" : "b"}"
|
||||||
|
" "
|
||||||
|
"${canWhiteCastleKingside ? "K" : ""}"
|
||||||
|
"${canWhiteCastleQueenside ? "Q" : ""}"
|
||||||
|
"${canBlackCastleKingside ? "k" : ""}"
|
||||||
|
"${canBlackCastleQueenside ? "q" : ""}"
|
||||||
|
"${!(canWhiteCastleKingside || canWhiteCastleQueenside || canBlackCastleKingside || canBlackCastleQueenside) ? "-" : ""}"
|
||||||
|
" "
|
||||||
|
"${enPassantTargetSquareAlgebraic ?? "-"}"
|
||||||
|
" "
|
||||||
|
"$halfmoveClock"
|
||||||
|
" "
|
||||||
|
"$fullmoveNumber";
|
||||||
|
}
|
||||||
|
|
||||||
|
Position({
|
||||||
|
required this.board,
|
||||||
|
required this.isWhiteTurn,
|
||||||
|
required this.canWhiteCastleKingside,
|
||||||
|
required this.canWhiteCastleQueenside,
|
||||||
|
required this.canBlackCastleKingside,
|
||||||
|
required this.canBlackCastleQueenside,
|
||||||
|
required this.enPassantTargetSquare,
|
||||||
|
required this.halfmoveClock,
|
||||||
|
required this.fullmoveNumber,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Position.from(Position position) => Position(
|
||||||
|
board : List<int>.from(position.board, growable: false),
|
||||||
|
isWhiteTurn : position.isWhiteTurn,
|
||||||
|
canWhiteCastleKingside : position.canWhiteCastleKingside,
|
||||||
|
canWhiteCastleQueenside: position.canWhiteCastleQueenside,
|
||||||
|
canBlackCastleKingside : position.canBlackCastleKingside,
|
||||||
|
canBlackCastleQueenside: position.canBlackCastleQueenside,
|
||||||
|
enPassantTargetSquare : position.enPassantTargetSquare,
|
||||||
|
halfmoveClock : position.halfmoveClock,
|
||||||
|
fullmoveNumber : position.fullmoveNumber,
|
||||||
|
);
|
||||||
|
|
||||||
|
factory Position.fromStartingPosition() => Position(
|
||||||
|
board : startingBoard,
|
||||||
|
isWhiteTurn : true,
|
||||||
|
canWhiteCastleKingside : true,
|
||||||
|
canWhiteCastleQueenside: true,
|
||||||
|
canBlackCastleKingside : true,
|
||||||
|
canBlackCastleQueenside: true,
|
||||||
|
enPassantTargetSquare : null,
|
||||||
|
halfmoveClock : 0,
|
||||||
|
fullmoveNumber : 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
factory Position.fromFen(
|
||||||
|
String fenPieces,
|
||||||
|
String fenTurnPlayer,
|
||||||
|
String fenCastlingRights,
|
||||||
|
String fenEnPassantTargetSquare,
|
||||||
|
String fenHalfmoveClock,
|
||||||
|
String fenFullmoveNumber,
|
||||||
|
) {
|
||||||
|
const String legalPieces = "pnbrqk";
|
||||||
|
int boardIndex = 0;
|
||||||
|
final List<int> newBoard = List<int>.filled(64, Piece.emptySquare);
|
||||||
|
for (int fenPiecesIndex = 0; fenPiecesIndex < fenPieces.length; fenPiecesIndex++) {
|
||||||
|
final String fenPiece = fenPieces[fenPiecesIndex];
|
||||||
|
final int? fenPieceNumber = int.tryParse(fenPiece);
|
||||||
|
if (fenPiece == "/") {
|
||||||
|
// Row separator, no action needed.
|
||||||
|
} else if (null != fenPieceNumber) {
|
||||||
|
boardIndex += fenPieceNumber;
|
||||||
|
} else if (legalPieces.contains(fenPiece.toLowerCase())) {
|
||||||
|
newBoard[boardIndex] = Piece.pieceByFenCharacter[fenPiece]!;
|
||||||
|
boardIndex++;
|
||||||
|
} else {
|
||||||
|
throw FormatException("Invalid character $fenPiece", fenPieces, fenPiecesIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (boardIndex != 64) {
|
||||||
|
throw FormatException("Invalid board size: Expected 64, Found $boardIndex", fenPieces);
|
||||||
|
}
|
||||||
|
final List<int> board = newBoard;
|
||||||
|
final bool isWhiteTurn = fenTurnPlayer == "w";
|
||||||
|
final bool canWhiteCastleKingside = fenCastlingRights.contains("K");
|
||||||
|
final bool canWhiteCastleQueenside = fenCastlingRights.contains("Q");
|
||||||
|
final bool canBlackCastleKingside = fenCastlingRights.contains("k");
|
||||||
|
final bool canBlackCastleQueenside = fenCastlingRights.contains("q");
|
||||||
|
final int? enPassantTargetSquare = fenEnPassantTargetSquare == "-"
|
||||||
|
? null
|
||||||
|
: squareStringToIndex(fenEnPassantTargetSquare);
|
||||||
|
final int halfmoveClock = int.parse(fenHalfmoveClock);
|
||||||
|
final int fullmoveNumber = int.parse(fenFullmoveNumber);
|
||||||
|
return Position(
|
||||||
|
board : board,
|
||||||
|
isWhiteTurn : isWhiteTurn,
|
||||||
|
canWhiteCastleKingside : canWhiteCastleKingside,
|
||||||
|
canWhiteCastleQueenside: canWhiteCastleQueenside,
|
||||||
|
canBlackCastleKingside : canBlackCastleKingside,
|
||||||
|
canBlackCastleQueenside: canBlackCastleQueenside,
|
||||||
|
enPassantTargetSquare : enPassantTargetSquare,
|
||||||
|
halfmoveClock : halfmoveClock,
|
||||||
|
fullmoveNumber : fullmoveNumber,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory Position.fromPositionCommand(String command) {
|
||||||
|
final Queue<String> commandComponents = Queue<String>.from(command.split(" "));
|
||||||
|
final String type = commandComponents.removeFirst();
|
||||||
|
Position position = switch (type) {
|
||||||
|
"fen" => (() {
|
||||||
|
final String fenPieces = commandComponents.removeFirst();
|
||||||
|
final String fenTurnPlayer = commandComponents.removeFirst();
|
||||||
|
final String fenCastlingRights = commandComponents.removeFirst();
|
||||||
|
final String fenEnPassantTargetSquare = commandComponents.removeFirst();
|
||||||
|
final String fenHalfmoveClock = commandComponents.removeFirst();
|
||||||
|
final String fenFullmoveNumber = commandComponents.removeFirst();
|
||||||
|
return Position.fromFen(
|
||||||
|
fenPieces,
|
||||||
|
fenTurnPlayer,
|
||||||
|
fenCastlingRights,
|
||||||
|
fenEnPassantTargetSquare,
|
||||||
|
fenHalfmoveClock,
|
||||||
|
fenFullmoveNumber,
|
||||||
|
);
|
||||||
|
})(),
|
||||||
|
"startpos" => Position.fromStartingPosition(),
|
||||||
|
_ => throw FormatException("Invalid position type $type"),
|
||||||
|
};
|
||||||
|
if (commandComponents.isEmpty) {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
commandComponents.removeFirst(); // "moves"
|
||||||
|
while (commandComponents.isNotEmpty) {
|
||||||
|
final String move = commandComponents.removeFirst();
|
||||||
|
position.playMove(move);
|
||||||
|
}
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
void playMoveIndices(int startSquare, int endSquare, [String? promotingPiece]) => isWhiteTurn
|
||||||
|
? _playMoveWhite(startSquare, endSquare, promotingPiece)
|
||||||
|
: _playMoveBlack(startSquare, endSquare, promotingPiece);
|
||||||
|
|
||||||
|
void playMove(String move) {
|
||||||
|
final (int startSquare, int endSquare, String? promotingPiece) = moveStringToIndices(move);
|
||||||
|
playMoveIndices(startSquare, endSquare, promotingPiece);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _playMoveWhite(int startSquare, int endSquare, String? promotingPiece) {
|
||||||
|
board[endSquare] = board[startSquare];
|
||||||
|
board[startSquare] = Piece.emptySquare;
|
||||||
|
isWhiteTurn = false;
|
||||||
|
if (0 == endSquare) { // sth moved to a8
|
||||||
|
canBlackCastleQueenside = false;
|
||||||
|
} else if (7 == endSquare) { // sth moved to h8
|
||||||
|
canBlackCastleKingside = false;
|
||||||
|
}
|
||||||
|
if (Piece.whitePawn == board[endSquare]) {
|
||||||
|
halfmoveClock = 0;
|
||||||
|
if (enPassantTargetSquare == endSquare) {
|
||||||
|
board[endSquare + 8] = Piece.emptySquare;
|
||||||
|
}
|
||||||
|
enPassantTargetSquare = (startSquare - endSquare == 16) ? startSquare - 8 : null;
|
||||||
|
if (endSquare < 8) {
|
||||||
|
board[endSquare] = Piece.pieceByFenCharacter[promotingPiece]!;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
halfmoveClock++;
|
||||||
|
enPassantTargetSquare = null;
|
||||||
|
if (Piece.whiteKing == board[endSquare]) {
|
||||||
|
canWhiteCastleQueenside = false;
|
||||||
|
canWhiteCastleKingside = false;
|
||||||
|
if (startSquare - endSquare == 2) { // queenside castle
|
||||||
|
board[56] = Piece.emptySquare;
|
||||||
|
board[59] = Piece.whiteRook;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (endSquare - startSquare == 2) { // kingside castle
|
||||||
|
board[63] = Piece.emptySquare;
|
||||||
|
board[61] = Piece.whiteRook;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (56 == startSquare) { // sth from a1 moved
|
||||||
|
canWhiteCastleQueenside = false;
|
||||||
|
} else if (63 == startSquare) { // sth from h1 moved
|
||||||
|
canWhiteCastleKingside = false;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _playMoveBlack(int startSquare, int endSquare, String? promotingPiece) {
|
||||||
|
board[endSquare] = board[startSquare];
|
||||||
|
board[startSquare] = Piece.emptySquare;
|
||||||
|
isWhiteTurn = true;
|
||||||
|
fullmoveNumber = fullmoveNumber + 1;
|
||||||
|
if (56 == endSquare) { // sth moved to a8
|
||||||
|
canWhiteCastleQueenside = false;
|
||||||
|
} else if (63 == endSquare) { // sth moved to h8
|
||||||
|
canWhiteCastleKingside = false;
|
||||||
|
}
|
||||||
|
if (Piece.blackPawn == board[endSquare]) {
|
||||||
|
halfmoveClock = 0;
|
||||||
|
if (enPassantTargetSquare == endSquare) {
|
||||||
|
board[endSquare - 8] = Piece.emptySquare;
|
||||||
|
}
|
||||||
|
enPassantTargetSquare = (endSquare - startSquare == 16) ? startSquare + 8 : null;
|
||||||
|
if (endSquare > 55) {
|
||||||
|
board[endSquare] = Piece.pieceByFenCharacter[promotingPiece]!;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
halfmoveClock++;
|
||||||
|
enPassantTargetSquare = null;
|
||||||
|
if (Piece.blackKing == board[endSquare]) {
|
||||||
|
canBlackCastleQueenside = false;
|
||||||
|
canBlackCastleKingside = false;
|
||||||
|
if (startSquare - endSquare == 2) { // queenside castle
|
||||||
|
board[0] = Piece.emptySquare;
|
||||||
|
board[3] = Piece.blackRook;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (endSquare - startSquare == 2) { // kingside castle
|
||||||
|
board[7] = Piece.emptySquare;
|
||||||
|
board[5] = Piece.blackRook;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (0 == startSquare) { // sth from a8 moved
|
||||||
|
canBlackCastleQueenside = false;
|
||||||
|
} else if (7 == startSquare) { // sth from h8 moved
|
||||||
|
canBlackCastleKingside = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setStartingPosition() {
|
||||||
|
board = startingBoard;
|
||||||
|
isWhiteTurn = true;
|
||||||
|
canWhiteCastleKingside = true;
|
||||||
|
canWhiteCastleQueenside = true;
|
||||||
|
canBlackCastleKingside = true;
|
||||||
|
canBlackCastleQueenside = true;
|
||||||
|
enPassantTargetSquare = null;
|
||||||
|
halfmoveClock = 0;
|
||||||
|
fullmoveNumber = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool setFenPosition(
|
||||||
|
String fenPieces,
|
||||||
|
String fenTurnPlayer,
|
||||||
|
String fenCastlingRights,
|
||||||
|
String fenEnPassantTargetSquare,
|
||||||
|
String fenHalfmoveClock,
|
||||||
|
String fenFullmoveNumber,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const String legalPieces = "pnbrqk";
|
||||||
|
int boardIndex = 0;
|
||||||
|
final List<int> newBoard = List<int>.filled(64, Piece.emptySquare);
|
||||||
|
for (int fenPiecesIndex = 0; fenPiecesIndex < fenPieces.length; fenPiecesIndex++) {
|
||||||
|
final String fenPiece = fenPieces[fenPiecesIndex];
|
||||||
|
final int? fenPieceNumber = int.tryParse(fenPiece);
|
||||||
|
if (fenPiece == "/") {
|
||||||
|
// Row separator, no action needed.
|
||||||
|
} else if (null != fenPieceNumber) {
|
||||||
|
boardIndex += fenPieceNumber;
|
||||||
|
} else if (legalPieces.contains(fenPiece.toLowerCase())) {
|
||||||
|
newBoard[boardIndex] = Piece.pieceByFenCharacter[fenPiece]!;
|
||||||
|
boardIndex++;
|
||||||
|
} else {
|
||||||
|
// If character is not recognized return false.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (boardIndex != 64) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
board = newBoard;
|
||||||
|
isWhiteTurn = fenTurnPlayer == "w";
|
||||||
|
canWhiteCastleKingside = fenCastlingRights.contains("K");
|
||||||
|
canWhiteCastleQueenside = fenCastlingRights.contains("Q");
|
||||||
|
canBlackCastleKingside = fenCastlingRights.contains("k");
|
||||||
|
canBlackCastleQueenside = fenCastlingRights.contains("q");
|
||||||
|
enPassantTargetSquare = fenEnPassantTargetSquare == "-"
|
||||||
|
? null
|
||||||
|
: squareStringToIndex(fenEnPassantTargetSquare);
|
||||||
|
halfmoveClock = int.parse(fenHalfmoveClock);
|
||||||
|
fullmoveNumber = int.parse(fenFullmoveNumber);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
stderr.writeln("error \"$e\"");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static String squareIndexToString(int index) => String.fromCharCodes([
|
||||||
|
97 + (index % 8),
|
||||||
|
56 - (index ~/ 8)
|
||||||
|
]);
|
||||||
|
|
||||||
|
static int squareStringToIndex(String square) => (56 - square.codeUnitAt(1)) * 8 + square.codeUnitAt(0) - 97;
|
||||||
|
|
||||||
|
static (int, int, String?) moveStringToIndices(String move) => (
|
||||||
|
(56 - move.codeUnitAt(1)) * 8 + move.codeUnitAt(0) - 97,
|
||||||
|
(56 - move.codeUnitAt(3)) * 8 + move.codeUnitAt(2) - 97,
|
||||||
|
move.length > 4 ? move.substring(4).toLowerCase() : null,
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
import "package:meta/meta.dart";
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class Piece {
|
||||||
|
// Private constructor to prevent instantiation.
|
||||||
|
Piece._();
|
||||||
|
|
||||||
|
static const emptySquare = 0;
|
||||||
|
static const whiteKing = 1;
|
||||||
|
static const whiteQueen = 2;
|
||||||
|
static const whiteRook = 3;
|
||||||
|
static const whiteBishop = 4;
|
||||||
|
static const whiteKnight = 5;
|
||||||
|
static const whitePawn = 6;
|
||||||
|
static const blackKing = 7;
|
||||||
|
static const blackQueen = 8;
|
||||||
|
static const blackRook = 9;
|
||||||
|
static const blackBishop = 10;
|
||||||
|
static const blackKnight = 11;
|
||||||
|
static const blackPawn = 12;
|
||||||
|
|
||||||
|
static const Map<String, int> pieceByFenCharacter = {
|
||||||
|
"k": blackKing,
|
||||||
|
"q": blackQueen,
|
||||||
|
"r": blackRook,
|
||||||
|
"b": blackBishop,
|
||||||
|
"n": blackKnight,
|
||||||
|
"p": blackPawn,
|
||||||
|
"K": whiteKing,
|
||||||
|
"Q": whiteQueen,
|
||||||
|
"R": whiteRook,
|
||||||
|
"B": whiteBishop,
|
||||||
|
"N": whiteKnight,
|
||||||
|
"P": whitePawn,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const Map<int, String> fenCharacterByPiece = <int, String>{
|
||||||
|
Piece.emptySquare: " ",
|
||||||
|
Piece.blackKing : "k",
|
||||||
|
Piece.blackQueen : "q",
|
||||||
|
Piece.blackRook : "r",
|
||||||
|
Piece.blackBishop: "b",
|
||||||
|
Piece.blackKnight: "n",
|
||||||
|
Piece.blackPawn : "p",
|
||||||
|
Piece.whiteKing : "K",
|
||||||
|
Piece.whiteQueen : "Q",
|
||||||
|
Piece.whiteRook : "R",
|
||||||
|
Piece.whiteBishop: "B",
|
||||||
|
Piece.whiteKnight: "N",
|
||||||
|
Piece.whitePawn : "P",
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool isWhite(int piece) => piece >= whiteKing && piece <= whitePawn;
|
||||||
|
static bool isBlack(int piece) => piece >= blackKing && piece <= blackPawn;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
class KingNotFoundException implements Exception {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,571 @@
|
||||||
|
part of "../legal_moves.dart";
|
||||||
|
|
||||||
|
class _LegalMovesBlack {
|
||||||
|
|
||||||
|
_LegalMovesBlack._();
|
||||||
|
|
||||||
|
static Stream<(String, Position)> _getLegalMoves(Position position) => _getValidOrCheckedMoves(position).where(((String, Position) move) => !_isBlackChecked(move.$2));
|
||||||
|
|
||||||
|
static Stream<(String, Position)> _getValidOrCheckedMoves(Position position) async* {
|
||||||
|
for (int i = 0; i < 64; i++) {
|
||||||
|
switch (position.board[i]) {
|
||||||
|
case Piece.emptySquare:
|
||||||
|
case Piece.whitePawn:
|
||||||
|
case Piece.whiteKnight:
|
||||||
|
case Piece.whiteBishop:
|
||||||
|
case Piece.whiteRook:
|
||||||
|
case Piece.whiteQueen:
|
||||||
|
case Piece.whiteKing:
|
||||||
|
break;
|
||||||
|
case Piece.blackPawn:
|
||||||
|
yield* _getBlackPawnMoves(position, i);
|
||||||
|
break;
|
||||||
|
case Piece.blackKnight:
|
||||||
|
yield* _getBlackKnightMoves(position, i);
|
||||||
|
break;
|
||||||
|
case Piece.blackBishop:
|
||||||
|
yield* _getBlackBishopMoves(position, i);
|
||||||
|
break;
|
||||||
|
case Piece.blackRook:
|
||||||
|
yield* _getBlackRookMoves(position, i);
|
||||||
|
break;
|
||||||
|
case Piece.blackQueen:
|
||||||
|
yield* _getBlackBishopMoves(position, i);
|
||||||
|
yield* _getBlackRookMoves(position, i);
|
||||||
|
break;
|
||||||
|
case Piece.blackKing:
|
||||||
|
yield* _getBlackKingMoves(position, i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<(String, Position)> _getBlackPawnMoves(Position position, int i) async* {
|
||||||
|
int j = i+8;
|
||||||
|
if (Piece.emptySquare == position.board[j]) { // pawn forward
|
||||||
|
if (i > 47) {
|
||||||
|
final String squareFrom = Position.squareIndexToString(i);
|
||||||
|
final String squareTo = Position.squareIndexToString(j);
|
||||||
|
yield ("$squareFrom${squareTo}q", Position.from(position)..playMoveIndices(i, j, "q"));
|
||||||
|
yield ("$squareFrom${squareTo}r", Position.from(position)..playMoveIndices(i, j, "r"));
|
||||||
|
yield ("$squareFrom${squareTo}b", Position.from(position)..playMoveIndices(i, j, "b"));
|
||||||
|
yield ("$squareFrom${squareTo}k", Position.from(position)..playMoveIndices(i, j, "k"));
|
||||||
|
} else {
|
||||||
|
final String squareFrom = Position.squareIndexToString(i);
|
||||||
|
yield ("$squareFrom${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
j = i+16;
|
||||||
|
if (i < 16 && Piece.emptySquare == position.board[j]) { // pawn more forward
|
||||||
|
yield ("$squareFrom${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j = i+7;
|
||||||
|
if (i % 8 > 0 && (j == position.enPassantTargetSquare || Piece.isWhite(position.board[j]))) { // pawn takes left
|
||||||
|
if (i > 47) {
|
||||||
|
final String squareFrom = Position.squareIndexToString(i);
|
||||||
|
final String squareTo = Position.squareIndexToString(j);
|
||||||
|
yield ("$squareFrom${squareTo}q", Position.from(position)..playMoveIndices(i, j, "q"));
|
||||||
|
yield ("$squareFrom${squareTo}r", Position.from(position)..playMoveIndices(i, j, "r"));
|
||||||
|
yield ("$squareFrom${squareTo}b", Position.from(position)..playMoveIndices(i, j, "b"));
|
||||||
|
yield ("$squareFrom${squareTo}k", Position.from(position)..playMoveIndices(i, j, "k"));
|
||||||
|
} else {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j = i+9;
|
||||||
|
if (i % 8 < 7 && (j == position.enPassantTargetSquare || Piece.isWhite(position.board[j]))) { // pawn takes right
|
||||||
|
if (i > 47) {
|
||||||
|
final String squareFrom = Position.squareIndexToString(i);
|
||||||
|
final String squareTo = Position.squareIndexToString(j);
|
||||||
|
yield ("$squareFrom${squareTo}q", Position.from(position)..playMoveIndices(i, j, "q"));
|
||||||
|
yield ("$squareFrom${squareTo}r", Position.from(position)..playMoveIndices(i, j, "r"));
|
||||||
|
yield ("$squareFrom${squareTo}b", Position.from(position)..playMoveIndices(i, j, "b"));
|
||||||
|
yield ("$squareFrom${squareTo}k", Position.from(position)..playMoveIndices(i, j, "k"));
|
||||||
|
} else {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<(String, Position)> _getBlackKnightMoves(Position position, int i) async* {
|
||||||
|
final bool canGoUp2 = i > 15;
|
||||||
|
final bool canGoUp1 = i > 7;
|
||||||
|
final bool canGoDown1 = i < 56;
|
||||||
|
final bool canGoDown2 = i < 48;
|
||||||
|
final int column = i % 8;
|
||||||
|
final bool canGoLeft2 = column > 1;
|
||||||
|
final bool canGoLeft1 = column > 0;
|
||||||
|
final bool canGoRight1 = column < 7;
|
||||||
|
final bool canGoRight2 = column < 6;
|
||||||
|
if (canGoUp1) {
|
||||||
|
if (canGoLeft2 && !Piece.isBlack(position.board[i-10])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(i-10)}", Position.from(position)..playMoveIndices(i, i-10));
|
||||||
|
}
|
||||||
|
if (canGoRight2 && !Piece.isBlack(position.board[i-6])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(i-6)}", Position.from(position)..playMoveIndices(i, i-6));
|
||||||
|
}
|
||||||
|
if (canGoUp2) {
|
||||||
|
if (canGoLeft1 && !Piece.isBlack(position.board[i-17])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(i-17)}", Position.from(position)..playMoveIndices(i, i-17));
|
||||||
|
}
|
||||||
|
if (canGoRight1 && !Piece.isBlack(position.board[i-15])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(i-15)}", Position.from(position)..playMoveIndices(i, i-15));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (canGoDown1) {
|
||||||
|
if (canGoLeft2 && !Piece.isBlack(position.board[i+6])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(i+6)}", Position.from(position)..playMoveIndices(i, i+6));
|
||||||
|
}
|
||||||
|
if (canGoRight2 && !Piece.isBlack(position.board[i+10])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(i+10)}", Position.from(position)..playMoveIndices(i, i+10));
|
||||||
|
}
|
||||||
|
if (canGoDown2) {
|
||||||
|
if (canGoLeft1 && !Piece.isBlack(position.board[i+15])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(i+15)}", Position.from(position)..playMoveIndices(i, i+15));
|
||||||
|
}
|
||||||
|
if (canGoRight1 && !Piece.isBlack(position.board[i+17])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(i+17)}", Position.from(position)..playMoveIndices(i, i+17));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<(String, Position)> _getBlackBishopMoves(Position position, int i) async* {
|
||||||
|
final int column = i % 8;
|
||||||
|
// up left
|
||||||
|
bool hasEncounteredPiece = false;
|
||||||
|
int k = column-1;
|
||||||
|
int j = i-9;
|
||||||
|
while (!hasEncounteredPiece && k >= 0 && j >= 0) {
|
||||||
|
if (Piece.emptySquare == position.board[j]) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
} else {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (Piece.isWhite(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k--;
|
||||||
|
j-=9;
|
||||||
|
}
|
||||||
|
// up right
|
||||||
|
hasEncounteredPiece = false;
|
||||||
|
k = column+1;
|
||||||
|
j = i-7;
|
||||||
|
while (!hasEncounteredPiece && k < 8 && j >= 0) {
|
||||||
|
if (Piece.emptySquare == position.board[j]) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
} else {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (Piece.isWhite(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k++;
|
||||||
|
j-=7;
|
||||||
|
}
|
||||||
|
// down left
|
||||||
|
hasEncounteredPiece = false;
|
||||||
|
k = column-1;
|
||||||
|
j = i+7;
|
||||||
|
while (!hasEncounteredPiece && k >= 0 && j < 64) {
|
||||||
|
if (Piece.emptySquare == position.board[j]) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
} else {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (Piece.isWhite(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k--;
|
||||||
|
j+=7;
|
||||||
|
}
|
||||||
|
// down right
|
||||||
|
hasEncounteredPiece = false;
|
||||||
|
k = column+1;
|
||||||
|
j = i+9;
|
||||||
|
while (!hasEncounteredPiece && k < 8 && j < 64) {
|
||||||
|
if (Piece.emptySquare == position.board[j]) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
} else {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (Piece.isWhite(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k++;
|
||||||
|
j+=9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static Stream<(String, Position)> _getBlackRookMoves(Position position, int i) async* {
|
||||||
|
final int column = i % 8;
|
||||||
|
// up
|
||||||
|
bool hasEncounteredPiece = false;
|
||||||
|
int j = i-8;
|
||||||
|
while (!hasEncounteredPiece && j > 0) {
|
||||||
|
if (Piece.emptySquare == position.board[j]) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
} else {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (Piece.isWhite(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j-=8;
|
||||||
|
}
|
||||||
|
// down
|
||||||
|
hasEncounteredPiece = false;
|
||||||
|
j = i+8;
|
||||||
|
while (!hasEncounteredPiece && j < 64) {
|
||||||
|
if (Piece.emptySquare == position.board[j]) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
} else {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (Piece.isWhite(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j+=8;
|
||||||
|
}
|
||||||
|
// left
|
||||||
|
hasEncounteredPiece = false;
|
||||||
|
int k = column-1;
|
||||||
|
j = i-1;
|
||||||
|
while (!hasEncounteredPiece && k >= 0) {
|
||||||
|
if (Piece.emptySquare == position.board[j]) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
} else {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (Piece.isWhite(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j-=1;
|
||||||
|
k-=1;
|
||||||
|
}
|
||||||
|
// right
|
||||||
|
hasEncounteredPiece = false;
|
||||||
|
k = column+1;
|
||||||
|
j = i+1;
|
||||||
|
while (!hasEncounteredPiece && k < 8) {
|
||||||
|
if (Piece.emptySquare == position.board[j]) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
} else {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (Piece.isWhite(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j+=1;
|
||||||
|
k+=1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<(String, Position)> _getBlackKingMoves(Position position, int i) async* {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
j = i+8;
|
||||||
|
if (j < 64 && !Piece.isBlack(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
if (i % 8 > 0) {
|
||||||
|
j = i-1;
|
||||||
|
if (!Piece.isBlack(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
j = i-9;
|
||||||
|
if (j >= 0 && !Piece.isBlack(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
j = i+7;
|
||||||
|
if (j < 64 && !Piece.isBlack(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i % 8 < 7) {
|
||||||
|
j = i+1;
|
||||||
|
if (!Piece.isBlack(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
j = i-7;
|
||||||
|
if (j >= 0 && !Piece.isBlack(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
j = i+9;
|
||||||
|
if (j < 64 && !Piece.isBlack(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!_isWhiteAttacking(position, i)) {
|
||||||
|
j = i-2;
|
||||||
|
if (
|
||||||
|
position.canBlackCastleQueenside &&
|
||||||
|
Piece.emptySquare == position.board[i-1] &&
|
||||||
|
Piece.emptySquare == position.board[i-2] &&
|
||||||
|
!_isWhiteAttacking(position, i-1)
|
||||||
|
) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
j = i+2;
|
||||||
|
if (
|
||||||
|
position.canBlackCastleKingside &&
|
||||||
|
Piece.emptySquare == position.board[i+1] &&
|
||||||
|
Piece.emptySquare == position.board[i+2] &&
|
||||||
|
!_isWhiteAttacking(position, i+1)
|
||||||
|
) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _getBlackKingSquare(Position position) {
|
||||||
|
for (int i = 0; i < 64; i++) {
|
||||||
|
if (position.board[i] == Piece.blackKing) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw KingNotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _isBlackChecked(Position position) => _isWhiteAttacking(position, _getBlackKingSquare(position));
|
||||||
|
|
||||||
|
static bool _isWhiteAttacking(Position position, int square) {
|
||||||
|
if (_isWhitePawnAttacking(position, square)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (_isWhiteKnightAttacking(position, square)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (_isWhiteBishopOrQueenAttacking(position, square)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (_isWhiteRookOrQueenAttacking(position, square)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (_isWhiteKingAttacking(position, square)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _isWhitePawnAttacking(Position position, int square) {
|
||||||
|
if (square < 8) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final int column = square % 8;
|
||||||
|
if (column > 0 && Piece.whitePawn == position.board[square + 7]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (column < 7 && Piece.whitePawn == position.board[square + 9]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _isWhiteKnightAttacking(Position position, int square) {
|
||||||
|
final bool canGoUp2 = square > 15;
|
||||||
|
final bool canGoUp1 = square > 7;
|
||||||
|
final bool canGoDown1 = square < 56;
|
||||||
|
final bool canGoDown2 = square < 48;
|
||||||
|
final int column = square % 8;
|
||||||
|
final bool canGoLeft2 = column > 1;
|
||||||
|
final bool canGoLeft1 = column > 0;
|
||||||
|
final bool canGoRight1 = column < 7;
|
||||||
|
final bool canGoRight2 = column < 6;
|
||||||
|
if (canGoUp1) {
|
||||||
|
if (canGoLeft2 && Piece.whiteKnight == position.board[square-10]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (canGoRight2 && Piece.whiteKnight == position.board[square-6]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (canGoUp2) {
|
||||||
|
if (canGoLeft1 && Piece.whiteKnight == position.board[square-17]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (canGoRight1 && Piece.whiteKnight == position.board[square-15]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (canGoDown1) {
|
||||||
|
if (canGoLeft2 && Piece.whiteKnight == position.board[square+6]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (canGoRight2 && Piece.whiteKnight == position.board[square+10]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (canGoDown2) {
|
||||||
|
if (canGoLeft1 && Piece.whiteKnight == position.board[square+15]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (canGoRight1 && Piece.whiteKnight == position.board[square+17]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _isWhiteBishopOrQueenAttacking(Position position, int square) {
|
||||||
|
final int column = square % 8;
|
||||||
|
// up left
|
||||||
|
bool hasEncounteredPiece = false;
|
||||||
|
int k = column-1;
|
||||||
|
int j = square-9;
|
||||||
|
while (!hasEncounteredPiece && k >= 0 && j >= 0) {
|
||||||
|
final int piece = position.board[j];
|
||||||
|
if (Piece.emptySquare != piece) {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (piece case Piece.whiteBishop || Piece.whiteQueen) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k--;
|
||||||
|
j-=9;
|
||||||
|
}
|
||||||
|
// up right
|
||||||
|
hasEncounteredPiece = false;
|
||||||
|
k = column+1;
|
||||||
|
j = square-7;
|
||||||
|
while (!hasEncounteredPiece && k < 8 && j >= 0) {
|
||||||
|
final int piece = position.board[j];
|
||||||
|
if (Piece.emptySquare != piece) {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (piece case Piece.whiteBishop || Piece.whiteQueen) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k++;
|
||||||
|
j-=7;
|
||||||
|
}
|
||||||
|
// down left
|
||||||
|
hasEncounteredPiece = false;
|
||||||
|
k = column-1;
|
||||||
|
j = square+7;
|
||||||
|
while (!hasEncounteredPiece && k >= 0 && j < 64) {
|
||||||
|
final int piece = position.board[j];
|
||||||
|
if (Piece.emptySquare != piece) {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (piece case Piece.whiteBishop || Piece.whiteQueen) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k--;
|
||||||
|
j+=7;
|
||||||
|
}
|
||||||
|
// down right
|
||||||
|
hasEncounteredPiece = false;
|
||||||
|
k = column+1;
|
||||||
|
j = square+9;
|
||||||
|
while (!hasEncounteredPiece && k < 8 && j < 64) {
|
||||||
|
final int piece = position.board[j];
|
||||||
|
if (Piece.emptySquare != piece) {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (piece case Piece.whiteBishop || Piece.whiteQueen) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k++;
|
||||||
|
j+=9;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _isWhiteRookOrQueenAttacking(Position position, int square) {
|
||||||
|
final int column = square % 8;
|
||||||
|
// up
|
||||||
|
bool hasEncounteredPiece = false;
|
||||||
|
int j = square-8;
|
||||||
|
while (!hasEncounteredPiece && j > 0) {
|
||||||
|
final int piece = position.board[j];
|
||||||
|
if (Piece.emptySquare != piece) {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (piece case Piece.whiteRook || Piece.whiteQueen) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j-=8;
|
||||||
|
}
|
||||||
|
// down
|
||||||
|
hasEncounteredPiece = false;
|
||||||
|
j = square + 8;
|
||||||
|
while (!hasEncounteredPiece && j < 64) {
|
||||||
|
final int piece = position.board[j];
|
||||||
|
if (Piece.emptySquare != piece) {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (piece case Piece.whiteRook || Piece.whiteQueen) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j+=8;
|
||||||
|
}
|
||||||
|
// left
|
||||||
|
hasEncounteredPiece = false;
|
||||||
|
j = square - 1;
|
||||||
|
int k = column - 1;
|
||||||
|
while (!hasEncounteredPiece && k > 0) {
|
||||||
|
final int piece = position.board[j];
|
||||||
|
if (Piece.emptySquare != piece) {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (piece case Piece.whiteRook || Piece.whiteQueen) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j--;
|
||||||
|
k--;
|
||||||
|
}
|
||||||
|
// right
|
||||||
|
hasEncounteredPiece = false;
|
||||||
|
j = square + 1;
|
||||||
|
k = column + 1;
|
||||||
|
while (!hasEncounteredPiece && k < 8) {
|
||||||
|
final int piece = position.board[j];
|
||||||
|
if (Piece.emptySquare != piece) {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (piece case Piece.whiteRook || Piece.whiteQueen) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j++;
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _isWhiteKingAttacking(Position position, int square) {
|
||||||
|
if (square > 7 && Piece.whiteKing == position.board[square - 8]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (square < 56 && Piece.whiteKing == position.board[square + 8]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (square % 8 > 0) {
|
||||||
|
if (Piece.whiteKing == position.board[square - 1]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (square > 7 && Piece.whiteKing == position.board[square - 9]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (square < 56 && Piece.whiteKing == position.board[square + 7]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (square % 8 < 7) {
|
||||||
|
if (Piece.whiteKing == position.board[square + 1]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (square > 7 && Piece.whiteKing == position.board[square - 7]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (square < 56 && Piece.whiteKing == position.board[square + 9]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,571 @@
|
||||||
|
part of "../legal_moves.dart";
|
||||||
|
|
||||||
|
class _LegalMovesWhite {
|
||||||
|
|
||||||
|
_LegalMovesWhite._();
|
||||||
|
|
||||||
|
static Stream<(String, Position)> _getLegalMoves(Position position) => _getValidOrCheckedMoves(position).where(((String, Position) move) => !_isWhiteChecked(move.$2));
|
||||||
|
|
||||||
|
static Stream<(String, Position)> _getValidOrCheckedMoves(Position position) async* {
|
||||||
|
for (int i = 0; i < 64; i++) {
|
||||||
|
switch (position.board[i]) {
|
||||||
|
case Piece.emptySquare:
|
||||||
|
case Piece.blackPawn:
|
||||||
|
case Piece.blackKnight:
|
||||||
|
case Piece.blackBishop:
|
||||||
|
case Piece.blackRook:
|
||||||
|
case Piece.blackQueen:
|
||||||
|
case Piece.blackKing:
|
||||||
|
break;
|
||||||
|
case Piece.whitePawn:
|
||||||
|
yield* _getWhitePawnMoves(position, i);
|
||||||
|
break;
|
||||||
|
case Piece.whiteKnight:
|
||||||
|
yield* _getWhiteKnightMoves(position, i);
|
||||||
|
break;
|
||||||
|
case Piece.whiteBishop:
|
||||||
|
yield* _getWhiteBishopMoves(position, i);
|
||||||
|
break;
|
||||||
|
case Piece.whiteRook:
|
||||||
|
yield* _getWhiteRookMoves(position, i);
|
||||||
|
break;
|
||||||
|
case Piece.whiteQueen:
|
||||||
|
yield* _getWhiteBishopMoves(position, i);
|
||||||
|
yield* _getWhiteRookMoves(position, i);
|
||||||
|
break;
|
||||||
|
case Piece.whiteKing:
|
||||||
|
yield* _getWhiteKingMoves(position, i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<(String, Position)> _getWhitePawnMoves(Position position, int i) async* {
|
||||||
|
int j = i-8;
|
||||||
|
if (Piece.emptySquare == position.board[j]) { // pawn forward
|
||||||
|
if (i < 16) {
|
||||||
|
final String squareFrom = Position.squareIndexToString(i);
|
||||||
|
final String squareTo = Position.squareIndexToString(j);
|
||||||
|
yield ("$squareFrom${squareTo}q", Position.from(position)..playMoveIndices(i, j, "q"));
|
||||||
|
yield ("$squareFrom${squareTo}r", Position.from(position)..playMoveIndices(i, j, "r"));
|
||||||
|
yield ("$squareFrom${squareTo}b", Position.from(position)..playMoveIndices(i, j, "b"));
|
||||||
|
yield ("$squareFrom${squareTo}k", Position.from(position)..playMoveIndices(i, j, "k"));
|
||||||
|
} else {
|
||||||
|
final String squareFrom = Position.squareIndexToString(i);
|
||||||
|
yield ("$squareFrom${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
j = i-16;
|
||||||
|
if (i > 47 && Piece.emptySquare == position.board[j]) { // pawn more forward
|
||||||
|
yield ("$squareFrom${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j = i-9;
|
||||||
|
if (i % 8 > 0 && (j == position.enPassantTargetSquare || Piece.isBlack(position.board[j]))) { // pawn takes left
|
||||||
|
if (i < 16) {
|
||||||
|
final String squareFrom = Position.squareIndexToString(i);
|
||||||
|
final String squareTo = Position.squareIndexToString(j);
|
||||||
|
yield ("$squareFrom${squareTo}q", Position.from(position)..playMoveIndices(i, j, "q"));
|
||||||
|
yield ("$squareFrom${squareTo}r", Position.from(position)..playMoveIndices(i, j, "r"));
|
||||||
|
yield ("$squareFrom${squareTo}b", Position.from(position)..playMoveIndices(i, j, "b"));
|
||||||
|
yield ("$squareFrom${squareTo}k", Position.from(position)..playMoveIndices(i, j, "k"));
|
||||||
|
} else {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j = i-7;
|
||||||
|
if (i % 8 < 7 && (j == position.enPassantTargetSquare || Piece.isBlack(position.board[j]))) { // pawn takes right
|
||||||
|
if (i < 16) {
|
||||||
|
final String squareFrom = Position.squareIndexToString(i);
|
||||||
|
final String squareTo = Position.squareIndexToString(j);
|
||||||
|
yield ("$squareFrom${squareTo}q", Position.from(position)..playMoveIndices(i, j, "q"));
|
||||||
|
yield ("$squareFrom${squareTo}r", Position.from(position)..playMoveIndices(i, j, "r"));
|
||||||
|
yield ("$squareFrom${squareTo}b", Position.from(position)..playMoveIndices(i, j, "b"));
|
||||||
|
yield ("$squareFrom${squareTo}k", Position.from(position)..playMoveIndices(i, j, "k"));
|
||||||
|
} else {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<(String, Position)> _getWhiteKnightMoves(Position position, int i) async* {
|
||||||
|
final bool canGoUp2 = i > 15;
|
||||||
|
final bool canGoUp1 = i > 7;
|
||||||
|
final bool canGoDown1 = i < 56;
|
||||||
|
final bool canGoDown2 = i < 48;
|
||||||
|
final int column = i % 8;
|
||||||
|
final bool canGoLeft2 = column > 1;
|
||||||
|
final bool canGoLeft1 = column > 0;
|
||||||
|
final bool canGoRight1 = column < 7;
|
||||||
|
final bool canGoRight2 = column < 6;
|
||||||
|
if (canGoUp1) {
|
||||||
|
if (canGoLeft2 && !Piece.isWhite(position.board[i-10])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(i-10)}", Position.from(position)..playMoveIndices(i, i-10));
|
||||||
|
}
|
||||||
|
if (canGoRight2 && !Piece.isWhite(position.board[i-6])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(i-6)}", Position.from(position)..playMoveIndices(i, i-6));
|
||||||
|
}
|
||||||
|
if (canGoUp2) {
|
||||||
|
if (canGoLeft1 && !Piece.isWhite(position.board[i-17])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(i-17)}", Position.from(position)..playMoveIndices(i, i-17));
|
||||||
|
}
|
||||||
|
if (canGoRight1 && !Piece.isWhite(position.board[i-15])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(i-15)}", Position.from(position)..playMoveIndices(i, i-15));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (canGoDown1) {
|
||||||
|
if (canGoLeft2 && !Piece.isWhite(position.board[i+6])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(i+6)}", Position.from(position)..playMoveIndices(i, i+6));
|
||||||
|
}
|
||||||
|
if (canGoRight2 && !Piece.isWhite(position.board[i+10])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(i+10)}", Position.from(position)..playMoveIndices(i, i+10));
|
||||||
|
}
|
||||||
|
if (canGoDown2) {
|
||||||
|
if (canGoLeft1 && !Piece.isWhite(position.board[i+15])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(i+15)}", Position.from(position)..playMoveIndices(i, i+15));
|
||||||
|
}
|
||||||
|
if (canGoRight1 && !Piece.isWhite(position.board[i+17])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(i+17)}", Position.from(position)..playMoveIndices(i, i+17));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<(String, Position)> _getWhiteBishopMoves(Position position, int i) async* {
|
||||||
|
final int column = i % 8;
|
||||||
|
// up left
|
||||||
|
bool hasEncounteredPiece = false;
|
||||||
|
int k = column-1;
|
||||||
|
int j = i-9;
|
||||||
|
while (!hasEncounteredPiece && k >= 0 && j >= 0) {
|
||||||
|
if (Piece.emptySquare == position.board[j]) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
} else {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (Piece.isBlack(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k--;
|
||||||
|
j-=9;
|
||||||
|
}
|
||||||
|
// up right
|
||||||
|
hasEncounteredPiece = false;
|
||||||
|
k = column+1;
|
||||||
|
j = i-7;
|
||||||
|
while (!hasEncounteredPiece && k < 8 && j >= 0) {
|
||||||
|
if (Piece.emptySquare == position.board[j]) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
} else {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (Piece.isBlack(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k++;
|
||||||
|
j-=7;
|
||||||
|
}
|
||||||
|
// down left
|
||||||
|
hasEncounteredPiece = false;
|
||||||
|
k = column-1;
|
||||||
|
j = i+7;
|
||||||
|
while (!hasEncounteredPiece && k >= 0 && j < 64) {
|
||||||
|
if (Piece.emptySquare == position.board[j]) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
} else {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (Piece.isBlack(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k--;
|
||||||
|
j+=7;
|
||||||
|
}
|
||||||
|
// down right
|
||||||
|
hasEncounteredPiece = false;
|
||||||
|
k = column+1;
|
||||||
|
j = i+9;
|
||||||
|
while (!hasEncounteredPiece && k < 8 && j < 64) {
|
||||||
|
if (Piece.emptySquare == position.board[j]) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
} else {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (Piece.isBlack(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k++;
|
||||||
|
j+=9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static Stream<(String, Position)> _getWhiteRookMoves(Position position, int i) async* {
|
||||||
|
final int column = i % 8;
|
||||||
|
// up
|
||||||
|
bool hasEncounteredPiece = false;
|
||||||
|
int j = i-8;
|
||||||
|
while (!hasEncounteredPiece && j > 0) {
|
||||||
|
if (Piece.emptySquare == position.board[j]) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
} else {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (Piece.isBlack(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j-=8;
|
||||||
|
}
|
||||||
|
// down
|
||||||
|
hasEncounteredPiece = false;
|
||||||
|
j = i+8;
|
||||||
|
while (!hasEncounteredPiece && j < 64) {
|
||||||
|
if (Piece.emptySquare == position.board[j]) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
} else {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (Piece.isBlack(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j+=8;
|
||||||
|
}
|
||||||
|
// left
|
||||||
|
hasEncounteredPiece = false;
|
||||||
|
int k = column-1;
|
||||||
|
j = i-1;
|
||||||
|
while (!hasEncounteredPiece && k >= 0) {
|
||||||
|
if (Piece.emptySquare == position.board[j]) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
} else {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (Piece.isBlack(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j-=1;
|
||||||
|
k-=1;
|
||||||
|
}
|
||||||
|
// right
|
||||||
|
hasEncounteredPiece = false;
|
||||||
|
k = column+1;
|
||||||
|
j = i+1;
|
||||||
|
while (!hasEncounteredPiece && k < 8) {
|
||||||
|
if (Piece.emptySquare == position.board[j]) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
} else {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (Piece.isBlack(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j+=1;
|
||||||
|
k+=1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<(String, Position)> _getWhiteKingMoves(Position position, int i) async* {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
j = i+8;
|
||||||
|
if (j < 64 && !Piece.isWhite(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
if (i % 8 > 0) {
|
||||||
|
j = i-1;
|
||||||
|
if (!Piece.isWhite(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
j = i-9;
|
||||||
|
if (j >= 0 && !Piece.isWhite(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
j = i+7;
|
||||||
|
if (j < 64 && !Piece.isWhite(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i % 8 < 7) {
|
||||||
|
j = i+1;
|
||||||
|
if (!Piece.isWhite(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
j = i-7;
|
||||||
|
if (j >= 0 && !Piece.isWhite(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
j = i+9;
|
||||||
|
if (j < 64 && !Piece.isWhite(position.board[j])) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!_isBlackAttacking(position, i)) {
|
||||||
|
j = i-2;
|
||||||
|
if (
|
||||||
|
position.canWhiteCastleQueenside &&
|
||||||
|
Piece.emptySquare == position.board[i-1] &&
|
||||||
|
Piece.emptySquare == position.board[i-2] &&
|
||||||
|
!_isBlackAttacking(position, i-1)
|
||||||
|
) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
j = i+2;
|
||||||
|
if (
|
||||||
|
position.canWhiteCastleKingside &&
|
||||||
|
Piece.emptySquare == position.board[i+1] &&
|
||||||
|
Piece.emptySquare == position.board[i+2] &&
|
||||||
|
!_isBlackAttacking(position, i+1)
|
||||||
|
) {
|
||||||
|
yield ("${Position.squareIndexToString(i)}${Position.squareIndexToString(j)}", Position.from(position)..playMoveIndices(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _getWhiteKingSquare(Position position) {
|
||||||
|
for (int i = 0; i < 64; i++) {
|
||||||
|
if (position.board[i] == Piece.whiteKing) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw KingNotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _isWhiteChecked(Position position) => _isBlackAttacking(position, _getWhiteKingSquare(position));
|
||||||
|
|
||||||
|
static bool _isBlackAttacking(Position position, int square) {
|
||||||
|
if (_isBlackPawnAttacking(position, square)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (_isBlackKnightAttacking(position, square)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (_isBlackBishopOrQueenAttacking(position, square)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (_isBlackRookOrQueenAttacking(position, square)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (_isBlackKingAttacking(position, square)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _isBlackPawnAttacking(Position position, int square) {
|
||||||
|
if (square < 8) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final int column = square % 8;
|
||||||
|
if (column > 0 && Piece.blackPawn == position.board[square - 9]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (column < 7 && Piece.blackPawn == position.board[square - 7]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _isBlackKnightAttacking(Position position, int square) {
|
||||||
|
final bool canGoUp2 = square > 15;
|
||||||
|
final bool canGoUp1 = square > 7;
|
||||||
|
final bool canGoDown1 = square < 56;
|
||||||
|
final bool canGoDown2 = square < 48;
|
||||||
|
final int column = square % 8;
|
||||||
|
final bool canGoLeft2 = column > 1;
|
||||||
|
final bool canGoLeft1 = column > 0;
|
||||||
|
final bool canGoRight1 = column < 7;
|
||||||
|
final bool canGoRight2 = column < 6;
|
||||||
|
if (canGoUp1) {
|
||||||
|
if (canGoLeft2 && Piece.blackKnight == position.board[square-10]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (canGoRight2 && Piece.blackKnight == position.board[square-6]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (canGoUp2) {
|
||||||
|
if (canGoLeft1 && Piece.blackKnight == position.board[square-17]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (canGoRight1 && Piece.blackKnight == position.board[square-15]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (canGoDown1) {
|
||||||
|
if (canGoLeft2 && Piece.blackKnight == position.board[square+6]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (canGoRight2 && Piece.blackKnight == position.board[square+10]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (canGoDown2) {
|
||||||
|
if (canGoLeft1 && Piece.blackKnight == position.board[square+15]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (canGoRight1 && Piece.blackKnight == position.board[square+17]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _isBlackBishopOrQueenAttacking(Position position, int square) {
|
||||||
|
final int column = square % 8;
|
||||||
|
// up left
|
||||||
|
bool hasEncounteredPiece = false;
|
||||||
|
int k = column-1;
|
||||||
|
int j = square-9;
|
||||||
|
while (!hasEncounteredPiece && k >= 0 && j >= 0) {
|
||||||
|
final int piece = position.board[j];
|
||||||
|
if (Piece.emptySquare != piece) {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (piece case Piece.blackBishop || Piece.blackQueen) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k--;
|
||||||
|
j-=9;
|
||||||
|
}
|
||||||
|
// up right
|
||||||
|
hasEncounteredPiece = false;
|
||||||
|
k = column+1;
|
||||||
|
j = square-7;
|
||||||
|
while (!hasEncounteredPiece && k < 8 && j >= 0) {
|
||||||
|
final int piece = position.board[j];
|
||||||
|
if (Piece.emptySquare != piece) {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (piece case Piece.blackBishop || Piece.blackQueen) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k++;
|
||||||
|
j-=7;
|
||||||
|
}
|
||||||
|
// down left
|
||||||
|
hasEncounteredPiece = false;
|
||||||
|
k = column-1;
|
||||||
|
j = square+7;
|
||||||
|
while (!hasEncounteredPiece && k >= 0 && j < 64) {
|
||||||
|
final int piece = position.board[j];
|
||||||
|
if (Piece.emptySquare != piece) {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (piece case Piece.blackBishop || Piece.blackQueen) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k--;
|
||||||
|
j+=7;
|
||||||
|
}
|
||||||
|
// down right
|
||||||
|
hasEncounteredPiece = false;
|
||||||
|
k = column+1;
|
||||||
|
j = square+9;
|
||||||
|
while (!hasEncounteredPiece && k < 8 && j < 64) {
|
||||||
|
final int piece = position.board[j];
|
||||||
|
if (Piece.emptySquare != piece) {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (piece case Piece.blackBishop || Piece.blackQueen) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k++;
|
||||||
|
j+=9;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _isBlackRookOrQueenAttacking(Position position, int square) {
|
||||||
|
final int column = square % 8;
|
||||||
|
// up
|
||||||
|
bool hasEncounteredPiece = false;
|
||||||
|
int j = square-8;
|
||||||
|
while (!hasEncounteredPiece && j > 0) {
|
||||||
|
final int piece = position.board[j];
|
||||||
|
if (Piece.emptySquare != piece) {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (piece case Piece.blackRook || Piece.blackQueen) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j-=8;
|
||||||
|
}
|
||||||
|
// down
|
||||||
|
hasEncounteredPiece = false;
|
||||||
|
j = square + 8;
|
||||||
|
while (!hasEncounteredPiece && j < 64) {
|
||||||
|
final int piece = position.board[j];
|
||||||
|
if (Piece.emptySquare != piece) {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (piece case Piece.blackRook || Piece.blackQueen) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j+=8;
|
||||||
|
}
|
||||||
|
// left
|
||||||
|
hasEncounteredPiece = false;
|
||||||
|
j = square - 1;
|
||||||
|
int k = column - 1;
|
||||||
|
while (!hasEncounteredPiece && k > 0) {
|
||||||
|
final int piece = position.board[j];
|
||||||
|
if (Piece.emptySquare != piece) {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (piece case Piece.blackRook || Piece.blackQueen) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j--;
|
||||||
|
k--;
|
||||||
|
}
|
||||||
|
// right
|
||||||
|
hasEncounteredPiece = false;
|
||||||
|
j = square + 1;
|
||||||
|
k = column + 1;
|
||||||
|
while (!hasEncounteredPiece && k < 8) {
|
||||||
|
final int piece = position.board[j];
|
||||||
|
if (Piece.emptySquare != piece) {
|
||||||
|
hasEncounteredPiece = true;
|
||||||
|
if (piece case Piece.blackRook || Piece.blackQueen) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j++;
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _isBlackKingAttacking(Position position, int square) {
|
||||||
|
if (square > 7 && Piece.blackKing == position.board[square - 8]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (square < 56 && Piece.blackKing == position.board[square + 8]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (square % 8 > 0) {
|
||||||
|
if (Piece.blackKing == position.board[square - 1]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (square > 7 && Piece.blackKing == position.board[square - 9]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (square < 56 && Piece.blackKing == position.board[square + 7]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (square % 8 < 7) {
|
||||||
|
if (Piece.blackKing == position.board[square + 1]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (square > 7 && Piece.blackKing == position.board[square - 7]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (square < 56 && Piece.blackKing == position.board[square + 9]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import 'package:omnichess/classes/position.dart';
|
||||||
|
import 'package:omnichess/constants/piece.dart';
|
||||||
|
import 'package:omnichess/exceptions/king_not_found.dart';
|
||||||
|
|
||||||
|
part "_partials/legal_moves_black.dart";
|
||||||
|
part "_partials/legal_moves_white.dart";
|
||||||
|
|
||||||
|
class LegalMoves {
|
||||||
|
LegalMoves._();
|
||||||
|
|
||||||
|
static Stream<(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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
import "dart:async";
|
||||||
|
import "dart:collection";
|
||||||
|
import "dart:convert";
|
||||||
|
import "dart:io";
|
||||||
|
|
||||||
|
import "package:omnichess/classes/best_move_searcher.dart";
|
||||||
|
import "package:omnichess/classes/position.dart";
|
||||||
|
import "package:omnichess/constants/piece.dart";
|
||||||
|
|
||||||
|
class Omnichess {
|
||||||
|
|
||||||
|
BestMoveSearcher bestMoveSearcher = BestMoveSearcher();
|
||||||
|
String positionCommand = "startpos";
|
||||||
|
String? bestMove;
|
||||||
|
|
||||||
|
bool debug = false;
|
||||||
|
|
||||||
|
Omnichess();
|
||||||
|
|
||||||
|
void _positionCommand(Queue<String> inputComponents) {
|
||||||
|
positionCommand = inputComponents.join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _goCommand(Queue<String> inputComponents) async {
|
||||||
|
final Position position = Position.fromPositionCommand(positionCommand);
|
||||||
|
final String? bestMove = await bestMoveSearcher.search(position);
|
||||||
|
print("bestmove ${bestMove ?? "(none)"}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _stopCommand() async {
|
||||||
|
await bestMoveSearcher.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> elaborate(String line) async {
|
||||||
|
final Queue<String> inputComponents = Queue.from(line.split(" "));
|
||||||
|
final String command = inputComponents.removeFirst();
|
||||||
|
if (command == "uci") {
|
||||||
|
print("id name Omnichess");
|
||||||
|
print("id author Dany Thach");
|
||||||
|
print("uciok");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (command == "debug") {
|
||||||
|
debug = "on" == inputComponents.first;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (command == "isready") {
|
||||||
|
print("readyok");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (command == "ucinewgame") {
|
||||||
|
positionCommand = "startpos";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (command == "position") {
|
||||||
|
_positionCommand(inputComponents);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (command == "go") {
|
||||||
|
await _goCommand(inputComponents);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (command == "stop") {
|
||||||
|
await _stopCommand();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (command == "quit") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void debugPrintPosition() {
|
||||||
|
final Position position = Position.fromPositionCommand(positionCommand);
|
||||||
|
for (int i = 0; i < position.board.length; i++) {
|
||||||
|
if (i % 8 == 0) {
|
||||||
|
stdout.write("info string ");
|
||||||
|
}
|
||||||
|
stdout.write(Piece.fenCharacterByPiece[position.board[i]]);
|
||||||
|
if (i % 8 == 7) {
|
||||||
|
stdout.writeln();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print(position.isWhiteTurn ? "info string white to move" : "info string black to move");
|
||||||
|
if (position.canWhiteCastleKingside) {
|
||||||
|
print("info string white can castle kingside");
|
||||||
|
}
|
||||||
|
if (position.canWhiteCastleQueenside) {
|
||||||
|
print("info string white can castle queenside");
|
||||||
|
}
|
||||||
|
if (position.canBlackCastleKingside) {
|
||||||
|
print("info string black can castle kingside");
|
||||||
|
}
|
||||||
|
if (position.canBlackCastleQueenside) {
|
||||||
|
print("info string black can castle queenside");
|
||||||
|
}
|
||||||
|
if (null != position.enPassantTargetSquare) {
|
||||||
|
print("info string en passant possible on ${position.enPassantTargetSquareAlgebraic}");
|
||||||
|
}
|
||||||
|
print("info string halfmove clock ${position.halfmoveClock}");
|
||||||
|
print("info string fullmove number ${position.fullmoveNumber}");
|
||||||
|
print("info string position fen ${position.fen}");
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() async {
|
||||||
|
late StreamSubscription<String> inputSubscription;
|
||||||
|
inputSubscription = stdin
|
||||||
|
.transform(utf8.decoder)
|
||||||
|
.transform(LineSplitter())
|
||||||
|
.listen((String line) async {
|
||||||
|
final String input = line.trim();
|
||||||
|
final bool keepGoing = await elaborate(input);
|
||||||
|
if (!keepGoing) {
|
||||||
|
await inputSubscription.cancel();
|
||||||
|
}
|
||||||
|
if (debug) {
|
||||||
|
debugPrintPosition();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await inputSubscription.asFuture();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,402 @@
|
||||||
|
# Generated by pub
|
||||||
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
|
packages:
|
||||||
|
_fe_analyzer_shared:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _fe_analyzer_shared
|
||||||
|
sha256: "03f6da266a27a4538a69295ec142cb5717d7d4e5727b84658b63e1e1509bac9c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "79.0.0"
|
||||||
|
_macros:
|
||||||
|
dependency: transitive
|
||||||
|
description: dart
|
||||||
|
source: sdk
|
||||||
|
version: "0.3.3"
|
||||||
|
analyzer:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: analyzer
|
||||||
|
sha256: c9040fc56483c22a5e04a9f6a251313118b1a3c42423770623128fa484115643
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.2.0"
|
||||||
|
args:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.6.0"
|
||||||
|
async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: async
|
||||||
|
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.12.0"
|
||||||
|
boolean_selector:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: boolean_selector
|
||||||
|
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: collection
|
||||||
|
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.19.1"
|
||||||
|
convert:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: convert
|
||||||
|
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.2"
|
||||||
|
coverage:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: coverage
|
||||||
|
sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.11.1"
|
||||||
|
crypto:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.6"
|
||||||
|
file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file
|
||||||
|
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.1"
|
||||||
|
frontend_server_client:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: frontend_server_client
|
||||||
|
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.0"
|
||||||
|
glob:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: glob
|
||||||
|
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.3"
|
||||||
|
http_multi_server:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_multi_server
|
||||||
|
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.2"
|
||||||
|
http_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.2"
|
||||||
|
io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: io
|
||||||
|
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.5"
|
||||||
|
js:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: js
|
||||||
|
sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.1"
|
||||||
|
lints:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: lints
|
||||||
|
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.1.1"
|
||||||
|
logging:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: logging
|
||||||
|
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
|
macros:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: macros
|
||||||
|
sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.3-main.0"
|
||||||
|
matcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: matcher
|
||||||
|
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.12.17"
|
||||||
|
meta:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: meta
|
||||||
|
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.16.0"
|
||||||
|
mime:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
|
node_preamble:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: node_preamble
|
||||||
|
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
|
package_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_config
|
||||||
|
sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
|
path:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path
|
||||||
|
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.9.1"
|
||||||
|
pool:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pool
|
||||||
|
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.1"
|
||||||
|
pub_semver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pub_semver
|
||||||
|
sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.5"
|
||||||
|
shelf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf
|
||||||
|
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.2"
|
||||||
|
shelf_packages_handler:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_packages_handler
|
||||||
|
sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
|
shelf_static:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_static
|
||||||
|
sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.3"
|
||||||
|
shelf_web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_web_socket
|
||||||
|
sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1"
|
||||||
|
source_map_stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_map_stack_trace
|
||||||
|
sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
source_maps:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_maps
|
||||||
|
sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.13"
|
||||||
|
source_span:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_span
|
||||||
|
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.10.1"
|
||||||
|
stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stack_trace
|
||||||
|
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.12.1"
|
||||||
|
stream_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_channel
|
||||||
|
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
|
string_scanner:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: string_scanner
|
||||||
|
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.1"
|
||||||
|
term_glyph:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: term_glyph
|
||||||
|
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.2"
|
||||||
|
test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: test
|
||||||
|
sha256: "8391fbe68d520daf2314121764d38e37f934c02fd7301ad18307bd93bd6b725d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.25.14"
|
||||||
|
test_api:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_api
|
||||||
|
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.4"
|
||||||
|
test_core:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_core
|
||||||
|
sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.8"
|
||||||
|
typed_data:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
vm_service:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vm_service
|
||||||
|
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "15.0.0"
|
||||||
|
watcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: watcher
|
||||||
|
sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web
|
||||||
|
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket
|
||||||
|
sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.6"
|
||||||
|
web_socket_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket_channel
|
||||||
|
sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
|
webkit_inspection_protocol:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webkit_inspection_protocol
|
||||||
|
sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: yaml
|
||||||
|
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.3"
|
||||||
|
sdks:
|
||||||
|
dart: ">=3.6.1 <4.0.0"
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
name: omnichess
|
||||||
|
description: A sample command-line application.
|
||||||
|
version: 1.0.0
|
||||||
|
# repository: https://github.com/my_org/my_repo
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: ^3.6.1
|
||||||
|
|
||||||
|
# Add regular dependencies here.
|
||||||
|
dependencies:
|
||||||
|
meta: ^1.16.0
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
lints: ^5.0.0
|
||||||
|
test: ^1.24.0
|
||||||
Loading…
Reference in New Issue