Random := java("Random") JFrame := java("JFrame") JPanel := java("JPanel") JButton := java("JButton") JLabel := java("JLabel") Thread := java("Thread") GridLayout := java("GridLayout") FlowLayout := java("FlowLayout") BorderFactory := java("BorderFactory") BevelBorder := java("javax.swing.border.BevelBorder") Font := java("Font") ZSwingHelper := java("ZSwingHelper") eventTunnel := ZSwingHelper() # A class to represent the playing field as a whole. class Mines extends JFrame # An inner class to represent an individual button on the field. class MineButton extends JButton @@font := Font("Arial", Font.ITALIC bor Font.BOLD, 30) def toSuper ["#"] enddef def initialize |x y field| setFont(@@font) # If user clicks this button, call openCell with coordinates. eventTunnel.addAction(this); eventTunnel.setMethod("actionPerformed", this, { field.openCell(x y) } ) enddef endclass # Some class variables @@rng := Random() @@neighbours := [[1,0] [1,1] [0,1] [-1,1] [-1,0] [-1,-1] [0,-1] [1,-1]] @@emptyBorder := BorderFactory.createEmptyBorder() @@bevelBorder := BorderFactory.createBevelBorder(BevelBorder.RAISED) # Pass a string parameter to the constructor of superclass JFrame def toSuper |gx gy| ["Zilk Minesweeper " + gx + "*" + gy] enddef # The constructor def initialize |gx gy| # Set up the game window @gx := gx @gy := gy @time := 0 @mustTerminate := false setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE) getContentPane().setLayout(nil) setSize(50*gy+50 50*gx+70) eventTunnel.addWindow(this) eventTunnel.setMethod("windowClosed", this, { @mustTerminate := true} ) # Create the panel to hold the buttons @panel := JPanel() @panel.setSize(50*gy 50*gx) @panel.setLocation(20 30) @panel.setLayout(GridLayout(@gx @gy)) getContentPane().add(@panel) # Create the label to hold the time @timeLabel := JLabel("0 ") getContentPane().add(@timeLabel) @timeLabel.setSize(70 20) @timeLabel.setLocation(20 5) # Initialize the game createButtons(@gx @gy) createField(@gx @gy) setVisible(true) @gameOn := true # Launch the background thread for the ticking timer t := Thread(this.tick) t.start() enddef def tick until @mustTerminate Thread.sleep(1000) if(@gameOn) @time := @time + 1 @timeLabel.setText("" + @time) endif enduntil enddef # Check if coordinates fall outside the field def isOutside |x y| x < 0 or x >= @gx or y < 0 or y >= @gy enddef # Returns the character that is used to show the value of cell (x,y). def cellChar |x y| if @mine[x][y] "M" else "" + " 12345678"[@value[x][y]] endif enddef # Opens the cell (x,y). def openCell |x y| unless @gameOn createField() return @gameOn := true endunless unless isOutside(x y) or @open[x][y] @open[x][y] := true @buttons[x][y].setBorder(@@emptyBorder) if @mine[x][y] @gameOn := false (0...@gx).each({ |x| (0...@gy).each({ |y| @buttons[x][y].setText(cellChar(x y)) }) }) else @buttons[x][y].setText(cellChar(x y)) if @value[x][y] = 0 neighbours.each({ |n| openCell(x + n[0], y + n[1]) }) endif endif endunless enddef # Creates the JButton objects used by the game. def createButtons @buttons := [] (0...@gx).each({ |x| @buttons[x] := [] (0...@gy).each({ |y| @buttons[x][y] := MineButton(x y this) @panel.add(@buttons[x][y]) }) }) enddef # Creates the mines and values of the playing field. def createField @mine is $List.create(@gx @gy) @open is $List.create(@gx @gy) @value is $List.create(@gx @gy) (0...@gx).each({ |x| (0...@gy).each({ |y| @mine[x][y] := (@@rng.nextInt(100) < 20) @open[x][y] := false @buttons[x][y].setText("#") @buttons[x][y].setBorder(@@bevelBorder) }) }) # Compute the number of mines in each cell's neighbourhood. (0...@gx).each({ |x| (0...@gy).each({ |y| count := 0 @@neighbours.each({ |n| x2 := x + n[0] y2 := y + n[1] if (not isOutside(x2 y2)) and @mine[x2][y2] count := count + 1 endif }) @value[x][y] := count }) }) @time := 0 enddef endclass # Start the game! m := Mines(10 12)