『囲碁ディープラーニングプログラミング』を引き続き読んでいる。
肝心のディープラーニングの内容についての部分まで、なかなか到達しそうにない(^_^;
今回は、「三目並べ」である。
囲碁AIが次の一手を決めるとき、妥当な次の一手のリストを作成し、そのリストのそれぞれについて、相手がどう打つか、それに対して自分がどうするか、先まで読んで、その一手についての結論を出す。
その検討を次の一手のリスト全てについておこなう。その結果で次の一手を決定する。そのアルゴリズムがこれである。
ただ、これはこのゲームだからできるので、このアルゴリズムは時間がかかりすぎる。
さて、このアルゴリズム minimax探索の根幹はこのbest_result 関数である。
この関数では再帰処理を使っている。
<A>の部分が終了条件である。
game_stateが is_over() であるとき、
winner() が next_player すなわち 自分であるとき、GameResult.win が返る。
winner() が None すなわち 勝者がいないときは、GameResult.draw が返る。
その他は、GameResult.loss が返る。
def best_result(game_state):
# <A> 終了条件
if game_state.is_over():
if game_state.winner() == game_state.next_player:
return GameResult.win
elif game_state.winner() is None:
return GameResult.draw
else:
return GameResult.loss
# <B> 次の手を求める処理
best_result_so_far = GameResult.loss # <1>
opponent = game_state.next_player.other # <2>
for candidate_move in game_state.legal_moves(): # <3>
next_state = game_state.apply_move(candidate_move) # <4>
opponent_best_result = best_result(next_state) # <5>
our_result = reverse_game_result(opponent_best_result) # <6>
if our_result.value > best_result_so_far.value: # <7>
best_result_so_far = our_result # <8>
return best_result_so_far # <9>
# best_result_so_far -- これまでの最高の結果
<B>が次の手を求める処理で、再帰処理をしながら最終的に <A> の終了条件に
いきつく。
<1> -- best_result_so_far に GameResult.loss をセットしておく。
<2> -- opponent を相手とする。
<3> -- game_state.legal_move() -- 妥当と思われる次の手のリストを返す。
candidate_move -- そのリストを1つひとつ candidate_move として
検討する。
<4> -- next_state -- candidate_move すなわち、検討すべき次の手を適用し
た局面。
<5> -- その next_state を引数として、この best_result を実行する。
例えば以下のような局面があったとする。
A B C
1|o| |
2|x|x|
3| | |
今、黒は B2 と打ったところである。
(1) B1(o)-(row=1,col=2)と打つ手から検討する。
A B C
1|o|o|
2|x|x|
3| | |
それに対して黒(x)は、C1、C2、A3、B3、C3 という手が考えられる。
(1-1) 黒(x)が C1(row=1,col=3) と打った場合
next_state.next_player == Player.black
A B C
1|o|o|x
2|x|x|
3| | |
(1-1-1) それに対して白(o)が C2(row=2,col=3) と打った場合
next_state.next_player == Player.white
A B C
1|o|o|x
2|x|x|o
3| | |
(1-1-1-1) それに対して黒(x)が A3(row=3,col=1) と打った場合
next_state.next_player === Player.black
opponent_best_result our_result
GameResult.win --> reverse --> GameResult.loss
A B C
1|o|o|x
2|x|x|o
3|x| |
(1-1-1-2) 次は黒(x)が B3(row=3,col=2) と打った場合
next_state.next_player === Player.black
A B C
1|o|o|x
2|x|x|o
3| |x|
(1-1-1-2-1) それに対して白(o)が A3(row=1,col=2) と打った場合
next_state.next_player === Player.white
opponent_best_result our_result
GameResult.draw --> reverse --> GameResult.draw
A B C
1|o|o|x
2|x|x|o
3|o|x|
他にもいろいろな結果が考えられるが、
(1-1)のように、黒(x)が C1(row=1,col=3) と打った場合には、
GameResult.loss と GameResult.draw の2種類の結果が得られる。
黒(x)も best_result関数を使って GameResult.win の手を探し出す。
(1)の白(o)B1 という手については、黒が C3 (GameResult.win) という手を見つけだす。だから、白(o)B1 という手は、GameResult.loss という値を返すことになる。
白(o)は、B1, C1, C2, A3, B3, C3 と、順に best_result関数を使って最善手を探す。B1 は GameResult.loss であったが、C1, A3, B3, C3 については、黒(x)が C2 と打てば GameResult.loss という値が返ってくるので、白(o)の C2 という手のみが GameResult.draw もしくは GameResult.winという値を返すことになる。