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文に書きますが、その前にrandomモジュールとtimeモジュールをコードの冒頭でimportしておきます。
import time, random
  • それでは、else文内の処理について見ていきましょう。
if current_player == O:
    (省略)
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行は、ボット考え中…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という名前の空のリストを作っています。
  • その下のfor文というのは、while文と同じくループを表現する書き方で、for i in [リスト]と書くと、ループのたびにリストから一つずつ順番に要素を取り出して変数iに入れる、という意味になります。
board = [
    "𝟶", "𝟷", "𝟸", ...
]

for item in board:  # boardリスト内の要素を一つずつ順番に取り出していく
    print(item)  # 処理部分

例えばこのコードなら、for文はboardリスト内の一つ一つの要素を順番取り出してitemに格納し、処理部分でitemprintしているので、

𝟶
𝟷
𝟸

と表示されます。

  • boardリストはインデックスによって要素を指定しますが、実際に格納しているのは要素だけです。今回使われている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に追加するということです。

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:

に変えます。

完成!

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

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("引き分け")                                           # 引き分け

コメントを残す

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