0

我正在创建一个使用 Stockfish 下棋的程序。我打算在处理板和命令的终端中玩。我希望能够通过首先在 python 中获取数据来将数据发送到终端,但我不知道如何从我的搜索中将数据发送到终端。

作为一个简单的例子,我想做这样的事情:

os.startfile(path_to_executable)  # dont necessaryily need this, but I found I can run the .exe through python using this

command = "arbitrary string" 

while command != "end":
    command = input("Type command, type end to finish")  # get the command to run
    if command != "end":
        write_command_to_terminal(command)  # send the command to the terminal (Question I am asking)
def write_command_to_terminal(command: str):
    # fixme: how do I write to the other executable?

我知道有一些库是为了利用鱼和其他国际象棋相关的东西而构建的,但是我打算不使用它们,而是使用 UCI 协议从头开始制作它。

4

2 回答 2

0

这是一个示例程序,允许我们从命令行播放像 stockfish 这样的 uci 引擎。

在提示符下,如果命令以# 开头,则它只是一个注释。

代码

from subprocess import Popen, PIPE, STDOUT


def command(p, command):
    p.stdin.write(f'{command}\n')
    
    
def play(efile):
    # start the engine, be sure to send quit command to terminate the program and the engine
    engine = Popen([efile], stdout=PIPE, stdin=PIPE, stderr=STDOUT, bufsize=0, text=True)
    
    while True:
        userinput = input('> ')
        line = userinput.rstrip()
        
        if line.startswith('go'):
            # go movetime 1000
            command(engine, line)
            for elines in iter(engine.stdout.readline, ''):
                eline = elines.strip()
                print(eline)
                if 'bestmove' in eline:
                    break

        elif line.startswith('#'):
            pass
        
        elif line == 'ucinewgame':
            command(engine, line)

        elif line == 'board':
            # Show board
            command(engine, 'd')
            for elines in iter(engine.stdout.readline, ''):
                eline = elines.strip()
                print(eline)
                if 'Checkers' in eline:
                    break

        elif line.startswith('setoption name'):
            command(engine, line)

        elif 'position ' in line:
            # position startpos moves e2e4
            command(engine, line)

        elif line == 'uci':
            command(engine, 'uci')
            for elines in iter(engine.stdout.readline, ''):
                eline = elines.strip()
                print(eline)
                if 'uciok' in eline:
                    break

        elif line == 'isready':
            command(engine, line)
            for elines in iter(engine.stdout.readline, ''):
                eline = elines.strip()
                print(eline)
                if 'readyok' in eline:
                    break

        elif line == 'quit':
            command(engine, line)
            break
            

# Start
efile = 'F:\\Chess\\Engines\\stockfish\\sf14\\sf14.exe'
play(efile)

样本输出

python play_sf.py
> # send uci command to see the engine id and author 
> uci
Stockfish 14 by the Stockfish developers (see AUTHORS file)
id name Stockfish 14
id author the Stockfish developers (see AUTHORS file)      

option name Debug Log File type string default
option name Threads type spin default 1 min 1 max 512
option name Hash type spin default 16 min 1 max 33554432
option name Clear Hash type button
option name Ponder type check default false
option name MultiPV type spin default 1 min 1 max 500
option name Skill Level type spin default 20 min 0 max 20
option name Move Overhead type spin default 10 min 0 max 5000
option name Slow Mover type spin default 100 min 10 max 1000
option name nodestime type spin default 0 min 0 max 10000
option name UCI_Chess960 type check default false
option name UCI_AnalyseMode type check default false
option name UCI_LimitStrength type check default false
option name UCI_Elo type spin default 1350 min 1350 max 2850
option name UCI_ShowWDL type check default false
option name SyzygyPath type string default <empty>
option name SyzygyProbeDepth type spin default 1 min 1 max 100
option name Syzygy50MoveRule type check default true
option name SyzygyProbeLimit type spin default 7 min 0 max 7
option name Use NNUE type check default true
option name EvalFile type string default nn-3475407dc199.nnue
uciok
> # check if engine is ready
> isready
readyok
> # ohh its ready
> # set the hash memory to 64 mb
> setoption name Hash value 64
> # setup the start position
> position startpos
> # let's see the board
> board

