Pythonでマルバツゲームをつくろう【まとめ】

この記事は、「Pythonでマルバツゲームをつくろう」#1~#6のまとめ記事です。

#1 最初の盤面を表示するまで

まずは、最初の盤面を表示するまでです。

学ぶこと:
変数、リスト、整数値、文字列、print関数、代入演算子

完成するコード
current_player = "○"                                    # 現在のプレイヤー
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]} ")

#2 ロジックが完成するまで

次に、全体の骨格を完成させます。

学ぶこと:
while文、if-else文、PEP8、while-else文、基本的な演算子、外部ライブラリ

完成するコード
from IPython.display import clear_output
current_player = "○"                                    # 現在のプレイヤー
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)                             # 新たに出力され次第、表示済みの出力を消去
    index = int(input(f"{current_player} の手 : "))      # ユーザの入力を受け取る
    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="○" if current_player=="×" else "×"   # 手番交代
else:
    print("引き分け")                                     # 引き分け

#3 人と人が対局できるようになるまで

次は、人同士が対局する時にエラーが起きないよう、入力を安全にします。

学ぶこと:
try-except文

完成するコード
from IPython.display import clear_output
current_player = "○"                                    # 現在のプレイヤー
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 (0 <= index <= 8) and (board[index] != "○" and board[index] != "×"):
                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 = "○" if current_player == "×" else "×"   # 手番交代
else:
    print("引き分け")                                     # 引き分け

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

#4 人とランダムボットが対局できるようになるまで

いよいよ、初めてのボットを作ります。

学ぶこと:
timeモジュール、randomモジュール、リスト内包表記、enumerate関数

完成するコード
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("引き分け")                                           # 引き分け

#5 人と最強のボットが対局できるようになるまで

ランダムなボットから次はいきなり最強のボットが完成します。

学ぶこと:
ミニマックスアルゴリズム

完成するコード
from IPython.display import clear_output
import random, time

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

current_player = O
turn = 0
board = [
    "𝟶", "𝟷", "𝟸",
    "𝟹", "𝟺", "𝟻",
    "𝟼", "𝟽", "𝟾"
]

# boardを受け取ると、次に打てるインデックスをまとめたリストを返す
def legal_moves(board):
    return [index for index, value in enumerate(board) if value not in (O, X)]

# ○勝ちなら1を、×勝ちなら-1を、引き分けなら0を、そもそもまだ終局していないならNoneを返す
def result(board, player):
    # 勝利判定
    if  (board[0] == board[1] == board[2] == player) or \
        (board[3] == board[4] == board[5] == player) or \
        (board[6] == board[7] == board[8] == player) or \
        (board[0] == board[3] == board[6] == player) or \
        (board[1] == board[4] == board[7] == player) or \
        (board[2] == board[5] == board[8] == player) or \
        (board[0] == board[4] == board[8] == player) or \
        (board[2] == board[4] == board[6] == player):
        if player == O:  # もし勝利したのが O なら
            return "○ win"
        else:            # もし勝利したのが X なら
            return "× win"
    # boardに合法手が存在しない場合、すなわち引き分けなら
    elif not legal_moves(board):
        return "draw"

# 盤面とプレイヤを受け取ると、その盤面からプレイヤが受け取れる最も良い値を返す関数。○勝ち:1、引き分け:0、×勝ち:−1とする
# 具体的には:①もし終局盤面なら、その結果を返す
#           ②そうではなくもしゲーム進行中の盤面なら、子局面達のうち最も良い結果を返す
def minimax(board, player):
    if result(board, player) == 1:     # ○勝ち
        return 1
    elif result(board, player) == 0:   # 引き分け
        return 0
    elif result(board, player) == -1:  # ×勝ち
        return -1

    if player == O:
        max_val = -1
        for index in legal_moves(board):
            boardcopy = board.copy()
            boardcopy[index] = O
            val = minimax(boardcopy, X)
            if val > max_val:
                max_val = val
        return max_val
    else:
        min_val = 1
        for index in legal_moves(board):
            boardcopy = board.copy()
            boardcopy[index] = X
            val = minimax(boardcopy, O)
            if val < min_val:
                min_val = val
        return min_val

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:
        clear_output(wait=True)
        while True:
            try:
                index = int(input(f" {current_player} の手: "))
                if 0 <= index <= 8 and 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:
        print("ボット考え中...")
        clear_output(wait=True)
        time.sleep(2)
        win_moves = []
        draw_moves = []
        lose_moves = []
        for idx in legal_moves(board):
            boardcopy = board.copy()
            boardcopy[idx] = BOT
            val = minimax(boardcopy, USER)
            if (BOT == X and val == -1) or (BOT == O and val == 1):
                win_moves.append(idx)
            elif val == 0:
                draw_moves.append(idx)
            elif (BOT == X and val == 1) or (BOT == O and val == -1):
                lose_moves.append(idx)
        if win_moves:
            index = random.choice(win_moves)
        elif draw_moves:
            index = random.choice(draw_moves)
        else:
            index = random.choice(lose_moves)
        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 result(board, current_player) in (-1, 1):
        print(f"勝者: {current_player}")
        break
    current_player = O if current_player == X else X
