はじめに

 囲碁の棋譜は手順を座標で表示する方式で作成されます。もっとも普及している棋譜記述の方式はSGFといわれるテキスト形式です。ここではどのようにそのSGFをJavaScriptで棋譜再現をするかの仕組みを解説します。JavaScriptによる囲碁の棋譜再現は「NHK杯囲碁トーナメント」のページで使われているものです。このプログラム(goview)はパッケージになっているので、他のホームページでもご利用いただけます。

1.SGFの形式と正規表現によるパターンマッチング

SGF(Smart Game Format)とは囲碁に限らず色々なゲーム一般を記述する形式ですが、囲碁に一番多く用いられています。
書式規約は次のようになっています。現在のバージョンはFF[4]となっています。

 枝分かれを解析するには括弧のはじめと終わりのパターンマッチングで配列にし、括弧の終わりまで棋譜再現が終わったら分岐点まで戻る操作が必要になります。この枝分かれ形式が記述できることで、沢山の定石の変化を記述した定石辞典が作られています(=>Kogo's Joseki Dectionary)。

 文字座標の棋譜を解析するにはパターンマッチングの正規表現を使います。goviewは変化のあるSGFには対応していませんが着手は鍵括弧[座標]でくくられているので、同じく正規表現を使ってその単位で着手順の数値配列にします。
正規表現はregexp=new RegExp (pattern[,flag])の形でObjectを定義します。一旦Objectを定義するとpatternに一致する部分を取り出す”.match”などのメソッドなどを使うことができます。flagで繰り返しの”g”を指定すれば一致した部分を取り出して配列にすることもできます。次の例はsgftext=棋譜のときに着手を配列sg[ ]にする記述です。

  sg= new Array( ); //手順の配列;
  chakusyu= new RegExp (/\;W\[a-s]|\;B\[a-s]/g); //’a-s’=座標文字;
  sg=sgftext.match(chakusyu);// 棋譜のsgftextをパターンで配列にする;

 これでsgfの棋譜テキストを着手手順の配列 sg[1],sg[2],sg[3],。。。にすることができます。また配列オブジェクトのメソッド".length"で、配列の数=sg.lengthを求めることができます。さらに取り出した配列を,黒ならteban=1に白ならteban=2に、さらに座標a−sの文字を1−19の数値に変換します。


2.棋譜の取り込みと石の表示

JavaScriptではブラウザが制御しているデータのみが対象となるので棋譜の取り込みは次のように制限されます。

 JavaScriptではフォルダーにある棋譜を参照することができないので、あらかじめHTMLのページの中に埋め込まれた棋譜を碁盤の上に表示するのが一般的ですが、テキストエリアに棋譜を貼り付けてもらうことでも、それを読み取ることが可能です。このgoviewでは次のようにTEXTAREAを非表示(visibility:hidden)にし、 URL= bodoview.htm?sgf=(;B[jj];)の形式で付加された棋譜を、非表示のテキストエリアに取り込むようにしています。

<HTML><BODY>
<SCRIPT src="maz/bodoset.js" ></SCRIPT>
<FORM name=sgf><TEXTAREA name=kifu cols=30 rows=3 style="visibility:hidden;"></TEXTAREA></FORM>
<SCRIPT src="maz/bodoview.js"></SCRIPT>
</BODY></HTML>

それでは棋譜を取り込むプログラムのbodoview.jsの中身を覗いてみましょう。(なおこのプログラムではTEXSTAREAを表示するように(visiblity:visible)にすれば、そこに貼り付けた棋譜を取り込むことができるようにもしてあります。)棋譜を一手ごとの配列sgに変換するときに正規表現によるパターンマッチングを使います。

==========================================================
args=getArgs();
if (args.sgf) {document.sgf.kifu.value=args.sgf;//URLに付加されていれば取り込み
sgftext=args.sgf;
} else {
sgftext=document.sgf.kifu.value;//なければテキストエリアから読み取り
sgftext=sgftext.replace(/\r\n|\n/g,'');//改行コードを削除
}
-----------------置き石配列の取出し------//通常置石は最初に黒だけですので少しいい加減に作っています。
addbtext='';
adbsg=sgftext.match(/AB(\[\w\w\]){2,}/);黒の置石AB[aa][bb]。。。を取出し
if(adbsg){
adbsg=adbsg[0].match(/(\[.+?])+/g);//ABを取り除いた配列を作る
addbtext=adbsg[0].replace(/(\[\w\w\])/g,";AB$1");//AB[aa]AB[bb]に変換
}
----白の置石配列addwtextも同様処理-----//通常白の置石はありませんが。。 
addsgtext=addbtext+addwtext;
addsg=addsgtext.match(/;(AW|AB)\[\w\w\]/g);//置石配列を作成
--------------------------
sg = new Array('');//棋譜なし
if (sgftext != '') {
sg = sgftext.match(/\;W\[\w\w\]|\;B\[\w\w\]|\;AW\[\w\w\]|\;AB\[\w\w\]/g);//一手配列sgの作成
}
//以下、配列sgによって手順を表示する。ABあるいはAWの置石では手順を増やさない

==========================================================
棋譜に従って碁盤に表示する方法を置石配列addsgについて覗いてみましょう。下記の関数は手順の中で置石が見つかった時に表示するものです。これで石と手順の表示の仕組みがわかると思います。
if(addsg) {
    for (ii=0; ii<addsg.length;ii++) {   //置石配列addsgがあれば連続処理する
       bw=addsg[ii].match(/AW|AB/);
       if (bw == 'AW') {teban=2;}
       if (bw == 'AB') {teban=1;}
       pos=addsg[ii].match(/[a-s][a-s]/);//SGFの19路座標はa-s
       zahyo='Z'+pos[0];//文字座標、bodoset.jsのposnameと同じ
          if (bw=='AB') {
             document.getElementById(zahyo).className='bisi';//黒石を表示
             document.getElementById(zahyo).innerHTML=''; //手順番号表示せず
          } else {
             document.getElementById(zahyo).className='wisi';//白石を表示
             document.getElementById(zahyo).innerHTML='';//手順番号表示せず
          }
   }
}
==========================================================

3.スマートな再現プログラム

棋譜のテキストに随って表示するときでも、囲った石があればそれを取り除く配列が必要になります。また検討のため碁盤をクリックして石を置くときにはその位置におけるか否かの判断が必要であり、終局に地を数えるならばどこが空いているかを知る必要があります。これらのために表示とは別に、内部にシミュレーション用の碁石の配列を用意しておきます。
最初にこのスマートなシュミレーション用碁盤プログラムの作り方を知ったのは「鈍」のホームページでした。それ以前にもhpのポケコン用にniftyのfigo棋譜表示のプログラムを作ったことがありますが、日浅さんの「鈍」の講座(今はなくなったようです)で学習した内容を以下に記します。

ポイントは19路盤の場合は21x21の配列を用意し、空(あき)、黒石、白石、境界を0,1,2,3と数値化します。こうしておくと囲われたかどうかの判断が周りに空があるか否かだけで出来ます。囲われているか否かを判断する関数が次のdameです。引数のposはpos=posx+posy*21の座標数値でban[pos]=0ならばその点は空きでありteban=1かteban=2かで黒か白かになります。また盤の大きさははnxt= new Array(1,-1,21,-21);で定義しておきます。従ってnxt.lengthで上下左右の4方向を調べます。この時のban[pos]=6はチェックした箇所を示し、関数の戻り値がreturn=1なら囲われていないことを表します。関数の中で同じ関数を呼び出すものを回帰関数といいます。関数dameは隣が味方で空いていなければ隣に移って空きを調べます。こうして囲われていることがわかったらチェックした箇所の石を容易に取り除くことができます。またチェック点を元に戻す時は下のmodosi関数を使います。これも回帰関数です。引数のisiにはtebanとは逆の戻す石の値が入ります。
==============================================================
function dame(pos,teban) {
var dd = 0;
for (dd=0;dd<nxt.length;dd++) {
ban[pos] = 6;
if (ban[pos + nxt[dd]] == 0) {return (1);}  //隣は空
if ((ban[pos+nxt[dd]]!= 0) && (ban[pos + nxt[dd]] == teban)) { //隣は空ではなく味方
if (dame( pos + nxt[dd], teban) != 0) {return (1);}  //隣の味方に移って調べたら空がない
}
}
return (0); // tonari=akinasi,tonari=mikata,ren dama=akinasi==>return(0);
}

function modosi(pos,isi) {
nxt= new Array(1,-1,21,-21);
var mm=0;
ban[pos] = isi;
for (mm=0;mm<nxt.length;mm++) {
if (ban[pos + nxt[mm]] == 6) {modosi( pos + nxt[mm], isi);}
}
}
==============================================================
上記の関数を使って着手可能かどうかを調べるのに次の関数poscheckを使用します。poscheckは着手可能ならば配列21x21の盤にtebanの石の数値を入れ、囲んだ石を取り除きhamaにその数を入れます。取ったばかりのコウの点kouには石をおくことが出来ません。石を置くことができたら関数poscheckの戻り値はreturn=1になります。 JavaScriptではありませんがアルゴリズムの解説は右図の共立出版の「コンピュータ囲碁の入門」にも詳しく記述されているので参考にしてください。


==============================================================
function poscheck(teban,pos,kou,hama) {
nxt= new Array(1,-1,21,-21);
var kk=0;
var teki = 3 - teban; //if teban=1 then teki=2  この計算で白と黒の数値が切り替わります。;
var koupos = 0;
var sc = 0; //sc=0--->tonari aki nasi;
var tc = 0; //tori isi count;
var mc = 0; //mc=0--->tonari zenbu teki;
var flag = 0; //flag=0--->ren no aki mo nasi;
if (ban[pos] != 0) { return (0);} //isi ari;
if (kou == pos) {return(0);} //zenkai= kou;
for (kk=0;kk<nxt.length;kk++) {
if (ban[pos+(nxt[kk])] == 0) { sc=1;flag=1;} //tonari aki;
}
ban[pos] = teban; //kari oki;

// tonari tori
for(kk=0;kk<nxt.length;kk++) {
if (ban[pos + nxt[kk]] == teki) {
if (dame( pos + nxt[kk], teki) == 0) {koupos = pos + nxt[kk]; tc += tori( pos + nxt[kk], 0); flag = 1;}
else {modosi( pos + nxt[kk], teki);}
}
}

for (kk=0;kk<nxt.length;kk++) {
if (ban[pos + nxt[kk]] == teban) {
mc = 1;
if (flag == 0) {
if (dame( pos + nxt[kk], teban) != 0) {flag = 1;} //tonari ren dame ari
modosi( pos + nxt[kk], teban);
}
}
}

if (flag == 0) {
ban[pos] = 0; //kari oki hagasi =>jisatu te;
} else {
hama[teban] += tc; //hama count;
if ((sc == 0) && (tc == 1) && (mc == 0)) {
kou = koupos; // (tonari=akinasi,tori=1, tonari=zenbuteki) => kou!;
} else {
kou = 0;
}
}

return (flag); //flag=1(OK!) flag =0 (jisatute or kou not OK);
}

==============================================================
プログラムの全部の説明は省略しますが、上記の関数が要となります。


4.クリカブルマップで石のクリックを

 棋譜を再現表示するには碁盤画像と、碁石画像が必要です。それらの画像をベースにJavaScriptでページを作りだします。 まず 碁盤の上にはあらかじめ非表示の碁石を座標名をつけて配置しておきます。これを棋譜再現に従って黒白の碁石を座標名を指定して表示します。 あわせて石の上に手順を表示する番号を付けます。碁石画像には●と○の他に最新の着手点を表す黒と白の石も必要です。また終局には地を表示するための小さなも用意してあります。
 さらにこのプログラムでは、碁盤をクリックしたときには石を表示して棋譜の検討ができるように透明碁盤を一番上にかぶせてあります。 透明碁盤の交点にクリカブルマップを配置しておきます。

最初はこれらの画像を読み込むために次のタグだけのWEBページ(bodogoview.htm)を用意します。
<HTML><BODY>
<SCRIPT src="maz/bodoset.js" ></SCRIPT>
<FORM name=sgf><TEXTAREA name=kifu cols=30 rows=3 style="visibility:hidden;"></TEXTAREA></FORM>
<SCRIPT src="maz/bodoview.js"></SCRIPT>
</BODY></HTML>
 囲碁の棋譜再現のページは上記だけですが、bodoset.jsでそのWEBページにダイナミックにドキュメントを書き出し、棋譜再現のための碁盤と碁石を読み込みます。ここでは、そのbodoset.jsの構造を下記に示します。bodoset.jsは下記の全部をdocument.writeでラップしたものです。なお、bodoview.jsは棋譜を取り込むプログラムですが、これは後ほど解説します。

==========================================================
<STYLE type=text/css>
.ban {POSITION: absolute; TOP: 0px ;LEFT: 0px;} //碁盤座標;
.sub {POSITION: absolute; TOP: 385px ; LEFT: 40px;} //押釦操作板の座標;
.bisi {"手順文字領域と背景の石画像のスタイル"} //黒石の上に手順番号を表示;
.wisi {"手順文字領域と背景の石画像のスタイル"} //白石の上に。。;
.empty{"手順文字領域と背景の石画像のスタイル"} //透明石も同様に。。;
//---その他に石の画像とスタイルの定義---;
</STYLE>

<SCRIPT src="棋譜取り込み変換のJavaScripファイル"><\/SCRIPT>

<SPAN class=ban> //位置合わせのために絶対座標にする「利用するときはフレーム内に!」;
<IMG src='"碁盤画像' border='0'> //碁盤の読み込み;

<SCRIPT Language='JavaScript'> //以下ページ内JavaScriptのプログラム;
//----以下盤の全格子点に透明石を配置-----;
posname='Z'+文字座標; //文字座標は19路縦横a-sの2文字;
document.write("<SPAN class=empty style='position:座標位置' id='posname'></SPAN>"); //透明石;
//--------ここまで繰り返し---------------;

// ------------------------以下クリカブルな透明碁盤レイヤーを重ねる-------------;
document.write("<IMG usemap='#goban' src=透明碁盤の画像 border='0'> <BR>");
document.write("<MAP name='goban'>");
// ----以下盤の全格子点に、ipos=座標のx+y*19、xypos=クリカブル領域の範囲、utu=クリック時のJavaScript動作---;
document.write ("<AREA href='JavaScript:utu(",ipos,");' shape='rect' coords='",xypos,"'>");
// -----ここまで19x19繰り返す-------;
document.write("<AREA shape='default' nohref>"); //それ以外はクリック無効;
document.write("</MAP>"); //クリカブルマップの終了;
// -------------------------重ねレイヤーの終了---------------------------------------;

</SCRIPT> //ページ内JavaScriptの終了;
//--------;

<SPAN class=sub >
<FORM name=control>")
//--------操作押釦などの配列--------;
</FORM></SPAN>
//--------;
</SPAN>
==========================================================

5.棋譜の進行制御と次の一手推定および終局の計算

 棋譜を再現するには順送りや戻しの機能が必要です。sgfの棋譜を読み取って手順にしたがった配列を作っておけば、進行するときはその手順までの石を盤に表示すればよいので簡単です。しかし戻すときには盤から表示している石を取り除くことに加えて、打ち上げた石がある場合はその石を埋め戻す必要があります。このため手順ごとに打ち上げた石の群を記憶させておきます。これで進行と戻しが可能になります。また指定した手数までジャンプ表示の場合は、最初からその手数までをシュミレーション用の碁盤に書き出し、それを表示させます。
盤面にはすべて透明石を配置してあるので、手順にしたがってその属性を変え白や黒石を表示します。例えば手番が黒なら次のように黒石に手番をつけて表示します。
document.getElementById(zahyo).className='bisi';
document.getElementById(zahyo).innerHTML=手番号;

一手づつ進行させるのが面倒な場合を想定してAUT進行も可能です。しかし石の数が増えて混んでくると手順がわかりにくくなるので石の数に応じて少しずつ進行を遅らせるようにしています。これは時間の関数を使えば容易です。

以上に加えてgoviewではJump釦を押すことで「次の一手」モードに切り替えることができます。囲碁では互いに重要な点を打ち合っているのでそれらの着手候補点をまとめて表示し、いずれが次の一手か選択してもらい正解ならば次に進みます。ただし次の候補の手数は乱数で増減させています。これは表示する次の手数を決めておくと推定しやすくなることを防止するためです。このモードは通常の一手送り進行釦を押せばリセットされます。


また、goviewにはScoreの機能もあります。ただしこれは終局で領域が確定したとき、白か黒かに囲われている空き地を数えるだけの機能です。死石はクリックして知らせてやれば空き地にカウントします。手順表示のある場合はこの操作ボタンはありませんが、手順表示のないサンプルにはこのScore釦があるので確認してください。