Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the 3rd lecture #4

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
357 changes: 357 additions & 0 deletions docs/03.md
Original file line number Diff line number Diff line change
@@ -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 )
<class 'NoneType'>
```

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 "<stdin>", line 1, in <module>
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 ) )
<class 'range'>
>>> 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.
Binary file added docs/img/L3_robot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ theme:

nav:
- Home: index.md
- 03.md

markdown_extensions:
- pymdownx.highlight:
Expand Down