2012/08/04

Forsyth-Edwards Notation

Chessの局面の表現として、Forsyth-Edwards Notation(FEN)という記法がある。これは、19世紀にDavid Forsythによって新聞でチェスの棋譜をコンパクトに記事にするために開発されたもので、コンピュータチェスでは、チェスを含むボードゲームの棋譜を保存するためのPortable Game Notation (PGN)で用いられたり、コンピュータチェスにおいてコンピュータ同士が通信するためのインタフェースであるUniversal Chess Interface (UCI)でも用いられている。

実際のFENは、例えば次のようなものである。

r1bqkbnr/ppp2ppp/2np4/8/2P1P3/4Q3/PP3PPP/RNB1KBNR b KQkq c3 0 5

最初のブロックが駒の配置を表している。/で区切られた文字列が盤面の上(8行目)から順に各行を表している。各行は左(A列)からのコマの配置を表し、数字は連続する空マスの数である。つまり、上の文字列は次の盤面を表している。

r.bqkbnr
ppp..ppp
..np....
........
..P.P...
....Q...
PP...PPP
RNB.KBNR

次のブロックは、次の手が白(w)の番か、黒(b)の番かを表している。

3つ目のブロックは、キャスリングが可能かどうかを表している、KQkqはそれぞれ白のKingサイド、白のQueenサイド、黒のKingサイド、黒のQueenサイドを表す。いずれのキャスリングも不可能なときは、- となる。

4つ目のブロックは少し面白くて、アンパッサンのターゲットとなるマスを表している。つまり、このマスに駒があるとしたらとれるポーンがあるならばアンパッサンできるということを意味している。

5つ目のブロックは50手ルールに関するカウントで、最後にポーンが動くか、駒が取られるかしたあとで経過した手数を表す。

最後の6つ目のブロックは最初からの手数を表している。

このFENを実装したことで盤面の状態を文字列で表せるようになった。これを利用して、すべての駒のすべての配置からの可能な移動と、あるゲームの経過の中での可能な手とある手を打った後での盤面を確認するテストコードを生成した。ユニットテストというよりもレグレッションテストに近いですが、早速、キャスリングの実装にバグを発見した。テスト重要。

テストフレームワークはgoogletest http://code.google.com/p/googletest/ を使い、一部を抜粋すると次のようなコードになっている。これはレグレッションテストの方で5000行程度、駒の動きの方が18000行程度あり、コード本体の10倍以上のコードでテストしていることになっている。

あれ、コードのフォーマットが壊れているな。後で直しておきます。

2012/05/28

Evaluating function and min-max search

チェスではカードゲームなどとは異なり、各プレイヤーは次の手を考える上で完全情報 (perfect information)を持っている。 すると、各盤面に対して、次のいずれかがいえる [von Neumann and Morgenstern, 1944]
  1. 白が勝ちの盤面であれば、白は黒がどのような手を打とうとも必ず勝つことができる。
  2. 引き分けの盤面であれば、白は黒がどのような手を打とうとも少なくとも引き分けることができるし、黒も白がどのような手を打とうとも少なくとも引き分けることができる。しかし、両者が最善手を打ち続ければゲームは引き分けに終わる。
  3. 黒が勝ちの盤面であれば、黒は白がどのような手を打とうとも必ず勝つことができる。

そこで、評価関数(evaluating function / evaluation function) f(P) を白が勝ちのとき f(P) = +1、引き分けのとき f(P) = 0、負けのとき f(P) = -1とすると、各盤面 P から次の盤面 P' の評価関数 f(P') が最大となる手を打ち続ければいい。

もっともこれは存在定理のようなもので、実際にチェスの各盤面が与えられたときに、それが上記の3つのどれに属しているかを判定する手法は知られていない。 masterのゲームが引き分けで終わることが多いことからチェスの初期盤面は引き分けではないかとはいわれている。 しかし、それを確認するためには初期盤面からのすべての手を確認する必要がある(後にαβ探索の発明ですべては必要ないことがわかったが)。 チェスの各盤面に対する有効な1 move(白、黒と順に動かした場合を1 moveという。将棋のように片側だけの手を数えるときは1 plyという)はおよそ103通りある。 典型的なチェスのゲームは40手程度であることから初期状態から終盤まで読み切るには10120程度の盤面を評価する必要がある。 1つの盤面を評価するのに1μsとしても計算を終えるには1090年かかることになる(と原著でなってますが10105年かな?まあ人の寿命が102なので誰も答えを知りえないという意味では同じ)。