+---+---+---+---+---+---+---+---+  
| r | n | b | q | k | b | n | r | 8
+---+---+---+---+---+---+---+---+  
| p | p | p | p | p | p | p | p | 7
+---+---+---+---+---+---+---+---+  
|   |   |   |   |   |   |   |   | 6
+---+---+---+---+---+---+---+---+  
|   |   |   |   |   |   |   |   | 5
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   | 4
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   | 3
+---+---+---+---+---+---+---+---+
| P | P | P | P | P | P | P | P | 2
+---+---+---+---+---+---+---+---+
| R | N | B | Q | K | B | N | R | 1
+---+---+---+---+---+---+---+---+
a   b   c   d   e   f   g   h

Fen: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
Key: 8F8F01D4562F59FB
Checkers:
> # we play white and our first move is e2e4
> position startpos moves e2e4
> # We let the engine search for 1sec or 1000 millisec
> go movetime 1000
info string NNUE evaluation using nn-3475407dc199.nnue enabled
info depth 1 seldepth 1 multipv 1 score cp -13 nodes 22 nps 22000 tbhits 0 time 1 pv e7e5
info depth 2 seldepth 2 multipv 1 score cp -3 nodes 46 nps 46000 tbhits 0 time 1 pv e7e5 a2a3
info depth 3 seldepth 3 multipv 1 score cp -1 nodes 116 nps 116000 tbhits 0 time 1 pv e7e5 a2a3 f8c5
info depth 4 seldepth 4 multipv 1 score cp -47 nodes 403 nps 201500 tbhits 0 time 2 pv g8f6 b1c3 e7e5 g1f3
info depth 5 seldepth 5 multipv 1 score cp -65 nodes 968 nps 322666 tbhits 0 time 3 pv e7e6 d2d4 d7d5 b1c3
info depth 6 seldepth 6 multipv 1 score cp -40 nodes 1706 nps 341200 tbhits 0 time 5 pv c7c6 d2d4 d7d5 e4e5
info depth 7 seldepth 7 multipv 1 score cp -32 nodes 2308 nps 384666 tbhits 0 time 6 pv c7c6 d2d4 d7d5 e4d5 c6d5 g1f3 c8g4
info depth 8 seldepth 9 multipv 1 score cp -37 nodes 5030 nps 558888 tbhits 0 time 9 pv e7e5 g1f3 b8c6 f1b5 g8f6 e1g1 f6e4
info depth 9 seldepth 11 multipv 1 score cp -40 nodes 8039 nps 535933 tbhits 0 time 15 pv e7e5 g1f3 b8c6 f1b5 g8f6 e1g1 f6e4 f1e1 e4d6 f3e5 c6e5
info depth 10 seldepth 15 multipv 1 score cp -36 nodes 16143 nps 597888 tbhits 0 time 27 pv e7e5 g1f3 b8c6 f1b5 g8f6 e1g1 f6e4 d2d4 e5d4 f1e1
info depth 11 seldepth 15 multipv 1 score cp -55 nodes 38538 nps 621580 tbhits 0 time 62 pv e7e5 g1f3 b8c6 d2d4 e5d4 f3d4 f8b4 c2c3 b4c5 d4c6 b7c6
info depth 12 seldepth 16 multipv 1 score cp -37 nodes 64205 nps 668802 tbhits 0 time 96 pv c7c5 g1f3 b8c6 f1b5 e7e5 e1g1 d7d6 f1e1 g8f6 b1c3 c8g4
info depth 13 seldepth 16 multipv 1 score cp -32 nodes 101299 nps 693828 tbhits 0 time 146 pv c7c5 g1f3 d7d6 f1b5 c8d7 b5d7 d8d7 e1g1 g7g6 c2c3 g8f6 f1e1 f8g7
info depth 14 seldepth 18 multipv 1 score cp -33 nodes 135174 nps 700383 tbhits 0 time 193 pv c7c5 g1f3 d7d6 f1b5 c8d7 b5d7 d8d7 e1g1 g7g6 c2c3 g8f6 f1e1 f8g7 d2d4 c5d4 c3d4 b8c6
info depth 15 seldepth 21 multipv 1 score cp -33 nodes 209066 nps 718439 tbhits 0 time 291 pv c7c5 c2c3 g8f6 e4e5 f6d5 g1f3 b8c6 f1c4 d5b6 c4b5 d7d5 d2d4 c5d4 c3d4
info depth 16 seldepth 20 multipv 1 score cp -25 nodes 348508 nps 723045 tbhits 0 time 482 pv c7c5 c2c3 g8f6 e4e5 f6d5 d2d4 c5d4 g1f3 b8c6 c3d4 e7e6 f1c4 d7d6 e1g1 f8e7 d1e2 e8g8 b1c3
info depth 17 seldepth 22 multipv 1 score cp -26 nodes 456537 nps 720089 tbhits 0 time 634 pv c7c5 g1f3 d7d6 f1b5 c8d7 b5d7 d8d7 e1g1 g8f6 f1e1 e7e6 c2c3 f8e7 d2d4 e8g8 d4d5 e6d5
info depth 18 seldepth 25 multipv 1 score cp -26 nodes 643841 nps 718572 tbhits 0 time 896 pv c7c5 g1f3 d7d6 f1b5 c8d7 b5d7 d8d7 e1g1 e7e6 c2c3 d6d5 e4d5 d7d5 b1a3 b8c6 d2d4 c5d4 f3d4
info depth 19 seldepth 24 multipv 1 score cp -35 upperbound nodes 721201 nps 720480 hashfull 90 tbhits 0 time 1001 pv c7c5 g1f3
bestmove c7c5 ponder g1f3
> # engine move is c7c5, let's see the board
> board

