めもめも

このブログに記載の内容は個人の見解であり、必ずしも所属組織の立場、戦略、意見を代表するものではありません。

PineSweeper

『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