Úvod do programovania v Pythone (praktická časť)
Skôr, ako sa zahryzneme do analýzi a poniektorí azda aj vylepšovania niekoľkých zaujímavých a jednoduchých pluginov pre čítač obrazovky NVDA, oboznámime sa ešte s konceptom trieda, spomenutým na konci teoretickej časti. Kopec toho zanedbáme, povieme si len to, čo naozaj treba na to, aby sme mohli porozumieť vyššie spomenutým príkladom. Záujemcovia o konzistentné a vyčerpávajúce informácie nahliadnu do seriálu spomenutého v prvej časti tohoto minikurzu a do dokumentov spomenutých na konci tohoto textu.
Zľahka o triedach
V predchádzajúcom sme sa viac krát stretli s použitím objektov. Jazyk python je plne objektový jazyk, z čoho vyplýva, že všetko v jazyku je objekt.
---(číselná premenná)
>>> a=5
>>> dir(a)
['__abs__', '__add__', '__and__', '__class__', '__cmp__', '__coerce__', '__delattr__', '__div__', '__divmod__', '__doc__', '__float__', '__floordiv__', '__format__', '__getattribute__', '__getnewargs__', '__hash__', '__hex__', '__index__', '__init__', '__int__', '__invert__', '__long__', '__lshift__', '__mod__', '__mul__', '__neg__', '__new__', '__nonzero__', '__oct__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdiv__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'bit_length', 'conjugate', 'denominator', 'imag', 'numerator', 'real']
---
Funkcia dir, ktorú sme v príklade použili vypíše všetko, čo je v objekte definované. Teda všetky atribúty a metódy.
O metódach už niečo vieme, atribútmy budeme nazývať premenné, ktoré sú definované vovnútry v objekte.
Ukážme si ešte jeden objekt:
---(funkcia)
>>> def pokus():
... print "ahoj, ja som funkcia pokus"
...
>>> pokus()
ahoj, ja som funkcia pokus
>>> dir(pokus)
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']
---
Z textu vyššie vidíme, že aj funkcia je objekt. Môžete si vyskúšať vyvolať help na ľubovoľného člena funkcie.
Na to aby mohli v pythone vznikať objekty, musia existovať tzv triedy, z ktorých sa objekty vytvárajú. Ako celkom fajn analógiu z reálneho sveta môžme použiť škatuľku. Škatuľkou nazveme hocičo, čo je vovnútri duté, dá sa to nejako otvoriť, nieje to príliš veľké no zároveň je vo vnútri dosť priestoru na to, aby sa ta dalo niečo vložiť. Pojem škatuľka teda môžme nazvať triedou.
Ak si predstavíme nejakú konkrétnu škatuľku, napríklad tú od lentiliek, tak sme jej priradili nejaké presné črty (materiál, rozmery, otvárateľnosť z oboch strán, grafický popis) a budeme jej hovoriť objekt. Objekt škatuľka od lentiliek je inštanciou triedy škatuľka.
Rovnako v pythone existuje trieda list, ktorá sa použije ako "šablóna" v prípade, že vytvárame objekt typu zoznam. (vyskúšajte dir(list)).
Skúsme si teraz vyrobiť triedu škatuľka:
---(trieda skatulka)
class skatulka:
dlzka=0
vyska=0
sirka=0
obojstranna=""
popis=""
pocet=0
def __init__(self,dlzka,sirka,vyska,obojstranna,popis):
self.dlzka=dlzka
self.sirka=sirka
self.vyska=vyska
self.obojstranna=obojstranna
self.popis=popis
skatulka.pocet+=1
def objem(self):
return self.vyska*self.sirka*self.dlzka
---
Uložte ju do priečinka python, ktorý sme si vyrobili a použite v konzole (nezabudnite si do sys.path pridať cestu k priečinku python, po reštarte nvda tam nieje).
---
>>> import a # moj modul so skatulkou sa vola a.py
>>> zoznam=[]
>>> zoznam.append(a.skatulka(5,5,5,"nie","na nausnice"))
>>> zoznam.append(a.skatulka(5,4,1,"nie","od zapaliek"))
>>> zoznam
[<a.skatulka instance at 0x05ABE8F0>, <a.skatulka instance at 0x055A51E8>]
>>> for x in zoznam:
... print "%s ma objem %f" %(x.popis,x.objem())
...
na nausnice ma objem 125.000000
od zapaliek ma objem 20.000000
>>> a.skatulka.pocet
2
---
Nakoniec ešte pár poznámok ku kódu triedy:
- kľúčové slovo class uvádza definíciu triedy, podobne ako def uvádza definíciu funkcie.
- vrámci triedy sme nadefinovali a nainicializovali pár premenných, tie môžme nazvať atribútmi.
- potom sme vytvorili metódu so zvláštnym názvom __init__, ktorá má jeden parameter self. Táto metóda sa zavolá automaticky vždy, keď vytvárame objekt založený na vytváranej triede. Všimnite si, akým spôsobom sme do zoznamu pridávali objekty triedy skatulka. Tejto metóde sa vrámci triedy hovorí konštruktor, pretože sa vykonáva pri konštruovaní inštancie triedy.
- možno ste si všimli, že parameter self sme používali vždy, ak sme chceli ukladať alebo čítať z atribútu vytvoreného v triede. cez parameter self pristupujeme k atribútom ale aj funkciám inštancie objektu, teda self.popis obsahuje popis tej triedy, ktorej kód ho práve číta.
- raz sme ale modifikovali atribút triedy a namiesto self sme použili názov modulu a triedy. Takže sme nemodifikovali atribút pocet vrámci inštancie skatulky, ale v triede skatulka. Tento trik môžme využiť napríklad v situáciách, keď chceme počítať, koľko inštancií našej triedy program vytvoril. V tejto situácii sme ho použili pre to, lebo veríme, že tým pomôžeme neistému čitateľovy pochopiť, načo slúži atribút self, ktorý používame pri deklarácii všetkých metód triedy.
- Nakoniec sme v triede zadefinovali jednu triviálnu metódu na vypisovanie informácií o inštancii triedy.
zľahka o dedičnosti (inheritance)
Z doteraz poznaného nám snáď vyplýva, že triedy majú pre bežného programátora veľký význam v tom, že nám umožňujú použiť existujúci kód. Ak si vytvoríme zoznam, nemusíme sa zamýšľať nad tým, akým spôsobom ho metóda sort utriedi, ako je v pamäti reprezentovaný, pretože aj tieto veci za nás rieši trieda list.
Triedy teda možno chápať ako batérie užitočných kusov kódu, ktoré môžeme používať v našich programoch. Ich celkom zaujímavou vlastnosťou je ale aj fakt, že ich môžeme rozširovať. Môžme vyrobiť svoju vlastnú triedu, ktorá je založená na inej triede. Vlastne každá trieda (aj tá ktorú sme vytvorili v predchádzajúcej kapitole) je založená na nejakej triede.
Ak vytvoríme triedu, ktorú odvodíme z inej existujúcej triedy, môžeme meniť metódy pôvodnej triedy, ale aj vytvárať nové metódy, ktoré nadradená metóda nemá.
Práve na dedičnosti a upravovaní metód je založené vytváranie pluginov pre NVDA. Skôr, ako sa pustíme do hrania sa s prvým jednoduchým nvda pluginom, priblížme si ešte dedenie a upravovanie metód na nejakom príklade z reálneho života.
V knižkách o programovaní sa dedenie často prirovnáva k ríši zvierat. My ale tento problém skúsime uchopiť cez ovládanie rádia, pretože to sa bude aspoň trochu podobať na to, čo robí nvda s global pluginmi.
Ak si predstavíme bežné rádio a jeho ovládanie, tak má určite vypínač, ktorým ho môžeme zapnúť, ďalej nejaké ladiace prvky, ovládač hlasitosti a povedzme že ešte prepínanie vĺn. Ak vieme akým spôsobom sa tieto ovládacie prvky označujú, tak by pre nás nemalo byť ťažké ovládať ľubovoľné rádio.
Existujú rôzne rádiá, ktoré k bežnému štandardu pridávajú rôzne funkcie navyše a okrem toho fungujúna rôznych princípoch. Povedzme že úplne základné rádio má riešené ladenie staníc otočným kolieskom.
Potom môže existovať iné rádio, ktoré má rovnaké vlastnosti ako to základné (dá sa zapnúť, dať tichšie a hlasnejšie,...) len ladenie staníc je riešené digitálnym spôsobom. Ak výrobca rádia vyrobil ladenie tak, že z rádia trčí otočné koliesko, ktorého točením ladíme stanice, tak nás vôbec netrápi, ako samotné ladenie staníc funguje, hoci by bolo vo vnútri otočné koliesko aj sprevodované na nejaký digitálny ladiaci systém. Z nášho pohľadu toto moderné rádio zdedilo všetky vlastnosti toho základného.
Tretie rádio môže pridávať napríklad možnosť ukladať predvoľby do pamätí. Ak by sme o takejto funkcii nevedeli, tak by sme mohli pokojne tlačidlá slúžiace na ukladanie staníc ignorovať a rádio používať ako klasické. Toto rádio však pridáva nejaké nové funkcie, teda rozširuje obyčajné základné rádio.
Čítač obrazovky nvda obsahuje špeciálnu triedu nazvanú globalPluginHandler.GlobalPlugin, ktorá definuje nejaké základné metódy a atribúty, cez ktoré nvda dokáže komunikovať so všetkým čo je na tejto triede založené. Teda, táto trieda je to základné rádio, ktoré NVDA vie ovládať, pretože definuje, na čo presne sa mápoužiť ktorý atribút a kedy sa zavolá ktorá metóda.
My môžme vytvárať triedy odvodené z tejto základnej triedy a upraviť metódy a atribúty tak, aby na to čítač obrazovky NVDA "neprišiel" (z jeho pohľadu obsahujú atribúty zmysluplné hodnoty a metódy vracajú to, čo sa očakáva). Okrem toho môžeme tieto triedy rozširovať o nové funkcie, o ktorých nvda nemusí vedieť, pretože to sú metódy, ktoré sú určené na rôzne pomocné úlohy a volajú sa z metód, ktoré volá nvda.
Pozrime si teraz úplne jednoduchý plugin, ktorý pridá do sys.path cestu k priečinku python, na disku c.
---(mojpython.py)
import globalPluginHandler
import sys
class GlobalPlugin(globalPluginHandler.GlobalPlugin):
def __init__(self):
super(GlobalPlugin, self).__init__()
sys.path.append("c:\\python")
---
- na začiatku sme naimportovali modul, ktorý obsahuje triedu Global plugin
- pri deklarácii triedy sme za názvom triedy v zátvorkách uviedli názov triedy, ktorú chceme rozširovať. Tým pádom do našej triedy dedíme všetko, čo je vytvorené v triede Global plugin
- v konštruktore sme najprv zavolali konštruktor nadradenej triedy (tej od ktorej dedíme) aby sme zabezpečili, že sa vykoná všetko, čo nvda od konštruktora požaduje
- do konštruktora sme navyše pridali riadok, ktorý sme používali na konzole aby sme pridali do sys.path nový priečinok.
- za zmienku ešte stojí informácia, že v module sme museli importovať aj modul sys. To sme na konzole nemuseli, pretože konzola to pri spustení robí za nás. Vyskúšajte dir().
Pozrite si aj help k triede Global Plugin, aby ste mali základný prehľad.
Posledná informácia, ktorá vám ešte môže naozaj chýbať je fakt, že akékoľvek nezrovnalosti v kóde, nad ktorými na konzole interpreter hundral rovno do výstupného poľa konzoly sú v prípade nvda pluginov komentované v nvda logu (nástroje/zobraziť log).
Hra šnaps
Stiahnite si funkčnú implementáciu. Pravidlá hry snáď poznáte, tuhľa stručné zhrnutie:
- hrá sa so sedmovými kartami
- na začiatku každý dostane 16 kariet
- Hráč a počítač striedavo vyhadzujú karty a ich hodnoty sa sčítajú.
- sedmička má hodnotu -5 bodov, osmička 8, devina 9, desina 10, dolník 3, horník 2, kráľ 1 a eso -10 bodov.
- hrá sa do 60, súčet nikdy nemôže prekročiť 60 bodov.
- Teda napríklad: ak som na ťahu a kôpka má hodnotu 60, tak môžem použiť len sedmičku alebo eso, pretože tie karty znižujú.
- Hra končí, ak hráč ktorý je na ťahu nemá žiaddnu kartu, ktorú by mohol vyhodiť. Teda napríklad: som na ťahu, kôpka má hodnotu 58, a ja mám len dolníka a osmičku. Prehral som, pretože nemám čo vyhodiť.
Ovládanie hry
- ctrl+windows+f9, ctrl+windows+f11: pohyb kurzorom doľava a doprava. Skratky slúžia na pohyb po zozname kariet hráča. Teda týmito kombináciami si vyberáme kartu, ktorú chceme vyhodiť. Ak sa hra ešte nezačala, tak stlačenie niektorej z týchto kombinácií spustí rozdávanie kariet.
- ctrl+windows+f10: vyhodenie karty. Použite ak chcete vyhodiť kartu, ktorú ste vybrali pomyseľným kurzorom.
- ctrl+windows+f12: informácie o aktuálnom súčte a vrchnej karte.
Chyby
Hra obsahuje niekoľko úmyselne urobených a možno aj nejaké neúmyselné chyby. Tu je krátky zoznam, aj s poznámkami, skúste ich opraviť:
farby
Tú ste určite neprehliadli. Každá karta má všetky 4 farby. Nejaký nápad? Ak by náhodou nie, tak si vyskúšajte v konzole tento kúsok kódu:
---(he he he)
>>> zoznam1=[1,"ahoj"]
>>> zoznam2=zoznam1
>>> zoznam2[1]+=" jozo"
>>> zoznam1
[1, 'ahoj jozo']
---
To je ale zákerné čo?
Triedenie kariet
Možno ste si všimli, že triedenie nefunguje celkom tak ako by malo. Vyskúšajte si pozrieť vaše karty hneď po rozdaní. Opravte aj triedenie kariet počítača, je v ňom rovnaká chyba.
vylepšenia
Ak si trúfate, tak môžete skúsiť nasledovné:
- momentálne sa dá po spustení nvda zahrať len jedna hra, ak chcete hru reštartovať, musíte reštartovať NVDA, alebo nanovo načítať pluginy (nvda+ctrl+f3). Dorobte funkcionalitu, ktorá umožňí začať novú hru potom ako sa predchádzajúca skončí.
- V mojej implementácii vždy začína hráč. Skúste sem vniesť náhodu, alebo nech sa náhodne rozhoduje pri prvej hre a potom nech vždy začína ten, kto prehral.
- skúste doprogramovať počítanie výhier a prehier. Aby ste si mohli kedykoľvek pozrieť, koľko hier ste vyhrali a koľko prehrali. Stačí ak bude počítanie reštartnuté vždy po reštarte nvda.
- skúste vylepšiť hernú stratégiu počítača. V súčasnosti hrá dosť blbú hru, pretože vždy vyhadzuje najvyššiu kartu, ktorá sa práve vyhodiť dá. Možno by mohol napríklad hrať rozumnejšie, ak má kôpka hodnotu 55 a je na ťahu. Bolo by ale fajn, ak by nenazeral do kariet hráča. Ak si trúfate, tak mu môžete naprogramovať simuláciu pamäte človeka, v žiadnom prípade nech ale nenazerá do zoznamu kariet hráča. Hernú stratégiu by mohol napríklad zlepšiť aj tak, že by si na začiatku pozrel koľko má ies a podľa toho by si nechal vysoké karty, aby mohol "kryť" vaše esá, keď ich začnete vyhadzovať.
- ak dorobíte parádnu hernú stratégiu, tak začne byť príliš dobrý. Urobte mu ľucký faktor. Nech sa občas aj pomýli. Povedzme s pravdepodobnosťou 1:50