『Hello, world. の次は Mine Sweeper を書いてみると、その言語と仲良くなれそうか感触が分かる。かも。』と先日つぶやいた折に、Python の Tkinter をちょっと調べる機会があってさっそく作ってみました。
環境は RHEL6 の Python 2.6.5 です。Tkinter を使う際の基本要素がほぼほぼ入っているはずなので、ちょっと使い方を思い出したいときにこのコードをながめるだけでいいので便利かなぁと。
ちなみに、HineSweeper は ここ。
## 2011/05/12 ConfWindow からの返り値の受け取り方法をちょっと変更。
pinesweeper.py
#!/usr/bin/python # vim:fileencoding=utf-8 import random from Tkinter import * from tkMessageBox import * class GridBoard( Frame ): def __init__( self, parent, size_x, size_y, init_char=" " ): Frame.__init__( self, parent ) self.panel = [ [ None for x in range( size_x ) ] for y in range( size_y ) ] self.mark = [ [ None for x in range( size_x ) ] for y in range( size_y ) ] self.size_x, self.size_y = size_x, size_y labelfont = ( "Courier", 16, "bold" ) for y in range ( size_y ): self.rowconfigure( y, weight=1 ) for x in range( size_x ): self.columnconfigure( x, weight=1 ) mark = StringVar() mark.set( init_char ) label = Label( self, textvariable=mark, relief=RIDGE, height=1, width=2, font=labelfont ) label.grid( row=y, column=x, sticky=NSEW ) self.mark[ y ][ x ] = mark self.panel[ y ][ x ] = label class MineField( GridBoard ): def __init__( self, parent, gameover, size_x, size_y, mines ): GridBoard.__init__( self, parent, size_x, size_y, "-" ) self.mine = [ [ False for x in range( size_x ) ] for y in range( size_y ) ] self.toggle = { "-":"X", "X":"?", "?":"-" } self.gameover = gameover for c in range( int( size_x * size_y * mines / 100 ) ): x = random.randint( 0, size_x - 1 ) y = random.randint( 0, size_y - 1 ) self.mine[ y ][ x ] = True for y in range ( size_y ): for x in range( size_x ): panel = self.panel[ y ][ x ] panel.bind( "<Button-1>", lambda event, x=x, y=y: self.dig( x, y ) ) panel.bind( "<Button-3>", lambda event, x=x, y=y: self.check( x, y ) ) def finish( self ): result = True for y in range( 0, self.size_y ): for x in range( 0, self.size_x ): mark = self.mark[ y ][ x ].get() if mark in [ "-", "?" ]: result = False if mark == "X" and self.mine[ y ][ x ] == False: result = False return result def dig( self, x, y ): if not self.mark[ y ][ x ].get() in self.toggle: return if self.mine[ y ][ x ]: self.mark[ y ][ x ].set( "*" ) self.gameover( 1 ) c = 0 for my in range( y - 1, y + 2 ): if my < 0 or my >= self.size_y: continue for mx in range( x - 1, x + 2 ): if mx < 0 or mx >= self.size_x: continue if self.mine[ my ][ mx ]: c += 1 self.mark[ y ][ x ].set( str( c ) ) if c == 0: for my in range( y - 1, y + 2 ): if my < 0 or my >= self.size_y: continue for mx in range( x - 1, x + 2 ): if mx < 0 or mx >= self.size_x: continue self.dig( mx, my ) if self.finish(): self.gameover( 0 ) def check( self, x, y ): c = self.mark[ y ][ x ].get() if c in self.toggle: self.mark[ y ][ x ].set( self.toggle[ c ] ) if self.finish(): self.gameover( 0 ) class MessageArea( Frame ): def __init__( self, parent, params ): Frame.__init__( self, parent ) labeltext = "Field: %d x %d, Mines: %d%%" % ( params[ "size_x" ], params[ "size_y" ], params[ "mines" ] ) self.label = Label( self, text=labeltext ) self.label.pack( side=RIGHT ) class ConfWindow( Frame ): def __init__( self, params ): self.cancel = False self.win = Toplevel() self.win.title( "Settings" ) Frame.__init__( self, self.win ) self.params = { "size_x":IntVar(), "size_y":IntVar(), "mines":IntVar() } for i in params: self.params[ i ].set( params[ i ] ) Scale( self, label="Size X", relief=SUNKEN, variable=self.params[ "size_x" ], from_=5, to=20, length=300, tickinterval=5, showvalue=YES, orient="horizontal" ).pack( side=TOP, fill=BOTH, expand=YES ) Scale( self, label="Size Y", relief=SUNKEN, variable=self.params[ "size_y" ], from_=5, to=20, length=300, tickinterval=5, showvalue=YES, orient="horizontal" ).pack( side=TOP, fill=BOTH, expand=YES ) Scale( self, label="Mine(%)", relief=SUNKEN, variable=self.params[ "mines" ], from_=5, to=40, length=300, tickinterval=5, showvalue=YES, orient="horizontal" ).pack( side=TOP, fill=BOTH, expand=YES ) Button( self, text="Retry", command=self.onOK ).pack( side=LEFT ) Button( self, text="Cancel", command=self.onCancel ).pack( side=LEFT ) self.pack( fill=BOTH, expand=YES ) self.win.focus_set() self.win.grab_set() self.win.wait_window() def onOK( self ): self.win.destroy() def onCancel( self ): self.cancel = True self.win.destroy() class Game: def __init__( self, parent, **params ): self.parent = parent self.params = params self.mf = MineField( self.parent, self.gameover, **params ) self.ma = MessageArea( self.parent, self.params ) self.parent.title( "PineSweeper" ) self.makemenu( self.parent ) self.ma.pack( side=BOTTOM, fill=X, expand=NO ) self.mf.pack( side=TOP, fill=BOTH, expand=YES ) def makemenu( self, win ): gamemenu = [ ( "Retry", self.restartgame ), ( "Settings...", self.settings ), ( "Quit", self.quitgame ) ] top = Menu( win ) win.config( menu=top ) game = Menu( top, tearoff=False ) for ( label, cmd ) in gamemenu: game.add_command( label=label, command=cmd, underline=0 ) top.add_cascade( label="Game", menu=game, underline=0 ) def gameover( self, rc ): mes = "Congraturations!" if rc == 0 else "BANG!" if askyesno( mes, "Replay?" ): self.restartgame() else: self.quitgame() def settings( self ): cw = ConfWindow( self.params ) if not cw.cancel: for i in self.params: self.params[ i ] = cw.params[ i ].get() self.restartgame() def restartgame( self, params=None ): if params == None: params = self.params self.mf.destroy() self.ma.destroy() self.__init__( self.parent, **params ) def quitgame( self ): self.parent.quit() if __name__ == "__main__": root = Tk() game = Game( root, size_x=10, size_y=10, mines=10 ) root.mainloop() # vi:ts=4