Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make match stats better #327

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
using ChessChallenge.Chess;
using ChessChallenge.Chess;
using ChessChallenge.Example;
using Raylib_cs;
using System;
using System.IO;
using System.Linq;
using System.Runtime.ExceptionServices;
using System.Text;
using System.Threading;
Expand All @@ -23,12 +22,11 @@ public enum PlayerType
}

// Game state
Random rng;
int gameID;
bool isPlaying;
Board board;
public ChessPlayer PlayerWhite { get; private set; }
public ChessPlayer PlayerBlack {get;private set;}
public ChessPlayer PlayerBlack { get; private set; }

float lastMoveMadeTime;
bool isWaitingToPlayMove;
Expand All @@ -40,7 +38,7 @@ public enum PlayerType
readonly string[] botMatchStartFens;
int botMatchGameIndex;
public BotMatchStats BotStatsA { get; private set; }
public BotMatchStats BotStatsB {get;private set;}
public BotMatchStats BotStatsB { get; private set; }
bool botAPlaysWhite;


Expand All @@ -54,22 +52,26 @@ public enum PlayerType
readonly MoveGenerator moveGenerator;
readonly int tokenCount;
readonly StringBuilder pgns;
public bool fastForward;

int totalMovesPlayed = 0;
public int trueTotalMovesPlayed = 0;

