gnugo (2)

久しぶりにvboxを起動したら、新しい版である5系の準備が整ってるんで、取りに来いと案内が有った。 で、5系にしたんだけど、この際だから、いつも失敗続きのOpenBSDを試してみるかって気になった。 Fedora 23が出た事は知ってるけど、それは置いておいて、です。

4系で駄目だったから5系でどうか? どう失敗してたかと言うと、正しくキーイン入力を 受け付けず、対話形式で進められるインストール作業が、途中で立ち往生しちゃうのだ。 そもそも、その前にvboxを起動した時に、糞石が機能不足だから、諦めろと言ってくるんだけど、 それを無視して強引にやってるからかも知れないけれど。。。

ネットを検索すると、しつこくインストールを繰り返して、どうやらこうやらインストールを 成功させた猛者もいるようだけど。

やってみた。そしていつもの失敗。今回は、どんな失敗してるかちょっと調べた。 shellに落ちてから、次にコマンドで起動

# sh -x install
+ MODE=install
   :
+ getopts af: opt
+ shift 0
+ 0 == 0
install: //install.sub[1776]: 0: not found
IIIegal instraction
#

以上は、vboxコンソールからの写経品(間違い、ご容赦)

しょうがないのでvmwareの方に素直に入れた。基本的な状況。

$ df -k
Filesystem  1K-blocks      Used     Avail Capacity  Mounted on
/dev/wd0a     7731102    545506   6799042     7%    /
$ ps awx
  PID TT  STAT       TIME COMMAND
    1 ??  Is      0:01.05 /sbin/init
17235 ??  Is      0:00.02 dhclient: vic0 [priv] (dhclient)
21371 ??  Is      0:00.01 dhclient: vic0 (dhclient)
20947 ??  Is      0:00.02 syslogd: [priv] (syslogd)
27661 ??  I       0:00.17 /usr/sbin/syslogd
11515 ??  Is      0:00.01 pflogd: [priv] (pflogd)
 5190 ??  S       0:00.26 pflogd: [running] -s 160 -i pflog0 -f /var/log/pflog (pflogd)
16637 ??  I<s     0:00.07 ntpd: [priv] (ntpd)
 7716 ??  S<      0:01.14 ntpd: ntp engine (ntpd)
28440 ??  I       0:00.12 ntpd: dns engine (ntpd)
 9210 ??  Is      0:00.02 /usr/sbin/sshd
28641 ??  Is      0:00.03 smtpd: [priv] (smtpd)
27204 ??  I       0:00.03 smtpd: lookup (smtpd)
20805 ??  I       0:00.03 smtpd: control (smtpd)
15897 ??  I       0:00.02 smtpd: queue (smtpd)
 3357 ??  I       0:00.01 smtpd: scheduler (smtpd)
28416 ??  I       0:00.04 smtpd: klondike (smtpd)
 1877 ??  I       0:00.03 smtpd: pony express (smtpd)
15766 ??  I<s     0:00.00 /usr/bin/sndiod
 6374 ??  Ss      0:00.05 /usr/sbin/cron
 3864 ??  Is      0:00.16 sshd: sakae [priv] (sshd)
15093 ??  S       0:01.72 sshd: sakae@ttyp0 (sshd)
22482 p0  Ss      0:00.11 -ksh (ksh)
22465 p0  R+      0:00.01 ps -awx
 8606 C0  Is+     0:00.02 /usr/libexec/getty std.9600 ttyC0
16846 C1  Is+     0:00.01 /usr/libexec/getty std.9600 ttyC1
19076 C2  Is+     0:00.01 /usr/libexec/getty std.9600 ttyC2
18250 C3  Is+     0:00.01 /usr/libexec/getty std.9600 ttyC3
27301 C5  Is+     0:00.01 /usr/libexec/getty std.9600 ttyC5

色々動きすぎの感じがしないでもないな。次は、インストールに使ったCDを覗いてみる。

# mount_cd9660 /dev/cd0a /mnt
$ cd /mnt
$ ls
5.8       TRANS.TBL etc
$ cat etc/boot.conf
set image /5.8/i386/bsd.rd
$ ls 5.8/i386/
INSTALL.i386 base58.tgz   bsd.mp       cdbr         man58.tgz    xserv58.tgz
SHA256       boot.catalog bsd.rd       comp58.tgz   xbase58.tgz  xshare58.tgz
TRANS.TBL    bsd          cdboot       game58.tgz   xfont58.tgz

