LoginSignup
2
2

More than 5 years have passed since last update.

コンピュータ将棋ソフトとの対局サーバーを立てよう<その4>

Last updated at Posted at 2017-03-08

前の記事 : http://qiita.com/muzudho1/items/3ff3e8dfde0ff5bed203

プロセス間通信も、Cronも、なんか動いてくれないが、
Apache、PHPだけ まともに動いてくれるので Expect を試す。

Expect で浮かむ瀬を起こせるか?

浮かむ瀬は

./apery

で起きてくれるのだった。ディレクトリを変えようかな。

/usr/games/ukamuse

とかどうだろう。と思って中を見てみたんだが 誰かが使ってて ごちゃごちゃだ。
だったら ユーザーのホーム・フォルダーでいいや……。

/home/★user/ukamuse

にするか。いや、これだとオリジナルの 浮かむ瀬 とごっちゃになる。現状の

/home/★user/shogi/

でいいや。この下に bash_shogi ディレクトリを切る。

mkdir bash_shogi

対局関連のシェル・スクリプトをここへ移そう。

mv ape1.sh ../../bash_shogi/ape1.sh
mv ape2.sh ../../bash_shogi/ape2.sh
mv ape3.sh ../../bash_shogi/ape3.sh
mv ape4.sh ../../bash_shogi/ape4.sh
mv ape5.sh ../../bash_shogi/ape5.sh
mv ape6.sh ../../bash_shogi/ape6.sh
mv ape7.sh ../../bash_shogi/ape7.sh
mv ape8.sh ../../bash_shogi/ape8.sh

他に、

mkdir csharp_shogi

フォルダを切る。

mv ape9.cs ../../bash_shogi/ape9.cs
mv ape10.cs ../../bash_shogi/ape10.cs
mv ape9.exe ../../bash_shogi/ape9.exe
mv ape10.exe ../../bash_shogi/ape10.exe

これでだいぶすっきりした。

今回新しく作るファイル

PHP で作ろうと思うので、

mkdir php_shogi

フォルダを切る。

Expect

「Expect の使用例」 (PHPマニュアル)
http://php.net/manual/ja/expect.examples-usage.php

このページを読んで少し勉強する。

インストール方法

インストール方法を探そう。

「expect」 (PECL)
http://pecl.php.net/package/expect

ここからダウンロードしたらいいんだろうか?

「expect_expectl関数 - PHPから対話型シェルに応答する」 (Developer☆STYLE)
http://blog.livedoor.jp/morningmist7/archives/1342932.html

PHP 7 のは無いのだろうか。composer でインストールできるとか。
なんで無いんだろ。

さっきのは破棄して別の方法で。

「expectコマンドが便利」 (試される大地から)
http://geektrainee.hatenablog.jp/entry/2013/11/23/191650

Ubuntu の場合。

apt-get -y install expect
Reading package lists... Done
Building dependency tree
Reading state information... Done
expect is already the newest version (5.45-7).
The following package was automatically installed and is no longer required:
  libodbc1
Use 'apt autoremove' to remove it.
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.

あれ? 既に入ってるのか?

PHPでもBashでもなく、Expectスクリプト

expect_service というフォルダーを切ろう。

cd /home/★user/shogi
mkdir expect_service
cd expect_service
nano tamesi17.expect
#!/usr/bin/expect
respawn ../ukamuse_sdt4/bin/apery
send "bench\n"
ls -l
total 4
-rw-r--r-- 1 root root 68 Mar  8 20:21 tamesi17.expect
chmod 755 tamesi17.expect
ls -l
total 4
-rwxr-xr-x 1 root root 68 Mar  8 20:21 tamesi17.expect
   ~  ~  ~
   ※ここがxになった
./tamesi17.expect
invalid command name "respawn"
    while executing
"respawn ../ukamuse_sdt4/bin/apery"
    (file "./tamesi17.expect" line 2)
man expect

こうなんじゃないか。

tamesi17.expect

#!/usr/bin/expect
spawn ../ukamuse_sdt4/bin/apery
send "bench\n"
# ./tamesi17.expect
spawn ../ukamuse_sdt4/bin/apery

終わった。
直接叩いてみよう。

# ../ukamuse_sdt4/bin/apery

0.数秒で apery が実行されるな。

bench

うん、ベンチマーク走る。

「expectコマンドの使い方」 (Linuxで自宅サーバ構築(新森からの雑記))
http://www.uetyi.com/server-const/command/entry-158.html

「EXPECT」 (linuxjm.osdn.jp)
https://linuxjm.osdn.jp/html/expect/man1/expect.1.html

/usr/bin/expect ファイルは在る。

カレント・ディレクトリの設定

書き直し

#!/usr/bin/expect

# カレント・ディレクトリを設定
cd /home/★user/shogi/expect_service

# タイムアウトさせない
set timeout -1

# 浮かむ瀬を実行
spawn ../ukamuse_sdt4/bin/apery

expect ""

send "bench\n"

