1

I am developing a program for analyzing chess problems - especially endgame problems - using the .exe version of the open-source chess-engine Stockfish 9.

Here is the (very simplified) EndgameAnalyzer class:

class EndgameAnalyzer
{
    private StockfishOracle oracle = new StockfishOracle();

    public void AnalyzeByFEN(string fen)
    {
        var evaluation = oracle.GetEvaluation(fen);
        Console.WriteLine($"{fen}\t{evaluation}");
    }
}

The AnalyzeByFEN method receives a FEN (a string representing a chess position) and writes down the engine evaluation for this position.

StockfishOracle is the class which is used to communicate with the engine (like oracles are used to communicate with the gods :)), using the UCI protocol. The relevant UCI commands for this question are:

uci: Enter uci mode.
position fen //followed by a FEN: Set a position to analyze.
go depth 1: Analyze the position one ply ("move") deep.

And here is the (again, very simplified) StockfishOracle class:

class StockfishOracle
{
    private Process process = new Process();

    public StockfishOracle()
    {
        process.StartInfo = new ProcessStartInfo()
        {
            FileName = @"C:\stockfish_9_x64.exe",
            UseShellExecute = false,
            RedirectStandardError = true,
            RedirectStandardInput = true,
            RedirectStandardOutput = true
        };

        process.Start();
        SendUCICommand("uci");
    }
    public string GetEvaluation(string fen)
    {
        SendUCICommand($"position fen {fen}");
        SendUCICommand("go depth 1");

        string result = string.Empty;
        while (!process.StandardOutput.EndOfStream)
        {
            result = process.StandardOutput.ReadLine();
        }
        return result;

    }
    private void SendUCICommand(string command)
    {
        process.StandardInput.WriteLine(command);
        process.StandardInput.Flush();
    }
}

When calling the AnalyzeByFEN method with a FEN, no output is shown in the console. A careful investigation led to the observation that the loop while (!process.StandardOutput.EndOfStream) is going forever, so the output is never returned. I am pretty new to processes, so I am pretty sure there are some basic mistakes in my code. How to fix this?

Thanks!

manlio
  • 18,345
  • 14
  • 76
  • 126
Michael Haddad
  • 4,085
  • 7
  • 42
  • 82
  • 1
    Well, i guess, as long as the process you started is running you won't encounter an "end of stream" on the process' standard output stream. Just parse the data from stockfish in your GetEvaluation method until you got what you are looking for. Then exit the stockfish process (writing the appropriate command to stockfish standard input stream, i would guess) and leave GetEvaluation by returning the obtained data. –  Jul 04 '18 at 17:45
  • @elgonzo - Thanks! Could you please demonstrate in an answer how will the code look like using your suggestion? The UCI command for stopping the engine is `quit`. – Michael Haddad Jul 04 '18 at 18:06

2 Answers2

1

It appears that stockfish returns "uciok" in the end of his job. You can try the following code to determine when it is finished (see if (line == "uciok")):

class Program
{
    class StockfishOracle
    {
        private readonly Process process = new Process();

        public StockfishOracle()
        {
            process.StartInfo = new ProcessStartInfo
            {
                FileName = @"D:\stockfish-9-win\Windows\stockfish_9_x64.exe",
                UseShellExecute = false,
                RedirectStandardError = true,
                RedirectStandardInput = true,
                RedirectStandardOutput = true
            };

            process.Start();
            SendUciCommand("uci");
        }
        public IEnumerable<string> GetEvaluation(string fen)
        {
            SendUciCommand($"position fen {fen}");
            SendUciCommand("go depth 1");

            while (!process.StandardOutput.EndOfStream)
            {
                var line = process.StandardOutput.ReadLine();
                yield return line;

                if (line == "uciok")
                {
                    break;
                }
            }
        }

        private void SendUciCommand(string command)
        {
            process.StandardInput.WriteLine(command);
            process.StandardInput.Flush();
        }
    }

    static void Main(string[] args)
    {
        var s = new StockfishOracle();

        foreach (var @out in s.GetEvaluation(""))
        {
            Console.WriteLine(@out);
        }
    }
}
Nicklaus Brain
  • 884
  • 6
  • 15
  • Thanks for the answer! It seems that you really put an effort into it. After running your code, I get all the output until `uciok`. But after that, how do I send a FEN to the `GetEvaluation` method and get the result? Thanks again! – Michael Haddad Jul 05 '18 at 14:40
  • Evaluate the second answer please – Nicklaus Brain Jul 05 '18 at 19:24
  • Is there a way to actually do that synchronically? – Michael Haddad Jul 09 '18 at 11:44
  • 1
    It is possible if you can determine what output line is going to be the last one. Otherwise, your ReadLine method will block your program eternally waiting for the output that will never happen. – Nicklaus Brain Jul 10 '18 at 17:04
