【Python】◯×ゲームを作る【初心者向け】
※本エントリは”Automate the Boring Stuff With Python”(Automate the Boring Stuff with Python)を勉強して得られた内容のメモです。
タイトルにあるように、◯×ゲームを作ってみる。2人のプレイヤーが3*3のマスに◯と×を交互に書き込んでいき、一列揃えた方が勝ちという例のアレだ。
作成にあたっては、辞書型のデータを使うことにする。
目次:
コード概観
theBoard = {'top-L': ' ', 'top-M': ' ', 'top-R': ' ', 'mid-L': ' ', 'mid-M': ' ', 'mid-R': ' ', 'low-L': ' ', 'low-M': ' ', 'low-R': ' '} def printBoard(board): print(board['top-L'] + '|' + board['top-M'] + '|' + board['top-R']) print('-+-+-') print(board['mid-L'] + '|' + board['mid-M'] + '|' + board['mid-R']) print('-+-+-') print(board['low-L'] + '|' + board['low-M'] + '|' + board['low-R']) turn = 'X' for i in range(9): printBoard(theBoard) print('Turn for ' + turn + '. Move on which space?') move = input() theBoard[move] = turn if turn == 'X': turn = 'O' else: turn = 'X' printBoard(theBoard)
実行すると、対話的に◯および×を書き込む場所を指定するプロセスを繰り返す。
| | -+-+- | | -+-+- | | Turn for X. Move on which space? top-L X| | -+-+- | | -+-+- | | Turn for O. Move on which space? mid-M X| | -+-+- |O| -+-+- | | Turn for X. Move on which space? top-M X|X| -+-+- |O| -+-+- | | Turn for O. Move on which space? mid-R X|X| -+-+- |O|O -+-+- | | Turn for X. Move on which space? top-R X|X|X -+-+- |O|O -+-+- | | Turn for O. Move on which space?
本当はこの時点で決着はついているのだが、ソースコード自体に勝敗を判定する要素が含まれていないので、依然として入力を要求されている。まぁ気にしないでおこう。いつものように、コード全体を分割して、一つ一つ見ていく。
盤面
まずは◯と×を書き込む盤面から。
theBoard = {'top-L': ' ', 'top-M': ' ', 'top-R': ' ', 'mid-L': ' ', 'mid-M': ' ', 'mid-R': ' ', 'low-L': ' ', 'low-M': ' ', 'low-R': ' '}
まだこの時点では盤面っぽさはない。ゲーム開始前なので9マスの全てがblank(" ")になっているが、見てわかるように"theBoard"は辞書型として定義されている。後のゲーム展開では「左上に◯」「中央右に×」という風に、書き込む場所と記号が1:1の関係で指定されていくから、辞書型として定義しておくのが都合がいいと言うことだ。
辞書型データを引数として盤面をprintする
次に、printBoard関数を定義する。
def printBoard(board): print(board['top-L'] + '|' + board['top-M'] + '|' + board['top-R']) print('-+-+-') print(board['mid-L'] + '|' + board['mid-M'] + '|' + board['mid-R']) print('-+-+-') print(board['low-L'] + '|' + board['low-M'] + '|' + board['low-R'])
boardを引数とする関数だが、このdef文の中身を見るとわかるように、boardは辞書型のデータである必要がある(コード全体の後半部分では、この引数に先ほどの"theBoard"を用いる)。まだゲームが開始されていないから、いずれのセルも空白であり、
| | -+-+- | | -+-+- | |
このような状態になっている。ゲームが始まると、それぞれのマスのblank(" ")が◯または×の記号で置き換えられて(=ゲーム進行状況が更新されて)、各ターンでの盤面の状況をprint文で返す、という仕組みになっている。
ターン進行のループ
残りの部分は一気に解説してしまおう。
turn = 'X' for i in range(9): printBoard(theBoard) print('Turn for ' + turn + '. Move on which space?') move = input() theBoard[move] = turn if turn == 'X': turn = 'O' else: turn = 'X' printBoard(theBoard)
◯と×どちらから始めてもいいのだが、今回は×が先手、◯が後手となっている。3*3のマスなので最大9ターンしかあり得ないから、for i in range(9)としておく。
forループの中の最初の2行は単なるprint文なので省略。3行目で◯または×を書き込むマスを指定する必要がある。左上ならtop-L, 右下ならlow-Rといった具合。
4行目が、コード全体でもっとも大事な部分と言えるだろう。
#4行目 theBoard[move] = turn
先に述べたように先手は×だから、最初のターンでは右辺=×だ。また、theBoardは何の記号も書き込まれていない辞書型のデータだが、これに"move"を代入することで、記号が書き込まれる。"move"は3行目のinputだから、例えば"top-L"と入力したならば、4行目は
#4行目 theBoard[top-L] = X
に相当することになる。辞書型データの取り扱いは別エントリとして作成予定だが、以下のようなコードでわかるように、ミュータブルなデータとして扱える。リンゴの数を5個から500個に更新してみた。
fruits = {"apple":5, "orange":3, "melon":4} fruits["apple"]=500 print(fruits) #{'apple': 500, 'orange': 3, 'melon': 4}
脱線したが、元々のコードに戻ろう。残りの部分は大したことはない。
if turn == 'X': turn = 'O' else: turn = 'X' printBoard(theBoard)
◯と×のターンを交互に入れ替えなければならないから、このようにif分がついている。ターンを入れ替えた後にfor文の先頭に戻る。
しかし、一点注意しなければいけないのは、末尾のprintBoard関数だ。人間的な感覚としては1個目の記号が書き込まれた状態を1ターン目としたくなるところだが、実はコード上では何も書き込まれていない状態が1ターン目になっているということだ。だから、for文で9回ループを繰り返しても、(内部データ的には9個の記号が書き込まれているが)表示される盤面には8個しか記号が書き込まれていない。そこでforループを抜け出した後に、最後にもう一回だけ盤面を表示させる必要がある。
今後の課題
これでもゲームとしては成立しているが、先に述べたように勝敗の判定機能がついていないし、3*3のマスではゲーム的な要素が薄く、大抵の場合は引き分けに陥るだろう。これを踏まえて、勝敗判定機能つきの五目並べとか作ってみたい。盤面を拡大するだけなら難しくないだろうから、勝敗判定機能を実装するのがネックになるだろう。気長に勉強していく予定。