えーん、インストールスクリプトとかは、bsd.rdの中だよ。ヒントはINSTALL.i386に書いて あるのかなあ。 OpenBSDでLive CDをつくる でも参考にすると、更に中へ入っていけるのかな。ソースとかのおまけサービス品がCDの 中に入っているかと思ったら、それはなかった、残念。

ひょっとしてVMPlayerで作った仮想Diskをvboxでも使えるかと思って試してみたら、そんな 田舎仕様のフォーマットは知らんと拒否された。DellのEMC買収でこの先起こること EMCは儲かっているらしい、片やオラクル勢のvboxとは喧嘩したく無い。棲み分け維持だろうね。

# vnconfig -v vnd0 /home/sakae/5.8inst/bsd.rd
vnd0: 7032320 bytes on /home/sakae/5.8inst/bsd.rd
# mount /dev/vnd0a /mnt
mount_ffs: /dev/vnd0a on /mnt: Device not configured

あれれ???? 使い方を間違ったかと純正なやつで試してみると

# vnconfig -v vnd0 /home/sakae/5.8inst/miniroot58.fs
vnd0: 3571712 bytes on /home/sakae/5.8inst/miniroot58.fs
# mount /dev/vnd0a /mnt
# ls -F /mnt
boot  bsd

ちゃんと動く。ひょっとしてxv6-armv7の時みたいに、カーネルとファイルシステムが 合体してないかな。後でゆっくり解析してみれ。

で、冒頭のインストール時のエラーなんだけど、どんなshellコードだったか調べてみた。 /usr/src/distrib/miniroot/install.subにそれっぽいコードが有った。

# Parse parameters
AUTO=false
RESPFILE=
while getopts "af:" opt; do
        case $opt in
        a)      AUTO=true;;
        f)      RESPFILE=$OPTARG;;
        *)      usage;;
        esac
