HOME Python download 書き込む

9. ボードゲーム用 GUI を作ろう


1. 初めに

今回から、Tk.Canvas の説明をします。Tk.Canvas は Tkinter の中で 最も使いでがある widget です。

この章では、Tk.Canvas について簡単に説明した後、まず手始めにチェス版と碁盤を作ってみます。

2. Tk.Canvas の簡単な説明

2.1. 作り方

Tk.Canvas は他の widget と同じように、第一引数に親 widget, それ以降にオプションを指定して作成します。
Tk.Canvas(master, **options)
Tk.Canvas には以下の特徴があります。 Tk.Canvas の主なメソッドを次に示します。 下の表で、ID は、ID ナンバーまたは Tag を指します。
.delete(ID) ID で指定されたオブジェクトを削除します。
.itemcget(ID, option) ID で指定されたオブジェクトの option の値を返します。
.itemconfigure(ID, option) ID で指定されたオブジェクトの属性を指定します。
.move(ID, x, y) ID で指定されたオブジェクトを (x, y) だけ動かします。
.scale(ID, x0, y0, rx, ry) ID で指定されたオブジェクトを、(x0, y0) を基準点にして、 (rx, ry) だけ拡大します。
.tag_bind(ID, event, func) ID で指定されたオブジェクトと event を結びつけ、event が起こると func が呼ばれるようにします。
そのほかにも山ほどのメソッドがあります。 詳しくは、以下を参照してください。

2.2.Tk.Canvas に描けるオブジェクト

Tk.Canvas に描けるオブジェクトを下の表に示します。詳しくは Tkinter reference: 6. The Canvas widget を見てください。 下の表で、TkRef とは Tkinter reference を指します。
Object 作成するメソッド 説明
.create_arc() 円弧(の一部)を描きます。PIESLICE, CHORD, ARC の3つのタイプがあります。 see TkRef 6.3. The canvas arc object
ビットマップ .create_bitmap() ビットマップイメージを作成します。 See TkRef 6.4. The canvas bitmap object
イメージ .create_image() Graphic Image を作成します。 See TkRef 6.5. The canvas image object
線分 .create_line() 線分を描きます。曲線も描くことができます。 See TkRef 6.6. The canvas line object
楕円 .create_oval() 円、楕円を描きます。 See TkRef 6.7. The canvas oval object
多角形 .create_polygon() See TkRef 6.8. The canvas polygon object
長方形 create_rectangle() See TkRef 6.8. The canvas rectangle object
テキスト .create_text() See TkRef 6.10. The canvas text object
ウインドウ .create_window() Tk.Canvas 上に他の widget を入れるとき使います。 See TkRef 6.11. The canvas window object

3. Eight Queens 再び

wxPython と Tkinter で Eight Queens を作る。 でとりあげた Eight Queens 用 GUI を Tk.Canvas で作ってみます。 wxPython と Tkinter で Eight Queens を作る。では、 Queen を移動させるとき、もとあった画像を壊して、新しい画像をはめ込んでいましたが、 Tk.Canvas で盤を作り、透過 GIF の Queen を移動させたほうが効率がいいことに気づいたので、書き直してみました。 下の図に示すような画面が表示されます。
コードを [code 1] に示します。

