I believe there is no need for much introduction to this game.

Any Microsoft Window’s users would be very familiar with this famous game.

I just wanted to try out how it would be, and what I would have learnt if I were to make this out from python + pyqt4.

And indeed, I’ve really gained a lot in the process of writing this game.



I won’t go thru everything here, but will only pick a few of the more interesting thing to share it over here.



Laying out the …. Layout

    self.button = []
    for r in range(16):
      self.button.append( [] )
      for c in range(16):
        self.button[r].append( MyButton() )
        self.button[r][c].setFixedSize(20,20)
        self.button[r][c].row = r
        self.button[r][c].col = c
        self.button[r][c].parent = self
        self.button[r][c].revealed = False
        layout.addWidget( self.button[r][c], r, c)
        QtCore.QObject.connect(self.button[r][c], QtCore.SIGNAL("clicked()"), self.revealButtonWrapper )
  • I’m here fixing the land mines to a 16 x 16 layout.
  • Each box is a customized QPushButton. We need a customized QPushButton(MyButton in line 05) because we would like the buttons to react to a right-mouse-click, which the original button doesn’t give the flexibility of doing it. More to this later.


Customizing Our Own QPushButton

class MyButton(QtGui.QPushButton):
  def __init__(self, parent=None):
    super(MyButton, self).__init__(parent)
  def contextMenuEvent(self, event):
    if self.text() == "?":
      self.setText("")
      self.setPalette(QtGui.QPalette(QtGui.QColor(222,222,222)))
      self.parent.marked -= 1
    else:
      self.setText("?")
      self.setPalette(QtGui.QPalette(QtGui.QColor(222,255,222)))
      self.parent.marked += 1
    self.parent.bombcount.setText("%d/40" %self.parent.marked)
  • [line 01] We create a new class call MyButton, inheriting the original QtGui.QPushButton class,
  • [line 02-03] The standard stuff, instantiating the original QPushButton Object into out current class.
  • [line 04] Now this is the interesting part. contextMenuEvent is a built-in event handler which is called whenever a right-mouse-click event happens.
    • The reason why we can’t use this contextMenuEvent in the main class is because, we want to capture the right-mouse-click event which happens inside each of the buttons instead of the parent widget
    • if we were to incorperate this event handler in the main class, we wouldn’t be able to track which exact button is being right clicked.
  • [line 05-12] This if-else loop checks that, if the right-click event happens on an empty box, then it will display a “?” on the box, else, it will remove the existing “?” text from the box.


Expanding/Hiding the score frame without affecting the main layout

    self.scoreframe.setVisible(False)
    mainLayout = QtGui.QGridLayout()
    mainLayout.addLayout(layouttop,0,0)
    mainLayout.addLayout(layout,1,0)
    mainLayout.addWidget(self.scoreframe,0,1,2,1)
    self.setLayout(mainLayout)
    self.layout().setSizeConstraint(QtGui.QLayout.SetFixedSize)
    QtCore.QObject.connect(newgameButton, QtCore.SIGNAL("clicked()"), self.newGame)
    QtCore.QObject.connect(self.timer, QtCore.SIGNAL("timeout()"), self.updateTime)
    QtCore.QObject.connect(topscore, QtCore.SIGNAL("clicked(bool)"), self.scoreframe.setVisible )
  • This is the 2nd thing that I’ve learnt from the process of writing this game.
  • [line 10] We are creating a button call “topscore”. Every time it is being clicked, we will expand the scoreboard so it is visible, clicked again, the scoreboard will contract and be hidden.
  • [line 01] First, lets set the scoreframe to invisible.
  • [line 07] Here, we set the main widget’s layout’s size to ‘SetFixedSize’ modal. This will make the widget to expand/contact as needed ….automagically, so that whenever we set the scoreboard to visible, the widget will expand to accomodate for it, and when the scoreboard is set to invisible. the main widget will in turn contract its size according.


These are the main 2 things that I’ve picked up this time.

The rest of the parts of the program are just pure logical things that is easily achievable, and to name a few (without spoiling the fun so that you can figure it yourself), here are those few useful methods that completes the program:-

def grepSurroundingBombCount(self, row, col):
def grepSurroundingLocation(self, row, col):


As for those that doesn’t like so much challenges, then here’s the entire code.

