前のブログエントリーで、kouryo()関数になっていたところです。
なぜコンピュータは計算しかできないのにあんなに強い将棋を指せるのか、
という問いに対する簡単な答えを提供できたらな、と思います。
まず思考ルーチンには二つの関数が必要です。
一つは与えられた盤面に対して先手、あるいは後手が動かすことのできる全ての手(可能手といいます)を列挙する関数です。ここではkanoshu()関数という名前とします。
もう一つは与えられた盤面がどの程度先手にとっていいかを数値化してくれる関数(評価関数といいます)です。hyouka()関数という名前だとします。
本当は評価関数としてhabu()みたいな、羽生先生が見て点数を付けたような評価関数が
欲しいところですがw いまのところ無理なので、「ある程度」確からしい評価関数を使う
ことにします。
実は、コンピュータのすごいところは、この評価関数がそれほど正確でなくても何手も
深く読んで強い一手を指せるところにあります。
思考ルーチンの関数kouryo()は、おおむね次のような処理をします。
ここで、kouryo()は、選んだ手(整数)と、その評価値(整数)を返すことに変更します。C言語で書くと書きづらいですが、こんな感じです。
int te;
int hyouka = kouryo(&te, 0);
こうすると、teには選んだ手の整数が、hyoukaには評価値が入ります。
では処理を見て行きましょう!
- 自分が先手なら盤面で先手の指せる手、後手なら後手の指せる手を、まずは全部列挙します。初手で30手くらいでしたでしょうか
- 全ての手の評価値を求めます。これはすべての手について、盤面を一手進めた盤面を作って、kouryo()を計算します。で、評価値はkouryo()の値をマイナス符号を付けた値(-1を掛けた値)になります
- 全ての手の評価値の中で、一番大きい値を持つ手を選んでteとして返します。この手の評価値も一緒に返してあげます
鋭い人は「kouryoがkouryoを呼んで、またkouryoを呼んで、、、終わらなくない?」という問いかけをすると思います。確かに今の仕様ではそうなってしまいます。
それを避けるために、呼び出す深さがN(決められた数値、例えば3とか)になったら、kouryo()の代わりに、hyouka()という、暫定の評価値を返す関数を呼ぶ事にします。
これが最初二つ用意した関数のうちの2番目の評価関数というものです。
C言語で書いてみましょう。
int kouryo(int *te, int fukasa)
{
int kazu;
int hairetsu[1024];
kazu = kanoushu(hairetsu); // hairetsu[0] から hairetsu[kazu-1]に手が入る
if (fukasa > 3) { return hyouka();}
int i;
int ban = -1000000; // とても小さい数
int ret = 0;
for ( i = 0 ; i < kazu ; i++) {
int te, te2;
te = hairetsu[i];
sasu(te);
int a = - kouryo( &te2, fukasa + 1);
if ( a > ban) { // 暫定の最大値とその手を計算する
ban = a;
ret = te;
}
modosu(te);
}
*te = ret;
return ban;
}
hairetsuとは、箱が横にいっぱいならんでいて、hairetsu[10]とすると10個目の箱の値を取り出せる仕組みです。
for文は、ここでは0からkazu-1まで、iを増やしながら {}を繰り返す、の意味です。
*teという記述があります。これはポインタというのですが、外から場所をもらい、
その場所に数字を書くという意味になります。結果として、kouryoの第一引数の指定
された変数(&が付いている)に、最善の手が入ります。
sasuは前に出てきました。一手進める手です。modosuはその反対の動作をする関数です。つまり一手もとに戻します。
実際にはkouryoはもっとたくさんの引数を必要とします。先手か、後手かなどです。
そして重要なことですが、この関数はかなり遅いです。実は処理をさぼっても同じ結果を得られる方法があります。これについては後日お話します。
まずは計算だけで将棋が指せることを実感してみてください!
ではねちゃお!
0 件のコメント:
コメントを投稿