public ChallengeController()
{
Log($"Launching Chess-Challenge version {Settings.Version}");
tokenCount = GetTokenCount();
Warmer.Warm();

rng = new Random();
moveGenerator = new();
boardUI = new BoardUI();
board = new Board();
pgns = new();
fastForward = false;

BotStatsA = new BotMatchStats("IBot");
BotStatsB = new BotMatchStats("IBot");
botMatchStartFens = FileHelper.ReadResourceFile("Fens.txt").Split('\n').Where(fen => fen.Length > 0).ToArray();
botMatchStartFens = FileHelper.ReadResourceFile("Fens.txt").Split('\n');
botTaskWaitHandle = new AutoResetEvent(false);

StartNewGame(PlayerType.Human, PlayerType.MyBot);
Expand All @@ -79,7 +81,7 @@ public void StartNewGame(PlayerType whiteType, PlayerType blackType)
{
// End any ongoing game
EndGame(GameResult.DrawByArbiter, log: false, autoStartNextBotMatch: false);
gameID = rng.Next();
gameID++;

// Stop prev task and create a new one
if (RunBotsOnSeparateThread)
Expand Down Expand Up @@ -142,7 +144,11 @@ void BotThinkerThread()

Move GetBotMove()
{

totalMovesPlayed++;

API.Board botBoard = new(board);

try
{
API.Timer timer = new(PlayerToMove.TimeRemainingMs);
Expand Down Expand Up @@ -271,15 +277,16 @@ void PlayMove(Move move)

void EndGame(GameResult result, bool log = true, bool autoStartNextBotMatch = true)
{
trueTotalMovesPlayed += totalMovesPlayed;
totalMovesPlayed = 0;
if (isPlaying)
{
isPlaying = false;
isWaitingToPlayMove = false;
gameID = -1;

if (log)
{
Log("Game Over: " + result, false, ConsoleColor.Blue);
Log("Game Over: " + result + " Match: " + CurrGameNumber, false, ConsoleColor.Blue);
}

string pgn = PGNCreator.CreatePGN(board, result, GetPlayerName(PlayerWhite), GetPlayerName(PlayerBlack));
Expand All @@ -295,16 +302,24 @@ void EndGame(GameResult result, bool log = true, bool autoStartNextBotMatch = tr
if (botMatchGameIndex < numGamesToPlay && autoStartNextBotMatch)
{
botAPlaysWhite = !botAPlaysWhite;
const int startNextGameDelayMs = 600;
System.Timers.Timer autoNextTimer = new(startNextGameDelayMs);
int originalGameID = gameID;
autoNextTimer.Elapsed += (s, e) => AutoStartNextBotMatchGame(originalGameID, autoNextTimer);
autoNextTimer.AutoReset = false;
autoNextTimer.Start();

if (fastForward)
{
StartNewGame(PlayerBlack.PlayerType, PlayerWhite.PlayerType);
}
else
{
const int startNextGameDelayMs = 600;
System.Timers.Timer autoNextTimer = new(startNextGameDelayMs);
int originalGameID = gameID;
autoNextTimer.Elapsed += (s, e) => AutoStartNextBotMatchGame(originalGameID, autoNextTimer);
autoNextTimer.AutoReset = false;
autoNextTimer.Start();
}
}
else if (autoStartNextBotMatch)
{
fastForward = false;
Log("Match finished", false, ConsoleColor.Blue);
}
}
Expand Down Expand Up @@ -350,31 +365,41 @@ void UpdateStats(BotMatchStats stats, bool isWhiteStats)

public void Update()
{
if (isPlaying)

do
{
PlayerWhite.Update();
PlayerBlack.Update();
if (isPlaying)
{
PlayerWhite.Update();
PlayerBlack.Update();

PlayerToMove.UpdateClock(Raylib.GetFrameTime());
if (PlayerToMove.TimeRemainingMs <= 0)
PlayerToMove.UpdateClock(Raylib.GetFrameTime() + MinMoveDelay);
if (PlayerToMove.TimeRemainingMs <= 0)
{
EndGame(PlayerToMove == PlayerWhite ? GameResult.WhiteTimeout : GameResult.BlackTimeout);
}
else
{
if (isWaitingToPlayMove && (Raylib.GetTime() >= playMoveTime || fastForward))
{
isWaitingToPlayMove = false;
PlayMove(moveToPlay);
}
}
}

if (hasBotTaskException)
{
EndGame(PlayerToMove == PlayerWhite ? GameResult.WhiteTimeout : GameResult.BlackTimeout);
hasBotTaskException = false;
botExInfo.Throw();
}
else

if (PlayerWhite.IsHuman || PlayerBlack.IsHuman)
{
if (isWaitingToPlayMove && Raylib.GetTime() > playMoveTime)
{
isWaitingToPlayMove = false;
PlayMove(moveToPlay);
}
fastForward = false;
}
}

if (hasBotTaskException)
{
hasBotTaskException = false;
botExInfo.Throw();
}
} while (fastForward && isWaitingToPlayMove);
}

public void Draw()
Expand Down
3 changes: 2 additions & 1 deletion Chess-Challenge/src/Framework/Application/Core/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ public static class Settings
// Game settings
public const int GameDurationMilliseconds = 60 * 1000;
public const float MinMoveDelay = 0;
public static readonly bool RunBotsOnSeparateThread = true;
public static bool RunBotsOnSeparateThread = true; // IF NOT IN FAST FORWARD, TURN THIS ON - It's no longer readonly

// Display settings
public const bool DisplayBoardCoordinates = true;
public static readonly Vector2 ScreenSizeSmall = new(1280, 720);
public static readonly Vector2 ScreenSizeBig = new(1920, 1080);
public static readonly Vector2 ScreenSizeXS = new (400, 200);

// Other settings
public const int MaxTokenCount = 1024;
Expand Down
89 changes: 83 additions & 6 deletions Chess-Challenge/src/Framework/Application/UI/MatchStatsUI.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Raylib_cs;
using Raylib_cs;
using System.Numerics;
using System;
using static System.Formats.Asn1.AsnWriter;

namespace ChessChallenge.Application
{
Expand All @@ -14,7 +15,10 @@ public static void DrawMatchStats(ChallengeController controller)
int regularFontSize = UIHelper.ScaleInt(35);
int headerFontSize = UIHelper.ScaleInt(45);
Color col = new(180, 180, 180, 255);
Vector2 startPos = UIHelper.Scale(new Vector2(1500, 250));
Color white = new(225, 225, 225, 225);
Color red = new Color(200, 0, 0, 255);
Color green = new Color(0, 200, 0, 255);
Vector2 startPos = UIHelper.Scale(new Vector2(1500, 150));
float spacingY = UIHelper.Scale(35);

DrawNextText($"Game {controller.CurrGameNumber} of {controller.TotalGameCount}", headerFontSize, Color.WHITE);
Expand All @@ -23,22 +27,95 @@ public static void DrawMatchStats(ChallengeController controller)
DrawStats(controller.BotStatsA);
startPos.Y += spacingY * 2;
DrawStats(controller.BotStatsB);


startPos.Y += spacingY * 2;

string eloDifference = CalculateElo(controller.BotStatsA.NumWins, controller.BotStatsA.NumDraws, controller.BotStatsA.NumLosses);
string errorMargin = CalculateErrorMargin(controller.BotStatsA.NumWins, controller.BotStatsA.NumDraws, controller.BotStatsA.NumLosses);

DrawNextText($"Elo Difference:", headerFontSize, Color.WHITE);
DrawNextText($"{eloDifference} {errorMargin}", regularFontSize, Color.GRAY);

void DrawStats(ChallengeController.BotMatchStats stats)
{
DrawNextText(stats.BotName + ":", nameFontSize, Color.WHITE);
DrawNextText($"Score: +{stats.NumWins} ={stats.NumDraws} -{stats.NumLosses}", regularFontSize, col);
DrawNextText($"Score: +{stats.NumWins} ={stats.NumDraws} -{stats.NumLosses}", regularFontSize, white);
DrawNextText($"Num Timeouts: {stats.NumTimeouts}", regularFontSize, col);
DrawNextText($"Num Illegal Moves: {stats.NumIllegalMoves}", regularFontSize, col);
DrawNextText($"Winrate: {(float)stats.NumWins / (controller.CurrGameNumber - 1) * 100}%", regularFontSize, green);
DrawNextText($"Draw rate: {(float)stats.NumDraws / (controller.CurrGameNumber - 1) * 100}%", regularFontSize, white);
DrawNextText($"Loss rate: {(float)stats.NumLosses / (controller.CurrGameNumber - 1) * 100}%", regularFontSize, red);
}

DrawNextText($"Average moves per game: {controller.trueTotalMovesPlayed / controller.CurrGameNumber - 1}", regularFontSize, white);


void DrawNextText(string text, int fontSize, Color col)
{
UIHelper.DrawText(text, startPos, fontSize, 1, col);
startPos.Y += spacingY;
}
}
}

private static string CalculateElo(int wins, int draws, int losses)
{
double score = wins + draws / 2;
int totalGames = wins + draws + losses;
double difference = CalculateEloDifference(score / totalGames);
if ((int)difference == -2147483648)
{
if (difference > 0) return "+Inf";
else return "-Inf";
}

return $"{(int)difference}";
}

private static double CalculateEloDifference(double percentage)
{
return -400 * Math.Log(1 / percentage - 1) / 2.302;
}

private static string CalculateErrorMargin(int wins, int draws, int losses)
{
double total = wins + draws + losses;
double winP = wins / total;
double drawP = draws / total;
double lossP = losses / total;

double percentage = (wins + draws / 2) / total;
double winDev = winP * Math.Pow(1 - percentage, 2);
double drawsDev = drawP * Math.Pow(0.5 - percentage, 2);
double lossesDev = lossP * Math.Pow(0 - percentage, 2);

double stdDeviation = Math.Sqrt(winDev + drawsDev + lossesDev) / Math.Sqrt(total);

double confidenceP = 0.95;
double minConfidenceP = (1 - confidenceP) / 2;
double maxConfidenceP = 1 - minConfidenceP;
double devMin = percentage + PhiInv(minConfidenceP) * stdDeviation;
double devMax = percentage + PhiInv(maxConfidenceP) * stdDeviation;

double difference = CalculateEloDifference(devMax) - CalculateEloDifference(devMin);
double margin = Math.Round(difference / 2);
if (double.IsNaN(margin)) return "";
return $"+/- {margin}";
}

private static double PhiInv(double p)
{
return Math.Sqrt(2) * CalculateInverseErrorFunction(2 * p - 1);
}

private static double CalculateInverseErrorFunction(double x)
{
double a = 8 * (Math.PI - 3) / (3 * Math.PI * (4 - Math.PI));
double y = Math.Log(1 - x * x);
double z = 2 / (Math.PI * a) + y / 2;

double ret = Math.Sqrt(Math.Sqrt(z * z - y / a) - z);
if (x < 0) return -ret;
return ret;
}
}
}
}
14 changes: 13 additions & 1 deletion Chess-Challenge/src/Framework/Application/UI/MenuUI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,22 @@ public static void DrawButtons(ChallengeController controller)
{
Program.SetWindowSize(isBigWindow ? Settings.ScreenSizeSmall : Settings.ScreenSizeBig);
}

if(NextButtonInRow("Smallerer Window", ref buttonPos, spacing, buttonSize))
{
Program.SetWindowsSize(Settings.ScreenSizeXS);
}

if (NextButtonInRow("Exit (ESC)", ref buttonPos, spacing, buttonSize))
{
Environment.Exit(0);
}
if (NextButtonInRow("Fast forward", ref buttonPos, spacing, buttonSize))
{
controller.fastForward = !controller.fastForward;
if(controller.fastForward) Settings.RunBotsOnSeparateThread = false;
else Settings.RunBotsOnSeparateThread = true;
}

bool NextButtonInRow(string name, ref Vector2 pos, float spacingY, Vector2 size)
{
Expand All @@ -78,4 +90,4 @@ bool NextButtonInRow(string name, ref Vector2 pos, float spacingY, Vector2 size)
}
}
}
}
}
10 changes: 10 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM docker.io/archlinux:latest
RUN pacman-key --init
RUN pacman -Syu --noconfirm \
dotnet-sdk-6.0 \
mesa \
&& pacman -Scc --noconfirm
COPY . /app/
WORKDIR /app/Chess-Challenge/
RUN cd /app/ && dotnet build
CMD dotnet run
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ All names (variables, functions, etc.) are counted as a single token, regardless
* For example: `cd C:\Users\MyName\Desktop\Chess-Challenge\Chess-Challenge`
* Now use the command: `dotnet run`
* This should launch the project. If not, open an issue with any error messages and relevant info.
* [Running on Linux](https://github.com/SebLague/Chess-Challenge/discussions/3)
* [Running on Linux](https://github.com/SebLague/Chess-Challenge/discussions/3) Or with the Dockerfile provided (run: `./run.sh` (you need to have docker or podman installed on your machine))
* Issues with illegal moves or errors when making/undoing a move
* Make sure that you are making and undoing moves in the correct order, and that you don't forget to undo a move when exiting early from a function for example.
* How to tell what colour MyBot is playing
Expand Down
Loading