Jocul de X și 0 - TicTacToe - în Python și PySide - partea a IV-a
python pyside tic-tac-toe x-si-0
La mutarea a doua am folosit două funcții: verVarianteWin()
și verIncoltiri()
- prima verifică dacă este vreo variantă de câștig la mutarea următoare, a doua dacă omul încearcă vreo încolțire. Prima este folosită la fiecare mutare începând cu mutarea a doua, a doua este folosită numai la mutarea a doua (încolțirile la "x și 0" se inițiază la mutarea a doua). Aș putea spune că verVarianteWin()
este funcția esențială a jocului:
#fct care verifica daca exista variante de castig imediat def verVarianteWin(self, listavarwin, listavarplayer, listavarramase, listavaradversar): listavarplayer = [list(x) for x in itertools.combinations(listavarplayer, 2)] for i in listavarwin: for j in listavarplayer: if (j[0] in i) and (j[1] in i): for k in i: if k not in listavarplayer and k in listavarramase and k not in listavaradversar: self.mutaricalc.append((k[0], k[1])) listavarramase.remove((k[0], k[1])) return (k[0], k[1])
Este o funcție care primește patru argumente: lista cu variantele de câștig listavarwin
, lista cu mutările jucătorului (fie omul, fie calculatorul) listavarplayer
, lista cu mutările rămase listavarramase
, si lista cu mutările adversarului listavaradversar
. Folosind funcția combinations
din modulul itertools
din Pyhton combin două câte două toate mutările făcute de jucător obținând o listă de subliste compuse fiecare din două tupluri (două mutări)
listavarplayer = [list(x) for x in itertools.combinations(listavarplayer, 2)]
Apoi verific dacă aceste două mutări sunt într-o variantă de câștig, dacă sunt - verific dacă mutarea care mai rămâne (o variantă de câștig în "x și 0" are trei mutări) pentru a câștiga este disponibilă, adică nu este deja efectuată și dacă este așa funcția returnează mutarea respectivă care este următoare mutare a calculatorului.
A doua funcție - cea cu încolțirile - e mult mai simplă: transformă mutările omului într-un tuplu (e vorba de primele două mutări - am spus mai sus că aceasta funcție se folosește numai la mutarea a doua pentru că atunci pot apărea încolțirile) și apoi folosind acest tuplu ca cheie (key) în dicționarul incoltiri
, folosind metoda get
specifică unui dicționar (în Pyhton) obținem valoarea (value) atașată respectivei chei
#fct care verifica daca exista vreo incoltire si da mutarea contra ei def verIncoltiri(self): t = tuple(self.mutariom) mutare = self.incoltiri.get(t) if mutare: return (mutare[0], mutare[1]) else: return False
dacă există respectiva cheie (deci dacă e vorba de o încolțire) returnează valoarea atașată ei (mutarea de răspuns la încolțire) dacă nu, returnează False
.
La mutarea a treia
elif len(self.mutariom) == 3: v = self.verVarianteWin(self.varianteWin, self.mutaricalc, self.mutariramase, self.mutariom) if v: self.tabel.item(v[0], v[1]).setText("o") check, litera = self.verWin() else: vv = self.verVarianteWin(self.varianteWin, self.mutariom, self.mutariramase, self.mutaricalc) if vv: self.tabel.item(vv[0], vv[1]).setText("o") else: for i in self.colturi: if i in self.mutariramase: self.tabel.item(i[0], i[1]).setText("o") self.mutaricalc.append((i[0], i[1])) self.mutariramase.remove((i[0], i[1])) break
verificăm mai întâi dacă calculatorul poate câștiga
v = self.verVarianteWin(self.varianteWin, self.mutaricalc, self.mutariramase, self.mutariom)
,dacă poate câștiga pune acolo și chemăm o nouă funcție - verWin()
- cea care verifică dacă cineva (de fapt calculatorul) a câștigat
if v: self.tabel.item(v[0], v[1]).setText("o") check, litera = self.verWin()
, dacă calculatorul nu poate câștiga verifică daca nu cumva omul poate câștiga
vv = self.verVarianteWin(self.varianteWin, self.mutariom, self.mutariramase, self.mutaricalc)
, dacă omul poate câștiga calculatorul o să mute acolo pentru a împiedica asta
if vv: self.tabel.item(vv[0], vv[1]).setText("o")
. Dacă nici omul nici calculatorul nu pot câștiga atunci calculatorul mută într-unul din colțurile rămase libere (nu este cea mai bună mutare dar nici nu pierde mutând așa)
else: for i in self.colturi: if i in self.mutariramase: self.tabel.item(i[0], i[1]).setText("o") self.mutaricalc.append((i[0], i[1])) self.mutariramase.remove((i[0], i[1])) break
La mutarea a patra
elif len(self.mutariom) == 4: v = self.verVarianteWin(self.varianteWin, self.mutaricalc, self.mutariramase, self.mutariom) if v: self.tabel.item(v[0], v[1]).setText("o") check, litera = self.verWin() else: vv = self.verVarianteWin(self.varianteWin, self.mutariom, self.mutariramase, self.mutaricalc) if vv: self.tabel.item(vv[0], vv[1]).setText("o") else: for i in self.colturi: if i in self.mutariramase: self.tabel.item(i[0], i[1]).setText("o") self.mutaricalc.append((i[0], i[1])) self.mutariramase.remove((i[0], i[1])) break else: for i in self.mutariramase: self.tabel.item(i[0], i[1]).setText("o") self.mutaricalc.append((i[0], i[1])) self.mutariramase.remove((i[0], i[1])) break
calculatorul procedează la fel ca la mutara a treia: verifică mai întâi dacă el poate câștiga
v = self.verVarianteWin(self.varianteWin, self.mutaricalc, self.mutariramase, self.mutariom) if v: self.tabel.item(v[0], v[1]).setText("o") check, litera = self.verWin()
, dacă nu poate verifică dacă omul poate câștiga
else: vv = self.verVarianteWin(self.varianteWin, self.mutariom, self.mutariramase, self.mutaricalc) if vv: self.tabel.item(vv[0], vv[1]).setText("o")
, dacă nici acesta nu poate câștiga, calculatorul încearcă să pună într-un colț liber, dacă nu mai e niciun colț liber pune - la întâmplare într-una din cele două celule rămase libere
else: for i in self.colturi: if i in self.mutariramase: self.tabel.item(i[0], i[1]).setText("o") self.mutaricalc.append((i[0], i[1])) self.mutariramase.remove((i[0], i[1])) break else: for i in self.mutariramase: self.tabel.item(i[0], i[1]).setText("o") self.mutaricalc.append((i[0], i[1])) self.mutariramase.remove((i[0], i[1])) break
Dacă lista cu mutări rămase nu mai are niciun element - adică dacă s-au efectuat toate mutările înseamnă că s-a ajuns la remiză
if len(self.mutariramase) == 0: self.statusBar().showMessage("Jocul s-a terminat remiza")
Am terminat de descris functiaPrincipala
.
Mai rămâne de prezentat funcția verWin()
care verifică dacă calculatorul a câștigat - în mod normal ar fi trebuit să verifice și dacă omul a câștigat dar dacă, prin construcția jocului, acesta nu are șansă de câștig de ce să mai verificăm dacă câștigă... - și care selectează (colorează în albastru) celulele câștigătoare (deselectând mutări anterioare)
#fct care verifica daca calc a castigat si daca da, slecteaza campurile castigatoare si deselecteaza campul ultimei mutari def verWin(self): if (self.unu.text() == self.doi.text() == self.trei.text() != ''): for i in self.items: if i.isSelected(): i.setSelected(False) self.unu.setSelected(True) self.doi.setSelected(True) self.trei.setSelected(True) self.statusBar().showMessage("Calculatorul a castigat!") return True, self.unu.text() elif (self.patru.text() == self.cinci.text() == self.sase.text() != ''): for i in self.items: if i.isSelected(): i.setSelected(False) self.patru.setSelected(True) self.cinci.setSelected(True) self.sase.setSelected(True) self.statusBar().showMessage("Calculatorul a castigat!") return True, self.patru.text() elif (self.sapte.text() == self.opt.text() == self.noua.text() != ''): for i in self.items: if i.isSelected(): i.setSelected(False) self.sapte.setSelected(True) self.opt.setSelected(True) self.noua.setSelected(True) self.statusBar().showMessage("Calculatorul a castigat!") return True, self.sapte.text() elif (self.unu.text() == self.patru.text() == self.sapte.text() != ''): for i in self.items: if i.isSelected(): i.setSelected(False) self.unu.setSelected(True) self.patru.setSelected(True) self.sapte.setSelected(True) self.statusBar().showMessage("Calculatorul a castigat!") return True, self.unu.text() elif (self.doi.text() == self.cinci.text() == self.opt.text() != ''): for i in self.items: if i.isSelected(): i.setSelected(False) self.doi.setSelected(True) self.cinci.setSelected(True) self.opt.setSelected(True) self.statusBar().showMessage("Calculatorul a castigat!") return True, self.doi.text() elif (self.trei.text() == self.sase.text() == self.noua.text() != ''): for i in self.items: if i.isSelected(): i.setSelected(False) self.trei.setSelected(True) self.sase.setSelected(True) self.noua.setSelected(True) self.statusBar().showMessage("Calculatorul a castigat!") return True, self.trei.text() elif (self.unu.text() == self.cinci.text() == self.noua.text() != ''): for i in self.items: if i.isSelected(): i.setSelected(False) self.unu.setSelected(True) self.cinci.setSelected(True) self.noua.setSelected(True) self.statusBar().showMessage("Calculatorul a castigat!") return True, self.unu.text() elif (self.trei.text() == self.cinci.text() == self.sapte.text() != ''): for i in self.items: if i.isSelected(): i.setSelected(False) self.trei.setSelected(True) self.cinci.setSelected(True) self.sapte.setSelected(True) self.statusBar().showMessage("Calculatorul a castigat!") return True, self.trei.text() else: return False
: pentru toate variantele de câștig verificăm dacă cele trei celule au același text (în speță 0) și dacă nu sunt goale
if (self.unu.text() == self.doi.text() == self.trei.text() != ''):
în PySide widgetul QTableWidget
se comportă ca un tabel normal: celula în care faci clic este selectată (e colorată în albastru) ori eu vreau ca dacă cineva (de fapt - calculatorul) câștigă, numai celulele câștigătoare să fie selectate nu și ultima celulă în care a mutat omul. Pentru aceasta deselectez mai întâi celula ultimei mutări a omului
for i in self.items: if i.isSelected(): i.setSelected(False)
, selectez celulele câștigătoare
self.unu.setSelected(True) self.doi.setSelected(True) self.trei.setSelected(True)
, scriu în bara de stare
self.statusBar().showMessage("Calculatorul a castigat!")
Si cu această funcție am terminat de prezentat jocul.
Concluzii
Evident jocul poate fi îmbunătățit. Aș vrea să adaug câteva neajunsuri (un fel de TO DO list):
- dacă omul face clic de două ori în aceeași celulă calculatorul se încurcă. Evident că se poate corecta asta în cod: să se afișeze un mesaj de eroare și clicul resprectiv să nu fie luat în calcul.
- așa cum e făcut jocul omul trebuie să mute primul. Nu știu cât de mult ar trebui modificat codul pentru a da omului posibilitatea să aleagă dacă mută primul sau al doilea.
- jocul nu are grade de dificultate (așa cum am văzut la alte jocuri de "x și 0"). Jocul e conceput la nivel expert din prima.
- codul poate fi optimizat. Anumite funcțioanalități (cred că) pot fi încapsulate în funcții care pot fi chemate apoi de câte ori e nevoie fără a fi nevoie să rescrii mereu aceleași bucăți de cod. Probabil și logica jocului (partea de "inteligență artificială") poate fi îmbunătățită (oarecum generalizată) dar momentan nu prea-mi dau seama cum.
Acestea fiind spuse închei si acest lung și ultim articol despre jocul de "x și 0" în Python și PySide. (Ro)cod cu spor!
P.S. Codul complet al jocului îl puteti descărca de pe GitHub, aici.