Jocul de X și 0 - TicTacToe - în Python și PySide - partea a II-a


 21 Oct, 2014  doru  633  
python pyside tic-tac-toe x-si-0

În această parte, a doua, a tutorialului despre "x și 0" o să prezint partea grafică. Grafica este realizată folosind biblioteca grafică gratuită PySide.

Toată acțiunea jocului se derulează într-o singură clasă (nu sunt sigur dacă e cea mai bună abordare...):

data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
class Tabel(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)

Clasa Tabel subclasează/moștenește clasa QMainWindow din PySide. Inițial folosisem ca superclasă QWidget dar nu puteam folosi (din câte am văzut) QStatusBar, deci nu puteam adăuga o bară de stare (status bar) ferestrei principale.

Variabila globală data (o listă de liste) mă va ajuta să adaug iteme/elemente fiecărei celule din tabel.

Urmează stabilirea titlului și a dimensiunilor ferestrei principale:

self.setWindowTitle("X si 0")
self.setGeometry(300, 200, 302, 364)

Apoi inițializez clasa QTableWidget atribuind instanța obținută variabilei tabel, de fapt un tabel, în care se va desfășura jocul, cu trei rânduri și trei coloane:

colcnt = len(data[0])
rowcnt = len(data)
self.tabel = QTableWidget(rowcnt, colcnt)

Ascund header-ul vertical și pe cel orizontal (pentru ca un tabel are headere implicit în PySide) întrucât nu am nevoie de ele:

self.tabel.horizontalHeader().hide()
self.tabel.verticalHeader().hide()

Stabilesc fontul pentru (textul din) tabel și dimensiunea acestuia:

font = QFont()
font.setFamily(u"DejaVu Sans")
font.setPointSize(60)
self.tabel.setFont(font)

Am observat că, în Windows, dacă nu se stabilește o familie de font, textul ( icși și zrourile) nu sunt centrate în celule.

Stabilesc înălțimea fiecărui rând din tabel la 100 de pixeli pentru a avea celule pătrate (și nu dreptunghiulare ca într-un tabel Excel):

for row in range(rowcnt):
    self.tabel.setRowHeight(row, 100)

Și, folosind clasa QTableWidgetItem și variabilele inițiate mai sus - rowcnt și colcnt - adaug fiecărei celule din tabel un item dându-i textului din acesta un aliniament de 5 (ca să fie în centrul celulei) - pentru că, de fapt, în aceste iteme se scrie textul:

for i in range(rowcnt):
    for j in range(colcnt):
        item = QTableWidgetItem()
        item.setTextAlignment(5)
        self.tabel.setItem(i, j, item)

Fac ca tabelul să fie elementul central al ferestrei:

self.setCentralWidget(self.tabel)

Adaug o bară de stare ferestrei, cu un mesaj inițial care dispare după trei secunde:

self.statusBar().showMessage("Jocul a inceput. Omul muta primul.", 3000)

Creez două acțiuni (folosind clasa QAction din PySide) - restartAction și exitAction - cărora le adaug câte un icon, o comandă scurtă folosind o combinație de taste, o descriere și le conectez la două funcții, restartJoc și exitJoc pe care le apelează atunci când aceste acțiuni sunt accesate (când se dă clic pe icon sau când se tastează combinația de taste specificată):

restartAction = QAction(QIcon('res3.png'), 'Restart', self)
restartAction.setShortcut('Ctrl+R')
restartAction.triggered.connect(self.restartJoc)

exitAction = QAction(QIcon('exit1.png'), 'Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.triggered.connect(self.exitJoc)

Acțiunile trebuie adăugate unei bare de unelte (tool bar) sau unei bare de meniuri (menu bar), eu o să aleg o bară de unelete pe care o s-o adaug ferestrei principale:

self.toolbar = self.addToolBar('Actions')

Acestei bare de unelete îi adaug apoi cele două acțiuni între care adaug un separator (o mică linie verticală):

self.toolbar.addAction(restartAction)
self.toolbar.addSeparator()
self.toolbar.addAction(exitAction)

Mai am de adăugat cele două funcții care sunt chemate de acțiuni:

def restartJoc(self):
    self.close()
    subprocess.call("python" + " TicTacToe.py", shell=True)

def exitJoc(self):
    self.close()

Aceasta e toată partea grafică a jocului; aș putea să adaug aici și funcția care nu lipsește din nicio aplicație PySide - în care are loc de fapt inițierea și rularea aplicației:

def main():
    app = QApplication(sys.argv)
    widget = Tabel()
    widget.show()
    widget.raise_()
    sys.exit(app.exec_())
        
    
 
if __name__ == "__main__":
    main()

Mai rămâne ca - în postarea următoare - să arăt cum fiecare clic/mutare într-o celulă din tabel este conectat la o funcție care generează un răspuns (o mutare) și să descriu și celelalte funcții necesare folosite. (Ro)cod cu spor!

P.S. Codul complet al jocului îl puteti descărca de pe GitHub, aici.