Just click to reveal it, and you are free to copy and use it anyway you like.

Enjoy :)

#!/usr/bin/python
"""
Mine Sweeper Game
"""

import optparse
import sys
import os
import re
import subprocess
import random
from PyQt4 import QtCore, QtGui

#-----------------------------------------------------
#-----------------------------------------------------
#-----------------------------------------------------

class MyButton(QtGui.QPushButton):
  def __init__(self, parent=None):
    super(MyButton, self).__init__(parent)
  def contextMenuEvent(self, event):
    if self.text() == "?":
      self.setText("")
      self.setPalette(QtGui.QPalette(QtGui.QColor(222,222,222)))
      self.parent.marked -= 1
    else:
      self.setText("?")
      self.setPalette(QtGui.QPalette(QtGui.QColor(222,255,222)))
      self.parent.marked += 1
    self.parent.bombcount.setText("%d/40" %self.parent.marked)

#-----------------------------------------------------
#-----------------------------------------------------
#-----------------------------------------------------
class MainWindow(QtGui.QWidget):
  """ Main Wrapper For GUI """
  def __init__(self, parent=None):
    super(MainWindow, self).__init__(parent)
    setting = QtCore.QSettings()
    tmp = setting.value("position", QtCore.QPoint(0,0))
    self.move(tmp)
    layout = QtGui.QGridLayout()
    layout.setMargin(1)
    layout.setSpacing(1)
    self.button = []
    for r in range(16):
      self.button.append( [] )
      for c in range(16):
        self.button[r].append( MyButton() )
        self.button[r][c].setFixedSize(20,20)
        self.button[r][c].row = r
        self.button[r][c].col = c
        self.button[r][c].parent = self
        self.button[r][c].revealed = False
        layout.addWidget( self.button[r][c], r, c)
        QtCore.QObject.connect(self.button[r][c], QtCore.SIGNAL("clicked()"), self.revealButtonWrapper )
    layouttop = QtGui.QHBoxLayout()
    layouttop.addWidget(QtGui.QLabel("BombCount"))
    self.bombcount = QtGui.QLabel("0/40")
    layouttop.addWidget(self.bombcount)
    self.timer = QtCore.QTimer()
    self.timelabel = QtGui.QLabel("0.0 secs")
    self.timelabel.setStyleSheet("background-color: rgb(0, 0, 0); color: rgb(0,255,0)")
    self.newGame()
    layouttop.addWidget(self.timelabel)
    newgameButton = QtGui.QPushButton("&NewGame")
    newgameButton.setEnabled(True)
    layouttop.addWidget(newgameButton)
    topscore = QtGui.QPushButton("Top&Score>>")
    topscore.setCheckable(True)
    layouttop.addWidget(topscore)
    self.scoreframe = QtGui.QFrame()
    self.scoreframe.setFrameStyle(QtGui.QFrame.StyledPanel|QtGui.QFrame.Sunken)
    scorelayout = QtGui.QVBoxLayout()
    scorelayout.addWidget(QtGui.QLabel("<b>Created By</b> <i>-Lionel Tan-</i> <br><br><br>" +
                                        "Email:<a href=#>yltan@altera.com</a><br>" +
                                        "Ext  :<a href=#>#6315</a> <br><br>" +
                                        "<a href=#>http://lionel.textmalaysia.com</a><br>") )
    self.scoreframe.setLayout(scorelayout)
    self.scoreframe.setVisible(False)
    mainLayout = QtGui.QGridLayout()
    mainLayout.addLayout(layouttop,0,0)
    mainLayout.addLayout(layout,1,0)
    mainLayout.addWidget(self.scoreframe,0,1,2,1)
    self.setLayout(mainLayout)
    self.layout().setSizeConstraint(QtGui.QLayout.SetFixedSize)
    QtCore.QObject.connect(newgameButton, QtCore.SIGNAL("clicked()"), self.newGame)
    QtCore.QObject.connect(self.timer, QtCore.SIGNAL("timeout()"), self.updateTime)
    QtCore.QObject.connect(topscore, QtCore.SIGNAL("clicked(bool)"), self.scoreframe.setVisible )

