nmi.jp Twitter → @tkihira
生WebGL入門:初音ミクの美麗3Dモデルを表示する(後編) embona - ブラウザで動くBonanzaを作ってみた(その2)

embona - ブラウザで動くBonanzaを作ってみた(その1)


2015-01-19
Takuo Kihira

今回、ちょっとした縁によりBonanzaをブラウザで動かしてみました。Bonanza 6.0のソースコードをほぼそのまま利用して、EmscriptenでJavaScriptに変換しています。

技術的なことに興味はない、すぐに遊んでみたい!という方はこちらからどうぞ。初回に45Mほどダウンロードが発生します!ご注意ください。
http://tkihira.github.io/embona/index.html

  • 上のURLに行くとCPU vs CPUの戦いが始まります。自分で戦いたい方は新規対局で設定してください
  • UI周りは相当手を抜いて実装したために多数バグがあるかと思います。ご容赦ください
  • ブラウザが固まったりしませんが、CPU思考中に手を入力したり新規対局するのには大変反応が重い(というか思考が終わるまで反応しない)のにご注意ください
  • データダウンロードで43Mbyte、さらに内部で展開してハッシュテーブルなどを用意し、最終的に数百Mのメモリを消費します。モバイル端末ではほぼ間違いなく動きません
  • コンソールログを開くと、Bonanzaの出力をほぼそのまま楽しむことも出来ます

Emscriptenは今後重要になる技術だと踏んでいますが、残念ながら日本語の資料があまりないようです。今回の移植を利用して、初めてEmscriptenを触る人向けに記事を書いてみます。

Emscriptenとは?

Emscriptenとは、誤解を恐れずにざっくり言うと「ソースコードからソースコードへのコンバータ」です。LLVMのバイトコードを入力とし、JavaScriptを出力します。LLVMのバイトコードは主にC/C++などの言語から出力されるので、C言語などで書かれたプログラムを機械的にJavaScriptに移植することが可能になります。

しかし、JavaScriptでC言語の各種ライブラリなどの機能をすべてサポート出来るわけではありません。例えばsinやcosなどの数値演算はJavaScriptでも利用可能ですが、スレッド処理やソケット通信などを現時点のJavaScriptでそのまま実現するのは難しいでしょう。実際のところEmscriptenは、OpenGLベースのゲームの移植(JavaScriptではWebGLで対応)や、純粋な数値演算のみで解決する物理エンジンの移植などでよく使われている印象があります。例えば、最近話題になった「MSDOSのゲームがブラウザで動く」の記事で動いているゲームは、Emscriptenを用いてJavaScriptに移植されたエミュレータのDOSBOXの上で動いております。

今回移植するBonanzaも、基本的には数値演算のみで動く将棋エンジンです。なのでEmscriptenで移植するのは比較的楽な方だと考えて選びましたが、実際はEmscripten独特の文化を理解するのに時間がかかったのですが、今回この記事ではそれらのポイントをご紹介しようと思います。

Emscriptenのインストール

ここらへんはEmscripten公式を見てやっていただいた方がいいと思いますが、一応自分が自分のMacでやった手順を紹介します。前提条件として、git、python2、nodeなどEmscriptenが要求しているツール群と、Xcodeのコマンドラインツールは入っているものとします。

  1. http://kripken.github.io/emscripten-site/docs/getting_started/downloads.html から emsdk-portable.tar.gz をダウンロードし、展開しておく
  2. 手順にしたがって最新版のEmscriptenを取得、パスを通す
$ ./emsdk update
$ ./emsdk install latest
$ ./emsdk activate latest
$ source ./emsdk_env.sh

これで ./emcc が使えるようになります。

今のところはこれでインストールされると思いますが、公式で確認しつつされることをオススメします。Windows環境でもほとんど同じようにインストール出来るでしょう。

Bonanzaをビルドしてみる

とりあえずBonanzaを普通にビルドしてみましょう。Bonanzaは公式サイトより入手します。

Bonanza - The Computer Shogi Program: http://www.geocities.jp/bonanza_shogi/

最新版のBonanza 6.0(bonanza_v6.0.zip) をダウンロードして利用します。展開するとreadme.txt、srcフォルダ、winbinフォルダが出てきます。srcの下にはclient、server、sikou_dllがありますね。今回利用するのはエンジン部分なので、clientのビルドをしてみましょう。

$ cd src/client/
$ make
try targets as:

  gcc
  gcc-pg
  gcc-pgo
  icc
  icc-pgo
  icc-ampl
$ make gcc
(略)
$ ./bonanza

ERROR: Can't open a file, fv.bin

とりあえずビルドは出来ましたが、起動にはfv.binを必要とするようです。winbinの下にあるので、持ってきちゃいましょう。

$ cp ../../winbin/fv.bin .
$ ./bonanza

ERROR: Can't open a file, log/n000.log

今度はlogファイルが作成出来ないようなので log ディレクトリを作ってあげます。

$ mkdir log
$ ./bonanza
Bonanza 6.0
Trans. Table Entries = 3072K (48MB)
cleanning the transposition table ... done ( 0.02s)
DFPN Table Entries = 1024k (24MB)

WARNING: Can't open a file, book.bin

rand seed = f1f096f2
Black 1>

book.bin (おそらく定跡ファイル)がないと警告されましたが、とりあえず起動しました。ここでのコマンドについてはclientディレクトリの下にある readme.txt に解説があります。とりあえずコンピュータ対コンピュータで3手ほど指させてみましょう。