# 入出力をキーボードと画面に返す
interact
./tamesi17.expect
spawn ../ukamuse_sdt4/bin/apery
bench

終わってしまった。

こうする。

#!/usr/bin/expect

# カレント・ディレクトリを設定
cd /home/★user/shogi/expect_service

# Expectをタイムアウトさせない
set timeout -1

# 浮かむ瀬を実行
spawn ../ukamuse_sdt4/bin/apery bench

# 入出力をキーボードと画面に返す
interact
./tamesi17.expect
spawn ../ukamuse_sdt4/bin/apery bench
info string start setting eval table
info string end setting eval table

これなら動く。次。

tamesi17a1.expect

#!/usr/bin/expect

# カレント・ディレクトリを設定
cd /home/★user/shogi/expect_service

# Expectをタイムアウトさせない
set timeout -1

# 浮かむ瀬を実行
spawn ../ukamuse_sdt4/bin/apery

send "bench\n"

# 入出力をキーボードと画面に返す
interact
chmod 755 tamesi17a1.expect
./tamesi17a1.expect

これもいけた。

動きは始まりはしたが、途中で終わった。

Expectに 完了と思われたんだろうか。浮かむ瀬のプロセスを途中で開放したんだろうか?
じゃあ、bestmove と表示させるまで監視させよう。

tamesi17a2.expect

#!/usr/bin/expect

# カレント・ディレクトリを設定
cd /home/★user/shogi/expect_service

# Expectをタイムアウトさせない
set timeout -1

# 浮かむ瀬を実行
spawn ../ukamuse_sdt4/bin/apery

send "bench\n"

expect "bestmove"

# 入出力をキーボードと画面に返す
interact

apery は bin フォルダーの中で実行しなければいけないのでは?

benchmark.sfen や、定跡ファイルのパスが、カレント・ディレクトリが bin の中にあることを前提としている。
書き方を変えてみる。

tamesi17a2.expect

#!/usr/bin/expect

# カレント・ディレクトリを設定
cd /home/★user/shogi/ukamuse_sdt4/bin

# Expectをタイムアウトさせない
set timeout -1

# 浮かむ瀬を実行
spawn ./apery

send "bench\n"

expect "bestmove"

# 入出力をキーボードと画面に返す
interact

ビンゴ!

ただ、最後の interact が働いていない。書き方が違うのか?

むしろ bestmove という単語は しょっちゅう出てきていて、最後の bestmove を判定するのがむずかしい。
timeout も使わないといけないのか。

TCL

Expectコマンドの関連技術はこれか。

「文法とコマンド」 (freesoftnet.co.jp)
http://www.freesoftnet.co.jp/webfiles/tclkits/doc/tclcom.html

ぜんぜんわからん。
トライ&エラーで 当たるまででたらめに 書き直し。

tamesi17a2.expect

#!/usr/bin/expect

# カレント・ディレクトリを設定
cd /home/★user/shogi/ukamuse_sdt4/bin

# Expectをタイムアウトさせない
set timeout -1

# 浮かむ瀬を実行
spawn ./apery

send "bench\n"

expect {
    # "bestmove"と帰ってきていて、5秒間出力がなければ、入出力をキーボードと画面に返す
    "bestmove"
    -timeout 5
        interact
}

こういう書き方にすると、それっぽく動いているが。

じゃあ、Expect で対局しようぜ

どうやって書くのか。少しずつやってみよう。

というか複数条件は動いていなかった。どう書くのか。

「制御構造」 (Tcl 入門編)
http://bitwalk.sitemix.jp/tcltk_intro_tcl_control.php

「&&」とかいう ふつうの答えでいいのか?

expect {
    # "bestmove"と帰ってきていて、5秒間出力がなければ、入出力をキーボードと画面に返す
    "bestmove" && -timeout 10
        interact
}

いいようではあるものの、「-timeout 10」というのがトータル・タイムなのがつらい。出力中でも10秒で切れてしまう。

じゃあ、こうか。

    # "bestmove"と帰ってきたあと、5秒間出力がなければ、入出力をキーボードと画面に返す
    "bestmove"
    {
        -timeout 5
            interact
    }

突然切れた。

*3c 3d2c+ 3c2c G*3d S*2b 4b3b N*1g 1i1g 3i1g+ 3b2b 2c2b G*2c
info nodes 1387645 time 9516
bestmove S*2c ponder 2d2c+invalid command name "-timeout"
    while executing
"-timeout 5"
    invoked from within
