Kezdésként a jól ismert Hangman játékot fogjuk elkészíteni, de objektumorientált szemlélettel. Az első működő változat linux terminálon fog futni, az utolsó viszont a népszerű Qt GUI-toolkit felülettel jelenik majd meg.
Ha valaki nem ismerné, a játékot két fél játsza. Az egyik gondol egy szóra - általában előre meghatározott témakörben -, és megmondja, hány betűből áll. A másik játékos megpróbálja kitalálni a szó betűit egyesével. Ha jó betűt mond, az első játékos beírja a kitalált betűt a helyére, ha rosszat, akkor egy akasztott ember testrészeit kezdi kirajzolni. A játék addig tart, míg a játékos ki nem találja a szót, vagy az aksztott emberke minden testrésze kirajzolásra nem kerül.
Akinek ez alapján sem világos a játék, annak minden megvilágosodik majd a program készítése során :)
Kezdjünk is hozzá!
Vegyük számba mit is kell csinálni a programunknak.
- "gondolnia" kell egy szót
- be kell kérnie a játékostól egy betűt
- meg kell vizsgálnia a kapott betűt, ha jó, akkor meg kell jelenítenie a szóban, ha nem jó, akkor rajzolni kell az akasztott embert
- ezután meg kell vizsgálnia, ki lett-e találva a szó, vagy ki lett-e rajzolva teljesen az akasztott ember
- ha nem, akkor a 2. ponttól folytatjuk tovább, ha igen, akkor befejeződik a játék
Először is kell egy olyan objektum, ami a szavakat fogja tárolni. Ez lesz felelős a szavak betöltéséért és véletlenszerű kiválasztásáért.
Legyen ez a 'Szavak' objektum.
Milyen műveleteket fog biztosítani nekünk ez az objektum, azaz milyen metódusai lesznek?
Legfontosabb feladata, hogy betöltsön egy listát a lemezről, majd kérésre adjon vissza nekünk egy véletlenszerű szót.
Első prototípusunk legyen ez:
class Szavak: def __init__(self): fajl=open(SZOFAJL,'r') self.lista=fajl.readlines() 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)]
Ahhoz hogy ez működjön pár beállítás kell a hangman.py fájlunkba a fenti classon kívül, így véglegesen legyen ez a tartalma:
#!/usr/bin/python # -*- coding: utf-8 -*- ######################### # HANGMAN program # oktatási célra ######################### import sys import os import os.path import random # konfigurációs adatok SZOFAJL="hangman_szolista.txt" 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)] s=Szavak() print s.lista print s.valaszt()
Az alsó három sor mindössze a 'Szavak' osztályunk tesztelését szolgálja. A teszteléshez csináljunk egy 'hangman_szolista.txt' fájlt pár szóval (minden sorban egy), az enyém így néz ki:
autó káposzta asztal narancs ember szekrény
Teszteléskor ezt az eredményt kaptam:
ati@ati-laptop hangman $ python hangman.py
['aut\xc3\xb3\n', 'k\xc3\xa1poszta\n', 'asztal\n', 'narancs\n',
'ember\n', 'szekr\xc3\xa9ny \n']
narancs
Ugye emlékszünk még, hogy a python 2.x verzióiban a stringek ascii-ban kódolva tárolódnak?
Most menjünk végig a kódon, tekintsük át mi is történik a futtatásakor!
- az első sor a kód futtatásához szükséges, ha 'exec' jogot adunk a fájlunknak (google->shebang), a második sorban pedig megadjuk a python interpreternek, hogy a forráskód utf-8 kódolásban van
- a 9-12 sorokban beimportálunk pár standard modult, amire szükségünk lesz a későbbiekben. A sys az argumentumok kezeléséhez (is) fog kelleni, az os és os.path a fájlok eléréséhez, a random meg a véletlen számokhoz szükséges
- a 14. sortól olyan változóknak adunk helyet, amik a programunk futását alapvetően tudják befolyásolni. Jelenleg csak a SZOFAJL található itt, ebben tudjuk megadni programunknak, hogy melyik fájlt használja a szavak beolvasásához
- a 18-26 sorban helyezkedik el a Szavak osztályunk az __init__ és a valaszt metódusokkal
- az __init__ metódus az osztály példányosításakor fut le, azaz kódunkban akkor, mikor az s változó értéket kap. Ekkor az __init__ megnyitja a SZOFAJL-t, és az s.lista objektumváltozóba teszi a szavakat listaként. A 23-24 sorban lévő ciklusban egy karakterrel megrövidítjük a szavakat, mivel a readlines az újsor karaktert is a szó végére teszi. Az nekünk nem kell.
- a valaszt metódus meghívásakor választ egy véletlenszerű szót a listából a random.randint függvénnyel, amit return-nel ad vissza.
- a 28-30 sorokban az s változóban létrehozunk egy új Szavak példányt, majd kiiratjuk a szó-listát, és választatunk egy véletlenszerű szót a valaszt() metódus segítségével
Így néz ki a teljes akasztott emberünk:
. ######## | # # / \ ## \_/ # /|\ # / | \ # / \ # / \ # # ###########
Egy akasztott ember kép 10 sorból áll, és maga a lógó emberke 7 sor magas, így 7 képet raktam a fájlba. A fájl első sorában ez szerepel: "10", ami az egy képben lévő sorok száma. Ezzel rugalmassá tettem az akasztófa emberkémet, hiszen ha később átrajzolom, akkor csak átírom a sorok számát és máris helyesen kerül beolvasásra. Ha eddig nem érthető valakinek, akkor meg fogja érteni magából a kódból:
class Akasztofa: def __init__(self): fajl=open(AKASZTOFAFAJL,'r') sorokSzama=int(fajl.readline()) self.levelKepek=[] while True: kep="" for i in range(sorokSzama): sor=fajl.readline() kep+=sor if not sor: break self.levelKepek.append(kep[:-1]) if not sor: break fajl.close() def kiir(self,level): print self.levelKepek[level]
A teljes akasztófaember-fájl itt található: link
Nézzük az Akasztofa osztályt.
- az __init__ megnyitja az AKASZTOFAFAJL-t, és rögtön beolvassa az első sort, amiben a képmagasság található, ezt átkovertálja egészre, és a sorokSzama változóba teszi
- ezután a self.levelKepek listát egy ciklusban feltölti az akasztófa képekkel
- az egyes akasztófaképeket annyi sor beolvasásával fűzi össze, amennyit a sorokSzama változóba beolvasott. Ezt a célt szolgálja a for ciklus.
- a readline függvény által beolvasott karakterlánc tartalmazza az új sor karaktert is, ezért van, hogy a listába egy karakterrel rövidebb karakterlánc kerül: self.levelKepek.append(kep[:-1]). Az utolsó sor után ugyanis nincs szükségünk extra sortörésre
- a kiir metódus egy paraméter alapján kiírja a kimenetre a megfelelő akasztottember képet
class Jatek: def __init__(self): self.szavak=Szavak() self.akasztofa=Akasztofa() def start(self): self.vege=False self.nyert=False self.koszonto() self.szo=unicode(self.szavak.valaszt(),'utf-8') self.eredmeny=[0]*len(self.szo) self.hiba=0 self.maxhiba=len(self.akasztofa.levelKepek)-2 self.tippek=[] while not self.vege: self.megjelenit() tipp=self.beker() if tipp is not None: self.tippek.append(tipp) self.ertekel(tipp) self.jatekvege()
Tehát:
- Jatek osztályunk inicializáláskor mindössze egy-egy példányt hoz létre az előzőleg definiált Szavak és Akasztofa osztályból
- A start() metódus a játék két fontos állapotát hamisra állítja, azaz a játéknak még nincs vége, és nem nyerte meg a játékos. Ezután egy köszöntőt nyomtat a konzolra az osztály koszonto() metódusával.
- Gondol egy szóra, azaz meghívja a szavak objektum valaszt metódusát. A kapott szót unicode-ra konvertáljuk, mert a szóban lehet utf-8 kódolású karakter, ami ugye ascii-ban több karakterre konvertálódik. Ezt nem tudjnánk egy bekért másik betűvel összehasonlítani.
- Beállítjuk az eredménylistánkat, amiben a betűk kitalált állapotát fogjuk tárolni, és nullázzuk a játékos hibáit, valamint meghatározzuk, hány hibát véthet. Ez abból adódik, hány hibaképet tároltunk az akasztofa objektumban. Csinálunk egy üres listát a játékos tippjeinek a tárolására
- Ezek után belépünk a játék fő ciklusába. A ciklusban megjelenítjük a megjeleníteni valókat, bekérjük a tippet, és értékeljük. Ez megy addig, amíg az értékelés során a self.vege igaz értéket nem kap.
- Ezután kiírjuk a játék végének üzeneteit
Köszöntő:
Egyszerű, szinte magyarázatot nem igényel.
def koszonto(self): WIDTH=20 print "*"*WIDTH print "HANGMAN" print "*"*WIDTH print "\n" print "Üdvözöllek a játékban!" print "Gondoltam egy szóra, próbáld kitalálni!" print "\n"
A megjelenit metódus a következő:
def megjelenit(self): self.akasztofa.kiir(self.hiba) print "\n" szo=" " for c in range(len(self.szo)): if self.eredmeny[c]==0: szo+="_ " else: szo+=self.szo[c]+" " print szo print "" print "Eddigi tippjeid: "+", ".join(self.tippek) print ""
Kiírja a hibaszintnek megfelelő akasztófa ábrát, és a megfejtési állapotnak megfelelő szót. A nem megfejtett betűk helyett aláhúzás vonal jelenik meg. Ez alatt a játékos eddig megtett tippjei láthatók.
A beker metódus következik:
def beker(self): while True: tipp=unicode(raw_input("Addj meg egy betűt: "),'utf-8') if tipp=="": print "Nem lehet üres tipped! Lógni akarsz??" elif len(tipp)>1: print "Ez érvénytelen tipp, csak egy betűt adj meg!" else: break return tipp
Szintén nagyon egyszerű, beolvas egy adatot, amit unicode-ra konvertál a fentebb ismertetett okok miatt, majd megnézi, hogy érvényes-e a bírt tipp. Ha nem érvényes, azaz üres, vagy nem egy betű, akkor újat kér helyette.
A bekért tipp értékelését az ertekel metódus végzi:
def ertekel(self,tipp): 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: print "Ez nem talált!" self.hiba+=1 if self.hiba>=self.maxhiba: self.vege=True
Megnézi, szerepel-e a tipp a megfejtendő szóban, és ennek megfelelően módosítja a self.eredmeny és self.hiba tartalmát. Ha minden betű megfejtésre került, vagy elértük a hibák maximális számát, a játék végét igazra állítja (self.vege=True).
Már csak a jatekvege metódusunk van hátra:
def jatekvege(self): if self.nyert: print "Gratulálok! Kitaláltad!" else: self.megjelenit() print "Vesztettél! Érezd magad felakasztva :D" print "A megfejtés: "+self.szo
Ebben értékeljük az eredményt, és tájékoztatjuk róla a játékost.
A teljes kód itt található: link
Ne felejtsük el, hogy kell hozzá a hangman_rajz.txt és a hangman_szolista.txt
kedvet kaptam, hogy kipróbáljam Tkinterrel, meg ASCII art helyett képpel. ha kész, dobom a linket:)
VálaszTörlésszuper, köszönjük! mi is el fogunk ide jutni, de előtte parancssori paraméterek és gettext localization a terv. ha gondolod, és írsz egy cikket, betehetjük ide. lehetnél cikkíró is esetleg, ha van kedved/időd hozzá
VálaszTörlésA 'jatekvege' funkcióban a
VálaszTörlésprint "A megfejtés: "+self.szo
sor unicode errort dob 2.7.2-es pythonnal.
A self.szo.encode("utf-8") megoldotta a dolgot.
print u"A megfejtés: " + unicode(self.szo,"utf-8")
Törlésegy kicsit biztonságosabb, mert mindkét stringelem unicode objektum lesz. Igen egyébként a 2.7.2 "more strict" a stringek tekintetében, a fenti megoldás vígan futott 2.6-tal.
Szia Attila !
VálaszTörlésEz érdekes. Nálam a "unicode(self.szo,"utf-8")"
hibaüzenetet ad (TypeError: decoding Unicode is
not supported), a "self.szo.encode("utf-8") "
működik. Mi lehet ennek az oka ?
Miben is különbözik a kettő?
melyik python verzió?
VálaszTörléspython 3 esetén nem kell az unicode konverzió. amennyiben a saját locale utf-8, akkor sem kell unicode konverzió 2.7 esetén (azt hiszem, de ebben nem vagyok 100%-ig biztos). 2.6-nál kellett azért, mert a saját locale utf-8 esetén a self.szo str objektum volt a locale szerint kódolva, és ebből unicode objektumot gyártottunk ezzel, a két unicode objektumot ezután össze tudtuk fűzni. Lassan teljesen beérik a python3 (értsd modulok tekintetében), és ezeket a problémákat teljesen elfelejthetjük.
Egyébként a hibaüzenet azt jelenti, hogy utf-8 kódolásból való unicode konverzió nem támogatott.
esetleg, ha kiveszed az encoding paramétert, simán unicode(self.szo), akkor automatikusan kéne csinálnia a locale alapján, de ez nem minden esetben adhat jó végeredményt
TörlésPython 2.6.6
VálaszTörlésCsak az encode-al megy
(self.szo.encode("utf-8")).
Ha kiveszem az encoding paramétert,
(simán unicode(self.szo)), akkor is
hibaüzenet jön
(UnicodeEncodeError: 'ascii' codec
can't encode character...)
ha az egyik szoban unicode ékezetes
characterrel találkozik.
Nem alakítja át a locale alapján
automatikusan, pedig még a
nyelvterületet is megadtam
(# -*- coding: utf-8 -*-
import locale
locale.setlocale(locale.LC_ALL,
'hu_HU.UTF-8')).
Azt tudom, hogy a Python 3.x -ban már
nem gond ez, mivel az eleve unicode
kódolást használ. Legalább ez sikerült
a Python 3-ban.
Sajnos a Python 3 nagyon sokban eltér
az elődjétől, néhol nem teljesen
kompatibilis.
Van akik szerint jók a változások,
sokan viszont inkább ésszerűtlennek
tarják a változások többségét.
Neked milyen tapasztalataid vannak
átállás kérdésében ?
Kiadtak ugyan egy programot(2to3) ami
átalakítja a régi forrást, de a
próbáim szerint inkább csak
"többé-kevésbé" oldja meg ezt a
feladatot. Át kell írni a programot
sajna. És elég sok csomag,
python-rendszer még egy jó ideig nem
fog áttérni.
Rég óta szemeztem a Python-al de
mostanáig igazából nem mélyedtem el
benne. Kettő-három hónappal ezelőtt
egy megfelelő CMS-t kerestem magamnak
és így akadtam rá a Plone-ra, ami
nagyon megradadott és így most egy
nyomós okkal több a Python tanulására.
Ezért is foglalkozom vele.
Én még nem használom a Python 3-mat,
mivel sajna a Plone még nem tért át
arra. Tervezik az átállást (szavazási
lista arról mit szeretne a python
közösség átállítani :
http://python.org/3kpoll ) nem csak a
Plone, Zope, hanem sok egyéb rendszer/
csomag esetén is, ami még szerintem
várhatóan eltarthat 2-3 évig is.
Azt azt azért jó látni,hogy már elég
sok rendszer python 3 kompatibilis.