Pythonでマルバツゲームをつくろう #4 ランダムボット編

今回は、マルバツゲームで、ランダムに手を選ぶボット🤖と対戦できるようにしましょう。

前回:Pythonでマルバツゲームをつくろう #3.5 見た目を色々変えてみよう

前回までのコード
from IPython.display import clear_output

O = "○"
X = "×"

current_player = O                             # 現在のプレイヤー
turn = 0                                          # 今何ターン目か
board = [                                         # 盤面(リスト)
    "𝟶", "𝟷", "𝟸",
    "𝟹", "𝟺", "𝟻",
    "𝟼", "𝟽", "𝟾"
]

print(f" {board[0]} │ {board[1]} │ {board[2]} ")  # 最初の盤面を表示
print("───┼───┼───")
print(f" {board[3]} │ {board[4]} │ {board[5]} ")
print("───┼───┼───")
print(f" {board[6]} │ {board[7]} │ {board[8]} ")

while turn < 9:
    turn += 1                                # ターンを進める
    print(f"次は {current_player} の番です")
    clear_output(wait=True)                  # 新たに出力され次第、表示済みの出力を消去
    while True:                              # ユーザの入力を受け取る
        try:
            index = int(input(f"{current_player} の手 : "))
            if board[index] != O and board[index] != X:
                break
            else:
                print("𝟶 ~ 𝟾 の空いているマスを選んでください")
        except ValueError:
            print("整数を入力してください")
        print(f" {board[0]} │ {board[1]} │ {board[2]} ")
        print("───┼───┼───")
        print(f" {board[3]} │ {board[4]} │ {board[5]} ")
        print("───┼───┼───")
        print(f" {board[6]} │ {board[7]} │ {board[8]} ")
        clear_output(wait=True)
    board[index] = current_player                       # 入力手を盤面に適用
    print(f" {board[0]} │ {board[1]} │ {board[2]} ")    # 現在の盤面を表示
    print("───┼───┼───")
    print(f" {board[3]} │ {board[4]} │ {board[5]} ")
    print("───┼───┼───")
    print(f" {board[6]} │ {board[7]} │ {board[8]} ")
                                                         # 勝利判定
    if  (board[0] == board[1] == board[2] == current_player) or \
        (board[3] == board[4] == board[5] == current_player) or \
        (board[6] == board[7] == board[8] == current_player) or \
        (board[0] == board[3] == board[6] == current_player) or \
        (board[1] == board[4] == board[7] == current_player) or \
        (board[2] == board[5] == board[8] == current_player) or \
        (board[0] == board[4] == board[8] == current_player) or \
        (board[2] == board[4] == board[6] == current_player):
        print(f"勝者: {current_player}")
        break
    current_player = O if current_player == X else X  # 手番交代
else:
    print("引き分け")                                           # 引き分け

1. マルバツゲームにランダムボットのターンを追加

=人間、×=ボット

  • 最初は○が人間、×はボットということにしましょう。○の番に人間が打つという条件に変更するのは簡単です。
if current_player == O:  # これを付け加えるだけ
    while True:  # ユーザ入力部分
        (省略)
  • while文による入力部分を、current_playerO(="○")である時のみ実行するよう、if文で囲めばいいのです。

ボットの処理はelseブロックに

  • これに対するロボットの処理はelse文に書きます。
  • その前にrandomモジュールとtimeモジュールをコードの冒頭でimportしておきます。
import time, random
  • それでは、else文内の処理について見ていきましょう。
else:  # もしcurrent_playerがXなら
    print("ボット考え中...")
    time.sleep(2)
    index = random.choice([index for index, value in enumerate(board) if value not in ("○", "×")])
    print(f"ボットの手: {index}")

ただ2秒待つだけのコード

  • 最初の2行は、ボット考え中…print関数によって表示してから、time.sleep(2)によって2秒間何もしないで待つ、というコードです。これは、ユーザが打った瞬間にボットが打ち返してこないようにするためです。
index = random.choice([index for index, value in enumerate(board) if value not in ("○", "×")])
  • この部分は、
lst = []
for index, value in enumerate(board):
    if value not in ("○", "×"):
        lst.append(index)
index = random.choice(lst)

このように書いているのと同じ意味です。まずはこちらを見ていきます。

