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