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.
[ad code=1]
[ad code=1]
I won’t go thru everything here, but will only pick a few of the more interesting thing to share it over here.
[ad code=1]
[ad code=1]
Laying out the …. Layout
self.button = []
for row in range(16):
self.button.append( [] )
for col in range(16):
self.button[row].append( MyButton() )
self.button[row][col].setFixedSize(20,20)
self.button[row][col].row = row
self.button[row][col].col = col
self.button[row][col].parent = self
self.button[row][col].revealed = False
layout.addWidget( self.button[row][col], row, col)
QtCore.QObject.connect(self.button[row][col], 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.
[ad code=1]
[ad code=1]
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.
[ad code=1]
[ad code=1]
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.
[ad code=1]
[ad code=1]
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):
[ad code=1]
[ad code=1]
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 rr in range(16):
self.button.append( [] )
for cc in range(16):
self.button[rr].append( MyButton() )
self.button[rr][cc].setFixedSize(20,20)
self.button[rr][cc].row = r
self.button[rr][cc].col = c
self.button[rr][cc].parent = self
self.button[rr][cc].revealed = False
layout.addWidget( self.button[rr][cc], rr, cc)
QtCore.QObject.connect(self.button[rr][cc], 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 rr in range(16):
for cc in range(16):
self.button[rr][cc].revealed = False
self.button[rr][cc].setText("")
self.button[rr][cc].setFlat(False)
self.button[rr][cc].setEnabled(True)
self.button[rr][cc].setPalette(QtGui.QPalette(QtGui.QColor(222,222,222)))
#-----------------------------------------------------
def disableAll(self):
for rr in range(16):
for cc in range(16):
self.button[rr][cc].setEnabled(False)
self.button[rr][cc].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 rr,cc in self.grepSurroundingLocation(button.row, button.col):
if self.button[rr][cc].revealed == False:
self.revealButton( self.button[rr][cc] )
#-----------------------------------------------------
def grepSurroundingBombCount(self, row, col):
count = 0
tmp = self.grepSurroundingLocation(row,col)
for rr,cc in self.grepSurroundingLocation(row,col):
count = count + 1 if self.button[rr][cc].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 row in range(16):
for col in range(16):
self.button[row][col].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_())
#__________________________________________________________________________
#__________________________________________________________________________

Recommended Readings:-
- http://www.cs.usfca.edu/~afedosov/qttut/
- http://diotavelli.net/PyQtWiki/Tutorials
- http://zetcode.com/tutorials/pyqt4/
- http://wiki.python.org/moin/JonathanGardnerPyQtTutorial
- http://www.rkblog.rk.edu.pl/w/p/introduction-pyqt4/
- http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/classes.html
- http://www.commandprompt.com/community/pyqt/x1214




[...] Minesweeper In Python GUI [...]
I subscribed to your blog as a python noob and only flicked past your posts but this one caught my eye, great work. I’ll be sure I read all your posts from now on.
too many Top in your code (ligne 181 etc.)
but, nice idea to implement a classic game…
Oh, yeah, this is a perfect fit for the Planet Perl Ironman feed.
[...] Minesweeper In Python GUI [...]
Not sure which version of QT, I got this error.
Me using Python 2.6.5
pyqt_version 4.7.2
<<<
Traceback (most recent call last):
File "minesweeper-in-python-gui.py", line 204, in
myapp = MainWindow()
File “minesweeper-in-python-gui.py”, line 41, in __init__
self.move(tmp)
TypeError: arguments did not match any overloaded call:
QWidget.move(QPoint): argument 1 has unexpected type ‘QVariant’
QWidget.move(int, int): argument 1 has unexpected type ‘QVariant’
>>>
[...] Minesweeper In Python GUI [...]
Very nice article. I absolutely love Python + PyQt combination. BTW if anyone is interested in how this is done in C++ + Qt take a look at source code here: http://websvn.kde.org/trunk/KDE/kdegames/kmines/
Which version of PyQt are you using? If it’s a relatively modern one, then you really should use the new-style connections, they’re much nicer. Instead of
QtCore.QObject.connect(self.button[r][c], QtCore.SIGNAL("clicked()"), self.revealButtonWrapper )you can write:
Yiaks ~~ >.<
can u pls comment out [line 041] "self.move(tmp)" and give it a try again?
removing line 041 works,
)
I received the same error, and commenting that line did the trick.
I just found Qt and I’m falling in love rapidly
I will study your code,
Thank you very much
Thanks
Yupe i’m aware of that, but I just seems to be much more comfortable sticking to the old way … hahaha
Thanks a lot of the sharing anyway
[...] Minesweeper In Python GUI [...]
[...] Minesweeper In Python GUI [...]
[...] Minesweeper in Python GUI Nice work [...]
[...] Minesweeper In Python GUI [...]
[...] and PyQT The best game ever written for Microsoft Windows can now finally be duplicated using python and the PyQT widget framework. var a2a_config = a2a_config || {}; a2a_config.linkname="Minesweeper in Python [...]
[...] Minesweeper In Python GUI [...]
Владельцу сайта Lionel's Blog…
Здравствуйте! Предлагаем вам поучаствовать в партнерской программе с оплатой за клики. Цена: 700р за 1000 кликов. Формат рекламы: Тизеры. Наш сайт: http://bit.ly/d4Pd5E...
[...] a view of my recently minesweeper game board (link), added with the above mentioned 2 [...]
[...] Minesweeper In Python GUI [...]
[...] Minesweeper In Python GUI [...]
[...] Minesweeper In Python GUI [...]
[...] Minesweeper In Python GUI [...]
[...] Minesweeper In Python GUI [...]