次の手の候補を格納するリストlst

  • lst = []の部分でlstという名前の空のリストを作っています。このリストには、次打てる場所、すなわち空きマスのインデックスが格納されます。

for文で実際に次に打てる手を一つ一つ格納していく

  • その下のfor文というのは、while文と同じくループを表現する書き方で、for i in [リスト]と書くと、ループのたびにリストから一つずつ順番に要素を取り出して変数iに入れる、という意味になります。
board = [
    "𝟶", "𝟷", "𝟸", ...
]

for item in board:  # boardリスト内の要素を一つずつ順番に取り出していく
    print(item)  # 処理部分
  • 例えばこのコードなら、for文はboardリスト内の一つ一つの要素を順番取り出してitemに格納し、処理部分でitemprintしているので、
𝟶
𝟷
𝟸

と表示されます。

要素とインデックスをペアで返してくれるenumerate関数

  • enumerate関数は、要素に加えてインデックスも一緒に返してくれる関数です。
for index, value in enumerate(board):  # enumerate(board)とすることで、要素だけでなくインデックスも取り出せる
    print(index, value)

これを実行すると、

0 𝟶
1 𝟷
2 𝟸

このように、インデックスとそれに対応する要素が一緒に返されます。

実際のコード

  • さて、実際にはfor index, value in enumerate(board):の後の処理は以下のようになっていました。
if value not in ("○", "×"):
    lst.append(index)
  • これは、もしvalueすなわちboard内の要素( "𝟶", "𝟷", …) が("○", "×")というタプルに含まれていなければ、lstリストにindexを追加する、という意味です。つまり、まだ打たれていないインデックスであればlstに追加するということです。

random.choice関数

index = random.choice(lst)
  • そして、randomモジュールのchoice関数によって、lstリスト内のどれかの要素がランダムに選ばれ、変数indexに格納されるのです。
  • これでユーザはボットと対戦できるようになりました。
ここまでのコード
from IPython.display import clear_output

O = "○"
X = "×"

current_player = O                             # 現在のプレイヤー
turn = 0                                          # 今何ターン目か
board = [                                         # 盤面(リスト)
    "𝟶", "𝟷", "𝟸",
    "𝟹", "𝟺", "𝟻",
    "𝟼", "𝟽", "𝟾"
]

print(f" {board[0]} │ {board[1]} │ {board[2]} ")  # 最初の盤面を表示
print("───┼───┼───")
print(f" {board[3]} │ {board[4]} │ {board[5]} ")
print("───┼───┼───")
print(f" {board[6]} │ {board[7]} │ {board[8]} ")

while turn < 9:
    turn += 1                                # ターンを進める
    print(f"次は {current_player} の番です")
    clear_output(wait=True)                  # 新たに出力され次第、表示済みの出力を消去
    if current_player == O:               # もしcurrent_playerがOなら
        while True:                          # ユーザの入力を受け取る
            try:
                index = int(input(f"{current_player} の手 : "))
                if board[index] != O and board[index] != X:
                    break
                else:
                    print("𝟶 ~ 𝟾 の空いているマスを選んでください")
            except ValueError:
                print("整数を入力してください")
            print(f" {board[0]} │ {board[1]} │ {board[2]} ")
            print("───┼───┼───")
            print(f" {board[3]} │ {board[4]} │ {board[5]} ")
            print("───┼───┼───")
            print(f" {board[6]} │ {board[7]} │ {board[8]} ")
            clear_output(wait=True)
    else:                                    # もしcurrent_playerがXなら
        print("ボット考え中...")
        time.sleep(2)
        index = random.choice([index for index, value in enumerate(board) if value not in ("○", "×")])
        print(f"ボットの手: {index}")
    board[index] = current_player                       # 入力手を盤面に適用
    print(f" {board[0]} │ {board[1]} │ {board[2]} ")    # 現在の盤面を表示
    print("───┼───┼───")
    print(f" {board[3]} │ {board[4]} │ {board[5]} ")
    print("───┼───┼───")
    print(f" {board[6]} │ {board[7]} │ {board[8]} ")
                                                         # 勝利判定
    if  (board[0] == board[1] == board[2] == current_player) or \
        (board[3] == board[4] == board[5] == current_player) or \
        (board[6] == board[7] == board[8] == current_player) or \
        (board[0] == board[3] == board[6] == current_player) or \
        (board[1] == board[4] == board[7] == current_player) or \
        (board[2] == board[5] == board[8] == current_player) or \
        (board[0] == board[4] == board[8] == current_player) or \
        (board[2] == board[4] == board[6] == current_player):
        print(f"勝者: {current_player}")
        break
    current_player = O if current_player == X else X  # 手番交代
