Pythonで三目並べを学習させる(AIプログラミング 第2回)
はじめに
前回、「考えるマッチ箱」というコンピュータを使わずに三目並べを学習する方法を学びました。
今回はその学習手順をPythonでプログラミングしていきます。
ソースコード
import random
import csv
# csvファイルを読み込む
with open("matchbox.csv", "r", encoding="Shift_jis") as f:
reader = list(csv.reader(f))
print("マッチ箱の数:", len(reader))
# 繰り返し処理(試行回数)
for step in range(1000):
# 盤面
board_1 = [0,0,0,0,0,0,0,0,0]
# 1手目(ランダム)==========
sente = random.randrange(9)
board_1[sente] = 1
print("1手目碁盤:", board_1)
# 2手目(コンピュータ)==========
box1 = -1
for i in range(len(reader)):
# 文字リストを数値に変換
reader[i] = [int(j) for j in reader[i]]
# 同じマッチ箱があるか探す
if board_1[0:9] == reader[i][0:9]:
box1 = i
print("マッチ箱が見つかりました(2手目)",box1)
break
# マッチ箱が見つからなかった場合
if box1 == -1:
print("マッチ箱の数2:", len(reader))
box1 = len(reader)
# リストを結合
board_1.extend([0,0,0,0,0,0,0,0,0])
# ビーズを用意
for i in range(9):
if board_1[i] == 0:
board_1[i+9] = 1
# マッチ箱を追加
reader.append(board_1[:])
board_2 = reader[box1][:]
n = sum(board_2[9:])
print("ビーズ数:", n)
n = random.randrange(n)
print("次の手(仮)", n)
for i in range(9):
if n - board_2[i+9] < 0:
# 次の一手
next_com1 = i
board_2[i] = -1
board_2[i+9] = 0
break
n = n - board_2[i+9]
print("次の一手", next_com1)
print("2手目碁盤:", board_2)
# 3手目(ランダム)==========
board_3 = board_2[:]
sente = random.randrange(7)
print("次の手(仮)", sente)
for i in range(9):
if board_3[i] == 0:
if sente == 0:
# 次の一手
next_move = i
board_3[i] = 1
board_3[i+9] = 0
break
sente -= 1
print("次の一手", next_move)
print("3手目碁盤:", board_3)
# 4手目(コンピュータ)==========
box2 = -1
for i in range(len(reader)):
# 文字リストを数値に変換
reader[i] = [int(j) for j in reader[i]]
# 同じマッチ箱があるか探す
if board_3[0:9] == reader[i][0:9]:
box2 = i
print("===マッチ箱が見つかりました===",box2)
break
# マッチ箱が見つからなかった場合
if box2 == -1:
print("マッチ箱の数4:", len(reader))
box2 = len(reader)
# ビーズを初期化(これが大事)
for i in range(9):
if board_3[i] == 0:
board_3[i+9] = 1
# マッチ箱を追加
reader.append(board_3[:])
print("マッチ箱/ビーズ(4)", reader[box2])
board_4 = reader[box2][:]
n = sum(board_4[9:])
print("ビーズ数(4):", n)
if n == 0:
print("打つ手がありません!(4手目)")
# 2手目がダメだったで、2手目は打たないようにする
print("2手目の「×」の手は?")
print(board_2)
print("前の一手", next_com1)
# 1手前に戻してこの手は打たないようにする
reader[box1][next_com1+9] = 0
print(reader[box1])
print(board_4[:3])
print(board_4[3:6])
print(board_4[6:9])
continue
n = random.randrange(n)
print("次の手(仮)4", n)
for i in range(9):
if n - board_4[i+9] < 0:
# 次の一手
next_com2 = i
board_4[i] = -1
board_4[i+9] = 0
break
n = n - board_4[i+9]
print("次の一手(4)", next_com2)
print("4手目碁盤:", board_4)
# 5手目(ランダム)==========
board_5 = board_4[:]
sente = random.randrange(5)
print("次の手(仮)", sente)
for i in range(9):
if board_5[i] == 0:
if sente == 0:
# 次の一手
next_move = i
board_5[i] = 1
board_5[i+9] = 0
break
sente -= 1
print("次の一手", next_move)
print("5手目碁盤:", board_5)
#「〇」の勝ちを確認
print("4手目碁盤:", board_4[:9])
if (board_5[0] == 1 and board_5[1] == 1 and board_5[2] == 1) \
or (board_5[3] == 1 and board_5[4] == 1 and board_5[5] == 1) \
or (board_5[6] == 1 and board_5[7] == 1 and board_5[8] == 1) \
or (board_5[0] == 1 and board_5[3] == 1 and board_5[6] == 1) \
or (board_5[1] == 1 and board_5[4] == 1 and board_5[7] == 1) \
or (board_5[2] == 1 and board_5[5] == 1 and board_5[8] == 1) \
or (board_5[0] == 1 and board_5[4] == 1 and board_5[8] == 1) \
or (board_5[2] == 1 and board_5[4] == 1 and board_5[6] == 1):
print("「〇」の勝ちです")
print(board_5[:3])
print(board_5[3:6])
print(board_5[6:9])
# ここに負けた時の処理
print("1手前の「×」の手は?")
print(board_4)
print("前の一手", next_com2)
# 1手前に戻してこの手は打たないようにする
reader[box2][next_com2] = 0
reader[box2][next_com2+9] = 0
print(reader[box2])
#break
continue
# 6手目(コンピュータ)==========
box3 = -1
for i in range(len(reader)):
# 文字リストを数値に変換
reader[i] = [int(j) for j in reader[i]]
# 同じマッチ箱があるか探す
if board_5[0:9] == reader[i][0:9]:
box3 = i
print("===マッチ箱が見つかりました===",box3)
break
# マッチ箱が見つからなかった場合
if box3 == -1:
print("マッチ箱の数6:", len(reader))
box3 = len(reader)
# ビーズを初期化(これが大事)
for i in range(9):
if board_5[i] == 0:
board_5[i+9] = 1
# マッチ箱を追加
reader.append(board_5[:])
print("マッチ箱/ビーズ", reader[box3])
board_6 = reader[box3][:]
n = sum(board_6[9:])
print("ビーズ数:", n)
if n == 0:
print("打つ手がありません!(6手目)")
# 4手目がダメだったで、4手目は打たないようにする
print("4手目の「×」の手は?")
print(board_4)
print("前の一手", next_com2)
# 1手前に戻してこの手は打たないようにする
reader[box2][next_com2] = 0
reader[box2][next_com2+9] = 0
print(reader[box2])
print(board_6[:3])
print(board_6[3:6])
print(board_6[6:9])
continue
n = random.randrange(n)
print("次の手(仮)", n)
for i in range(9):
if n - board_6[i+9] < 0:
# 次の一手
next_com3 = i
board_6[i] = -1
board_6[i+9] = 0
break
n = n - board_6[i+9]
print("次の一手", next_com3)
print("6手目碁盤:", board_6)
#「×」の勝ちを確認
print("5手目碁盤:", board_5[:9])
if (board_6[0] == -1 and board_6[1] == -1 and board_6[2] == -1) \
or (board_6[3] == -1 and board_6[4] == -1 and board_6[5] == -1) \
or (board_6[6] == -1 and board_6[7] == -1 and board_6[8] == -1) \
or (board_6[0] == -1 and board_6[3] == -1 and board_6[6] == -1) \
or (board_6[1] == -1 and board_6[4] == -1 and board_6[7] == -1) \
or (board_6[2] == -1 and board_6[5] == -1 and board_6[8] == -1) \
or (board_6[0] == -1 and board_6[4] == -1 and board_6[8] == -1) \
or (board_6[2] == -1 and board_6[4] == -1 and board_6[6] == -1):
print("「×」の勝ちです")
print(board_6[:3])
print(board_6[3:6])
print(board_6[6:9])
# ここに勝った時の処理
continue
# 7手目(ランダム)==========
board_7 = board_6[:]
sente = random.randrange(3)
print("次の手(仮)", sente)
for i in range(9):
if board_7[i] == 0:
if sente == 0:
# 次の一手
next_move = i
board_7[i] = 1
board_7[i+9] = 0
break
sente -= 1
print("次の一手", next_move)
print("7手目碁盤:", board_7)
#「〇」の勝ちを確認
print("6手目碁盤:", board_6[:9])
if (board_7[0] == 1 and board_7[1] == 1 and board_7[2] == 1) \
or (board_7[3] == 1 and board_7[4] == 1 and board_7[5] == 1) \
or (board_7[6] == 1 and board_7[7] == 1 and board_7[8] == 1) \
or (board_7[0] == 1 and board_7[3] == 1 and board_7[6] == 1) \
or (board_7[1] == 1 and board_7[4] == 1 and board_7[7] == 1) \
or (board_7[2] == 1 and board_7[5] == 1 and board_7[8] == 1) \
or (board_7[0] == 1 and board_7[4] == 1 and board_7[8] == 1) \
or (board_7[2] == 1 and board_7[4] == 1 and board_7[6] == 1):
print("「〇」の勝ちです")
print(board_7[:3])
print(board_7[3:6])
print(board_7[6:9])
# ここに負けた時の処理
print("1手前の「×」の手は?")
print(board_6)
print("前の一手", next_com3)
# 1手前に戻してこの手は打たないようにする
reader[box3][next_com3+9] = 0
print(reader[box3])
#break
continue
# 8手目(コンピュータ)
box4 = -1
for i in range(len(reader)):
# 文字リストを数値に変換
reader[i] = [int(j) for j in reader[i]]
# 同じマッチ箱があるか探す
if board_7[0:9] == reader[i][0:9]:
box4 = i
print("===マッチ箱が見つかりました===",box4)
break
# マッチ箱が見つからなかった場合
if box4 == -1:
print("マッチ箱の数8:", len(reader))
box4 = len(reader)
# ビーズを初期化(これが大事)
for i in range(9):
if board_7[i] == 0:
board_7[i+9] = 1
# マッチ箱を追加
reader.append(board_7[:])
print("マッチ箱/ビーズ", reader[box4])
board_8 = reader[box4][:]
n = sum(board_8[9:])
print("ビーズ数:", n)
if n == 0:
print("打つ手がありません!(8手目)")
# 6手目がダメだったで、6手目は打たないようにする
print("6手目の「×」の手は?")
print(board_6)
print("前の一手", next_com3)
# 1手前に戻してこの手は打たないようにする
reader[box3][next_com3] = 0
reader[box3][next_com3+9] = 0
print(reader[box3])
print(board_8[:3])
print(board_8[3:6])
print(board_8[6:9])
continue
n = random.randrange(n)
print("次の手(仮)", n)
for i in range(9):
if n - board_8[i+9] < 0:
# 次の一手
next_com4 = i
board_8[i] = -1
board_8[i+9] = 0
break
n = n - board_8[i+9]
print("次の一手", next_com4)
print("8手目碁盤:", board_8)
#「×」の勝ちを確認
print("7手目碁盤:", board_7)
if (board_8[0] == -1 and board_8[1] == -1 and board_8[2] == -1) \
or (board_8[3] == -1 and board_8[4] == -1 and board_8[5] == -1) \
or (board_8[6] == -1 and board_8[7] == -1 and board_8[8] == -1) \
or (board_8[0] == -1 and board_8[3] == -1 and board_8[6] == -1) \
or (board_8[1] == -1 and board_8[4] == -1 and board_8[7] == -1) \
or (board_8[2] == -1 and board_8[5] == -1 and board_8[8] == -1) \
or (board_8[0] == -1 and board_8[4] == -1 and board_8[8] == -1) \
or (board_8[2] == -1 and board_8[4] == -1 and board_8[6] == -1):
print("「×」の勝ちです")
print(board_8[:3])
print(board_8[3:6])
print(board_8[6:9])
# ここに勝った時の処理
continue
# 9手目(ランダム)
board_9 = board_8[:]
sente = random.randrange(1)
print("次の手(仮)", sente)
for i in range(9):
if board_9[i] == 0:
if sente == 0:
# 次の一手
next_move = i
board_9[i] = 1
board_9[i+9] = 0
break
sente -= 1
print("次の一手", next_move)
print("9手目碁盤:", board_9)
#「〇」の勝ちを確認
print("8手目碁盤:", board_8[:9])
if (board_9[0] == 1 and board_9[1] == 1 and board_9[2] == 1) \
or (board_9[3] == 1 and board_9[4] == 1 and board_9[5] == 1) \
or (board_9[6] == 1 and board_9[7] == 1 and board_9[8] == 1) \
or (board_9[0] == 1 and board_9[3] == 1 and board_9[6] == 1) \
or (board_9[1] == 1 and board_9[4] == 1 and board_9[7] == 1) \
or (board_9[2] == 1 and board_9[5] == 1 and board_9[8] == 1) \
or (board_9[0] == 1 and board_9[4] == 1 and board_9[8] == 1) \
or (board_9[2] == 1 and board_9[4] == 1 and board_9[6] == 1):
print("「〇」の勝ちです")
print(board_9[:3])
print(board_9[3:6])
print(board_9[6:9])
# ここに負けた時の処理
print("1手前の「×」の手は?")
print(board_8)
print("前の一手", next_com4)
# 1手前に戻してこの手は打たないようにする
reader[box4][next_com4] = 0
reader[box4][next_com4+9] = 0
print(reader[box4])
#break
continue
print("引き分けです")
print(board_9[:3])
print(board_9[3:6])
print(board_9[6:9])
# ここに引き分けの時の処理
# csvファイルに書き込み
with open("matchbox.csv", "w", encoding="Shift_jis") as f: # 文字コードをShift_JISに指定
writer = csv.writer(f, lineterminator="\n") # writerオブジェクトの作成 改行記号で行を区切る
writer.writerows(reader) # csvファイルに書き込み
縦長で一直線の超初心者のプログラムです。(突っ込みどころ満載なのはご了承ください)
解説(苦労した点)
まず「matchbox.csv」の空のファイルを作っておきます。この「matchbox.csv」が今回のプログラムの頭脳の部分になります。
使用するモジュールは、乱数を出すための「random」とcsvファイルを読み書きするための「csv」のみです。
reader[i] = [int(j) for j in reader[i]]
csvファイルを読み込んだ時に文字列になるので、数値に変換します。
board_2 = reader[box1][:]
最後の「[:]」の部分が必要です。これがないと、リスト変数「board_2」を変更するとリスト変数「reader」も連携して勝手に書き換わってしまいます。
「[:]」を付けることで、「board_2」はコピーになるので変更されても「reader」の値は変わりません。
4手目、6手目、8手目の「マッチ箱が見つからなかった場合のビーズの初期化」
この部分がなくてずっと上手くいかなくて悩みました。
必要な理由は「2手目で打ってはいけない手(無いビーズの色)は4手目では打てる手にもなる」ということです。
例えば、
1┃✖┃3
━✚━✚━
4┃〇┃6
━✚━✚━
7┃8┃9
になると、後手は必ず負ける手が先手にあるので2手目では「✖」の位置のビーズがなくなります。すると、
✖┃2┃3
━✚━✚━
4┃〇┃6
━✚━✚━
7┃〇┃9
4手目のビーズを初期化せずに2手目とビーズが入ったマッチ箱を4手目にも用意してしまうと、この盤面の時でも4手目のマッチ箱に2番の位置の色のビーズがないので2番に打たなくなってしまいます。
打つ手がない(マッチ箱にビーズがない)の時の1手前の手は打たないようにする
ここはもっと良い方法があると思うのですが、1手ごとの盤面「board_1~9」とその盤面を記録している場所「box1~4」そして1手ごとに打った手「next_com1~4」の変数を作ることで対応しました。(変数の名前の付け方にセンスがないと感じます)
勝ち負けの確認方法
これももっと良い方法があると思います。タテヨコ斜めの8通り全てを調べているだけです。
勝負が付いたときのfor文の処理
「break」だとfor文自体が終わってしまうので「continue」を使っています。もし負けた時だけ処理を終了させたいなどあれば、処理を終了したいところを「break」に変えてください。例えば、何度か学習させてみて「そろそろ負けることはないかな」と確認するのに使えます。
まとめ
1000回のループ処理をしていますが、for文のループ回数を増やしてもいいです。何万回か繰り返すとほぼ負けることがなくなりました。(2097個のマッチ箱が出来上がりました)頭脳の部分の「matchbox.csv」が増えていくと学習している実感がわきました。
今回は勝った時に報酬を与えていないので、ここに打ったら勝てるという盤面でも勝つ手を打ってくれないことも多そうです。(負けない手を打ちます)
次回は、先手をランダムから選んで打てるように変更して、学習したマッチ箱と勝負してみたいと思います。