#-----------------------------------------------------
#-----------------------------------------------------
#-----------------------------------------------------
  def newGame(self):
    self.populateBombs()
    self.marked = 0
    self.gamestarted = 0
    self.timer.stop()
    self.time = QtCore.QTime(0,0,0)
    self.timelabel.setText("0.0 secs")
    self.bombcount.setText("0/40")

    for r in range(16):
      for c in range(16):
        self.button[r][c].revealed  = False
        self.button[r][c].setText("")
        self.button[r][c].setFlat(False)
        self.button[r][c].setEnabled(True)
        self.button[r][c].setPalette(QtGui.QPalette(QtGui.QColor(222,222,222)))
#-----------------------------------------------------
  def disableAll(self):
    for r in range(16):
      for c in range(16):
        self.button[r][c].setEnabled(False)
        self.button[r][c].setFlat(True)
#-----------------------------------------------------
  def updateTime(self):
    tmp = round(self.time.elapsed() / 1000, 1)
    self.timelabel.setText( str(tmp) + " secs" )
#-----------------------------------------------------
  def closeEvent(self, event):
    setting = QtCore.QSettings()
    setting.setValue("position", self.pos())
#-----------------------------------------------------
  def revealButtonWrapper(self):
    if self.gamestarted == 0:
      self.time.start()
      self.timer.start(100)
      self.gamestarted = 1
    button = self.sender()
    self.revealButton( button )
#-----------------------------------------------------
  def revealButton(self, button):
    if button.bomb == True:
      txt = "X"
      color = QtGui.QColor(222,0,0)
      self.timer.stop()
      self.disableAll()
    else:
      txt = str( self.grepSurroundingBombCount(button.row, button.col) )
      color = QtGui.QColor(0,0,222)
    button.revealed = True
    button.setText(txt)
    button.setFlat(True)
    button.setEnabled(False)
    button.setPalette(QtGui.QPalette(color))
    if txt == "0":
      for r,c in self.grepSurroundingLocation(button.row, button.col):
        if self.button[r][c].revealed == False:
          self.revealButton( self.button[r][c] )
#-----------------------------------------------------
  def grepSurroundingBombCount(self, row, col):
    count = 0
    tmp = self.grepSurroundingLocation(row,col)
    for r,c in self.grepSurroundingLocation(row,col):
      count = count + 1 if self.button[r][c].bomb == True else count
    return(count)
#-----------------------------------------------------
  def grepSurroundingLocation(self, row, col):
    """
    Grep all the surrounding cells, and
    return back the location in a list of [row,col]
    format. e.g:-
    [ [row,col], [r,c], [r,c] ....]
    """
    ret = []
    ttop = row-1
    bot = row+1
    left = col-1
    right = col+1
    ### Getting top 3 cells if available
    if top > -1:
      ret.append( [ttop, col] )      # Top
    if bot < 16:
      ret.append( [bot, col] )      # Bot
    if left > -1:
      ret.append( [row, left] )     # Left
    if right < 16:
      ret.append( [row, right] )    # Right
    if top > -1 and left > -1:
      ret.append( [ttop, left] )   # UpperLeft
    if top > -1 and right < 16:
      ret.append( [ttop, right] )  # UpperRight
    if bot < 16 and left > -1:
      ret.append( [bot, left] )   # LowerLeft
    if bot < 16 and right < 16:
      ret.append( [bot, right] )  # LowerRight
    return(ret)
#-----------------------------------------------------
  def populateBombs(self):
    bomblocation = random.sample(range(256), 40)
    x = 0
    for r in range(16):
      for c in range(16):
        self.button[r][c].bomb = True if x in bomblocation else False
        x = x + 1
#-----------------------------------------------------
#-----------------------------------------------------
#-----------------------------------------------------

### Main script
if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    myapp = MainWindow()
    myapp.show()
    sys.exit(app.exec_())
#__________________________________________________________________________
#__________________________________________________________________________
    self.button = []
    for r in range(16):
      self.button.append( [] )
      for c in range(16):
        self.button[r].append( MyButton() )
        self.button[r]
[/c]

 .setFixedSize(20,20)  self.button[r][c]

.row = r self.button[r]

[/c]

 .col = c  self.button[r][c]

.parent = self self.button[r]

[/c]

 .revealed = False  layout.addWidget( self.button[r][c]

, r, c) QtCore.QObject.connect(self.button[r]

[/c]

, QtCore.SIGNAL("clicked()"), self.revealButtonWrapper )