一方で、盤面の種類数で考えると 64! / 32!(8!)2(2!)6 = 1043 程度であるが、これも計算可能な数字ではない。 (チェスとは違い、チェッカーが最近解かれたが、チェッカーは1015程度の盤面数で、それでも解くのに18年かかっている。)

そこで、完全ではないが評価関数の近似を求めることを考える。 評価関数は白黒それぞれの駒に対するスコアと各駒のレイアウトに応じて計算される。 Shannonは次のような項(いまでいうfeature)を提案している。

  1. クイーン、ルーク、ビショップ、ナイト、ポーンそれぞれのスコアを9, 5, 3, 3, 1とする。
  2. ポーンの陣形
    1. 後ろにいたり、孤立していたり、重なっているポーンは弱い。
    2. センター e4, d4, c4 にいる。
    3. キングのそばのポーンの弱さ。
    4. ビショップと異なる色のマスにいるポーン。
    5. passしたポーン。
  3. 駒の位置
    1. 進んだナイト e5, d5, c5, f5, e6, d6, c6, f6 で特にポーンに守られており、敵のポーンに攻められていない。
    2. ルークの前が開いている (open file / semi-open file)。
    3. 7列目のルーク。
    4. 重なったルーク。
  4. コミットメント、攻撃など
    1. 守りに必要な駒。
    2. 攻撃している駒。
    3. pin
  5. 晒されているキングは弱い。
  6. movability (有効な手の数)

Shannonは、この評価関数は中盤で用いられ、序盤と終盤は異なった原則を用いるべきであるといい。また、これらの項の重みは実験に基づいて決められるべきと今で言うところのmachine learningの必要性を示唆している。

重みを決めるのが難しいので、最初の駒得の部分しか実装してないが、プログラムは次のようになる。

そして、この評価関数を用いてminmax探索を行うことを述べている。 minmax探索については以前にもScalaで書いたので詳しいことは省略するが、C++で書くと次のようになる

チェックメイトの判定のあたり、スコアとして帰ってくる値を使って、もうちょっとスマートにできないかなとは思うのだけど。

minmaxはすべての枝を探索するので、max_depthを1つ上げるごとに30倍程度の時間がかかる。 Shannonの論文では3 moves (max_depth = 6) で、109盤面の評価をしなくてはいけなくて、1つあたり1μsとすると16分程度かかると書かれているが、実際に計算してみると1つの盤面評価に13μs程度かかり、盤面数は3×108で70分かかった。 1 game終わらせるのに徹夜でも2日強は最低かかりそう。

論文ではさらに静止探索 (quiescent search)や前向き枝刈り(forward pruning)のアイデアも書かれている。 それらについては、またの機会に実装することにするが、Shannonの先見の明に驚くばかり。

2012/05/15

more C vs C++ for Chess

前回に続いて、CとC++でのチェスのプログラムのスピード比較を行った。前回と違い今回はg++でCのコードをコンパイルしただけでなく、コード全体をclassを使って書きなおした。class使ったら遅くなるんじゃないの?という疑問に答えようというわけだ。

まずはCとほぼ同じ処理をするコードをC++らしくclassで書きなおしたものが以下のプログラム。今回の話に関係のない入出力周りとmain周辺は省略してあるが、そこもCと等価なコードになっている。主な違いは

  • Move* mやPosition* posが関数の第1引数に必ずのようについていたのが、classとすることでその引数が暗黙のthisとなり不要になった。しかし、これは表面的な差に過ぎず、アセンブル出力をみると this は第1引数の (%rdi) に割り当てられているので実質的には差はない。
  • 変数の宣言が最初の使用位置へ。とくにfor loopでscopeが変化している。
  • 変数はprivateになり、setter/getterを通してアクセス。ただし、inline化されているので実質は同じと考えられる。
まずはプログラムを見てもらうとして、分析はその後で。

claude.h

claude.cc

前回と同様に random player 同士の対戦を1万回実行の実行時間でベンチマークを行ったところ、C版とほぼ同じ実行時間となった。どちらかというとC++の方がちょっと速いかもしれないが有意とはいえない程度の差。