done
shift $((OPTIND-1))
[ $# = 0 ] || usage

このdirにある、dot.profileが、現場では .profileになって活躍するんだな。 たとえば、こんなコードが書かれている。

        while :; do
                echo -n '(I)nstall, (U)pgrade, (A)utoinstall or (S)hell? '
                read REPLY

                # If the timeout has expired, begin the installation.
                if $timeout; then
                        timeout=false
                        echo
                        REPLY=a
                 :

ほっとくと、autoinstallが走り出すんか。このスペックを何とか埋め込めれば何とかなるかも 知れないと、妄想してみる。

その場合は、同dirの上位にあるramdisk内のlistに梱包物を追加しておけとな。いずれにしろ、 distribの下は、OpenBSDをCDなりで出荷する時の、製作に関わる大事な現場って事が分かりました。 autoinstall誤解してた。こちらを見れ。 man autoinstallするのも忘れずに。そしてFully Automated OpenBSD Installation Using PXE

regress.pike

前回やったpikeの続き。スクリプトを見てたら、debugって変数をonにすると、gnugoとの やり取りをモニター出来るって判明。折角なんで試してみる。 テストコードは下記

[sakae@fedora regression]$ cat bbb.tst
loadsgf games/kgs/yagr4.sgf 184
60 restricted_genmove black J4 J8
#? [J8]

games/kgs/yagr4.sgfと言う譜(実戦で使われた、リアルな譜って事が読み取れる) の184手まで実行した時、次の手は何かなってテストだろう。 期待値はgnugoが狂ってなければJ8ですよって事だな。

[sakae@fedora regression]$ ./regress.pike bbb
Waiting for writing to be finished.
Sent: loadsgf games/kgs/yagr4.sgf 184
Sent: reset_reading_node_counter
Sent: reset_owl_node_counter
Sent: reset_connection_node_counter
Sent: 60 restricted_genmove black J4 J8
Sent: 10000 get_reading_node_counter
Sent: 10001 get_owl_node_counter
Sent: 10002 get_connection_node_counter
Sent: 10003 cputime
Signalling finish of writing
Sent: 10004 cputime
Finished waiting for writing to be finished.
Recv: = black
Recv: =60 J8
Recv: =10000 152919
Recv: =10001 87
Recv: =10002 736
Recv: =10003 0.890
Recv: =10004 0.890
Reader closing down.
bbb                                      0.89    152919      87      736
Sent: 10004 quit
Total nodes: 152919 87 736
Total time: 0.89 (0.89)
Total uncertainty: 0.00

コマンドの冒頭に付いている番号は、送ったコマンドに対する受信データの整合を取って いるんだな。10000番代は、pikeが勝手に付けているのかな?

とか言いながら、コードを見てたら、gnugoとのやり取りの舞台裏が分かった。

    object f1 = Stdio.File();
    object pipe1 = f1->pipe();
    object f2 = Stdio.FILE();
    object pipe2 = f2->pipe();
    engine = Process.create_process(program_start_array,
                                    (["stdin":pipe1, "stdout":pipe2]));
    thread_create(program_reader, f2);
    thread_create(program_writer, f1);
    thread_create(program_monitor);
      :
  if (engine == "") {
    engine = "../interface/gnugo";
    engine_options |= "--quiet --mode gtp" / " ";
  }

エンジンがgnugoで、そいつをサブプロセスとして起こしておいて、pike側とは、パイプ 接続、それをファイルに結び付けているとな。こうしておくと、上位の部分では、getsを 使って読み込んだり、writeを使って書き出す風に使えるとな。

でも、いかんせんpikeを常用しようとすると、サンプルが少なすぎるな。単にオイラーが オブジェクト嗜好に慣れていないだけかも知れませんが。。。

gnugo 1.2

今まで、サイトを指定して翻訳するWebアプリは使っていたけど、クリップボードのテキストも 簡単に翻訳したい。ちょいと調べたら、 Google Translate Desktop 2.1.92 が見つかった。試してみようと思ったら、フラッシュが必要と言われたんで諦めた。 (諦めたと言うより、積極的に止めた)次なる候補は? Google翻訳君アプリよさそうなんだけど、 リンク切れ。ぐぐるに逆鱗に触れて、公開中止? 三番目の正直で、 クリップボードに取り込まれたデータの翻訳・読み上げを行う「ClipBoard Translator」 を選んでみた。暫く使ってみるか。

なんで、いきなりこんな事を書いたかと言うと、gnugoを起動した時に出て来る、使い方の 説明に戸惑ったから。ちょいと翻訳したいって思った時、クリップボード経由があれば楽だからね。

この前からやってるgnugo、3.8バージョンは複雑過ぎてちょっと手に負えないので、初期 バージョンを試してみる事にした。(何と20年前のもの)

ソース詠みする時、GNUスタイルは体に馴染まないので、indent -kr -i4 main.c とかやって 変換したよ。それから、ソースの冒頭にあるクレジットを毎回見せられるのには辟易するんで、 クレジットカッターを通した。

[sakae@fedora gnugo]$ for f in *.[ch]
> do
> cat $f | sed '1,27d' > $$
> mv $$ $f
> done

BSDに入っているviだと、冒頭コメントはスキップして初画面を開いてくれたはずだったんだけど、 今nviで試したらそんな機能無かった。果たしてemacsでそういう機能が有るのだろうか?

黒石のハンディキャップは幾つにする? お前は黒い人(挑戦者)、白い人(防衛者)か、 選べよ。座標位置は、a5みたいに置くんだぞ。

passするなら、お前の番の時、passって打ち込め。両者がpassした時点で対戦終了。 saveって打ち込めば、盤面情報ともろもろの情報をgnugo.datってファイルに保存して ゲーム終了出来るぞ。次回起動すると前の画面を再現して継続出来るよ。

ゲームの途中でstopって打ち込むと、その時の陣地を計数してゲーム終了出来るよ。 stopの後に出て来る質問にyで答えると、死に石の設定を求められて、それが終わると、 結果が表示されてゲーム終了。nって答えると、結果未判定でゲーム終了するぞ。

以上、M$の力を借りての翻訳なんで、間違っていたらM$に文句を言ってくれ。当方は一切の 責任を放棄します。細かい事は全てソースに書いてあるんで、それを読んどくれ。

ソース読む前に、Documentationをみとけ。関数の簡単な説明が載ってる。次は、gnugo.hを 見るのが定石。

#define EMPTY 0
#define WHITE 1
#define BLACK 2

盤上の石の定義。空か白か黒か。白石は大はまぐりの貝殻から出来てて、高級品は2000万円 しますって、NHKの昼ブラでやってた。宮崎県の日向市が産地ですってさ。黒石は、那智の 石が有名らしい。

次は、patterns.h 移動の為のパターンって事。どういう風に、石の繋がりを 成長させたら徳か表現してるんだな。

#define PATNO 24
#define MAXPC 16

/* pattern x, y coor and attribute */
struct patval {
    int x, y, att;
};
/* att = 0 - empty,
         1 - your piece,
         2 - my piece,
         3 - my next move
         4 - empty on edge,
         5 - your piece on edge,
         6 - my piece on edge
*/
struct pattern {
    struct patval patn[MAXPC];  /* pattern */
    int patlen;                 /* number of pieces in pattern */
    int trfno;                  /* no. of transformation */
    int patwt;                  /* pattern value */
};

で、どんな定石かと言うと、24個の定石中、最初のものを挙げる。

static struct pattern pat[PATNO] = {
/*
   pattern 0: 232   connect if invaded(攻める)
              010
*/
    {{{0, 0, 2},
      {0, 1, EMPTY},
      {1, 0, 3},
      {1, 1, 1},
      {2, 0, 2},
      {2, 1, EMPTY}}, 6,   4,   82},
//     x  y  att      len  trf  pval
   :

trfnoが4になってるけど、これは4方向に展開出来るって事かな。8ってのもあるな。隅にも 展開出来るって事かな。

次も定石通り、main.cから見てく。 変数の説明は勿論、英語だ。辞書を引け。opponentってコンピュータの敵って意味に使ってるんか。 libertyって自由かと思ったら、業界用語で、ダメって事らしい。で、業界用語を引く

囲碁の終局時に、どちらの陣地でもない領域のことをいう。IT用語に直すとDMZですかい。 懐かしいな。もう一つ意味があるらしい。石の呼吸点。石も人間にたとえられるので、 逃げ道が無いと、やってられません。完全包囲で捕虜にはなりたくないですから。 この用語も、文脈依存で使えって事ですか。日本の得意技、あ、うんの呼吸。

main()の前半部分は、ゲームの準備をやってる。前ゲームからの継続なら、データファイルから盤面を 再現して、その後データファイル削除。新規なら、ハンディキャップを問うて、適当に置石。

リアル世界でレールの上に置き石すると、列車往来妨害罪でしょっぴかれるけど、囲碁の置石 なら、心置きなく出来るぞ。その置石ルーチンは、sethand()なんて名前が付いていた。 手でセットするからと思ったら、set up handicap pieces って事だった。我ながら、想像力 無さ杉な硬い頭に嫌気がするぞ。

void sethand(int i)
/* set up handicap pieces */
{
    if (i > 0) {
        p[3][3] = BLACK;
        if (i > 1) {
            p[15][15] = BLACK;
            if (i > 2) {
                p[3][15] = BLACK;
                if (i > 3) {
                    p[15][3] = BLACK;
                       :
                                                        if (i > 16)
                                                            p[12][6] = 2;
                                                    }
                                                }
                        :
}

って具合に、見事な雁の渡り風な形をしています。昔のrubyコードは、雁の渡り風で 美しいと思ったけど、いつの間にか、ごちゃごちゃした作りが増えてますな。 rubyじゃないけど、コードの形が美しいと思ったのは、 mini-camlのメインルーチン。見事にネストしてる。 最後の行は閉じ括弧が乱打されてるけど、これはLisp風。括弧は空気のようなものだから、 意識する必要は無いんです。無駄な事で、行数を使うな。

置石には置く場所に一応習慣があるようで(ルールになっているかどうかは知らない)、それを 忠実に再現してるようです。本来なら定数BLACKって書かなければいけないんだけど、途中から 2って、直値を書いてるよ。関数への入力は0-17の範囲なんだけど、最大値のチェックは、 コード自身に委ねてるね。

main()の後半は、メインループ。whileループ内にある、getmove()関数内で、コマンド類(save,stop,pass)の処理と、 敵側の処理を行っている。敵側の処理ってのは、何処に打つを入力させて、それが有効な 場所(石が無い事)か確認、そして石が劫か判定石を取るって処理。それらが

                if (!getij(move, i, j) || (p[*i][*j] != EMPTY)
                    || suicide(*i, *j)) {
                    printf("illegal move !\n");
                    printf("your move? ");
                    scanf("%s", move);
                    getmove(move, i, j);
                }

こんな風に表現されてた。これってLisp屋が考えるロジックだ。ifのテスト句を 頭から処理してって、一つでも成功すると、{}内が実行される。そう、再起するんだ、 やり直すんだ、ああ、それって再帰でないかい。

suicideで、まず 呼吸点(liberty)があるか確認。呼吸点があれば、石を置けて成功。 呼吸点(liberty)が無かったら劫になっていないか確認。発生してなかったら成功。 劫が発生してたら失敗って事で1を返す。

まだまだ続くけど、この辺で、、、

今日の読み物

コンピュータ囲碁 にある コンピュータ囲碁(<特集>ゲームとコンピュータ) が、面白い。