1

Well, this appeared a nice riddle for me. Let's consider another approach and try to communicate with the chess oracle asynchronously:

class Program
{
    class StockfishOracle
    {
        private readonly Process process = new Process();

        public StockfishOracle()
        {
            process.StartInfo = new ProcessStartInfo
            {
                FileName = @"D:\stockfish-9-win\Windows\stockfish_9_x64.exe",
                UseShellExecute = false,
                RedirectStandardError = true,
                RedirectStandardInput = true,
                RedirectStandardOutput = true
            };

            process.OutputDataReceived += (sender, args) => this.DataReceived.Invoke(sender, args);
        }

        public event DataReceivedEventHandler DataReceived = (sender, args) => {};

        public void Start()
        {
            process.Start();
            process.BeginOutputReadLine();
        }

        public void Wait(int millisecond)
        {
            this.process.WaitForExit(millisecond);
        }

        public void SendUciCommand(string command)
        {
            process.StandardInput.WriteLine(command);
            process.StandardInput.Flush();
        }

    }

    static void Main()
    {
        var oracle = new StockfishOracle();
        // this will contain all the output of the oracle
        var output = new ObservableCollection<string>();
        // in this way we redirect output from oracle to stdout of the main process
        output.CollectionChanged += (sender, eventArgs) => Console.WriteLine(eventArgs.NewItems[0]);
        // in this way collect all the output from oracle
        oracle.DataReceived += (sender, eventArgs) => output.Add(eventArgs.Data);

        oracle.Start();

        oracle.SendUciCommand("position fen rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
        oracle.SendUciCommand("position startpos moves e2e4");
        oracle.SendUciCommand("go depth 20");

        oracle.Wait(5000); // if output does not contain bestmove after given time, you can wait more

        var bestMove = output.Last();

        Console.WriteLine("Oracle says that the best move is: " + bestMove);
    }
}

As far as i understood you are looking for the prediction of the best move. Now you can wait until it appear in the output. Also using the same event handler you can analyze every string the oracle writes into output until you see desired one.

Nicklaus Brain
  • 884
  • 6
  • 15
  • This works! Thanks. How would you change the code if you would like to get the "score cp" instead of the best move? – Michael Haddad Jul 05 '18 at 19:37
  • public class MoveInfo { private readonly string moveString; private const string scorecp = "score cp", nodes = "nodes"; public MoveInfo(string moveString) { this.moveString = moveString; } public string Score => moveString.Contains(scorecp) ? moveString.Substring(moveString.IndexOf(scorecp), moveString.IndexOf(nodes) - moveString.IndexOf(scorecp)) : string.Empty; } var moves = new List(); oracle.DataReceived += (sender, args) => { if (args.Data.Contains("info")) moves.Add(new MoveInfo(args.Data)); }; – Nicklaus Brain Jul 05 '18 at 20:09
  • Works perfectly! Question: is it necessary to have the `oracle.Wait(5000)` method? I would really like it to give me the result the fastest possible. – Michael Haddad Jul 06 '18 at 10:17
  • 1
    It isn't necessary if you can determine the end condition in your event handler: 'oracle.DataReceived += (sender, eventArgs) => ...' then you can just exit from your app when you determine it. Also, it isn't necessary if your application has GUI (since it has a main thread with the event loop). But in this particular example, console application will exit just when it reaches the end of Main procedure, Therefore I had to put wait block. – Nicklaus Brain Jul 06 '18 at 11:40
  • You are very helpful. My main goal is to have a loop that sends thousands of fens into the oracle, and then log the results into a .txt file (each line is like this: `{fen} {scroe}`). How would you do that? I am having trouble doing this with the asynchronous way. – Michael Haddad Jul 06 '18 at 11:45
  • 1
    In that case can't you just send all the commands to oracle and then wait infinitely until there is no output in terminal, and then shut down the program manually? – Nicklaus Brain Jul 06 '18 at 12:23
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/174497/discussion-between-sipo-and-nicklaus-brain). – Michael Haddad Jul 06 '18 at 12:24