ただし、どのように書いても同じというわけではなく C と C++ の違いに注意する必要はある。クラス Position のなかで Move next_moves_[200]; としている場所があるが、今度のC++コードでは Move はクラスであるのでコンストラクタが200回呼ばれることになる。このとき Move のコンストラクタとして次のように教科書通りのデフォルトコンストラクタを設定すると実行速度はC版が24秒程度だったのに対して、C++版が35秒程度と大幅に低下した。

実際に new Move[1000000] の実行時間を測ってみると、上記のコンストラクタだと 0.015192 s, 0.014435 s, 0.015185 s と1つあたり0.015 μsかかる。一方、デフォルトコンストラクタ Move::Move() {} だと、0.000804 s, 0.000762 s, 0.000467 s となる。new の数を1桁増やすと上記のコンストラクタの実行時間は1桁増えるが、後者は変わらないので、デフォルトコンストラクタは実際にはなにもせず、この実行時間はメモリ確保のための時間だろう。

そもそも Move next_moves_[200]; という固定配列はよくない。これを STL の vector にする。すると実行時間は27秒前後と3秒程度低下する。このスピード低下を STL を使うからと短絡的に考える前に原因を探ってみよう。DoMoveのなかで、 *dst = *this; としている部分が怪しい。この処理の中でnext_moves_も当然コピーされるが、それは重い。しかし次の盤面では next_moves_.clear() を呼んでいることからも、 next_moves_ のコピーは不要である。is_under_attack_ のコピーも同様に不要。そこでこれらをコピーしないようにプログラムを修正する。

board_ のコピーは memcpyでも出来るがそれほど実行時間に差はでないので、ここでは普通の書き方にしている。実行時間を測定すると 17.166239 s, 16.805014 s, 16.713350 s おお、C++ 速い! いやいや、同じことが C でもできるよねと、やってみると 18.646168 s, 18.581420 s, 18.566730 s 。

アセンブル出力を見比べてみると、ところどころ微妙な差があるのが分かる。2次元配列にアクセスするコードが違っていて、C では以下の5命令になっている部分が、

C++ では4命令になっている。

is_under_attack の型が int から bool になったので、バイト数が4倍違うせいか。C の方の is_under_attack の型を char にしてみると同じになった。なぜ leaq (%rdi, %rax, 32), %rax とはならないのだろう。32倍はダメなんだっけ?

以上のことから class を使っても、C と C++ では実行時間に差が付かないと結論づけてよさそうである。遠慮無く C++ でコードを書くことにする。 むしろ、C++ の方が速いという計測結果がでているのが不思議。STLを使ってメモリ消費が少なくなった分かな?

あとは virtual 使ったらどうなるのかが残された疑問だけど、今回は使わなかったのでまた次回。

2012/05/10

C vs C++ for Chess

チェスプログラムを敢えてCで書いてきた。それの理由のひとつは、Shannonの論文中で関数F1, F2のように書かれていることからも、間違いなくオブジェクト指向では書いてないということがある。そもそもAlgol以前かな?しかしそれ以上に、Cの方がC++より速いという神話は本当か?という疑問を確認したいからという動機があった。

というわけで、単純な実験としてプログラムをCとC++でコンパイルしてみて実行速度を計測してみた。まだ、思考ルーチンは書いていないが、一番、ベースになる駒の動きの部分のスピードは常に効いてくるので、今の段階での比較には意味がある。

計測は、random player vs random playerの対戦(決着がつかないため99手で打ち切り)の1万回の実行をgettimeofdayによるwall clockの計測。 また乱数の引きの影響を受けないために最初にsrand(1)に固定している。

Cによる結果

C++による結果

有意な差はなさそう。むしろ、この結果だけをみたらgccよりg++の方が速い。

というわけでCの方が速いは迷信だろうというのが、ここでの結論。ただし、g++でコンパイルしただけで、C++らしいコードになっていないので、それについては、これから書きなおしてみて、再度、計測することにする。

2012/05/08

Castling, checkmate and random player

駒の動きの最後にキャスリングの実装。キャスリングは、キングを隅に移動することで守り、ルークを中央に出すという意味がある。キャスリングがないころのチェスでは1手ずつ順に駒を動かしてキャスリングと同じ事をしていた。しかし、双方とも、キャスリングをすることが多かったため、ゲームのスピードアップのために1手として実行できるようになった。ただし、強力な手のため以下の制約を満たさないと実行できない。

  1. キングが以前に動いていない。
  2. キャスリングをするサイドのルークが以前に動いていない。
  3. キングとルークの間に他の駒がない。
  4. キングが移動する経路が敵の駒によって攻撃されていない。

