今回は、マルバツゲームを人同士がエラーなく対戦できるようにしましょう。
前回までのコード
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)
- 再び試してみましょう。
- これで期待するような挙動になりました!どうしてこのような挙動になるのか、コードの流れを追ってみてください。なお、
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("引き分け") # 引き分け