Loop doesn't reach end of stream

285 Views Asked by At

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!

2

There are 2 best solutions below

7
On BEST ANSWER

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.

4
On

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);
        }
    }
}