どくとる・めも

化学、数学、プログラミング、英語などに関する諸々

【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のマスではゲーム的な要素が薄く、大抵の場合は引き分けに陥るだろう。これを踏まえて、勝敗判定機能つきの五目並べとか作ってみたい。盤面を拡大するだけなら難しくないだろうから、勝敗判定機能を実装するのがネックになるだろう。気長に勉強していく予定。