From 637cb053c461ab0e10849348366a9ace3e02cbda Mon Sep 17 00:00:00 2001 From: Dany Thach Date: Sun, 9 Feb 2025 11:47:39 +0100 Subject: [PATCH] Initial commit --- .gitignore | 6 + LICENSE | 21 + README.md | 60 ++ analysis_options.yaml | 30 + bin/omnichess.dart | 6 + lib/classes/best_move_searcher.dart | 76 +++ lib/classes/position.dart | 366 +++++++++++ lib/constants/piece.dart | 55 ++ lib/exceptions/king_not_found.dart | 3 + .../_partials/legal_moves_black.dart | 571 ++++++++++++++++++ .../_partials/legal_moves_white.dart | 571 ++++++++++++++++++ lib/functions/legal_moves.dart | 19 + lib/omnichess.dart | 123 ++++ pubspec.lock | 402 ++++++++++++ pubspec.yaml | 15 + 15 files changed, 2324 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 analysis_options.yaml create mode 100644 bin/omnichess.dart create mode 100644 lib/classes/best_move_searcher.dart create mode 100644 lib/classes/position.dart create mode 100644 lib/constants/piece.dart create mode 100644 lib/exceptions/king_not_found.dart create mode 100644 lib/functions/_partials/legal_moves_black.dart create mode 100644 lib/functions/_partials/legal_moves_white.dart create mode 100644 lib/functions/legal_moves.dart create mode 100644 lib/omnichess.dart create mode 100644 pubspec.lock create mode 100644 pubspec.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..03641f0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ + +# project-specific +builds/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7be2f33 --- /dev/null +++ b/LICENSE @@ -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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..fc3f1f1 --- /dev/null +++ b/README.md @@ -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 +``` + +Replace `` 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! diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..dee8927 --- /dev/null +++ b/analysis_options.yaml @@ -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 diff --git a/bin/omnichess.dart b/bin/omnichess.dart new file mode 100644 index 0000000..2bcb830 --- /dev/null +++ b/bin/omnichess.dart @@ -0,0 +1,6 @@ +import 'package:omnichess/omnichess.dart'; + +void main(List arguments) { + final Omnichess engine = Omnichess(); + engine.loop(); +} \ No newline at end of file diff --git a/lib/classes/best_move_searcher.dart b/lib/classes/best_move_searcher.dart new file mode 100644 index 0000000..fc84807 --- /dev/null +++ b/lib/classes/best_move_searcher.dart @@ -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 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 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 stop() async { + if (!_isRunning) { + return; + } + _isStopped = true; + } + +} \ No newline at end of file diff --git a/lib/classes/position.dart b/lib/classes/position.dart new file mode 100644 index 0000000..6b0fddf --- /dev/null +++ b/lib/classes/position.dart @@ -0,0 +1,366 @@ +import 'dart:collection'; +import 'dart:io'; + +import 'package:omnichess/constants/piece.dart'; + +class Position { + + static List get startingBoard => [ + 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 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.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 newBoard = List.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 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 commandComponents = Queue.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 newBoard = List.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, + ); + +} \ No newline at end of file diff --git a/lib/constants/piece.dart b/lib/constants/piece.dart new file mode 100644 index 0000000..67eb3f3 --- /dev/null +++ b/lib/constants/piece.dart @@ -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 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 fenCharacterByPiece = { + 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; +} \ No newline at end of file diff --git a/lib/exceptions/king_not_found.dart b/lib/exceptions/king_not_found.dart new file mode 100644 index 0000000..d5a7241 --- /dev/null +++ b/lib/exceptions/king_not_found.dart @@ -0,0 +1,3 @@ +class KingNotFoundException implements Exception { + +} \ No newline at end of file diff --git a/lib/functions/_partials/legal_moves_black.dart b/lib/functions/_partials/legal_moves_black.dart new file mode 100644 index 0000000..c479f50 --- /dev/null +++ b/lib/functions/_partials/legal_moves_black.dart @@ -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; + } + +} \ No newline at end of file diff --git a/lib/functions/_partials/legal_moves_white.dart b/lib/functions/_partials/legal_moves_white.dart new file mode 100644 index 0000000..3b80663 --- /dev/null +++ b/lib/functions/_partials/legal_moves_white.dart @@ -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; + } + +} \ No newline at end of file diff --git a/lib/functions/legal_moves.dart b/lib/functions/legal_moves.dart new file mode 100644 index 0000000..ef78237 --- /dev/null +++ b/lib/functions/legal_moves.dart @@ -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 isCheckmate(Position position) async => position.isWhiteTurn + ? (_LegalMovesWhite._isWhiteChecked(position) && await getLegalMoves(position).isEmpty) + : (_LegalMovesBlack._isBlackChecked(position) && await getLegalMoves(position).isEmpty); + +} \ No newline at end of file diff --git a/lib/omnichess.dart b/lib/omnichess.dart new file mode 100644 index 0000000..c685b18 --- /dev/null +++ b/lib/omnichess.dart @@ -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 inputComponents) { + positionCommand = inputComponents.join(" "); + } + + Future _goCommand(Queue inputComponents) async { + final Position position = Position.fromPositionCommand(positionCommand); + final String? bestMove = await bestMoveSearcher.search(position); + print("bestmove ${bestMove ?? "(none)"}"); + } + + Future _stopCommand() async { + await bestMoveSearcher.stop(); + } + + Future elaborate(String line) async { + final Queue 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 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(); + } + +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..b7cb01a --- /dev/null +++ b/pubspec.lock @@ -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" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..1d22ece --- /dev/null +++ b/pubspec.yaml @@ -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