[code 1] (8queens.py)

 01:     #! /usr/bin/env python
 02:     
 03:     """
 04:     eight queens, whose gui uses Tkinter
 05:     """
 06:     
 07:     import Tkinter as Tk
 08:     import queen as Q
 09:     
 10:     
 11:     
 12:     Q_font = ("Times", 14)
 13:     
 14:     
 15:     
 16:     def move_queen(now, next):
 17:         return [(i, z1-z0) for i, (z0, z1) in enumerate(zip(now, next)) if z0 != z1]
 18:     
 19:     
 20:     
 21:     class Cboard(Tk.Canvas):
 22:         cell_size = 46
 23:         margin = 5
 24:         q_images = []
 25:         q_figure = []
 26:         
 27:         def __init__(self, master):
 28:             cwidth = 8*self.cell_size 
 29:     
 30:             Tk.Canvas.__init__(self, master, relief=Tk.RAISED, bd=4, bg='white',
 31:                                 width=cwidth, height=cwidth)
 32:                                 
 33:             self.q_answers = Q.eight_queens()
 34:             
 35:     
 36:             for i in range(8):
 37:     
 38:                 for j in range(8):
 39:                     bcolor = (i-j)%2==0 and "#699C69" or "#D4D49F"
 40:                     x0 = i*self.cell_size + self.margin
 41:                     y0 = j*self.cell_size + self.margin
 42:                     self.create_rectangle(x0, y0, x0+self.cell_size, y0+self.cell_size, fill=bcolor, width=0)
 43:     
 44:                 self.q_images.append(Tk.PhotoImage(file="queen.gif"))
 45:                 z =  self.q_answers[0][i]
 46:                 x = self.cell_size*((z / 8)+0.5) + self.margin
 47:                 y = self.cell_size*((z % 8)+0.5) + self.margin
 48:                 self.q_figure.append(self.create_image(x, y, image=self.q_images[i], tags="queen"))
 49:     
 50:     
 51:         def refresh(self, now, next):
 52:             answer_now = self.q_answers[now]
 53:             answer_next = self.q_answers[now+next]
 54:             for i, j in move_queen(answer_now, answer_next):
 55:                 self.move(self.q_figure[i], 0, j*self.cell_size) 
 56:     
 57:     
 58:     
 59:     
 60:     class Queen(Tk.Frame):
 61:     
 62:         def __init__(self, master=None):
 63:             Tk.Frame.__init__(self, master)
 64:             self.master.title("8 Queens")
 65:     
 66:             
 67:             # title
 68:             l_title = Tk.Label(self, text='Eight Queens', font=('Times', '24', ('italic', 'bold')),
 69:                                     fg='#191970', bg='#EEE8AA', width=12)
 70:             l_title.pack(padx=10, pady=10)
 71:     
 72:             # chess board
 73:             self.f_board = Cboard(self)
 74:             self.f_board.pack(padx=10, pady=10)
 75:     
 76:             # buttons and a counter
 77:             self.q_counter = 0
 78:             self.f_footer = Tk.Frame(self)
 79:             self.f_footer.pack()
 80:             self.s_counter = Tk.StringVar()
 81:             self.s_counter.set("%d/12" % (1 + self.q_counter))
 82:             self.a_button = Tk.Button(self.f_footer, text="next", font=Q_font, command = self.show_next)
 83:             self.a_button.pack(side=Tk.LEFT, padx=5,pady=5)
 84:             self.b_button = Tk.Button(self.f_footer, text="prev", font=Q_font, command = self.show_prev)
 85:             self.b_button.pack(side=Tk.LEFT, padx=5,pady=5)
 86:             self.f_label = Tk.Label(self.f_footer, textvariable = self.s_counter, font=Q_font)
 87:             self.f_label.pack(side=Tk.LEFT, padx=5, pady=5)
 88:     
 89:     
 90:         def show_next(self):
 91:             if(self.q_counter < 11):
 92:                 self.f_board.refresh(self.q_counter, 1)
 93:                 self.change_counter(1)
 94:     
 95:     
 96:         def show_prev(self):
 97:             if(self.q_counter > 0):
 98:                 self.f_board.refresh(self.q_counter, -1)
 99:                 self.change_counter(-1)
100:     
101:     
102:         def change_counter(self, i):
103:             self.q_counter += i
104:             self.s_counter.set("%d/12" % (1 + self.q_counter))
105:         
106:                 
107:     ##--------------------------------------------------- 
108:     if __name__ == "__main__":
109:         app = Queen()
110:         app.pack()
111:         app.mainloop()

3.1. ソースコードの簡単な説明

Tk.Canvas の派生クラス Cboard を作り、その中に升目を作って、Queen を配置します。 Queen を動かすときは .move メソッドを使って Queen のイメージを動かします。

4. 詰碁作成用 GUI

