はじめに
この記事はプログラミング初心者さんがPythonで三目並べを作成するために作成されています。
学校での課題で出たよとか、とりあえずPythonで何か作ってみたいよって方を想定しています。
せっかくなのでエンジニア気分を味わってもらうべく、ちょっとカッコつけた(エンジニアっぽい)書き方を随所でしています。
ですが基本的には省略しすぎずにコードを書いていきます。
(全体的に、図少なめです。ご了承ください。)
システム要件
このゲームは下記に示す様な、ターミナル上で動きます。
使用環境はPython3です。
よくある三目並べと比べると、かなり簡易化されています。
具体的には、今回紹介するゲームは以下の要件を満たします。
- CPUとの対戦形式
- 自分が先攻 (o) でCPUが後攻 (x)
- CPUの手はランダムで確定
- 勝ち負け・引き分け判定がある
設計する
コーディングを始める前に、設計をします。
初心者さんは特に設計に時間かけないとコードを書いている内に迷子になります。
ゲームの流れや細かい機能を考えていきます。
盤面管理
ゲームに利用する盤面はどのように管理する必要があると思いますか?
この記事では盤面(gameBoard)は以下のように定義します。
gameBoard = [0,1,2,3,4,5,6,7,8] # ゲームに利用する盤面
普通のリストです。
番号と座標が対応しており、例えばプレイヤーが 1
座標に o
を入力した場合は以下のように盤面が変化します。
gameBoard = [0,'o',2,3,4,5,6,7,8] # プレイヤーが座標1に'o'を入力
ゲームフローを考える
初めに「マルバツゲームを進行するのにどんな手順が必要だったっけ?」と考えます。
最初はざっくりとで大丈夫です。
例えば今回だと、
- 3x3の盤面を表示する
- ユーザーに入力をさせる
- 入力を盤面に反映させる
- 盤面表示
- 勝利判定をする
- CPUに入力をさせる
- 入力を盤面に反映させる
- 勝利判定をする
- 盤面表示
との具合で進めると、とりあえずプレイヤーとCPUがそれぞれ1手ずつ指すことができます。
機能の抽出
続いてはフローに基づいて「機能」を抽出していきます。
前節でのゲームフローでは9つのステップを表示しました。
ですが、必要な機能はもっとシンプルです。
- 3x3 盤面を表示
- ユーザーまたはCPUに入力をさせて盤面に反映
- 勝利判定
これら 3つの機能を繰り返すことでゲームが成立させます 。
以上、設計終わり!
実装
それではお待ちかね、実装をしていきます。
前節でピックアップした3つの機能の実装 についてそれぞれ見ていきます!
3x3 盤面の表示
以下のリストがあったとすると、
gameBoard = [0,'o',2,3,4,5,6,7,8] # プレイヤーが座標1に'o'を入力
以下のように表示する機能を実装します。
0o2
345
678
(※補足) リストへのアクセス方法は下記を参考にしてください。
gameBoard = [0,'o',2,3,4,5,6,7,8] # こんな盤面の時
print(gameBoard[1]) # ← こうすると o が表示される
実装の方針は gameBoard[0]
、 gameBoard[1]
...と1マスずつ表示をさせ、 2番目と5番目 のときは表示後に改行させます。
言い換えると 2番目と5番目以外は改行しないで表示する ということになります。
ここはエンジニアっぽい書き方をしてみます。
とりあえず、コードを読んでみて下さい。
解説はコードの下に。
for i in range(0, len(gameBoard)):
if(i%3 == 2): # 3回に1回は改行
print(gameBoard[i]) # 改行する
else :
print(gameBoard[i], end="") # 改行しない
for
で1マスずつ表示を行います。
i
は0から始まり、ループのたびに1ずつ増え、9になったら for
が終了します。
(つまり i
は 0,1,2,3... と変化していく)
ポイントは
if(i%3 == 2):
です。 i%3
は 「 i
を3で割った数の余り」を示しています。
例えば、 i=2
のとき、 i%3 = 2
、 i=4
のとき、 i%3 = 1
となります。
つまり、 for
の中で
i
が 0,1,2,3,4,5,6,7,8
と増えるのに対して、
i%3
は 0,1,2,0,1,2,0,1,2,
となります。
もうお分かり頂けたでしょうか?
i%3 == 2
の時というのは i
が2,5,8の時を表しています。
これで、本節の冒頭で書いた
2番目と5番目 のときは表示後に改行させます。
という処理が書けました!
ちなみに改行と改行しないの区別はそれぞれ print(表示するもの)
と print(表示するもの, end="")
の違いです。
後者が改行をさせない書き方になります 。
とりあえず実行
以下のコードを実行してみて下さい。
gameBoard = [0,1,2,3,4,5,6,7,8] # ゲームに利用する盤面
for i in range(0, len(gameBoard)):
if(i%3 == 2): # 3回に1回は改行
print(gameBoard[i]) # 改行する
else :
print(gameBoard[i], end="") # 改行しない
以下のように結果が出ましたか!?
012
345
678
関数定義
さて、これらの機能を関数として定義します。
関数とは、何かの入力に対して出力があるカタマリのことを指します。
よく自動販売機で例えられますね。
お金を入れる(入力)と飲み物が出てくる(出力)感じです。
とりあえず、以下を実行!
gameBoard = [0,1,2,3,4,5,6,7,8] # ゲームに利用する盤面
# 盤面を3x3で表示する関数
def displayBoard():
for i in range(0, len(gameBoard)):
if(i%3 == 2): # 3回に1回は改行
print(gameBoard[i]) # 改行する
else :
print(gameBoard[i], end="") # 改行しない
# ここが本命
displayBoard() # 盤面表示
print('Hello!') # 無駄にHelloを表示
displayBoard() # 盤面表示
以下の一文を書くだけで、
def displayBoard():
さっきの for
が入った処理を displayBoard()
として呼び出せるようになっています!
2回for文を書かなくても、下記のように盤面が2回表示されるはずです!
012
345
678
Hello!!
012
345
678
ターンを進める
続いて、ターンを進めるためにinputBoard
という関数を作ります。
ターンを進めるのには3つのステップが考えられます。
- 0~8の座標を入力
- 入力座標に
o
かx
が入っていたら再入力 - それ以外ならgameBoardに反映
これだけ!簡単!
ちなみに inputBoard
は
def inputBoard(playerType):
とし、
inputBoard('o')
とやるとプレイヤー、
inputBoard('x')
とやるとCPU
といった具合に、引数である playerType
に o
か x
が入る想定です。
とりあえず完成形。
# ターンを進めるための関数
def inputBoard(playerType):
# 1.座標を入力させる
if(playerType == "o"): # o が渡されたら座標を入力
tgt = int(input("0~8の座標を入れてください: "))
else: # xが渡されたらランダムで座標を入力
tgt = random.randint(0,8)
# 2.入力座標に 'o'か 'x' が入っていたら再入力
if(gameBoard[tgt] == 'o' or gameBoard[tgt] == 'x'):
inputBoard(playerType)
# 3.gameBoardに反映
else:
gameBoard[tgt] = playerType
inputBoard('o') # これでユーザーに入力をさせることができる
inputBoard('x') # これでCPUに入力をさせることができる
Step1: 0~8の座標を入力
プレイヤーかCPUに0~8の座標を入力させます。
プレイヤーからの座標入力を受け付ける
それでは、プレイヤーが入力した座標を tgt
という変数に格納します。
input()
は文字列を受け取るので int()
というので括って、入力値を数字に変換しています。
tgt = int(input("0~8の座標を入れてください: ")) # プレイヤーの入力座標受付
CPUはランダムな値を入力する
プレイヤーと同様に、CPUの座標も tgt
という変数に格納しましょう。
これもかなりシンプル。
tgt = random.randint(0,8)
整数の乱数はrandom.randint(最小値,最大値)
という具合に使用します。
randomを使うために、コードの一番上に
import random
を入れる必要があります。
プレイヤーかCPUか
これら2つの機能が if
文で分割されており、 playerType
を見ることで、どちらを呼び出すかを判定しています。
以下、Step1の完成形
def inputBoard(playerType):
# 1.座標を入力させる
if(playerType == "o"): # o が渡されたら座標を入力
tgt = int(input("0~8の座標を入れてください: "))
else: # xが渡されたらランダムで座標を入力
tgt = random.randint(0,8)
Step2: 入力座標に o
か x
が入っていたら再入力
続いては、入力座標に o
か x
が入っていたら再入力機能を実装します。
Step2は以下の一文だけ。
# 2.入力座標に 'o'か 'x' が入っていないことを確認
if(gameBoard[tgt] == 'o' or gameBoard[tgt] == 'x'): inputBoard(playerType)
Step1で tgt
という座標をユーザーかCPUが入力しました。
そのため、 gameBoard[tgt]
に何が入っているのかを if
で検証します。
if
では条件を or
で繋ぐことできます。
if( 条件A or 条件B): 実行するもの
を日本語に訳すなら もしも「条件A」または「条件B」を満たしていたら「実行するもの」を実行する といった具合です。
今回の場合は inputBoard
をもう一度呼び出すことで実装しています。
Step3: 何も入ってないならgameBoardに反映
これもとてもシンプル。
# 3.gameBoardに反映
else: gameBoard[tgt] = playerType
Step2の if
と繋がっていることに注意して下さい。
ここまでで、表示と入力ができました。
全てをつなぎ合わせると、以下のようになります。
import random
gameBoard = [0,1,2,3,4,5,6,7,8] # ゲームに利用する盤面
# 盤面を3x3で表示する
def displayBoard():
# gameBoardの要素を1つずつ表示
for i in range(0, len(gameBoard)):
if(i%3 == 2): # 3回に1回は改行
print(gameBoard[i]) # 改行する
else :
print(gameBoard[i], end="") # 改行しない
# ターンを進める
def inputBoard(playerType):
# 1.座標を入力させる
if(playerType == "o"): # o が渡されたら座標を入力
tgt = int(input("0~8の座標を入れてください: "))
else: # xが渡されたらランダムで座標を入力
tgt = random.randint(0,8)
# 2.入力座標に 'o'か 'x' が入っていないことを確認
if(gameBoard[tgt] == 'o' or gameBoard[tgt] == 'x'):
inputBoard(playerType)
# 3.gameBoardに反映
else:
gameBoard[tgt] = playerType
# ゲームの進行
displayBoard()
inputBoard('o') # ユーザーのターン
displayBoard()
inputBoard('x') # CPUのターン
displayBoard()
これを実行すると、自分の入力をしたあとに、CPUがランダムで入力をしてくれます。
ちなみに最後の方にある、
# ゲームの進行
displayBoard()
inputBoard('o') # ユーザーのターン
displayBoard()
inputBoard('x') # CPUのターン
displayBoard()
を
# ゲームの進行
while(True):
displayBoard()
inputBoard('o') # ユーザーのターン
displayBoard()
inputBoard('x') # CPUのターン
とすると while(True)
の中が無限ループするので、盤面が埋まるまで入力をし続けることができます。
Step:3 勝敗判定
最後に勝敗判定を行います。
とりあえず、勝敗判定を行う winner()
という関数の完成形をお見せします。
# 勝利判定
def winner():
# 勝ち手を列挙
lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
]
# forで勝ち手を1パターンずつ見ていく
for i in range(0, len(lines)):
[a, b, c] = lines[i]
# 勝ち手の場所に同じ記号が入っていないかを確認
if gameBoard[a] and gameBoard[a] == gameBoard[b] and gameBoard[a] == gameBoard[c]:
# 同じ記号が入っていたら、入っている記号を返す
return gameBoard[a]
# どちらも勝っていない場合はNoneを返す
return None
winner()
を呼び出すと、 o
か x
か None
が returnされます。
linesに入っている勝ち手を全てチェックして、同一の記号が入っていないかチェックしているだけです。
1つ補足をすると、
[a, b, c] = lines[i]
は
a = lines[i][0]
b = lines[i][1]
c = lines[i][2]
を省略しています。
lines[i]
には [0, 1, 2]
や [3, 4, 5]
などの勝ち手のいずれか1つが格納されます。
もっと具体的に書くと lines[1]
は [3, 4, 5]
となり、 lines[1][0]
は 3
です。
a,b,cに「勝ち手座標」が格納され、 gameBoard[a]
と gameBoard[b]
と gameBoard[c]
に同じ記号が入ってたら、その記号のプレイヤーが勝利です。
以上で機能は全て完成!
ゲームを進行する
さて、上記で作成した機能をループで回していきましょう!
燃え尽きたので、細かい説明は省略!
displayBoard() # 実行時に初めに表示される盤面
for turn in range(0,9):
# 誰のターンかを判定する
if(turn %2 == 0) : # あなたのターン
print("You")
inputBoard("o")
else: # CPUのターン
print("CPU")
inputBoard("x")
displayBoard() # 盤面表示
if winner(): # 勝敗判定
print(winner() + "の勝ち")
break
if turn == 8: # 8手目で引き分け
print("引き分け")
break
完成
おめでとうございます!
以上で完成です!
下記ソースは説明に使ったもの以外に、デコレーションが入っています。
それでは、良いエンジニアライフを!
import random
gameBoard = [0,1,2,3,4,5,6,7,8] # ゲームに利用する盤面
# 盤面を3x3で表示する
def displayBoard():
# gameBoardの要素を1つずつ表示
print("--+---+--") # デコレーション
for i in range(0, len(gameBoard)):
if(i%3 == 2): # 3回に1回は改行
print(gameBoard[i]) # 改行する
print("--+---+--") # デコレーション
elif(i%3 == 1):
print(" | " + str(gameBoard[i]) + " | ", end="") # 改行しない
else :
print(gameBoard[i], end="") # 改行しない
print()
# ターンを進める
def inputBoard(playerType):
# 1.座標を入力させる
if(playerType == "o"): # o が渡されたら座標を入力
tgt = int(input("0~8の座標を入れてください: "))
else: # xが渡されたらランダムで座標を入力
tgt = random.randint(0,8)
# 2.入力座標に 'o'か 'x' が入っていないことを確認
if(gameBoard[tgt] == 'o' or gameBoard[tgt] == 'x'):
inputBoard(playerType)
# 3.gameBoardに反映
else:
gameBoard[tgt] = playerType
# 勝利判定
def winner():
# 勝ち手を列挙
lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
]
# forで勝ち手を1パターンずつ見ていく
for i in range(0, len(lines)):
[a, b, c] = lines[i]
# 勝ち手の場所に同じ記号が入っていないかを確認
if gameBoard[a] and gameBoard[a] == gameBoard[b] and gameBoard[a] == gameBoard[c]:
# 同じ記号が入っていたら、入っている記号を返す
return gameBoard[a]
# どちらも勝っていない場合はNoneを返す
return None
displayBoard() # 実行時に初めに表示される盤面
# ゲームを実行する
for turn in range(0,9):
# 誰のターンかを判定する
if(turn %2 == 0) : # あなたのターン
print("You")
inputBoard("o")
else: # CPUのターン
print("CPU")
inputBoard("x")
displayBoard() # 盤面表示
if winner(): # 勝敗判定
print(winner() + "の勝ち")
break
if turn == 8: # 8手目で引き分け
print("引き分け")
break