USI プロトコルを WebSocket でブリッジする
USI プロトコルとはなにか
USI(Universal Shogi Interface)プロトコルとは、将棋GUIソフトと思考エンジンが通信をするために、Tord Romstad氏によって考案された通信プロトコルです。
GUIとエンジン間の通信のやり取りは、標準入出力を通してテキストのコマンドで行われます。
例えば世の中には ShogiHome という将棋のフロントエンドがあって、駒を動かして対局をしたりできるんだけど、こういうアプリに「将棋AIによる解析機能」を実装するようなときに USI プロトコルが利用される。 将棋フロントエンドが将棋AIを利用するときは以下のような流れになる。
将棋フロントエンドが将棋AIを起動
普通の実行可能ファイルとして起動すれば良い
将棋フロントエンドが将棋AIの標準入力にコマンドを入力し、将棋AIの標準出力に出力された結果を受け取る
たとえば、盤面の状態を特定のフォーマットで入力すると、そのときの最善手が出力されるとかそういう感じ
言うまでもないことだが、上記の方式は、将棋フロントエンドと将棋AIが同一マシン内で稼働していることを前提としている。
つまり、例えばスマホで将棋フロントエンドを動かして、サーバーで将棋AIを動かすみたいなことは想定されていない。
でも将棋AIってすごいメモリ食うし、スマホ上で将棋AIを動かすのは現実的じゃないよねという話がある。
USI プロトコルを WebSocket でブリッジする
世の中には WebSocket というものがあって、インターネット越しに全二重通信を実現することができる。 この上に USI プロトコルを乗せてしまえば、スマホでフロントエンドを動かし、サーバーでAIを動かすというような構成が可能になると考えた。
前述の通り USI プロトコルを実現するには標準入力と標準出力だけがあればいいので、socket に到達した文字列を標準入力に入れて、標準出力から出てきた文字列を socket に入れれば良いだけのことである。
ということで作ってみました:
本質的に、AI 側のコードは基本的には以下だけである。
(実際には websocket を立ち上げるコードとか、終了時の諸々の処理とかはある)
code:ruby
# 将棋AIを起動
usi_stdin, usi_stdout, usi_stderr, usi_wait_thr = Open3.popen3('./command', chdir: './shinden3')
# 将棋AIの標準出力を読んで websocket で送信する
t1 = Thread.new do
usi_stdout.each_line(chomp: true) do |line|
EM.schedule { ws.send(line) }
end
end
# websocket でメッセージが送信されてきたら将棋AIの標準入力に書き込む
ws.on :message do |event|
event.data.lines(chomp: true).each do |line|
usi_stdin.puts line
end
end
これだけでAI側の準備は完了だ。
フロントエンド(shogihome)側には以下のような修正を加えた。
つまり ChildProcess クラスと同じインターフェースを持つが、内部的には websocket での通信を行うクラスを追加し、それを ChildProcess の代わりに利用するようにしただけだ。
code:src/background/usi/engine.ts
launch(): void {
this.logger.info("sid=%d: launch: %s", this.sessionID, this.path);
this.setLaunchTimeout();
- this.process = new ChildProcess(this.path);
+ if (this.path.startsWith("ws://") || this.path.startsWith("wss://")) {
+ this.process = new UsiWebsocket(this.path);
+ } else {
+ this.process = new ChildProcess(this.path);
+ }
this.process.on("error", this.onError.bind(this));
this.process.on("close", this.onClose.bind(this));
this.process.on("receive", this.onReceive.bind(this));
code:src/background/usi/ws.ts
// 新しく定義したクラス
// 細かいところは省略している
export class UsiWebsocket {
on(event: string, listener: (...args: any[]) => void): this {
switch (event) {
case "receive":
this.ws.addEventListener("message", (event) => {
listener(event.data);
});
break;
...
}
return this;
}
send(line: string): void {
this.ws.send(line + "\n");
}
}
このように、USIプロトコルを WebSocket の上に乗せることで、ブラウザの将棋アプリにおいてもAI検討が可能であることを示した。