+---+---+---+---+---+---+---+---+
| r | n | b | q | k | b | n | r | 8
+---+---+---+---+---+---+---+---+
| p | p | p | p | p | p | p | p | 7
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   | 6
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   | 5
+---+---+---+---+---+---+---+---+
|   |   |   |   | P |   |   |   | 4
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   | 3
+---+---+---+---+---+---+---+---+
| P | P | P | P |   | P | P | P | 2
+---+---+---+---+---+---+---+---+
| R | N | B | Q | K | B | N | R | 1
+---+---+---+---+---+---+---+---+
a   b   c   d   e   f   g   h

Fen: rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1
Key: B46022469E3DD31B
Checkers:
> # engine move is not on the board, we need to send the position command
> position startpos moves e2e4 c7c5
> # let's see the board now
> board

+---+---+---+---+---+---+---+---+
| r | n | b | q | k | b | n | r | 8
+---+---+---+---+---+---+---+---+
| p | p |   | p | p | p | p | p | 7
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   | 6
+---+---+---+---+---+---+---+---+
|   |   | p |   |   |   |   |   | 5
+---+---+---+---+---+---+---+---+
|   |   |   |   | P |   |   |   | 4
+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   | 3
+---+---+---+---+---+---+---+---+
| P | P | P | P |   | P | P | P | 2
+---+---+---+---+---+---+---+---+
| R | N | B | Q | K | B | N | R | 1
+---+---+---+---+---+---+---+---+
a   b   c   d   e   f   g   h

Fen: rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2
Key: 4CA78BCE9C2980B0
Checkers:
> # ok board is updated now
> # lets quit
> quit
于 2021-12-12T13:38:27.653 回答
0

这个问题不是微不足道的,因为您不能使用Popen.communicate()与进程进行持续通信。communicate()实际上关闭了正在运行的进程的标准输入,因此您只能写入一次,这似乎对您不起作用。

以下示例程序显示了一种与正在运行的进程进行持续通信的方法。它包括“客户端”代码(您希望编写的代码)以及“服务器”代码的存根(在您的情况下可能是一个stockfish 程序)。

from subprocess import PIPE, Popen
from sys import argv


def server():
    """Dummy 'other' process to show how this works"""
    while True:
        line = input()
        print(f"Got {line} from stdin in server")


def client():
    """
    Starts another process and sends arbitrary commands to it. Replace
    the command arguments to Popen to start the appropriate process.
    """
    terminal = Popen(["python3", __file__, "server"], stdin=PIPE)
    try:
        command = "arbitrary string"

        while command != "end":
            command = input("Type command, type end to finish: ")
            if command == "end":
                return

            write_command_to_terminal(command, terminal)
    finally:
        terminal.kill()


def write_command_to_terminal(command, terminal):
    """
    Writes the command to the terminal process.
    """
    terminal.stdin.write((command + "\n").encode())
    terminal.stdin.flush()


if __name__ == "__main__":
    if len(argv) == 1 or argv[1] == "client":
        client()
    elif argv[1] == "server":
        server()
    else:
        raise Exception("Invalid argument")
于 2021-09-09T19:37:20.040 回答