diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cdfeeb5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +venv/ +site/ +*.swp diff --git a/README.md b/README.md index d79ac7b..82a564f 100644 --- a/README.md +++ b/README.md @@ -1 +1,6 @@ # RLLR -- RoboLab Learning Resources + +1. Install the required packages: `pip install -r requirements.txt` +2. Preview by `mkdocs serve` +3. Open [http://127.0.0.1:8000/](http://127.0.0.1:8000/) + in Your favorite browser. diff --git a/docs/01.md b/docs/01.md new file mode 100644 index 0000000..70587b6 --- /dev/null +++ b/docs/01.md @@ -0,0 +1,623 @@ +# Úvod do Pythonu + +[Python](https://www.python.org/) je jeden z mnohých *programovacích jazykov*. +Tak ako ľudia medzi sebou sa rozprávame spoločným jazykom, potrebujeme sa +naučiť nový jazyk, ktorému rozumie počítač či robot. Tak mu budeme vedieť +povedať, kedy sa pohnúť, ako rýchlo, čo zobraziť na displeji, a podobne. + +Poďme na to! + +## Python ako kalkulačka + +Spustíme program [IDLE](https://docs.python.org/3/library/idle.html). Otvorí sa +okno podobné tomu na obrázku nižšie. + +![Python IDLE](img/L1_python_shell_win.png) + +Tento program nám bude slúžiť na písanie *zdrojového kódu* a jeho spustenie. +Zdrojový kód -- to sú príkazy, ktoré napíšeme počítaču. On si potom tento kód +prečíta riadok za riadkom a vykoná ich. Hovoríme tiež, že ich *interpretuje*. V +okne, ktoré sa nám otvorilo, môžeme zadávať jednotlivé príkazy. Na poslednom +riadku vidíme tzv. *prompt*: `>>>`. Označuje, že program je pripravený počúvať +na naše príkazy. Dokáže napr. fungovať ako kalkulačka, stačí hneď za prompt +napísať jednoduchý príklad a stlačiť ++enter++. + +```python +>>> 40 + 2 +42 +>>> 30 - 5 * 8 +-10 +``` + +Znak `*` znamená násobenie. Vidíme, že Python pozná základné matematické +pravidlá, najprv vypočítal násobenie, až potom odčítanie. Ak by sme ho predsa +len chceli presvedčiť, aby najprv odčítal, použijeme zátvorky. Rovnako ako v +matematike. S tým rozdielom, že musíme použiť okrúhle zátvorky `()`, tie +ostatné (`[]{}`) majú pre Python iný význam a len by sme ho poplietli. + +```python +>>> (30 - 5) * 8 +200 +``` + +Pozrime sa na delenie: + +```python +>>> 40 / 5 +8.0 +>>> 40 // 5 +8 +>>> 40 / 7 +5.714285714285714 +>>> 40 // 7 +5 +``` + +V Pythone sa rozlišujú dva typy delenia: + +1. `/` je klasické delenie ako ho poznáme, +2. `//` je *celočíselné delenie*. To znamená, že výsledkom je číslo, koľkokrát + najviac sa zmestí deliteľ do delenca celý. `7 * 5 = 35`, všetko je ešte v + poriadku, ale `7 * 6 = 42`, a to je už viac než 40, preto `40 // 7 = 5`. + +???+ note "Poznámka" + Presnejšie, `//` je delenie `/` nasledované zaokrúhlením nadol. + +Ukážeme si ešte dve matematické špeciality. + +### Modulo + +Alebo inak: zvyšok po celočíselnom delení. Je to tá časť, ktorá nám ešte chýba, +keď použijeme celočíselné delenie `//` namiesto `/`. Pre túto operáciu sa používa znak +`%`. + +```python +>>> 40 % 7 +5 +>>> 40 % 5 +0 +>>> 40 // 7 * 7 + 40 % 7 +40 +``` + +???+ question "Úloha" + Prečo posledný príklad vyšiel práve 40? Fungovalo by to aj s inými číslami? + Napr. s trojkou: + + ```python + >>> 3 // 7 * 7 + 3 % 7 + ``` + + Bude výsledok 3? Platí to pre všetky čísla? + +Na poslednom príklade sa vám možno nepozdáva, že sme nepoužli žiadne zátvorky. +Nemuseli sme, lebo násobenie, delenie aj modulo majú rovnakú *prioritu*. +Sčítanie a odčítanie majú od nich menšiu prioritu, takže sa vyhodnotia neskôr. +To už vieme, keď sme toto správanie zmenili použitím zátvoriek. No a pri +viacerých operáciach rovnakej priority za sebou, Python ich vyhodnocuje zľava +doprava. Znovu ako v matematike. + +Takže tento príklad sa interpretuje akoby bol uzátvorkovaný takto: + +```python +>>> ((40 // 7) * 7) + (40 % 7) +40 +``` + +### Mocnina + +Pripomeňme si, že mocnina je opakované násobenie. +23 = 2 * 2 * 2 = 8 + +V Pythone používame na umocňovanie operátor `**`: + +```python +>>> 2 ** 3 +8 +>>> 4 ** 1 +4 +>>> 1 ** 1000000000 +1 +>>> 5 ** 0 +1 +``` + +Umocňovanie má ešte väčšiu prioritu než násobenie. + +```python +>>> 4 * 2 ** 3 +32 +>>> (4 * 2) ** 3 +512 +``` + +???+ question "Úloha" + Aký je výsledok nasledujúcich matematických výrazov? Overte si správnu odpoveď + využitím Python IDLE. + + * `- 5 + 8` + * `- (5 + 8)` + * `2 % 4 - 1` + * `10 // 3 ** 2` + * `5 / -3` + * `5 // -3 * 3 + 5 % -3` + +### Premenné + +Pri zložitejších výpočtoch si často chceme uložiť medzivýsledok. Moderné +kalkulačky to zvládnu a Python IDLE tiež! + +```python +>>> x = 4 + 8 +>>> x +12 +>>> 2 * x +24 +``` + +Príkaz `x = 4 + 8` znamená priradenie hodnoty na pravej strane (`4 + 8`) do +*premennej* na ľavej strane (`x`). Najprv Python vypočíta `4 + 8`. Potom si na +miesto v pamäti, kde má uložený výsledok, nalepí poznámku "x". Je to názov +novej premennej, aby si pamätal kam sa má pozrieť, keď ju uvidí v našich +príkazoch nabudúce. Napr. `2 * x` -> `2 * 12` -> `24`. + +![premenná](img/L1_x.png){ style="width:50%;height:auto" } + +Názvy môžu byť aj viacpísmenkové, môžu obsahovať veľké aj malé znaky, tiež +cifry (ale cifra nemôže stáť na začiatku mena) a podtržítko ("\_"). + +Čo sa stane ak budeme chcieť ukázať hodnotu premennej, ktorú Python ešte +nepozná? + +```python +>>> y +Traceback (most recent call last): + File "", line 1, in +NameError: name 'y' is not defined +``` + +Všimnime si posledný riadok. Hovorí nám, že názov "y" nie je definovaný, inými +slovami, nepozná ho, nevie kam sa pozrieť. + +???+ question "Úloha" + Čo je výsledkom nasledujúcej postupnosti príkazov? + + ```python + >>> a = 3 + >>> b = 2 + >>> a = a + b + >>> b = a - b + >>> a = a - b + >>> a + ? + >>> b + ? + ``` + +Aby sme si skratili zápis, namiesto + +```python +>>> a = a + b +``` + +môžme používať + +```python +>>> a += b +``` + +Význam je ten istý. Rovnako existuje `-=`, `*=`, `/=`, `//=`, `%=`, `**=`. +Vyskúšajte si použiť niektoré z nich. + +## Tlačiť! + +V tejto časti si ukážeme nový príkaz. Volá sa "print". Z angličtiny vieme +odhadnúť, na čo bude slúžiť. Vypíše text na obrazovku. Zatiaľ sme pracovali len +s číslami, ešte nie z textom. V Pythone sa text píše medzi úvodzovky ("). + +!!! warning "Pozor!" + Ak napíšeme text bez úvodzoviek, interpretuje sa to ako názov, ktorý + bude Python hľadať a pravdepodobne dostaneme podobnú sťažnosť ako naposledy + (hovorí sa im *chybové hlášky*). + + ```python + >>> print( "Ahoj" ) + Ahoj + >>> print( Ahoj ) + Traceback (most recent call last): + File "", line 1, in + NameError: name 'Ahoj' is not defined + ``` + +Namiesto dvojitých úvodzoviek sa dajú použiť aj jednoduché ('). + +```python +>>> print( 'Cau' ) +Cau +``` + +Text síce nie je číslo, ale v Pythone dokážeme text sčítavať a dokonca aj +násobiť! + +```python +>>> print( 'Ahoj ' + 'Robo' + 'Lab' ) +Ahoj RoboLab +>>> print( 'tra' + 'la' * 3 ) +tralalala +>>> print( 3 + ' jablka' ) +Traceback (most recent call last): + File "", line 1, in +TypeError: unsupported operand type(s) for +: 'int' and 'str' +``` + +Sčítať text a číslo sa už nedá. To presne nám povie chybová hláška na poslednom +riadku: operátor `+` nefunguje s číslom (ang. **int**eger) a textom (ang. +character **str**ing). Ak potrebujeme vypísať v jednom príkaze text a zároveň +aj číslo, môžme ich oddeliť čiarkou. Python medzi ne automaticky vloží medzeru. + +```python +>>> pocet = 3 +>>> print( pocet ) +3 +>>> print( 'Mam', pocet, 'jablka' ) +Mam 3 jablka +``` + +Takže `print` funguje aj s číslami, nielen textom! Pozor, môže sa zdať, že +nasledujúce riadky spravia to isté, ale nie je to celkom tak. Za chvíľu si o +tom povieme viac. + +```python +>>> 3 +3 +>>> print( 3 ) +3 +``` + +## Náš prvý program + +Python IDLE obsahuje dve časti. Jednu z nich, *príkazový riadok* (tiež +*shell*), sme používali doteraz. Slúži na vykonávanie jednotlivých príkazov, +ale naše programy v budúcnosti budú omnoho dlhšie, pozostávajúc z niekoľko +desiatok, aj stoviek riadkov. Tu nám pomôže druhá časť: *editor*. Otvoríme ho +cez "File" -> "New". Napíšme doň náš prvý program: + +```python +print( 'Ahoj', 'RoboLab' ) +print( 'RoboLab zdravi' ) +``` + +Používame to, čo sme sa už naučili, ale tentokrát nepíšeme naše príkazy do +príkazového riadku a nevykonajú sa hneď. Keď chceme spustiť program, stlačíme +kláves ++f5++. IDLE nás vyzve aby sme si program uložili a potom ho spustí v okne +príkazového riadku. Mali by sme zbadať vypísané: + +```text +Ahoj RoboLab +RoboLab zdravi +``` + +Vráťme sa teraz k problému vypisovania čísel. Keď spustíme jednoduchý program, + +```python +print( 3 * 5 ) +``` + +na výstupe sa nám zobrazí `15`. Skúste si to! Správne, Python najprv vypočíta +"3 * 5 = 15" a následne výsledok príkazom `print` vypíše na obrazovku. Skúsme +teraz spustiť program bez príkazu `print`. + +```python +3 * 5 +``` + +Na výstupe sa nám nič nezobrazí! Prečo? Python znovu vyhodnotí "3 * 5 = 15", +ale tento **výsledok** sa stratí, nemá žiadny príkaz, aby ho vypísal. +Príkazový riadok nám vždy aj ukáže **výsledok** toho, čo mu zadáme. + +```python +>>> 3 * 5 +15 +``` + +Ale ak chceme niečo vypísať z **programu**, vždy musíme použiť príkaz `print`! + +!!! tip "Tip" + V ukážkach na týchto stránkach odlíšime príkazový riadok jednoducho: + začína tromi väčšítkami `>>>`. Ak tam nie sú, znamená to, že daná ukážka je + **program**, a teda ho treba napísať do editoru. + +## Prečo si to nezopakovať? + +Doteraz bol Python celkom nudný. Keď sme chceli, aby nám niečo napísal, museli +sme mu to sami napísať a ešte k tomu naviac dopísať "print". Na čo nám je taký +Python? Ukážme si teda, že je silnejší, než sa zdá. Spustite si tento program. +Predposledné dva riadky začínajú štyrmi medzerami a namiesto štyroch stlačení +medzerníka môžeme použiť kláves ++tab++. + +```python +i = 0 +while i < 3: + print( 'tra' + 'la' * i ) + i += 1 +print( 'koniec' ) +``` + +Zdá sa, že sme použili nový príkaz: `while`. Aby sme mu porozumeli, musíme si +najprv povedať niečo o ne/pravdách v Pythone. + +### Logické áno a nie + +Presuňme sa nachvíľu znovu do príkazového riadku. Pracovali sme už s textovými +a číselnými hodnotami, teraz si ukážeme logické hodnoty (ang. *boolean* podľa +matematika [Georgea Boolea](https://sk.wikipedia.org/wiki/George_Boole)). +Existujú len dve: `True` (pravda) a `False` (nepravda). V počítačoch je všetko +jasné, buď to je jednoducho pravdivé alebo nie je. + +```python +>>> 1 < 2 +True +>>> 3 < -5 +False +>>> 2 == 2 +True +>>> 2 >= 1 +True +>>> 2 >= 2 +True +``` + +Máme celkovo 6 *porovnávacích operátorov*: + +Operátor | Význam +:-------:|:------ +`==` | rovná sa +`!=` | nerovná sa +`<` | menšie než +`<=` | menšie, nanajvýš rovné +`>` | väčšie než +`>=` | väčšie, nanajvýš rovné + +!!! warning "Pozor!" + Ak chceme porovnávať, používame `==` (dva znaky "="), ale ak + priraďujeme hodnotu do premennej, používame len `=` (jeden znak "="). + +```python +>>> a = 1 +>>> a == 1 +True +``` + +Porovnávať nemusíme len čísla: + +```python +>>> 'text' != 'text' +False +>>> 2 == 'text' +False +>>> 2 == '2' +False +>>> 2 < 'text' +Traceback (most recent call last): + File "", line 1, in +TypeError: '<' not supported between instances of 'int' and 'str' +>>> 'text' < 'text' +False +>>> 'a' < 'b' +True +>>> 'a' < 'aa' +True +>>> 'ab' <= 'aa' +False +``` + +Textové reťazce sa porovnávajú *lexikograficky*. To znamená, ak nejaké slovo +nájdeme v abecednom zozname skôr než nejaké druhé slovo, tak to prvé bude +menšie. Číslo sa nikdy nebude rovnať žiadnemu textu a dokonca na menšie/vačšie +sa ani nemôžeme spýtať. + +```python +>>> False == False +True +>>> False == 0 +True +>>> True == 1 +True +>>> False < True +True +``` + +Logické hodnoty sa správajú ako čísla, pričom `False` je nula a `True` +jednotka. + +```python +>>> True + True +2 +>>> True * False +0 +``` + +- - - +V Pythone má každá premenná (a každá hodnota) nejaký typ (*dátový typ*). +Dokážeme ho zistiť pomocou príkazu `type`: + +```python +>>> type( 3 ) + +>>> type( 'text' ) + +>>> type( True ) + +>>> a = False +>>> type( a ) + +``` + +S presným významom slovíčka `class` si zatiaľ nebudeme lámať hlavu, stačí si +zapamätať, že každá hodnota má nejaký jednoznačný typ. +- - - + +### Príkaz `while` + +Teraz už budeme vedieť použiť príkaz `while` (nazýva sa tiež *cyklus*): + +```python +while "podmienka": + "príkaz 1" + "príkaz 2" + ... +"príkaz 3" +... +``` + +"while" znamená po anglicky "pokým". A tak mu Python aj rozumie. Pokým je +"podmienka" pravdivá, teda `True`, tak bude postupne vykonávať "príkaz 1", +"príkaz 2", ..., všetko, čo je odsadené o štyri medzery vpravo (týmto príkazom +hovoríme *telo cyklu*). Keď príde na koniec, znovu sa spýta, či je "podmienka" +pravdivá, ak áno, vykoná znovu telo cyklu. A tak stále dokola. Keď už +"podmienka" pravdivá nebude, Python skočí na "príkaz 3" a pokračuje ďalej. + +Vyskúšajme si spraviť nekonečný cyklus. Napíšeme takýto jednoduchý program: + +```python +while 1 == 1: + print( 'ahoj' ) +``` + +Spustíme, a keďže `1 == 1` sa vždy vyhodnotí na `True`, tak máme celú obrazovku +"ahoj"ov. Program môžme zastaviť klávesovou kombináciou Ctrl+C. Rovnako by sme +mohli napísať: + +```python +while True: + print( 'ahoj' ) +``` + +???+ question "Úloha" + Čo vypíše nasledujúci program? + + ```python + while False: + print( 'ahoj' ) + print( 'koniec' ) + ``` + + Najprv skúste na to prísť sami, odpoveď si potom overte spustením. + +Vieme už dosť na to, aby sme si napísali trochu zaujímavejší program. Budeme +"kresliť" trojuholníky z hviezdičiek (\*). Od teraz budeme písať zdrojový kód +po anglicky. Nebudú sa nám miešať slovenské a anglické slová (napr. +`#!py print( 'jablko' )`), a keď vytvoríme nejaký úžasný program, budeme sa môcť oň +podeliť s celým svetom! + +Ako chceme aby vyzerali trojuholníky z hviezdičiek? Najmenší trojuholník +veľkosti 1 je jednoduchý: + +```text +* +``` + +Veľkosť dva: + +```text +** +* +``` + +Veľkosť 4: + +```text +**** +*** +** +* +``` + +Už máme predstavu, čo chceme naprogramovať, takže pustime sa do toho. +Potrebujeme vedieť, aký veľký trojuholník chceme. + +```python +size = 3 +``` + +Vidíme, že trojuholník je nakreslený na toľko isto riadkov aká je jeho veľkosť. + +```python +size = 3 + +while size > 0: + # print one row + size -= 1 +``` + +Na miesto `# print one row` napíšeme kód, ktorý to vykoná, zatiaľ sme si to len +poznačili. Takáto časť v zdrojovom kóde sa nazýva *komentár*. Začína sa znakom +"#" a pokračuje až do konca riadku. Komentáre Python ignoruje, sú to poznámky +nás, programátorov. Ak by sme úvodný "#" nepoužili, Python by sa snažil +interpretovať daný riadok ako príkaz a skončil by s chybovou hláškou. + +Pozrime sa na to, ako sme použili premennú "size" v podmienke cyklu. Na +začiatku programu sa nastaví na hodnotu 3. Podmienka `size > 0` sa vyhodnotí na +`True` a telo cyklu sa vykoná. V ňom máme príkaz `size -= 1`. Takže teraz už +platí "size = 2". Znovu príde na podmienku cyklu, "2 > 0", a teda telo cyklu sa +vykoná, "size" sa zníži o jedna, na hodnotu 1. Podmienka je znovu vyhodnotená +ako `True`, "size" sa zníži na hodnotu 0. Teraz už podmienka splnená nie je a +telo cyklu sa nezopakuje. + +Celkovo sa teda cyklus zopakoval 3 krát. Presne to sme chceli! Zostáva nám v +každom opakovaní vykresliť jeden riadok. Na prvom riadku chceme mať 3 +hviezdičky. Na druhom 2 a na poslednom jednu. + +```python +size = 3 + +while size > 0: + print( '*' * size ) + size -= 1 +``` + +Hurá a je to! Teraz stačí zmeniť v prvom riadku "size" na veľkosť akú chceme a +Python nám vykreslí žiadaný trojuholník. Vyskúšajte si rôzne veľkosti. + +???+ question "Úloha 1" + Napíšte program podobný tomu poslednému, ktorý bude kresliť štvorce. + +???+ question "Úloha 2" + Napíšte program kresliaci trojuholníky, ale naopak ako doteraz. Teda, pre + veľkosť 4 bude vyzerať: + + ```text + * + ** + *** + **** + ``` + + Toto je už trochu ťažšia úloha, vyžaduje použiť až dve premenné. "size" sa + tentokrát meniť nebude, ale druhá premenná bude mať postupne hodnoty od 1 až po + "size", takže budete vedieť jednoducho vykresliť hviezdičky na každom riadku + podľa nej. Premyslite si to dobre. + +???+ question "Úloha 3" + Napíšte program, ktorý vykreslí obdĺžnik. Na začiatku budú teda dve premenné: + jedna pre veľkosť každej strany. + +???+ question "Úloha 4" + Pozor, toto je veľmi tvrdý oriešok! Napíšte program, tentokrát kresliaci + pyramídu. Tu sú vzory pre veľkosti 1, 2 a 4: + + ```text + * + ``` + + ```text + * + *** + ``` + + ```text + * + *** + ***** + ******* + ``` diff --git a/docs/02.md b/docs/02.md new file mode 100644 index 0000000..9e18a17 --- /dev/null +++ b/docs/02.md @@ -0,0 +1,724 @@ +# Python: vstup, riadiace štruktúry, zoznam + +Dnes si ukážeme ako v programe aj čítať, nielen vypisovať na obrazovku, ukážeme +si viac príkazov podobných `while`u a nový, zložitejší, dátový typ. + +## Načítanie vstupu + +Pripomeňme si náš program na kreslenie trojuholníkov: + +```python +size = 3 + +while size > 0: + print( '*' * size ) + size -= 1 +``` + +Na to, aby sme zmenili veľkosť, vždy znovu a znovu musíme prepísať náš zdrojový +kód. Bolo by pekné, keď by sme mali len jeden program, ten spustíme a spýta sa +nás na veľkosť, akú má použiť. Tomu sa hovorí *načítanie vstupu* a slúži na to +príkaz `input`. Poďme si ho vyskúšať v príkazovom riadku: + +```python +>>> input() +hello +'hello' +>>> input( 'Enter Your name: ' ) +Enter Your name: robot +'robot' +``` + +Po zadaní príkazu `input()`, shell čaká, pokým zadáme nejaký text a stlačíme +++enter++. Potom vypíše výsledok (rovnako ako keď sme využívali Python na rátanie +príkladov) -- výsledkom príkazu `input()` je v tomto prípade text 'hello'. Keď +medzi zátvorky vpíšeme text, použije tento text ako prompt. Vypíše ho a znovu +čaká na nás, aby sme mu niečo napísali. Výsledok `input()`u si môžme uložiť do +premennej: + +```python +>>> year = input( 'Favorite year? ' ) +Favorite year? 1965 +>>> year +'1965' +``` + +### Vylepšenie kresliča + +Načítanie veľkosti cez `input()` je jednoduché... + +```python +size = input( 'Enter size: ' ) + +while size > 0: + print( '*' * size ) + size -= 1 +``` + +...alebo nie? Dokážete zistiť z chybovej hlášky, čo sa stalo? Pri porovnávaní +`size > 0` sa Pythonu nepáči, že porovnávame 'str' a 'int', teda text a číslo. +Takže premenná "size" má typ 'str'. Pozrime sa na to v shelli: + +```python +>>> a = input() +1 +>>> type( a ) + +``` + +Naozaj, výsledkom `input()`u je text. My potrebujeme tento text konvertovať na +číslo, na pomoc nám príde ďalší príkaz -- `int()`: + +```python +>>> int( '32' ) +32 +>>> int( '-1' ) +-1 +>>> int( 42 ) +42 +>>> int( '4.2' ) +Traceback (most recent call last): + File "", line 1, in +ValueError: invalid literal for int() with base 10: '4.2' +>>> int( 'd4' ) +Traceback (most recent call last): + File "", line 1, in +ValueError: invalid literal for int() with base 10: 'd4' +>>> int( 4.2 ) +4 +>>> int( False ) +0 +``` + +Tento príkaz sa snaží hociakú hodnotu previesť na celé číslo. Keď sa to nedá, +skončí s chybou. Desatinné číslo 4.2 prevedie tak, že desatinnú časť jednoducho +zahodí. Ale text `4.2` nevie konvertovať. Na to by sme najprv museli previesť +text do desatinného čísla a potom do celého: + +```python +>>> int( float( '4.2' ) ) +4 +``` + +???+ question "Úloha" + Pre každý datový typ, ktorý poznáme, existuje takáto konvertovacia funkcia: + `int`, `float`, `str`, `bool`. Vyskúšajte si napr. konvertovať číslo na 'bool' + alebo previesť číslo na text a potom zase späť na číslo. + +Náš opravený program teda vyzerá: + +```python +size = int( input( 'Enter size: ' ) ) + +while size > 0: + print( '*' * size ) + size -= 1 +``` + +Výsledok z `input()`, čo je text, vhodíme do príkazu `int()`, a dostaneme +výsledok ako celé číslo, ktoré už môžme použiť na porovnanie, odčítanie aj +násobenie. Problém je, ak zadáme text, ktorý sa nedá konvertovať na číslo. +Tento problém sa dá vyriešiť, my to zatiaľ nevieme, ale nie je to také dôležité. +Ak chce niekto používať náš program, tak má vedieť, ako písať čísla! + +???+ question "Úloha 1" + Napíšte program, ktorý sa spýta používateľa na meno a následne ho pozdraví. Beh + programu potom môže vyzerať napríklad takto: + + ```text + What's Your name? Kubko + Hello Kubko! + ``` + +???+ question "Úloha 2" + Vytvorte program, ktorý si vyžiada rok narodenia a vypíše, koľkaté narodeniny + má daný človek v súčasnom roku. + +## Podmienený príkaz + +Jeden problém s našim kresličom trojuholníkov ale predsa len vyriešime. Keď si +teraz niekto pustí náš program a zadá veľkosť "-3", program nič nevypíše, len +ticho skončí (overte si to). Bolo by pekné používateľa informovať o tom, že +musí zadať nezápornú veľkosť. Potrebujeme otestovať, či platí `size < 0`, na to +využijeme podmienený príkaz `if`. Pozrime sa na nasledujúci program (spustite +si ho): + +```python +if 1 < 2: + print( '1 < 2' ) + +if 2 < 1: + print( 'Python is wrong' ) +else: + print( 'Python is right' ) +``` + +Anglické slovíčko "if" znamená "ak" a "else" znamená "inak". Takže, keď si +prečítame zdrojový kód, hneď vieme, ako tento nový príkaz (presnejšie, +*riadiaca štruktúra*) funguje. Ak 1 je menej než 2, napíš "1 < 2". Ak 2 < 1, +napíš "Python is wrong", inak napíš "Python is right". Všimnime si, že na konci +riadku s `if` alebo `else` je vždy dvojbodka. Rovnako sme ju tam písali, keď +sme používali `while`. Označuje, že niekoľko ďalších riadkov bude odsadených o +štyri medzeri vpravo a majú sa brať ako jeden *blok* kódu. Všimnime si rozdiel +v týchto dvoch programoch: + +```python +if False: + print( 'A' ) +print( 'B' ) +``` + +```python +if False: + print( 'A' ) + print( 'B' ) +``` + +Keď ich spustíme, každý vykoná niečo iné. Python je veľmi háklivý na medzery na +začiatku riadku. V podstate je jedno, koľko ich tam bude, ale vždy, ak má byť +nejaký blok kódu odsadený vpravo (teda vždy za dvojbodkou), nejaká medzera tam +musí byť a treba tento počet dodržiavať. Obydva nasledujúce programy skončia s +chybou (skúste si ich spustiť): + +```python +if False: +print( 'A' ) +``` + +```python +if False: + print( 'A' ) + print( 'A' ) +``` + +Prvý z nich nemá blok odsadený vôbec, druhý nepoužíva počet medzier +konzistentne. Je bežné odsadzovať bloky vždy o 4 medzery, a tak to budeme robiť +aj my. + +### elif + +K príkazom `if`, `else`, ešte existuje tretí kamarát: `elif`. Znamená niečo ako +"inak, ak ...". Ukážeme si jeho použitie, z ktorého to bude najlepšie vidieť. + +```python +grade = input() + +if grade == 'A': + print( 'Excellent!' ) +elif grade == 'B': + print( 'Good' ) +elif grade == 'C': + print( 'Not bad' ) +elif grade == 'D': + print( 'You could do better' ) +else: + print( 'I know only four grades: A, B, C, D' ) +``` + +Python najprv otestuje, či používateľ zadal písmeno "A", ak nie, otestuje "B", +atď., ak nie ani "D", vykoná posledný blok kódu. + +???+ question "Úloha" + Skúste ešte vylepšiť kresliča tak, že ak užívateľ zadá záporné číslo, napíše + mu, že musí zadať kladné (alebo nulu). Ak zadá väčšie číslo než 10, vypíše + správu otom, že je to príliš veľa a mal by požiadať o menšiu veľkosť. Ak ani + jeden z týchto scenárov sa nestane, tak potom program vykreslí trojuholník. + + Použite na to príkazy `if`, `elif` a `else`. Malá rada: odsadený môže (dokonca + musí) byť aj už odsadený kód. Napríklad nasledujúci zdrojový kód je validný: + + ```python + if 1 == 1: + if 2 == 2: + print( '1 == 1 and 2 == 2' ) + ``` + +### Logické operátory + +Už poznáme logické hodnoty: `False` a `True`. Vieme, že výsledkom porovnávacích +operátorov sú práve tieto hodnoty. Ale čo ak potrebujeme otestovať dve +podmienky naraz? Napr. "Ak vonku prší a je pondelok, nepôjdem do školy". Môžeme +taký program napísať pomocou dvoch príkazov `if`: + +```python +if wheather == 'raining': + if weekday == 'monday': + go_to_school = False +``` + +Teraz si ukážeme 3 logické operátory, ktoré nám pomôžu napísať aj zložitejšie +podmienky v jednom `if`e. + +#### A zároveň + +```python +if a and b: + ... +``` + +a | b | Výsledok +:----:|:-----:|:--------: +True | True | True +True | False | False +False | True | False +False | False | False + +#### Alebo + +```python +if a or b: + ... +``` + +a | b | Výsledok +:----:|:-----:|:--------: +True | True | True +True | False | True +False | True | True +False | False | False + +#### Zápor + +```python +if not a: + ... +``` + +a | Výsledok +:----:|:--------: +True | False +False | True + +#### Použitie + +Niekoľko prípadov použitia si ukážeme v shelli: + +```python +>>> ( 1 < 2 ) and ( 3 < 4 ) +True +>>> 'A' == 'B' or 1 != 2 +True +>>> 1 == 2 or 2 == 1 +False +>>> not 4 > 5 +True +>>> False or not 1 >= 1 +False +``` + +Zátvorky použiť môžeme, ale keďže porovnávacie operátory majú vyššiu prioritu +ako logické, vyhodnotia sa ešte pred nimi, a teda to nie je nutné. + +### Neodbytnejší kreslič + +Upravíme teraz kresliča trojuholníkov tak, aby v prípade nevalidnej veľkosti +neskončil hneď, ale bol neodbytný a pýtal sa na veľkosť, pokým nejakú nedostane +(alebo nebude nakoniec v zúrivosti vypnutý pomocou ++ctrl+c++). + +```python linenums="1" +done = False + +while not done: + size = int( input( 'Enter size: ' ) ) + + if size < 0 or size > 10: + print( 'Error!' ) + + if size < 0: + print( 'We need a non-negative size' ) + elif size > 10: + print( 'This is too much. We accept at most size of 10.' ) + else: + done = True + +while size > 0: + print( '*' * size ) + size -= 1 +``` + +Prejdime si zdrojový kód riadok za riadkom: + +```python linenums="1" +done = False +``` + +V tejto premennej si budeme uchovávať informáciu o tom, či sa nám už podarilo +načítať validný vstup (veľkosť). Na začiatku programu rozhodne ešte žiaden +vstup od užívateľa nemáme, preto nastavíme `done` na `False`. + +```python linenums="3" +while not done: + size = int( input( 'Enter size: ' ) ) +``` + +Toto je hlavný cyklus načítavania veľkosti. Pokým nebudeme mať poznačené v +`done`, že už sme načítali dobrý vstup, cyklus sa bude opakovať. Spôsob +načítania už poznáme. + +```python linenums="6" + if size < 0 or size > 10: + print( 'Error!' ) + + if size < 0: + print( 'We need a non-negative size' ) + elif size > 10: + print( 'This is too much. We accept at most size of 10.' ) +``` + +Otestujeme, či je veľkosť validná. Ak nie, napíšeme používateľovi, že niečo je +zle a potom aj ČO je zle. + +```python linenums="13" + else: + done = True +``` + +Ak je všetko OK, poznačíme si, že sa nám podarilo načítať veľkosť, a tak pri +ďalšom testovaní podmienky cyklu, výraz `not done` sa vyhodnotí na False a +program bude pokračovať za `while` cyklom. + +```python linenums="16" +while size > 0: + print( '*' * size ) + size -= 1 +``` + +Tu sa nič nezmenilo, tak ako aj doteraz, vykreslíme trojuholník. + +???+ question "Úloha" + Vytvorte program, ktorý nechá používateľa hádať, aké číslo si myslí počítač. + Vyzerať by to mohlo podobne ako: + + ```text + Let's play a game: Guess the number I've chosen (1..100 inclusive) + Your guess: 12 + Try a greater number + Your guess: 50 + Try a lower number + Your guess: 45 + Try a lower number + Your guess: 42 + Congratulation! + ``` + + Poznámka: Vytvoriť v programe náhodné číslo ešte nevieme, zatiaľ kľudne môže + byť toto číslo priamo napísané v zdrojovom kóde a uložené v premennej na + začiatku kódu. + + ```python + number = 42 + + # Main program logic follows... + ``` + +## Zoznam + +Zopakujme si, aké dátové typy poznáme: + +* celé čísla - 'int' +* desatinné čísla - 'float' +* textový reťazec - 'str' +* logická hodnota - 'bool' + +Teraz si do tohto zoznamu pridáme ďalší: + +* zoznam - 'list' + +Ako aj názov napovedá, do zoznamu si budeme ukladať viacero hodnôt. Je to +vlastne postupnosť čísel, textu, logických hodnôt, ..., dokonca aj samotných +zoznamov. Aby sme vytvorili zoznam hodnôt, vpíšeme ich medzi hranaté zátvorky. + +```python +>>> [ 1, 2, 3, 4, 5 ] +[1, 2, 3, 4, 5] +>>> [ 1, False, 3, 'ABCD', 5, [ 4.2, [] ] ] +[1, False, 3, 'ABCD', 5, [4.2, []]] +>>> type( [ 1, False, 'ABC' ] ) + +``` + +Hodnoty uložené v zozname nazývame *prvky* a pristupujeme k nim cez *index*. +Index je poradie prvku od začiatku zoznamu, pričom prvý prvok má index 0. Na +indexovanie používame hranaté zátvorky. + +```python +>>> l = [ 1, False, 3, 'ABCD', 5, [ 4.2, [] ] ] +>>> l[0] +1 +>>> l[1] +False +>>> l[5] +[4.2, []] +>>> l[5][1] +[] +>>> l[-1] +[4.2, []] +>>> l[-2] +5 +>>> l[6] +Traceback (most recent call last): + File "", line 1, in +IndexError: list index out of range +``` + +Indexy môžu byť aj záporné, vtedy označuje poradie odzadu. No pozor na príliš +vysoké indexy. Ak chceme pristúpiť k prvku za koncom zoznamu, dostaneme chybovú +hlášku. + +Prvky v zozname môžeme aj upravovať: + +```python +>>> l[1] = 2 +>>> l +[1, 2, 3, 'ABCD', 5, [4.2, []]] +``` + +A prípadne celý zoznam vieme predĺžiť. Existuje viacero spôsobov: + +```python +>>> l = [ 1, 2 ] +>>> l.append( 3 ) +>>> l +[1, 2, 3] +>>> l.extend( [ 4, 5, 5 ] ) +>>> l +[1, 2, 3, 4, 5, 5] +>>> l + [ 0 ] +[1, 2, 3, 4, 5, 5, 0] +>>> l +[1, 2, 3, 4, 5, 5] +>>> l += [ 0 ] +>>> l +[1, 2, 3, 4, 5, 5, 0] +``` + +Operátor `+` vytvorí **nový** zoznam z pôvodných dvoch -- tieto dva zostanú +nezmenené. Ak chceme zoznam predĺžiť, a teda naozaj ho zmeniť, musíme +na to použiť operátor `+=`. +A vieme ho zase skrátiť: + +```python +>>> l.pop() +0 +>>> l +[1, 2, 3, 4, 5, 5] +>>> a = l.pop() +>>> a +5 +>>> l +[1, 2, 3, 4, 5] +>>> del l[1] +>>> l +[1, 3, 4, 5] +``` + +`pop()` zmaže posledný prvok, ktorý si zároveň môžeme niekam uložiť. `del` +vymaže ľubovoľný prvok na danom indexe. + +Ukážme si, čo ešte vieme so zoznamom robiť: + +```python +>>> l.reverse() # obráti zoznam +>>> l +[5, 4, 3, 1] +>>> l.clear() # vymaže všetky prvky +>>> l +[] +>>> l = [ 1 ] +>>> l.insert( 0, 8 ) # vloží prvok 8 na index 0 +>>> l +[8, 1] +>>> l.insert( 1, 9 ) +>>> l +[8, 9, 1] +>>> l.remove( 8 ) # odstráni prvý výskit prvku 8 +>>> l +[9, 1] +>>> l.remove( 0 ) # ak daný prvok neexistuje, chyba +Traceback (most recent call last): + File "", line 1, in +ValueError: list.remove(x): x not in list +>>> l = [ 1, 4, 4, 9, 2, 0, 3, 4 ] +>>> l.count( 1 ) # spočíta počet výskitov prvku +1 +>>> l.count( 4 ) +3 +>>> l.count( 42 ) +0 +>>> l.sort() # zoradí zoznam +>>> l +[0, 1, 2, 3, 4, 4, 4, 9] +>>> l.index( 4 ) # vráti index prvého výskitu prvku +4 +>>> l.index( 5 ) # chyba, podobne ako pri 'remove' +Traceback (most recent call last): + File "", line 1, in +ValueError: 5 is not in list +>>> len( l ) # zistí dĺžku (počet prvkov) zoznamu +8 +>>> len( [ 'robo', 'lab' ] ) +2 +>>> len( [] ) +0 +>>> 3 in [ 2, 3, 1 ] # test na prítomnosť prvku v zozname +True +>>> 1 in [ 4, 5 ] +False +>>> 'l' in 'hello' +True +>>> 0 in [] +False +``` + +### Priemer čísel + +Aby sme si ukázali príklad využitia zoznamu, vytvoríme program, ktorý vypočíta +priemer čísel zadaných používateľom. Tie si budeme ukladať do zoznamu. Ako +budeme ale vedieť, kedy užívateľ už napísal všetky čísla? Dáme mu možnosť to +oznámiť napr. tým, že zadá text "end". + +```python linenums="1" +print( 'I\'ll compute the mean value of all the entered numbers.' ) +print( 'Type "end" after entering all of them.' ) + +done = False +numbers = [] + +while not done: + number = input( 'Enter a number (or "end"): ' ) + + if number == 'end': + done = True + else: + numbers.append( int( number ) ) + +print( 'Entered numbers:', numbers ) +``` + +Na začiatku užívateľa informujeme o tom ako používať náš program. Všimnime si, +že na to, aby sme vypísali jednoduchú úvodzovku, musíme pred ňu napísať +špeciálny znak spätné lomítko (\\). Je to preto, lebo úvodzovkami sa označuje +začiatok a koniec textového reťazca. Ak by sme nepoužili spätné lomítko, Python +by ako text pochopil iba "I" a dostali by sme chybu. + +Vstup načítame najprv ako text, aby sme mohli otestovať koniec zadávania čísel, +a keď ten nenastane, konvertujeme ho na celé číslo a pridáme do zoznamu. Na +konci vypíšeme všetky čísla z ktorých budeme rátať priemer. + +```python linenums="1" +i = 0 +summ = 0 + +while i < len( numbers ): + summ += numbers[i] + i += 1 + +print( 'Mean:', summ / len( numbers ) ) +``` + +Aby sme vypočítali priemer, najprv pomocou cyklu sčítame všetky čísla. Premenná +`i` má na začiatku hodnotu nula, teda index prvého prvu. Postupne sa zvyšuje o +jedna, takže sa posúvame na ďalšie prvky. Musíme si dať pozor, aby sme +zastavili cyklus dostatočne skoro a zároveň nezabudli na žiaden prvok. Index +prvého prvku je nula, index druhého jedna, atď. Takže ak dĺžka zoznamu je 5, +posledný index je 4. Posledný index je vždy o jedna menší než dĺžka zoznamu. +Preto sme použili v podmienke porovnanie `i < len( numbers )`. Keď po poslednom +prvku zvýšime `i` o jedna, už sa bude rovnať dĺžke zoznamu, a cyklus skončí. + +???+ question "Úloha" + Vytvorte podobný program s nasledujúcimi dvomi rozdielmi: + + 1. Namiesto priemeru, vypočítajte + [medián](https://sk.wikipedia.org/wiki/Medi%C3%A1n): + > "Na nájdenie mediánu daného súboru stačí hodnoty usporiadať podľa + > veľkosti a zobrať hodnotu, ktorá sa nachádza v strede zoznamu. Ak má + > súbor párny počet prvkov, zvyčajne sa za medián označí aritmetický + > priemer hodnôt na mieste n/2 a (n+2)/2, ktoré sa nachádzajú v oblasti + > prostrednej hodnoty." + 2. Užívateľ najprv zadá koľko čísel bude nasledovať, potom na každom riadku + napíše jedno číslo. Takže už nemusí ukončiť čísla textom "end". Príklad + toho, čo môže zadať používateľ: + + ```text + 4 + 25 + 7 + 1 + 13 + ``` + + Medián týchto štyroch čísel je (7 + 13) / 2 = 10. + +### for ... in ... + +Na *iterovanie* (prechádzanie prvkov) cez zoznam sme použili `while` cyklus. +Nie je to jediná možnosť a v tejto časti si ukážeme vhodnejšiu riadiacu +štruktúru. + +```python +>>> for n in [ 1, 5, 3 ]: +... print( 'n =', n ) +... +n = 1 +n = 5 +n = 3 +``` + +Na konci príkazu `for in` je dvojbodka, takže telo cyklu znovu musí byť +odsadené doprava. Takéto krátke programy vieme písať aj priamo v shelli, `...` +na začiatku znamenajú, že náš program/príkaz pokračuje. Za poslednými `...` +stlačíme hneď Enter, tým dáme najavo, že sme náš program dopísali a shell ho +spustí (interpretuje). + +Medzi slovíčkami `for` a `in` píšeme názov premennej. Do tejto premennej budú +postupne ukladané všetky hodnoty zo zoznamu uvedenom po `in`. + +Tento nový typ cyklu využijeme v programe rátajúcom priemer čísel. Namiesto +`while` cyklu a pomocnej premennej `i`, napíšeme len: + +```python +summ = 0 + +for number in numbers: + summ += number +``` + +- - - +Program vieme dokonca zjednodušiť ešte viac. Python pozná príkaz `sum()`, +ktorým sčíta všetky hodnoty v zozname: + +```python +summ = sum( numbers ) +``` + +Mimochodom, to je dôvod, prečo sme na názov premennej nepoužili "sum" ale +"summ". Podobne by sme nemali používať názvy premenných ako "for", "in", +"else", atď. Väčšinou platí, že ak nám editor zafarbí slovo na nejakú farbu, +tak je to slovo vyhradené pre jazyk Python a ak nechceme mať problémy, nebudeme +ho používať na názov premennej. + +!!! note "Poznámka" + Aj na textový reťazec sa môžme pozerať ako na zoznam, a to zoznam znakov. + + ```python + >>> for c in 'word': + ... print( 2 * c ) + ... + ww + oo + rr + dd + ``` + +???+ question "Úloha" + Napíšte program, ktorý sa spýta užívateľa na jedno, ľubovoľné slovo. Potom + vypíše jeho písmena spolu s poradím. Napr.: + + ```text + Enter one word: apple + 1. letter: a + 2. letter: p + 3. letter: p + 4. letter: l + 5. letter: e + ``` 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/04.md b/docs/04.md new file mode 100644 index 0000000..343c2f2 --- /dev/null +++ b/docs/04.md @@ -0,0 +1,260 @@ +# Prvý robot + +Dnes si postavíme a naprogramujeme našeho prvého LEGO robota! Bude mať dva +motory aby sa vedel pohybovať a *EV3-kocku* (*EV3 brick*), ktorá je takým mozgom +celého robota, bude riadiť motory, ktoré pripojíme do nej káblami. Káble by sme +mohli prirovnať k nervom a žilám v človeku. EV3-kocka je zároveň aj srdcom -- +v zadnej časti obsahuje batérie, bez ktorých by mozog ani nohy (motory) +nemohli fungovať. Návod ako robota postaviť nájdete [tu](https://education.lego.com/v3/assets/blt293eea581807678a/bltc8481dd2666822ff/5f8801e3f4f4cf0fa39d2fef/ev3-rem-driving-base.pdf). + +![robot](img/L4_robot.png) + +## Príprava EV3-kocky + +Programy budeme stále písať na počítači a potom ich cez USB kábel nahráme do +kocky. Aby EV3-kocka vedela spustiť Python, musíme jej trochu pomôcť. Na micro +SD kartu nahráme špeciálny operačný systém [ev3dev](https://www.ev3dev.org/). +Keď bude SD karta v EV3-kocke a zapneme ju, spustí sa `ev3dev`. Ak sa chceme +dostať do pôvodného režimu, jednoducho vypneme kocku, vytiahneme SD kartu +a znovu spustíme robota. + +!!! tip "Tip" + Návod na prípravu SD karty nájdete na + [stránkach ev3dev](https://www.ev3dev.org/docs/getting-started/), + kroky 1, 2 a 4. + +## Príprava počítaču + +Keďže EV3-kocka je omnoho jednoduchšie zariadenie než bežný počítač, nebudeme +na nej spúšťať skutočný Python, ale jeho jednoduchšiu verziu: +[MicroPython](https://micropython.org/) špeciálne upravený pre LEGO, nazýva sa +[Pybricks](https://pybricks.com/). Tak budeme môcť z Pythonu spúšťať LEGO +motory, vykreslovať obrázky na displeji, či načítavať dáta z rôznych senzorov. +Aby sme tieto programy vedeli spustiť na EV3-kocke, potrebujeme vykonať ešte +niekoľko vecí: + +1. Nainštalovať si [Visual Studio Code](https://code.visualstudio.com/Download) +2. Spustiť ho +3. Otvoriť *extension tab* + ![extension tab](img/L4_extensions.png) +4. Vyhľadať rozšírenie *EV3 MicroPython* a nainštalovať ho + ![extension install](img/L4_extension_install.png) + +Tak a máme všetko pripravené :) + +## Vpred! + +Teraz si napíšeme prvý jednoduchý program, ktorý pohne robota o kúsok vpred. + +1. Vo *Visual Studio Code* si vytvoríme nový projekt. + ![nový projekt](img/L4_new_project.png) +2. Zadáme názov, napr. "2motors", a stlačíme ++enter++. +3. Otvorí sa nám prehliadač súborov. Tu zvolíme priečinok, kde chceme náš + projekt mať uložený. +4. Dostaneme bezpečnostné upozornenie, či veríme autorom súborov vo zvolenom + priečinku. Za predpokladu, že všetky budeme písať my, potvrdíme, že áno +5. Vo zvolenom priečinku sa nám vytvoril nový projektový priečinok s názvom + aký sme zvolili v kroku 2 ("2motors"). Ten obsahuje súbor `main.py` s kódom, ktorý sa + bude na robotovi spúšťať. + ![main_py](img/L4_open_main_py.png) +6. Vymažme počiatočný vygenerovaný kód v `main.py` + a skopírujme doň tento: + ```py linenums="1" + #!/usr/bin/env pybricks-micropython + + from pybricks.ev3devices import Motor + from pybricks.parameters import Port + from pybricks.tools import wait + + + left_motor = Motor( Port.B ) + right_motor = Motor( Port.C ) + + left_motor.run( 360 ) + right_motor.run( 360 ) + + wait( 2000 ) + + left_motor.stop() + right_motor.stop() + ``` + Vyzerá trochu inak ako tie, čo sme písali doteraz. Zatiaľ sa tým netrápme, + onedlho si všetko rýchlo vysvetlíme. +7. Teraz zapneme EV3-kocku (s SD kartou s nahratým `ev3dev`), prípojíme ju USB + káblom k počítaču a pripojíme sa ku nej z *Visual Studio Code*. + ![connect](img/L4_connect.png) +8. Zvolíme našu EV3-kocku. + ![connect2](img/L4_connect_my.png) +9. Vľavo dole by sme mali vidieť najprv žltý krúžok (EV3-kocka sa pripája), + potom zelený, indikujúci úspešné pripojenie. Ak je červený, spojenie zlyhalo. + Klik pravým -> "Reconnect" pre nový pokus. + ![status](img/L4_status.png) + Ak sa pripojiť nedarí a používate Ubuntu, môže pomôcť nastaviť + "IPv4 Method" na "Shared to other computers" vrámci nastavení siete + "USB Ethernet". +10. Teraz nahráme program do EV3-kocky pomocou tlačidla vpravo od + "EV3DEV DEVICE BROWSER" (vľavo dole). + ![nahrať program](img/L4_download.png) +11. Aby sme program spustili, musíme ho nájsť an EV3-kocke. Zvolíme (stredným + tlačidlom) `File Browser` -> `2motors` -> `main.py`. + ![file browser](img/L4_file_browser.png) +12. Po spustení sa motory na 2 sekundy rozbehnú a potom zastavia. To je všetko, + čo náš program teraz robí. Celý tento postup nahrania kódu do EV3-kocky + a spustenie ho sa dá spraviť naraz pomocou klávesu ++f5++, skúste si to. + +## Vysvetlenie kódu + +Prejdime si teraz náš program postupne, riadok po riadku: + +```py linenums="1" +#!/usr/bin/env pybricks-micropython +``` + +Toto je špeciálny riadok, ktorý hovorí, ako (akým *interpretrom*) spustiť +napísaný kód. Nechceme ho spustiť ako klasický Python, ktorý sme používali +doteraz. Tento riadok tiež budeme písať v každom našom ďalšom programe pre +robota. Vždy musí byť hneď na začiatku súboru. + +```py linenums="3" +from pybricks.ev3devices import Motor +from pybricks.parameters import Port +from pybricks.tools import wait +``` + +Zatiaľ sme používali len bežné funkcie Pythonu, napr. sme ho používali ako +jednoduchú kalkulačku, používali sme `#!py print` či `#!py list`. Ale existuje +veľmi veľa rôznych funkcií, ktoré sa v Pythone dajú využiť. Aby v nich bol +aspoň trochu poriadok, delia sa na *balíčky* (*package*) a *moduly* (*module*). +Väčšinu z nich je treba doinštalovať, my si vystačíme s tými, ktoré sú vstavané +v `Pybricks`. Balíček `pybricks` obsahuje všetko potrebné na riadenie EV3-kocky, +motorov, komunikáciu so senzormi. Tento balíček obsahuje niekoľko modulov. +Napr. `ev3devides`, `parameters`, `tools`. No a tieto moduly zase +obsahujú funkcie a triedy (o triedach si povieme niečo viac o chvíľu) -- ak ich +chceme v našom kóde využiť, musíme si ich *naimportovať*: +`#!py from package.module import function`. A to presne sa deje na týchto 4 +riadkoch. Naimportujeme si triedy `Motor`, `Port` a funkciu `wait`, +ktoré budeme ďalej používať. Bez tohto `#!py import` by sme dostali chybovú +hlášku ak by sme sa ich pokúsili použiť. + +```py linenums="8" +left_motor = Motor( Port.B ) +right_motor = Motor( Port.C ) +``` + +Ty sme využili naimportovanú *triedu* (*class*) `Motor`. Trieda slúži ako +továreň na *objekty* (*object*). Takýto objekt v počítači často reprezentuje +nejaký objekt v reálnom svete. Teraz je to jednoduché. `Motor` vytvorí +v pamäti EV3 objekt, pomocou ktorého budeme ovládať skutočný motor pripojený +káblom na EV3-kocku. Aby sa nám tento objekt v pamäti nestratil, pomenujeme +si ho. Ale náš robot má dva motory, jeden pripojený k portu `B`, druhý k portu +`C`. Musíme továrni povedať: "Teraz mi vyrob motor (objekt triedy Motor), ktorý +je pripojený na porte B. Teraz zase chcem motor pripojený na C-čko." Podobne +ako sme dávali argumenty do funkcií (napr. `#!py "ahoj"` je argumentom +v `#!py print( "ahoj" )`), argumenty dávame aj keď vytvárame objekty pomocou +tried. `Port.B` je spôsob ako povedať, že motor je pripojený k portu `B`. + +Takže po vykonaní týchto dvoch riadkov, v pamäti EV3 sú dva motory, jeden +pomenovaný `left_motor`, pripojený na port `B`, a druhý, `right_motor`, +pripojený na port `C`. + +??? note "Poznámka k triedam" + Ak si pamätáte, slovíčko "class" sme už niekde videli. + ```py + >>> type( "hello" ) + + >>> type( 1 ) + + >>> type( [ 1, 2, 3] ) + + ``` + Vlastne všetko je v Pythone objekt. `"hello"` povie továrni triedy `str` + aby vytvorila objekt -- text "hello". `1` vytvorí tiež objekt -- číslo 1. + A rovnako pre `list` aj, v podstate, všetko ostatné :) + +```py linenums="11" +left_motor.run( 360 ) +right_motor.run( 360 ) +``` + +Keď chceme k vytvoreným objektom pristupovať, použijeme bodku `.`. Ako sme +zvykli zavolať *funkciu* (`#!py print( "hello" )`), tak zavoláme aj funkciu +priradenú k objektu, tzv. *metódu* (*method*): `objekt.metóda(argumenty)`. +Funkcia `#!py print` jednoducho vypíše daný text na obrazovku. Ale `run` +potrebuje vedieť, aký motor má spustiť, preto je priradená k objektu, a teda +nie je len *funkciou*, ale *metódou*. + +Parameter metódy `run` je rýchlosť, akou motor bude spustený, v stupňoch za +sekundu. Keďže kruh má 360 stupňov, je to rýchlosť 1 RPM (*rotates per minute*), +1 otočenie kolesa za sekundu. Ak by sme použili zápornú hodnotu, motor by sa +otáčal do druhej strany. + +Tieto dva príkazy teda spustia oba motory rovnakou rýchlosťou. A budú bežať +až pokým ich znovu nevypneme. + +```py linenums="14" +wait( 2000 ) +``` + +Funkcia `wait` jednoducho zastaví vykonávanie programu na daný počet milisekúnd. +Takže vykonávanie počká na tomto riadku 2 sekundy (1 sekunda má 1000 +milisekúnd). Počas tejto doby sa nič nové nedeje, motory sú stále zapnuté. + +```py linenums="16" +left_motor.stop() +right_motor.stop() +``` + +Po ubehnutí 2 sekúnd oba motory vypneme metódou `stop`, ktorá neberie žiadne +argumenty. Takže kolesá by sa mali otočiť (približne) o dve celé otáčky -- +skontrolujte to :) + +???+ question "Úloha" + Vyskúšajte si program rôzne upravovať. + + 1. Vyskúšajte ako rýchlo dokáže robot ísť. + 2. Naprogramujte ho tak, aby šiel dozadu, nie dopredu. + 3. Čo sa stane, ak spustíte len jeden motor, zatiaľ čo druhý bude stáť? + 4. Ako otočiť robota na mieste? + 5. Zamyslite sa, akú rýchlosť treba nastaviť motorom, aby sa robot pohyboval + do kruhu. + +!!! example "Príklad" + Teraz si ukážeme zaujímavejší pohyb -- do špirály. Začneme v strede a + postupne budeme robiť akoby väčší a väčší kruh. Už vieme, že na pohyb + do kruhu je potrebné jednému motoru udeliť väčšiu rýchlosť než druhému. + A čím je tento rozdiel rýchlostí menší, tým je kruh väčší. Až pokým nebude + rozdiel nulový, vtedy pôjde robot rovno. + + ```py linenums="1" + #!/usr/bin/env pybricks-micropython + + from pybricks.ev3devices import Motor + from pybricks.parameters import Port + from pybricks.tools import wait + + + left_motor = Motor( Port.B ) + right_motor = Motor( Port.C ) + + left_motor.run( 360 ) + + for right_speed in range( 1000, 359, -1 ): + right_motor.run( right_speed ) + wait( 20 ) + + wait( 1000 ) + + left_motor.stop() + right_motor.stop() + ``` + + Na postupné znižovanie rýchlosti motoru `C` sme použili nám už známu + konštrukciu `#!py for ... in range(...)`. `#!py range` generuje čísla + od prvého vrátane, po posledné, ale zastaví sa jeden krok pred ním, preto + sme použili `359`, aby na konci bola rýchlosť o jedna väčšia, teda `360`, + a robot šiel rovno. + +Na dnes toho bolo už dosť, nabudúce si ukážeme jeden spôsob ako púšťať motory +o niečo presnejšie, a tiež použijeme prvý krát senzor! Bude to tlačidlový +senzor, ktorým môžeme napr. odštartovať rôzne akcie robota alebo detekovať +zrážku s iným predmetom. diff --git a/docs/05.md b/docs/05.md new file mode 100644 index 0000000..b6f4d2a --- /dev/null +++ b/docs/05.md @@ -0,0 +1,230 @@ +# Presnejší pohyb a dotykový senzor + +V minulej lekcii sme sa naučili ako spustiť motor: `#!py motor.run( speed )`. +To ale nemusí byť vždy najlepší spôsob ako posunúť robota. Skúsme si na robotovi +spustiť nasledujúci jednoduchý program: + +```py linenums="1" +from pybricks.ev3devices import Motor +from pybricks.parameters import Port +from pybricks.tools import wait + +motor = Motor( Port.B ) + +motor.run( 360 ) +wait( 2000 ) +motor.stop() +``` + +Keď zastavíme alebo len spomalíme točiace sa koleso, jednoducho sa otočí o toľko +menej. Podobne, ak bude na zemi nejaká prekážka, či len iný povrch, môže +sa robot správať inak a posunúť sa o menej či viac než keď sme naposledy náš +program skúšali. Čo sa s tým dá robiť? + +## Presnejší pohyb + +Ukážeme si novú dôležitú metódu `motor.run_angle`: + +```py linenums="1" +from pybricks.ev3devices import Motor +from pybricks.parameters import Port +from pybricks.tools import wait + +motor = Motor( Port.B ) + +motor.run_angle( 360, 2 * 360 ) +``` + +"angle" znamená po anglicky "uhol", takto vieme pootočiť motor presne o určený +uhol. Prvý parameter je rýchlosť, znovu v stupňoch za sekundu. Druhý parameter +je uhol v stupňoch, o ktorý sa motor pootočí. Keďže kruh má 360 stupňov, motor +sa otočí o dve celé rotácie. Aj ak skúsime koleso na robotovi zachytiť, po jeho +pustení sa dotočí tak, aby nakoniec tie dve rotácie vykonalo. + +!!! note "Poznámka" + Všimnime si, že už nemusíme volať `#!py motor.stop()`. Motor po dosiahnutí + svojho cieľa zastaví. + +To je pre nás dobrá správa, že takéto metóda triedy `Motor` existuje. Poďme +ju využiť a posunúť sa s celým robotom vpred, nie len jednou stranou. + +```py linenums="5" +left_motor = Motor( Port.B ) +right_motor = Motor( Port.C ) + +left_motor.run_angle( 360, 2 * 360 ) +right_motor.run_angle( 360, 2 * 360 ) +``` + +A nastal problém. Takto sa robot neposunie vpred rovno, ale najprv jedným +kolesom, potom druhým. Je to preto, že `run_angle` má ešte ďalší, prednastavený +(*default*) parameter: `wait`. Ak ho neuvedieme medzi argumentami pri volaní +metódy, je nastavený na `#!py True`, čo znamená, že robot čaká, pokým sa tento +príkaz pootočenia motora nevykoná do konca. Keď ho nastavíme na `#!py False`, +čakať nebude, ale hneď bude pokračovať nasledujúcim riadkom, teda spustí oba +motory (skoro, no je to zanedbateľný rozdiel v čase) naraz. Koniec nášho +programu teraz vyzerá: + +```py linenums="8" +left_motor.run_angle( 360, 2 * 360, wait=False ) +right_motor.run_angle( 360, 2 * 360) +``` + +Program už funguje ako má, hurá! Pri `right_motor` sme už nenastavili `wait` +na `#!py False`, lebo naopak chceme počkať, pokým sa tento motor dotočí. +Predpokladáme, že vtedy už aj `left_motor` je na správnej pozícii, keď sa majú +otočiť oba tou istou rýchlosťou o ten istý úsek. + +??? note "Naozaj presný pohyb" + Toto je už trochu pokročilejšie. No môže sa stať, že `left_motor` ešte + nebude dotočený (napr. ho blokuje nejaká prekážka). Preto je lepšie počkať, + pokým kontrolér riadiaci tento motor nebude hlásiť, že je už všetko + v poriadku. To vieme zistiť cez `#!py motor.control.done()`. Dobré je si + pripraviť takúto pomocnú funkciu na začiatku programu, ktorú je možné ďalej + využívať a nepísať zakaždým v programe celý kód znovu: + + ```py linenums="1" + def move_angle( speed, rotation_angle ): + left_motor.run_angle( speed, rotation_angle, wait=False ) + right_motor.run_angle( speed, rotation_angle, wait=True ) + + while not left_motor.control.done(): + wait(1) + ``` + +??? note "Ďalší prednastavený parameter" + V skutočnosti má `run_angle` ešte jeden *default* parameter: `then`. + Určuje, akým spôsobom motor na konci zastaví. Sú tri možnosti: + + - `#!py Stop.COAST` plynule nechá motor dôjsť zotrvačnosťou. + - `#!py Stop.BRAKE` zastaví motor hneď. + - `#!py Stop.HOLD` udržuje motor v konečnej pozícii. To znamená, že ak by + niečo pootočilo koleso, robot sa bude snažiť vrátiť ho do pôvodnej + pozície. + + Prednastavená hodnota je `#!py then=Stop.HOLD`. + +???+ question "Jednoduchá úloha" + Skúste upraviť program tak, aby sa robot raz otočil na mieste a skončil + (čo najviac) presne tak ako na začiatku. + +???+ question "Ťažšia úloha" + Napíšte program, ktorým robot pôjde do tvaru štvorca. Využite informáciu + z predchádzajúcej úlohy, kde ste už zistili ako sa otočiť o celú rotáciu, + teraz potrebujete len o štvrť rotácie. Používajte vždy metódy + `run_angle`. + +???+ question "Úloha na zamyslenie" + Možno ste na minulú úlohu použili veľa zavolaní `run_angle`. Ak áno, skúste + svoj program skrátiť použitím `#!py for ... in ...` cyklu a/alebo + zadefinovaním si svojej pomocnej funkcie. + +??? example "Riešenie" + Ukážeme si ako napríklad by sa dala úloha vyriešiť: + + ```py linenums="1" + from pybricks.ev3devices import Motor + from pybricks.parameters import Port + from pybricks.tools import wait + + left_motor = Motor( Port.B ) + right_motor = Motor( Port.A ) + + def move_angle( speed, angle, rotate=False ): + left_motor.run_angle( speed, angle, wait=False ) + if rotate: + angle = -angle + right_motor.run_angle( speed, angle, wait=True ) + + for _ in range( 4 ): + move_angle( 360, 2 * 360 ) + move_angle( 360, 150, rotate=True ) + ``` + + Štvorec sme rozdelili na štyri rovnaké úseky: najprv sa robot pohne dopredu + a potom sa otočí o 90°. V kóde vidíme dve nové veci: + + - Definícia vlastnej funkcie s *default* parametrami. Píšeme ich vždy + na koniec a pomocou znaku ++equal++ prednastavíme hodnotu. + - `#!py for _ in range( 4 ):` Namiesto `_` by sme rovnako mohli napísať + napr. `i`, ako sme zvyknutí, ale ak túto premennú vnútri cyklu nevyužijeme + (čo je tento prípad), tak je zvykom nazvať ju jednoducho `_`. + +## Dotykový senzor + +Na robota teraz pridáme dotykový senzor. Tak bude dostávať aj spätnú väzbu +z okolia, čo je pre robotov veľmi dôležité. Podľa toho môže zmeniť správanie. +Napr. pri narazení do steny dotykovým senzorom, robot náraz detekuje, otočí sa +a bude hľadať cestu iným smerom. Návod je [tu](https://education.lego.com/v3/assets/blt293eea581807678a/blt95682a19090a6923/5f8801e2ad20281d51fbc1cc/ev3-touch-sensor-driving-base.pdf). + +![robot](img/L5_robot.png) + +V návode ste si určite všimli, že kábel od dotykového senzoru je zapojený +na druhej strane ako káble od motorov. Zariadenia, ktoré pripájame +k EV3-kocke, môžeme rozdeliť na dva typy: + +1. *vstupné* -- To sú senzory. Ich porty sú označené číslami 1 - 4. Senzory + snímajú rôzne hodnoty z okolia (napr. teplotu, vzdialenosť od objektu vpredu, + intenzitu svetla, dotyk, ...) a posielajú ich do EV3-kocky, ktorá následne + hodnoty spracuje a robot zmení podľa toho správanie. Je to našou úlohou, + programátorov, zvoliť tie správne reakcie na hodnoty z okolia. +2. *výstupné* -- Cez ne robot mení správanie. Napr. pohybom pomocou motorov. + Alebo začne blikať svetelnými diódami, vykreslí niečo na displeji, či vydá + zvuky, môže dokonca napodobniť ľudskú reč. + +## Ako pracovať s dotykovým senzorom v Pythone + +Balíček `pybricks` ponúka modul `ev3devices`, kde sa okrem triedy `Motor` +nachádza aj trieda `TouchSensor`. Na jeho objektoch zavoláme metódu `pressed`, +ktorá vráti hodnotu `#!py True` alebo `#!py False` podľa toho, či je spínač +stlačený. + +!!! example "Príklad" + Poďme si to vyskúšať na jednoduchom príklade. Stlačením senzoru budeme + posúvať robota vpred, keď senzor stlačený nebude, robot zostane stáť. + + ```py linenums="1" + from pybricks.ev3devices import Motor, TouchSensor + from pybricks.parameters import Port + from pybricks.tools import wait + + left_motor = Motor( Port.B ) + right_motor = Motor( Port.C ) + touch_sensor = TouchSensor( Port.S1 ) + + def move( speed ): + left_motor.run( speed ) + right_motor.run( speed ) + + while True: + if touch_sensor.pressed(): + move( 360 ) + else: + move( 0 ) + ``` + + `#!py while True:` vytvorí nekonečný cyklus, v ktorom sa zakaždým spýtame + dotykového senzoru, či je stlačený, a podľa toho určíme motorom rýchlosť. + Tu je ďalší spôsob ako zastaviť motory -- nastaviť im rýchlosť na nulu. + +???+ question "Úloha 1" + Upravte program pohybu robota do štvorca tak, aby na začiatku robot stál + na mieste a čakal na stlačenie tlačidla. Potom prejde štvorec, znovu čaká + na mieste na stlačenie tlačidla, a tak stále dokola. + +???+ question "Úloha 2" + Napíšte program, kde robot pôjde pomaly rovno dopredu, pokým nenarazí + do prekážky dotykovým senzorom. Vtedy zastane, trochu cúvne, otočí sa + (približne) o 90° a pôjde znovu pred až pokým nenarazí opäť. Toto správanie + sa bude opakovať donekonečna. + +???+ question "Úloha 3" + Teraz využijete program z predchádzajúcej úlohy, len sa zmení prostredie. + Postavte robota na stôl a upravte ho tak, aby bol dotykový senzor otočený + smerom k zemi a dotýkal sa jej tak, že je stlačený. Ak robot príde na okraj + stola, namiesto narazenia do prekážky ako v minulej úlohe, dotykový senzor + sa už nebude opierať o stôl a tlačidlo sa uvoľní. Vtedy robot vykoná rovnaký + manéver ako v minulej úlohe. Budete teda potrebovať aj program mierne + upraviť. Pri testovaní pozor! Nech robot nespadne zo stola, aj pri dobre + napísanom programe sa to môže stať. + trochu upraviť program. diff --git a/docs/06.md b/docs/06.md new file mode 100644 index 0000000..12ff8de --- /dev/null +++ b/docs/06.md @@ -0,0 +1,214 @@ +# Svetelný senzor + +Dnes si ukážeme ďalší senzor, pomocou ktorého budeme vedieť rozlišovať farby +predmetov a zisťovať intenzitu svetla. Aby sme si ho prakticky vyskúšali, +vymeníme dotykový senzor z minulej lekcie za svetelný senzor, ktorý bude otočený +smerom do zeme a umiestnený na prednej strane robota v strede, asi 1.5 cm +od zeme (spodná časť senzoru). Tentokrát to skúste bez návodu :) + +![svetelný senzor](img/L6_color_sensor.png){ style="width:30%;height:auto" } + +## Ako funguje svetelný senzor? + +Tento senzor má v sebe tri svetelné diódy: červenú, zelenú a modrú. Sú to tri +základné farby v počítačovej grafike, ktorých miešaním vieme vytvoriť ostatné +farby. A senzor tiež obsahuje svetelný prijímač, ktorý zistí intenzitu +dopadajúceho svetla. Ak chceme rozlíšiť farbu predmetu pred senzorom, krátko +sa rozsvieti každá z troch svetelných diód a zmeria sa odrazené svetlo. Tieto +hodnoty sa potom dajú dokopy a senzor výsledok vyhodnotí ako jednu zo +7 farieb: čierna, modrá, zelená, žltá, červená, biela a hnedá. Alebo "žiadna" +farba, ak napr. pred senzorom žiaden objekt v blízkosti nie je, len vzduch. + +## Ako svetelný senzor použijeme v programe? + +V moduli `ev3devices` existuje trieda `ColorSensor`. Na objektoch tejto triedy +budeme volať metódy: + +- `color`: vráti farbu objektu `Color.BLACK`, `Color.BLUE`, ..., alebo `None` + pre žiadnu farbu (`Color` pochádza z modulu `parameters`). +- `ambient`: vráti intenzitu (číslo medzi 0 a 100) svetla z okolia. +- `reflection`: ako `ambient`, ale pre odrazené svetlo, to znamená, že senzor + krátko zasvieti a zmeria intentzitu až potom. +- `rgb`: vráti intenzitu každej zložky svetla, červenej (*r*ed), zelenej + (*g*reen) a modrej (*b*lue) ako *n-ticu* (*tuple*). + +???+ note "*tuple*" + *tuple* je nový dátový tip, s ktorým sme sa ešte nestretli. Je podobný + zoznamu, až na to, že sa nedá meniť. Namiesto hranatých zátvoriek sa + zapisuje pomocou okrúhlych. Je užitočný ak viacero hodnôt patrí úzko + ku sebe. + ```py + >>> t = (1, 2, 5) + >>> t + (1, 2, 5) + >>> t[0] + 1 + >>> t[0] = 3 + Traceback (most recent call last): + File "", line 1, in + TypeError: 'tuple' object does not support item assignment + >>> t += (3, 4) # "t" sa nezmení, ale vznikne nový objekt a pomenuje sa "t" + >>> t + (1, 2, 5, 3, 4) + ``` + +!!! example "Príklad" + Naprogramujeme si robota tak, aby chodil po čiernej čiare. Predstavme si, + že všade okolo je biela zem a len jedna kľukatá čierna čiara, hrubá asi + 1.5 cm. Ak robota postavíme na začiatok čiary tak, aby svetelný senzor + snímal pravý okraj čiary a pustíme ho rovno vpred, bude sa môcť riadiť ďalej + týmito pravidlami: + + 1. Nasníma nižšiu intenzitu svetla -- to znamená, že je na zákrute vpravo, + senzor sa dostal z okraju čiary viac do stredu a preto "vidí" viac + čiernej farby (teda menej intenzity svetla). + 2. Naopak, keď nasníma vyššiu intenzitu sveta, tak čiara musí byť naľavo, + robot sa dostal od čiary preč, na bielu plochu okolo. + + Poďla toho sa pootočí na stranu, kde pokračuje čiara. + + ```py linenums="1" + from pybricks.ev3devices import Motor, ColorSensor + from pybricks.parameters import Port + from pybricks.tools import wait + + left_motor = Motor( Port.B ) + right_motor = Motor( Port.A ) + color_sensor = ColorSensor( Port.S1 ) + + def move( speed_left, speed_right ): + left_motor.run( speed_left ) + right_motor.run( speed_right ) + + WHITE = 60 + BLACK = 40 + + while True: + intensity = color_sensor.reflection() + + if intensity > WHITE: + move( -180, 180 ) + elif intensity < BLACK: + move( 180, -180 ) + else: + move( 360, 360 ) + ``` + + Číselné hodnoty v kóde sú závislé na konkrétnom prostredí, kde púšťame + robota. Je vhodné si najprv zistiť, akú intenzitu svetla robot vníma + na bielej a čiernej čiare a podľa toho nastaviť `WHITE` a `BLACK` tak, aby + `WHITE` bola hodnota, keď sa robot dostane kúsok mimo čiary, a `BLACK` + keď sa robot (svetelný senzor na ňom) dostane príliš dovnútra čiernej + čiary. Nastaviť dobré hodnoty môže chvíľu trvať. + +???+ question "Úloha 1" + Pripravte si dráhu, na ktorej budete robota chodiaceho po čiare testovať. + Mohli by stačiť veľké biele papere a čierna elektroizolačná páska. + Nastavte hodnoty v programe tak, aby robot zvládol po čiare prejsť. + +???+ question "Úloha 2" + Upravte program tak, aby robot mohol začínať na ľavej a nie pravej strane + čiary. Teda nebude sledovať pravý okraj, ale ľavý. + +???+ question "Úloha 3" + Toto je ťažká úloha. Ale dá sa vyriešiť a človekovi uľahčí prácu -- + automatické nastavenie hodnôt `BLACK` a `WHITE`. Robota bude stačiť + na začiatku postaviť na čiaru, otočeného tak, aby svetelný senzor bol + nad bielou plochou. Robot nasníma hodnotu bielej farby a pomaly sa bude + otáčať na mieste a sledovať stále intenzitu svetla. Keď nadíde na čiernu + čiaru, intenzita sa zníži, robot si zapamätá jej minimum, lebo potom + sa znovu začne zvyšovať, robot prejde cez stred čiary a otáča sa ďalej. + Pomocou týchto dvoch hodnôt sa potom určia hodnoty `BLACK` a `WHITE`. + (Vlastne, už by sme ich nemali písať veľkými písmenami, lebo to nebudú + konštanty.) Ale pozor, nemôžeme ich jednoducho nastaviť na práve tú nameranú + hodnotu, to by nefungovalo (prečo?). + +??? example "Riešenie úlohy 3" + ```py linenums="1" + from pybricks.ev3devices import Motor, ColorSensor + from pybricks.parameters import Port + from pybricks.tools import wait + + left_motor = Motor( Port.B ) + right_motor = Motor( Port.A ) + color_sensor = ColorSensor( Port.S1 ) + + def move( speed_left, speed_right ): + left_motor.run( speed_left ) + right_motor.run( speed_right ) + + white = color_sensor.reflection() + + move( -90, 90 ) + last_intensity = color_sensor.reflection() + decreasing = True + while decreasing: + new_intensity = color_sensor.reflection() + if new_intensity > last_intensity: + decreasing = False + last_intensity = new_intensity + move( 0, 0 ) + + black = color_sensor.reflection() + + while True: + intensity = color_sensor.reflection() + + if intensity > white: + move( -180, 180 ) + elif intensity < black: + move( 180, -180 ) + else: + move( 360, 360 ) + ``` + V tomto programe sa robot začne otáčať vľavo a v každom opakovaní `while` + cyklu načíta intenzitu odrazeného svetla. Ak je už vyššia ako tá + predošlá, znamená to, že je niekde blízko stredu čiary, kúsok za stredom, + a vtedy zastaví. + +## Červená, stáť! + +Ako sme už hovorili, svetelný senzor dokáže viac než len odmerať intenzitu +dopadajúceho svetla. Vie odlíšiť aj niekoľko bežných farieb. My si teraz +vylepšíme našeho robota, aby zastavil na červenej značke. Len upravíme náš +už existujúci program -- jeho nekonečný `while` cyklus: + +```py linenums="16" +while True: + if color_sensor.color() == Color.RED + break + + intensity = color_sensor.reflection() + + if intensity > white: + move( -180, 180 ) + elif intensity < black: + move( 180, -180 ) + else: + move( 360, 360 ) + +move( 0, 0 ) +``` + +Hneď na začiatku každého opakovania robot vyskúša, či nabehol na červenú značku, +ak áno, cez `break` "vyskočí" von z nekonečného cyklu a pokračuje za ním, teda +na riadku s `#!py move( 0, 0 )`. Ešte potrebujeme naimportovať triedu `Color`, +ktorú sme doteraz nevyužívali: + +```py linenums="2" +from pybricks.parameters import Port, Color +``` + +Vyskúšajte, či program funguje :) + +???+ question "Úloha 1" + Použite ďalšiu značku, napr. modrú, na ktorú keď robot nadíde, otočí sa + na mieste a bude pokračovať po čiare naspäť, z kade prišiel. + +???+ question "Úloha 2" + Upravte svoju trénovaciu plochu s čiarou tak, aby na niektorých miestach + bola čiara prerušená. Pridajte robotovi aj druhý svetelný senzor, + tentokrát si bude udržovať čiernu čiaru medzi dvomi senzormi. Nedávajte ich + teda od seba veľmi ďaleko. Výhoda použitia dvoch senzorov je, že robot + dokáže sledovať cestu aj s vynechanými úsekmi čiary. Logiku programu budete + musieť ale úplne zmeniť. diff --git a/docs/img/L1_python_shell_win.png b/docs/img/L1_python_shell_win.png new file mode 100644 index 0000000..7f4b03d Binary files /dev/null and b/docs/img/L1_python_shell_win.png differ diff --git a/docs/img/L1_x.png b/docs/img/L1_x.png new file mode 100644 index 0000000..eadbd78 Binary files /dev/null and b/docs/img/L1_x.png differ 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/docs/img/L4_connect.png b/docs/img/L4_connect.png new file mode 100644 index 0000000..cec0db0 Binary files /dev/null and b/docs/img/L4_connect.png differ diff --git a/docs/img/L4_connect_my.png b/docs/img/L4_connect_my.png new file mode 100644 index 0000000..0f2ed36 Binary files /dev/null and b/docs/img/L4_connect_my.png differ diff --git a/docs/img/L4_download.png b/docs/img/L4_download.png new file mode 100644 index 0000000..5621ddb Binary files /dev/null and b/docs/img/L4_download.png differ diff --git a/docs/img/L4_extension_install.png b/docs/img/L4_extension_install.png new file mode 100644 index 0000000..1d48305 Binary files /dev/null and b/docs/img/L4_extension_install.png differ diff --git a/docs/img/L4_extensions.png b/docs/img/L4_extensions.png new file mode 100644 index 0000000..9bf47ba Binary files /dev/null and b/docs/img/L4_extensions.png differ diff --git a/docs/img/L4_file_browser.png b/docs/img/L4_file_browser.png new file mode 100644 index 0000000..9ad41bb Binary files /dev/null and b/docs/img/L4_file_browser.png differ diff --git a/docs/img/L4_new_project.png b/docs/img/L4_new_project.png new file mode 100644 index 0000000..83b32c3 Binary files /dev/null and b/docs/img/L4_new_project.png differ diff --git a/docs/img/L4_open_main_py.png b/docs/img/L4_open_main_py.png new file mode 100644 index 0000000..9ee4413 Binary files /dev/null and b/docs/img/L4_open_main_py.png differ diff --git a/docs/img/L4_robot.png b/docs/img/L4_robot.png new file mode 100644 index 0000000..c7a2336 Binary files /dev/null and b/docs/img/L4_robot.png differ diff --git a/docs/img/L4_status.png b/docs/img/L4_status.png new file mode 100644 index 0000000..5081a41 Binary files /dev/null and b/docs/img/L4_status.png differ diff --git a/docs/img/L5_robot.png b/docs/img/L5_robot.png new file mode 100644 index 0000000..18bdab0 Binary files /dev/null and b/docs/img/L5_robot.png differ diff --git a/docs/img/L6_color_sensor.png b/docs/img/L6_color_sensor.png new file mode 100644 index 0000000..f8c7891 Binary files /dev/null and b/docs/img/L6_color_sensor.png differ diff --git a/docs/img/logo_head.png b/docs/img/logo_head.png new file mode 100644 index 0000000..981539c Binary files /dev/null and b/docs/img/logo_head.png differ diff --git a/docs/img/logo_icon.png b/docs/img/logo_icon.png new file mode 100644 index 0000000..c553292 Binary files /dev/null and b/docs/img/logo_icon.png differ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..fe9582a --- /dev/null +++ b/docs/index.md @@ -0,0 +1,5 @@ +# RLLR - RoboLab Learning Resources + +Tieto dokumenty chcú pomôcť žiakom naučiť sa programovať [Lego Mindstorms +EV3](https://en.wikipedia.org/wiki/Lego_Mindstorms_EV3) v programovacom jazyku +[Python](https://www.python.org/). diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..5f70100 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,27 @@ +site_name: RLLR +theme: + name: material + logo: img/logo_head.png + favicon: img/logo_icon.png + +nav: + - Home: index.md + - 01.md + - 02.md + - 03.md + - 04.md + - 05.md + - 06.md + +markdown_extensions: + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.superfences + - pymdownx.details + - pymdownx.superfences + - pymdownx.keys + - tables + - attr_list + - admonition diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..261b796 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,20 @@ +click==8.1.3 +ghp-import==2.1.0 +importlib-metadata==4.12.0 +Jinja2==3.1.2 +Markdown==3.3.7 +MarkupSafe==2.1.1 +mergedeep==1.3.4 +mkdocs==1.3.1 +mkdocs-material==8.4.1 +mkdocs-material-extensions==1.0.3 +packaging==21.3 +Pygments==2.13.0 +pymdown-extensions==9.5 +pyparsing==3.0.9 +python-dateutil==2.8.2 +PyYAML==6.0 +pyyaml_env_tag==0.1 +six==1.16.0 +watchdog==2.1.9 +zipp==3.8.1