プログラムにすると以下のようになる。 ここで、is_under_attackは駒の動きの計算と同様にして計算される。

is_under_attackを使うと、キングが攻撃されているか、すなわちチェックされているかが判定できる。 チェスでは自分が行動したあとで自分のキングがチェックされる手は無効である。 将棋では負けだけど、チェスだとその手は無効ですよと相手にやり直しを求めることになる。

もし、自分の手の前からチェックされていて、自分の手のあとでチェックにならない盤面になる手がないのであれば、それはチェックメイトといい負けとなる。 一方、自分の手の前にはチェクされていなかったのに、自分の手のあとではチェックになる盤面しかないのであれば、それはステールメイトといい引き分けとなる。

ここまででランダムな手で実行するチェスプログラムができた。全コードをGistにあげておく。どこかにバグがまだ潜んでいてキャスリングできるはずなのにできないことがあったりします。

Shannonの論文では、ランダムプレイヤーは 1. f3 e5 2. g4 Qh4# とfool's mateで負けると書いてあるが、さすがにその手を打ってくれることは滅多になく、最後にチェックだけはかわしてくるので、それなりに楽しめます。

2012/05/05

Knight, Bishop, Rook, Queen, King Move

前回のポーンに続いて残りの駒の動き。ポーン以外の駒はキャスリングを除いて状況に応じて動きが変わることもなく、実装は比較的単純。実際、将棋と異なり、どちらを向いているかという白と黒の差もない。
指定された位置へ移動できるかをチェックして可能なら追加するユーティリティ関数を用意。指定された位置が空白なら返り値は1、それ以外なら0。 ナイトの移動はL字型に8方向。 ビショップは斜めに敵の駒にぶつかるまで。 ルークは十字。 クイーンはビショップとルークの両方を合わせた8方向を動ける最強の駒。BIshopのための関数とRookのための関数を呼べばいいよねというのはShannonの論文に書いてあるアイデア。 キングは8方向に1つだけ動ける。キャスリングという特殊な動きがあるけど、いまはまだ実装するための道具が足りないので後回し。

2012/05/04

Pawn move

まずはポーンから動きを実装していく。まずはというものの実際にはポーンがもっとも複雑な動きをする駒で実装も複雑。

1. 1つ前に進む。ただし、1つ前に敵味方関係なく他の駒があるときは進めない。ポーン以外の他の駒は敵の駒を取りつつ進めるがポーンだけは敵の駒を取るときの動きが異なる。

...
...
.P.
...
.P.
...

AddPawnMovesはあとで解説。

2. 最初の位置(白だと2列目、黒だと7列目)からだと2つ一度に進める。これはゲームの序盤で同じポーンを2手連続で前に進めることがよくあるので、ゲームの進行を早めるために導入された。したがって1つ前の駒を飛び越えれるわけではなく、1つ前と2つ前が共に開いていることが条件。

....P...
........
PPPP.PPP
RNBQKBNR

3. 斜め前に敵の駒があるときは、それを取って進むことができる。このときポーンの前に他の駒があっても構わない。

...
.pq
.P.
...
.pP
...

4. ポーンは最初の位置からだと2つ進めるけど、そのときに斜め前に敵のポーンがいたら2つ進む前に敵は取ることができるよねというわけで、その取り方をアンパッサンという。これはポーンが2つ前進した直後の手でしか有効ではないので最後の手を見て判断。

..p..
.....
PPPPP
QKBNR
.Pp..
.....
P.PPP
QKBNR
.....
.p...
P.PPP
QKBNR

Pawnは前にしか進めないので、盤の端に到達すると、そのままだと動けなくなってしまう。端まで到達したポーンは必ずポーンとキング以外の他の種類の駒つまり、ナイト、ルーク、ビショップ、クイーンにpromoteしないといけない。「必ず」というのはステールメイトが関わっていて、ポーンが動ける限りステールメイトにならないところが、端のポーンがpromoteしないとステールメイトになりやすくなってしまう。また、クイーン以外にpromoteするというのも、ごくごく稀にクイーンにpromteするとステールメイトという盤面がある程度。普通はpromoteはクイーンにと思ってもいい。

2012/04/13

Blogger parts: Chess Board (cont.)

暗い背景の上に白いチェスの駒を表示すると、駒の本体のあたりが透けて白く見えない問題があったが、「黒の駒を白く表示した上に、白の駒を表示すればいいんだよ」というすごいアイデアを見つけた。 さっそくやってみる。
おお!ついでに、黒の駒の後ろにも白で描画された白の駒を置いてあります。分かりにくいけどbishopの十字がちゃんと白くなっていたりね。

JavaScript / CSS は次のようになった。

こうなると、盤面は違う色でも構わないので変更。さらにマスが正方形になっていないのでサイズを調整。
r.bqkbnr
pppp.ppp
..n.....
.B..p...
....P...
.....N..
PPPP.PPP
RNBQK..R

Blogger parts: Chess Board

これまでチェス盤の表示はプログラムの出力のASCII出力のチェス盤をpreで貼っただけだった。こんな感じ。
r. bqkbnr
pppp. ppp
. . n. . . . .
. B. . p. . .
. . . . P. . .
. . . . . N. .
PPPP. PPP
RNBQK. . R
しかし、これだとみづらいので、これをclass="chessboard"をつけるだけで下図に変換するJavaScriptを書いた。 (2012/4/12 追記: スクリプトの変更前の画像に差し替え)
JavaScriptは次の通り。文字列をスキャンしてKとかnとかをチェスの駒へと変換し(unicodeにチェスの駒がある!)、全体をtableとして構成し、もとのエレメントと入れ替える。 Closure compilerにかけてscriptタグで囲んだものを、Bloggerのレイアウト > HTML/JavaScriptガジェットとして登録する。 次にCSSを設定。CSSはCSS3 Chess Boardを参考にさせてもらった。以下のCSSをテンプレート > アドバンス > CSSを追加 から設定。 unicodeに含まれるチェスの駒を使うと白い駒の白い部分が白くならないという問題があって(フォントは黒い部分を定義するものなので)、オリジナルのCSSだと市松模様にグラデーションをかけて白く見せている。どちらがいいかなと思ったんだけどグラデーションはなしにしている。

2012/03/28

Stalemate

現在の実装ではPawnを1歩づつしか進ませることしかできないので、32手で必ず手がなくなる。
32.
rnbqkbnr
. . . . . . . .
. . . p. . . .
pp. Ppppp
PPp. PPPP
. . P. . . . .
. . . . . . . .
RNBQKBNR
このとき引き分けとして1/2-1/2と表示して終了する。 ちなみに、上の例では白にも黒にも手がないので引き分けというのがしっくりくるが、このプログラムはどちらかが有効手がなくなった時点で引き分けにしている。リバーシなら相手にも手がないときに初めて引き分けだったが、Chessではこのプログラムであっていて、どちらかに有効手がなくなった時点でそれをstalemateとよび引き分けになる。 また将棋では、次のような場合は王には自殺する手しか残されてなく攻め手の勝ちとなる。
_|_|王|
_|飛|_|
_|金|_|
しかしChessではKingの自殺は有効手ではなく、パスもないため、そういう手しかなかった場合は有効手がないということで引き分けになる。 (Chessでは相手のKingは殺さない。殺してしまっては領民は従わない、生かして降伏させる方がよい。) 例えば、上とほぼ同じこういう局面(黒の番)はstalemateで引き分けである。
. . k
. R .
. Q .
圧倒的に不利な局面でもChessではstalemateに持込み引き分けにしてしまうことがままあるので攻め手は最後まで気が抜けない。 Stalemateの具体例はWikipediaが詳しい。http://en.wikipedia.org/wiki/Stalemate

2012/03/26

Shannon: Programming a computer for playing chess, 1950.

Shannonが1949年に書いた論文に"Programming a computer for playing chess"というChessをコンピュータに実行させるにはというものがある。
http://portal.acm.org/citation.cfm?id=61701.67002
ENIACができたのが1946年で、プログラム内蔵方式のEDVACが1949年から研究所に入ったという話なのでEDVACのテストして書いたプログラムの話かもしれない。

Shannonはこの論文で、MinMax手法、盤面評価、静止探索、機械学習の可能性といった現在でも基本となっている手法をすでに発表している。
彼の書いたプログラムがどんなものであったかは分からないが論文から再現してみるのは面白そうなので書いてみようと思った。
ただし、彼が用いたのがENIACだったとすると配線で、EDVACだったとするとマシン語であったろうが、それはハードルが高すぎるのでC++ではなくCという程度にする。

今回は全体の枠組みをとりあえず書いてみた。

#include 
#include 
#include 
#include 
#include 

typedefstruct {
  int from_x;
  int from_y;
  int to_x;
  int to_y;
  int piece;
} Move;

typedefstruct {
  int board[8][8];
  int side;
  
  // castling is available?
  int king_is_moved[2];
  int rook_is_moved[2][2];
  
  // last move
  Move last_move;
  
  // for 50 moves rule
  int moves_after_pawn;
  
  // next moves
  Move next_moves[8];  // 8 Pawns
  int num_moves;
} Position;

まだlast_moveやcastlingに関する情報は利用していないが、論文には必要だと書いてあったのでエントリーだけでも作っておく。
こういうところをみると実際に作ったなと感じる。

void InitPosition(Position*);
void CopyPosition(Position* dst, constPosition* src);
void PrintPosition(constPosition*);
void PrintMove(constMove*);
void DoMove(constPosition*, constMove*, Position*);
void CalcPawnMoves(Position*, int, int);
void CalcBishopMoves(Position*, int, int);
void CalcKnightMoves(Position*, int, int);
void CalcRookMoves(Position*, int, int);
void CalcQueenMoves(Position*, int, int);
void CalcKingMoves(Position*, int, int);
void CalcMoves(Position*);
void ReadMove(Move*);
int IsValidMove(constPosition*, constMove*);
int a2piece(char);
char piece2a(int);
void ThinkNextMove(Position*, Move*);

conststaticchar* PIECE_MARK = "kqrbnp.PNBRQK";

staticint init_board[] = {
  4, 2, 3, 5, 6, 3, 2, 4,
  1, 1, 1, 1, 1, 1, 1, 1,
  0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0,
  -1, -1, -1, -1, -1, -1, -1, -1,
  -4, -2, -3, -5, -6, -3, -2, -4
};

初期レイアウトも論文のままに、この数字が盤面評価のスコアとして使われる。

staticPosition position[200];  // how many is needed?

void InitPosition(Position* pos) {
  int x, y;
  int i = 0;
  for (y = 0; y < 8; ++y) {
    for (x = 0; x < 8; ++x) {
      pos->board[x][y] = init_board[i];
      ++i;
    }
  }
  pos->side = 1;
  for (int s = 0; s < 2; ++s) {
    pos->king_is_moved[s] = 0;
    pos->rook_is_moved[s][0] = 0;
    pos->rook_is_moved[s][1] = 0;
  }
  pos->last_move.from_x = -1;
  pos->last_move.from_y = -1;
  pos->last_move.to_x = -1;
  pos->last_move.to_y = -1;
  pos->moves_after_pawn = 0;
  
  pos->num_moves = 0;
}

void CopyPosition(Position* dst, constPosition* src) {
  memcpy(dst, src, sizeof(Position));
}

void PrintPosition(constPosition* pos) {
  for (int y = 7; y >= 0; --y) {
    for (int x = 0; x < 8; ++x) {
      printf("%c", piece2a(pos->board[x][y]));
    }
    printf("\n");
  }
  for (int i = 0; i < pos->num_moves; ++i) {
    constMove* m = &(pos->next_moves[i]);
    PrintMove(m);
  }
  printf("\n\n");
}

Cで書いてもC++っぽくなってしまう。

void PrintMove(constMove* move) {
  if (move->piece != 0) {
    printf("%c%d%c%d%c ", 'a' + move->from_x, move->from_y + 1, 'a' + move->to_x, move->to_y + 1, piece2a(move->piece));
  } else {
    printf("%c%d%c%d ", 'a' + move->from_x, move->from_y + 1, 'a' + move->to_x, move->to_y + 1);
  }  
}

void DoMove(constPosition* src, constMove* m, Position* dst) {
  assert(m->from_x >= 0 && m->from_x < 8);
  assert(m->from_y >= 0 && m->from_y < 8);
  
  CopyPosition(dst, src);
  int x = dst->board[m->from_x][m->from_y];
  dst->board[m->from_x][m->from_y] = 0;
  dst->board[m->to_x][m->to_y] = x;
  dst->side = -(dst->side);

}