Black 1> move 3
- time ctrl: 10800 -- 10800
nsuc_check: [0]=0 [1]=0
- root move generation ... done (30 moves,  0.00s)
- drive an iterative deepening search starting from depth 1
  5   0.02    1.83  1.+7776FU  2.-3334FU  3.+5968OU  4.-2288UM  5.+7988GI
                    6.-5142OU
  6   0.03   -0.09  1.+7776FU  2.-5142OU  3.+5968OU  4.-7162GI  5.+3948GI
                    6.-8384FU
      0.04   -0.07  1.+5756FU  2.-3334FU  3.+3948GI  4.-8384FU  5.+4857GI
                    6.-8485FU
  7   0.06    0.12  1.+5756FU  2.-8384FU  3.+7776FU  4.-3334FU  5.+3948GI
                    6.-2288UM  7.+7988GI  8.-5142OU
  8   0.07   -0.74  1.+5756FU  2.-8384FU  3.+3948GI  4.-3334FU  5.+4857GI
      0.10   -0.60  1.+6978KI  2.-3334FU  3.+7776FU  4.-4344FU  5.+3948GI
                    6.-3132GI  7.+5969OU  8.-3243GI
      0.17    0.01  1.+7776FU  2.-8384FU  3.+6978KI  4.-8485FU  5.+3948GI
                    6.-8586FU  7.+8786FU  8.-8286HI  9.+5969OU 10.-8676HI
  9   0.28    1.32  1.+7776FU  2.-3334FU  3.+2726FU  4.-8384FU  5.+2625FU
                    6.-8485FU  7.+2524FU  8.-2324FU  9.+2824HI 10.-4132KI
 10   0.42    1.40  1.+7776FU  2.-8384FU  3.+6978KI  4.-8485FU  5.+2726FU
                    6.-8586FU  7.+8786FU  8.-8286HI  9.+2625FU 10.-8676HI
                   11.+8877KA
 11   0.70    1.77  1.+7776FU  2.-8384FU  3.+7968GI  4.-6364FU  5.+3948GI
                    6.-7162GI  7.+5756FU  8.-5142OU
 12   1.33    1.08  1.+7776FU  2.-8384FU  3.+2726FU  4.-3334FU  5.+2625FU
                    6.-8485FU  7.+2524FU  8.-2324FU  9.+2824HI 10.-4132KI
                   11.+6978KI 12.-8586FU 13.+8786FU 14.-8286HI 15.+2434HI
                   16.-2288UM 17.+7988GI 18.-8676HI
 13   4.76    1.00  1.+7776FU  2.-3334FU  3.+2726FU  4.-4132KI  5.+2625FU
                    6.-2288UM  7.+7988GI  8.-3122GI  9.+5968OU 10.-5142OU
                   11.+8877GI 12.-2233GI 13.+3948GI 14.-7162GI
      5.30    1.08  1.+2726FU  2.-8384FU  3.+7776FU  4.-3334FU  5.+2625FU
                    6.-8485FU  7.+2524FU  8.-2324FU  9.+2824HI 10.-4132KI
                   11.+6978KI 12.-8586FU 13.+8786FU 14.-8286HI 15.+2434HI
                   16.-2288UM 17.+7988GI 18.-8676HI
 14   9.21    0.17  1.+2726FU  2.-3334FU  3.+5968OU  4.-3142GI  5.+7776FU
                    6.-4233GI  7.+3948GI  8.-8384FU  9.+4746FU 10.-8485FU
                   11.+4847GI 12.-8586FU 13.+8786FU 14.-8286HI 15.+6978KI
              0.17  1.+7776FU [+0!]
    pruning  -> rep=0.348%  hash=15%  null=87%  fh1st=94.2%
    extension-> chk=257599 recap=66923 1rep=1618
    futility -> misc=752 drop=803 cap=1581 mt=1981 misc(k)=1463 cap(k)=1542
    hashing  -> always=19% prefer=49% supe=1.82% infe=0.02%
    hashing  -> exact=0 lower=12% upper=1.38% sat=25% age=1
    n=4282955 quies=2226796 eval=13277187 rep=6 0(chk) 7142(supe)
    time=10.80  cpu= 99%  mat=0.0  nps=396.35K
Black 1> 7776FU '(18!) 000:10/000:10  elapsed: b10, w0
'  9  8  7  6  5  4  3  2  1
P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
P2 * -HI *  *  *  *  * -KA *
P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
P4 *  *  *  *  *  *  *  *  *
P5 *  *  *  *  *  *  *  *  *
P6 *  * +FU *  *  *  *  *  *
P7+FU+FU   +FU+FU+FU+FU+FU+FU
P8 * +KA *  *  *  *  * +HI *
P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
- time ctrl: 10800 -- 10800
nsuc_check: [0]=0 [1]=0
- a pv was peeked through the transposition table.
 1:-5142OU  2:+2726FU  3:-3334FU  4:+5968OU  <HASH HIT>

(以下大幅に略)

quit

    pruning  -> rep=0.355%  hash=20%  null=88%  fh1st=94.7%
    extension-> chk=1825585 recap=417324 1rep=56769
    futility -> misc=795 drop=786 cap=1758 mt=2316 misc(k)=1573 cap(k)=1672
    hashing  -> always=34% prefer=35% supe=4.54% infe=0.06%
    hashing  -> exact=0 lower=14% upper=1.81% sat=85% age=4
    n=26455637 quies=11331507 eval=97628596 rep=184 0(chk) 53504(supe)
    time= 1:33  cpu= 98%  mat=0.0  nps=283.77K
$

デフォルトの設定では持ち時間10秒で指すようになっているので、10秒ごとに1手ずつ3手まで進むのが見られると思います。3手進んでも先読みを行っているので、そこでquit と入力すれば終了します。

Bonanzaのビルドは良い感じで行っているようです。では早速、このBonanzaのプログラムをEmscriptenでJavaScriptに移植していきましょう!

次の記事はこちら→ embona - ブラウザで動くBonanzaを作ってみた(その2)