diff --git a/ChessAI.sln b/ChessAI.sln
index 798df40..25c6b03 100644
--- a/ChessAI.sln
+++ b/ChessAI.sln
@@ -2,6 +2,10 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChessAI", "ChessAI\ChessAI.csproj", "{D75AC44F-AC92-418E-8B9B-6911AE1FE830}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "UnitTests\UnitTests.csproj", "{0AB26848-CC9A-4CB1-AA2D-439A45A2DE66}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChessBenchMarks", "ChessBenchMarks\ChessBenchMarks.csproj", "{3E43ADE7-9DE7-4D19-BA25-890FD9A41D0D}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -12,5 +16,13 @@ Global
{D75AC44F-AC92-418E-8B9B-6911AE1FE830}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D75AC44F-AC92-418E-8B9B-6911AE1FE830}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D75AC44F-AC92-418E-8B9B-6911AE1FE830}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0AB26848-CC9A-4CB1-AA2D-439A45A2DE66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0AB26848-CC9A-4CB1-AA2D-439A45A2DE66}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0AB26848-CC9A-4CB1-AA2D-439A45A2DE66}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0AB26848-CC9A-4CB1-AA2D-439A45A2DE66}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3E43ADE7-9DE7-4D19-BA25-890FD9A41D0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3E43ADE7-9DE7-4D19-BA25-890FD9A41D0D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3E43ADE7-9DE7-4D19-BA25-890FD9A41D0D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3E43ADE7-9DE7-4D19-BA25-890FD9A41D0D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/ChessAI/ChessAI.csproj b/ChessAI/ChessAI.csproj
index 2c0dacc..0d3c58a 100644
--- a/ChessAI/ChessAI.csproj
+++ b/ChessAI/ChessAI.csproj
@@ -2,7 +2,8 @@
Exe
- netcoreapp3.1
+ net5.0
+ true
diff --git a/ChessAI/DataClasses/Board.cs b/ChessAI/DataClasses/Board.cs
new file mode 100644
index 0000000..48448c4
--- /dev/null
+++ b/ChessAI/DataClasses/Board.cs
@@ -0,0 +1,124 @@
+using System;
+using System.Collections.Generic;
+
+namespace ChessAI.DataClasses
+{
+ public readonly struct Board
+ {
+ private const byte Width = 0x10;
+ private const byte Height = 0x8;
+ private static readonly sbyte[] Directions = {
+ +0x10, // 1 up
+ -0x10, // 1 down
+ +0x01, // 1 right
+ -0x01, // 1 left
+ };
+
+ public static sbyte WhiteDirection(Direction direction) => Directions[(byte)direction];
+ public static sbyte BlackDirection(Direction direction) => (sbyte) -(Directions[(byte)direction]);
+
+ private readonly Piece[] _fields; //TODO consider replacing this array with a stack allocated ReadOnlySpan
+
+ ///
+ /// A constructor taking an array of pieces representing the board. Note that the array length must
+ /// be consistent with an 0x88 board.
+ ///
+ /// an array representing the board
+ /// thrown if the given array is not of the right length
+ public Board(Piece[] fields)
+ {
+ var expectedSize = Width * Height;
+ if (fields.Length != expectedSize)
+ {
+ throw new ArgumentException(
+ "fields must be an array of length 0x" + expectedSize.ToString("X") + " / 0d" + expectedSize +
+ " but the provided array had length 0x" + fields.Length.ToString("X") + " / 0d" + fields.Length
+ );
+ }
+
+ _fields = fields;
+ }
+
+ ///
+ /// A Constructor that takes a list of fields. It is assumed that all pieces have valid positions set.
+ /// If one or more pieces has an invalid position it can lead to unexpected behavior.
+ ///
+ /// A list of pieces with valid positions
+ public Board(List pieceList)
+ {
+ var fields = new Piece[Width * Height];
+ foreach (var piece in pieceList)
+ {
+ fields[piece.Position] = piece;
+ }
+
+ _fields = fields;
+ }
+
+ public Piece this[int i] => Fields[i];
+
+ public ReadOnlySpan Fields => _fields.AsSpan();
+
+ public bool IsFieldOccupied(byte position)
+ {
+ return Fields[position] == Piece.Empty;
+ }
+
+ public static bool IsIndexValid(byte index)
+ {
+ //Checks that only bits used for counting from 0-7 is used for each digit in the hex representation of the index
+ return (index & 0b1000_1000) == 0;
+ }
+
+ public static string IndexToString(byte index)
+ {
+ if (!IsIndexValid(index)) throw new ArgumentException("Argument must be a valid index");
+
+ return (index & 0xF0) switch
+ {
+ 0x00 => "A" + (index & 0xF),
+ 0x10 => "B" + (index & 0xF),
+ 0x20 => "C" + (index & 0xF),
+ 0x30 => "D" + (index & 0xF),
+ 0x40 => "E" + (index & 0xF),
+ 0x50 => "F" + (index & 0xF),
+ 0x60 => "G" + (index & 0xF),
+ 0x70 => "H" + (index & 0xF),
+ _ => throw new ArgumentException("Argument must be a valid index")
+ };
+ }
+
+ public static byte StringToIndex(string fieldName)
+ {
+ if (fieldName.Length != 2
+ || !"ABCDEFGHabcdefgh".Contains(fieldName[0])
+ || !"12345678".Contains(fieldName[1]))
+ {
+ throw new ArgumentException("Argument must be a valid field name");
+ }
+
+ var lastDigit = Byte.Parse(fieldName.Substring(1));
+ return (fieldName[0]) switch
+ {
+ 'A' => (byte)(0x00 + lastDigit),
+ 'B' => (byte)(0x10 + lastDigit),
+ 'C' => (byte)(0x20 + lastDigit),
+ 'D' => (byte)(0x30 + lastDigit),
+ 'E' => (byte)(0x40 + lastDigit),
+ 'F' => (byte)(0x50 + lastDigit),
+ 'G' => (byte)(0x60 + lastDigit),
+ 'H' => (byte)(0x70 + lastDigit),
+ _ => throw new ArgumentException("Argument must be a valid index")
+ };
+ }
+ }
+
+ public enum Direction : byte
+ {
+ Up = 0,
+ Down = 1,
+ Left = 2,
+ Right = 3,
+ };
+
+}
\ No newline at end of file
diff --git a/ChessAI/DataClasses/GameState.cs b/ChessAI/DataClasses/GameState.cs
new file mode 100644
index 0000000..f4e9b33
--- /dev/null
+++ b/ChessAI/DataClasses/GameState.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Collections.Generic;
+using static ChessAI.DataClasses.Piece;
+
+namespace ChessAI.DataClasses
+{
+ /**
+ *
+ * A struct that keeps track of all relevant information about a game
+ *
+ */
+ public readonly struct GameState
+ {
+ ///
+ /// An easy to use constructor taking only a board.
+ ///
+ /// The board that should be represented by this state
+ ///
+ /// This implementation is slow as it has to construct a PieceList itself.
+ /// Use other constructors in time critical sections of code.
+ ///
+ public GameState(Board board)
+ {
+ State = board;
+
+ Span tempBoardFields = stackalloc Piece[State.Fields.Length];
+ State.Fields.CopyTo(tempBoardFields);
+
+ List tempWhitePieceList = new List();
+ List tempBlackPieceList = new List();
+
+ // Fill PieceLists
+ for (byte index = 0; index < 0x88; index++)
+ {
+ // if the index is invalid we have exceeded the boundaries of the board.
+ // The body of the loop is skipped until the next valid index is encountered.
+ if (!Board.IsIndexValid(index)) continue;
+
+ var currentPiece = board[index];
+ if (currentPiece.PieceType != Empty)
+ {
+
+ // If the found piece doesn't have the right position then set it
+ if (currentPiece.Position != index)
+ {
+ currentPiece = new Piece(currentPiece.Content, index);
+ tempBoardFields[index] = currentPiece;
+ }
+
+ if ((currentPiece.PieceFlags & White) == White)
+ {
+ tempWhitePieceList.Add(currentPiece);
+ }
+ else
+ {
+ tempBlackPieceList.Add(currentPiece);
+ }
+ }
+ }
+
+ State = new Board(tempBoardFields.ToArray());
+ WhitePieces = tempWhitePieceList.ToArray();
+ BlackPieces = tempBlackPieceList.ToArray();
+ }
+
+ public readonly Board State;
+ public readonly Piece[] WhitePieces;
+ public readonly Piece[] BlackPieces;
+
+
+ /**
+ * A dummy method representing some logic to calculate the applying a move to the state
+ * The move that should be applied to a state
+ * A new with the move applied
+ */
+ public GameState ApplyMove(Move move)
+ {
+ Span newFields = stackalloc Piece[State.Fields.Length];
+ State.Fields.CopyTo(newFields);
+
+ var movedPiece = newFields[move.StartPos];
+ newFields[move.StartPos] = new Piece(Empty);
+ var capturedPiece = newFields[move.EndPos];
+ newFields[move.EndPos] = movedPiece;
+
+ //TODO use another constructor to make this implementation faster (perhaps make one that takes a span)
+ var newBoard = new Board(newFields.ToArray());
+ var newState = new GameState(newBoard);
+ return newState;
+ }
+ }
+}
\ No newline at end of file
diff --git a/ChessAI/DataClasses/Move.cs b/ChessAI/DataClasses/Move.cs
new file mode 100644
index 0000000..1e573ac
--- /dev/null
+++ b/ChessAI/DataClasses/Move.cs
@@ -0,0 +1,141 @@
+using System;
+using static ChessAI.DataClasses.MoveType;
+using static ChessAI.DataClasses.Piece;
+
+namespace ChessAI.DataClasses
+{
+ public readonly struct Move
+ {
+ public readonly byte StartPos;
+ public readonly byte EndPos;
+ public readonly MoveType MoveType;
+ public readonly Piece MovePiece;
+ public readonly Piece TargetPiece;
+
+
+ ///
+ /// a simple constructor taking the start and end position.
+ ///
+ /// starting position
+ /// destination
+ ///
+ /// This constructor has only been left here for compatibility concerns and should not be used.
+ /// When encountered it should be replaced by a factory method call.
+ /// The Factory is the recommended alternative. It makes sure to set all the
+ /// required fields in the correct way only given start and end position and a state.
+ ///
+ [ObsoleteAttribute("When encountered it should be replaced by a factory method call."
+ + "The CreateSimpleMove Factory is the recommended alternative.", false)]
+ public Move(byte startPos, byte endPos)
+ {
+ StartPos = startPos;
+ EndPos = endPos;
+
+ MoveType = Ordinary;
+ MovePiece = new Piece(Empty);
+ TargetPiece = MovePiece;
+ }
+
+ ///
+ /// A constructor that takes all the required parameters to define a move
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private Move(byte startPos, byte endPos, MoveType moveType, Piece movePiece, Piece targetPiece)
+ {
+ StartPos = startPos;
+ EndPos = endPos;
+ MoveType = moveType;
+ MovePiece = movePiece;
+ TargetPiece = targetPiece;
+ }
+
+ ///
+ /// A factory method that creates a simple move where no special rules are involved.
+ ///
+ /// The position from which the movePiece originates
+ /// The destination of the movePiece
+ /// The state of the game at the time of the move
+ /// A new move from startPos to endPos
+ public static Move CreateSimpleMove(byte startPos, byte endPos, GameState state)
+ {
+ var movePiece = state.State[startPos];
+ var targetPiece = state.State[endPos];
+ var move = new Move(startPos, endPos, Ordinary, movePiece, targetPiece);
+ return move;
+ }
+
+ ///
+ /// A factory method that creates a castling move.
+ /// This method performs no checks on the legality of the move, not even if the pieces required are
+ /// actually there. This method should therefore only be called after the legality has been determined.
+ ///
+ /// The position of the rook that is involved in the castling
+ /// The state of the game at the time of the move
+ ///
+ /// A new castling move that represents the king taking the castlePosition and the
+ /// occupying rook being moved in the appropriate direction
+ ///
+ public static Move CreateCastleMove(byte castlePosition, GameState state)
+ {
+ var targetPiece = state.State[castlePosition];
+
+ // Dependant on the board. Should this be a static member of board?
+ byte whiteKingIndex = 0x04;
+ byte blackKingIndex = 0x73;
+
+ var kingIndex = (targetPiece.PieceFlags & White) == White ? whiteKingIndex : blackKingIndex;
+ var movePiece = state.State[kingIndex];
+
+ var move = new Move(kingIndex, castlePosition, Castling, movePiece, targetPiece);
+ return move;
+ }
+
+ ///
+ /// A factory method that creates a pwn promotion move.
+ /// This method performs no checks on the legality of the move, not even if the pieces required are
+ /// actually there. This method should therefore only be called after the legality has been determined.
+ ///
+ /// The starting position of the pawn
+ ///
+ /// The destination of the pawn. note that this may also occur through an attack
+ /// which opens the possibility of three different end positions
+ ///
+ /// The piece the pawn should be promoted to
+ /// The state of the game at the time of the move
+ ///
+ /// A new pawn promotion move that represents a pawn moving to the 8th rank, being promote to the promotionPiece
+ ///
+ public static Move CreatePawnPromotionMove(byte startPosition, byte endPos, Piece promotionPiece,
+ GameState state)
+ {
+ var movePiece = state.State[startPosition];
+ var promotionType = promotionPiece.PieceType switch
+ {
+ Queen => PromotionQueen,
+ Knight => PromotionKnight,
+ Bishop => PromotionBishop,
+ Rook => PromotionRook,
+ _ => throw new ArgumentOutOfRangeException(
+ $"The piece type {promotionPiece.PieceType} is not an option for promotion"
+ )
+ };
+
+ var move = new Move(startPosition, endPos, promotionType, movePiece, promotionPiece);
+ return move;
+ }
+ }
+
+ public enum MoveType : byte
+ {
+ Ordinary,
+ Castling,
+ PromotionQueen,
+ PromotionRook,
+ PromotionBishop,
+ PromotionKnight
+ }
+}
\ No newline at end of file
diff --git a/ChessAI/DataClasses/Piece.cs b/ChessAI/DataClasses/Piece.cs
new file mode 100644
index 0000000..e8939b9
--- /dev/null
+++ b/ChessAI/DataClasses/Piece.cs
@@ -0,0 +1,151 @@
+using System;
+using System.Text;
+
+namespace ChessAI.DataClasses
+{
+ public readonly struct Piece : IEquatable
+ {
+ ///
+ /// _piece stores information describing a single piece possibly on a board.
+ /// The byte representing the piece is conceptually split into two parts; one describing the pieces color
+ /// and type, and another describing additional information relating to the state it is in.
+ /// The piece type is represented by the three least significant bits as consecutive numbers starting at 1.
+ /// The color is represented by the next bit where a 1 is white and 0 is black.
+ ///
+ /// below is a description of the layout of _piece starting from the most significant bit
+ /// towards the least significant on
+ ///
+ /// unused: 2 bits
+ /// challenges: 1 bit
+ /// is challenged: 1 bit
+ /// is white: 1 bit
+ /// Piece type: 3 bits
+ ///
+ ///
+ private readonly byte _piece;
+
+ public byte Content => _piece;
+ public byte PieceFlags => (byte)(_piece & 0b1111_1000);
+ public byte PieceType => (byte)(_piece & PieceMask);
+
+ ///
+ /// The position of the piece as an index on an 0x88 board.
+ /// If a position doesn't make sense or is unknown for this piece an invalid index is set instead.
+ ///
+ public byte Position { get; }
+
+ public Piece(byte flags)
+ {
+ _piece = flags;
+ Position = 0xAA; // Outside valid indexes as the piece has been given no
+ }
+
+ public Piece(int flags)
+ {
+ _piece = (byte)flags;
+ Position = 0xAA; // Outside valid indexes as the piece has been given no
+ }
+
+ public Piece(byte flags, byte position)
+ {
+ _piece = flags;
+ Position = position;
+ }
+
+ public Piece(int flags, byte position)
+ {
+ Position = position;
+ _piece = (byte)flags;
+ }
+ //######################################//
+ // Constants for easy use of this type //
+ //######################################//
+
+ public const byte Empty = 0;
+ public const byte PieceMask = 0b0111;
+
+ // Piece definitions
+ public const byte Pawn = 0b0001;
+ public const byte Rook = 0b0010;
+ public const byte Knight = 0b0011;
+ public const byte Bishop = 0b0100;
+ public const byte Queen = 0b0101;
+ public const byte King = 0b0110;
+
+ // Flags
+ public const byte White = 0b1000;
+ public const byte Black = 0;
+
+
+ public override string ToString()
+ {
+ var builder = new StringBuilder();
+
+ builder.Append((PieceFlags & White) == White ? "White" : "Black");
+ builder.Append(" ");
+ builder.Append(
+ (PieceType) switch
+ {
+ 0 => "None",
+ 1 => "Pawn",
+ 2 => "Rook",
+ 3 => "Knight",
+ 4 => "Bishop",
+ 5 => "Queen",
+ 6 => "King",
+ _ => "Invalid"
+ }
+ );
+
+ //Insert appends for other flags here as they are decided on
+
+ return builder.ToString();
+ }
+
+ public bool Equals(Piece other)
+ {
+ return _piece == other._piece;
+ }
+
+ public override bool Equals(object obj)
+ {
+ return obj is Piece other && Equals(other);
+ }
+
+ public override int GetHashCode()
+ {
+ return _piece.GetHashCode();
+ }
+
+ //######################################//
+ // Operator overloads //
+ //######################################//
+
+ // bitwise And
+ public static Piece operator &(Piece a, Piece b) => new Piece((byte)(a._piece & b._piece));
+ public static Piece operator &(Piece a, byte b) => new Piece((byte)(a._piece & b));
+ public static Piece operator &(byte a, Piece b) => new Piece((byte)(a & b._piece));
+
+ // bitwise Or
+ public static Piece operator |(Piece a, Piece b) => new Piece((byte)(a._piece | b._piece));
+ public static Piece operator |(Piece a, byte b) => new Piece((byte)(a._piece | b));
+ public static Piece operator |(byte a, Piece b) => new Piece((byte)(a | b._piece));
+
+ // bitwise XOr
+ public static Piece operator ^(Piece a, Piece b) => new Piece((byte)(a._piece ^ b._piece));
+ public static Piece operator ^(Piece a, byte b) => new Piece((byte)(a._piece ^ b));
+ public static Piece operator ^(byte a, Piece b) => new Piece((byte)(a ^ b._piece));
+
+ // Equality
+ public static bool operator ==(Piece a, Piece b) => a._piece == b._piece;
+ public static bool operator ==(byte a, Piece b) => a == b._piece;
+ public static bool operator ==(Piece a, byte b) => a._piece == b;
+ public static bool operator !=(Piece a, Piece b) => a._piece != b._piece;
+ public static bool operator !=(byte a, Piece b) => a != b._piece;
+ public static bool operator !=(Piece a, byte b) => a._piece != b;
+ public static bool operator ==(int a, Piece b) => a == b._piece;
+ public static bool operator ==(Piece a, int b) => a._piece == b;
+ public static bool operator !=(int a, Piece b) => a != b._piece;
+ public static bool operator !=(Piece a, int b) => a._piece != b;
+ }
+}
\ No newline at end of file
diff --git a/ChessAI/GameController.cs b/ChessAI/GameController.cs
new file mode 100644
index 0000000..2d761a7
--- /dev/null
+++ b/ChessAI/GameController.cs
@@ -0,0 +1,9 @@
+using ChessAI.DataClasses;
+using ChessAI.IO;
+namespace ChessAI
+{
+ public class GameController
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/ChessAI/IO/IO.cs b/ChessAI/IO/IO.cs
new file mode 100644
index 0000000..3c22414
--- /dev/null
+++ b/ChessAI/IO/IO.cs
@@ -0,0 +1,7 @@
+namespace ChessAI.IO
+{
+ public class IO
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/ChessAI/MoveSelection/MoveGeneration/IMoveCalculator.cs b/ChessAI/MoveSelection/MoveGeneration/IMoveCalculator.cs
new file mode 100644
index 0000000..872d52d
--- /dev/null
+++ b/ChessAI/MoveSelection/MoveGeneration/IMoveCalculator.cs
@@ -0,0 +1,18 @@
+using System.Collections.Generic;
+using ChessAI.DataClasses;
+
+namespace ChessAI.MoveSelection.MoveGeneration
+{
+ public interface IMoveCalculator
+ {
+ /**
+ * A dummy method representing some logic to calculate all moves from a given state
+ * The state for which possible moves should be calculated
+ *
+ * A boolean flag that tells which side's moves should be generated. true -> white and false -> black
+ *
+ * A of all legal moves for the given player
+ */
+ List CalculatePossibleMoves(GameState state, bool calculateForWhite);
+ }
+}
\ No newline at end of file
diff --git a/ChessAI/MoveSelection/MoveGeneration/MoveCalculator.cs b/ChessAI/MoveSelection/MoveGeneration/MoveCalculator.cs
new file mode 100644
index 0000000..a84b7a1
--- /dev/null
+++ b/ChessAI/MoveSelection/MoveGeneration/MoveCalculator.cs
@@ -0,0 +1,157 @@
+using System.Collections.Generic;
+using ChessAI.DataClasses;
+
+namespace ChessAI.MoveSelection.MoveGeneration {
+
+ public enum DirectionIndex {
+ Up = 0, Down = 1, Left = 2, Right = 3,
+ UpLeft = 4, DownRight=5, UpRight=7, DownLeft=8
+ };
+
+
+ // Moves Implementation
+ public class MoveGenerator : IMoveCalculator{
+
+ //public enum DirectionIndex {
+ //Up = 0, Down = 1, Left = 2, Right = 3,
+ //UpLeft = 4, DownRight=5, UpRight=7, DownLeft=8};
+
+ // this is made to fit this Direction Index.
+
+ public static readonly sbyte[] X88Dirs = {
+ 0x10, // 1 up 0 x
+ -0x10, // 1 down 0 x
+ 0x01, // 0 x 1 right
+ -0x01, // 0 x 1 left
+ 0x0e, // 1 up 1 left
+ -0x11, // 1 down 1 right
+ 0x11, // 1 up 1 right
+ -0x0e // 1 down 1 left
+ };
+
+ private bool king , queen ;
+ public List CalculatePossibleMoves(GameState state, bool calculateForWhite){
+ return new List();
+ }
+
+ public List calcMovesForPiece(Piece piece , byte position ) {
+
+ king = ( piece == Piece.King );
+ queen = (piece == Piece.Queen);
+
+ List moves = new List();
+ if( king || queen || piece == Piece.Rook ){
+ // i add king to this mehtod, because it is the only piece that has a limit of 1 distance, since "king" is a bool, i pass it as "depthIs1"
+ moves.AddRange( genLineMoves(position, king) );
+ }
+
+ if( king || queen || piece == Piece.Bishop ){
+ // COPY OF PREV COMMENT ::: i add king to this mehtod, because it is the only piece that has a limit of 1 distance, since "king" is a bool, i pass it as "depthIs1"
+ moves.AddRange( genDiagMoves(position, king) );
+ }
+
+ return null;
+ }
+
+
+ bool moreMoves;
+ byte dirs;
+ byte tempPos;
+
+ private List genLineMoves(byte position, bool depthIs1 ){
+ List moves = new List();
+ moreMoves = true;
+ for(dirs = 0 ; dirs < 4 ; dirs++) {
+
+ tempPos = position;
+
+ while(moreMoves){
+ // this adds the offset
+ tempPos = (byte)(tempPos + X88Dirs[ dirs ]);
+
+ if(false) // IS OUT OF BOUNDS OF BOARD
+ break;
+
+ if(false) // is Blocked
+ break;
+
+ // createMove and Add to list
+ moves.Add( new Move( position, tempPos ) ) ;
+
+
+ // for all directions. First 4, up down left right.
+ if(depthIs1)
+ break;
+ }
+ }
+
+ return null;
+ }
+ private List genDiagMoves(byte position, bool depthIs1 ){
+ List moves = new List();
+ moreMoves = true;
+ for(dirs = 4 ; dirs < 8 ; dirs++) {
+
+ tempPos = position;
+
+ while(moreMoves) {
+ // this adds the offset
+ tempPos = (byte)(tempPos + X88Dirs[ dirs ]);
+
+ if(false) // IS OUT OF BOUNDS OF BOARD
+ break;
+
+ if(false) // is blocked by self ??
+ break;
+
+ // createMove and Add to list
+ moves.Add(new Move(position , tempPos));
+
+
+ // for all directions. First 4, up down left right.
+ if(depthIs1)
+ break;
+ }
+ }
+ return moves;
+ }
+
+ // source https://learn.inside.dtu.dk/d2l/le/content/80615/viewContent/284028/View
+ sbyte[] horseMoves = {
+ 0x21, // two up one right
+ 0x1F, // two up one left
+ 0x12, // one up two right
+ 0x0E, // one up two left
+ -0x21, // two down one left
+ -0x1F, // two down one right
+ -0x12, // one down two left
+ -0x0E
+ };
+ private List genHorseMoves(byte position , bool depthIs1) {
+ List moves = new List();
+ for(dirs = 0 ; dirs < horseMoves.Length ; dirs++) {
+ // this adds the offset
+ tempPos = (byte)(tempPos + horseMoves[ dirs ]);
+
+ if(false) { // IS OUT OF BOUNDS OF BOARD
+
+ continue;
+ }
+
+ if(false) { // is blocked by self ??
+
+ continue;
+ }
+
+
+ // createMove and Add to list
+ moves.Add(new Move(position , tempPos));
+ }
+ return moves;
+ }
+ }
+
+
+
+
+}
diff --git a/ChessAI/MoveSelection/MoveSelector.cs b/ChessAI/MoveSelection/MoveSelector.cs
new file mode 100644
index 0000000..847e68d
--- /dev/null
+++ b/ChessAI/MoveSelection/MoveSelector.cs
@@ -0,0 +1,164 @@
+using System;
+using System.Threading.Tasks;
+using ChessAI.DataClasses;
+using ChessAI.MoveSelection.MoveGeneration;
+using ChessAI.MoveSelection.StateAnalysis;
+
+namespace ChessAI.MoveSelection
+{
+ /**
+ *
+ * A Class that contains all the logic for finding the best move.
+ *
+ */
+ public class MoveSelector
+ {
+ private readonly bool _isWhite;
+ private Move[] _tempBestMoves;
+
+ private readonly IStateAnalyser _stateAnalyser;
+ private readonly IMoveAnalyser _moveAnalyser;
+ private readonly IMoveCalculator _moveCalculator;
+
+ public Move[] BestMoves { get; private set; }
+
+ public MoveSelector(bool playerIsWhite, IStateAnalyser stateAnalyser, IMoveAnalyser moveAnalyser,
+ IMoveCalculator moveCalculator, int initialMoveArraySize = 6)
+ {
+ _isWhite = playerIsWhite;
+ BestMoves = new Move[initialMoveArraySize];
+ _tempBestMoves = new Move[initialMoveArraySize];
+ _stateAnalyser = stateAnalyser;
+ _moveAnalyser = moveAnalyser;
+ _moveCalculator = moveCalculator;
+ }
+
+ /**
+ *
+ * The method to use if you want to perform a fixed depth search for the best move.
+ *
+ * The depth to which the algorithm will search
+ * The current state of the game
+ */
+ public Move BestMove(GameState state, int depth)
+ {
+ //TODO reduce amount of allocations and copies of arrays
+ if (BestMoves.Length < depth)
+ {
+ var newArray = new Move[depth];
+ BestMoves.CopyTo(newArray, 0);
+ BestMoves = newArray;
+ }
+
+ // To avoid _bestMoves and _tempBestMoves referring to the same array,
+ // _tempBestMoves have to be reassigned every call.
+ _tempBestMoves = new Move[depth];
+
+
+ MinMax(depth, 0, true, state);
+ BestMoves = _tempBestMoves;
+
+ return BestMoves[0];
+ }
+
+ /**
+ *
+ * The method to use if you want to perform a search for the best move with a time constraint.
+ *
+ * The current state of the game
+ *
+ * The time constraint of the search.
+ * When this limit is hit the function will return and drop any potential searches it is attempting.
+ *
+ *
+ * The depth to which the algorithm will search if it can do so within the allotted time span.
+ * If there is no max depth, set this argument to -1;
+ *
+ */
+ public Move BestMoveIterative(GameState state, TimeSpan timeLimit, int maxDepth = -1)
+ {
+ var task = Task.Run(() =>
+ {
+ for (int depth = 1; depth <= maxDepth; depth++)
+ {
+ BestMove(state, depth);
+ }
+ });
+
+ task.Wait(timeLimit);
+
+ return BestMoves[0];
+ }
+
+ /**
+ *
+ * The implementation of the minMax algorithm using alpha pruning that we can use to search for the best move
+ *
+ * The desired depth to which it should search
+ * The depth of the current node
+ * A value deciding which role the node takes on; maximiser or minimiser
+ * The state of the game as it would look all moves leading to this node were taken
+ * The value storing the maximiser's current best value
+ * The value storing the minimiser's current best value
+ * The evaluation value of the best outcome
+ */
+ private int MinMax(int searchDepth, int currentDepth, bool isMaximizer, in GameState state,
+ int alpha = int.MinValue, int beta = int.MaxValue)
+ {
+ if (searchDepth <= currentDepth)
+ {
+ return _stateAnalyser.StaticAnalysis(state);
+ }
+
+ // Generate moves, sort them and remove the previous best move to avoid
+ // it being used in other branches than the best
+ var moves = _moveCalculator.CalculatePossibleMoves(state, _isWhite == isMaximizer);
+ _moveAnalyser.SortMovesByBest(state, moves, BestMoves[currentDepth]);
+
+ if (isMaximizer)
+ {
+ foreach (var move in moves)
+ {
+ var child = state.ApplyMove(move);
+ var value = MinMax(searchDepth, currentDepth + 1, false, child, alpha, beta);
+
+ if (value > alpha)
+ {
+ alpha = value;
+
+ if (alpha >= beta)
+ {
+ return alpha;
+ }
+
+ _tempBestMoves[currentDepth] = move;
+ }
+ }
+
+ return alpha;
+ }
+ else
+ {
+ foreach (var move in moves)
+ {
+ var child = state.ApplyMove(move);
+ var value = MinMax(searchDepth, currentDepth + 1, true, child, alpha, beta);
+
+ if (value < beta)
+ {
+ beta = value;
+
+ if (alpha >= beta)
+ {
+ return beta;
+ }
+
+ _tempBestMoves[currentDepth] = move;
+ }
+ }
+
+ return beta;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ChessAI/MoveSelection/StateAnalysis/IMoveAnalyser.cs b/ChessAI/MoveSelection/StateAnalysis/IMoveAnalyser.cs
new file mode 100644
index 0000000..701b4c1
--- /dev/null
+++ b/ChessAI/MoveSelection/StateAnalysis/IMoveAnalyser.cs
@@ -0,0 +1,40 @@
+using System.Collections.Generic;
+using ChessAI.DataClasses;
+
+namespace ChessAI.MoveSelection.StateAnalysis
+{
+ public interface IMoveAnalyser
+ {
+ /**
+ *
+ * A method representing some logic to calculate the value of a move.
+ * This method should not be used to actually calculate the value of a potential state, as that is what the
+ * is for, but should give a rough estimate of how likely it is to be a good
+ * move to make.
+ *
+ * The current state before the move is applied
+ * The move that would be applied to the current state
+ *
+ * A numeric value that represents how good the move is likely to be. A positive value is in
+ * favor of this engine while a negative one is in favor of its opponent no matter their color
+ *
+ */
+ int MoveAnalysis(GameState state, Move move);
+
+ /**
+ *
+ * A dummy method representing some logic to sort the list of moves according to some simple
+ * fast logistic to increase the chance of greater cutoffs.
+ * Note that it sorts the list in place and therefore won't return anything.
+ *
+ * The current state before the move is applied
+ * A list of possible moves
+ *
+ * The best move at this point in an earlier run.
+ * This should always be the first move in the sorted list as it has a very
+ * high likelihood of being the best again
+ *
+ */
+ void SortMovesByBest(GameState state, List moves, Move previousBest);
+ }
+}
\ No newline at end of file
diff --git a/ChessAI/MoveSelection/StateAnalysis/IStateAnalyser.cs b/ChessAI/MoveSelection/StateAnalysis/IStateAnalyser.cs
new file mode 100644
index 0000000..f4ec01e
--- /dev/null
+++ b/ChessAI/MoveSelection/StateAnalysis/IStateAnalyser.cs
@@ -0,0 +1,17 @@
+using ChessAI.DataClasses;
+
+namespace ChessAI.MoveSelection.StateAnalysis
+{
+ public interface IStateAnalyser
+ {
+ /**
+ * A method representing some logic to calculate the value of the state
+ * The state that should be Analyses
+ *
+ * A numeric value that represents how good a state is. A positive value is in favor of this engine
+ * while a negative one is in favor of its opponent no matter their color
+ *
+ */
+ int StaticAnalysis(GameState state /*todo may need an argument for who the engine is*/);
+ }
+}
\ No newline at end of file
diff --git a/ChessAI/Program.cs b/ChessAI/Program.cs
index 6ae5e68..6b15ab6 100644
--- a/ChessAI/Program.cs
+++ b/ChessAI/Program.cs
@@ -1,12 +1,9 @@
-using System;
+using ChessAI.DataClasses;
-namespace ChessAI
-{
- class Program
- {
- static void Main(string[] args)
- {
- Console.WriteLine("Hello World!");
- }
- }
+namespace ChessAI{
+ class Program {
+ static void Main(string[ ] args) {
+ var board = new Board();
+ }
+ }
}
diff --git a/ChessBenchMarks/ChessBenchMarks.csproj b/ChessBenchMarks/ChessBenchMarks.csproj
new file mode 100644
index 0000000..be66ede
--- /dev/null
+++ b/ChessBenchMarks/ChessBenchMarks.csproj
@@ -0,0 +1,19 @@
+
+
+
+ Exe
+ netcoreapp5.0
+ BenchMarks
+ true
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ChessBenchMarks/MoveSelectorBenchmark.cs b/ChessBenchMarks/MoveSelectorBenchmark.cs
new file mode 100644
index 0000000..25b7a1c
--- /dev/null
+++ b/ChessBenchMarks/MoveSelectorBenchmark.cs
@@ -0,0 +1,50 @@
+using System;
+using BenchmarkDotNet.Attributes;
+using ChessAI.DataClasses;
+using ChessAI.MoveSelection;
+using ChessAI.MoveSelection.MoveGeneration;
+using ChessAI.MoveSelection.StateAnalysis;
+using NUnit.Framework;
+using UnitTests;
+
+namespace BenchMarks
+{
+ [MemoryDiagnoser]
+ public class MoveSelectorBenchmark
+ {
+ [Params(1, 2, 3, 4, 6)] //todo try deeper depths when they can be auto generated
+ public int Depth { get; set; }
+ [Params(0, 6, 12)]
+ public int InitialPathArraySize { get; set; }
+
+ public MoveSelector MoveSelector;
+
+ private IMoveAnalyser _moveAnalyser;
+ private IMoveCalculator _moveCalculator;
+ private IStateAnalyser _stateAnalyser;
+
+ public MoveSelectorBenchmark()
+ {
+ //TODO switch out the interfaces with actual implementations once they are ready
+ var moveAndStateProvider = new MoveCalculatorStateAnalyserStub();
+ _moveAnalyser = new MoveAnalyserStub();
+ _moveCalculator = moveAndStateProvider;
+ _stateAnalyser = moveAndStateProvider;
+ }
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ MoveSelector =
+ new MoveSelector(true, _stateAnalyser, _moveAnalyser, _moveCalculator, InitialPathArraySize);
+ }
+
+ [Benchmark]
+ public Move BestMove() => MoveSelector.BestMove(new GameState(), Depth);
+
+ [Benchmark]
+ public Move BestMoveIterative() =>
+ MoveSelector.BestMoveIterative(new GameState(), TimeSpan.FromSeconds(30), Depth);
+
+ }
+}
\ No newline at end of file
diff --git a/ChessBenchMarks/Program.cs b/ChessBenchMarks/Program.cs
new file mode 100644
index 0000000..9eb849d
--- /dev/null
+++ b/ChessBenchMarks/Program.cs
@@ -0,0 +1,14 @@
+using System;
+using BenchmarkDotNet.Running;
+
+namespace BenchMarks
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ var summary =
+ BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Move.cs b/Move.cs
new file mode 100644
index 0000000..67d3378
--- /dev/null
+++ b/Move.cs
@@ -0,0 +1,13 @@
+public struct Move{
+ public byte from, to , pieceIndex;
+ public bool hasCaptured;
+
+ public Move(byte from, byte to, byte pieceIndex, bool hasCaptured = false){
+ this.from = from;
+ this.to = to;
+ this.pieceIndex = pieceIndex;
+ this.hasCaptured = hasCaptured;
+ }
+
+ public Move(){}
+}
\ No newline at end of file
diff --git a/MoveGen.cs b/MoveGen.cs
new file mode 100644
index 0000000..7bc3b00
--- /dev/null
+++ b/MoveGen.cs
@@ -0,0 +1,9 @@
+public struct MoveGen{
+ public list generateMoves( byte pieceType ){
+
+ }
+
+ private list generateLineMoves(){
+
+ }
+}
\ No newline at end of file
diff --git a/UML.png b/UML.png
new file mode 100644
index 0000000..f31844f
Binary files /dev/null and b/UML.png differ
diff --git a/UML.uxf b/UML.uxf
new file mode 100644
index 0000000..ea79ff2
--- /dev/null
+++ b/UML.uxf
@@ -0,0 +1,229 @@
+
+
+ 9
+
+ UMLClass
+
+ 540
+ 351
+ 117
+ 90
+
+ Move
+--
+from : byte
+to : byte
+hasCaptured:bool
+pieceIndex:byte
+
+
+
+ UMLClass
+
+ 333
+ 252
+ 144
+ 45
+
+ MoveGen
+
+
+
+ UMLClass
+
+ 288
+ 351
+ 117
+ 45
+
+ State
+
+
+
+ UMLClass
+
+ 288
+ 414
+ 117
+ 45
+
+ piece
+
+
+
+ UMLClass
+
+ 144
+ 351
+ 117
+ 45
+
+ Board
+
+
+
+ UMLClass
+
+ 0
+ 243
+ 117
+ 54
+
+ IMPCLOADR
+
+
+
+ UMLClass
+
+ 207
+ 180
+ 117
+ 45
+
+ stateAnalyser
+
+
+
+ UMLClass
+
+ 360
+ 180
+ 117
+ 45
+
+ MoveSelector
+
+
+
+ UMLClass
+
+ 576
+ 252
+ 117
+ 45
+
+ MoveSorter
+
+
+
+ Relation
+
+ 603
+ 288
+ 27
+ 81
+
+ lt=-
+ 10.0;10.0;10.0;70.0
+
+
+ Relation
+
+ 468
+ 270
+ 117
+ 99
+
+ lt=-
+ 10.0;10.0;110.0;10.0;110.0;90.0
+
+
+ Relation
+
+ 396
+ 360
+ 162
+ 27
+
+ lt=-
+ 160.0;10.0;10.0;10.0
+
+
+ Relation
+
+ 351
+ 387
+ 27
+ 45
+
+ lt=-
+ 10.0;10.0;10.0;30.0
+
+
+ Relation
+
+ 252
+ 369
+ 54
+ 27
+
+ lt=-
+ 40.0;10.0;10.0;10.0
+
+
+ Relation
+
+ 405
+ 216
+ 27
+ 54
+
+ lt=-
+ 10.0;40.0;10.0;10.0
+
+
+ Relation
+
+ 468
+ 189
+ 162
+ 81
+
+ lt=-
+ 10.0;10.0;160.0;10.0;160.0;70.0
+
+
+ Relation
+
+ 351
+ 288
+ 27
+ 81
+
+ lt=-
+ 10.0;10.0;10.0;70.0
+
+
+ Relation
+
+ 315
+ 189
+ 63
+ 27
+
+ lt=-
+ 50.0;10.0;10.0;10.0
+
+
+ Relation
+
+ 108
+ 270
+ 225
+ 99
+
+ lt=-
+ 10.0;10.0;230.0;10.0;230.0;90.0
+
+
+ Relation
+
+ 108
+ 216
+ 171
+ 63
+
+ lt=-
+ 10.0;50.0;170.0;50.0;170.0;10.0
+
+
diff --git a/UnitTests/DataClasses/BoardTests.cs b/UnitTests/DataClasses/BoardTests.cs
new file mode 100644
index 0000000..b6d809e
--- /dev/null
+++ b/UnitTests/DataClasses/BoardTests.cs
@@ -0,0 +1,29 @@
+using ChessAI.DataClasses;
+using NUnit.Framework;
+
+namespace UnitTests.DataClasses
+{
+ public class BoardTests
+ {
+ [Test]
+ public void IsIndexValidTest()
+ {
+ for (byte i = 0; i < 0x10; i++)
+ {
+ for (byte j = 0; j < 0x10; j++)
+ {
+
+ var index = (byte) (( i * 0x10 ) + j); // converts i and j to a combined index that should be correct
+ if (i < 8 && j < 8)
+ {
+ Assert.IsTrue(Board.IsIndexValid(index));
+ }
+ else
+ {
+ Assert.IsFalse(Board.IsIndexValid(index));
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/UnitTests/DataClasses/GameStateTests.cs b/UnitTests/DataClasses/GameStateTests.cs
new file mode 100644
index 0000000..35ea39e
--- /dev/null
+++ b/UnitTests/DataClasses/GameStateTests.cs
@@ -0,0 +1,76 @@
+using System;
+using System.Linq;
+using ChessAI.DataClasses;
+using static ChessAI.DataClasses.Piece;
+using NUnit.Framework;
+
+
+namespace UnitTests.DataClasses
+{
+ public class GameStateTests
+ {
+ private readonly Piece _rook = new Piece(White | Rook, 0x00);
+ private readonly Piece _pawn = new Piece(White | Pawn, 0x02);
+ private readonly Piece _queen = new Piece(Black | Queen, 0x20);
+
+ [Test]
+ public void GameStateConstructorTest()
+ {
+ var pieces = new[] { _rook, _pawn, _queen}.ToList();
+
+ var fields = new Piece[0x80];
+ fields[_rook.Position] = _rook;
+ fields[_pawn.Position] = _pawn;
+ fields[_queen.Position] = _queen;
+
+ var boardFromPieces = new Board(pieces);
+ var boardFromFields = new Board(fields);
+
+ var stateFromPieces = new GameState(boardFromPieces);
+ var stateFromFields = new GameState(boardFromFields);
+
+ AssertStatesDeepEqual(stateFromFields, stateFromPieces);
+
+ Assert.AreEqual(1, stateFromFields.BlackPieces.Length);
+ Assert.Contains(_queen, stateFromFields.BlackPieces);
+ Assert.Contains(_queen, stateFromPieces.BlackPieces);
+
+ Assert.AreEqual(2, stateFromPieces.WhitePieces.Length);
+ Assert.Contains(_rook, stateFromFields.WhitePieces);
+ Assert.Contains(_rook, stateFromPieces.WhitePieces);
+ Assert.Contains(_pawn, stateFromFields.WhitePieces);
+ Assert.Contains(_pawn, stateFromPieces.WhitePieces);
+ }
+
+ [Test]
+ public void ApplyMoveTest()
+ {
+ var state0 = new GameState(new Board(new[] { _rook, _pawn, _queen }.ToList()));
+
+ var move1 = Move.CreateSimpleMove(0x20, 0x22, state0);
+ var state1 = state0.ApplyMove(move1);
+
+ var move2 = Move.CreateSimpleMove(move1.EndPos, move1.StartPos, state1);
+ var state2 = state1.ApplyMove(move2);
+
+ AssertStatesDeepEqual(state0, state2);
+
+ var move3 = Move.CreateSimpleMove(move2.EndPos, _rook.Position, state2);
+ var state3 = state2.ApplyMove(move3);
+
+ Assert.AreEqual(1, state3.WhitePieces.Length);
+ Assert.Contains(_pawn, state3.WhitePieces);
+ }
+
+
+ private void AssertStatesDeepEqual(GameState state1, GameState state2)
+ {
+ //Test piece lists
+ Assert.AreEqual(state1.BlackPieces.Length, state2.BlackPieces.Length);
+ Assert.AreEqual(state1.WhitePieces.Length, state2.WhitePieces.Length);
+
+ //Test that the same board was constructed
+ Assert.IsTrue(state1.State.Fields.SequenceEqual(state2.State.Fields));
+ }
+ }
+}
\ No newline at end of file
diff --git a/UnitTests/DataClasses/PieceTests.cs b/UnitTests/DataClasses/PieceTests.cs
new file mode 100644
index 0000000..791d02d
--- /dev/null
+++ b/UnitTests/DataClasses/PieceTests.cs
@@ -0,0 +1,42 @@
+using ChessAI.DataClasses;
+using NUnit.Framework;
+using static ChessAI.DataClasses.Piece;
+
+namespace UnitTests.DataClasses
+{
+ public class PieceTests
+ {
+ [Test]
+ public void CreatePiece()
+ {
+ Piece piece = new Piece( White | Bishop );
+
+ var expected = 0b1000 | Bishop;
+ Assert.IsTrue(expected == piece);
+ }
+
+ [Test]
+ public void PieceToString()
+ {
+ var black = "Black";
+ var white = "White";
+ var pieces = new[]
+ {
+ "None",
+ "Pawn",
+ "Rook",
+ "Knight",
+ "Bishop",
+ "Queen",
+ "King",
+ };
+
+
+ for (byte i = 1; i < 6; i++)
+ {
+ Assert.AreEqual(white + " " + pieces[i], new Piece(White | i).ToString());
+ Assert.AreEqual(black + " " + pieces[i], new Piece(Black | i).ToString());
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/UnitTests/MinMaxTests.cs b/UnitTests/MinMaxTests.cs
new file mode 100644
index 0000000..bf3c5c8
--- /dev/null
+++ b/UnitTests/MinMaxTests.cs
@@ -0,0 +1,127 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using ChessAI;
+using ChessAI.DataClasses;
+using ChessAI.MoveSelection;
+using ChessAI.MoveSelection.MoveGeneration;
+using ChessAI.MoveSelection.StateAnalysis;
+using NUnit.Framework;
+
+namespace UnitTests
+{
+ public class MinMaxTests
+ {
+ [Test]
+ public void BestMoveTest()
+ {
+ var moveAndState = new MoveCalculatorStateAnalyserStub();
+ var moveSelector =
+ new MoveSelector(true, moveAndState, new MoveAnalyserStub(), moveAndState);
+
+ Assert.AreEqual("b", moveSelector.BestMove(new GameState(), 3 ));
+ Assert.AreEqual(new[] { "b", "g", "s" }, moveSelector.BestMoves);
+ }
+
+ [Test]
+ public void BestMoveIterativeTest()
+ {
+ var moveAndState = new MoveCalculatorStateAnalyserStub();
+ var moveSelector =
+ new MoveSelector(true, moveAndState, new MoveAnalyserStub(), moveAndState);
+
+ Assert.AreEqual(
+ "b",
+ moveSelector.BestMoveIterative(new GameState(), TimeSpan.FromSeconds(2), 3)
+ );
+
+ var expectedPath = new[] { "b", "g", "s" };
+ for (int i = 0; i < expectedPath.Length; ++i)
+ {
+ Assert.AreEqual(expectedPath[i], moveSelector.BestMoves[i]);
+ }
+ }
+ }
+
+ public class MoveAnalyserStub : IMoveAnalyser
+ {
+ public int MoveAnalysis(GameState state, Move move)
+ {
+ return 0;
+ }
+
+ public void SortMovesByBest(GameState state, List moves, Move previousBest)
+ {
+ moves.Sort((s, s1) =>
+ {
+ if (s.Equals(previousBest))
+ {
+ return 1;
+ }
+ else if (s1.Equals(previousBest))
+ {
+ return -1;
+ }
+
+ return (MoveAnalysis(state, s) - MoveAnalysis(state, s1)) % 1;
+ }
+ );
+ }
+ }
+
+ public class MoveCalculatorStateAnalyserStub : IMoveCalculator, IStateAnalyser
+ {
+ private Dictionary _tree;
+
+ public MoveCalculatorStateAnalyserStub()
+ {
+ // _tree = new Dictionary()
+ // {
+ // { new Move(), (5, new[] { "a", "b", "c" }) },
+ // { "a", (4, new[] { "d", "e", "f" }) },
+ // { "d", (4, new[] { "l" }) },
+ // { "l", (4, Array.Empty()) },
+ // { "e", (6, new[] { "m", "n", "o" }) },
+ // { "m", (6, Array.Empty()) },
+ // { "n", (2, Array.Empty()) },
+ // { "o", (6, Array.Empty()) },
+ // { "f", (9, new[] { "p", "q" }) },
+ // { "p", (3, Array.Empty()) },
+ // { "q", (9, Array.Empty()) },
+ // { "b", (5, new[] { "g", "h" }) },
+ // { "g", (5, new[] { "s", "t" }) },
+ // { "s", (5, Array.Empty()) },
+ // { "t", (2, Array.Empty()) },
+ // { "h", (7, new[] { "u", "v" }) },
+ // { "u", (7, Array.Empty()) },
+ // { "v", (3, Array.Empty()) },
+ // { "c", (1, new[] { "i", "j", "k" }) },
+ // { "i", (1, new[] { "w" }) },
+ // { "w", (1, Array.Empty()) },
+ // { "j", (7, new[] { "x", "y" }) },
+ // { "x", (7, Array.Empty()) },
+ // { "y", (2, Array.Empty()) },
+ // { "k", (6, new[] { "z", "aa", "ab" }) },
+ // { "z", (4, Array.Empty()) },
+ // { "aa", (6, Array.Empty()) },
+ // { "ab", (3, Array.Empty()) }
+ // };
+ // TODO re implement stub
+ }
+
+ public MoveCalculatorStateAnalyserStub(Dictionary nodeMap)
+ {
+ _tree = nodeMap;
+ }
+
+ public List CalculatePossibleMoves(GameState state, bool calculateForWhite)
+ {
+ return new List(); //_tree[state.State].Item2.ToList();
+ }
+
+ public int StaticAnalysis(GameState state)
+ {
+ return 0; //_tree[state.State].Item1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj
new file mode 100644
index 0000000..abb57b8
--- /dev/null
+++ b/UnitTests/UnitTests.csproj
@@ -0,0 +1,20 @@
+
+
+
+ netcoreapp5.0
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+