Haskell で詰碁を解くプログラムを作ったので、その GUI を Tkinter で作りました。 碁盤をクリックすると黒石が置かれ、黒石をクリックすると白石に変わり、 白石をクリックすると石が消えます。盤上の石を、詰碁プログラムが読める形式で保存します。 少し長いので、Tk.Canvas に関する部分だけを示します。興味のある方は 詰碁を解く を見てください。
 01:     class Gboard(Tk.Canvas):
 02:         """Tk.Canvas for Go Board"""
 03:         
 04:         grid_size = 20
 05:         stones = dict()
 06:     
 07:     
 08:         def __init__(self, master):
 09:             
 10:             en = self.grid_size * 19
 11:             z = self.grid_size * 0.36
 12:             hosi = 2
 13:     
 14:             Tk.Canvas.__init__(self, master, relief=Tk.RAISED, bd=4, bg="#F7EE8A",
 15:                                 width=20*self.grid_size, height=20*self.grid_size, highlightthickness=0)
 16:             # binding the goban canvas to put_stones method
 17:             self.bind("<1>", self.put_stones)
 18:             
 19:             # drawing lines and coordinate 
 20:             for i in range(19):
 21:                 x= (i+1) * self.grid_size
 22:                 self.create_line(x, self.grid_size, x, en)
 23:                 self.create_line( self.grid_size, x, en, x)
 24:                 
 25:                 self.create_text(z, x, text=str(i), justify=Tk.RIGHT, fill=" #002010",
 26:                                        font = ("Helvetica","6","normal"))
 27:                 self.create_text(x, z , text=str(i), justify=Tk.CENTER, fill="#002010",
 28:                                        font = ("Helvetica","6","normal"))
 29:     
 30:             # drawing black dots
 31:             for i in range(4,17,6):
 32:                 for j in range(4,17,6):
 33:                     x = i * self.grid_size
 34:                     y = j * self.grid_size
 35:                     self.create_oval(x-hosi, y-hosi, x+hosi, y+hosi, fill= "black")
 36:     
 37:     
 38:     
 39:     
 40:         def bclear(self):
 41:             """ clear stones in the map and on the canvas"""
 42:             self.stones.clear()
 43:             self.delete("stone")
 44:     
 45:     
 46:     
 47:     
 48:         def bopen (self, fname):
 49:             """open *.gbn file, whose content is ( [kuro ishi positions], [shiro ishi positions])"""
 50:             fp = file(fname)
 51:             lb, lw = eval(rm_cmt(fp.read()))
 52:             fp.close()
 53:             
 54:             for row, col in lb:
 55:                 self.stones[(row,col)] = self.put_a_stone(row, col, "black")
 56:      
 57:             for row, col in lw:
 58:                 self.stones[(row,col)] = self.put_a_stone(row, col, "white")
 59:     
 60:     
 61:     
 62:     
 63:         def bsave(self, fname):
 64:             """ save positions of stones in a file """
 65:             lb = []
 66:             lw = []
 67:             for key, val in self.stones.iteritems():
 68:                 if self.itemcget(val, "fill") == "black":
 69:                     lb.append(key)
 70:                 else:
 71:                     lw.append(key)
 72:             fp = file(fname, 'w')
 73:             fp.write("-- ([black stones], [white stones])\n")
 74:             fp.write ("(\n     ")
 75:             fp.write(str(lb))
 76:             fp.write(",\n     ")
 77:             fp.write(str(lw))
 78:             fp.write("\n)\n")
 79:             fp.close()
 80:     
 81:     
 82:     
 83:     
 84:         def put_stones(self, event):
 85:             """ putting go ishis on the goban,
 86:             this function is bounded to the left one click of the mouse"""
 87:     
 88:             cx = self.canvasx(event.x, self.grid_size)
 89:             cy = self.canvasy(event.y, self.grid_size)
 90:     
 91:             col = int(cx/self.grid_size) - 1 
 92:             row = int(cy/self.grid_size) - 1
 93:             if (0<=row<=18 and 0<=col<=18):
 94:                 if (not ((row,col) in self.stones)) :
 95:                     self.stones[(row,col)] = self.put_a_stone(row, col, "black")
 96:                 elif(self.itemcget(self.stones[(row,col)], "fill") == "black"):
 97:                     self.itemconfig(self.stones[(row,col)], fill="white")
 98:                 else:
 99:                     self.delete(self.stones[(row,col)])
100:                     del self.stones[(row,col)]
101:     
102:     
103:     
104:     
105:         def put_a_stone(self, row, col, color):
106:             """ it drows a go-ishi on the goban canvas and returns the go-ishi's ID """
107:             r = self.grid_size * 0.45
108:             x_left = (col+1)*self.grid_size-r
109:             x_right = (col+1)*self.grid_size+r
110:             y_top = (row+1)*self.grid_size-r
111:             y_bottom = (row+1)*self.grid_size+r
112:             return self.create_oval(x_left, y_top, x_right, y_bottom, fill = color, tags = "stone")
113:     
114:     
115:     
116:     
117:         def add_n_on_stone(self, i, row, col, tcolor):
118:             """ draw number of a stone """
119:             return self.create_text((col+1)*self.grid_size, (row+1)*self.grid_size,
120:                                      text=str(i+1), justify=Tk.CENTER, fill=tcolor,
121:                                        font = ("Helvetica","8","bold"), tags="stone")

4.1. 簡単な説明

GboardTk.Canvas の派生クラスで、碁盤を表します。

5. 終わりに

今回は、Tk.Canvas を使ってボードゲーム用 GUI を作成しました。 Tk.Canvas の使い方もコツを覚えれば、それほど難しくありません。 tag_binditemconfigure メソッドが使いこなせればいろいろなことができます。 Tkinter demo にも Tk.Canvas のいろいろな例がありますので 見てみてください。

次回は大画面の Tk.Canvas を用いて スクロールバーのつけ方について解説します。