Chess-Engine (UCI-Protokoll) anbinden



  • Sehr geehrte Community,

    ich möchte gerne eine Chess-Engine (UCI-Protokoll) an ein eigenes Programm (Console) anbinden. Meine Vorstellung wäre einen externen Prozess (z.b. Stockfish) zu starten und dessen Ein -und Ausgaben umzuleiten. Gibt es dazu im Standard-C++ einfache Möglichkeiten? Ich brauche keinen vollständigen Code, es genügen schon Denkanstöße.

    Vielen Dank im Voraus

    Thomas



  • Schau mal in How do I execute a command and get the output of the command within C++ using POSIX? (du benötigst jedoch betriebssystemabhängigen Code dafür).

    Wenn du jedoch die Sourcen (bzw. als DLL/SO) direkt in dein Konsolenprojekt eingebunden hast, so kannst du mittels ios::rdbuf die Ein- und Ausgaben (cin, cout, cerr) umleiten.



  • Vielen Dank für die schnelle Antwort. Ich werde es mir gleich einmal anschauen.

    Thomas



  • das macht man mit pipes. bedenke, dass du 2 pipes brauchst.



  • Für Windows hab ich mal was geschrieben, was es ermöglicht, ein externes Programm aufzurufen und dessen Ausgabe wieder einzusammeln, Du findest den Code hier:
    https://www.c-plusplus.net/forum/topic/248213/system-befehl-ausgabe-umleiten/11



  • Hallo zusammen,

    vielen Dank Wade1234, ich bin nun auf _popen() gestoßen. Diesen Ansatz werde ich mal verfolgen.

    Wegen einer möglichen Portierung auf andere Systeme möchte ich auf #include "Windows.h"-Varianten zunächst erstmal verzichten.
    Aber trotzdem vielen Dank Belli. Ich werde mir deinen Code gerne einmal genauer anschauen. So kann ich nat. immer etwas dazu lernen.

    Vielen, vielen Dank für die konstruktiven Vorschläge!!!!!

    Thomas



  • @SakuraHanami bei popen hast du nur eine pipe, das funktioniert so nicht. aber andererseits sollst du ja auch deinen spaß haben und herumprobieren. 😜



  • Hallo Wade1234,

    ok danke. Ich hatte meinen Spaß und jetzt habe ich Kopfschmerzen 🙂 Bin schon dahintergekommen, dass ich so nicht weiterkomme. Ich versuche mein Glück nun mit _pipe(). Ich verstehe nicht, warum die Beispiele so unpraktisch, sinnfrei und gekünstelt wirken. Obwohl ich glaube, dass sie mich in die richtige Richtung weisen, kann ich sie nicht auf meine Bedürfnisse anpassen. Wie kann ich denn meinen externen Prozess mit einem Pipe verbinden?

    Danke für euer Interesse
    Thomas



  • also unter unix geht das allgemein mit pipe, dup2, fork und execv. ich saß da bestimmt eine woche lang dran, um das programm zu laufen zu bringen. vereinfacht gesagt musst du dir je eine pipe für stdin und für stdout erstellen, den prozess forken, mit dup2 stdin und stdout auf die jeweiligen pipes umleiten, das "andere ende" schließen und den prozess starten.

    für windows ist das ähnlich, wenn du die dokumentation für CreateProcess und CreatePipe schön durchliest, wirst du auf lange sicht auf ein beispiel stoßen, das dir erklärt, wie du ein- und ausgabe umleitest. also ich meine, dass du dir pipes erstellen und diese dann als input- und outputhandle an CreateProcess übergeben musst.

    achja printf, scanf und gets funktionieren mit pipes irgendwie immer nur manchmal, wenn du "\r" bzw. "\r\n" an das ende der zeichenkette anhängst (sprintf) . außerdem kannst du scanf irgendwie (mit setvbuf oder so) davon abhalten, die daten irgendwie zu puffern und das programm daher vom einfrieren abhalten. wie gesagt: viel spaß, mir konnte damals niemand mit meinem problem helfen.

    ps: für "portablen" code kannst du präprozessordirektiven verwenden:

    #ifdef WINDOWS
    //mach ganz viele windows-spezifische funktionen
    #endif
    
    #ifdef UNIX
    //mach ganz viele unix-spezifische funktionen
    #endif
    
    //mach irgendwas mit C++ bzw. ganz viele "allgemeine" funktionen
    


  • Danke, für deine hilfreichen Hinweise und deine wertvolle Zeit, die du geopfert hast. Ich hatte mir irgendwie eingebildet, dass C++ plattformunabhängig eine elegante Lösung bietet. Schließlich sind das ja elementare Funktionen. Eingabe und Ausgabe umbiegen und schon läuft der Laden. Wenn ich es dann auf Windows zum laufen bekomme, ist es nicht mehr weit bis zum Raspberry. Aber leider weit gefehlt. Naja, Aufgeben ist keine Option.

    Vielen Dank
    Thomas



  • also unter linux sieht das theoretisch etwa so aus:

    #include <unistd.h>
    #include <iostream>
    
    int main()
    {
         int inputpipe[2];
         int outputpipe[2];
    
         pid_t pid;
    
         int rc;
    
         pipe(inputpipe);
         pipe(outputpipe);
    
         pid = fork();
         if(pid == -1)
         {
              std::cout << "error on fork" << endl;
              return 1;
         }
         else if(pid == 0)
         {
              close(inputpipe[1]);
              close(outputpipe[0]);
    
              dup2(0, inputpipe[0]);
              dup2(1, outputpipe[1]);
    
              execv("meinprogrammpfad", NULL); //statt NULL evtl. auch die parameter
    
              std::cout << "error on execv" << endl;
              return 1;
         }
         else
         {
              close(inputpipe[0]);
              close(outputpipe[1]);
    
              std::cout << "Please enter command: ";
    
              std::string command;
              std::cin >> command;
    
              rc = write(inputpipe[1], command.c_str(), command.length() + 1);
              if(rc != command.length() + 1)
              {
                   std::cout << "error on write" << endl;
                   return 1;
              } 
    
              char output[1024]; //evtl. kann man statt dessen auch einen string verwenden, das weiß ich leider nicht
              rc = read(outputpipe[0], output, 1023);
              if(rc <= 0)
              {
                   std::cout << "error on read" << endl;
                   return 1;
              }
    
              output[rc] = 0;
              cout << child answered: << output << endl;
              
         }
    
         return 0;
    }
    

    für windows kannst du das beispielprogramm unter https://docs.microsoft.com/en-us/windows/win32/ProcThread/creating-a-child-process-with-redirected-input-and-output entsprechend anpassen.

    praktisch habe ich nach etwa 2 wochen genervt aufgegeben, weil ich nicht wusste, wie ich die standardfunktionen ansteuern muss.



  • Nach langen Recherchen, habe ich mich nun entschieden das Ganze mit Java umzusetzen. Ich habe nun endlich mein Ziel erreicht, und kann auf die Engine zugreifen. Ich komme ja eigentlich eher aus der Mikrocontroller-Ecke. Deshalb meine Affinität zu C++. Aber ich musste mir in diesem Fall eingestehen, dass es den Aufwand nicht rechtfertigt, meine Aufgabe unbedingt in C++ umzusetzen. Windows unterscheidet sich derart von unixbasierenden BS, dass es nur ein "Entweder Oder" geben kann.
    TEXT ist inkompatibel zu LPTSTR und solche Geschichten. Recherche ohne Ende, nur um einen simplen Text auf der Konsole anzuzeigen. Das mag alles irgendwie Sinn machen, aber bis man erst einmal darauf kommt!
    In Windows bleibe ich bei Java und in Unix/Linux werde ich es z.g.Z. noch mal mit C++ versuchen und mich an den Vorschlag von Wade1234 orientieren.

    Vielen Dank für euer Interesse

    Mit freundlichen Grüßen
    Thomas



  • @Wade1234 Danke, ich freue mich darauf es auszuprobieren. So macht das doch Sinn, oder? Es hat Spaß gemacht mit dir Erfahrungen auszutauschen. Alles Gute weiterhin.

    LG Thomas



  • @SakuraHanami sagte in Chess-Engine (UCI-Protokoll) anbinden:

    Ich hatte mir irgendwie eingebildet, dass C++ plattformunabhängig eine elegante Lösung bietet

    Nein, aber wie so oft in solchen Fällen gibt es da was von Boost.



  • @manni66 Danke für den wertvollen Hinweis, sieht sehr interessant aus! Unglaublich, wie umfassend auch für andere spezifischen Anliegen!



  • @SakuraHanami

    https://github.com/billforsternz/tarrasch-chess-gui
    inkl. C++ Source für UCI-Enginezugriff

    TEXT ist inkompatibel zu LPTSTR

    Das ist natürlich Unsinn als Argument gegen C++ und gar für Java.
    Ein #undef UNICODE bei MSVC hilft da schon sehr;
    ich spreche aus Erfahrung, wenige reine C Codezeilen mit wenigen (auch unter Windows vorhandenen) POSIX-Funktionen zur Synchronisation reichen aus, um das Protokoll von SMK zu implementieren.


Log in to reply