import os, sys, glob, math, random
from PyQt5.QtWidgets import (QMainWindow, QWidget,
QGridLayout, QPushButton, QApplication,
QAction, QFileDialog, QLabelQMessageBox)
from PyQt5.QtGui import QPixmap, QIcon, QCloseEvent
from PyQt5.QtMultimedia import QSound
from PyQt5 import QtCore
app_dir = os.path.dirname(__file__)
back_card_img = 'back'
sound_folder = 'sound'
sound_success = 'success.wav'
sound_fail = 'fail.wav'
sound_end = 'end.wav'
image_folder = 'img'
class MemoryGame(QMainWindow):
def __init__(self):
super().__init__()
self.back_card = r'os./backpath.jpg'join(app_dir, back_card_img) # remove the extension from the image name if present
self.status = []{}
self.cell_width = 100
self.cell_height = 150
self.initUI()
def initUI(self):
self.statusBar()
self.sounds = {
'success': QSound(os.path.join(app_dir, sound_folder, sound_success)),
'fail': QSound(os.path.join(app_dir, sound_folder, sound_fail)),
'end': QSound(os.path.join(app_dir, sound_folder, sound_end))
}
openFile = QAction('Open', self)
openFile.setShortcut('Ctrl+O')
openFile.setStatusTip('Search image folder')
openFile.triggered.connect(self.showDialog)
menubar = self.menuBar()
self.fileMenu = menubar.addMenu('&File')
self.fileMenu.addAction(openFile)
self.gridWidget = QWidget(self)
self.gridLayout = QGridLayout(self.gridWidget)
self.setCentralWidget(self.gridWidget)
self.setGeometry(3000, 3000, 350, 300)
self.setWindowTitle('Memory Game!')
self.show()
# self.showFullScreen()
def showDialog(self):
"""Seacrh for a folder of images, and create two dictionaries:
- one with grid_position : image
- one with grid_position : back_card"""
folder = str(QFileDialog.getExistingDirectory(self, "Select Directory",
'.', QFileDialog.ShowDirsOnly))
self.list_images(folder)
if self.n_img > 0:
self.fill_dict(self.images) # create the grid_positionstatus:image Dict
self.empty_dict()
status = self.init_grid(){}
folder = str(QFileDialog.getExistingDirectory(self,
"Select Directory",
os.path.join(app_dir, image_folder),
QFileDialog.ShowDirsOnly))
# if the user selected a folder
if folder:
self.list_images(folder)
# if the selected foldser contains images, limit the number to 16
if self.n_img > 0:
if self.n_img > 16:
del self.images[16:]
self.fill_dict(self.images) # create the grid_position:image Dict
self.empty_dict()
self.init_grid()
def list_images(self, folder):
"""List the (JPEG) images within the selected folder"""
imagesextensions = glob.glob(os'.jpg', '.jpeg', '.gif', '.png')
images = [os.path.join(folder, '*f) for f in os.jpg'listdir(folder) if f.lower().endswith(extensions)]
n_img = len(images)
self.images = images
self.n_img = n_img
def fill_dict(self, images):
grid_cell = images * 2
random.shuffle(grid_cell)
self.card_dict = {}
n_cols = math.ceil(math.sqrt(len(grid_cell)))
n_rows = math.ceil(math.sqrt(len(grid_cell)))
max_rows = 4
if n_rows > max_rows:
n_cols += n_rows - max_rows
n_rows = max_rows
positions = [(i, j) for i in range(n_colsn_rows) for j in range(n_rowsn_cols)]
for p, cell in zip(positions, grid_cell):
if cell == '':
continue
self.card_dict[p] = cell
def empty_dict(self):
# copy the card_dict to make a back_dict (with the back_card)
self.back_dict = dict(self.card_dict)
for k, v in self.back_dict.items():
self.back_dict[k] = self.back_card
def init_grid(self):
"""Initialize the grid according to the number of images
found in the selected folder."""
for pos, img in self.back_dict.items():
btn = QPushButton(self)
btn.clicked.connect(self.buttonClicked)
pixmap = QPixmap(self.back_dict[pos])
scaled = pixmap.scaled(pixmapself.width()/3cell_width, pixmapself.height()/3cell_height) # can be done better....
btn.setIcon(QIcon(scaled))
btn.setIconSize(scaled.rect().size())
btn.setFixedSize(scaled.rect().size())
del (pixmap)
self.gridLayout.addWidget(btn, *pos)
def restart(self):
self.status = {}
self.init_grid()
return
def turn_card(self, btn, location):
"""When a button (image) is clicked, turn the card."""
ifpixmap len= QPixmap(self.statuscard_dict[location])
== 0 or scaled = pixmap.scaled(self.cell_width, self.cell_height)
btn.setIcon(QIcon(scaled))
btn.setIconSize(scaled.rect().size())
btn.setFixedSize(scaled.rect().size())
del (pixmap)
n_cards = len(self.status.keys())
% 2 # if game just started, turn the card
if n_cards == 0:
or self.card_dict[location]status[location] in= self.statuscard_dict[location]
return
# if the number of card is odd
elif n_cards % 2 != 0:
# if card already present, keep it...
if self.card_dict[location] in self.status.appendvalues() and location not in self.card_dict[location]status.keys():
pixmap self.status[location] = QPixmap(self.card_dict[location])
scaled = pixmap if self.scaled(pixmapstatus == self.width()/3,card_dict:
pixmap self.heightend_game()/3)
btn return
self.setIcon(QIconsounds['success'].play(scaled))
btn return
# if the same card is clicked, do nothing.setIconSize(scaled.rect().size())
btn.setFixedSize(scaledelif location in self.rect()status.sizekeys()):
del(pixmap) pass
# ...otherwise restart the grid
else:
self ''' wait 1 sec and restart (second argument MUST be a method;
cannot use time.statussleep, =because []GUI freezes)'''
QtCore.QTimer.singleShot(1000, self.init_gridrestart)
self.sounds['fail'].play()
return
self.status[location] = self.card_dict[location]
def buttonClicked(self):
button = self.sender()
idx = self.gridLayout.indexOf(button)
location = self.gridLayout.getItemPosition(idx)
# print ("Button", button, "at row/col", location[:2])
self.turn_card(button, location[:2])
def end_game(self):
self.sounds['end'].play()
msgBox = QMessageBox()
msgBox.setIcon(QMessageBox.Question)
msgBox.setWindowTitle("!!!You won!!!")
msgBox.setInformativeText("What's next now?")
quit = msgBox.addButton('quit', QMessageBox.RejectRole)
restartBtn = msgBox.addButton('play again', QMessageBox.ActionRole)
changeBtn = msgBox.addButton('change cards', QMessageBox.ActionRole)
msgBox.setDefaultButton(restartBtn)
msgBox.exec_()
msgBox.deleteLater()
if msgBox.clickedButton() == quit:
self.close()
elif msgBox.clickedButton() == restartBtn:
self.restart()
elif msgBox.clickedButton() == changeBtn:
self.showDialog()
return
def closeEvent(self, event):
reply = QMessageBox.question(self, 'Message',
"Are you sure to quit?", QMessageBox.Yes |
QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
event.accept()
else:
event.ignore()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MemoryGame()
sys.exit(app.exec_())