// Calculates pawn moves.
// Assumes the piece of the given (x, y) is a pawn.
void CalcPawnMoves(Position* pos, int x, int y) {
  Move* m = &(pos->next_moves[pos->num_moves]);
  int to_y = y + pos->side;
  if (to_y >= 0 && to_y < 8) {
    if (pos->board[x][to_y] == 0) {
      m->from_x = x;
      m->from_y = y;
      m->to_x = x;
      m->to_y = to_y;
      ++(pos->num_moves);
    }
  }
}

とりあえずPawnが前に進むだけ。

void CalcBishopMoves(Position* pos, int x, int y) {
  
}

void CalcKnightMoves(Position* pos, int x, int y) {
  
}

void CalcRookMoves(Position* pos, int x, int y) {
  
}

void CalcQueenMoves(Position* pos, int x, int y) {
  
}

void CalcKingMoves(Position* pos, int x, int y) {
  
}

void CalcMoves(Position* pos) {
  for (int y = 0; y < 8; ++y) {
    for (int x = 0; x < 8; ++x) {
      switch (pos->board[x][y] * pos->side) {
        case1:
          CalcPawnMoves(pos, x, y);
          break;
          
        case2:
          CalcKnightMoves(pos, x, y);
          break;
          
        case3:
          CalcBishopMoves(pos, x, y);
          break;
          
        case4:
          CalcRookMoves(pos, x, y);
          break;
          
        case5:
          CalcQueenMoves(pos, x, y);
          break;
          
        case6:
          CalcKingMoves(pos, x, y);
          break;
          
        default:
          // error
          break;
      }
    }
  }
}

void ReadMove(Move* move) {
  printf("? ");
  move->from_x = -1;
  char buf[100];
  if (fgets(buf, 100, stdin)) {
    switch (strlen(buf)) {  // fgets includes \n.
      case5:
        move->from_x = buf[0] - 'a';
        move->from_y = buf[1] - '1';
        move->to_x = buf[2] - 'a';
        move->to_y = buf[3] - '1';
        move->piece = 0;
        break;
        
      case6:
        move->from_x = buf[0] - 'a';
        move->from_y = buf[1] - '1';
        move->to_x = buf[2] - 'a';
        move->to_y = buf[3] - '1';
        move->piece = a2piece(buf[4]);
        break;
        
      default:
        // error!
        break;
    }
  }
}

int IsValidMove(constPosition* pos, constMove* m) {
  constMove* next_move = pos->next_moves;
  for (int i = 0; i < pos->num_moves; ++i) {
    if (next_move->from_x == m->from_x &&
        next_move->from_y == m->from_y &&
        next_move->to_x == m->to_x &&
        next_move->to_y == m->to_y &&
        next_move->piece == m->piece) {
      return1;
    }
    ++next_move;
  }
  return0;
}

generateしたmovesに入っていればvalidということにする。

int a2piece(char a) {
  switch (a) {
    case'p':
      return1;
      
    case'n':
      return2;
      
    case'b':
      return3;
      
    case'r':
      return4;
      
    case'q':
      return5;
      
    case'k':
      return6;
      
    default:
      // error!
      break;
  }
  return0;
}

char piece2a(int p) {
  if (p >= -6 && p <= 6) {
    returnPIECE_MARK[p + 6];
  }
  return' ';
}

void ThinkNextMove(Position* pos, Move* move) {
  int i = rand() % pos->num_moves;
  move->from_x = pos->next_moves[i].from_x;
  move->from_y = pos->next_moves[i].from_y;
  move->to_x = pos->next_moves[i].to_x;
  move->to_y = pos->next_moves[i].to_y;
  move->piece = pos->next_moves[i].piece;
}

コンピュータはとりあえずランダムに。論文にはランダムはとても弱いと書いてある。まあ、fool's mateが決まるよね、ランダムじゃ。

int main(int argc, char* argv[]) {
  srand((unsigned)time(NULL));
  
  InitPosition(&position[0]);
  int ply = 0;
  while (1) {
    Position* pos = &position[ply];
    CalcMoves(pos);
    PrintPosition(pos);
    Move move;
    if (ply % 2 == 0) {
      // player move
      move.from_x = -1;
      while (move.from_x == -1) {
        ReadMove(&move);
        if (!IsValidMove(pos, &move)) {
          move.from_x = -1;
        }
      }
    } else {
      ThinkNextMove(pos, &move);
      printf("-> ");
      PrintMove(&move);
      printf("\n\n");
    }
    DoMove(&position[ply], &move, &position[ply+1]);
    ++ply;
  }
  return0;
}