The Cure To His Own Curiosity
Minesweeper In Python GUI
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 )
| This entry was posted by lionel on June 6, 2010 at 9:36 am, and is filed under Programming, Python. Follow any responses to this post through RSS 2.0. You can leave a response or trackback from your own site. |
- Email Marketing Software and Privately Owned Used Car Dealerships
- Hyper-Fast scrolling with the Logitech V550 Nano Laser Mouse
- Zoom Digital Cameras » Blog Archive » Brand New Canon Powershot A1100is 12.1 Megapixel Silver
- What is the weight of an average ball python? | beauty style answers
- Top 10 : Web Hosting Service Providers | Good Web Hosting
- Random Links #207 | YASDW – yet another software developer weblog
- Python “located
- Minesweeper in Python and PyQT | The Pythonian
- Linux Gui Programming Glade | Linux Mandrake
- Предложение
- ScoreBoard and ClockTimer Widget For Games
- How do I become a gangster? | Tea
- FlashMinesweeper:MP | Games
- Minesweeper | Games
- Minesweeper: Classic | Games
- Bauble Sweeper | Games


about 2 months ago
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.
about 2 months ago
too many Top in your code (ligne 181 etc.)
but, nice idea to implement a classic game…
about 2 months ago
Oh, yeah, this is a perfect fit for the Planet Perl Ironman feed.
about 2 months ago
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’
>>>
about 2 months ago
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/
about 2 months ago
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:
about 2 months ago
Yiaks ~~ >.<
can u pls comment out [line 041] "self.move(tmp)" and give it a try again?
about 2 months ago
Thanks
about 2 months ago
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