コンピュータに手を選択させる(2)

人間が3目並べをやって経験的に得られる優れていると思われる手の選択方法を導入してみましょう。このような作戦を経験的作戦(ヒューリスティック Heuristic)と呼びます。

経験的作戦@

経験的作戦@として

  1. 中心に置けるときには置く。
  2. 角に置けるときには置く。

を導入してみましょう。前回の戦略的戦術よりも優先度は低いので、戦略的戦術で決定されない場合に経験的作戦を適用することにします。

if ($b[4]==0) { # 中心に置けるならば置く
	return 4;
}

for ($i=0; $i<9; $i=$i+2) { # 角に置けるならば置く
	if ($i!=4 && $b[$i]==0) {
		return $i;
	}
}

とりあえず初手で中心には置くようになります。しかし、

のは問題なので改良しましょう。

経験的作戦A

経験的作戦@の代わりに経験的作戦Aとして「引ける直線が多いマスを優先する」を導入してみましょう。

最初の状態では、そのマスを通る直線の数は下図の状態になっています。最初の状態では中央のマスや角が優先されることがわかります。

3 2 3
2 4 2
3 2 3

相手がどこかのマスにコマを置いた場合、そのマスを通る直線は成立しないことになるので、直線の数はその分減ることになります。中央右のマスに相手が置いている盤面では、以下のようになります。

3 2 2
1 3 ×
3 2 2

盤面の評価を行うような関数を評価関数といいます。通常、盤面が変わるたびに評価をしなおします。

直線の数を数えて配列@hに格納するサブルーチン

「×が入っていないラインに属するマスは直線の数を一つ増やす」を全てのラインで行えば数えることができます。

プログラムは以下のようになります。勝敗チェックルーチンと似てますね。

# 直線の数を数えて配列@hに格納(配列@hは大域変数)
sub count_line { 
	my $i;

	for ( $i=0;$i<9;$i++ ) { # 配列@hの初期化。
		$h[$i]=0
	}
	for ( $i=0; $i<3; $i++ ) { # 横ラインチェック
		if ( $b[$i*3+0]!=-1 && $b[$i*3+1]!=-1 && $b[$i*3+2]!=-1 ) {
			$h[$i*3+0]++;
			$h[$i*3+1]++;
			$h[$i*3+2]++;
		}
	}
	for ( $i=0; $i<3; $i++ ) { # 縦ラインチェック
		if ( $b[0+$i]!=-1 && $b[3+$i]!=-1 && $b[6+$i]!=-1 ) {
			$h[0+$i]++;
			$h[3+$i]++;
			$h[6+$i]++;
		}
	}
	if ( $b[0]!=-1 && $b[4]!=-1 && $b[8]!=-1 ) { # 斜めチェック
		$h[0]++;
		$h[4]++;
		$h[8]++;
	}
	if ( $b[2]!=-1 && $b[4]!=-1 && $b[6]!=-1 ) { # 斜めチェック
		$h[2]++;
		$h[4]++;
		$h[6]++;
	}
}

手の選択サブルーチンの変更

経験的作戦@のプログラムの部分を以下に置き換えます。

my $best; # 最も良い結果の得られるマスを格納
my $hi=0; # 最も良い結果の値を格納

&count_line; # 直線を数える

for ($i=0; $i<9; $i++) { # すべてのマスを調べる
	if ($b[$i]==0 && $h[$i]>$hi) { # 空きマスでかつ最も良い値ならば
		$best=$i; # 最も良い結果のマスを更新
		$hi=$h[$i]; # 最も良い結果の値を更新
	}
}
return $best; # 最も良い結果のマスを返す

このプログラムでは必ずreturn文を実行してサブルーチンを抜けてしまうので、この下に続けてプログラムを書いてもその部分は実行されないことになります。