所以我目前正在使用Python 3.4使用PyQt4编写扫雷图形GUI。除了我无法创造新游戏之外,一切都很容易实现。所以这就是我的程序的设计方式:
我有一个主文件,它只是创建了我在Qt设计器中创建的Ui_MainWindow。在那个Ui_MainWindow类中,我创建了一个Board类的实例。 Board类基本上是所有游戏玩法的控制者。在这个Board类中,我有一个for循环,用于在框架上创建按钮。这些按钮实际上是列表中Cell类的实例(例如[Cell [0],Cell [1]等...] .Cell类继承自QPushButton类,以便我可以为鼠标按钮创建信号。 / p>
除了我无法开始新游戏之外,一切都很顺利。当用户说开始新游戏时,Board类应该删除所有按钮并重新绘制板。它会删除所有按钮,但不会重绘主板。我可能知道造成这种情况的原因,但我的测试似乎都没有证明我的假设。
这就是我的想法:
因为我在2D列表中跟上我的单元格,当我调用.deleteLater()(仅调度删除)时,它会删除新的Cell类实例,因为我告诉Python删除与该列表元素关联的小部件。 (示例数组[row] [col] .deleteLater ... cells = [] ... cells [row] .append(Cell(...))
如果我上面说的话毫无意义,那么这就是我的所有代码:
Main.py
from Main_Window import Ui_MainWindow, QtGui
import sys
if __name__ == "__main__":
# Create GUI application
application = QtGui.QApplication(sys.argv)
window = Ui_MainWindow()
window.show()
sys.exit(application.exec_())
Main_Window.py
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '..\UI\Main_Window.ui'
#
# Created: Mon Jan 12 01:04:45 2015
# by: PyQt4 UI code generator 4.11.3
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
from Board import *
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_MainWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.setupUi(self)
def setupUi(self, MainWindow):
MainWindow.setObjectName(_fromUtf8("MainWindow"))
MainWindow.resize(0, 0)
self.centralwidget = QtGui.QWidget(MainWindow)
self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
self.horizontalLayout_1 = QtGui.QHBoxLayout(self.centralwidget)
self.horizontalLayout_1.setObjectName(_fromUtf8("horizontalLayout_1"))
spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.horizontalLayout_1.addItem(spacerItem)
self.verticalLayout_1 = QtGui.QVBoxLayout()
self.verticalLayout_1.setObjectName(_fromUtf8("verticalLayout_1"))
self.horizontalLayout_2 = QtGui.QHBoxLayout()
self.horizontalLayout_2.setSpacing(0)
self.horizontalLayout_2.setObjectName(_fromUtf8("horizontalLayout_2"))
self.minesLCD = QtGui.QLCDNumber(self.centralwidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.minesLCD.sizePolicy().hasHeightForWidth())
self.minesLCD.setSizePolicy(sizePolicy)
self.minesLCD.setMinimumSize(QtCore.QSize(90, 32))
self.minesLCD.setObjectName(_fromUtf8("minesLCD"))
self.horizontalLayout_2.addWidget(self.minesLCD)
spacerItem1 = QtGui.QSpacerItem(30, 30, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem1)
self.pushButton = QtGui.QPushButton(self.centralwidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.pushButton.sizePolicy().hasHeightForWidth())
self.pushButton.setSizePolicy(sizePolicy)
self.pushButton.setMinimumSize(QtCore.QSize(32, 32))
self.pushButton.setMaximumSize(QtCore.QSize(32, 32))
self.pushButton.setText(_fromUtf8(""))
self.pushButton.setObjectName(_fromUtf8("pushButton"))
self.horizontalLayout_2.addWidget(self.pushButton)
spacerItem2 = QtGui.QSpacerItem(30, 30, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem2)
self.timeLCD = QtGui.QLCDNumber(self.centralwidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.timeLCD.sizePolicy().hasHeightForWidth())
self.timeLCD.setSizePolicy(sizePolicy)
self.timeLCD.setMinimumSize(QtCore.QSize(90, 32))
self.timeLCD.setFrameShape(QtGui.QFrame.Box)
self.timeLCD.setLineWidth(1)
self.timeLCD.setMidLineWidth(0)
self.timeLCD.setObjectName(_fromUtf8("timeLCD"))
self.horizontalLayout_2.addWidget(self.timeLCD)
self.verticalLayout_1.addLayout(self.horizontalLayout_2)
spacerItem3 = QtGui.QSpacerItem(20, 20, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
self.verticalLayout_1.addItem(spacerItem3)
self.gridFrame = QtGui.QFrame(self.centralwidget)
self.gridFrame.setMinimumSize(QtCore.QSize(272, 272))
self.gridFrame.setFrameShape(QtGui.QFrame.StyledPanel)
self.gridFrame.setFrameShadow(QtGui.QFrame.Raised)
self.gridFrame.setObjectName(_fromUtf8("gridFrame"))
self.verticalLayout_1.addWidget(self.gridFrame)
spacerItem4 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.verticalLayout_1.addItem(spacerItem4)
self.horizontalLayout_1.addLayout(self.verticalLayout_1)
spacerItem5 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.horizontalLayout_1.addItem(spacerItem5)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtGui.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 620, 31))
self.menubar.setObjectName(_fromUtf8("menubar"))
self.menuFile = QtGui.QMenu(self.menubar)
self.menuFile.setObjectName(_fromUtf8("menuFile"))
self.menuHelp = QtGui.QMenu(self.menubar)
self.menuHelp.setObjectName(_fromUtf8("menuHelp"))
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtGui.QStatusBar(MainWindow)
self.statusbar.setObjectName(_fromUtf8("statusbar"))
MainWindow.setStatusBar(self.statusbar)
self.actionNew = QtGui.QAction(MainWindow)
self.actionNew.setObjectName(_fromUtf8("actionNew"))
self.actionLeaderboards = QtGui.QAction(MainWindow)
self.actionLeaderboards.setObjectName(_fromUtf8("actionLeaderboards"))
self.actionDifficulty = QtGui.QAction(MainWindow)
self.actionDifficulty.setObjectName(_fromUtf8("actionDifficulty"))
self.actionSettings = QtGui.QAction(MainWindow)
self.actionSettings.setObjectName(_fromUtf8("actionSettings"))
self.actionTips = QtGui.QAction(MainWindow)
self.actionTips.setObjectName(_fromUtf8("actionTips"))
self.actionHow_To_Play = QtGui.QAction(MainWindow)
self.actionHow_To_Play.setObjectName(_fromUtf8("actionHow_To_Play"))
self.actionReadMe = QtGui.QAction(MainWindow)
self.actionReadMe.setObjectName(_fromUtf8("actionReadMe"))
self.menuFile.addAction(self.actionNew)
self.menuFile.addSeparator()
self.menuFile.addAction(self.actionLeaderboards)
self.menuFile.addSeparator()
self.menuFile.addAction(self.actionDifficulty)
self.menuFile.addAction(self.actionSettings)
self.menuFile.addSeparator()
self.menuHelp.addAction(self.actionTips)
self.menuHelp.addAction(self.actionHow_To_Play)
self.menuHelp.addAction(self.actionReadMe)
self.menubar.addAction(self.menuFile.menuAction())
self.menubar.addAction(self.menuHelp.menuAction())
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
self.board = Board(self.gridFrame, self.minesLCD, self.timeLCD, self.pushButton, [spacerItem1, spacerItem2])
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(_translate("MainWindow", "Minesweeper", None))
self.menuFile.setTitle(_translate("MainWindow", "File", None))
self.menuHelp.setTitle(_translate("MainWindow", "Help", None))
self.actionNew.setText(_translate("MainWindow", "New", None))
self.actionLeaderboards.setText(_translate("MainWindow", "Leaderboards", None))
self.actionDifficulty.setText(_translate("MainWindow", "Difficulty", None))
self.actionSettings.setText(_translate("MainWindow", "Settings", None))
self.actionTips.setText(_translate("MainWindow", "Tips", None))
self.actionHow_To_Play.setText(_translate("MainWindow", "How To Play", None))
self.actionReadMe.setText(_translate("MainWindow", "ReadMe", None))
Functions.py
import configparser as cfg
import winsound as win
import random, sys, wave
def play_sound(file):
win.PlaySound(file, win.SND_ASYNC)
def save(file, settings):
"""Saves new default settings"""
configParser = cfg.RawConfigParser() # Get parser
# Set each attribute
configParser.add_section("info")
configParser.set("info", "default", settings[0])
configParser.set("info", "color", str(settings[1]))
configParser.set("info", "sound", str(settings[2]))
configParser.set("info", "question", str(settings[3]))
# Write data to file
with open(file, 'wb') as configFile:
configParser.write(configFile)
def defaults(file):
"""Reads the default settings from a cfg file"""
configParser = cfg.RawConfigParser() # Get parser
configParser.read(file) # Parse file
# Get each attribute
default = configParser.get("info", "default")
color = configParser.getboolean("info", "color")
sound = configParser.getboolean("info", "sound")
question = configParser.getboolean("info", "question")
# Change format if the default is custom
if default == "custom":
sizex = configParser.getint("custom", "sizex")
sizey = configParser.getint("custom", "sizey")
mines = configParser.getint("custom", "mines")
default = [sizex,sizey,mines]
return [default, color, sound, question] # Return list of values
def generate_array(sizex, sizey, mines):
"""Generates an array of the minesweeper board"""
array = []
# Create array with given dimensions
for i in range(sizex):
array.append([])
for j in range(sizey):
array[i].append(0)
coords = []
# Add mines to board array
for amt in range(mines):
while True:
# Set index for mine
x = random.randint(0, sizex-1)
y = random.randint(0, sizey-1)
# Add 1 to each surrounding coordinates if the index is not already a mine
if [x,y] not in coords:
array[x][y] = -1 # -1 is the number for a mine
coords.append([x,y]) # Add index to list of coordinates
# Note: second/third arguments in some if statements guards against negative indexing
try:
if array[x-1][y-1] != -1 and x != 0 and y != 0:
array[x-1][y-1] += 1
except:
pass
try:
if array[x+1][y+1] != -1:
array[x+1][y+1] += 1
except:
pass
try:
if array[x-1][y+1] != -1 and x != 0:
array[x-1][y+1] += 1
except:
pass
try:
if array[x+1][y-1] != -1 and y != 0:
array[x+1][y-1] += 1
except:
pass
try:
if array[x-1][y] != -1 and x != 0:
array[x-1][y] += 1
except:
pass
try:
if array[x+1][y] != -1:
array[x+1][y] += 1
except:
pass
try:
if array[x][y-1] != -1 and y != 0:
array[x][y-1] += 1
except:
pass
try:
if array[x][y+1] != -1:
array[x][y+1] += 1
except:
pass
break
return(array)
Board.py
from PyQt4 import QtGui, QtCore
from Cell import *
import Functions as fn
class Board:
def __init__(self, frame, mineCounter, timeCounter, face, spacers):
"""Class for gameplay board"""
# Save passed in variables
self.frame = frame
self.mineCounter = mineCounter
self.timeCounter = timeCounter
self.face = face
self.spacers = spacers
# Get default settings
self.settings = fn.defaults("..\\Settings\default.cfg")
self.difficultyTable = {"easy": [9,9,10], "intermediate": [16,16,40], "expert": [16,30,99]}
# Set initial difficulty
try:
self.difficulty = self.difficultyTable[self.settings[0]]
except:
self.difficulty = self.settings[0]
self.settings[0] = "custom"
# Declare other variables variables
self.face.clicked.connect(lambda: self.newGame(True))
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.tick)
self.time = 0
self.remaining = 0
self.mines = 0
self.array = []
self.newGame()
def clear(self):
"""Deletes all buttons from frame"""
for row in range(self.difficulty[0]):
for col in range(self.difficulty[1]):
self.cells[row][col].deleteLater()
def newGame(self, clear=False):
"""Start a new game"""
# Clears the current frame
if clear:
self.clear()
# Reset Variables
self.cells = []
self.array = fn.generate_array(self.difficulty[0], self.difficulty[1], self.difficulty[2])
self.mines = self.difficulty[2]
self.remaining = self.difficulty[0] * self.difficulty[1] - self.difficulty[2]
self.time = 0
self.face.setText(":)")
# Start Counters
self.mineCounter.display(self.mines)
self.timeCounter.display(self.time)
# Resize GUI aspects
self.frame.setMinimumSize(QtCore.QSize(self.difficulty[1]*32+2, self.difficulty[0]*32+2))
self.spacers[0].changeSize(32+(self.difficulty[1]-9)*16.25, 32)
self.spacers[1].changeSize(32+(self.difficulty[1]-9)*16.25, 32)
# Create list of cells
for row in range(self.difficulty[0]):
self.cells.append([])
for col in range(self.difficulty[1]):
self.cells[row].append(Cell(self.frame, QtCore.QRect(col*32, row*32, 34, 34), self.array[row][col], self.settings[1], self.settings[3]))
self.cells[row][col].leftClicked.connect(lambda boolean=True, x=row, y=col: self.show(x, y, boolean))
self.cells[row][col].rightClicked.connect(lambda boolean=True, x=row, y=col: self.flag(x, y))
self.cells[row][col].Clicked.connect(lambda: self.face.setText(":o"))
def setMode_difficulty(self, difficulty):
"""Sets game difficulty"""
self.clear() # Remove old buttons
try:
self.difficulty = self.difficultyTable[difficulty]
self.settings[0] = difficulty
except:
self.difficulty = difficulty
self.settings[0] = "custom"
self.newGame(True)
def setMode_graphics(self, value):
"""Sets graphics to true or false"""
self.settings[1] = value
for row in range(self.difficulty[0]):
for col in range(self.difficulty[1]):
self.cells[row][col].setColor(value)
def setMode_sound(self, value):
"""Sets sound to true or false"""
self.settings[2] = value
def setMode_question(self, value):
"""Sets question to true or false"""
self.settings[3] = value
for row in range(self.difficulty[0]):
for col in range(self.difficulty[1]):
self.cells[row][col].setQuestion(value)
def tick(self):
"""Add 1 second to the timer"""
# Timer maxes out at 999
if self.time != 999:
self.time += 1
self.timeCounter.display(self.time)
def endGame(self, win):
"""Stops the current game"""
self.timer.stop()
# Play the game ending sound
if self.settings[2]:
if win:
fn.play_sound("..\\Sounds\\Complete_Sound.wav")
else:
fn.play_sound("..\\Sounds\\Wrong_Sound.wav")
# Disable signal for all buttons and set the coordinate to a flag if the player has won
for row in range(self.difficulty[0]):
for col in range(self.difficulty[1]):
self.cells[row][col].setSignal(False)
if self.array[row][col] == -1 and win:
self.cells[row][col].setDisplay("<|")
def showAll(self):
"""Reveals all mine cells"""
for row in range(self.difficulty[0]):
for col in range(self.difficulty[1]):
self.cells[row][col].reveal(True) # Reveals a mine
def flag(self, row, col):
"""Flags coordinate"""
self.face.setText(":)")
# Set the mine display to the correct number
check = self.cells[row][col].flag()
self.mines += check
self.mineCounter.display(self.mines)
if self.settings[2]:
fn.play_sound("..\\Sounds\\Flag_Sound.wav")
def check(self):
"""Checks to see if the player has won"""
if self.remaining == 0:
self.face.setText("8)")
self.mineCounter.display(0)
self.endGame(True)
def show(self, row, col, mode=True):
"""Reveals coordinate"""
check = True
if mode:
self.face.setText(":)")
if not(self.timer.isActive()):
# First move is always a useful click
while self.array[row][col] == -1:
self.array = fn.generate_array(self.difficulty[0], self.difficulty[1], self.difficulty[2]) # Generate new array
# Change the number of each cell
if self.array[row][col] != -1:
for ind1 in range(self.difficulty[0]):
for ind2 in range(self.difficulty[1]):
self.cells[ind1][ind2].setNumber(self.array[ind1][ind2])
self.timer.start(1000) # Start timer if it is the first move
# Determine the number of the coordinate and set that coordinate as clicked
if self.array[row][col] > 0 and not(self.cells[row][col].flagged) and not(self.cells[row][col].questioned):
self.array[row][col] = None
self.cells[row][col].reveal()
self.remaining -= 1
self.check()
elif self.array[row][col] == 0 and not(self.cells[row][col].flagged) and not(self.cells[row][col].questioned):
self.array[row][col] = None
self.cells[row][col].reveal()
self.remaining -= 1
self.showEmpty(row, col) # Reveals surrounding empty spaces
self.check()
elif not(self.cells[row][col].flagged) and not(self.cells[row][col].questioned):
self.face.setText("x(")
self.showAll() # Reveals all mine locations
self.endGame(False)
else:
check = False
# The sound is only played if it is not being called recursively, the game is not over, and its not a flag
if mode and check and self.settings[2] and self.timer.isActive():
fn.play_sound("..\\Sounds\\Correct_Sound.wav")
def showEmpty(self, row, col):
"""Reveals surrounding empty coordinates"""
# Recursively check each surrounding coordinate and reveal if it equals 0
# Note: second/third arguments in some if statements guards against negative indexing
try:
if self.array[row-1][col-1] >= 0 and row != 0 and col != 0:
self.show(row-1, col-1, False)
except:
pass
try:
if self.array[row+1][col+1] >= 0:
self.show(row+1, col+1, False)
except:
pass
try:
if self.array[row-1][col+1] >= 0 and row != 0:
self.show(row-1, col+1, False)
except:
pass
try:
if self.array[row+1][col-1] >= 0 and col != 0:
self.show(row+1, col-1, False)
except:
pass
try:
if self.array[row-1][col] >= 0 and row != 0:
self.show(row-1, col, False)
except:
pass
try:
if self.array[row+1][col] >= 0:
self.show(row+1, col, False)
except:
pass
try:
if self.array[row][col-1] >= 0 and col != 0:
self.show(row, col-1, False)
except:
pass
try:
if self.array[row][col+1] >= 0:
self.show(row, col+1, False)
except:
pass
Cell.py
from PyQt4 import QtGui, QtCore
colorTable = {"": "black", "1": "blue", "2": "green", "3": "red", "4": "navy", "5": "brown", "6": "teal", "7": "black", "8": "grey", "?": "black", "<|": "black", "x": "red", "*": "black"}
class Cell(QtGui.QPushButton):
# Create signals
leftClicked = QtCore.pyqtSignal()
rightClicked = QtCore.pyqtSignal()
Clicked = QtCore.pyqtSignal()
def __init__(self, parent, geometry, number, color=False, mode=False):
"""Class for a cell"""
# Save passed in variables
self.number = number
self.color = color
self.mode = mode
# Set initial flagging states
self.flagged = False
self.questioned = False
# Inherit from QPushButton
QtGui.QPushButton.__init__(self, parent)
self.setGeometry(geometry)
def mousePressEvent(self, event):
"""Defines event handler for mouse button press"""
self.Clicked.emit() # Emit signal
def mouseReleaseEvent(self, event):
"""Defines event handler for mouse button release"""
QtGui.QPushButton.mousePressEvent(self, event) # Get event handler
# Determinate which button was pressed and emit that signal
if event.button() == QtCore.Qt.LeftButton:
self.leftClicked.emit()
elif event.button() == QtCore.Qt.RightButton:
self.rightClicked.emit()
def flag(self):
"""Flags the cell"""
# Determine value to return
if self.mode:
if self.flagged:
self.flagged = False
self.questioned = True
self.setDisplay("?")
value = 1
elif self.questioned:
self.questioned = False
self.setDisplay("")
value = 0
else:
self.flagged = True
self.setDisplay("<|")
value = -1
else:
if self.flagged:
self.flagged = False
self.setDisplay("")
value = 1
else:
self.flagged = True
self.setDisplay("<|")
value = -1
return value
def reveal(self, mine=False):
"""Reveals the cell"""
# Reveal a mine or wrong flag location
if mine:
if self.flagged and self.number != -1:
self.setDisplay("x")
self.setClick(False)
elif not(self.flagged) and self.number == -1:
self.setDisplay("*")
self.setClick(False)
else:
self.setSignal(False)
# Reveal a non-mine location
else:
if not(self.flagged) and not(self.questioned):
if self.number != 0:
self.setDisplay(str(self.number))
else:
self.setText("")
self.setClick(False)
def setDisplay(self, string):
"""Sets button text"""
self.setText(string)
if self.color:
self.setStyleSheet("QPushButton {color: " + colorTable[string] + "}")
def setClick(self, value1):
"""Sets the button click status"""
self.setEnabled(value1)
def setSignal(self, value):
"""Sets the singal sending status"""
self.blockSignals(True)
def setNumber(self, number):
"""Sets button number"""
self.number = number
注意:显然我的主窗口不完整。