"expect {
    # "bestmove"と帰ってきていて、5秒間出力がなければ、入出力をキーボードと画面に返す
    "bestmove"
    {
..."
    (file "./tamesi17a2.expect" line 14)

"-timeout" がコマンドと認識されてしまって、コマンドが無いというエラーになったのか。
じゃあこれでどうか。

expect
{
    # "bestmove"と帰ってきたあと、5秒間出力がなければ、入出力をキーボードと画面に返す
    "bestmove"
    {
        expect {
            -timeout 5
                interact
        }
    }
}

おや? でけたぜ。

こんなん、トライ&エラーだよな。

非効率的な方法で進む。

でも、interact 効いてないな。

でも進む。

tamesi17a3.expect

#!/usr/bin/expect

# カレント・ディレクトリを設定
cd /home/★user/shogi/ukamuse_sdt4/bin

# Expectをタイムアウトさせない
set timeout -1

# 浮かむ瀬を実行
spawn ./apery

send "usi\n"

expect {
    "usiok" {
        exit
    }
}
  • interact じゃなくて、exit を使うんじゃないか。
  • ブレス『{』は行末に置いておかないとダメだろうか?

選択構造じゃなくて、シーケンシャルなんじゃないか?

書き直し。

tamesi17a4.expect

#!/usr/bin/expect

# カレント・ディレクトリを設定
cd /home/★user/shogi/ukamuse_sdt4/bin
#  /home/★user/shogi/expect_service ではない。

# Expectをタイムアウトさせない
set timeout -1

# 浮かむ瀬を実行
spawn ./apery

send "usi\n"

expect "usiok"

send "isready\n"

expect "readyok"

exit

これで readyok まで返ってくる。

もっと進めよう

tamesi17a5.expect

#!/usr/bin/expect

# カレント・ディレクトリを設定
cd /home/★user/shogi/ukamuse_sdt4/bin
#  /home/★user/shogi/expect_service ではない。

# Expectをタイムアウトさせない
set timeout -1

# 浮かむ瀬を実行
spawn ./apery

send "usi\n"

expect "usiok"

# 0.1秒は考えろ
send "setoption name Minimum_Thinking_Time value 100\n"

send "isready\n"

expect "readyok"

send "newgame\n"

send "position sfen lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL w - 1 moves\n"

send "go\n"

expect "bestmove"

exit
chmod 755 tamesi17a5.expect
./tamesi17a5.expect
spawn ./apery
usi
id name ukamuse_SDT4
id author Hiraoka Takuya

option name Best_Book_Move type check default false
option name Book_File type string default book/20150503/book.bin
option name Byoyomi_Margin type spin default 500 min 0 max 2147483647
option name Clear_Hash type button
option name Draw_Ply type spin default 256 min 1 max 2147483647
option name Engine_Name type string default ukamuse_SDT4
option name Max_Book_Ply type spin default 32767 min 0 max 32767
option name Max_Random_Score_Diff type spin default 0 min 0 max 32600
option name Max_Random_Score_Diff_Ply type spin default 32767 min 0 max 32767
option name Min_Book_Ply type spin default 32767 min 0 max 32767
option name Min_Book_Score type spin default -180 min -32601 max 32601
option name Minimum_Thinking_Time type spin default 20 min 0 max 2147483647
option name Move_Overhead type spin default 30 min 0 max 5000
option name MultiPV type spin default 1 min 1 max 594
option name OwnBook type check default true
option name Slow_Mover type spin default 89 min 1 max 1000
option name Slow_Mover_10 type spin default 10 min 1 max 1000
option name Slow_Mover_16 type spin default 20 min 1 max 1000
option name Slow_Mover_20 type spin default 40 min 1 max 1000
option name Threads type spin default 2 min 1 max 256
option name Time_Margin type spin default 4500 min 0 max 2147483647
option name USI_Hash type spin default 256 min 1 max 1048576
option name USI_Ponder type check default true
usiok
setoption name Minimum_Thinking_Time value 100
isready
readyok
newgame
position sfen lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL w - 1 moves
go
unknown command: newgame
info string optimum_time = 100
info string maximum_time = 100
info string book_ply 32767
info depth 1 seldepth 1 multipv 1 score cp 63 nodes 66 nps 904 time 73 pv 8c8d
info depth 2 seldepth 2 multipv 1 score cp 82 nodes 154 nps 1480 time 104 pv 8c8d 2g2f
info depth 3 seldepth 3 multipv 1 score cp 46 nodes 264 nps 1859 time 142 pv 3a3b 2g2f 3c3d
info depth 4 seldepth 4 multipv 1 score cp -28 nodes 735 nps 2254 time 326 pv 3a3b 2g2f 3c3d 2f2e
info depth 5 seldepth 7 multipv 1 score cp 68 nodes 736 nps 2243 time 328 pv 3c3d 2g2f 2b3c 2f2e 3a3b
bestmove 3c3d ponder 2g2f

はい、おっけ。

これを PHP から叩けるようにしたい。

PHP から 「#!/usr/bin/expect」で始まるスクリプトは叩けるのだろうか?
例えば、

/usr/bin/expect ./tamesi17a5.expect

とかコマンドを打ったら実行されるのだろうか?
コマンドラインに打ち込んだら実行された。

「exec」 (PHPマニュアル)
http://php.net/manual/ja/function.exec.php
じゃあ、PHP の exec( ) で外部コマンドを叩いたら動くのだろうか?

単純に考えると

# cd /home/★user/shogi/php_service
# nano tamesi18.php

php_service/tamesi18.php

<?php
exec    ( '/usr/bin/expect ../expect_service/tamesi17a5.expect', $out, $ret );
print_r ( $out );
var_dump( $ret );
# ls -l
略
-rw-r--r-- 1 root root  120 Mar  9 00:08 tamesi18.php
# chmod 755 tamesi18.php
# php tamesi18.php

ここで数秒止まる。

Array
(
    [0] => spawn ./apery
    [1] => usi
    [2] => id name ukamuse_SDT4
    [3] => id author Hiraoka Takuya
    [4] =>
    [5] => option name Best_Book_Move type check default false
    [6] => option name Book_File type string default book/20150503/book.bin
    [7] => option name Byoyomi_Margin type spin default 500 min 0 max 2147483647
    [8] => option name Clear_Hash type button
    [9] => option name Draw_Ply type spin default 256 min 1 max 2147483647
    [10] => option name Engine_Name type string default ukamuse_SDT4
    [11] => option name Max_Book_Ply type spin default 32767 min 0 max 32767
    [12] => option name Max_Random_Score_Diff type spin default 0 min 0 max 32600
    [13] => option name Max_Random_Score_Diff_Ply type spin default 32767 min 0 max 32767
    [14] => option name Min_Book_Ply type spin default 32767 min 0 max 32767
    [15] => option name Min_Book_Score type spin default -180 min -32601 max 32601
    [16] => option name Minimum_Thinking_Time type spin default 20 min 0 max 2147483647
    [17] => option name Move_Overhead type spin default 30 min 0 max 5000
    [18] => option name MultiPV type spin default 1 min 1 max 594
    [19] => option name OwnBook type check default true
    [20] => option name Slow_Mover type spin default 89 min 1 max 1000
    [21] => option name Slow_Mover_10 type spin default 10 min 1 max 1000
    [22] => option name Slow_Mover_16 type spin default 20 min 1 max 1000
    [23] => option name Slow_Mover_20 type spin default 40 min 1 max 1000
    [24] => option name Threads type spin default 2 min 1 max 256
    [25] => option name Time_Margin type spin default 4500 min 0 max 2147483647
    [26] => option name USI_Hash type spin default 256 min 1 max 1048576
    [27] => option name USI_Ponder type check default true
    [28] => usioksetoption name Minimum_Thinking_Time value 100
    [29] => isready
    [30] =>
    [31] => readyok
    [32] => newgame
    [33] => position sfen lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL w - 1 moves
    [34] => go
    [35] => unknown command: newgame
    [36] => info string optimum_time = 100
    [37] => info string maximum_time = 100
    [38] => info string book_ply 32767
    [39] => info depth 1 seldepth 1 multipv 1 score cp 63 nodes 66 nps 1736 time 38 pv 8c8d
    [40] => info depth 2 seldepth 2 multipv 1 score cp 82 nodes 133 nps 2418 time 55 pv 8c8d 2g2f
    [41] => info depth 3 seldepth 3 multipv 1 score cp 46 nodes 297 nps 4125 time 72 pv 3a3b 2g2f 3c3d
    [42] => info depth 4 seldepth 4 multipv 1 score cp -28 nodes 748 nps 3777 time 198 pv 3a3b 2g2f 3c3d 2f2e
    [43] => info depth 5 seldepth 5 multipv 1 score cp 135 nodes 1434 nps 3051 time 470 pv 3c3d 6i7h 2b3c 2g2f 8c8d
    [44] => bestmove 3c3d ponder 6i7h
)
int(0)

数秒止まったところが気になるが、$ret 変数の最終行に「bestmove 3c3d ponder 6i7h」が入っているようだ。
これだけ返す方法を考えたい。

tamesi19.php

<?php
exec    ( '/usr/bin/expect ../expect_service/tamesi17a5.expect', $out );
echo $out[ count( $out ) - 1 ] . "\n";
php tamesi19.php
bestmove 8c8d ponder 2g2f

でけた。0.5秒どころではなく、数秒待つけど。

じゃあ、これを サーバーの外部からアクセスできるように、外側に向けてみよう。

単純な方法は、

cp /home/★user/shogi/php_service/tamesi19.php /var/www/html/tamesi19.php

で、tamesi19.php の中のファイルパスは 絶対パスにしてしまおう。

http://★.★.★.★/tamesi19.php へアクセス。

bestmove 3a3b ponder 2g2f

3秒ぐらいかかってるかな。

どう早くするか。

その前に tamesi14.php と tamesi19.php を合体させよう

あれ?

Expectスクリプトはどうやって引数を受け取るのか?

「EXPECT」 (linuxjm.osdn.jp)
https://linuxjm.osdn.jp/html/expect/man1/expect.1.html

「$argv」配列にでも入ってるんだろうか?

tamesi21.expect 抜粋

send "position ${argv(0)}\n"

こう書くと

can't read "argv(0)": variable isn't array
    while executing
"send "position ${argv(0)}\n""
    (file "/home/★user/shogi/expect_service/tamesi21.expect" line 27)

と帰ってくる。argv は配列じゃないのか。

send "position ${argv}\n"

こう書くと

position /home/★user/shogi/expect_service/tamesi21.expect

こんな出力になってしまう。

「--」で区切ると、argv変数の中身を渡せるようだが。

# /usr/bin/expect /home/★user/shogi/expect_service/tamesi21.expect --sfen lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL w - 1 moves

違うのか。

#!/usr/bin/expect --

こっちか。
で、こう書いて。

send "position ${argv}\n"

こう。

# /usr/bin/expect /home/★user/shogi/expect_service/tamesi21.expect sfen lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL w - 1 moves

いけた。つまり

tamesi21.expect

#!/usr/bin/expect --
# 末尾の -- は、「${argv}」変数にコマンドライン引数を渡す指定

# カレント・ディレクトリを設定
cd /home/★user/shogi/ukamuse_sdt4/bin
#  /home/★user/shogi/expect_service ではない。

# Expectをタイムアウトさせない
set timeout -1

# 浮かむ瀬を実行
spawn ./apery

send "usi\n"

expect "usiok"

# 0.1秒は考えろ
# send "setoption name Minimum_Thinking_Time value 100\n"

send "isready\n"

expect "readyok"

send "newgame\n"

# 「sfen lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL w - 1 moves」
send "position ${argv}\n"

send "go\n"

expect "bestmove"

exit

こう書く。

じゃあ PHP側はこう書く。

<?php
require_once __DIR__ . '/vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;

// GETクエリ文字列を取得
$QStr = urldecode($_SERVER['QUERY_STRING']);
if( "" === $QStr )
{
    $QStr = "sfen lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL w - 1 moves";
}

// 別のクエリーも送れることを説明
echo 'このURLと同じだぜ☆m9(^~^)!<br />';
echo '<a href="http://★.★.★.★/tamesi21.php?' . urldecode( $QStr ) . '">http://★.★.★.★/tamesi21.php?' . urldecode( $QStr ) . '</a><br />';


echo '浮かむ瀬さん(^q^) よろしくたのんますだぜ☆(^▽^)v<br />' . "\n";

// /usr/bin/expect /home/★/shogi/expect_service/tamesi21.expect sfen lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL w - 1 moves
exec    ( '/usr/bin/expect /home/★/shogi/expect_service/tamesi21.expect ' . $QStr , $out );
echo '<br />(´・_・`) URLで別のリソースが出てくるのはちゃうんとちゃうかな……。<br />' . $out[ count( $out ) - 1 ] . '<br />' . "\n";

これで URL でアクセスすると、浮かむ瀬は ベストムーブをブラウザ画面に返してくれる。

浮かむ瀬は 0.1秒ぐらいで反応するんだが、画面に出るまで5秒ぐらいかかって遅い。高速化を検討しよう。

高速化を検討

ゴールは 1秒未満。

よく見ると

require_once __DIR__ . '/vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;

とかいう、RabbitMQ 用のコードが残ってるじゃないか。消そう。

高速化ということで、もうちょい短くしてみよう。

tamesi22.php

<?php exec('/usr/bin/expect /home/★user/shogi/expect_service/tamesi21.expect '.urldecode($_SERVER['QUERY_STRING']),$o);echo $o[count($o)-1];

ここで、 .expect を別途呼んでいるので遅いのは分かる。
PHP で expect を直書きするための環境設定が できればいいんだが。

計測しないと

なんつーの、何秒高速化できたのか 数字を取るプロファイラが欲しいんだが、とりあえず 体感時間でいいか……(/_\)

Expectスクリプトの expect命令は -exフラグを付けると完全一致になるらしい。
こういうの付けると 高速化につながると思うんだが。

手短に書くと こうなるだろうか?

tamesi22.expect

#!/usr/bin/expect --
cd /home/★user/shogi/ukamuse_sdt4/bin
set timeout -1
spawn ./apery
send "usi\n"
expect -ex "usiok"
send "isready\n"
expect -ex "readyok"
send "newgame\n"
send "position ${argv}\n"
send "go\n"
expect "bestmove"
exit

手短にはなったが、別に速くはならない。

あと いじるところと言えば、PHP で直接 Expect をやる、ということなんだが。

PHP で直接 Expect をやるには

「PHP Expect not working properly」 (stack overflow)
http://stackoverflow.com/questions/17894359/php-expect-not-working-properly

phpinfo() を見てみるか。

expect 入ってないわ。

「expectを利用したssh接続時のパスワード入力の自動化」 (Qiita)
http://qiita.com/digitalpeak/items/cb51e0ca808de53800bb

sudo yum -y install expect
There are no enabled repos.
 Run "yum repolist all" to see the repos you have.
 You can enable repos with yum-config-manager --enable <repo>

repo って何なのか。

Ubuntu に yum なんか入ってるのか? apt-get じゃないのか?

root@tk2-217-18401:/var/www/html# sudo apt-get -y install expect
Reading package lists... Done
Building dependency tree
Reading state information... Done
expect is already the newest version (5.45-7).
The following package was automatically installed and is no longer required:
  libodbc1
Use 'sudo apt autoremove' to remove it.
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.

まあ、使ってたからな。 5.45-7 バージョンが入ってるみたいだ。
phpinfo(); では出てこないけど。

コマンド数を少なくする

send "newgame\n"
send "position ${argv}\n"
send "go\n"

ここは、

send "newgame\nposition ${argv}\ngo\n"

こう書ける。

PHPのExpect

「XXXV. Expect 関数」 (PHPマニュアル)
http://manual.xwd.jp/ref.expect.html

多分、こんな感じになると思うんだが。

tamesi16.php

<?php
ini_set("expect.timeout", "-1");
ini_set("expect.loguser", "Off");
$stream = fopen("/home/★user/shogi/ukamuse_sdt4/bin/apery", "r");
define("USI_OK"   ,"usiok"   );
define("READYOK"  ,"readyok" );
define("BESTMOVE" ,"bestmove");
$cases = array (
    array ( "usiok"    ,USI_OK   ),
    array ( "readyok"  ,READYOK  ),
    array ( "bestmove" ,BESTMOVE )
);

fwrite ($stream, "usi\n");
switch (expect_expectl ($stream, $cases)) {
    case USI_OK:
        fwrite ($stream, "isready\n");
        break;
    default:
        die ("Expect実行中にエラーが発生しました!\n");
}
switch (expect_expectl ($stream, $cases)) {
    case READYOK:
        fwrite ($stream, "newgame\nposition " . urldecode($_SERVER['QUERY_STRING']) . "\ngo\n");
        break;
    default:
        die ("Expect実行中にエラーが発生しました!\n");
}
switch (expect_expectl ($stream, $cases)) {
    case BESTMOVE:
        break;
    default:
        die ("Expect実行中にエラーが発生しました!\n");
}

// 出力結果を全部出す
while ($line = fgets($stream)) {
      print $line;
}
fclose ($stream);

http://★.★.★.★/tamesi16.php にアクセスすると、

Fatal error: Uncaught Error: Call to undefined function expect_expectl() in /var/www/html/tamesi16.php:15 Stack trace: #0 {main} thrown in /var/www/html/tamesi16.php on line 15

となる。PHPのExpectライブラリの最新版は、PHP5.いくつだろうか?

Expect の大元は

「Expect」 (NIST)
https://www.nist.gov/services-resources/software/expect

ここか。Source Forge を見てみよう。2013年が最終更新日か。

「Expect」 (source forge)
https://sourceforge.net/projects/expect/

PHP7が出たのが 2015 なので、対応してないな。

必要なもの

libexpect

「Requirements」 (PHPマニュアル)
http://php.net/manual/en/expect.requirements.php

バージョン 5.43.0 以上の libexpect が必要とのこと。

5.45 なら、さっきのページで配布してるやつだ。

「Expect」 (source forge)
https://sourceforge.net/projects/expect/

中身のソースは バリバリのC言語だが……。

「Installation」 (PHPマニュアル)
http://php.net/manual/en/expect.installation.php

PHP は入ってないらしい。

「Installation of PECL extensions」 (PHPマニュアル)
http://php.net/manual/en/install.pecl.php

5年前のマニュアルが最新か。

composer で expect を使えないのか?

copmoser で PEAR をインストールする方法は書いてあるが、

「05-repositories」 (getcomposer.org)
https://getcomposer.org/doc/05-repositories.md#pear

どうも Expectライブラリは PECL拡張を使っていて、PECL拡張を使うために PEARを使え、ということらしい。
で、Expectライブラリは2013年が最終更新日なので、じゃあ Composer に乗り換えてないのか?

「Expect」 (Packagist)
https://packagist.org/search/?q=expect

どこかにないか。

無いようだ。

ところでExpectが「what(): std::bad_alloc」を返してきた。

エラー対応はどう書くか。

tamesi22a1.expect

#!/usr/bin/expect --
cd /home/★user/shogi/ukamuse_sdt4/bin
set timeout -1
spawn ./apery
send "usi\n"
expect -ex "usiok"
send "isready\n"
expect -ex "readyok"
send "newgame\n"
send "position ${argv}\n"
send "go\n"
expect -re "bestmove.*" {
  exit
} ".*bad_alloc.*" {
  spawn echo "error 3\n"
}

expect の「-re」オプションは正規表現らしい。本当に正規表現だろうか?
こんな書き方でいいのだろうか?

動いてはいる。エラーが出たときの流れもテストしないといけないが……。

  spawn echo "success 3\n"

の出力結果が

spawn echo success 3

なんだが、理解しがたい。

あと、「-re」オプションは正規表現じゃなくて、ワイルドカードが使える、ぐらいの意味のようだ。
だから「bestmove.」ではなく「bestmove」と書く。

echo の代わりに 「send_user」

send_user "fish 4\n"

と書いたところは

fish 4

と出てくる。どうもコンソールに出てくるようだ。じゃあ、標準出力に投げるには?

標準出力に投げなくても、Webページに表示してくれたのでいいか。

まとめると こうなる。

tamesi22a1.expect

#!/usr/bin/expect --
cd /home/★user/shogi/ukamuse_sdt4/bin
set timeout -1
spawn ./apery
send "usi\n"
expect -ex "usiok"
send "isready\n"
expect -ex "readyok"
send "usinewgame\nposition ${argv}\ngo\n"
expect -re "bestmove*" {
  exit
} "checkmate nomate *" {
  send "quit\n"
  send_user "successful 0.0\n"
} "*info time 1*" {
  send "quit\n"
  send_user "error 3.1\n"
} "checkmate timeout *" {
  send "quit\n"
  send_user "error 3.2\n"
} "*bad_alloc*" {
  send "quit\n"
  send_user "error 3.3\n"
} "*Out of memory*" {
  send "quit\n"
  send_user "error 3.4\n"
} "*recursively*" {
  send_user "error 3.5\n"
} "checkmate *" {
  send "quit\n"
  send_user "successful 1.0\n"
}

エラーハンドリングをがんばって書いたんだが、

what(): std::bad_alloc

とか画面に出力されて ショックを受けている。

レスポンス時間を測ろう

  • (1)ページにアクセスして、すぐレスポンスする時間
  • (2)ページにアクセスして、浮かむ瀬を起動する振りをして起動せず、うそレスポンスを返す時間
  • (3)ページにアクセスして、浮かむ瀬を起動し、指し手を返す時間

の3つも測れば贅沢だろう。方法としては全部 PHP にやらせればいいのではないか。
PHP にストップウォッチはあっただろうか?

PHPのストップウォッチ

「PHP – STARTTIME() STOPTIME($TIME) – ストップウォッチ」 (aoringo works)
http://ao-works.net/blog/808

これで十分だろう。

時間測定は tamesi23 さんにしよう。

tamesi23a1.php

<?php
$time=microtime(true);
echo '<br />' . (microtime(true) - $time);

tamesi23a2.php

<?php
$time=microtime(true);
exec('/usr/bin/expect /home/★user/shogi/expect_service/tamesi23a2.expect '.urldecode($_SERVER['QUERY_STRING']),$o);echo $o[count($o)-1];
echo '<br />' . (microtime(true) - $time);

tamesi23a3.php

<?php
$time=microtime(true);
exec('/usr/bin/expect /home/★user/shogi/expect_service/tamesi23a3.expect '.urldecode($_SERVER['QUERY_STRING']),$o);echo $o[count($o)-1];
echo '<br />' . (microtime(true) - $time);

tamesi23a2.expect

#!/usr/bin/expect --
cd /home/★user/shogi/ukamuse_sdt4/bin
set timeout -1
send_user "bestmove XXXX ponder XXXX"
exit

ダミー文字列を返している。

tamesi23a3.expect

#!/usr/bin/expect --
cd /home/★user/shogi/ukamuse_sdt4/bin
set timeout -1
spawn ./apery
send "usi\n"
expect -ex "usiok"
send "isready\n"
expect -ex "readyok"
send "usinewgame\nposition ${argv}\ngo\n"
expect -re "bestmove*" {
  exit
} "checkmate nomate *" {
  send "quit\n"
  send_user "successful 0.0\n"
} "*info time 1*" {
  send "quit\n"
  send_user "error 3.1\n"
} "checkmate timeout *" {
  send "quit\n"
  send_user "error 3.2\n"
} "*bad_alloc*" {
  send "quit\n"
  send_user "error 3.3\n"
} "*Out of memory*" {
  send "quit\n"
  send_user "error 3.4\n"
} "*recursively*" {
  send_user "error 3.5\n"
} "checkmate *" {
  send "quit\n"
  send_user "successful 1.0\n"
}

テストは このソースで行うとする。

10回ずつ計測すればいいだろう。

アクセスするときに使うURLはこれ。

http://★.★.★.★/tamesi23a★.php?sfen lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1 moves

本将棋の平手初期局面で、向かって下側(手前側)からスタート。

キャッシュ・ページを見ているかもしれない。どうしたものか。 あっ、そうだ!

テスト回数に応じて

http://~略~ b - 1 moves
                 ~
                 ※この数字

を増やしていくものとする。何手目を表す数字だが、多分、誰も使ってないだろ。

計測結果

a1(No.1)→a2(No.1)→a3(No.1)→a1(No.2) の順で計測。

a1

No Time
1 2.0027160644531E-5
2 1.0013580322266E-5
3 1.1205673217773E-5
4 1.3828277587891E-5
5 1.0013580322266E-5
6 3.2901763916016E-5
7 4.3153762817383E-5
8 1.9073486328125E-5
9 1.2874603271484E-5
10 1.2874603271484E-5

a2

No Time
1 bestmove XXXX ponder XXXX 0.021795034408569
2 bestmove XXXX ponder XXXX 0.022739887237549
3 bestmove XXXX ponder XXXX 0.022423982620239
4 bestmove XXXX ponder XXXX 0.019282102584839
5 bestmove XXXX ponder XXXX 0.029183149337769
6 bestmove XXXX ponder XXXX 0.037684917449951
7 bestmove XXXX ponder XXXX 0.033998966217041
8 bestmove XXXX ponder XXXX 0.030500888824463
9 bestmove XXXX ponder XXXX 0.02174711227417
10 bestmove XXXX ponder XXXX 0.017857074737549

a3

No Time
1 bestmove 2g2f ponder 1c1d 3.8728079795837
2 bestmove 2g2f ponder 3c3d 3.7108631134033
3 bestmove 2g2f ponder 1c1d 3.6325130462646
4 bestmove 2g2f ponder 8c8d 3.0715250968933
5 bestmove 2g2f 3.605082988739
6 bestmove 2g2f ponder 8c8d 3.1143889427185
7 bestmove 2g2f ponder 3c3d 3.1693458557129
8 bestmove 2g2f ponder 8c8d 3.0223209857941
9 bestmove 2g2f ponder 3c3d 3.7790269851685
10 bestmove 2g2f ponder 8c8d 4.1685428619385

microtime の精度はマイクロ秒のようだ。

「microtime」 (PHPマニュアル)
http://php.net/manual/ja/function.microtime.php

見やすくしてみよう。

a1

No Time
1 0.000020
2 0.000010
3 0.000011
4 0.000013
5 0.000010
6 0.000032
7 0.000043
8 0.000019
9 0.000012
10 0.000012

a2

No Time
1 bestmove XXXX ponder XXXX 0.021795
2 bestmove XXXX ponder XXXX 0.022739
3 bestmove XXXX ponder XXXX 0.022423
4 bestmove XXXX ponder XXXX 0.019282
5 bestmove XXXX ponder XXXX 0.029183
6 bestmove XXXX ponder XXXX 0.037684
7 bestmove XXXX ponder XXXX 0.033998
8 bestmove XXXX ponder XXXX 0.030500
9 bestmove XXXX ponder XXXX 0.021747
10 bestmove XXXX ponder XXXX 0.017857

a3

No Time
1 bestmove 2g2f ponder 1c1d 3.872807
2 bestmove 2g2f ponder 3c3d 3.710863
3 bestmove 2g2f ponder 1c1d 3.632513
4 bestmove 2g2f ponder 8c8d 3.071525
5 bestmove 2g2f 3.605082
6 bestmove 2g2f ponder 8c8d 3.114388
7 bestmove 2g2f ponder 3c3d 3.169345
8 bestmove 2g2f ponder 8c8d 3.022320
9 bestmove 2g2f ponder 3c3d 3.779026
10 bestmove 2g2f ponder 8c8d 4.168542

なんか、ぱっと見、

  • a1 は 0.00001 秒、
  • a2 は 0.02 秒、
  • a3 は 3.5 秒

ぐらいだな。桁が違う。

浮かむ瀬さんの isready → readyok 時間を調査するか。
ビットボードの初期化とか、評価値ファイルの読み込みとか、やってるんだろうけど。

とりあえず 中央値を出しておこう。スプレッドシート開くのも面倒なので 手作業で。

a1

No Time
2 0.000010
5 0.000010
3 0.000011
9 0.000012
10 0.000012 <----
4 0.000013 <----
8 0.000019
1 0.000020
6 0.000032
7 0.000043

はいっ! およそ 0.0000125 秒

a2

No Time
10 bestmove XXXX ponder XXXX 0.017857
4 bestmove XXXX ponder XXXX 0.019282
9 bestmove XXXX ponder XXXX 0.021747
1 bestmove XXXX ponder XXXX 0.021795
3 bestmove XXXX ponder XXXX 0.022423 <----
2 bestmove XXXX ponder XXXX 0.022739 <----
5 bestmove XXXX ponder XXXX 0.029183
8 bestmove XXXX ponder XXXX 0.030500
7 bestmove XXXX ponder XXXX 0.033998
6 bestmove XXXX ponder XXXX 0.037684

はいっ! およそ 0.022581 秒。

a3

No Time
8 bestmove 2g2f ponder 8c8d 3.022320
4 bestmove 2g2f ponder 8c8d 3.071525
6 bestmove 2g2f ponder 8c8d 3.114388
7 bestmove 2g2f ponder 3c3d 3.169345
5 bestmove 2g2f 3.605082 <----
3 bestmove 2g2f ponder 1c1d 3.632513 <----
2 bestmove 2g2f ponder 3c3d 3.710863
9 bestmove 2g2f ponder 3c3d 3.779026
1 bestmove 2g2f ponder 1c1d 3.872807
10 bestmove 2g2f ponder 8c8d 4.168542

はいっ! およそ 3.6187975 秒。

次は話ががらりと変わるので、次の記事に移る

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2