else:
    print("引き分け")

#6 最強のボットを効率的にする

最後に、不要な探索を省いて、完成です!

学ぶこと:
ゲーム木の枝切り

完成するコード
from IPython.display import clear_output
import random, time

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

current_player = O
turn = 0
board = [
    "𝟶", "𝟷", "𝟸",
    "𝟹", "𝟺", "𝟻",
    "𝟼", "𝟽", "𝟾"
]

# boardを受け取ると、次に打てるインデックスをまとめたリストを返す
def legal_moves(board):
    return [index for index, value in enumerate(board) if value not in (O, X)]

# ○勝ちなら1を、×勝ちなら-1を、引き分けなら0を、そもそもまだ終局していないならNoneを返す
def result(board, player):
    if  (board[0] == board[1] == board[2] == player) or \
        (board[3] == board[4] == board[5] == player) or \
        (board[6] == board[7] == board[8] == player) or \
        (board[0] == board[3] == board[6] == player) or \
        (board[1] == board[4] == board[7] == player) or \
        (board[2] == board[5] == board[8] == player) or \
        (board[0] == board[4] == board[8] == player) or \
        (board[2] == board[4] == board[6] == player):
        if player == O:
            return "○ win"
        else:
            return "× win"
    elif not legal_moves(board):
        return "draw"

# 盤面とプレイヤを受け取ると、その盤面からプレイヤが受け取れる最も良い値を返す関数。○勝ち:1、引き分け:0、×勝ち:−1とする
# 具体的には:①もし終局盤面なら、その結果を返す
#           ②そうではなくもしゲーム進行中の盤面なら、子局面達のうち最も良い結果を返す
def minimax(board, player):
    if result(board, player) == "○ win":    # ○勝ち
        return 1
    elif result(board, player) == "draw":   # 引き分け
        return 0
    elif result(board, player) == "× win":  # ×勝ち
        return -1

    if player == O:
        max_val = -1
        moves = legal_moves(board)
        random.shuffle(moves)
        for index in moves:
            boardcopy = board.copy()
            boardcopy[index] = O
            val = minimax(boardcopy, X)
            if val > max_val:
                max_val = val
                if max_val == 1:
                    return max_val
        return max_val
    else:
        min_val = 1
        moves = legal_moves(board)
        random.shuffle(moves)
        for index in moves:
            boardcopy = board.copy()
            boardcopy[index] = X
            val = minimax(boardcopy, O)
            if val < min_val:
                min_val = val
                if min_val == -1:
                    return min_val
        return min_val

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:
        clear_output(wait=True)
        while True:
            try:
                index = int(input(f" {current_player} の手: "))
                if 0 <= index <= 8 and 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:
        print("ボット考え中...")
        clear_output(wait=True)
        time.sleep(2)
        win_moves = []
        draw_moves = []
        lose_moves = []
        moves = legal_moves(board)
        random.shuffle(moves)
        for idx in moves:
            boardcopy = board.copy()
            boardcopy[idx] = BOT
            val = minimax(boardcopy, USER)
            if (BOT == X and val == -1) or (BOT == O and val == 1):
                win_moves.append(idx)
                break
            elif val == 0:
                draw_moves.append(idx)
            elif (BOT == X and val == 1) or (BOT == O and val == -1):
                lose_moves.append(idx)
        if win_moves:
            index = random.choice(win_moves)
        elif draw_moves:
            index = random.choice(draw_moves)
        else:
            index = random.choice(lose_moves)
        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 result(board, current_player) in (-1, 1):
        print(f"勝者: {current_player}")
        break
    current_player = O if current_player == X else X
else:
    print("引き分け")

コメントを残す

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