Skip to content

Commit

Permalink
Added UCIPlayer.cs and UCI engine compatibility and integrated stockf…
Browse files Browse the repository at this point in the history
…ish, partially standardized active player system. Other misc fixes
  • Loading branch information
Sargates authored and Sargates committed Aug 10, 2023
1 parent d0d0e4f commit b80e3fd
Show file tree
Hide file tree
Showing 25 changed files with 984 additions and 284 deletions.
3 changes: 3 additions & 0 deletions Chess-Bot.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,8 @@
<ItemGroup>
<PackageReference Include="Raylib-cs" Version="4.5.0.2" />
</ItemGroup>
<ItemGroup>
<None Include="resources/**/*.*" CopyToOutputDirectory="Always" CopyToPublishDirectory="Always"/>
</ItemGroup>

</Project>
22 changes: 22 additions & 0 deletions license.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
MIT License

Copyright (c) 2023 Nicholas Glenn
Credits to Yaroslav Bondarev (Stockfish.NET) (https://github.com/Oremiro/Stockfish.NET)

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.
3 changes: 1 addition & 2 deletions resources/Fonts/sdf.fs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ out vec4 finalColor;

// NOTE: Add here your custom variables

void main()
{
void main() {
// Texel color fetching from texture sampler
// NOTE: Calculate alpha using signed distance field (SDF)
float distanceFromOutline = texture(texture0, fragTexCoord).a - 0.5;
Expand Down
222 changes: 215 additions & 7 deletions src/Application/Core/Controller.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
using Raylib_cs;
using System;
using System.IO;
using System.Numerics;
using System.Globalization;
using System.Diagnostics;
using ChessBot.Engine;
using ChessBot.Helpers;
using ChessBot.Engine.Stockfish;


namespace ChessBot.Application {
Expand All @@ -17,6 +15,32 @@ public class Controller {
static Camera2D cam;
Model model;
View view;
public static Random random = new Random();

public bool SuspendPlay = false;
ChessPlayer whitePlayer;
ChessPlayer blackPlayer;

// Square selected on each interaction, -1 for invalid square
// { leftDown, leftUp, rightDown, rightUp }
public int[] mouseClickInfo = {-1, -1, -1, -1};
// in the format of: leftReleased, leftPressed, rightPressed, rightReleased
public static int mouseButtonsClicked; // 0b1111
public static int pressedKey=0;

public static bool IsLeftPressed => (Controller.mouseButtonsClicked & 8) == 8;
public static bool IsLeftReleased => (Controller.mouseButtonsClicked & 4) == 4;
public static bool IsRightPressed => (Controller.mouseButtonsClicked & 2) == 2;
public static bool IsRightReleased => (Controller.mouseButtonsClicked & 1) == 1;


// Return active player based on color passed, throw error if invalid color
public ChessPlayer GetPlayerFromColor(char color) => ("wb".IndexOf(color) == -1) ? throw new Exception("Invalid Color") : (color == 'w') ? whitePlayer : blackPlayer;


public void ExitPlayerThreads() { whitePlayer.RaiseExitFlag(); blackPlayer.RaiseExitFlag(); }
public void Join() { whitePlayer.Join(); blackPlayer.Join(); }
public ChessPlayer ActivePlayer => model.board.whiteToMove ? whitePlayer : blackPlayer;

public Controller() {

Expand All @@ -25,28 +49,39 @@ public Controller() {
Raylib.SetTraceLogLevel(TraceLogLevel.LOG_FATAL); // Ignore Raylib Errors unless fatal
Raylib.InitWindow(1600, 900, "Chess");

Debug.Assert(random != null);

cam = new Camera2D();
int screenWidth = Raylib.GetScreenWidth();
int screenHeight = Raylib.GetScreenHeight();
cam.target = new Vector2(0, 0);
cam.offset = new Vector2(screenWidth / 2f, screenHeight / 2f);
cam.zoom = 1.0f;


screenSize = new Vector2(Raylib.GetScreenWidth(), Raylib.GetScreenHeight());
model = new Model();
view = new View(screenSize, model, cam);

ChessPlayer.OnMoveChosen += MakeMove;

whitePlayer = new ChessPlayer();
blackPlayer = new UCIPlayer('b', "stockfish-windows-x86-64-avx2.exe", model.board);



}

public void MainLoop() {
float dt = 0f;

Stockfish stockfish = new Stockfish("./resources/stockfish-windows-x86-64-avx2.exe");


while (!Raylib.WindowShouldClose()) {
dt = Raylib.GetFrameTime();
mouseButtonsClicked = 0;
mouseButtonsClicked += Raylib.IsMouseButtonPressed(MouseButton.MOUSE_BUTTON_LEFT) ? 8 : 0;
mouseButtonsClicked += Raylib.IsMouseButtonReleased(MouseButton.MOUSE_BUTTON_LEFT) ? 4 : 0;
mouseButtonsClicked += Raylib.IsMouseButtonPressed(MouseButton.MOUSE_BUTTON_RIGHT) ? 2 : 0;
mouseButtonsClicked += Raylib.IsMouseButtonReleased(MouseButton.MOUSE_BUTTON_RIGHT) ? 1 : 0;
pressedKey = Raylib.GetKeyPressed();

if (Raylib.IsWindowResized()) {
view.camera.offset = new Vector2(Raylib.GetScreenWidth() / 2f, Raylib.GetScreenHeight() / 2f);
Expand All @@ -56,19 +91,192 @@ public void MainLoop() {
Raylib.BeginDrawing();
Raylib.ClearBackground(new Color(22, 22, 22, 255));
Raylib.DrawFPS(10, 10);

model.Update();
view.Update(dt);

if (! ActivePlayer.IsSearching && ! SuspendPlay) {
ActivePlayer.IsSearching = true;
}

if (pressedKey != 0) {
HandleKeyboardInput();
}
if (view.ui.activeAnimation is null && mouseButtonsClicked > 0) {
HandleMouseInput();
}

//* Draw menu here

Raylib.EndDrawing();
}

Raylib.CloseWindow();
ExitPlayerThreads();
Join();


view.Release();
UIHelper.Release();
}

public void HandleMouseInput() {
// TODO Test how ineffective it would be to constantly update mousePos and check if mouse is on a square
Piece clickedPiece = Piece.None;
int squareClicked = -1;
Move validMove = new Move(0);

Vector2 pos = Raylib.GetMousePosition() - screenSize/2;

Vector2 boardPos = (pos/BoardUI.squareSize);
if (view.ui.isFlipped) { boardPos *= -1; }
boardPos += new Vector2(4);

if ((0 <= boardPos.X && boardPos.X < 8 && 0 <= boardPos.Y && boardPos.Y < 8) ) {
squareClicked = 8*((int)(8-boardPos.Y))+(int)boardPos.X;
clickedPiece = model.board.GetSquare(squareClicked);
} // If the interaction (click/release) is in bounds, set square clicked and clicked piece, otherwise they will be -1 and {Piece.None}

if (squareClicked == -1) { // Case 1
view.ui.DeselectActiveSquare();
return;
} // Passes guard clause if the click was in bounds

if (view.ui.selectedIndex != -1) {
foreach (Move move in view.ui.movesForSelected) {
if (move == new Move(view.ui.selectedIndex, squareClicked)) {
validMove = move;
break;
}
}
}

if (IsLeftPressed) {
mouseClickInfo[0] = squareClicked;
view.ui.highlightedSquares = new bool[64];
if (! validMove.IsNull ) { // Case 3
view.ui.DeselectActiveSquare();
MakeMove(validMove);
//* ANIMATION HERE
} else
if (view.ui.selectedIndex != -1 && squareClicked == view.ui.selectedIndex) { // Case 5
view.ui.isDraggingPiece = true;
} else
if (view.ui.selectedIndex == -1 && clickedPiece != Piece.None) { // Case 2
if (model.enforceColorToMove && clickedPiece.Color == model.board.activeColor) {
view.ui.selectedIndex = squareClicked;
view.ui.movesForSelected = MoveGenerator.GetMoves(model.board, squareClicked);
view.ui.isDraggingPiece = true;
}
} else
if (validMove.IsNull && clickedPiece.Type != Piece.None) { // Case 6
if (model.enforceColorToMove && clickedPiece.Color == model.board.activeColor) {
view.ui.selectedIndex = squareClicked;
view.ui.movesForSelected = MoveGenerator.GetMoves(model.board, squareClicked);
view.ui.isDraggingPiece = true;
}
} else
if (validMove.IsNull) { // Case 4
view.ui.DeselectActiveSquare();
}
}

if (IsLeftReleased) {
mouseClickInfo[1] = squareClicked;
view.ui.isDraggingPiece = false;

if (! validMove.IsNull) {
view.ui.DeselectActiveSquare();
MakeMove(validMove, false); // Do not animate a move made on the release
}
mouseClickInfo[0] = -1; mouseClickInfo[1] = -1;
}

if (IsRightPressed) {
mouseClickInfo[2] = squareClicked;
view.ui.DeselectActiveSquare();
view.ui.isDraggingPiece = false;
}

if (IsRightReleased) {
mouseClickInfo[3] = squareClicked;
if (view.ui.selectedIndex == -1 && mouseClickInfo[0] == -1) {
view.ui.highlightedSquares[squareClicked] = ! view.ui.highlightedSquares[squareClicked];
} else
if (true) {
view.drawnArrows.Add((mouseClickInfo[2], mouseClickInfo[3]));
}
mouseClickInfo[2] = -1; mouseClickInfo[3] = -1;
}

//* Case 1: No square is selected, and square clicked is out of bounds => call DeselectActiveSquare ✓
//* Case 2: No square is selected and piece is clicked => Set selectedIndex to square clicked ✘
//* Case 3: Square is selected and square clicked is a valid move => call model.board.MakeMove ✘
//* Case 4: Square is selected and square clicked is not a valid move => Deselect piece and fallthrough to case 7 ✘
//* Case 5: Square is selected and square clicked == selected index => set isDragging to true ✘
//* Case 6: Square is selected and clicked piece is the same color => Subset of Case 7 ✓
//* Case 7: Square is selected and clicked piece is not in the valid moves => Superset of case 4 ✘
//* Case 7.1: If clicked square is a piece, select that square
}

public void HandleKeyboardInput() {
switch (pressedKey) {
case (int) KeyboardKey.KEY_Z :{
view.ui.DeselectActiveSquare();
Piece[] old = model.board.board.ToArray();
model.board.SetPrevState();
model.board.SetPrevState();
view.ui.activeAnimation = new BoardAnimation(old, model.board.board, 0.08f);
break;
}
case (int) KeyboardKey.KEY_X :{
view.ui.DeselectActiveSquare();
Piece[] old = model.board.board.ToArray();
model.board.SetNextState();
model.board.SetNextState();
view.ui.activeAnimation = new BoardAnimation(old, model.board.board, 0.08f);
break;
}
case (int) KeyboardKey.KEY_C :{
SuspendPlay = ! SuspendPlay;
break;
}

case (int) KeyboardKey.KEY_P :{
Console.WriteLine();
Console.WriteLine(model.board.GetUCIGameFormat());
break;
}
case (int) KeyboardKey.KEY_O :{
foreach(Fen state in model.board.stateHistory) {
Console.WriteLine($"{state} {state.moveMade}");
}
break;
}
case (int) KeyboardKey.KEY_I :{
Console.WriteLine(model.board.currentStateNode.Value);
break;
}
default: {
break;
}
}
}

public void MakeMove(Move move, bool animate=true) {
if (! move.IsNull) { // When null move is attempted, it's assumed it's checkmate, active color is the loser
ActivePlayer.IsSearching = false;
if (animate) {
Piece[] oldState = model.board.board.ToArray();
model.board.MakeMove(move);
view.ui.activeAnimation = new BoardAnimation(oldState, model.board.board, .12f);
return;
}
model.board.MakeMove(move);
return;
}

// Handle Checkmate
}

}
Expand Down
20 changes: 13 additions & 7 deletions src/Application/Core/Model.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics;
using ChessBot.Engine;
using ChessBot.Helpers;

Expand All @@ -6,30 +7,35 @@ public class Model {

public Board board;
public bool enforceColorToMove = false;
public readonly string[] botMatchStartFens;


public Model() {
board = new Board("r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1");
StartNewGame();

Player whitePlayer = new Player('w');
Player blackPlayer = new Player('b');
Debug.Assert(board != null);
botMatchStartFens = FileHelper.ReadResourceFile("Fens.txt").Split('\n');
}





public void StartNewGame() { StartNewGame(Fen.startpos); }
public void StartNewGame(string fenString) {
//* Instantiate starting gamestate
//* Instantiate new Board passing starting gamestate
//* Recalc bitboards
//*
//*


board = new Board(fenString);
ConsoleHelper.WriteLine($"\nNew game started\nFEN: {fenString}", ConsoleColor.Yellow);
}

public void Update() {

}




}
}
Loading

0 comments on commit b80e3fd

Please sign in to comment.