こんにちは。ヒトツメです。
前回は、実際に盤面を作った後、石を置いていく操作を一行で表せるように、石の設置に関する関数を作ってみました。これで、白と黒の分、交互に石を置いていく操作を入力していけば、終局までオセロを楽しむことができます。
ただ、これだと味気ないですし、何より石の設置コマンドを毎回打ち込むのは面倒です。そこで今回は、tkinterというライブラリを使って、GUIを作り、マウス操作で楽しめるオセロを作っていきたいと思います。
tkinterの簡単な説明
tkinterは、Pythonに標準搭載されている、GUIつまりグラフィック・ユーザー・インターフェースを扱うためのライブラリです。ただ、基礎の説明から詳しく述べていくと、かなりややこしい話になるので、そのあたりは専門書に譲るとして、今回は最低限の説明とコードの記載で、実際に動くところを楽しんでいただきたいと思います。
とはいえ、最低限の説明がないと、写経になってしまいます。そこで「tkinterとは」という話になるわけですが、tkinterは、一般にnumpyのように関数を呼び出して使うようなものではなく、GUIを動かすための最低限の土台のように使われるものです。
Python以外の言語でも広く使われる手法に、クラスというものがあります。これは、かなりざっくり言うと、予め指定した動きをしてくれるモジュールをパッケージ化したものです。
イメージしやすいように説明すると、ここでいうクラスとは、「車」といったようなものです。車と一口に言っても、どんなエンジンを載せるか、どんなハンドルを付けるか、どんな外観にするか、といった具合に様々なカスタマイズが可能です。ただ、どんなエンジンを載せるとしても、「車」という概念は変わりません。同じように、どんな関数を使ってどんな値を入れればよいかをあらかじめコーディングし、構造をひな型化したものがクラスです。
Pythonでは、このようなクラスを「継承」し、新たなクラスを作ることができます。ちょうど「車」という構造をベースにして、さらに条件を付与することで「トラック」にするように、クラスを継承してさらに条件を付けくわえたクラスを作ることができます。
少々前置きの説明が長くなりましたが、tkinterは、GUIのための一番ベースとなるクラスを提供してくれるライブラリとして使うことができるようになっています。
tkinterを動かしてみる
このように、tkinterの基本的な構造は、クラスになっているので、何か変数を入れなくても、動かすこと自体はできます。試しに次のコードを実行してみると、何もないウィンドウが生成されます。
import tkinter as tk
f = tk.Frame()
f.mainloop()
事前に用意されたtkinterのFrameというクラスに該当するようにfという変数を置き、fに関してmainloopという処理を施すことで、実際にfに割り当てられたウィンドウを表示させるといったイメージです。
オセロのUIを表現してみる
以上を踏まえて、このようなtkinterのFlameに関し、次のような条件を足しこむことで、オセロの初期盤面の表示をしていきたいと思います。
サイズを決める
まず、初期状態のFrameでは、ウィンドウのサイズが0になっています。なので、最小化や×のボタンを表記するための最低限のサイズしかありません。これを解消するため、次の表にFrameを継承しつつ、新たな条件を加えたgameUIというクラスを作成します。
class gameUI(tk.Frame):
def __init__(self, game, master=None):
tk.Frame.__init__(self, master)
self.master.title('othello')
self.c = tk.Canvas(self, width = 480, height = 480, highlightthickness = 0)
self.c.pack()
f = gameUI(game)
f.pack()
f.mainloop()
ここでの「__init__」は、gameUIというクラスの初期状態を決めるための関数で、外部から呼び出すことを目的としているものではありません。
この中で、ウィンドウのタイトルを「othello」にし、「self.c」によって、Canvasを呼び出し、縦と横のサイズを480に指定しています。このようにすることで、gameUIが呼び出されると、自動でself.cが呼び出され、480×480のキャンバスが表示されます。
ちなみに、f.pack()という部分がないと、指定した内容がきちんと配置されないので、注意が必要です。
色を決めるて升目を引く
続いて、このように決めたウィンドウ全体の色を決め、さらに升目を引かなければなりません。
#盤の色を指定
self.c.create_rectangle(0, 0, 480, 480, width = 0.0, fill = '#006400')
#升目を表示
for rowIter in range(8):
self.c.create_line(0, rowIter * 60, 480, rowIter * 60, width = 1.0, fill = '#FFFFFF')
for clmIter in range(8):
self.c.create_line(clmIter * 60, 0, clmIter * 60, 480, width = 1.0, fill = '#FFFFFF')
少々長いですが、create_rectangleという部分で、指定したキャンバスいっぱいに、#006400の色の正方形を設置しています。続いてのfor文二つで、create_line、つまり線を引いています。
石を設置する
最後に、石を設置します。
すでにgameUIでは、オセロを裏で動かすためのgameが引数として指定されていますので、それを内部で呼び出し、一つ一つの内容を見ていき、1か-1かに応じて、適切な位置に白い丸か黒い丸を設置すればOKです。
#石を配置
self.game = game
for row in range(8):
for clm in range(8):
if self.game[row][clm] == 1:
self.c.create_oval(clm * 60 + 5, row * 60 + 5, (clm + 1) * 60 - 5, (row + 1) * 60 - 5, width = 0.0, fill = '#FFFFFF')
if self.game[row][clm] == -1:
self.c.create_oval(clm * 60 + 5, row * 60 + 5, (clm + 1) * 60 - 5, (row + 1) * 60 - 5, width = 0.0, fill = '#000000')
create_ovalというので円形を記載していますが、この関数では、外接する正方形の四つの端の位置を指定してあげることで、表示することが可能です。
さいごに
以上、無事初期画面を表示することができました。
これでいかにもオセロっぽくなってきました。あとはこれに対するクリック操作に応じて、以前までに作成した関数を呼び出して再度石を表記すれば、うまく動かすことができそうです。
最後に、いままでのところをまとめて表記すると以下の通りです。
import numpy
import tkinter as tk
game = numpy.zeros((8, 8))
game[3][3] = 1
game[4][4] = 1
game[3][4] = -1
game[4][3] = -1
def nextgame(game, row, clm, Player):
If game[row][clm] == 0:
for d_iter in range(9):
dc = int(d_iter / 3) - 1
dr = d_iter % 3 - 1
length = 0
while True:
length += 1
if row + dr * length < 0 or row + dr * length > 7 \
or clm + dc * length < 0 or clm + dc * length > 7:
break
buf = game[row + dr * length][clm + dc * length]
if length == 1 and buf != -1 * Player:
break
if length > 1 and buf == 0:
break
if length > 1 and buf == Player:
for iter in range(length):
game[row + dr * iter][clm + dc * iter] = Player
class gameUI(tk.Frame):
def __init__(self, game, master=None):
tk.Frame.__init__(self, master)
self.master.title('othello')
self.c = tk.Canvas(self, width = 480, height = 480, highlightthickness = 0)
self.c.pack()
#盤の色を指定
self.c.create_rectangle(0, 0, 480, 480, width = 0.0, fill = '#006400')
#升目を表示
for rowIter in range(8):
self.c.create_line(0, rowIter * 60, 480, rowIter * 60, width = 1.0, fill = '#FFFFFF')
for clmIter in range(8):
self.c.create_line(clmIter * 60, 0, clmIter * 60, 480, width = 1.0, fill = '#FFFFFF')
#石を配置
self.game = game
for row in range(8):
for clm in range(8):
if self.game[row][clm] == 1:
self.c.create_oval(clm * 60 + 5, row * 60 + 5, (clm + 1) * 60 - 5, (row + 1) * 60 - 5, width = 0.0, fill = '#FFFFFF')
if self.game[row][clm] == -1:
self.c.create_oval(clm * 60 + 5, row * 60 + 5, (clm + 1) * 60 - 5, (row + 1) * 60 - 5, width = 0.0, fill = '#000000')
f = gameUI(game)
f.pack()
f.mainloop()
コメント