I have the basic UCI and Console application skeleton pretty much done:
Code: Select all
using System.Collections.Generic;
using StringBuilder = System.Text.StringBuilder;
namespace Crispy.UCI
{
public sealed class UCIClient
{
public delegate void OnUCIMessageDelegate(string _message);
public event OnUCIMessageDelegate OnUCIMessage;
private string fen;
private string ucicommand;
private bool running = false;
private Dictionary<string,string> positions = new Dictionary<string, string>()
{
{"startpos" , "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 "}
};
/* ---------------------------------------- *
*
* UCI Loop
*
* ---------------------------------------- */
public bool ParseUCICommand(string _uciCommand)
{
ucicommand = _uciCommand;
return false;
}
public void Run()
{
running = true;
UCILoop();
}
public void AbortAllTasks()
{
}
public void Stop()
{
running = false;
}
private void UCILoop()
{
while (running)
{
if (string.IsNullOrEmpty(ucicommand)) continue;
// get args from uci command
string[] args = ucicommand.Trim().Split();
if (args.Length < 1) continue;
// Parse uci command
switch (args[0])
{
// displays current board representation
case "d":
break;
// ---------------- UCI Commands -----------------
case "isready":
WriteMessage("readyok");
break;
case "uci":
DisplayEngineInfo();
break;
case "ucinewgame":
break;
case "position":
Parse_position(args);
break;
case "go":
Parse_go(args);
break;
default:
WriteMessage($"Unknown command: {args[0]}");
break;
}
// reset uci command
ucicommand = string.Empty;
}
}
private void DisplayEngineInfo()
{
WriteMessage($"id name Crispy");
WriteMessage($"id author Pedro Duran");
WriteMessage("readyok");
}
/* ---------------------------------------- *
*
* UCI parse
*
* ---------------------------------------- */
private void Parse_go(string[] _args)
{
if(_args.Length == 1 || _args[1] == "infinity")
{
WriteMessage("infinite mode");
return;
}
int wtime = GetParamValue<int>(_args, "wtime");
int btime = GetParamValue<int>(_args, "btime");
int movetime = GetParamValue<int>(_args, "movetime");
int depth = GetParamValue<int>(_args, "depth");
int movestogo = GetParamValue<int>(_args, "movestogo");
}
private void Parse_position(string[] _args)
{
if (_args.Length == 1) return;
if (positions.TryGetValue(_args[1] , out string _fen))
{
fen = _fen;
}else if (_args[1] == "fen")
{
// position fen [fenstring]
int endIndex = ucicommand.Contains("moves") ? ucicommand.IndexOf("moves") - 14 : ucicommand.Length - 13;
fen = ucicommand.Substring(13,endIndex);
}
WriteMessage($"fen: '{fen}'");
// read moves from input
if (ucicommand.Contains("moves"))
{
string[] moves_raw_list = ucicommand.Substring(ucicommand.IndexOf("moves") + 5).Trim().Split();
WriteMessage("moves: ", true);
for (int i = 0; i < moves_raw_list.Length; i++)
{
WriteMessage($"{moves_raw_list[i]}, " , true);
}
WriteMessage("\n", true);
}
}
private T GetParamValue<T>(string[] _args , string _paramName , T _defaultValue = default(T))
{
for (int x = 0; x < _args.Length; x++)
{
if (_args[x] == _paramName)
{
int nextIndex = x + 1;
if (nextIndex < _args.Length)
{
return (T)System.Convert.ChangeType(_args[nextIndex] , typeof(T));
}
}
}
return _defaultValue;
}
/* ---------------------------------------- *
*
* UCI Info
*
* ---------------------------------------- */
private void WriteMessage(string _message , bool _singleLine = false)
{
if (!_singleLine) _message += "\n";
if (OnUCIMessage != null) OnUCIMessage.Invoke(_message);
}
public void PrintInfoMessage(string _Infomessage)
{
WriteMessage($"info string {_Infomessage}");
}
public void PrintBestMove(int _bestMove)
{
// placeholder: bestmove
WriteMessage($"bestmove {_bestMove}");
}
public void PrintSearchInfo(int _depth, double _timeElapsed, ulong _nodeCount, int _score, int[] _pv)
{
double nps = (_nodeCount / (_timeElapsed / 1000));
StringBuilder pv_builder = new StringBuilder();
for (int i = 0; i < _pv.Length; i++) pv_builder.Append($"{_pv[i]} ");
WriteMessage($"info depth {_depth} time {_timeElapsed} nps {nps} nodes {_nodeCount} score cp {_score} pv {pv_builder.ToString()}");
}
}
}
That UCI Client runs in a background thread, it is called in the Main Application "Entry point", that is where the non-uci application commands are parsed, the engine is initialized, and it is contained the engine information such as engine version.
It is clearly the correct way to do it, in my older engine i was running everything in the main-thread, and it was impossible to the user to interrumpt the search using the "stop" command, now i want to allow that.
i've got some inspiration from "Cosette" for the "parse go" part, i wanted something powerful and simple to implement, the other things have been going smoothly, very happy with the results so far

i know is not very fast progress because i don't have very much free time right now to work in the engine but at least is something
