Láthatóan hangman programunk nem egy fene bonyolult valami, de az érdekesség kedvéért elkészítettem Qt alapokon is. Jelen előnye számunkra mindössze annyi, hogy a Qt "vászon" a Tk megfelelőjével szemben képes a grafikai elemek élsimítására, így sokkal szebben jelennek meg az akasztott alak vonalai.
Első lépésként a jelenleg pythonhoz létező Qt kötések közül kell választanunk: PyQt vagy PySide. A PyQt a Riverbank Ltd. terméke, régóta létezik és megbízható kiforrott rendszer, ám van egy kis licenszelési bibi vele. Mindaddig, amig GPL-es progit alkotunk vele ingyenes, ám ahogy eladható szoftvert szeretnénk vele készíteni, borsos licenszdíjat kell fizetni. Ezzel szemben a PySide LGPL licenszelésű, így kereskedelmi termékekben is könnyedén használható. A PySide a Nokia munkatársainak az alkotása, és bár az 1-es verziószámot már átlépte, könnyedén futhatunk bugba (én is találkoztam buggal). Ennek ellenére én azt javaslom bátran használjátok, mert általános célokra és felületekre tökéletesen és hibamentesen működik, a Model-Nézet implementációknál vannak néhol belső hibácskák, amiket viszont ki lehet kerülni, ha tudja, ismeri már őket az ember.
Én a Hangmanhez a PySide-ot választottam.
A Qt-s Hangman felépítése a Tkintereshez nagyon hasonló, illetve törekedtem is rá, hogy ne legyenek igazán nagy különbségek, csak ott, ahol ez a kétfajta gui megköveteli.
Először elkészítettem a Qtdesignerrel a program ablakát:
(A felületfájlt a zip-ben megtalálod)
A felület lényegi elemei: egy gomb, egy QGraphicsView, négy label. A gomb az indításhoz, újrakezdéshez kell, a QGraphicsView adja "vásznunkat", a négy labelbe pedig irogatjuk a szókitalálás dolgait, és a programüzeneteket.
Mielőtt a részletes forráskódot megvizsgáljuk, szót kell ejtenem a QGraphicsView használatáról. Ez a widget tulajdonképpen csak egy nézet, egy "ablak" egy grafikai "jelenetre", egy QGraphicsScene elemre. A jelenet tartalmazza az elemeket, míg a nézet megmutatja őket. Egy jelenethez több nézet is tartozhat, így ugyanazt a "jelenetet" több szemszögből is megnézhetjük. Ez jól hangzik, de sajnos akkor is így kell eljárnunk, ha csak egy szimpla nézetet szeretnénk pár vonallal.
Miután elmentettem a felületet hangman.ui néven a következő paranccsal python fájlá konvertáltam:
pyside-piuc hangman.ui > ui_hangmanqt.py
Ezután nekiálltam a hangmanqt.py programnak:
#!/usr/bin/python # -*- coding:utf-8 -*- ######################### # HANGMAN program # oktatási célra # v2.0- # Qt verzió ######################### import sys import os import os.path import random import getopt from PySide.QtCore import * from PySide.QtGui import * from ui_hangmanqt import * # konfigurációs adatok VERZIO="v2.0 Qt verzió" SZOFAJL="hangman_szolista.txt" def help(): print _("""HANGMAN program oktatási célra Használat: python hangmanqt.py [paraméterek] Paraméterek: -h, --help : ez a leírás -v, --version : a program verziószáma -f fajlnév, --file fájlnév : a betöltendő szófájl neve """) class Szavak: def __init__(self): fajl=open(SZOFAJL,'r') self.lista=fajl.readlines() fajl.close() for i in range(len(self.lista)): self.lista[i]=self.lista[i][:-1] def valaszt(self): return self.lista[random.randint(0,len(self.lista)-1)]
Az importok közé bekerültek a PySide importjai, valamint az előzőekben konvertált felületfájlunk python változata. A help függvényünk és Szavak osztályunk megegyezik az előző verziókban használttal. Az Akasztofa osztályunkat és a Jatek osztály Qt-sítani kell. Erre hamarosan rátérünk, de előbb nézzük meg hogy néz ki a programtörzsünk (ez ugye az osztálydefiníciók után, a program végén helyezkedik el):
if __name__=="__main__": try: optlist, args = getopt.getopt(sys.argv[1:], "hvf:", ["help", "version", "file="]) except getopt.GetoptError: help() sys.exit(2) for opt,param in optlist: if opt in ('-h','--help'): help() sys.exit(0) elif opt in ('-v', '--version'): print _("Hangman game %s") % VERZIO sys.exit(0) elif opt in ('-f','--file'): SZOFAJL=param app=QApplication(sys.argv) j=Jatek() j.show() app.exec_()
Ez is az előző verziókhoz hasonló, az eltérés Qt specifikus. Minden Qt programban kell készítenünk egy QApplication objektumot, ami a Qt könyvtárral fogja tartani a kapcsolatot. Ezután példányosítjuk a Jatek osztályt, megkérjük hogy jelenítse meg magát a show() metódussal, majd indítjuk a Qt feldolgozót az app._exec() paranccsal.
Az Akasztofa osztályunk a QGraphicsScene osztályon alapul, vagyis egy jelenet, aminek lehetnek elemei:
class Akasztofa(QGraphicsScene): def __init__(self,parent): super(Akasztofa,self).__init__() self.elemek = [ ['line',150,25,150,50,'#000000'], ['circle',125,50,50,50,'#ff0000'], ['line',150,100,150,160,'#613E20'], ['line',150,115,110,140,'#003700'], ['line',150,115,190,140,'#003700'], ['line',150,160,125,220,'#0000ff'], ['line',150,160,175,220,'#0000ff'], ] self.alap = [ ['line',150,25,275,25,"#4E1B01"], ['line',275,25,275,350,"#4E1B01"], ['line',285,350,50,350,"#4E1B01"], ['line',225,27,273,75,"#4E1B01"], ] self.level=0 self.alaprajz() def alaprajz(self): for vonal in self.alap: pen=QPen(QColor(vonal[5]),8) self.addLine(vonal[1],vonal[2],vonal[3],vonal[4],pen) def rajzol(self,level): pen=QPen(QColor(self.elemek[level][5]),4,Qt.SolidLine,Qt.RoundCap) e=self.elemek[level] if e[0]=='line': self.addLine(e[1],e[2],e[3],e[4],pen) elif e[0]=='circle': self.addEllipse(e[1],e[2],e[3],e[4],pen)
Felépítése hasonlít a Tkinteres változathoz. A jelenetet használat előtt inicializálnunk kell, ezért a konstruktorból meg kell hívnunk a szülő konstruktorát is. A self.elemek lista az egyes hibaszintekhez tartozó, kirajzolandó elemek adatait tartalmazza, a self.alap lista pedig az akasztófa vázát alkotó vonalak adatait. Az alaprajz metódusunk mindössze az akasztófaváz elemeit rajzolja ki. A jelenetre rajzolandó elemek kirajzolásához definiálnunk kell egy rajzoló "tollat", ehhez a QPen osztályt kell használnunk. A definiált toll segítségével vonalakat adunk a jelenethez. Hasonlóképpen működik a rajzol() metódusunk is, csak ebbe beleépítettük a kör és vonal rajzolás lehetőségét is.
Ezek után már csak a Jatek osztályunk maradt hátra:
class Jatek(QMainWindow,Ui_MainWindow): def __init__(self, parent=None): super(Jatek, self).__init__(parent) self.setupUi(self) self.szavak=Szavak() self.akasztofa=Akasztofa(self.horizontalLayout) self.vaszon.setScene(self.akasztofa) self.vaszon.setRenderHint(QPainter.Antialiasing) self.StartButton.clicked.connect(self.start) self.vege=True def start(self): self.segedszoveg.setText(u"Gondoltam egy szóra, próbáld kitalálni!") self.StartButton.setText(u"Új játék") self.StartButton.clicked.connect(self.ujrakezdes) self.vege=False self.nyert=False self.szo=unicode(self.szavak.valaszt(),'utf-8') self.eredmeny=[0]*len(self.szo) self.hiba=0 self.maxhiba=7 self.tippek_=[] self.szokiir() self.tippkiir() def ujrakezdes(self): self.segedszoveg.setText(u"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">p, li { white-space: pre-wrap; }</style></head><body style=\" font-family:\'Droid Sans\'; font-size:10pt; font-weight:400; font-style:normal;\"><p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">A kezdéshez kattints a</p><p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-weight:600;\">"Start"</span></p><p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">gombra.</p></body></html>") self.StartButton.setText(u"Start") self.tippek.setText("") self.nyertszoveg.setText("") self.szokiiro.setText("") self.StartButton.clicked.connect(self.start) for elem in self.akasztofa.items(): self.akasztofa.removeItem(elem) self.akasztofa.alaprajz() def szokiir(self): szo=" " for c in range(len(self.szo)): if self.eredmeny[c]==0: szo+="_ " else: szo+=self.szo[c]+" " self.szokiiro.setText(u'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"><html><head><meta name="qrichtext" content="1" /><style type="text/css">p, li { white-space: pre-wrap; }</style></head><body><p style="font-size:14pt;color:#009800;">' + szo + u'</p></body></html>') def tippkiir(self): self.tippek.setText("Eddigi tippjeid: %s" % ", ".join(self.tippek_)) def keyPressEvent(self,event): if self.vege==True: return self.beker(event) def beker(self,event): tipp=event.text() if tipp in self.szo: for c in range(len(self.szo)): if tipp==self.szo[c]: self.eredmeny[c]=1 if self.eredmeny.count(1)==len(self.szo): self.vege=self.nyert=True else: self.hiba+=1 self.akasztofa.rajzol(self.hiba-1) if self.hiba>=self.maxhiba: self.vege=True self.tippek_.append(tipp) self.szokiir() self.tippkiir() if self.vege: if self.nyert: self.nyertszoveg.setText(u"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\"><html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">p, li { white-space: pre-wrap; }</style></head><body style=\" font-family:'Droid Sans'; font-size:10pt; font-weight:400; font-style:normal;\"><p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:14pt; color:#f26f10;\">Gratulálok! Kitaláltad!</span></p></body></html>") else: self.nyertszoveg.setText(u"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\"><html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">p, li { white-space: pre-wrap; }</style></head><body style=\" font-family:'Droid Sans'; font-size:10pt; font-weight:400; font-style:normal;\"><p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:14pt; color:#f26f10;\">Vesztettél!<br>Érezd magad felakasztva :D<br>A megfejtés: "+self.szo+"</span></p></body></html>")
A Jatek osztályunk a Qt-s QMainWindow és a felületfájlból (ui_hangmanqt.py) származó Ui_MainWindow osztályokon alapul. A konstruktorban (__init__) nem feledkezünk el meghívni a szülő konstruktorát a super függvény segítségével, inicializáljuk a felületünket -> self.setupUi(self), létrehozzuk a Szavak és Akasztofa osztályok egy-egy példányát objektumváltozóként, és csatoljuk az akasztófánkat (ami egy QGraphicsScene!) a vászonhoz (ami egy QGraphicsView!). Csatoljuk a Start gomb kattintás szignáljához a self.start() metódust, és a self.vege változóban jelezzük, hogy nincs folyamatban játék.
Induláskor a start metódusunk lecseréli a gombot "Újrakezdés"-re, választ egy kitalálandó szót, és beállítja a játékváltozókat. A játék működése szempontjából a leglényegesebb metódus a keyPressEvent, ami egy felüldefiniált metódusa a QMainWindow osztálynak. Minden gombnyomás esetén ez a metódus meghívásra kerül. Megnézi, hogy van-e folyamatban játék, és ha igen, meghívja a beker() metódust. Ha nincs folyamatban játék, egyszerűen visszatér.
A beker(), szokiir() és tippkiir() metódusok működése gyakorlatilag megegyezik a Tkinteres megfelelőjükkel, ezért nagyon nem taglalom őket, de azt vegyük észre, hogy a Qt a címkékben is RichText képességekkel rendelkezik, így azok html-szerűen formázhatók.
A teljes program letölthető egy zipben innen.
Várom a hozzászólásokat! ;)
Nincsenek megjegyzések:
Megjegyzés küldése