else:
    print("引き分け")                                           # 引き分け

2. マルバツゲームのユーザが○か×かランダムに決定

  • 次は、ユーザが○か×か、ランダムに決まるようにします。
  • そのためには、現在○の番か×の番かを管理するための変数current_playerに加えて、ユーザが○か×かを格納する変数USERを用意し、○か×が半々の確率で格納されるようにします。
USER = O if random.choice([True, False]) else X
print(f"あなたは {USER} です")
  • ここでもrandomモジュールのchoice関数を使うことにします。リストの中からランダムで要素を選ぶので、1/2の確率でTrueが選ばれUSERは○になるか、もしくは1/2の確率でFalseが選ばれてUSERは×になります。
  • それに加えて、
if current_player == O:

となっていた部分は、

if current_player == USER:

に変えます。

完成!

from IPython.display import clear_output

O = "○"
X = "×"
USER = O if random.choice([True, False]) else X

current_player = O                             # 現在のプレイヤー
turn = 0                                          # 今何ターン目か
board = [                                         # 盤面(リスト)
    "𝟶", "𝟷", "𝟸",
    "𝟹", "𝟺", "𝟻",
    "𝟼", "𝟽", "𝟾"
]

print(f"あなたは {USER} です")
print(f" {board[0]} │ {board[1]} │ {board[2]} ")  # 最初の盤面を表示
print("───┼───┼───")
print(f" {board[3]} │ {board[4]} │ {board[5]} ")
print("───┼───┼───")
print(f" {board[6]} │ {board[7]} │ {board[8]} ")

while turn < 9:
    turn += 1                                # ターンを進める
    print(f"次は {current_player} の番です")
    if current_player == USER:               # もしcurrent_playerがOなら
        clear_output(wait=True)              # ⭐️
        while True:                          # ユーザの入力を受け取る
            try:
                index = int(input(f"{current_player} の手 : "))
                if board[index] != O and board[index] != X:
                    break
                else:
                    print("𝟶 ~ 𝟾 の空いているマスを選んでください")
            except ValueError:
                print("整数を入力してください")
            print(f" {board[0]} │ {board[1]} │ {board[2]} ")
            print("───┼───┼───")
            print(f" {board[3]} │ {board[4]} │ {board[5]} ")
            print("───┼───┼───")
            print(f" {board[6]} │ {board[7]} │ {board[8]} ")
            clear_output(wait=True)          # ⭐️
    else:                                    # もしcurrent_playerがXなら
        print("ボット考え中...")
        clear_output(wait=True)              # ⭐️
        time.sleep(2)
        index = random.choice([index for index, value in enumerate(board) if value not in ("○", "×")])
        print(f"ボットの手: {index}")
    board[index] = current_player                       # 入力手を盤面に適用
    print(f" {board[0]} │ {board[1]} │ {board[2]} ")    # 現在の盤面を表示
    print("───┼───┼───")
    print(f" {board[3]} │ {board[4]} │ {board[5]} ")
    print("───┼───┼───")
    print(f" {board[6]} │ {board[7]} │ {board[8]} ")
                                                         # 勝利判定
    if  (board[0] == board[1] == board[2] == current_player) or \
        (board[3] == board[4] == board[5] == current_player) or \
        (board[6] == board[7] == board[8] == current_player) or \
        (board[0] == board[3] == board[6] == current_player) or \
        (board[1] == board[4] == board[7] == current_player) or \
        (board[2] == board[5] == board[8] == current_player) or \
        (board[0] == board[4] == board[8] == current_player) or \
        (board[2] == board[4] == board[6] == current_player):
        print(f"勝者: {current_player}")
        break
    current_player = O if current_player == X else X  # 手番交代
else:
    print("引き分け")                                           # 引き分け

clear_outputの位置は、表示が適切に異なるようなタイミングに移動してあります。

次回:
Pythonでマルバツゲームをつくろう #5 最強ボット編

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です