Pythonでマルバツゲームをつくろう #3 人対人編

今回は、マルバツゲームを人同士がエラーなく対戦できるようにしましょう。

前回:Pythonでマルバツゲームをつくろう #2 完成編

前回までのコード
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("引き分け")                                     # 引き分け

1. inputを安全化する

  • 前回までのコードでマルバツゲームのロジックは完成したのですが、人と人が遊ぶとなると、少し心配な面が残っていました。それは入力部分です。
index = int(input("{current_player} の手 : "))

これの一体何が心配なのでしょうか?

問題点①:整数値以外を受け取るとエラーになる

  • int関数は"2"のような整数の文字列2という整数値に変換してくれる関数です。仮にユーザが"x"”3.14”などの整数値に変換できない文字列を入力してきた場合、エラーになってしまいます。
エラー
  • エラーというのは、コンピュータが「そんなことできないよ」と困って動くのをやめてしまうことです。
  • エラーにも色々種類があります。今回のエラーはValueErrorです。これは、「型はあっているけど、値がおかしいよ」というエラーです。今回の場合、"x"というのは文字列なのでは問題ないのですが、"3"のように整数値に変換できる文字列ではないということです。

解決策

  • try-except文を使います。
try:
    index = int(input(f"{current_player} の手 : "))
except ValueError:
    print("整数を入力してください")
  • コンピュータはいったんtryの処理を試し、もしエラーになるようなら途中でやめてexcept内の処理に進む、という動作を行います。execptに付け加えてValueErrorと書くことで、エラーの中でもValueErrorの場合のみexcept内の処理に進むようになっています。
  • さて、これでint関数によるエラーを避けることができました。とはいえ、結局のところユーザから正しい値を受け取るまでは先には進めません。そこで、正しい値を受け取るまでは抜けられないループを設定します。
while True:
    try:
        index = int(input(f"{current_player} の手 : "))
        break
    except ValueError:
        print("整数を入力してください")
  • while Trueのような書き方を無限ループと言います。条件部分が必ずTrueなので、処理部分breakしない限りループは抜けられません。

問題点②:盤面外のインデックスを受け取るとエラーになる

  • 実はこれでもまだ問題があります。indexが整数値を受け取ったとしても、それが例えば9のように、boardリストのインデックス(0~8)の範囲外のものだった場合、この後のboard[index] = current_playerで、board[index]に対応する要素は存在しないのでやはりエラーになります。

解決策

  • ユーザが入力した整数が0から8の間に収まっているかどうかも確認するようにします。
while True:
    try:                                                # 整数の文字列かどうかチェック
        index = int(input(f"{current_player} の手 : "))
        if 0 <= index <= 8:                             # さらにインデックスが0~8の範囲内に収まっているかチェック
            break
        else:
            print("𝟶 ~ 𝟾 を選んでください")
    except ValueError:
        print("整数を入力してください")

問題点③:すでに打たれている場所に打ててしまう

  • これでもまだ、すでに盤面上に×が存在するマスにも打てる、という問題が残っています。

解決策

  • board[index]"○"でも"×"でもない、という条件を追加しましょう。
while True:
    try:
        index = int(input(f"{current_player} の手 : "))
        if (0 <= index <= 8) and (board[index] != "○" and board[index] != "×"):  # 0~8 かつ "○"でも"×"でもない
            break
        else:
            print("𝟶 ~ 𝟾 の空いているマスを選んでください")
    except ValueError:
        print("整数を入力してください")

全て解決!

  • これで三つの問題点が全て解決されました!ここまでをまとめてみましょう。#で囲まれた部分が新しい部分です。
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("整数を入力してください")
    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 

    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("引き分け")                                     # 引き分け
  • 早速colabでテストプレイしてみましょう。
  • このような盤面で、次は○の番です。色々間違った入力をして、ちゃんと対応できるか試してみましょう。
  • 整数値に変換できない文字列を入力しても、
    boardリストのインデックス外の数値を入力しても、
    すでに打たれている場所を選んでも、
    どれも適切に対処できました!
  • …ですが、一度間違った値を入力すると盤面が消えてしまうようです。また、前の表示が表示され続けています。これは期待する挙動ではありません。
  • そこで、以下のように、入力チェック用のwhileループの最後に、盤面表示用のコードおよび出力を消去するようのコードを付け足しましょう。
    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リストのインデックスに存在しない数字が入力された場合
すでに打たれている場所が選ばれた場合
  • これで期待するような挙動になりました!どうしてこのような挙動になるのか、コードの流れを追ってみてください。なお、clear_output(wait=True)は、画面上の表示をすぐに消すのではなく、次の出力を待ってから消してくれる機能です。

完成!

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

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

コメントを残す

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