Python FlaskとBrythonで三目並べの人工知能AIを作る(後編)
はじめに
前回の続きです。前編では2手目まで条件式で指示を与えていました。
3手目からはミニマックス法もどきで、コンピュータ側の全通りの手を調べて勝つ手があればそれを打ちます。次のプレイヤーのターンも全通り調べて、負ける手があればそこには打たないようにします。(深さが2層のミニマックス法です)
コンピュータを完璧にしてプレイヤーがまったく勝てないと面白くないと思うので(本当は邪魔くさい)ある条件の負ける手は打ってしまうように考えます。
3手目以降
- 「元の位置(self.koma_no)」から大きさ(self.koma)を調べる
- 「元の位置(self.koma_no)」候補は最大6つ
- 「移動先(self.masu_no)」候補は最大9つ
- 合計54パターンを総調べ
- 勝てる手があればその位置に打つ
- 次の相手の手まで読む
- 負ける手があればその手は打たない
- 勝負が決まらなければ・・・
- 負ける手しかなければ・・・
この流れで考えていきます。
先手・後手 共通
「元の位置(self.koma_no)」から大きさ(self.koma)を調べる
# 「元の位置(self.koma_no)」から大きさ(self.koma)を調べる for i in range(9): if str(self.board[i])[0] == "2": if document[str(i)].classList.contains("red_l"): self.koma_no = str(i) self.koma = "red_l" # 仮で打ってみる self.max_value() if self.masu_no != 9 and self.masu_no >= 0: return elif document[str(i)].classList.contains("red_m"): self.koma_no = str(i) self.koma = "red_m" # 仮で打ってみる self.max_value() if self.masu_no != 9 and self.masu_no >= 0: return elif document[str(i)].classList.contains("red_s"): self.koma_no = str(i) self.koma = "red_s" # 仮で打ってみる self.max_value() if self.masu_no != 9 and self.masu_no >= 0: return if document["box6"].classList.contains("red_l"): self.koma_no = "box6" self.koma = "red_l" # 仮で打ってみる self.max_value() if self.masu_no != 9 and self.masu_no >= 0: return if document["box5"].classList.contains("red_m"): self.koma_no = "box5" self.koma = "red_m" # 仮で打ってみる self.max_value() if self.masu_no != 9 and self.masu_no >= 0: return if document["box2"].classList.contains("red_m"): self.koma_no = "box2" self.koma = "red_m" # 仮で打ってみる self.max_value() if self.masu_no != 9 and self.masu_no >= 0: return if document["box4"].classList.contains("red_s"): self.koma_no = "box4" self.koma = "red_s" # 仮で打ってみる self.max_value() if self.masu_no != 9 and self.masu_no >= 0: return if document["box1"].classList.contains("red_s"): self.koma_no = "box1" self.koma = "red_s" # 仮で打ってみる self.max_value() if self.masu_no != 9 and self.masu_no >= 0: return
「元の位置(self.koma_no)」候補は最大6つ
「移動先(self.masu_no)」候補は最大9つ
合計54パターンを総調べ
for j in range(9): # 盤面をコピー self.board_2 = self.board[:] # 盤面を記録 if self.koma == "red_s": if self.board_2[j] < 1: self.board_2[j] += 2 if self.koma_no[0:3] != "box": self.board_2[int(self.koma_no)] -= 2 elif self.koma == "red_m": if self.board_2[j] < 10: self.board_2[j] += 20 if self.koma_no[0:3] != "box": self.board_2[int(self.koma_no)] -= 20 elif self.koma == "red_l": if self.board_2[j] < 100: self.board_2[j] += 200 if self.koma_no[0:3] != "box": self.board_2[int(self.koma_no)] -= 200
勝てる手があればその位置に打つ
# 「元の位置」の駒を消す document[self.koma_no].classList.remove(self.koma) # 駒を打つ document[str(self.masu_no)].classList.add(self.koma) # 盤面を記録 if self.koma == "red_s": self.board[self.masu_no] += 2 if self.koma_no[0:3] != "box": self.board[int(self.koma_no)] -= 2 elif self.koma == "red_m": self.board[self.masu_no] += 20 if self.koma_no[0:3] != "box": self.board[int(self.koma_no)] -= 20 elif self.koma == "red_l": self.board[self.masu_no] += 200 if self.koma_no[0:3] != "box": self.board[int(self.koma_no)] -= 200
次の相手の手まで読む
# 「元の位置(self.koma_no)」から大きさ(self.koma)を調べる if document["box9"].classList.contains("blue_l"): self.koma_no2 = "box9" self.koma2 = "blue_l" # 仮で打ってみる self.min_value2() if self.masu_no != 9: self.masu_no = -100 return if document["box12"].classList.contains("blue_l"): self.koma_no2 = "box12" self.koma2 = "blue_l" # 仮で打ってみる self.min_value2() if self.masu_no != 9: self.masu_no = -100 return if document["box8"].classList.contains("blue_m"): self.koma_no2 = "box8" self.koma2 = "blue_m" # 仮で打ってみる self.min_value2() if self.masu_no != 9: self.masu_no = -10 return if document["box11"].classList.contains("blue_m"): self.koma_no2 = "box11" self.koma2 = "blue_m" # 仮で打ってみる self.min_value2() if self.masu_no != 9: self.masu_no = -10 return if document["box7"].classList.contains("blue_s"): self.koma_no2 = "box7" self.koma2 = "blue_s" # 仮で打ってみる self.min_value2() if self.masu_no != 9: self.masu_no = -1 return if document["box10"].classList.contains("blue_s"): self.koma_no2 = "box10" self.koma2 = "blue_s" # 仮で打ってみる self.min_value2() if self.masu_no != 9: self.masu_no = -1 return ''' # ここをコメント解除するとコンピュータが強くなりすぎる for i in range(9): if str(self.board[i])[0] == "1": if document[str(i)].classList.contains("blue_l"): self.koma_no2 = str(i) self.koma2 = "blue_l" # 仮で打ってみる self.min_value2() if self.masu_no != 9: self.masu_no = -100 return elif document[str(i)].classList.contains("blue_m"): self.koma_no2 = str(i) self.koma2 = "blue_m" # 仮で打ってみる self.min_value2() if self.masu_no != 9: self.masu_no = -10 return elif document[str(i)].classList.contains("blue_s"): self.koma_no2 = str(i) self.koma2 = "blue_s" # 仮で打ってみる self.min_value2() if self.masu_no != 9: self.masu_no = -1 return '''
負ける手があればその手は打たない
# 赤が打てるか調べる if (self.koma == "red_l" and self.board[j] < 100) \ or (self.koma == "red_m" and self.board[j] < 10) \ or (self.koma == "red_s" and self.board[j] < 1): # 悪い手は打たない if self.koma_no == "4" and self.koma == "red_l" and j == 7: pass else: self.koma_no1 = self.koma_no self.koma1 = self.koma self.masu_no1 = j self.masu_no = 9 # 盤面をコピー self.board_1 = self.board_2[:] # 相手のターンへ self.min_value() # 次で負けなければそこに打つ if self.masu_no == 9: self.koma_no0 = self.koma_no1 self.koma0 = self.koma1 self.masu_no0 = self.masu_no1 elif self.masu_no1 < 9: # 負ける手しかない場合用 self.koma_no3 = self.koma_no1 self.koma3 = self.koma1 self.masu_no3 = self.masu_no1 self.board_3 = self.board_2[:]
勝負が決まらなければ・・・
負ける手しかなければ・・・
if self.masu_no == 9: # 勝負が決まらない場合 self.koma_no = self.koma_no0 self.koma = self.koma0 self.masu_no = self.masu_no0 document[self.koma_no].classList.remove(self.koma) timer.set_timeout(self.AI_set, 100) elif self.masu_no >= 0: # 勝てる手がある場合 document[self.koma_no].classList.remove(self.koma) timer.set_timeout(self.AI_set, 100) elif self.masu_no < 0 and self.masu_no0 == 9: # 負ける手しかない場合 self.koma_no = self.koma_no3 self.koma = self.koma3 self.masu_no = self.masu_no3 document[self.koma_no].classList.remove(self.koma) timer.set_timeout(self.AI_set, 100) elif self.masu_no < 0 and self.masu_no0 >= 0: # 負ける手がある場合 self.koma_no = self.koma_no0 self.koma = self.koma0 self.masu_no = self.masu_no0 document[self.koma_no].classList.remove(self.koma) timer.set_timeout(self.AI_set, 100) else: alert("これが出たらバグ!else:") alert(self.koma_no + " , " + self.koma + " , " + str(self.masu_no)) alert(self.koma_no0 + " , " + self.koma0 + " , " + str(self.masu_no0))
まとめ
それなりに強いコンピュータが出来上がりました。2手目までは指示を与えることでランダム性がでて楽しめると思います。コンピュータの弱点としては「2手目まで『小』の駒にはかぶせてこない」「勝敗が見つからなかった場合の手があまい」「プレイヤーが盤面上の駒を移動する手は読まない」などがあります。
いろいろ調べているとAIを最初にゲームに使ったのはナムコの「パックマン」らしいです。また「ゼビウス」ではプレイヤーの上手さによって難易度が変わったらしいです。(初めて知りました)
プレイヤーの実力により難易度を変化させるのはすごく良いアイデアです。プレイヤーの連勝した数で徐々にコンピュータを強くする、連敗したら徐々にコンピュータを弱くするようにすれば、誰でも楽しめる三目並べが出来そうです。
↓よかったらポチッとしていってください。
Python FlaskとBrythonで三目並べの人工知能AIを作る(前編)
はじめに
今回は、Q学習やミニマックス法などの機械学習を使わずに一から人工知能を作っていきます。人間側で全て指示を与えるタイプの一昔前のAIで、自ら学習することはできません。
1手目と2手目は条件式で指示を与えます。
下の図でマスの位置番号と呼び方を決めておきます。
赤・・「中心」 青・・「角」 白・・「辺」と呼ぶことにします。
駒の種類は「大」「中」「小」です。
1手目を考える
先手の場合
「大」を中心に打てばよさそうですが、ランダムで角にも打ちます。
後手の場合
相手の「中」を調べてその位置に「大」をかぶせて打ちます。相手の「中」がなければ「大」を中心か角に打ちます。
# コンピュータの1手目 if self.ai_step == 1: # 相手の「中」を調べる for i in range(9): if document[str(i)].classList.contains("blue_m"): self.masu_no = i # 相手の「大」が真ん中にあるか調べる n = random.randrange(7) if self.masu_no == 9: if n <= 4: if not document[str(n * 2)].classList.contains("blue_l"): self.masu_no = n * 2 elif not document["4"].classList.contains("blue_l"): self.masu_no = 4 else: self.masu_no = 6 else: if not document["4"].classList.contains("blue_l"): self.masu_no = 4 else: self.masu_no = 6 # 「大」を動かす self.koma_no = "box3" self.koma = "red_l" document[self.koma_no].classList.remove(self.koma) self.AI_set()
解説
変数「n」をランダムで0~6の整数に決めます。
「n」が0~4(4以下)の場合は「n×2」の位置に打ちます。
ただし、相手の「大」が既にある場合は「中心」に打ちます。
さらに、中心に相手の「大」がある場合は「6の位置」に打ちます。
「n」が5か6(4より大きい)の場合は「中心」に打ちます。
ただし、相手の「大」が既にある場合は「6の位置」に打ちます。
※先手の時は、2,5,6で「中心」、0,1,3,4で「角」になります。
2手目を考える
先手の場合
相手の「中」を調べてその位置に「大」をかぶせて打ちます。
相手の「中」がない場合で
中心に駒がない場合は「中心」に「大」を打ちます。
中心に駒がある場合は「角」に「大」を打ちます。
後手の場合
相手の「中」を調べてその位置に「大」をかぶせて打ちます。
相手の「中」がなければ相手がリーチしているか調べて、リーチしていれば防ぐ位置に「中」を打ちます。(相手の「中」は無いはずなので「大」と「大」のリーチという考えです。ただし「小」が入っていると簡単に負けます。)
相手の「中」がなく、相手がリーチしていない場合で
中心に駒がない場合は「中心」に「大」を打ちます。
斜めに「青赤青」と並んでいる場合は「辺」に「大」を打ちます。
そうでない場合は「角」に「中」か「大」を打ちます。
・・・少しややこしくなってきました。ソースコードはこちらです。
# コンピュータの2手目 if self.ai_step == 2: # 相手の「中」を調べる for i in range(9): if document[str(i)].classList.contains("blue_m") \ and str(self.board[i])[0] != "2": self.masu_no = i self.koma_no = "box6" self.koma = "red_l" # 相手のリーチを調べる if self.masu_no == 9: # 9マス分を調べる self.koma_no = "box2" self.koma = "red_m" a = "1" b = "2" if ((str(self.board[1])[0] == a and str(self.board[2])[0] == a) \ or (str(self.board[3])[0] == a and str(self.board[6])[0] == a) \ or (str(self.board[4])[0] == a and str(self.board[8])[0] == a)) \ and str(self.board[0])[0] != b: self.masu_no = 0 elif ((str(self.board[0])[0] == a and str(self.board[2])[0] == a) \ or (str(self.board[4])[0] == a and str(self.board[7])[0] == a)) \ and str(self.board[1])[0] != b: self.masu_no = 1 elif ((str(self.board[0])[0] == a and str(self.board[1])[0] == a) \ or (str(self.board[5])[0] == a and str(self.board[8])[0] == a) \ or (str(self.board[4])[0] == a and str(self.board[6])[0] == a)) \ and str(self.board[2])[0] != b: self.masu_no = 2 elif ((str(self.board[4])[0] == a and str(self.board[5])[0] == a) \ or (str(self.board[0])[0] == a and str(self.board[6])[0] == a)) \ and str(self.board[3])[0] != b: self.masu_no = 3 elif ((str(self.board[3])[0] == a and str(self.board[5])[0] == a) \ or (str(self.board[1])[0] == a and str(self.board[7])[0] == a) \ or (str(self.board[0])[0] == a and str(self.board[8])[0] == a) \ or (str(self.board[2])[0] == a and str(self.board[6])[0] == a)) \ and str(self.board[4])[0] != b: self.masu_no = 4 elif ((str(self.board[3])[0] == a and str(self.board[4])[0] == a) \ or (str(self.board[2])[0] == a and str(self.board[8])[0] == a)) \ and str(self.board[5])[0] != b: self.masu_no = 5 elif ((str(self.board[7])[0] == a and str(self.board[8])[0] == a) \ or (str(self.board[0])[0] == a and str(self.board[3])[0] == a) \ or (str(self.board[2])[0] == a and str(self.board[4])[0] == a)) \ and str(self.board[6])[0] != b: self.masu_no = 6 elif ((str(self.board[6])[0] == a and str(self.board[8])[0] == a) \ or (str(self.board[1])[0] == a and str(self.board[4])[0] == a)) \ and str(self.board[7])[0] != b: self.masu_no = 7 elif ((str(self.board[6])[0] == a and str(self.board[7])[0] == a) \ or (str(self.board[2])[0] == a and str(self.board[5])[0] == a) \ or (str(self.board[0])[0] == a and str(self.board[4])[0] == a)) \ and str(self.board[8])[0] != b: self.masu_no = 8 # リーチでなければ else: self.koma_no = "box6" self.koma = "red_l" n = random.choice([0, 2, 6, 8]) if not (document["4"].classList.contains("blue_l") \ or document["4"].classList.contains("red_l")): self.masu_no = 4 elif (document["0"].classList.contains("blue_l") \ and document["4"].classList.contains("red_l") \ and document["8"].classList.contains("blue_l")) \ or (document["2"].classList.contains("blue_l") \ and document["4"].classList.contains("red_l") \ and document["6"].classList.contains("blue_l")): self.masu_no = 3 elif not (document[str(n)].classList.contains("blue_l") \ or document[str(n)].classList.contains("red_l")): self.koma_no = "box2" self.koma = "red_m" self.masu_no = n else: if n == 2 or n == 8: self.masu_no = 0 else: self.masu_no = n + 2 # 駒を動かす document[self.koma_no].classList.remove(self.koma) timer.set_timeout(self.AI_set, 100)
解説
斜めに「青赤青」と並んでいる場合の「辺」は(ランダムで辺を選んだら良かったのですがそんなに頻度もなさそうなので)「3の位置」に固定しました。
「角」に打つ時の位置はランダムにしました。random.choice()で「角」を選んで、その位置に「大」がなければ「中」を打ちます。その位置に「大」があれば別の「角」に「大」を打ちます。
3手目からは
- 「元の位置(self.koma_no)」から大きさ(self.koma)を調べる
- 「元の位置(self.koma_no)」候補は最大6つ
- 「移動先(self.masu_no)」候補は最大9つ
- 合計54パターンを総調べ
- 勝てる手があればその位置に打つ
- 次の相手の手まで読む
- 負ける手があればその手は打たない
- 勝負が決まらなければ・・・
- 負ける手しかなければ・・・
この流れで考えたいと思います。
後編に続きます。
PythonでWEBアプリの三目並べを作る(完成品:FlaskとBrythonを利用)
はじめに
今回はソースコードを一から見直して作り直します。新たに追加したいのはこちらです。
- クリックイベントの整理
- ドロップアンドドラッグの実装
- スマホのタップにも対応
こちらから遊べます。
http://startpython.pythonanywhere.com/game2/
クリックイベントの整理
JavaScriptだと当たり前かもしれないですが、イベントの発生条件がどのようにすればわかりませんでした。前回までは各要素をクリックした時に関数を呼び出すようにしていましたが、「マウスダウン」「マウスムーブ」「マウスアップ」にまとめて関数を作ります。
# マウスイベント document["wrapper"].bind('mousedown', self.click) document["wrapper"].bind('mousemove', self.move) document["wrapper"].bind('mouseup', self.release)
ドロップアンドドラッグの実装
前回までは「クリックして駒を選択しもう一度クリックした場所に駒を打つ」でしたが、「クリックしたまま移動(ドラッグ)してクリックを離した場所に駒を置く」ように変更します。
問題点1
移動用の駒を別に用意してクリックした時に表示させるようにしていましたが、マウスアップ(クリックを離した時)の要素が移動用の駒になってしまいます。
解決策1
クリックなどのイベントを無効化するCSSを使います。
#koma_move { pointer-events: none; }
問題点2
駒を移動中(ドラッグ中)にテキストの文字が選択されて反転してしまいます。そのままでも特に問題ないのですが気になるので調べました。
解決策2
テキストを選択させないCSSを使います。
.message-container { user-select: none; }
スマホのタップにも対応
PCでは「マウスダウン」「マウスムーブ」「マウスアップ」のイベントが、スマホの場合は「タッチスタート」「タッチムーブ」「タッチエンド」のイベントがあります。
(「マウスダウン」「マウスアップ」はスマホでも使えるのですが「マウスムーブ」はスマホでは認識しません。※最終的に理想のドラッグはスマホではできませんでした)
# タッチイベント document["wrapper"].bind('touchstart', self.click) document["wrapper"].bind('touchmove', self.move) document["wrapper"].bind('touchend', self.release)
タッチイベントとマウスイベントの順番
- touchstart
- touchmove
- touchend
- mousemove
- mousedown
- mouseup
- click
問題点3
スマホでドラッグすると画面がスクロールしてしまいます。全体的にひとまわり小さくして全体表示できるようにしましたが、それでも上下に少し動いてしまいました。
解決策3
全体「body」の配置方法を「position: fixed;」で固定絶対値にします。
「left: calc(50% - 268px/2);」で中央表示にします。(要素の幅が268pxの場合)
問題点4
要素をドラッグした時にタップの位置とずれてしまいます。おそらく原因は画面全体が少しスクロールしているためだと思います。
問題点5
タップを離した時の位置にある要素が検出できません。(PCでマウスを離した時の位置にある要素は検出できます)「タッチスタート」の位置にある要素がそのまま「タッチエンド」で検出する要素になってしまいます。
問題点6
「タッチエンド」イベントの後に「マウスダウン」イベントと「マウスアップ」イベントが発生してしまいます。(スマホの時も「マウスダウン」「マウスアップ」は発生するためです)
解決策4~6
「問題点3」の画面スクロールしないようにするのを諦めました。それで「問題点4」のドラッグした時の要素とのズレがなくなりました。
「問題点5」「問題点6」は「タッチエンド」を使わずに「マウスアップ」を利用することにしました。これで「タッチスタート」「タッチムーブ」「マウスアップ」の順番で処理できます。
「問題点5」のタップを離した時の位置に駒を置くのを諦めました。もう一度同じ位置でタップすると駒を置けます。
ソースコード
from browser import document, alert import random class main(): def __init__(self): # 移動確認用 self.drawing = False # 盤面を初期化 self.board = [0] * 9 # 先攻後攻 self.my_turn = True # プレイヤーの入力 self.state = "青の番" document["btn_text"].textContent = "コマをえらんでください" # マウスイベント document["wrapper"].bind('mousedown', self.click) document["wrapper"].bind('mousemove', self.move) document["wrapper"].bind('mouseup', self.release) # タッチイベント document["wrapper"].bind('touchstart', self.click) document["wrapper"].bind('touchmove', self.move) #document["wrapper"].bind('touchend', self.release) def click(self, event): # 駒を選択 if self.state == "青の番": # 選択した駒を記録・駒を不透明 self.target = event.target self.target.style.opacity = 1 # 選択した駒の設定・駒の大きさ self.koma = "" if event.target.classList.contains("blue_s"): if not (event.target.classList.contains("red_m") \ or event.target.classList.contains("red_l")): self.koma = "blue_s" self.center = 15 if event.target.classList.contains("blue_m"): if not event.target.classList.contains("red_l"): self.koma = "blue_m" self.center = 23 if event.target.classList.contains("blue_l"): self.koma = "blue_l" self.center = 30 # 青駒をクリックした場合 if self.koma[:4] == "blue": # 移動用の色を設定 document["koma_move"].style.backgroundColor = "#00b0f0" # 駒の移動 self.state = "駒の移動" document["btn_text"].textContent = "どこにおきますか?" # 移動用 self.drawing = True elif self.state == "赤の番": # 選択した駒を記録・駒を不透明 self.target = event.target self.target.style.opacity = 1 # 選択した駒の設定・駒の大きさ self.koma = "" if event.target.classList.contains("red_s"): if not (event.target.classList.contains("blue_m") \ or event.target.classList.contains("blue_l")): self.koma = "red_s" self.center = 15 if event.target.classList.contains("red_m"): if not event.target.classList.contains("blue_l"): self.koma = "red_m" self.center = 23 if event.target.classList.contains("red_l"): self.koma = "red_l" self.center = 30 # 赤駒をクリックした場合 if self.koma[:3] == "red": # 移動用の色を設定 document["koma_move"].style.backgroundColor = "#ff0000" # 駒の移動 self.state = "駒の移動" document["btn_text"].textContent = "どこにおきますか?" # 移動用 self.drawing = True def move(self, event): if not self.drawing: return # 駒の移動 element = document["koma_move"] element.style.display = "inline" element.left = event.x - self.center element.top = event.y - self.center element.width = self.center * 2 element.height = self.center * 2 def release(self, event): if event.target.id == "btn_text": # ゲームの初期化 if document["btn_text"].textContent == "もう一回あそぶ": self.initgame() return if self.target == event.target: document["koma_move"].style.display = "none" return if self.state != "駒の移動": return # 移動中の駒を非表示 document["koma_move"].style.display = "none" # 移動用 self.drawing = False # 盤面内か調べる if not event.target.classList.contains("square"): # 範囲外でクリックした場合 if self.koma[:4] == "blue": # 透明度を設定(青駒を全て) for motigoma in document.select(".blue_s"): if not (motigoma.classList.contains("red_m") \ or motigoma.classList.contains("red_l")): motigoma.style.opacity = 0.5 for motigoma in document.select(".blue_m"): if not motigoma.classList.contains("red_l"): motigoma.style.opacity = 0.5 for motigoma in document.select(".blue_l"): motigoma.style.opacity = 0.5 self.state = "青の番" document["btn_text"].textContent = "コマをえらんでください" elif self.koma[:3] == "red": # 透明度を設定(赤駒を全て) for motigoma in document.select(".red_s"): if not (motigoma.classList.contains("blue_m") \ or motigoma.classList.contains("blue_l")): motigoma.style.opacity = 0.5 for motigoma in document.select(".red_m"): if not motigoma.classList.contains("blue_l"): motigoma.style.opacity = 0.5 for motigoma in document.select(".red_l"): motigoma.style.opacity = 0.5 self.state = "赤の番" document["btn_text"].textContent = "コマをえらんでください" return if self.koma[:4] == "blue": # 打てない場所か調べる if self.koma == "blue_s": if event.target.classList.contains("blue_s") \ or event.target.classList.contains("blue_m") \ or event.target.classList.contains("blue_l") \ or event.target.classList.contains("red_s") \ or event.target.classList.contains("red_m") \ or event.target.classList.contains("red_l"): alert("そこには打てません") self.target.style.opacity = 0.5 self.state = "青の番" document["btn_text"].textContent = "コマをえらんでください" return elif self.koma == "blue_m": if event.target.classList.contains("blue_m") \ or event.target.classList.contains("blue_l") \ or event.target.classList.contains("red_m") \ or event.target.classList.contains("red_l"): alert("そこには打てません") self.target.style.opacity = 0.5 self.state = "青の番" document["btn_text"].textContent = "コマをえらんでください" return elif self.koma == "blue_l": if event.target.classList.contains("blue_l") \ or event.target.classList.contains("red_l"): alert("そこには打てません") self.target.style.opacity = 0.5 self.state = "青の番" document["btn_text"].textContent = "コマをえらんでください" return # 駒を設置 if self.koma == "blue_s": event.target.classList.add("blue_s") elif self.koma == "blue_m": event.target.classList.add("blue_m") elif self.koma == "blue_l": event.target.classList.add("blue_l") # 選択した駒を削除 self.target.classList.remove(self.koma); # 盤面を記録 if self.koma == "blue_s": self.board[int(event.target.id)] += 1 if self.target.id[0:3] != "box": self.board[int(self.target.id)] -= 1 elif self.koma == "blue_m": self.board[int(event.target.id)] += 10 if self.target.id[0:3] != "box": self.board[int(self.target.id)] -= 10 elif self.koma == "blue_l": self.board[int(event.target.id)] += 100 if self.target.id[0:3] != "box": self.board[int(self.target.id)] -= 100 # 透明度を解除(青駒を全て) for motigoma in document.select(".blue_s"): motigoma.style.opacity = 1 for motigoma in document.select(".blue_m"): motigoma.style.opacity = 1 for motigoma in document.select(".blue_l"): motigoma.style.opacity = 1 # 勝敗の判定 self.check_state() # 勝敗が付いてる場合 if document["btn_text"].textContent == "もう一回あそぶ": return # ターン交代 self.state = "赤の番" document["btn_text"].textContent = "コマをえらんでください" # 透明度を設定(赤駒を全て) for motigoma in document.select(".red_s"): if not (motigoma.classList.contains("blue_m") \ or motigoma.classList.contains("blue_l")): motigoma.style.opacity = 0.5 for motigoma in document.select(".red_m"): if not motigoma.classList.contains("blue_l"): motigoma.style.opacity = 0.5 for motigoma in document.select(".red_l"): motigoma.style.opacity = 0.5 # テキストを変更 document["turn_text"].style.color = "#ff0000" if self.koma[:3] == "red": # 打てない場所か調べる if self.koma == "red_s": if event.target.classList.contains("blue_s") \ or event.target.classList.contains("blue_m") \ or event.target.classList.contains("blue_l") \ or event.target.classList.contains("red_s") \ or event.target.classList.contains("red_m") \ or event.target.classList.contains("red_l"): alert("そこには打てません") self.target.style.opacity = 0.5 self.state = "赤の番" document["btn_text"].textContent = "コマをえらんでください" return elif self.koma == "red_m": if event.target.classList.contains("blue_m") \ or event.target.classList.contains("blue_l") \ or event.target.classList.contains("red_m") \ or event.target.classList.contains("red_l"): alert("そこには打てません") self.target.style.opacity = 0.5 self.state = "赤の番" document["btn_text"].textContent = "コマをえらんでください" return elif self.koma == "red_l": if event.target.classList.contains("blue_l") \ or event.target.classList.contains("red_l"): alert("そこには打てません") self.target.style.opacity = 0.5 self.state = "赤の番" document["btn_text"].textContent = "コマをえらんでください" return # 駒を設置 if self.koma == "red_s": event.target.classList.add("red_s") elif self.koma == "red_m": event.target.classList.add("red_m") elif self.koma == "red_l": event.target.classList.add("red_l") # 選択した駒を削除 self.target.classList.remove(self.koma); # 盤面を記録 if self.koma == "red_s": self.board[int(event.target.id)] += 2 if self.target.id[0:3] != "box": self.board[int(self.target.id)] -= 2 elif self.koma == "red_m": self.board[int(event.target.id)] += 20 if self.target.id[0:3] != "box": self.board[int(self.target.id)] -= 20 elif self.koma == "red_l": self.board[int(event.target.id)] += 200 if self.target.id[0:3] != "box": self.board[int(self.target.id)] -= 200 # 透明度を解除(赤駒を全て) for motigoma in document.select(".red_s"): motigoma.style.opacity = 1 for motigoma in document.select(".red_m"): motigoma.style.opacity = 1 for motigoma in document.select(".red_l"): motigoma.style.opacity = 1 # 勝敗の判定 self.check_state() # 勝敗が付いてる場合 if document["btn_text"].textContent == "もう一回あそぶ": return # ターン交代 self.state = "青の番" document["btn_text"].textContent = "コマをえらんでください" # 透明度を設定(青駒を全て) for motigoma in document.select(".blue_s"): if not (motigoma.classList.contains("red_m") \ or motigoma.classList.contains("red_l")): motigoma.style.opacity = 0.5 for motigoma in document.select(".blue_m"): if not motigoma.classList.contains("red_l"): motigoma.style.opacity = 0.5 for motigoma in document.select(".blue_l"): motigoma.style.opacity = 0.5 # テキストを変更 document["turn_text"].style.color = "#00b0f0" # 勝敗の判定 def check_state(self): if self.koma[:4] == "blue": # 赤を先に判定 a = "2" b = "1" c = "赤" d = "青" elif self.koma[:3] == "red": # 青を先に判定 a = "1" b = "2" c = "青" d = "赤" # 8列を調べる if (str(self.board[0])[0] == a and str(self.board[1])[0] == a and str(self.board[2])[0] == a) \ or (str(self.board[3])[0] == a and str(self.board[4])[0] == a and str(self.board[5])[0] == a) \ or (str(self.board[6])[0] == a and str(self.board[7])[0] == a and str(self.board[8])[0] == a) \ or (str(self.board[0])[0] == a and str(self.board[3])[0] == a and str(self.board[6])[0] == a) \ or (str(self.board[1])[0] == a and str(self.board[4])[0] == a and str(self.board[7])[0] == a) \ or (str(self.board[2])[0] == a and str(self.board[5])[0] == a and str(self.board[8])[0] == a) \ or (str(self.board[0])[0] == a and str(self.board[4])[0] == a and str(self.board[8])[0] == a) \ or (str(self.board[2])[0] == a and str(self.board[4])[0] == a and str(self.board[6])[0] == a): self.state = c + "の勝ちです" element = document["win_msg"] element.textContent = self.state element.style.display = "inline" document["btn_text"].textContent = "もう一回あそぶ" return if (str(self.board[0])[0] == b and str(self.board[1])[0] == b and str(self.board[2])[0] == b) \ or (str(self.board[3])[0] == b and str(self.board[4])[0] == b and str(self.board[5])[0] == b) \ or (str(self.board[6])[0] == b and str(self.board[7])[0] == b and str(self.board[8])[0] == b) \ or (str(self.board[0])[0] == b and str(self.board[3])[0] == b and str(self.board[6])[0] == b) \ or (str(self.board[1])[0] == b and str(self.board[4])[0] == b and str(self.board[7])[0] == b) \ or (str(self.board[2])[0] == b and str(self.board[5])[0] == b and str(self.board[8])[0] == b) \ or (str(self.board[0])[0] == b and str(self.board[4])[0] == b and str(self.board[8])[0] == b) \ or (str(self.board[2])[0] == b and str(self.board[4])[0] == b and str(self.board[6])[0] == b): self.state = d + "の勝ちです" element = document["win_msg"] element.textContent = self.state element.style.display = "inline" document["btn_text"].textContent = "もう一回あそぶ" return # ゲームの初期化 def initgame(self): # 盤面を初期化 self.board = [0] * 9 for square in document.select(".square"): square.classList.remove("blue_s") square.classList.remove("blue_m") square.classList.remove("blue_l") square.classList.remove("red_s") square.classList.remove("red_m") square.classList.remove("red_l") document["win_msg"].style.display = "none" # 持ち駒の初期化 for i in range(6): document["box"+str(i+1)].classList.remove("red_s") document["box"+str(i+1)].classList.remove("red_m") document["box"+str(i+1)].classList.remove("red_l") document["box"+str(i+1)].classList.remove("blue_s") document["box"+str(i+1)].classList.remove("blue_m") document["box"+str(i+1)].classList.remove("blue_l") document["box"+str(i+7)].classList.remove("red_s") document["box"+str(i+7)].classList.remove("red_m") document["box"+str(i+7)].classList.remove("red_l") document["box"+str(i+7)].classList.remove("blue_s") document["box"+str(i+7)].classList.remove("blue_m") document["box"+str(i+7)].classList.remove("blue_l") n = random.randrange(6) if n <= 2: document["box"+str(i+1)].classList.add("red_s") document["box"+str(i+7)].classList.add("blue_s") if n == 3 or n == 4: document["box"+str(i+1)].classList.add("red_m") document["box"+str(i+7)].classList.add("blue_m") if n == 5: document["box"+str(i+1)].classList.add("red_l") document["box"+str(i+7)].classList.add("blue_l") # ゲーム開始 if document["win_msg"].textContent == "赤の勝ちです": self.state = "青の番" # テキストを変更 document["turn_text"].style.color = "#00b0f0" # 透明度を解除(赤駒を全て) for motigoma in document.select(".red_s"): motigoma.style.opacity = 1 for motigoma in document.select(".red_m"): motigoma.style.opacity = 1 for motigoma in document.select(".red_l"): motigoma.style.opacity = 1 # 透明度を設定(青駒を全て) for motigoma in document.select(".blue_s"): self.style = motigoma.style self.style.opacity = 0.5 for motigoma in document.select(".blue_m"): self.style = motigoma.style self.style.opacity = 0.5 for motigoma in document.select(".blue_l"): self.style = motigoma.style self.style.opacity = 0.5 else: self.state = "赤の番" # テキストを変更 document["turn_text"].style.color = "#ff0000" # 透明度を解除(青駒を全て) for motigoma in document.select(".blue_s"): style = motigoma.style style.opacity = 1 for motigoma in document.select(".blue_m"): style = motigoma.style style.opacity = 1 for motigoma in document.select(".blue_l"): style = motigoma.style style.opacity = 1 # 透明度を設定(赤駒を全て) for motigoma in document.select(".red_s"): self.style = motigoma.style self.style.opacity = 0.5 for motigoma in document.select(".red_m"): self.style = motigoma.style self.style.opacity = 0.5 for motigoma in document.select(".red_l"): self.style = motigoma.style self.style.opacity = 0.5 if __name__ == '__main__': main()
次回からは、AIを使ってコンピュータと対戦できるようにしたいと思います。通常の三目並べと違い、駒の移動ができるため複雑で難しくなりそうです。
PythonでWEBアプリの三目並べを作る(改良品:FlaskとBrythonを利用)
はじめに
2戦目の初期駒と先攻後攻を変更しました。「もう一回遊ぶ」で持ち駒をランダムにしてましたが赤と青の差があったので、赤と青の持ち駒は同じにしました。先攻が圧倒的に有利なので負けた方を次戦の先攻にしました。
変更前のソースコード
# ゲームの初期化 def initgame(self, event): if document["btn_text"].textContent != "もう一回遊ぶ": alert(self.board) # テスト表示 return #alert(self.board) # テスト表示 # 盤面を初期化 self.board = [0] * 9 for square in document.select(".square"): square.classList.remove("blue_s") square.classList.remove("blue_m") square.classList.remove("blue_l") square.classList.remove("red_s") square.classList.remove("red_m") square.classList.remove("red_l") document["win_msg"].style.display = "none" # 持ち駒の初期化 for i in range(12): document["box"+str(i+1)].classList.remove("red_s") document["box"+str(i+1)].classList.remove("red_m") document["box"+str(i+1)].classList.remove("red_l") document["box"+str(i+1)].classList.remove("blue_s") document["box"+str(i+1)].classList.remove("blue_m") document["box"+str(i+1)].classList.remove("blue_l") #num = random.randint(1, 3) n = random.randrange(6) if i < 6 and n <= 2: document["box"+str(i+1)].classList.add("red_s") if i < 6 and (n == 3 or n == 4): document["box"+str(i+1)].classList.add("red_m") if i < 6 and n == 5: document["box"+str(i+1)].classList.add("red_l") if i >= 6 and n <= 2: document["box"+str(i+1)].classList.add("blue_s") if i >= 6 and (n == 3 or n == 4): document["box"+str(i+1)].classList.add("blue_m") if i >= 6 and n == 5: document["box"+str(i+1)].classList.add("blue_l") ''' if i == 0 or i == 3: document["box"+str(i+1)].classList.add("red_s") if i == 1 or i == 4: document["box"+str(i+1)].classList.add("red_m") if i == 2 or i == 5: document["box"+str(i+1)].classList.add("red_l") if i == 6 or i == 9: document["box"+str(i+1)].classList.add("blue_s") if i == 7 or i == 10: document["box"+str(i+1)].classList.add("blue_m") if i == 8 or i == 11: document["box"+str(i+1)].classList.add("blue_l") ''' # ゲーム開始 self.state = "青の番"
後半の「’’’」~「’’’」のコメント部分は、初期駒と同じ小中大2つずつのままです。
変数「n」を整数0~5のランダムにして、nが0,1,2の時は「小」の駒、nが3,4の時は「中」の駒、nが5の時は「大」の駒にしています。
最後にステータスを「青の番」にして、先攻は青に固定しています。
変更後のソースコード
# ゲームの初期化 def initgame(self, event): if document["btn_text"].textContent != "もう一回遊ぶ": alert(self.board) # テスト表示 return # 盤面を初期化 self.board = [0] * 9 for square in document.select(".square"): square.classList.remove("blue_s") square.classList.remove("blue_m") square.classList.remove("blue_l") square.classList.remove("red_s") square.classList.remove("red_m") square.classList.remove("red_l") document["win_msg"].style.display = "none" # 持ち駒の初期化 for i in range(6): document["box"+str(i+1)].classList.remove("red_s") document["box"+str(i+1)].classList.remove("red_m") document["box"+str(i+1)].classList.remove("red_l") document["box"+str(i+1)].classList.remove("blue_s") document["box"+str(i+1)].classList.remove("blue_m") document["box"+str(i+1)].classList.remove("blue_l") document["box"+str(i+7)].classList.remove("red_s") document["box"+str(i+7)].classList.remove("red_m") document["box"+str(i+7)].classList.remove("red_l") document["box"+str(i+7)].classList.remove("blue_s") document["box"+str(i+7)].classList.remove("blue_m") document["box"+str(i+7)].classList.remove("blue_l") n = random.randrange(6) if n <= 2: document["box"+str(i+1)].classList.add("red_s") document["box"+str(i+7)].classList.add("blue_s") if n == 3 or n == 4: document["box"+str(i+1)].classList.add("red_m") document["box"+str(i+7)].classList.add("blue_m") if n == 5: document["box"+str(i+1)].classList.add("red_l") document["box"+str(i+7)].classList.add("blue_l") # ゲーム開始 #n = random.randrange(2) #if n == 0: if document["win_msg"].textContent == "赤の勝ちです": self.state = "青の番" # テキストを変更 document["turn_text"].style.color = "#00b0f0" # 透明度を解除(赤駒を全て) for motigoma in document.select(".red_s"): motigoma.style.opacity = 1 for motigoma in document.select(".red_m"): motigoma.style.opacity = 1 for motigoma in document.select(".red_l"): motigoma.style.opacity = 1 # 透明度を設定(青駒を全て) for motigoma in document.select(".blue_s"): self.style = motigoma.style self.style.opacity = 0.5 for motigoma in document.select(".blue_m"): self.style = motigoma.style self.style.opacity = 0.5 for motigoma in document.select(".blue_l"): self.style = motigoma.style self.style.opacity = 0.5 else: self.state = "赤の番" # テキストを変更 document["turn_text"].style.color = "#ff0000" # 透明度を解除(青駒を全て) for motigoma in document.select(".blue_s"): style = motigoma.style style.opacity = 1 for motigoma in document.select(".blue_m"): style = motigoma.style style.opacity = 1 for motigoma in document.select(".blue_l"): style = motigoma.style style.opacity = 1 # 透明度を設定(赤駒を全て) for motigoma in document.select(".red_s"): self.style = motigoma.style self.style.opacity = 0.5 for motigoma in document.select(".red_m"): self.style = motigoma.style self.style.opacity = 0.5 for motigoma in document.select(".red_l"): self.style = motigoma.style self.style.opacity = 0.5
変数「n」を整数0~5のランダムにして「小・中・大」の駒を決めるところは一緒ですが、赤と青の駒を同じにして不公平をなくしました。
先攻後攻に関しては、ランダムでもやってみたのですがランダムはやめて、負けた方を先攻にすることにしました。
おまけ
おもしろそうだったので「じゃんけん三目並べ」を作ろうとしたのですが、ゲーム性がなく思ったより面白くなかったので途中でやめました。いいアイデアがあれば改良するかもしれません。
PythonでWEBアプリの三目並べを作る(試作品:FlaskとBrythonを利用)
はじめに
ブラウザ上で動く三目並べを作っていきます。とりあえず試作品が完成したのでソースコードを載せていきます。細かい解説は省略します。
動作環境
Windows10
Python 3.7.5
Flask 1.1.1
Brython 3.8.7
作業フォルダ/ ├ game1/ │ ├ templates/ │ │ └ game1/ │ │ └ oxgame.html │ └ server.py ├ static/ │ ├ brython.js │ ├ brython_stdlib.js │ ├ oxgame.css │ └ oxgame.py ├ templates/ │ └ index.html └ main.py
ソースコード
「作業フォルダ/game1/templates/game1/」
oxgame.html
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width,initial-scale=1"> <link rel="stylesheet" href="/static/oxgame.css"> <title>三目並べ</title> </head> <body onload="brython()"> <script type="text/python" src="/static/oxgame.py"></script> <div id="wrapper" class="wrapper"> <div class="game-container"> <div class="motigoma1_container"> <div id="nokori1_s"></div> <div id="nokori1_m"></div> <div id="nokori1_l"></div> </div> <div class="motigoma1_box"> <div id="box1" class="motigoma1 red_s"></div> <div id="box2" class="motigoma1 red_m"></div> <div id="box3" class="motigoma1 red_l"></div> </div> <div class="motigoma1_box"> <div id="box4" class="motigoma1 red_s"></div> <div id="box5" class="motigoma1 red_m"></div> <div id="box6" class="motigoma1 red_l"></div> </div> <div class="message-container"> <ul class="message-list"> <li> <span id="turn_text" class="maru">■</span>のばん </li> <li class="js-hidden"> <span class="batsu">■</span>のばん </li> <li class="js-hidden"> <span class="maru">■</span>の勝ち! </li> <li class="js-hidden"> <span class="batsu">■</span>の勝ち! </li> <li class="js-hidden"> 引き分け </li> </ul> </div> <div class="squares-container"> <div class="squares-box"> <div id="0" class="square"></div> <div id="1" class="square"></div> <div id="2" class="square"></div> <div id="3" class="square"></div> <div id="4" class="square"></div> <div id="5" class="square"></div> <div id="6" class="square"></div> <div id="7" class="square"></div> <div id="8" class="square"></div> <div id="win_msg">赤の勝ちです </div> </div> </div> <div class="btn-container"> <span id="btn_text" class="btn btn-reset"> もう一回遊ぶ </span> </div> <div class="motigoma2_box"> <div id="box7" class="motigoma2 blue_s"></div> <div id="box8" class="motigoma2 blue_m"></div> <div id="box9" class="motigoma2 blue_l"></div> </div> <div class="motigoma2_box"> <div id="box10" class="motigoma2 blue_s"></div> <div id="box11" class="motigoma2 blue_m"></div> <div id="box12" class="motigoma2 blue_l"></div> </div> <div class="motigoma2_container"> <div id="nokori2_s"></div> <div id="nokori2_m"></div> <div id="nokori2_l"></div> </div> </div> <div id="koma_move"> </div> </div> <script src="/static/brython.js"></script> <script src="/static/brython_stdlib.js"></script> </body> </html>
「作業フォルダ/game1/」
server.py
# Blueprint(pyファイルを分割するための関数)をインポート from flask import Blueprint #「app」を「Blueprint()」を使って定義 #「game1」の部分は、url_forで使用(今回は未使用) app = Blueprint('game1', __name__, template_folder='templates') # 必要なモジュールをインポート from flask import Flask, render_template #「/」へアクセスがあった場合 @app.route('/') def index(): return render_template('game1/oxgame.html')
「作業フォルダ/static/」
oxgame.css
html { box-sizing: border-box; font-size: 16px; } body { margin: 0; line-height: normal; } .wrapper { max-width: 500px; margin: 0 auto; padding: 0 10px; text-align: center; } .motigoma1_container { margin: 0 auto; width: 246px; height: 15px; display: flex; flex-wrap: wrap; } #nokori1_s { width: 80px; } #nokori1_m { width: 80px; } #nokori1_l { width: 80px; } .motigoma1_box { margin: 0 auto; width: 246px; height: 64px; display: flex; flex-wrap: wrap; border: solid 1px #fff; } .motigoma1 { position: relative; width: 80px; height: 64px; border: solid 1px #fff; } .motigoma1::before { content: ''; position: absolute; top: 50%; left: 50%; border-radius: 20%; transform: translate(-50%, -50%); } .message-container { font-size: 1.2rem; font-weight: bold; } .message-list { margin: 0; padding: 0; } ul { list-style: none; } #turn_text { color: #00b0f0; } .squares-container { margin: 0 auto; width: 246px; } .squares-box { width: 246px; height: 246px; display: flex; flex-wrap: wrap; border: solid 1px #333; } .square { position: relative; width: calc(240px / 3); height: calc(240px / 3); border: solid 1px #888; } .square::before { content: ''; border-radius: 20%; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } .motigoma2_container { margin: 0 auto; width: 246px; height: 5px; display: flex; } #nokori2_s { width: 80px; } #nokori2_m { width: 80px; } #nokori2_l { width: 80px; } .motigoma2_box { margin: 0 auto; width: 246px; height: 64px; display: flex; flex-wrap: wrap; border: solid 1px #fff; } .motigoma2 { position: relative; width: 80px; height: 64px; border: solid 1px #fff; opacity: 0.5; } .motigoma2::before { content: ''; position: absolute; top: 50%; left: 50%; border-radius: 20%; transform: translate(-50%, -50%); } .btn-container { padding: 10px 0; } .btn { display: inline-block; padding: 5px 20px; width: 200px; color: #fff; background-color: #ffc000; font-weight: bold; border-radius: 5px; cursor: pointer; } #btn-reset { color: #fff; background-color: #ffc000; font-weight: bold; } .btn-reset:hover { background-color: #ffd347; transition-duration: 0.4s; } .red_s::before { width: 30px; height: 30px; background-color: #ff0000; cursor: pointer; } .blue_s::before { width: 30px; height: 30px; background-color: #00b0f0; cursor: pointer; } .red_m::before { width: 45px; height: 45px; background-color: #ff0000; cursor: pointer; } .blue_m::before { width: 45px; height: 45px; background-color: #00b0f0; cursor: pointer; } .red_l::before { width: 60px; height: 60px; background-color: #ff0000; cursor: pointer; } .blue_l::before { width: 60px; height: 60px; background-color: #00b0f0; cursor: pointer; } #koma_move { position: absolute; background-color: #00b0f0; border-radius: 20%; cursor: pointer; } #win_msg { position: absolute; padding: 93px 3px; font-size: 40px; background-color: #ccc; opacity: 0.8; display: none; } .js-hidden { display: none; }
oxgame.py
from browser import document, alert import random class main(): def __init__(self): # 盤面を初期化 self.board = [0] * 9 #alert(self.board) # ゲーム開始 self.state = "ゲーム開始" # 先攻後攻 self.my_turn = True # プレイヤーの入力 self.state = "青の番" document["btn_text"].textContent = "駒を選択してください" # 駒の選択 for motigoma in document.select(".blue_s"): motigoma.bind("click", self.koma_sentaku) for motigoma in document.select(".blue_m"): motigoma.bind("click", self.koma_sentaku) for motigoma in document.select(".blue_l"): motigoma.bind("click", self.koma_sentaku) # 駒の移動 #document.bind("mousemove", self.mousemove) # 駒を非表示 document["koma_move"].bind("mousedown", self.movedelete) # 駒を打つ for i in range(9): document.select(".square")[i].bind("mouseup", self.change_class) document["wrapper"].bind("mouseup", self.put_back) # ゲームの初期化 document["btn_text"].bind("click", self.initgame) # コンピュータの入力 #self.state = "赤の番" #document["test"].textContent = self.state #for motigoma in document.select(".red_s"): # motigoma.bind("mouseup", self.koma_sentaku) #for motigoma in document.select(".red_m"): # motigoma.bind("mouseup", self.koma_sentaku) #for motigoma in document.select(".red_l"): # motigoma.bind("mouseup", self.koma_sentaku) # 駒を選択 def koma_sentaku(self, event): #if self.state == "青の番" or self.koma[:4] == "blue": if self.state == "青の番": # 透明度を設定(青駒を全て) for motigoma in document.select(".blue_s"): self.style = motigoma.style self.style.opacity = 0.5 for motigoma in document.select(".blue_m"): self.style = motigoma.style self.style.opacity = 0.5 for motigoma in document.select(".blue_l"): self.style = motigoma.style self.style.opacity = 0.5 document["koma_move"].style.backgroundColor = "#00b0f0" # 選択した駒だけ不透明 self.target = event.target self.target.style.opacity = 1 self.koma = "" if event.target.classList.contains("blue_s"): self.koma = "blue_s" self.center = 15 if event.target.classList.contains("blue_m"): self.koma = "blue_m" self.center = 23 if event.target.classList.contains("blue_l"): self.koma = "blue_l" self.center = 30 # 青駒をクリックした場合 if self.koma[:4] == "blue": # 駒の移動 document.bind("mousemove", self.mousemove) #event.preventDefault() self.state = "駒の移動" document["btn_text"].textContent = "打つ場所を選んでください" #if self.state == "赤の番" or self.koma[:3] == "red": elif self.state == "赤の番": # 透明度を設定(赤駒を全て) for motigoma in document.select(".red_s"): self.style = motigoma.style self.style.opacity = 0.5 for motigoma in document.select(".red_m"): self.style = motigoma.style self.style.opacity = 0.5 for motigoma in document.select(".red_l"): self.style = motigoma.style self.style.opacity = 0.5 document["koma_move"].style.backgroundColor = "#00b0f0" # 選択した駒だけ不透明 self.target = event.target self.target.style.opacity = 1 self.koma = "" if event.target.classList.contains("red_s"): self.koma = "red_s" self.center = 15 if event.target.classList.contains("red_m"): self.koma = "red_m" self.center = 23 if event.target.classList.contains("red_l"): self.koma = "red_l" self.center = 30 document["koma_move"].style.backgroundColor = "#ff0000" # 赤駒をクリックした場合 if self.koma[:3] == "red": # 駒の移動 document.bind("mousemove", self.mousemove) #event.preventDefault() self.state = "駒の移動" document["btn_text"].textContent = "打つ場所を選んでください" # 駒の移動 def mousemove(self, event): element = document["koma_move"] element.style.display = "inline" element.left = event.x - self.center element.top = event.y - self.center element.width = self.center * 2 element.height = self.center * 2 self.state = "非表示" #document["test"].textContent = self.state # 移動中の駒を非表示 def movedelete(self, event): if self.state != "非表示": return element = document["koma_move"] element.style.display = "none" # 駒の移動を解除 document.unbind("mousemove") # 駒を打つ #for i in range(9): # document.select(".square")[i].bind("mouseup", self.change_class) #document["wrapper"].bind("mouseup", self.put_back) #document["test"].textContent = self.state # 元に戻す(駒以外にクリックした場合) def put_back(self, event): if self.state != "非表示": return # 範囲外でクリックした場合 if self.koma[:4] == "blue": # 透明度を設定(青駒を全て) for motigoma in document.select(".blue_s"): self.style = motigoma.style self.style.opacity = 0.5 for motigoma in document.select(".blue_m"): self.style = motigoma.style self.style.opacity = 0.5 for motigoma in document.select(".blue_l"): self.style = motigoma.style self.style.opacity = 0.5 self.state = "青の番" document["btn_text"].textContent = "駒を選択してください" elif self.koma[:3] == "red": # 透明度を設定(赤駒を全て) for motigoma in document.select(".red_s"): self.style = motigoma.style self.style.opacity = 0.5 for motigoma in document.select(".red_m"): self.style = motigoma.style self.style.opacity = 0.5 for motigoma in document.select(".red_l"): self.style = motigoma.style self.style.opacity = 0.5 self.state = "赤の番" document["btn_text"].textContent = "駒を選択してください" # 駒を打つ def change_class(self, event): if self.state != "非表示": return # 打てない場所か調べる #if event.target.classList.contains("blue_s") \ #or event.target.classList.contains("blue_m") \ #or event.target.classList.contains("blue_l"): # alert("そこには打てません") # return if self.koma[:4] == "blue": # 打てない場所か調べる if self.koma == "blue_s": if event.target.classList.contains("blue_s") \ or event.target.classList.contains("blue_m") \ or event.target.classList.contains("blue_l") \ or event.target.classList.contains("red_s") \ or event.target.classList.contains("red_m") \ or event.target.classList.contains("red_l"): alert("そこには打てません") return elif self.koma == "blue_m": if event.target.classList.contains("blue_m") \ or event.target.classList.contains("blue_l") \ or event.target.classList.contains("red_m") \ or event.target.classList.contains("red_l"): alert("そこには打てません") return elif self.koma == "blue_l": if event.target.classList.contains("blue_l") \ or event.target.classList.contains("red_l"): alert("そこには打てません") return # 駒を設置 if self.koma == "blue_s": event.target.classList.add("blue_s") elif self.koma == "blue_m": event.target.classList.add("blue_m") elif self.koma == "blue_l": event.target.classList.add("blue_l") # 選択した駒を削除 self.target.classList.remove(self.koma); # 盤面を記録 #alert(self.target.id + "a") #alert(str(self.board[0])[0]) if self.koma == "blue_s": self.board[int(event.target.id)] += 1 if self.target.id[0:3] != "box": self.board[int(self.target.id)] -= 1 elif self.koma == "blue_m": self.board[int(event.target.id)] += 10 if self.target.id[0:3] != "box": self.board[int(self.target.id)] -= 10 elif self.koma == "blue_l": self.board[int(event.target.id)] += 100 if self.target.id[0:3] != "box": self.board[int(self.target.id)] -= 100 #alert(self.board) # テスト表示 #alert(str(self.board[0:3])[0]) # テスト表示 # IDを文字で返す alert(event.target.id *2) # 透明度を解除(自駒を全て) for motigoma in document.select(".blue_s"): style = motigoma.style style.opacity = 1 for motigoma in document.select(".blue_m"): style = motigoma.style style.opacity = 1 for motigoma in document.select(".blue_l"): style = motigoma.style style.opacity = 1 # 勝敗の判定 self.check_state() # 勝敗が付いてる場合 if document["btn_text"].textContent == "もう一回遊ぶ": return # ターン交代 self.my_turn = not(self.my_turn) self.state = "赤の番" document["btn_text"].textContent = "駒を選択してください" # 駒の選択用をリセット for motigoma in document.select(".red_s"): motigoma.bind("click", self.koma_sentaku) for motigoma in document.select(".red_m"): motigoma.bind("click", self.koma_sentaku) for motigoma in document.select(".red_l"): motigoma.bind("click", self.koma_sentaku) # 透明度を設定(赤駒を全て) for motigoma in document.select(".red_s"): self.style = motigoma.style if self.target.id != "": self.style.opacity = 0.5 for motigoma in document.select(".red_m"): self.style = motigoma.style if self.target.id != "": self.style.opacity = 0.5 for motigoma in document.select(".red_l"): self.style = motigoma.style if self.target.id != "": self.style.opacity = 0.5 # テキストを変更 document["turn_text"].style.color = "#ff0000" if self.koma[:3] == "red": # 打てない場所か調べる if self.koma == "red_s": if event.target.classList.contains("blue_s") \ or event.target.classList.contains("blue_m") \ or event.target.classList.contains("blue_l") \ or event.target.classList.contains("red_s") \ or event.target.classList.contains("red_m") \ or event.target.classList.contains("red_l"): alert("そこには打てません") return elif self.koma == "red_m": if event.target.classList.contains("blue_m") \ or event.target.classList.contains("blue_l") \ or event.target.classList.contains("red_m") \ or event.target.classList.contains("red_l"): alert("そこには打てません") return elif self.koma == "red_l": if event.target.classList.contains("blue_l") \ or event.target.classList.contains("red_l"): alert("そこには打てません") return # 駒を設置 #event.target.classList.add(self.koma) if self.koma == "red_s": event.target.classList.add("red_s") elif self.koma == "red_m": event.target.classList.add("red_m") elif self.koma == "red_l": event.target.classList.add("red_l") # 選択した駒を削除 self.target.classList.remove(self.koma); # 盤面を記録 if self.koma == "red_s": self.board[int(event.target.id)] += 2 if self.target.id[0:3] != "box": self.board[int(self.target.id)] -= 2 elif self.koma == "red_m": self.board[int(event.target.id)] += 20 if self.target.id[0:3] != "box": self.board[int(self.target.id)] -= 20 elif self.koma == "red_l": self.board[int(event.target.id)] += 200 if self.target.id[0:3] != "box": self.board[int(self.target.id)] -= 200 #alert(self.board) # テスト表示 # 透明度を解除(自駒を全て) for motigoma in document.select(".red_s"): motigoma.style.opacity = 1 for motigoma in document.select(".red_m"): motigoma.style.opacity = 1 for motigoma in document.select(".red_l"): motigoma.style.opacity = 1 # 勝敗の判定 self.check_state() # 勝敗が付いてる場合 if document["btn_text"].textContent == "もう一回遊ぶ": return # ターン交代 self.state = "青の番" document["btn_text"].textContent = "駒を選択してください" # 駒の選択用をリセット for motigoma in document.select(".blue_s"): motigoma.bind("click", self.koma_sentaku) for motigoma in document.select(".blue_m"): motigoma.bind("click", self.koma_sentaku) for motigoma in document.select(".blue_l"): motigoma.bind("click", self.koma_sentaku) # 透明度を設定(赤駒を全て) for motigoma in document.select(".blue_s"): self.style = motigoma.style self.style.opacity = 0.5 for motigoma in document.select(".blue_m"): self.style = motigoma.style self.style.opacity = 0.5 for motigoma in document.select(".blue_l"): self.style = motigoma.style self.style.opacity = 0.5 # テキストを変更 document["turn_text"].style.color = "#00b0f0" # 勝敗の判定 def check_state(self): if self.koma[:4] == "blue": # 赤を先に判定 a = "2" b = "1" c = "赤" d = "青" elif self.koma[:3] == "red": # 青を先に判定 a = "1" b = "2" c = "青" d = "赤" # 8列を調べる if (str(self.board[0])[0] == a and str(self.board[1])[0] == a and str(self.board[2])[0] == a) \ or (str(self.board[3])[0] == a and str(self.board[4])[0] == a and str(self.board[5])[0] == a) \ or (str(self.board[6])[0] == a and str(self.board[7])[0] == a and str(self.board[8])[0] == a) \ or (str(self.board[0])[0] == a and str(self.board[3])[0] == a and str(self.board[6])[0] == a) \ or (str(self.board[1])[0] == a and str(self.board[4])[0] == a and str(self.board[7])[0] == a) \ or (str(self.board[2])[0] == a and str(self.board[5])[0] == a and str(self.board[8])[0] == a) \ or (str(self.board[0])[0] == a and str(self.board[4])[0] == a and str(self.board[8])[0] == a) \ or (str(self.board[2])[0] == a and str(self.board[4])[0] == a and str(self.board[6])[0] == a): self.state = c + "の勝ちです" element = document["win_msg"] element.textContent = self.state element.style.display = "inline" document["btn_text"].textContent = "もう一回遊ぶ" return if (str(self.board[0])[0] == b and str(self.board[1])[0] == b and str(self.board[2])[0] == b) \ or (str(self.board[3])[0] == b and str(self.board[4])[0] == b and str(self.board[5])[0] == b) \ or (str(self.board[6])[0] == b and str(self.board[7])[0] == b and str(self.board[8])[0] == b) \ or (str(self.board[0])[0] == b and str(self.board[3])[0] == b and str(self.board[6])[0] == b) \ or (str(self.board[1])[0] == b and str(self.board[4])[0] == b and str(self.board[7])[0] == b) \ or (str(self.board[2])[0] == b and str(self.board[5])[0] == b and str(self.board[8])[0] == b) \ or (str(self.board[0])[0] == b and str(self.board[4])[0] == b and str(self.board[8])[0] == b) \ or (str(self.board[2])[0] == b and str(self.board[4])[0] == b and str(self.board[6])[0] == b): self.state = d + "の勝ちです" element = document["win_msg"] element.textContent = self.state element.style.display = "inline" document["btn_text"].textContent = "もう一回遊ぶ" return # ゲームの初期化 def initgame(self, event): if document["btn_text"].textContent != "もう一回遊ぶ": alert(self.board) # テスト表示 return #alert(self.board) # テスト表示 # 盤面を初期化 self.board = [0] * 9 for square in document.select(".square"): square.classList.remove("blue_s") square.classList.remove("blue_m") square.classList.remove("blue_l") square.classList.remove("red_s") square.classList.remove("red_m") square.classList.remove("red_l") document["win_msg"].style.display = "none" # 持ち駒の初期化 for i in range(12): document["box"+str(i+1)].classList.remove("red_s") document["box"+str(i+1)].classList.remove("red_m") document["box"+str(i+1)].classList.remove("red_l") document["box"+str(i+1)].classList.remove("blue_s") document["box"+str(i+1)].classList.remove("blue_m") document["box"+str(i+1)].classList.remove("blue_l") #num = random.randint(1, 3) n = random.randrange(6) if i < 6 and n <= 2: document["box"+str(i+1)].classList.add("red_s") if i < 6 and (n == 3 or n == 4): document["box"+str(i+1)].classList.add("red_m") if i < 6 and n == 5: document["box"+str(i+1)].classList.add("red_l") if i >= 6 and n <= 2: document["box"+str(i+1)].classList.add("blue_s") if i >= 6 and (n == 3 or n == 4): document["box"+str(i+1)].classList.add("blue_m") if i >= 6 and n == 5: document["box"+str(i+1)].classList.add("blue_l") ''' if i == 0 or i == 3: document["box"+str(i+1)].classList.add("red_s") if i == 1 or i == 4: document["box"+str(i+1)].classList.add("red_m") if i == 2 or i == 5: document["box"+str(i+1)].classList.add("red_l") if i == 6 or i == 9: document["box"+str(i+1)].classList.add("blue_s") if i == 7 or i == 10: document["box"+str(i+1)].classList.add("blue_m") if i == 8 or i == 11: document["box"+str(i+1)].classList.add("blue_l") ''' # ゲーム開始 self.state = "青の番" if __name__ == '__main__': main()
「作業フォルダ/templates/」
index.html
<!DOCTYPE html> <head> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>ふたり暮らし メニュー画面</title> </head> <body> <h2>メインメニュー</h2> <p><a href= "/blog" >ブログ(登録とログイン)</a></p> <p><a href= "/game1" >三目並べ(〇×ゲーム)</a></p> </body> </html>
「作業フォルダ/」
main.py
from flask import Flask, render_template app = Flask(__name__) # 三目並べ from game1.server import app as app3 app.register_blueprint(app3, url_prefix="/game1") @app.route('/') def index(): #return render_template('oxgame.html') return render_template('index.html') if __name__ == '__main__': app.run(debug=True)
動作確認
まとめ
まだまだ試作段階なのでソースで使っていない部分も消せてないです。バグもいくつか見つかっています。「もう一回遊ぶ」で持ち駒をランダムにしてみましたが赤と青の力の差がありすぎてしまいます。先攻後攻も決められるようにしたいです。
今回はこちらのサイトを参考にさせていただきました。
PythonでWEBアプリの三目並べを作る(準備編:FlaskとBrythonを利用)
はじめに
三目並べをブラウザ上で動かすにはどうしたらいいか考えました。本当はJavaScriptを使うのがいいのでしょうが、Brythonモジュールを使えばJavaScriptの代わりができるみたいです。
動作環境
Windows10
Python 3.7.5
Flask 1.1.1
Brython 3.8.7
作業フォルダ/ ├ static/ │ ├ brython.js │ ├ brython_stdlib.js │ └ test.py ├ templates/ │ └ sample.html └ main.py
Brythonを使ってみる
Brythonは、Pythonをブラウザ上で動かすことができるようにするモジュールです。
Brythonの公式サイトはこちらです。
https://brython.info/
モジュールのインストール
pip install brython
「brython.js」と「brython_stdlib.js」をダウンロード
https://github.com/brython-dev/brython/releases
こちらのサイトから「Brython-3.8.7.zip」をダウンロードしました。
これで準備完了です。簡単はテストページを作成してみます。
ソースコード
「作業フォルダ」
main.py
from flask import Flask, render_template app = Flask(__name__) @app.route('/') def index(): return render_template('sample.html') if __name__ == '__main__': app.run(debug=True)
Flaskの基本になる起動プログラムです。ブラウザページを開くと「sample.html」が表示されます。
「作業フォルダ/templates/」
sample.html
<html> <head> <script type="text/javascript" src="static/brython.js"></script> </head> <body onload="brython()"> <script type="text/python" src="static/test.py"></script> <input id="text1"> <button id="btn1">click !!</button> </body> </html>
<script type="text/javascript" src="static/brython.js"></script>
staticファルダにある「brython.js」を呼び出します。
<script type="text/python" src="static/test.py"></script>
staticファルダにある「test.py」を呼び出します。
「作業フォルダ/static/」
test.py
from browser import document, alert def echo(event): alert(document["text1"].value) document["btn1"].bind("click", echo)
alert(document["text1"].value)
id「text1」(テキストボックス)のテキストを、ポップアップで表示します。
document["btn1"].bind("click", echo)
id「btn1」(ボタン)をクリックした時に、「echo関数」を呼び出します。
「main.py」を実行して、ブラウザで「127.0.0.1:5000」を開きます。
まとめ
アラートを表示する方法で、print関数の代わりに変数の値を確認したりできます。
これでJavaScriptを一切使わずにPythonでWEBアプリが作れそうです。
JavaScriptを使えばまったく必要ないのでJavaScriptを勉強したほうが早いかもしれませんが、とりあえずこの方法で三目並べを完成させたいと思います。
JavaScriptを使わずBrython/2月の振り返り
2月を振り返って
ブログの更新はしばらく間が空いてしまいましたが、プログラミングの勉強は続けています。今やっているのは三目並べの変化系でテレビなどで紹介されていたアレです。
JavaScriptを使わずにPythonのモジュール「Brython」というのを使ってブラウザ上で三目並べを作り始めています。
マウス操作で駒を移動させたり駒を置いた場所から変数を取得したりを勉強しながらプログラミングしています。
思ったより苦労するところが多くなかなか先に進めないので、ブログに書くのもゴールがある程度見えてからにしようと思います。
今のところはこんな感じまで進んでいます。
Python ミニマックス法で五目並べに挑戦します(AIプログラミング 第8回)
はじめに
まず、5×5の盤面での「五目並べ」を作成してみます。前回の「三目並べ」を少し変更するだけで完成しました。それほど難しくはなかったです。
実行してみましたが時間がかかりすぎます。メモリ不足でフリーズしてしまいました。
3手先まで読むようにプログラムを変更しました。(深さを3に変更しました)
上手く動きました。(5手先までで時間がかかって待つのは限界です)
5×5の盤面での五目並べは引き分けにしかなりません。(勝敗が付かないです)
次に、5×5の盤面での「四目並べ」を作成しました。5手先まで読むようにしています。そこそこいい勝負?はするのですがかなり弱いです。できれば7手先まで読ませるようにしたいです。
アルファベータ法を学ぶ
アルファベータ法とは、基本的にミニマックス法と同じですが計算しなくても同じ結果かそれ以下になる結果の部分を省略して処理時間を短縮する手法です。
9手先まで読ませても時間はかかりますがなんとか動きました。結果はコンピュータが先手でも引き分けになりました。
7手先まで読ませるプログラムだと処理時間は短くなっていい感じです。結果も引き分けになり、先手後手どちらでもコンピュータが負けることはありません。
四目並べ(盤面は5×5)のソースコード
class main():
def __init__(self):
# 盤面の初期化
self.board = [" "] * 25
# 先攻後攻(「1」以外は後攻になる)
self.my_turn = input("先攻 1 : 後攻 2 >> ") == "1"
print(' 1| 2| 3| 4| 5')
print('--------------')
print(' 6| 7| 8| 9|10')
print('--------------')
print('11|12|13|14|15')
print('--------------')
print('16|17|18|19|20')
print('--------------')
print('21|22|23|24|25')
# ゲーム開始
self.state = "プレイ中"
while self.state == "プレイ中":
if self.my_turn:
# プレイヤーの入力
while True:
try:
value = int(input('あなたの番です >> ')) - 1
if self.board[value] == " ":
# 「value」に置く
self.board[value] = " o"
# 勝敗の判定
self.check_state()
# ターン交代
self.my_turn = not(self.my_turn)
break
else:
print('そこには打てません')
except:
print('入力し直してください')
else:
# AIの入力
print("AI")
# 1手目だけ手助け
if self.board[12] == " ":
self.board[12] = " x"
elif self.board[0] != " o" and self.board[6] == " ":
self.board[6] = " x"
else:
# 何手先まで読むか
self.n = 7
# ミニマックス法で最良の手を探索
value = self.max_value(0, -10, 10)
# 「最良の手」に打つ
self.board[value] = " x"
# 勝敗の判定
self.check_state()
# ターン交代
self.my_turn = not(self.my_turn)
# 盤面を表示する
print('{0[0]}|{0[1]}|{0[2]}|{0[3]}|{0[4]}'.format(self.board))
print('--------------')
print('{0[5]}|{0[6]}|{0[7]}|{0[8]}|{0[9]}'.format(self.board))
print('--------------')
print('{0[10]}|{0[11]}|{0[12]}|{0[13]}|{0[14]}'.format(self.board))
print('--------------')
print('{0[15]}|{0[16]}|{0[17]}|{0[18]}|{0[19]}'.format(self.board))
print('--------------')
print('{0[20]}|{0[21]}|{0[22]}|{0[23]}|{0[24]}'.format(self.board))
# 結果を表示する
print(self.state)
# 勝敗の判定
def check_state(self):
for i in range(5):
if self.board[i:20:5] == [" o"," o"," o"," o"] \
or self.board[i+5:25:5] == [" o"," o"," o"," o"] \
or self.board[5*i:5*i+4] == [" o"," o"," o"," o"] \
or self.board[5*i+1:5*i+5] == [" o"," o"," o"," o"] \
or self.board[0:19:6] == [" o"," o"," o"," o"] \
or self.board[1:20:6] == [" o"," o"," o"," o"] \
or self.board[5:24:6] == [" o"," o"," o"," o"] \
or self.board[6:25:6] == [" o"," o"," o"," o"] \
or self.board[3:16:4] == [" o"," o"," o"," o"] \
or self.board[4:17:4] == [" o"," o"," o"," o"] \
or self.board[8:21:4] == [" o"," o"," o"," o"] \
or self.board[9:22:4] == [" o"," o"," o"," o"]:
self.state = "あなたの勝ちです"
return
for i in range(4):
if self.board[i:20:5] == [" x"," x"," x"," x"] \
or self.board[i+5:25:5] == [" x"," x"," x"," x"] \
or self.board[5*i:5*i+4] == [" x"," x"," x"," x"] \
or self.board[5*i+1:5*i+5] == [" x"," x"," x"," x"] \
or self.board[0:19:6] == [" x"," x"," x"," x"] \
or self.board[1:20:6] == [" x"," x"," x"," x"] \
or self.board[5:24:6] == [" x"," x"," x"," x"] \
or self.board[6:25:6] == [" x"," x"," x"," x"] \
or self.board[3:16:4] == [" x"," x"," x"," x"] \
or self.board[4:17:4] == [" x"," x"," x"," x"] \
or self.board[8:21:4] == [" x"," x"," x"," x"] \
or self.board[9:22:4] == [" x"," x"," x"," x"]:
self.state = "AIの勝ちです"
return
if all(state != " " for state in self.board):
self.state = "引き分けです"
return
self.state = "プレイ中"
# ミニマックス法で探索
def max_value(self, depth, alpha, beta):
# 勝敗がついた場合
if self.state != "プレイ中" or depth >= self.n:
# 評価点を出す
return self.evaluate(depth)
best_value = 0
value = -1000
#for i in range(25):
for i in [12, 6, 7, 8, 13, 18, 17, 16, 11, 0, 1, 2, 3, 4, 9, 14, 19, 24, 23, 22, 21, 20, 15, 10, 5]:
# 「i」が空の場合
if self.board[i] == " ":
# 「i」に打つ
self.board[i] = " x"
# 勝敗の判定
self.check_state()
# ターン交代
self.my_turn = not(self.my_turn)
# 相手のターンへ
child_value = self.min_value(depth+1, alpha, beta)
# 1つ前の手に戻す
self.board[i] = " "
# ターン交代
self.my_turn = not(self.my_turn)
# 相手の手がβ値より大きい場合、これ以上の探索はしない(for文を抜ける)
if beta <= child_value:
return child_value
# 良い手が見つかった場合
if child_value > value:
value = child_value
best_value = i
# α値の更新
alpha = child_value
if depth == 0:
# 評価点を表示する
print("(打つ場所:",i+1,"評価点:",child_value,")")
if depth == 0:
return best_value
else:
return value
# 相手のターン
def min_value(self, depth, alpha, beta):
# 勝敗がついた場合
if self.state != "プレイ中" or depth >= self.n:
# 評価点を出す
return self.evaluate(depth)
value = 1000
for j in [12, 6, 7, 8, 13, 18, 17, 16, 11, 0, 1, 2, 3, 4, 9, 14, 19, 24, 23, 22, 21, 20, 15, 10, 5]:
# 「j」が空の場合
if self.board[j] == " ":
# 「j」に打つ
self.board[j] = " o"
# 勝敗の判定
self.check_state()
# ターン交代
self.my_turn = not(self.my_turn)
# コンピュータのターンへ
child_value = self.max_value(depth+1, alpha, beta)
# 1つ前の手に戻す
self.board[j] = " "
# ターン交代
self.my_turn = not(self.my_turn)
# コンピュータの手がα値より小さい場合、これ以上の探索はしない(for文を抜ける)
if alpha >= child_value:
return child_value
# 良い手が見つかった場合
if child_value < value:
value = child_value
# β値の更新
beta = child_value
if depth == 0:
return best_value
else:
return value
# 評価点
def evaluate(self, depth):
if self.state == "AIの勝ちです":
self.state = "プレイ中"
return 10 - depth
elif self.state == "あなたの勝ちです":
self.state = "プレイ中"
return depth - 10
else:
self.state = "プレイ中"
return 0
if __name__ == '__main__':
for i in range(3):
main()
ミニマックス法の「再帰関数」部分をコンピュータの手とプレイヤーの手の2つに分けました。「アルファベータ法」を利用するのに分けた方がわかりやすいです。
for j in [12, 6, 7, 8, 13, 18, 17, 16, 11, 0, 1, 2, 3, 4, 9, 14, 19, 24, 23, 22, 21, 20, 15, 10, 5]:
「アルファベータ法」だと早めに良い手が見つかると処理時間が短縮できるので、真ん中のほうから先に読ませるように変更しました。
まとめ
アルファベータ法の理解が追い付いてないのでまだ処理時間の短縮は可能なのかもしれないですが、とてもじゃないですけど通常の「五目並べ」をミニマックス法で全通りを読み切るのは不可能な気がしてきました。
ミニマックス法での「五目並べ」作成はあきらめました。「アルファベータ法」以上のアルゴリズムが見つかればまた挑戦するかもしれません。
↓よかったらポチッとしていってください。
Pythonで人工知能搭載の三目並べが完成しました(AIプログラミング 第7回)
こちらのサイトを参考にさせていただきまいた。
最後に全てのソースコードを載せておきます。今回新しく学べたことを解説していきます。前回「考えるマッチ箱」でコピペなしでプログラム作成してみたのですが今回「こうすればよかったんだ」と新しく気づけたことが多かったです。「下手でも動く方法を自分で考える」⇒「人の作ったソースコードを参考にする」⇒「新しい発見がある」
この流れが勉強するのには一番良さそうに感じました。
繰り返しリストの作成
# 盤面の初期化 board = [" "] * 9
board = [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '] のリストが作成できます。
input関数でTrueかFalseを返す
# 先攻後攻(「1」以外は後攻になる) my_turn = input("先攻 1 : 後攻 2 >> ") == "1"
[ 1 ]を入力すると「True」、[ 1 ]以外を入力すると「False」を返します。
※「変数 = 」の後に「==」を入れられるのがポイントです。
print関数で改行を使う
print('1|2|3\n-----\n4|5|6\n-----\n7|8|9\n')
1|2|3
-----
4|5|6
-----
7|8|9
が表示されます。※半角の「¥n」で改行になります。
リストを分解して表示する(format関数の使い方)
# 例 board = ["o","x"," ","o","o"," ","x"," "," "] # 盤面を表示する print('{0[0]}|{0[1]}|{0[2]}\n' \ '-----\n{0[3]}|{0[4]}|{0[5]}\n' \ '-----\n{0[6]}|{0[7]}|{0[8]}\n'.format(board))
o|x|
-----
o|o|
-----
x| |
が表示されます。「format(a,b)」を { } 内に「aは0、bは1」で指定できます。
TrueとFalseの切り替え
# ターン交代 self.my_turn = not(self.my_turn)
not()で、Trueは「False」に Falseは「True」に変わります。
三目並べの勝敗の判定
# 勝敗の判定 def check_state(self): for i in range(3): if self.board[i:9:3] == ["o","o","o"] or self.board[3*i:3*i+3] == ["o","o","o"] \ or self.board[0:9:4] == ["o","o","o"] or self.board[2:7:2] == ["o","o","o"]: self.state = "あなたの勝ちです" return if self.board[i:9:3] == ["x","x","x"] or self.board[3*i:3*i+3] == ["x","x","x"] \ or self.board[0:9:4] == ["x","x","x"] or self.board[2:7:2] == ["x","x","x"]: self.state = "AIの勝ちです" return if all(state != " " for state in self.board): self.state = "引き分けです" return self.state = "プレイ中"
タテヨコ斜めの8通り全てを調べてるのにfor文を使っています。(8通りくらいならfor文を使わずに全て並べた方がわかりやすいかもです)
all(state != " " for state in self.board)
リスト「board」の要素がすべて「" "」の時に「True」を返します。
※all関数は、全ての要素がTrueの時に「True」を返します。(andのようなも)
※any関数は、いずれかの要素がTrueの時に「True」を返します。(orのようなもの)
if self.my_turn: if child_value > value: value = child_value best_value = i else: if child_value < value: value = child_value best_value = i
ミニマックス法の評価点を引き継ぐ部分です。
今の局面の新しい評価点が「child_value」、新しく打った場所が「i」です。
自分のターンの時は現在持っているの評価点より新しい評価点が大きい場合は、新しい評価点に置き換えます。新しく打った場所も記録します。
相手のターンの時は現在持っているの評価点より新しい評価点が小さい場合は、新しい評価点に置き換えます。新しく打った場所も記録します。
最後に全てのソースコードを載せておきます。
三目並べでミニマックス法を使ったソースコード
class main():
def __init__(self):
# 盤面の初期化
self.board = [" "] * 9
# 先攻後攻(「1」以外は後攻になる)
self.my_turn = input("先攻 1 : 後攻 2 >> ") == "1"
print('1|2|3\n-----\n4|5|6\n-----\n7|8|9\n')
# ゲーム開始
self.state = "プレイ中"
while self.state == "プレイ中":
if self.my_turn:
# プレイヤーの入力
while True:
try:
value = int(input('あなたの番です >> ')) - 1
if self.board[value] == " ":
# 「value」に置く
self.board[value] = "o"
# 勝敗の判定
self.check_state()
# ターン交代
self.my_turn = not(self.my_turn)
break
else:
print('そこには打てません')
except:
print('入力し直してください')
else:
# AIの入力
print("AI")
# ミニマックス法で最良の手を探索
value = self.minimax(0)
# 「最良の手」に打つ
self.board[value] = "x"
# 勝敗の判定
self.check_state()
# ターン交代
self.my_turn = not(self.my_turn)
# ボードを表示する
print('{0[0]}|{0[1]}|{0[2]}\n' \
'-----\n{0[3]}|{0[4]}|{0[5]}\n' \
'-----\n{0[6]}|{0[7]}|{0[8]}\n'.format(self.board))
# 結果を表示する
print(self.state)
# 勝敗の判定
def check_state(self):
for i in range(3):
if self.board[i:9:3] == ["o","o","o"] or self.board[3*i:3*i+3] == ["o","o","o"] \
or self.board[0:9:4] == ["o","o","o"] or self.board[2:7:2] == ["o","o","o"]:
self.state = "あなたの勝ちです"
return
if self.board[i:9:3] == ["x","x","x"] or self.board[3*i:3*i+3] == ["x","x","x"] \
or self.board[0:9:4] == ["x","x","x"] or self.board[2:7:2] == ["x","x","x"]:
self.state = "AIの勝ちです"
return
if all(state != " " for state in self.board):
self.state = "引き分けです"
return
self.state = "プレイ中"
# ミニマックス法で探索
def minimax(self, depth):
# 勝敗がついた場合
if self.state != "プレイ中":
# 評価点を出す
return self.evaluate(depth)
best_value = 0
if self.my_turn:
value = 100
else:
value = -100
for i in range(9):
# 「i」が空の場合
if self.board[i] == " ":
# 「i」に打つ
if self.my_turn:
self.board[i] = "o"
else:
self.board[i] = "x"
# 勝敗の判定
self.check_state()
# ターン交代
self.my_turn = not(self.my_turn)
# 再帰
child_value = self.minimax(depth+1)
if self.my_turn:
if child_value > value:
value = child_value
best_value = i
else:
if child_value < value:
value = child_value
best_value = i
# 1つ前の手に戻す
self.board[i] = " "
# ターン交代
self.my_turn = not(self.my_turn)
# 評価点を表示する
if depth == 0:
print("(打つ場所:",i+1,"評価点:",child_value,")")
if depth == 0:
return best_value
else:
return value
# 評価点
def evaluate(self, depth):
if self.state == "AIの勝ちです":
self.state = "プレイ中"
return 10 - depth
elif self.state == "あなたの勝ちです":
self.state = "プレイ中"
return depth - 10
else:
self.state = "プレイ中"
return 0
if __name__ == '__main__':
for i in range(3):
main()
↓よかったらポチッとしていってください。
Python 再帰関数をもう少し詳しく学ぶ(AIプログラミング 第6回)
はじめに
実践で「再帰関数」を使ってみようと思ったのですが、少し気になったことがあったのでもう少し詳しく学んでいきます。
応用(動きを追いかける)
前回の最後の例を少し変えました。
def func4(n): if n >= 3: return for i in range(2): #print("n=", n,"i=", i) # 再帰 func4(n+1) print("n=", n,"i=", i) return func4(0)
値の表示を「再帰」の後にもってきてみました。
for文に書き直して考えてみます。
for i in range(2): for j in range(2): for k in range(2): print("n=", 2,"k=", k) print("n=", 1,"j=", j) print("n=", 0,"i=", i)
結果はこちらです。
n= 2 k= 0 n= 2 k= 1 n= 1 j= 0 n= 2 k= 0 n= 2 k= 1 n= 1 j= 1 n= 0 i= 0 n= 2 k= 0 n= 2 k= 1 n= 1 j= 0 n= 2 k= 0 n= 2 k= 1 n= 1 j= 1 n= 0 i= 1
図にするとこうなります。
「再帰関数」の前に記述すると上から処理していきますが、「再帰関数」の後に記述すると下から処理していくのがわかります。
「ゲーム木」で「再帰関数」を扱う場合は、下から処理していく必要があります。
三目並べのゲーム木(ミニマックス法)
拡大します。この部分で解説していきます。
プログラムの作り方
「再帰関数」を作ってその後に処理をします。上の図では①~⑫の順で処理することになります。
勝敗が決まった場合(引き分けも含みます)
・評価点をつけます。勝った時は「10 - [深さ] 」点、負けた時は「[深さ] - 10 」点、引き分けの時は0点にします。(早く勝てば点数が高く、早く負けると点数が低い)
・その「再帰関数」は終了して、評価点を返します。
勝敗が決まっていない場合(まだ手が打てる場合)
・自分(AI)のターンの時は、(一つ下の深さの評価点から)一番低い点数を引き継ぎます。※相手に打たれると一番都合の悪いルートを選びます。
・相手(プレイヤー)のターンの時は、(一つ下の深さの評価点から)一番高い点数を引き継ぎます。※自分が打てる一番良いルートを選びます。
深さが一番上の場合(n = 0)
・すべての「再帰関数」が終了し、打つ場所の値を返します。
深さが一番上でない場合(n > 0)
・その「再帰関数」は終了して、評価点を返します。
ちょっと細かいところまで詰められていませんが、この流れでいけそうです。
次回は、ミニマックス法を使って三目並べを作成したいと思います。
Python 再帰関数をわかりやすく解説(AIプログラミング 第5回)
はじめに
「ゲーム木」を扱う上で「再帰関数」はほぼ必須っぽかったので詳しく勉強していきます。
再帰関数とは
自分自身を呼び出す関数です。そもそも「再帰」という言葉で検索することは少ないと思います。「def関数内で自分自身の関数を使う」「def内でループしている」などで検索していて「再帰関数」という言葉が出てくることが多いと思います。
基本(前回のおさらい)
例:
def func(n): if n <= 0: return n return n + func(n-1) print(func(10))
実行してみると「55」(1から10までの和)が表示されました。
print(func(10))
func関数を「n=10」で呼び出しています。
return n + func(n-1)
func(10)で「10 + func(9)」を返します。
func(9)で「9 + func(8)」を返します。
func(8)で「8 + func(7)」を返します。
:
func(1)で「1 + func(0)」を返します。
func(0)で(if n <= 0:)「0」を返します。
一行にまとめます
func(10)で「10 + 9 + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1 + 0」を返します。
つまり、print(func(10))で「55」を表示することになります。
for文で書くとどうなるか
def func2(n): total = 0 for i in range(n, 0, -1): total += i return total print(func2(10))
あれ?そんなに変わらない?
for i in range(n, 0, -1):
これでは「0」を返していないので、厳密には「for i in range(n, -1, -1):」がいいかもしれないです。また「for i in range(n+1):」でも結果は同じですが、処理をする順番を考えると「10, 9, 8,・・, 2, 1, 0」が正解だと思います。
ここまではいろんなサイトで詳しく解説されています。問題はこの先です。
調べていても「再帰関数」の基本だけのところや基本の後に実践で使われたりしているので、いきなり実践だともう理解できなくなりました。
「基本ー応用ー実践」の「応用」の部分が大事だと思いますので、そのあたりを詳しく解説していきます。
応用(動きを追いかける)
例えば下記のプログラムでは「n」と「i」の値はどう変化するでしょうか。
def func3(n): if n >= 2: return for i in range(2): print("n=", n,"i=", i) # 再帰 func3(n+1) return func3(0)
とてもじゃないですが頭の中だけでは動きを追いきれないと思います。
実際に「print("n=", n,"i=", i)」で表示されるのはこうなります。
n= 0 i= 0 n= 1 i= 0 n= 1 i= 1 n= 0 i= 1 n= 1 i= 0 n= 1 i= 1
わかりやすく図にしてみます。
少しわかりにくいかもしれませんが、〇の中の数字が表示される順番です。
「n= 1 i= 0」と「n= 1 i= 1」は2回表示されています。
ここでピンときた方はすごいです。まだここでは気づきませんでした。
続けてこのプログラムではどうなるでしょうか。
def func3(n): if n >= 2: return for i in range(3): print("n=", n,"i=", i) # 再帰 func3(n+1) return func3(0)
結果はこう表示されます。
n= 0 i= 0 n= 1 i= 0 n= 1 i= 1 n= 1 i= 2 n= 0 i= 1 n= 1 i= 0 n= 1 i= 1 n= 1 i= 2 n= 0 i= 2 n= 1 i= 0 n= 1 i= 1 n= 1 i= 2
図にするとこうなります。
ここであることに気づきました。
下の図をご覧ください。
まさに「ゲーム木(木構造)」です。「n」が選択回数(深さ)で「i」が選択できる数です。つまり、三択を2回行っていることになります。
もう一つだけ例をあげます。
def func3(n): if n >= 3: return for i in range(2): print("n=", n,"i=", i) # 再帰 func3(n+1) return func3(0)
結果を図であらわすとこちらです。
2択「i」を3回「n」繰り返しています。頭の中だけでは動きは追えませんし、なぜこのような順番になるのかも今のところ理解できていません。
for文で書くとどうなるか
for i in range(2): print("n=", 0,"i=", i) for j in range(2): print("n=", 1,"j=", j) for k in range(2): print("n=", 2,"k=", k)
思ったよりシンプルです。これなら動きは追えそうです。
結果はこう表示されます。
n= 0 i= 0 n= 1 j= 0 n= 2 k= 0 n= 2 k= 1 n= 1 j= 1 n= 2 k= 0 n= 2 k= 1 n= 0 i= 1 n= 1 j= 0 n= 2 k= 0 n= 2 k= 1 n= 1 j= 1 n= 2 k= 0 n= 2 k= 1
(すべてのfor文で「i」を使っても大丈夫でしたが)for文を「i」「j」「k」で区別することで「ゲーム木(木構造)」にした時のどの位置(深さ)なのかがわかりやすくなりました。
これで「ゲーム木」を扱う上で「再帰関数」はほぼ必須な理由がわかりました。
あとは「三目並べ」でなら、その局面で勝負が付いたら点数をつけ、勝負がまだ付いてなければ次の手を考えることで「再帰関数」を使えそうです。
ミニマックス法はわかったけどソースコードがわからない(AIプログラミング 第4回)
はじめに
三目並べに限らず、チェスや将棋などの対戦ゲームでは何手目か先まで読んで一番良い手を打ちます。もし最後までの全通りを読めれば最強です。
それをコンピュータでやってしまおうというのが今回の試みです。
木構造(ツリー構造)とは?
局面が枝分かれして木のようなデータ構造のことをいいます。(始まりを「根(ルート)」、分かれ道を「枝(エッジ)」、次の局面を「節点(ノード)」、最終局面を「葉(リーフ)」と呼ぶことが多いです)
「ここに打てば相手はこう打ってきて、次はあそこに打とう」「こっちに打てばあそこに打たれたら負ける」と人が考えるのを「ゲーム木(木構造)」といい、機械学習などでよく使われます。
「完全ゲーム木」を使えば、全通りを試してみて最良を手を探すことが出来ます。
ミニマックス法とは?
コンピュータに思考させるためのアルゴリズムの一つです。簡単にいうと「自分の手と相手の手に点数を付けて一番有利な次の手を探す方法」です。
自分が打てる手に複数の候補がある時に最高maxの点数の手、相手が打てる手に複数の候補がある時に最低mini(相手にとっては最高)の点数の手を選びます。それを最終局面「葉」から現在の局面「根」までさかのぼって計算して繰り返します。
オセロや将棋だと最終局面まで全通り(完全ゲーム木)で読み切るには局面が膨大になりすぎて、計算処理する多大な時間が必要になります。(オセロの必勝法はまだ解析されていません。昔なにかで解析まで100年以上かかるような記事を見ました。6×6のオセロは後手必勝で解析の処理時間は100時間以内らしいです)また数手先までのゲーム木だと点数の付け方が難しいです。
ミニマックス法はわかったけど、どのようにプログラミングをすればいいかわかりません。
見つけました。こちらのサイトにPythonのソースコードとミニマックス法が詳しく解説されています。早速プログラムを拝借させていただきました。
動きました。AIと勝負しても勝てませんでした。
すごく短いプログラムで感動しました。じっくり解読していきます。いろんな技が使われていてとても勉強になります。ここでどうしても理解できない部分が出てきました。
「再帰関数」
def関数の中に自分自信を呼び出す関数があり、ループしているように見えます。
ググってみると「再帰」という言葉が見つかりました。説明を読んでも難しそうだったので簡単な例から実際の動きを追ってみたいと思います。
例:
def func(n): if n <= 0: return n return n + func(n-1) print(func(10))
実行してみると「55」(1から10の和)が表示されました。
print(func(10))
func関数を「n=10」で呼び出しています。
return n + func(n-1)
func(10)で「10 + func(9)」を返します。
func(9)で「9 + func(8)」を返します。
func(8)で「8 + func(7)」を返します。
:
func(1)で「1 + func(0)」を返します。
func(0)で(if n <= 0:)「0」を返します。
一行にまとめます
func(10)で「10 + 9 + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1 + 0」を返します。
つまり、print(func(10))で「55」を表示することになります。
なんとなく「再帰関数」の基本はわかったような気がしますが、まだしっくり来てないです。(このままミニマックス法に移っても再帰関数でつまづきそう)
次回は、再帰関数についてもう少し深く勉強していきたいと思います。
学習したマッチ箱と勝負する(AIプログラミング 第3回)
考えるマッチ箱
前回、「マッチ箱」に三目並べを学習させました。2097個のマッチ箱と勝負して果たして勝てるかという実験です。
先手を入力できるように変更したソースコードがこちらです。
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(10):
# 盤面
board_1 = [0,0,0,0,0,0,0,0,0]
# 1手目(入力)==========
print("1┃2┃3")
print("━✚━✚━")
print("4┃5┃6")
print("━✚━✚━")
print("7┃8┃9")
sente = int(input("1手目を入力してください。"))
board_1[sente-1] = 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:])
n = random.randrange(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]
# 3手目(入力)==========
board_3 = board_2[:]
print(board_3[:3])
print(board_3[3:6])
print(board_3[6:9])
next_move = -1
while next_move == -1:
sente = int(input("3手目を入力してください。"))
if sente <= 9 and board_3[sente-1] == 0:
next_move = sente - 1
board_3[next_move] = 1
board_3[next_move+9] = 0
# 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[:])
board_4 = reader[box2][:]
n = sum(board_4[9:])
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)
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]
# 5手目(ランダム)==========
board_5 = board_4[:]
print(board_5[:3])
print(board_5[3:6])
print(board_5[6:9])
next_move = -1
while next_move == -1:
sente = int(input("5手目を入力してください。"))
if board_5[sente-1] == 0:
next_move = sente - 1
board_5[next_move] = 1
board_5[next_move+9] = 0
#「〇」の勝ちを確認
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(board_5[:3])
print(board_5[3:6])
print(board_5[6:9])
print("「〇」の勝ちです")
# ここに負けた時の処理
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[:])
board_6 = reader[box3][:]
n = sum(board_6[9:])
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)
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]
#「×」の勝ちを確認
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(board_6[:3])
print(board_6[3:6])
print(board_6[6:9])
print("「×」の勝ちです")
# ここに勝った時の処理
continue
# 7手目(ランダム)==========
board_7 = board_6[:]
print(board_7[:3])
print(board_7[3:6])
print(board_7[6:9])
next_move = -1
while next_move == -1:
sente = int(input("7手目を入力してください。"))
if board_7[sente-1] == 0:
next_move = sente - 1
board_7[next_move] = 1
board_7[next_move+9] = 0
#「〇」の勝ちを確認
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(board_7[:3])
print(board_7[3:6])
print(board_7[6:9])
print("「〇」の勝ちです")
# ここに負けた時の処理
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[:])
board_8 = reader[box4][:]
n = sum(board_8[9:])
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)
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]
#「×」の勝ちを確認
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(board_8[:3])
print(board_8[3:6])
print(board_8[6:9])
print("「×」の勝ちです")
# ここに勝った時の処理
continue
# 9手目(ランダム)
board_9 = board_8[:]
sente = random.randrange(1)
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
#「〇」の勝ちを確認
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(board_9[:3])
print(board_9[3:6])
print(board_9[6:9])
print("「〇」の勝ちです")
# ここに負けた時の処理
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(board_9[:3])
print(board_9[3:6])
print(board_9[6:9])
print("引き分けです")
# ここに引き分けの時の処理
# csvファイルに書き込み
with open("matchbox.csv", "w", encoding="Shift_jis") as f: # 文字コードをShift_JISに指定
writer = csv.writer(f, lineterminator="\n") # writerオブジェクトの作成 改行記号で行を区切る
writer.writerows(reader) # csvファイルに書き込み
1~9以外の数字を入力すると、おかしくなったりエラーになりますが気にしません。
勝負しました。はい、勝てませんでした。成功です。
勝った時に報酬を与えていないので、ここに打ったら勝てるという盤面でも勝つ手を打ってくれないです。(負けない手を打ちます)
次回は、「オセロ」もやってみたいのですが、先に「ミニマックス法」が気になるので引き続き「三目並べ」を使って勉強していきたいと思います。
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」が増えていくと学習している実感がわきました。
今回は勝った時に報酬を与えていないので、ここに打ったら勝てるという盤面でも勝つ手を打ってくれないことも多そうです。(負けない手を打ちます)
次回は、先手をランダムから選んで打てるように変更して、学習したマッチ箱と勝負してみたいと思います。
簡単な人工知能を作ってみる(AIプログラミング 第1回)
AIとは?
AI(エーアイ)とは人工知能のことです。Artificial Intelligence(人工的な知能)です。
『「言語の理解や推論、問題解決などの知的行動を人間に代わってコンピューターに行わせる技術」、または、「計算機(コンピュータ)による知的な情報処理システムの設計や実現に関する研究分野」ともされる。』==Wikipediaより引用==
AIの定義
実はまだ専門家の間でも定まっていないらしいです。「知能を持つ機械」とも言われますし、「知能の定義が明確でないので、人工知能も定義できない」という意見もあります。
その中で「考える機械」ってのが一番しっくりきました。画像認識、音声認識、自動運転やAI将棋などは人工知能になると思います。電卓や翻訳機のような機械はちょっと人工知能とは違うような気がします。
簡単なAIを作ってみる
「考えるマッチ箱」という面白い記事を見つけました。「三目並べ(3×3の〇×ゲームです)」をコンピュータを使わずに機械学習をさせる方法です。
今回、三目並べを後攻で負けないように学習させてみたいと思います。(先手を〇、後手を×にします)
学習手順
まず、「マッチ箱」と「9色のビーズ(同じマスの場所には同じ色のビーズが対応します)」を大量に用意します。
こちらのサイトを見るとイメージしやすいかもしれません。
準備ができたらゲームを始めます。人間が先手、マッチ箱が後手とします。
1.先手が打ったら2手目を考えるマッチ箱を1つ作ります。そのマッチ箱の中に9色のビーズのうち、すでに〇か✖がある場所の色のビーズを抜いて、残りのビーズを入れます。(2回目以降でこの盤面のマッチ箱があればそっちのマッチ箱を使います)
例:
・┃・┃・
━✚━✚━
・┃〇┃・
━✚━✚━
・┃・┃・
この場合、マッチ箱の中に真ん中の色を抜いた8色のビーズが入ります。
2.マッチ箱からランダムに1つのビーズを取り出し、その色に対応した場所に✖を打ちます。取り出したビーズは元のマッチ箱に戻します。
例:
✖┃・┃・
━✚━✚━
・┃〇┃・
━✚━✚━
・┃・┃・
3.3手目を先手が打ったら4手目を考えるマッチ箱を1つ作ります。そのマッチ箱の中に9色のビーズのうち、すでに〇か×がある場所の色のビーズを抜いて、残りのビーズを入れます。(2回目以降でこの盤面のマッチ箱があればそっちのマッチ箱を使います)
例:
✖┃〇┃・
━✚━✚━
・┃〇┃・
━✚━✚━
・┃・┃・
この場合、マッチ箱の中に〇✖の3つの場所の色を抜いた6色のビーズが入ります。
4.マッチ箱からランダムに1つのビーズを取り出し、その色に対応した場所に✖を打ちます。取り出したビーズは元のマッチ箱に戻します。
例:
✖┃〇┃・
━✚━✚━
・┃〇┃・
━✚━✚━
・┃・┃✖
ランダムなのでここではマッチ箱側は適当に打ちます。
5.5手目を先手が打ったら勝負がついたかどうか(先手〇が一列に並んだかどうか)を判断します。
勝負がついてない場合は6手目を考えるマッチ箱を1つ作ります。そのマッチ箱の中に9色のビーズのうち、すでに〇か×がある場所の色のビーズを抜いて、残りのビーズを入れます。
例:
✖┃〇┃・
━✚━✚━
・┃〇┃・
━✚━✚━
・┃〇┃✖
もし勝負がついたら「4」のマッチ箱から取り出した色のビーズを捨てます。そして「1」からやり直します。※こうすることで次回4手目に同じ盤面がきた時、負けた手を打たなくなります。
6.勝負がつくまで同じようにマッチ箱がなければ新しいマッチ箱を作りながら進めていきます。勝負がつくか9手目で引き分けになれば「1」に戻ります。
これを何度も繰り返すと負ける度にマッチ箱の中のビーズが減っていきます。(マッチ箱は盤面の数だけ増えていきます)
7.どんどんビーズを捨てていくと、ビーズが入っていないマッチ箱が出てきます。その場合はその直前の手のビーズを捨てます。
例:
✖┃・┃・
━✚━✚━
・┃〇┃〇
━✚━✚━
・┃✖┃〇
この場合、次の6手目は、どこに✖を打っても負けます。その前の4手目の「✖」がダメだったので4手目のビーズを捨てます。
例:
✖┃・┃・
━✚━✚━
・┃〇┃・
━✚━✚━
・┃✖┃〇
次回からこの盤面では下の「✖」は打たなくなります。
これを何千回、何万回繰り返すことで絶対負けないマッチ箱が完成するはずです。
後手限定の勝った時に報酬を与えない方法ですが、次回は実際にPythonを使ってプログラミングしてみたいと思います。
こちらのサイトを参考にさせていただきました。