この記事は、「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("引き分け")