diff --git a/docs/03.md b/docs/03.md new file mode 100644 index 0000000..623e537 --- /dev/null +++ b/docs/03.md @@ -0,0 +1,357 @@ +# Python: funkcie, range, vlastná hra + +## Funkcie + +Príkazy `print`, `int`, `input`, `len`, ..., ktoré sme používali doteraz, majú +niečo spoločné. Pozrime sa na ne ako na stroje. + +1. Do stroja niečo vhodíme, spustíme ho, +2. on niečo spraví, +3. a nakoniec niečo vyhodí zo seba von. + +Do príkazu `int` vhodíme hodnotu, napr. `'10'`, prevedie ju na číslo a vrátí +nám hodnotu `10`. Takýmto príkazom hovoríme *funkcie*. Funkcie majú *vstup* a +*výstup*, niečo berú ako *argument* na vstupe a vrátia hodnotu na výstupe +(*návratová hodnota*). Niektoré funkcie nemusia +mať argument, napr. `input()`, naopak `print( 'a', 4, 10 )` má viac argumentov +(jednotlivé argumenty oddeľujeme čiarkou), ale zase žiadnu návratovú hodnotu. + +!!! note "Poznámka" + To, že `print` nemá návratovú hodnotu nie je tak úplne pravda: + + ```python + >>> r = print( 'test' ) + test + >>> r + >>> print( r ) + None + >>> type( r ) + + ``` + + Návratová hodnota je `None`. Je to jediná možná hodnota dátového typu + `NoneType`. Len pre zaujímavosť, pre nás to teraz vôbec nie je + dôležité. + +Dobrá správa je: Python umožňuje vytvoriť si vlastné funkcie! Slúži na to +príkaz `def`, znovu na konci obsahuje dvojbodku a nasleduje blok kódu (*telo +funckie*) odsadený vpravo, ktorý sa vykoná, ak túto funkciu *zavoláme* (to +znamená, použijeme ju v programe). + +```python +def say_hi( who ): + print( 'Hi', who ) + +say_hi( 'robot' ) +say_hi( 'Adam' ) +``` + +Prvý riadok sa nazýva *hlavička funkcie*. Určuje názov funkcie a *parametry*. +Medzi parametrom a argumentom je veľmi slabý rozdiel a často sa ich význam +zamieňa. Ak chceme byť presný, `'robot'` a `'Adam'` sú argumenty. `who` je +parameter. Ako **a**uto zaparkuje na **p**arkovisko, tak aj **a**rgument +zaparkuje na **p**arameter, takže hodnota `'robot'` (argument) sa uloží do +premennej `who` (parameter). Ešte inak: ak sa pozeráme zvnútra funkcie, vidíme +parametre. Ak sa pozeráme zvonka, vidíme argumenty. + +???+ question "Úloha 1" + Napíšte funkciu `print_max`, ktorá vypíše maximálny prvok v zozname. Ak je + zoznam prázdny, vypíše túto informáciu. Napr: + + ```text + print_max( [ 3, 9, 0 ] ) vypíše: 9 + print_max( [] ) vypíše: Max element does not exist (the list is empty) + print_max( 'dkrn' ) vypíše: r + ``` + +???+ question "Úloha 2" + Pokúste sa v definícii funkcie "print\_max" použiť funkciu `max` (ktorá je už + vstavaná v Pythone). Krátka ukážka ako funguje: + + ```python + >>> max( [ 1, 2, 0 ] ) + 2 + >>> max( 'dkrn' ) + 'r' + >>> max( [] ) + Traceback (most recent call last): + File "", line 1, in + ValueError: max() arg is an empty sequence + ``` + + Takže pozor na to, aby argumentom nebol prázdny zoznam, toto bude treba + otestovať ešte pred tým, ako sa zavolá funkcia `max`. + +Čo ak by sme chceli aj my niečo vrátiť z našej vlastnej funkcie, nielen +vypisovať? Slúži na to slovíčko `return`: + +```python +def area( a, b ): + return a * b + +print( area( 2, 3 ) ) +print( area( 0, 10 ) + area( 10, 0 ) ) +print( area( 1, area( 2, 3 ) ) ) # well, this is "volume" +``` + +Prvý riadok (po definícii funkcie `area`) zavolá `area( 2, 3 )`. Do premennej +`a` sa uloží hodnota `2`, do `b` hodnota `3`. Potom sa vyhodnotí `a * b` na +hodnotu `12`, ktorá sa vráti spať a vykoná sa `print( 12 )`. Podobne si vieme +rozobrať aj posledný riadok: + +1. `#!py print( area( 1, area( 2, 3 ) ) )` +2. `#!py print( area( 1, 6 ) )` +3. `#!py print( 6 )` +4. Na obrazovke uvidíme text "6" + +???+ question "Úloha" + Napíšte funkciu, ktorá vráti počet kladných čísel v zozname. + + ```text + [] -> 0 + [ 2, 3 ] -> 2 + [ 0, 4, 9, -2, 10 ] -> 3 + [ -2, 0, -5 ] -> 0 + ``` + +## range + +Ukážeme si jednu novú, užitočnú funkciu, hlavne v spojení s `for ... in` +cyklom. Volá sa `range` a môže brať jeden, dva, alebo aj tri argumenty. Vráti +špeciálnu hodnotu typu 'range', ktorú ale vieme konvertovať do zoznamu pomocou +funkcie `list`: + +```python +>>> list( range( 10 ) ) +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +>>> list( range( 0 ) ) +[] +>>> list( range( 1 ) ) +[0] +>>> list( range( 0, 10 ) ) +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +>>> list( range( 0, 0 ) ) +[] +>>> list( range( 3, 5 ) ) +[3, 4] +>>> list( range( -3, 3 ) ) +[-3, -2, -1, 0, 1, 2] +>>> list( range( -3, 3, 2 ) ) +[-3, -1, 1] +>>> list( range( 0, 10, 1 ) ) +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +>>> list( range( 0, 10, 3 ) ) +[0, 3, 6, 9] +>>> list( range( 10, 0, -3 ) ) +[10, 7, 4, 1] +``` + +`range(a, b, c)` vygeneruje čísla od `a` (vrátane) do `b` (vynímajúc) s +rozdielmi `c` medzi každými dvomi číslami. Ak použijeme iba dva argumenty, `c` +sa nastaví automaticky na jednotku. A ak iba jeden, `a` sa nastaví na nulu. + +Ak ale použijeme túto funkciu s konštrukciou `for ... in`, môžme vynechať +konvertovanie cez `list`, lebo vie pracovať aj s špeciálnou hodnotou typu +'range', akú vracia funkcia `range`. + +```python +>>> type( range( 5 ) ) + +>>> for i in range( 5 ): +... print( i ) +... +0 +1 +2 +3 +4 +``` + +`for` spolu s `range` častokrát dokáže nahradiť cyklus `while` a lepšie sa s +ním pracuje. Ak si spomenieme napr. na náš program kresliaci trojuholníky, +teraz by sme samotné kreslenie (načítanie veľkosti na vstupe zostáva rovnako) +vedeli napísať nasledovne: + +```python +for row_size in range( size, 0, -1 ): + print( row_size * '*' ) +``` + +A nakresliť opačný trojuholník (so špičkou na vrchu) by bolo podobne jednoduché: + +```python +for row_size in range( 1, size + 1 ): + print( row_size * '*' ) +``` + +???+ question "Úloha" + Napíšte program, ktorý sa spýta užívateľa na dve čísla. Tie budú znamenať + veľkosť strán obdĺžnika. Potom pomocou `for ... in range` vykreslite takýto + obdĺžnik. Pre veľkosti 4 a 6 by mal vyzerať takto: + + ```text + ###### + # # + # # + ###### + ``` + +## Labyrint + +Poďme si spolu naprogramovať jednoduchú hru. Už máme nejaké skusenosti s +programovaním, takže pokým sa trochu obmedzíme v našich nárokoch na kvalitu, +zvládneme to ľavou zadnou. + +Cieľom hry bude dostať sa v labyrinte do cieľa. Labyrint budeme kresliť podobne +ako v poslednej úlohe, mriežky (#) budú označovať steny, medzery ( ) voľné +políčka. Cieľ označíme ako písmeno X a našu postavičku O. V každom kole vyzveme +používateľa aby zadal jedno z písmenok: + +* w - krok hore +* a - krok vľavo +* s - krok dole +* d - krok vpravo +* q - ukončiť hru + +Na začiatku si zadefinujeme všetky tieto *konštanty*, teda premenné, ktoré sa +nebudú meniť. V Pythone je zvykom písať ich názvy veľkými písmenami. + +```python linenums="1" +FIELD = [ +'#############', +'# # ### #X#', +'# # # #', +'# ## ## # # #', +'# # # #', +'##### # # #', +'# # #######', +'# # #', +'#############' ] + +HEIGHT = len( FIELD ) +WIDTH = len( FIELD[ 0 ] ) + +END = 'X' +PLAYER = 'O' +WALL = '#' + +UP = 'w' +LEFT = 'a' +DOWN = 's' +RIGHT = 'd' +QUIT = 'q' +``` + +Hracie pole si ukladáme ako zoznam textových reťazcov - riadkov. Výška poľa je +počet riadkov, ktorý vieme zistiť funkciou `len`. Tiež ju použijeme na zistenie +šírky, t.j. počet stĺpcov, ktorý sa rovná dĺžke každého (a teda napr. prvého) +riadku. Na začiatku hráča umiestnime do ľavého dolného rohu. Pamätajme, že +indexy začínajú nulou. X-ová súradnica je horizontálna (zľava doprava), y-ová +je vertikálna (zhora dolu). + +```python linenums="25" +INITIAL_X = 1 +INITIAL_Y = 7 +``` + +Teraz si napíšeme funkciu, ktorá vykreslí hracie pole aj s hráčom. Funkcia bude +mať dva parametre: aktuálne súradnice hráča. Budeme prechádzať pomocou +vnoreného `for` cyklu celé hracie pole po riadkoch, ak na danom políčku stojí +hráč, vykreslíme jeho znak, inak vykreslíme políčko z hracieho pola. Doteraz +sme `print` používali tak, že na konci výstupu vždy vložil znak konca riadku +(akoby stlačil na konci riadku ++enter++). To je jeho prednastavené správanie. +Teraz ale nechceme aby bolo každé políčko na svojom vlastnom riadku, chceme ich +vykresliť hneď vedľa seba. Preto použijeme špeciálny (*optional*) parameter +`end`, kde mu nastavíme znak, ktorý má vykresliť na konci výstupu. Skúste si +v shelli spustiť napr. `#!py print( 'afad', end='###' )`. My naozaj nechceme vypísať +medzi políčkami nič, preto nastavíme tento parameter na prázdny text. + +```python linenums="28" +def print_field( player_x, player_y ): + for y in range( HEIGHT ): + for x in range( WIDTH ): + if y == player_y and x == player_x: + print( PLAYER, end='' ) + else: + print( FIELD[ y ][ x ], end='' ) + print() +``` + +Napíšeme si ešte jednu dôležitú funkciu. Bude načítavať vstup od užívateľa a +posúvať hráča. Ako vstup zoberie tiež súradnice hráča a vráti 3 hodnoty: nové +súradnice po posune a logickú hodnotu, či sa má hra ukončiť. Viac ako jednu +hodnotu sme doteraz nevrátili v žiadnej funkcii, ale je to jednoduché: + +```python linenums="37" +def is_valid_command( command ): + return command in 'wasdq' + +def is_empty( x, y ): + return not FIELD[ y ][ x ] == WALL + +def move( player_x, player_y ): + new_player_x = player_x + new_player_y = player_y + + while True: + command = input( 'Move "w", "a", "s", "d" or end "q": ' ) + if not is_valid_command( command ): + print( 'Invalid command' ) + else: + if command == 'w': + new_player_y -= 1 + elif command == 'a': + new_player_x -= 1 + elif command == 's': + new_player_y += 1 + elif command == 'd': + new_player_x += 1 + else: # quit + return player_x, player_y, True + + if is_empty( new_player_x, new_player_y ): + return new_player_x, new_player_y, False + else: + print( 'You cannot move there' ) + new_player_x = player_x + new_player_y = player_y +``` + +Teraz implementujeme hlavnú logiku. Načítať viacero hodnôt z funkcie je rovnako +jednoduché. + +```python linenums="70" +x = INITIAL_X +y = INITIAL_Y + +end = False + +while not end: + print_field( x, y ) + x, y, end = move( x, y ) + if not end and FIELD[ y ][ x ] == END: + print( 'Congratulation!' ) + end = True + +print( 'The game ends' ) +``` + +Tak, a hra je hotová :) Nezabudnite, že príkaz `input` vždy čaká na stlačenie +klávesy ++enter++, takže sa hra nedá ovládať plynule ako sme zvyknutí, len +držaním "w" "a" "s" "d". To by sme mohli ešte vylepšiť, no najbližšie si už +postavíme svojho prvého robota a rozpohybujeme ho Pythonom! Zároveň +si postupne budeme ukazovať všeliaké nové užitočné príkazy. + +![Robot](img/L3_robot.png){ style="width:30%;height:auto" } + +???+ question "Úloha" + Ak ešte máte chuť trochu popracovať na našej hre, tu je niekoľko návrhov: + + 1. Vytvorte viacero rôznych bludísk, nechajte hráča, aby si z nich vybral. + 2. Pridajte počítadlo krokov. Čím viac krokov hráč spraví pokým sa dostane + do cieľa, tým nižšie bude jeho skóre, ktoré na konci hra vypíše. + 3. Do bludiska pridajte diamanty (napr. znak bodky "."), tie bude hráč zbierať, + takže po prejdení políčka s diamantom, diamant na ňom už nesmie zostať! + Pozbierané diamanty hráčovi zvyšujú skóre. + 4. Do hry môžete pridať dvere "D" a kľúč "K". Keď hráč zoberie kľúč, dvere sa + mu otvoria a bude môcť prejsť do cieľa. diff --git a/docs/img/L3_robot.png b/docs/img/L3_robot.png new file mode 100644 index 0000000..de80c21 Binary files /dev/null and b/docs/img/L3_robot.png differ diff --git a/mkdocs.yml b/mkdocs.yml index 21a5cdd..0f895d7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -6,6 +6,7 @@ theme: nav: - Home: index.md + - 03.md markdown_extensions: - pymdownx.highlight: