diff --git a/courses/pyladies.yml b/courses/pyladies.yml index 2b014a91..5fed9b8d 100644 --- a/courses/pyladies.yml +++ b/courses/pyladies.yml @@ -3,7 +3,7 @@ description: Naučte se Python vážně od začátku. Žádné předchozí znalo long_description: | Zde najdeš materiály, které se používají na začátečnických kurzech PyLadies - v Praze, Brně a Ostravě. + v Praze, Brně, Ostravě, Plzni, Olomouci nebo Hradci Králové. Jednotlivé lekce jsou určeny naprostým začátečníkům, žádné předchozí znalosti nejsou nutné. Instrukce jsou uvedeny pro operační systémy Linux, @@ -15,12 +15,31 @@ sessions: - title: Instalace slug: install materials: + - title: Studijní materiály - lesson: beginners/cmdline - lesson: beginners/install - lesson: beginners/venv-setup - lesson: beginners/first-steps - lesson: beginners/install-editor - lesson: git/install + + - title: Videa ke shlédnutí + - title: (13:50) Příkazová řádka - Linux & macOS - video + url: https://youtu.be/GGMg8O4hE0c + - title: (22:34) Příkazová řádka - Windows - video + url: https://youtu.be/kriVWJmXpZc + - title: (8:15) Instalace Pythonu - Linux - video + url: https://youtu.be/7Qi7cSkoBA0 + - title: (4:48) Instalace Pythonu - Windows - video + url: https://youtu.be/Xr6liKJzRGA + - title: (7:48) Nastavení prostředí - video + url: https://youtu.be/AFVvpfQB0V0 + - title: (7:23) První krůčky - video + url: https://youtu.be/0Poe25XdKIA + - title: (8:01) Instalace editoru - video + url: https://youtu.be/JcXUCuneX04 + + - title: Další odkazy a materiály - title: Tahák na klávesnici (PDF) url: https://pyvec.github.io/cheatsheets/keyboard/keyboard-cs.pdf type: cheatsheet @@ -28,21 +47,56 @@ sessions: - title: První program slug: hello materials: + - title: Studijní materiál - lesson: beginners/hello-world - lesson: beginners/print - lesson: beginners/variables - lesson: beginners/comparisons - lesson: beginners/and-or + - title: Videa ke shlédnutí + - title: (7:50) První program - video + url: https://youtu.be/9aJcvOfleFs + - title: (12:38) Print a chybové hlášky - video + url: https://youtu.be/14-LVG9Edng + - title: (15:08) Proměnné - video + url: https://youtu.be/-zKws24FmCg + - title: (17:27) Porovnání - video + url: https://youtu.be/Q1YJqWzfnck + - title: (7:58) Nebo anebo a - video + url: https://youtu.be/5Mc-cgoaM10 + + - title: Další odkazy a materiály + - title: Tahák na debuggování (PDF) + url: https://pyvec.github.io/cheatsheets/errors/errors-cs.pdf + type: cheatsheet + - title: Cykly slug: loops materials: + - title: Studijní materiál - lesson: beginners/expressions - lesson: beginners/functions - lesson: beginners/basic-functions - lesson: intro/turtle - lesson: beginners/while - lesson: beginners/reassignment + + - title: Videa ke shlédnutí + - title: (8:32) Vyhodnocování výrazů - video + url: https://youtu.be/nNXDXuN6Smg + - title: (17:26) Funkce - video + url: https://youtu.be/w_d8VKS8i48 + - title: (15:51) Užitečné funkce - video + url: https://youtu.be/udEOvGIuZrs + - title: (33:02) Želva a cykly - video + url: https://youtu.be/A__45ibnsnc + - title: (6:57) Cyklus While - video + url: https://youtu.be/I_mkND45dB0 + - title: (9:28) Přepisování proměnných - video + url: https://youtu.be/fFh1LiksgdA + + - title: Další odkazy a materiály - title: Tahák s užitečnými funkcemi url: https://pyvec.github.io/cheatsheets/basic-functions/basic-functions-cs.pdf type: cheatsheet @@ -50,8 +104,11 @@ sessions: - title: Správa zdrojového kódu slug: git materials: + - title: Studijní materiál - lesson: git/basics - lesson: git/branching + + - title: Další odkazy a materiály - title: Gitový tahák url: https://pyvec.github.io/cheatsheets/basic-git/basic-git-cs.pdf type: cheatsheet @@ -59,10 +116,23 @@ sessions: - title: Řetězce slug: def-str materials: + - title: Studijní materiály - lesson: beginners/str - lesson: beginners/str-index-slice - lesson: beginners/str-methods - lesson: beginners/fstring + + - title: Videa ke shlédnutí + - title: (19:54) Zápis řetězců - video + url: https://youtu.be/xdZWIvryP20 + - title: (16:12) Výběr z řetězců - video + url: https://youtu.be/QOamCtzLPAo + - title: (9:19) Řetězcové funkce a metody - video + url: https://youtu.be/I3CB2YihRh8 + - title: (11:52) Šablony (formátovací řetězce) - video + url: https://youtu.be/2-r2e9aM1HU + + - title: Další odkazy a materiály - title: Řetězcový tahák url: https://pyvec.github.io/cheatsheets/strings/strings-cs.pdf type: cheatsheet @@ -70,17 +140,40 @@ sessions: - title: Definice funkcí slug: def materials: + - title: Studijní materiály - lesson: beginners/def - lesson: beginners/prefer-return - lesson: beginners/nested-traceback - lesson: beginners/local-variables + - lesson: beginners/recursion + + - title: Videa ke shlédnutí + - title: (17:01) Definice funkcí - video + url: https://youtu.be/6a-RjR9fNBY + - title: (8:54) Vrátit nebo vypsat? - video + url: https://youtu.be/4M6Yq9ZyObs + - title: (3:58) Chybové hlášky ze zanořených funkcí - video + url: https://youtu.be/I_DpjBDQy_k + - title: (14:25) Lokální proměnné - video + url: https://youtu.be/gbjXnMmuAxw + - title: (12:23) Rekurze - video + url: https://youtu.be/Vu6_jK8OiAI - title: Chyby a moduly slug: exc materials: + - title: Studijní materiály - lesson: beginners/exceptions - lesson: beginners/modules # XXX when homework is added, include lesson: beginners/circular-imports + + - title: Videa ke shlédnutí + - title: (17:11) Výjimky - video + url: https://youtu.be/s-P5mBjUJBw + - title: (10:32) Moduly - video + url: https://youtu.be/LFp_x7OgCBU + + - title: Další odkazy a materiály - title: Výjimkový tahák url: https://pyvec.github.io/cheatsheets/exceptions/exceptions-cs.pdf type: cheatsheet @@ -88,15 +181,30 @@ sessions: - title: Rozhraní a testy slug: tests materials: + + - title: Studijní materiály - lesson: beginners/interfaces - lesson: beginners/testing - lesson: beginners/main-module + - title: Videa ke shlédnutí + - title: (6:51) Rozhraní - video + url: https://youtu.be/xQ7J_R8VAJQ + - title: (15:25) Testování - video + url: https://youtu.be/-KS_VRerZQ0 + - title: (10:12) Spouštěcí moduly - video + url: https://youtu.be/S6HyFIUyPTw + - title: (3:53) Negativní testy - video + url: https://youtu.be/s-KPxX5ZcMs + - title: Spolupráce a Open-Source slug: foss materials: + - title: Studijní materiály - lesson: git/collaboration - lesson: git/ignoring + + - title: Další odkazy a materiály - title: Gitový tahák url: https://pyvec.github.io/cheatsheets/basic-git/basic-git-cs.pdf type: cheatsheet @@ -104,51 +212,116 @@ sessions: - title: Seznamy slug: list materials: + - title: Studijní materiály - lesson: beginners/list - lesson: beginners/tuple - lesson: beginners/nested-list + + - title: Videa ke shlédnutí + - title: (11:10) Seznamy - Úvod - video + url: https://youtu.be/abIMJjb9Nxw + - title: (32:52) Co všechno umí seznamy - video + url: https://youtu.be/-gINX_pOtQo + - title: (9:51) N-tice - video + url: https://youtu.be/dObtA75Ouzk + + + - title: Další odkazy a materiály - title: Tahák na seznamy url: https://pyvec.github.io/cheatsheets/lists/lists-cs.pdf type: cheatsheet + - lesson: beginners/vscode - title: Sekvence a soubory slug: seq materials: + - title: Studijní materiály - lesson: beginners/range - lesson: beginners/zip-enumerate - lesson: beginners/files - lesson: beginners/with -- title: Grafika - slug: pyglet - materials: - - lesson: intro/pyglet - - lesson: projects/pong - - title: Tahák na Pyglet - url: https://pyvec.github.io/cheatsheets/pyglet/pyglet-basics-cs.pdf - type: cheatsheet + - title: Videa ke shlédnutí + - title: (5:02) Range - video + url: https://youtu.be/UtyIKgsnLmk + - title: (16:39) Iterátory n-tic (enum & zip) - video + url: https://youtu.be/JUCb7iiGsW4 + - title: (13:17) Soubory - video + url: https://youtu.be/1X4TU_thg7s - title: Slovníky slug: dict materials: + - title: Studijní materiály - lesson: beginners/dict - - lesson: intro/json - - lesson: projects/github-api - lesson: beginners/dict-with-list-values + + - title: Videa ke shlédnutí + - title: (19:23) Slovníky - video + url: https://youtu.be/OVnUhWwd2C0 + - title: (4:42) Více hodnot v jednom záznamu slovníku - video + url: https://youtu.be/pQ268T2IZCo + + - title: Další odkazy a materiály - title: Slovníkový tahák url: https://pyvec.github.io/cheatsheets/dicts/dicts-cs.pdf type: cheatsheet +- title: JSON a API + slug: json + materials: + - lesson: intro/json + - lesson: projects/github-api + - title: Ukázka jednoduchého API + type: lesson + url: http://pyladies.cz/v1/s011-dicts/simple-api.html + + - title: Videa ke shlédnutí + - title: (12:54) Kódování dat - JSON - video + url: https://youtu.be/nPUSH7gSI4o + + - title: Další odkazy a materiály + - title: Co je API? (externí materiál) + url: https://cojeapi.cz + - title: Třídy slug: class materials: + - title: Studijní materiály - lesson: beginners/class - lesson: beginners/inheritance + - title: Videa ke shlédnutí + - title: (22:26) Třídy - video + url: https://youtu.be/x4iJDPOSv4A + - title: (12:58) Dědičnost - video + url: https://youtu.be/mqxMmSFiASw + +- title: Grafika + slug: pyglet + materials: + - title: Studijní materiály + - lesson: intro/pyglet + - lesson: projects/pong + - title: Kód celé hry Pong + url: http://pyladies.cz/v1/s012-pyglet/pong.py + + - title: Další odkazy a materiály + - title: Tahák na Pyglet + url: https://pyvec.github.io/cheatsheets/pyglet/pyglet-basics-cs.pdf + type: cheatsheet + - title: Závěrečný projekt slug: asteroids materials: + - title: Studijní materiály - lesson: projects/asteroids + - lesson: projects/snake + - lesson: klondike/cards + - lesson: klondike/decks + - lesson: klondike/game + + - title: Další odkazy a materiály - title: Množinový tahák url: https://pyvec.github.io/cheatsheets/sets/sets-cs.pdf type: cheatsheet diff --git a/lessons/beginners/vscode/index.md b/lessons/beginners/vscode/index.md new file mode 100644 index 00000000..e9c1e0d8 --- /dev/null +++ b/lessons/beginners/vscode/index.md @@ -0,0 +1,117 @@ +# Visual Studio Code & Python + +Visual Studio Code (také VSCode) je editor zdrojového kódu vyvíjený společností Microsoft pro operační systémy Windows, Linux a macOS. (viz oficiální web: [https://code.visualstudio.com/](https://code.visualstudio.com/) ) + +## Nastavení VSCode + +VSCode umožňuje nastavit spoustu věcí a jelikož pracuji na Windows, budu se bavit hlavně o Windows, ale postupy by měly být totožné i v jiných operačních systémech. + +Jedním ze základním příkazů ve VSCode je otevření příkazové řádky (pozor, jedná se o příkazovou řádku ve VSCode, neplést s příkazovou řádkou ve Winsows). Tato příkazová řádka umožňuje vykonávat různé operace, které jdou vyvolat i jinými způsoby (např. z menu), ale je snazší ji používat, protože umožňuje i textové vyhledávání. Vyvoláme ji jednám z následujících způsobů: + +- z horního menu: `View > Command Pallete...` +- klávesová zkr.: `F1` +- klávesová zkr.: `CTRL+SHIFT+P` (na Macu `CMD+SHIFT+P`) + +a po otevření vypadá následovně + +![Příkazová řádka](./static/cmd-pallete.png) + +### Nastavení terminálu + +VSCode umožňuje nastavit terminál v jakém budeme pracovat, nastavíme jej následovně: + +1. Otevřeme VSCode příkazový řádek +1. Napíšeme `>Terminal: Select Default Profile` +(nebo jen část. Pozor, znak `>` na začátku je důležitý - nemazat) + + ![Select default terminal](./static/cmd-terminal.png) + +1. A vybereme preferovaný terminál - na Windows doporučuju `Command Prompt`, pro jednoduchost a kompatibilitu s kurzy. + + ![CMD](./static/cmd-cmd.png) + +### Instalace rozšíření + +VSCode umožňuje instalovat spoustuužitečných rozšíření a vytvořit tak z jednoduchého textového editoru velmi mocný nástroj. + +Pro naši potřebu budeme potřebovat rozšíření `Python` (od Microsoftu), které nainstalujeme tak, že z levého panelu vybereme ikonu s čtverečky, vyhledávacího pole zadáme `Python` a klikneme na tlačítko `Install`. + +![rozšíření Python](./static/extension.png) + +**Doporučení:** Pokud používáte svůj počítač, máte nainstalováno více rozšíření a víte, že jste je neinstalovali, je dobré je buď zakázat nebo rovnou odstranit - můžou způsobovat nepříjemnosti a zpomalovat celý VSCode. + +## Práce s Pythonem ve VSCode + +### Vytvoření virtuálního prostředí + +1. Otevřete ve VSCode prázdnou složku (nebo složku bez virtuálního prostředí) - složka ve VSCode představuje něco jako projekt, obsahuje zdrojové soubory, ale ukládají se zde i různá nastavení, ... +1. Otevřete `Příkazovou řádku` ve VSCode (kláv. zkr: `F1`) a vyberte příkaz `>Python: Create Environment` (stačí napsat kousek, viz obrázek) a spustí se drobný průvodce, který vám usnadní vytvořit prostředí (viz jednotlivé kroky níže) + + ![Vytvoření prostředí](./static/create-venv.png) + + - Výběr virtuálního prostředí (v kurzech používáme `Venv`) + + ![Výběr typu prostředí](./static/venv-type.png) + + - výběr verze Pythonu (stačí vybrat doporučenou verzi) + + ![Vytvoření prostředí](./static/venv-python.png) + + V mé prázdné složce (`vscode_test`) se vytvoří virtuální prostředí (ve složce `.venv`) + + ![Nové virtuální prostředí](./static/new_venv.png) + +1. Koukneme, jestli se prostředí i aktivovalo... + + - otevřeme si terminál + + - pomocí klávesové zkratky `CTRL+;` (na Macu `CMD+;`) - pomocí této zkratky jde i skrývat a znovu zobrazovat + - z menu `View > Terminal` + + Otevře se nový terminál s aktivovaným prostředím + + ![Nový terminál](./static/terminal.png) + + Pokud chceme terminál odstranit, použijme ikonu koše (okno Terminálu v pravo nahoře) - ikona křížku terminál pouze schová. + + ![Odstranění terminálu](./static/terminal-kill.png) + + - ověřit aktivní prostředí jde i ve spodním panelu, kde se ukáže, jakou verzi pythonu používáme, a že je opravdu z naší složky `.venv` + + ![Ověření prostředí](./static/venv-panel.png) + +### Pytest + +Python rozšíření taky celkem dobře integruje testování do VSCode. Zprovozníme jej následovně: + +1. Otevřeme `VSCode příkazovou řádku` (`F1`) a zadáme příkaz `>Python: Configure Tests` (stčí napsat část, viz obr) + + ![Nastavení Testů](./static/tests-config.png) + +1. Vybereme testovací framework, který chceme použít (v našem případě `pytest`) + + ![Pytest](./static/pytest.png) + +1. Vybert složku, kde jsou (či budou) umístěny testy (v našem případě `.` - hlavní složka projektu). Pytest bude testy hledat pouze v této složce a jejich potomcích. + + ![Nastavení složky s testy](./static/tests-folder.png) + +To je vše - Pytest je nainstalován, nastaven a připraven k použití. VSCode si taky uložilo nějaké nastavení (které jsme mu před chvílí zadali) v naší složce (projektu), které se znova načte při otevření této složky. + + ![Testy: vytvořené testy](./static/tests-settings.png) + +Jak Testy spustit? Jednouše, mámě několik možností + +- přímo z editoru + + ![Testy ve VSCode](./static/pytest-1.png) + +- ze speciálního panelu pro testy + + ![Testy ve VSCode: Ukázka](./static/pytest-2.png) + +Ukázka chyby + + ![Testy ve VSCode: Ukázka](./static/pytest-error.png) + +Poznámka: Samozřejmě můžeme testy pustit i z Terminálu ;-). diff --git a/lessons/beginners/vscode/info.yml b/lessons/beginners/vscode/info.yml new file mode 100644 index 00000000..ad0f9d7d --- /dev/null +++ b/lessons/beginners/vscode/info.yml @@ -0,0 +1,4 @@ +title: VS Code & Python +style: md +attribution: Pro PyLadies Ostrava napsal Michal Vašut, 2022. +license: cc-by-sa-40 diff --git a/lessons/beginners/vscode/static/cmd-cmd.png b/lessons/beginners/vscode/static/cmd-cmd.png new file mode 100644 index 00000000..985ccad5 Binary files /dev/null and b/lessons/beginners/vscode/static/cmd-cmd.png differ diff --git a/lessons/beginners/vscode/static/cmd-pallete.png b/lessons/beginners/vscode/static/cmd-pallete.png new file mode 100644 index 00000000..fcef64a3 Binary files /dev/null and b/lessons/beginners/vscode/static/cmd-pallete.png differ diff --git a/lessons/beginners/vscode/static/cmd-terminal.png b/lessons/beginners/vscode/static/cmd-terminal.png new file mode 100644 index 00000000..8aa75b3a Binary files /dev/null and b/lessons/beginners/vscode/static/cmd-terminal.png differ diff --git a/lessons/beginners/vscode/static/create-venv.png b/lessons/beginners/vscode/static/create-venv.png new file mode 100644 index 00000000..327eb03f Binary files /dev/null and b/lessons/beginners/vscode/static/create-venv.png differ diff --git a/lessons/beginners/vscode/static/extension.png b/lessons/beginners/vscode/static/extension.png new file mode 100644 index 00000000..55982e0c Binary files /dev/null and b/lessons/beginners/vscode/static/extension.png differ diff --git a/lessons/beginners/vscode/static/new_venv.png b/lessons/beginners/vscode/static/new_venv.png new file mode 100644 index 00000000..17d39b13 Binary files /dev/null and b/lessons/beginners/vscode/static/new_venv.png differ diff --git a/lessons/beginners/vscode/static/pytest-1.png b/lessons/beginners/vscode/static/pytest-1.png new file mode 100644 index 00000000..ce1c3b48 Binary files /dev/null and b/lessons/beginners/vscode/static/pytest-1.png differ diff --git a/lessons/beginners/vscode/static/pytest-2.png b/lessons/beginners/vscode/static/pytest-2.png new file mode 100644 index 00000000..c6b73b74 Binary files /dev/null and b/lessons/beginners/vscode/static/pytest-2.png differ diff --git a/lessons/beginners/vscode/static/pytest-error.png b/lessons/beginners/vscode/static/pytest-error.png new file mode 100644 index 00000000..3ff213dd Binary files /dev/null and b/lessons/beginners/vscode/static/pytest-error.png differ diff --git a/lessons/beginners/vscode/static/pytest.png b/lessons/beginners/vscode/static/pytest.png new file mode 100644 index 00000000..18e553cd Binary files /dev/null and b/lessons/beginners/vscode/static/pytest.png differ diff --git a/lessons/beginners/vscode/static/terminal-kill.png b/lessons/beginners/vscode/static/terminal-kill.png new file mode 100644 index 00000000..464f9322 Binary files /dev/null and b/lessons/beginners/vscode/static/terminal-kill.png differ diff --git a/lessons/beginners/vscode/static/terminal.png b/lessons/beginners/vscode/static/terminal.png new file mode 100644 index 00000000..a0717b15 Binary files /dev/null and b/lessons/beginners/vscode/static/terminal.png differ diff --git a/lessons/beginners/vscode/static/tests-config.png b/lessons/beginners/vscode/static/tests-config.png new file mode 100644 index 00000000..d535bfca Binary files /dev/null and b/lessons/beginners/vscode/static/tests-config.png differ diff --git a/lessons/beginners/vscode/static/tests-folder.png b/lessons/beginners/vscode/static/tests-folder.png new file mode 100644 index 00000000..a9bc163f Binary files /dev/null and b/lessons/beginners/vscode/static/tests-folder.png differ diff --git a/lessons/beginners/vscode/static/tests-settings.png b/lessons/beginners/vscode/static/tests-settings.png new file mode 100644 index 00000000..09a11e8f Binary files /dev/null and b/lessons/beginners/vscode/static/tests-settings.png differ diff --git a/lessons/beginners/vscode/static/venv-panel.png b/lessons/beginners/vscode/static/venv-panel.png new file mode 100644 index 00000000..22728e3b Binary files /dev/null and b/lessons/beginners/vscode/static/venv-panel.png differ diff --git a/lessons/beginners/vscode/static/venv-python.png b/lessons/beginners/vscode/static/venv-python.png new file mode 100644 index 00000000..9b870da7 Binary files /dev/null and b/lessons/beginners/vscode/static/venv-python.png differ diff --git a/lessons/beginners/vscode/static/venv-type.png b/lessons/beginners/vscode/static/venv-type.png new file mode 100644 index 00000000..850351f3 Binary files /dev/null and b/lessons/beginners/vscode/static/venv-type.png differ diff --git a/lessons/klondike/cards/index.md b/lessons/klondike/cards/index.md new file mode 100644 index 00000000..acdc808c --- /dev/null +++ b/lessons/klondike/cards/index.md @@ -0,0 +1,349 @@ +# Klondike Solitaire: Karty + +Pojďme vytvořit karetní hru *Klondike Solitaire*, kterou možná znáš v nějaké +počítačové verzi. + +{{ figure(img=static('klondike.png'), alt="Jedna z grafických podob hry") }} + +Naše hra bude ze začátku jednodušší – nebudeme se zabývat grafikou, +ale logikou hry. +„Grafiku“ zatím zajistí textová konzole. Obrázek výše se dá ukázat jako: + +```plain + U V W X Y Z + [???] [ ] [ ] [ ] [ ] [ ] + + A B C D E F G + [3♣ ] [???] [???] [???] [???] [???] [???] + [5 ♥] [???] [???] [???] [???] [???] + [6♣ ] [???] [???] [???] [???] + [5♠ ] [???] [???] [???] + [Q ♥] [???] [???] + [4♠ ] [???] + [3 ♦] +``` + + +## Pasiáns + +*Klondike Solitaire* je [pasiáns](https://cs.wikipedia.org/wiki/Pasi%C3%A1ns) +– karetní hra pro jednoho hráče. +Tyto hry obecně fungují takto: + +* Karty se určitým způsobem *rozdají* do několika balíčků, hromádek nebo + jiných skupin +* Dokud hráč *nevyhrál*: + * Hráč *udělá tah*: podle určitých pravidel přesune karty z jedné hromádky + na druhou + +Než ale počítač naučíš hrát hru, je potřeba ho naučit pár základních věcí, +aby pak instrukce pro samotnou hru dávaly smysl. +Základní věci, které je potřeba počítač „naučit“, jsou: + +* Co je to *karta*? +* Co je to *balíček*? + +Odpovědí na tyhle otázky bude spousta vysvětlování a taky několik pythonních +funkcí, které použiješ i ve zbytku hry. + +Tady bych rád podotknul, že tyhle materiály ukazují předem vyzkoušený způsob, +jak napsat karetní hru. +Reálné projekty takhle nefungují: zahrnují spoustu plánování, slepých uliček, +oprav špatně navrženého kódu a jiných frustrací. +Neděláme tu reálný softwarový projekt – zatím stále *zkoušíme základy*, +jen z nich pak vyleze něco hezkého. + + +## Karta a balíček + +Co je to *karta* a *balíček*? +Jak tyhle koncepty *reprezentovat* v Pythonu – tedy pomocí čísel, řetězců, +seznamů, n-tic a jiných datových typů – abys s nimi mohl{{a}} +dál pracovat? + +Možností jak to udělat je více. +Dobrý návrh *datového modelu* je základ úspěšného projektu: odpověď na otázku +výše je základ k tomu, aby se pak program hezky psal. +Až budeš potřebovat dobrý návrh datového modelu pro nějaký svůj projekt, +doporučuju se ze začátku poradit se zkušenějším programátorem. + +Pro Solitaire je tento úkol za tebe vyřešený: hrou Klondike si procvičíš +seznamy a n-tice (a později slovníky). + + +### Karta + +O *kartě* potřebuješ znát tři kousky informace: hodnotu, barvu a to, jestli +je otočená rubem nebo lícem nahoru. + +*Hodnoty* karet jsou čísla 2-10 a navíc `J`, `Q`, `K`, `A`. +Hodnoty „obrázkových“ karet je dobré převést na čísla: J=11, Q=12, K=14, A=1. +Hodnoty se tak budou dát jednoduše porovnávat, nebo zjišťovat následující kartu +(např. po desítce je jedenáct – `J`). +V programu budeme tedy pro hodnoty používat jen čísla, a teprve když bude +potřeba kartu „ukázat“ člověku, převedeme ji na písmenko. + +Pro *barvu* jsou čtyři možnosti: ♥, ♦, ♣ nebo ♠. +Dají se reprezentovat v podstatě jakýmikoli čtyřmi různými hodnotami. +Různí programátoři by mohli použít čísla 0-3, symboly jako `♥`, nebo třeba jako +čtyři různé funkce. +My použijeme krátké řetězce bez diakritiky, aby se to dobře psalo: +`'Sr'` (srdce), `'Pi'` (piky), `'Ka'` (káry), `'Kr'` (kříže). +Použij prosím stejné řetězce (včetně velkých písmen), abys pak mohl{{a}} +kopírovat ukázkový kód. +Jako u hodnoty platí že tyhle řetězce budeme používat ve většině programu, +jen když bude potřeba kartu „ukázat“ člověku, převedeme je na hezčí symbol. + +Pro *otočení* karty jsou dvě možné hodnoty: buď lícem nebo rubem nahoru. +Když dvě hodnoty, je dobré použít `True` a `False`. +Jen je pak potřeba vybrat (a dodržovat) která je která. +Řekněme že `True` znamená lícem nahoru; `False` rubem. +Ideální je podle toho důsledně pojmenovávat proměnné: v programu vždy +používej `je_licem_nahoru=True`, ne `otoceni=True`. + +Samotná karta pak bude trojice těchto hodnot: (hodnota, barva, je_licem_nahoru). +Například: + +* `(12, 'Sr', True)` je 🂽 – srdcová královna otočená lícem nahoru +* `(7, 'Pi', False)` je 🂠 – piková sedma otočená rubem nahoru + + +### Balíček + +A balíček? Balíček bude seznam karet, tedy seznam trojic. +Jakákoli sekvence karet ve hře bude bude seznam trojic: dobírací a odkládací +balíčky, „sloupečky“ karet na herní ploše i „hromádky“ sežazených karet. + +Například jeden ze sloupečků z obrázku výše obsahuje 4 karty rubem nahoru +a na konci srdcovou královnu. +Jako seznam by to mohlo být: + +```python +[(7, 'Pi', False), (5, 'Kr', False), (1, 'Ka', False), (3, 'Pi', False), (12, 'Sr', True)] +``` + + +### Seznamy a n-tice + +Na balíčcích a kartách je vidět rozdíl v použití seznamů a n-tic: + +* N-tice má pevně dané N: karta je trojice, ne čtveřice + ani dvojice. + Oproti tomu seznamy nemívají pevně danou délku: hromádka karet může být velká, + malá, nebo dokonce prázdná. + Dokonce může během hry růst nebo se zmenšovat, třeba když si „lízneš“ kartu + nebo balíček rozdělíš na dvě části. +* Seznamy často dává smysl zamíchat nebo seřadit. + Když zamíchám balíček karet, je to stále baliček karet. + Když ale zamíchám pořadím prvků ve trojici *(hodnota, barva, je_licem_nahoru)*, + bude to sice pořád trojice, ale už to nejspíš nebude *karta*. +* S tím souvisí to, že v seznamy bývají tzv. *homogenní*: každý prvek stejný + typ. Máme balíček karet (trojic), ale karty jsou trojice + (číslo, řetězec, pravdivostní hodnota). + +> [note] +> Ne ve všech programech to bude takhle jednoznačné. Karta a balíček jsou +> skoro ideální příklady na seznamy a n-tice :) + +V Pythonu z použitých typů vyplývá, co se s nimi dá dělat. + +N-tice nejdou měnit: abys změnil{{a}} např. otočení karty, bude +potřeba udělat úplně novou trojici (podobně jako např u tahu +z `--------------------` na `-------------o------` v 1D piškvorkách). + +Seznamy ale měnit jdou. Seznamové operace dokonce často dávají smysl: + +* *append* je přiložení karty na vršek hromádky. +* *pop* je líznutí karty (z balíčku zmizí, ale karta zůstane v ruce – jako návratová hodnota). +* *extend* je přidání jednoho balíčku ke druhému. +* *random.shuffle* je zamíchání karet. +* *sort* je seřazení karet. + +Pozor ale na to, že se seznamem trojic toho jde dělat víc než s fyzickým +balíčkem karet. +Pro počítač není problém udělat kopii karty. + + +## Pomocné funkce + +Označovat dsrdcovou dámu jako `(12, 'Sr', True)` je skvělé pro počítač, +ale pro lidi je to nepřehledné. +Bude tedy vhodné napsat funkci, která kartu „ukáže“ trochu srozumitelněji. +Taková funkce by měla vyřeši i to, že kartu, která je rubem nahoru +– jako `(5, 'Kr', False)`, je potřeba před hráčem skrýt. + +Napsat tuhle funkci je docela otrava, a později bude potřeba +aby se chovala *přesně* podle mých očekávání +(včetně např. velkých písmen a mezer). +Proto ti ji dám k dispozici. Hlavičku má takovouhle: + +```python +def popis_kartu(karta): + """Vrátí popis karty, např. [Q ♥] nebo [6♣ ] nebo [???] + + Trojice čísla (2-13), krátkého řetězce ('Sr', 'Ka', 'Kr' nebo 'Pi') + a logické hodnoty (True - lícem nahoru; False - rubem) se jednoduše + zpracovává v Pythonu, ale pro "uživatele" není nic moc. + Proto je tu tahle funkce, která kartu hezky "popíše". + + Aby měly všechny hodnoty jen jeden znak, desítka se vypisuje jako + římská číslice "X". + + Aby se dalo rychle odlišit červené (♥♦) karty od černých (♣♠), + mají červené mezeru před symbolem a černé za ním. + """ +``` + +Druhá užitečná funkce umí otočit karu buď rubem nebo lícem nahoru. +Podobně jako `tah` z piškvorek vezme „starou“ hodnotu, rozloží ji +části a výsledek slepí z kombinace „starých“ a „nových“ hodnot. + +Projdi si ji řádek po řádku, abys věděl{{a}} jak funguje: + +```python +def otoc_kartu(karta, pozadovane_otoceni): + """Vrátí kartu otočenou lícem nahoru (True) nebo rubem nahoru (False) + + Nemění původní trojici; vytvoří a vrátí novou. + (Ani by to jinak nešlo – n-tice se, podobně jako řetězce čísla, měnit + nedají.) + """ + + # Rozbalení n-tice + hodnota, barva, stare_otoceni = karta + + # Vytvoření nové n-tice (kombinací staré hodnoty/barvy a nového otočení) + nova_karta = hodnota, barva, pozadovane_otoceni + + # Vrácení nové n-tice + return nova_karta +``` + +Funkce najdeš v souboru [`karty.py`]. Projdi si je; rozumíš jim? + +Testy k nim jsou v [`test_karty.py`] – ty procházet nemusíš. + +[`karty.py`]: {{ static('karty.py') }} +[`test_karty.py`]: {{ static('test_karty.py') }} + +Oba soubory si ulož. + + +### Testy a úkoly + +Další pomocné funkce už napíšeš {{gnd('sám', 'sama')}}. +Aby sis ověřil{{a}} že fungují, mám pro tebe předpřipravené testy. + +Stáhni si soubor s testy, [test_klondike.py], a dej ho do adresáře, +kde budeš tvořit hru a kde máš `karty.py`. + +Na ulehčení testování si nainstaluj modul `pytest-level`. +Ten umožňuje pouštět jen určité testy – podle toho, jak jsi daleko. + + python -m pip install pytest pytest-level + +Zkus pustit všechny testy. Asi ti neprojdou: + + python -m pytest -v + +Pak zkus pustit testy pro úroveň 0: + + python -m pytest -v --level 0 + +Teď se nepustí žádné testy – všechny nové testy se přeskočí; +projdou jen testy z `test_karty.py`. +Uvidíš něco jako: + +```pytest +===== 20 passed, 99 deselected in 0.06s ==== +``` + +Zadáš-li v posledním příkazu `--level 1`, aktivuje se první z testů. Pravděpodobně neprojde – v dalším úkolu ho spravíš! + +[test_klondike.py]: {{ static('test_klondike.py') }} + + + +### Vytvoření balíčku + +Do modulu `klondike` (tedy do nového souboru `klondike.py`) napiš +následující funkci: + +```python +def vytvor_balicek(): + """Vrátí balíček 52 karet – od esa (1) po krále (13) ve čtyřech barvách + + Všechny karty jsou otočené rubem nahoru. + """ +``` + +Zkus si funkci pustit a podívej se, co vrací. + +Aby sis ověřil{{a}}, že se chová správně, pusť na ni testy: + +* level 10: Funkce existuje +* level 11: V balíčku je 52 karet, žádné se neopakují. +* level 12: V balíčku jsou všechny požadované karty. +* level 13: Balíček je zamíchaný. + + +### Rozepsání balíčku + +Když výsledek funkce `vytvor_balicek` vypíšeš, je docela nepřehledný. +Aby se ti s balíčky lépe pracovalo, vytvoř následující funkci: + +```python +def popis_balicek(karty): + """Vrátí popis všech karet v balíčku. Jednotlivé karty odděluje mezerami. + """ +``` + +Funkce by měla vracet řetězec složený z popisů jednotlivých karet. +Například: + +```pycon +>>> karty = [ + (13, 'Pi', True), + (12, 'Sr', True), + (11, 'Ka', True), + (10, 'Kr', False), + ] + +>>> popis_balicek(karty) +[K♠ ] [Q ♥] [J ♦] [???] +``` + +Jak na to? +Vytváření celého řetězce najednou by bylo složité, ale lze si to rozdělit +na kroky, které už znáš: + +* Vytvoř prázdný seznam. +* Postupně do senamu přidej popisy jednotlivých karet. + (Využij funkci `popis_kartu` z modulu `karty`!) +* Vrať popisky oddělené mezerami. (Koukni na tahák k seznamům!) + +Funkci opět můžeš otestovat: + +* level 20: Funkce existuje +* level 21: Funkce správně popisuje balíček +* level 22: Funkce umí popsat i prázdný balíček + + +### Popis balíčku + +Občas je z balíčku vidět jen vrchní karta. +Napiš následující funkci, která popíše takový balíček: + +```python +def popis_vrchni_kartu(balicek): + """Vrátí popis daného balíčku karet -- tedy vrchní karty, která je vidět.""" +``` + +Funkci nezapomeň otestovat: + +* level 30: Funkce existuje +* level 31: Funkce vrátí popis poslední karty. (Bude se hodit funkce `popis_kartu` z modulu `karty`.) +* level 32: Funkce popíše prázdný balíček jako `[ ]` (3 mezery v hranatých závorkách). + + +Pokračování příště... diff --git a/lessons/klondike/cards/info.yml b/lessons/klondike/cards/info.yml new file mode 100644 index 00000000..4c830b68 --- /dev/null +++ b/lessons/klondike/cards/info.yml @@ -0,0 +1,4 @@ +title: "Klondike: Karty" +style: md +attribution: Pro PyLadies Brno napsal Petr Viktorin, 2014-2019. +license: cc-by-sa-40 diff --git a/lessons/klondike/cards/static/karty.py b/lessons/klondike/cards/static/karty.py new file mode 100644 index 00000000..cb516666 --- /dev/null +++ b/lessons/klondike/cards/static/karty.py @@ -0,0 +1,69 @@ +"""Základní operace s "kartou" - trojicí (hodnota, barva, je_licem_nahoru) +""" + +def popis_kartu(karta): + """Vrátí popis karty, např. [Q ♥] nebo [6♣ ] nebo [???] + + Trojice čísla (2-13), krátkého řetězce ('Sr', 'Ka', 'Kr' nebo 'Pi') + a logické hodnoty (True - lícem nahoru; False - rubem) se jednoduše + zpracovává v Pythonu, ale pro "uživatele" není nic moc. + Proto je tu tahle funkce, která kartu hezky "popíše". + + Aby měly všechny hodnoty jen jeden znak, desítka se vypisuje jako + římská číslice "X". + + Aby se dalo rychle odlišit červené (♥♦) karty od černých (♣♠), + mají červené mezeru před symbolem a černé za ním. + """ + + # Rozbalení n-tice, abychom mohli pracovat s jednotlivými složkami + hodnota, barva, je_licem_nahoru = karta + + # Když je vidět jen rub, rovnou vrátíme [???] + if not je_licem_nahoru: + return '[???]' + + # Popis hodnoty: pár speciálních případů, plus čísla 2-9 + if hodnota == 11: + popis_hodnoty = 'J' + elif hodnota == 12: + popis_hodnoty = 'Q' + elif hodnota == 13: + popis_hodnoty = 'K' + elif hodnota == 1: + popis_hodnoty = 'A' + elif hodnota == 10: + popis_hodnoty = 'X' + else: + popis_hodnoty = str(hodnota) + + # Popis barvy: čtyři možnosti + if barva == 'Sr': + popis_barvy = ' ♥' + elif barva == 'Ka': + popis_barvy = ' ♦' + elif barva == 'Kr': + popis_barvy = '♣ ' + elif barva == 'Pi': + popis_barvy = '♠ ' + + # Vrácení hodnoty + return f'[{popis_hodnoty}{popis_barvy}]' + + +def otoc_kartu(karta, pozadovane_otoceni): + """Vrátí kartu otočenou lícem nahoru (True) nebo rubem nahoru (False) + + Nemění původní trojici; vytvoří a vrátí novou. + (Ani by to jinak nešlo – n-tice se, podobně jako řetězce čísla, měnit + nedají.) + """ + + # Rozbalení n-tice + hodnota, barva, stare_otoceni = karta + + # Vytvoření nové n-tice (kombinací staré hodnoty/barvy a nového otočení) + nova_karta = hodnota, barva, pozadovane_otoceni + + # Vrácení nové n-tice + return nova_karta diff --git a/lessons/klondike/cards/static/klondike.png b/lessons/klondike/cards/static/klondike.png new file mode 100644 index 00000000..61d321e4 Binary files /dev/null and b/lessons/klondike/cards/static/klondike.png differ diff --git a/lessons/klondike/cards/static/test_karty.py b/lessons/klondike/cards/static/test_karty.py new file mode 100644 index 00000000..aff5a74c --- /dev/null +++ b/lessons/klondike/cards/static/test_karty.py @@ -0,0 +1,70 @@ +import pytest +import karty + + +def test_popis_rubem_nahoru(): + """Popis karty, která je rubem nahoru, by měl ukázat otazníky""" + karta = 13, 'Pi', False + assert karty.popis_kartu(karta) == '[???]' + + +def test_popis_srdcova_kralovna(): + """Popis srdcové královny by měl být "[Q ♥]".""" + karta = 12, 'Sr', True + assert karty.popis_kartu(karta) == '[Q ♥]' + + +def test_popis_krizova_sestka(): + """Popis křížové šestky by měl být "[6♣ ]".""" + karta = 6, 'Kr', True + assert karty.popis_kartu(karta) == '[6♣ ]' + + +def test_popis_karova_desitka(): + """Popis kárové desítky by měl být "[X ♦]".""" + karta = 10, 'Ka', True + assert karty.popis_kartu(karta) == '[X ♦]' + + +def test_popis_pikove_eso(): + """Popis pikového esa by měl být "[A♠ ]".""" + karta = 1, 'Pi', True + assert karty.popis_kartu(karta) == '[A♠ ]' + + +def test_otoc_kralovnu(): + """Kontrola otočení karty, co je na začátku lícem nahoru""" + karta = 12, 'Sr', True + assert karty.otoc_kartu(karta, True) == (12, 'Sr', True) + assert karty.otoc_kartu(karta, False) == (12, 'Sr', False) + + +def test_otoc_eso(): + """Kontrola otočení karty, co je na začátku rubem nahoru""" + karta = 1, 'Pi', False + assert karty.otoc_kartu(karta, True) == (1, 'Pi', True) + assert karty.otoc_kartu(karta, False) == (1, 'Pi', False) + + +# Tohle je testovací vychytávka, kterou zatím neznáme: +# několik podobných testů zadaných jednou funkcí +PRIKLADY = [ + (1, 'Ka', '[A ♦]'), + (2, 'Ka', '[2 ♦]'), + (3, 'Sr', '[3 ♥]'), + (4, 'Sr', '[4 ♥]'), + (5, 'Kr', '[5♣ ]'), + (6, 'Pi', '[6♠ ]'), + (7, 'Ka', '[7 ♦]'), + (8, 'Kr', '[8♣ ]'), + (9, 'Sr', '[9 ♥]'), + (10, 'Kr', '[X♣ ]'), + (11, 'Ka', '[J ♦]'), + (12, 'Sr', '[Q ♥]'), + (13, 'Kr', '[K♣ ]'), +] +@pytest.mark.parametrize(['hodnota', 'barva', 'pozadovany_popis'], PRIKLADY) +def test_popis_hodnoty(hodnota, barva, pozadovany_popis): + """Kontrola popisu karty""" + karta = hodnota, barva, True + assert karty.popis_kartu(karta) == pozadovany_popis diff --git a/lessons/klondike/cards/static/test_klondike.py b/lessons/klondike/cards/static/test_klondike.py new file mode 100644 index 00000000..731d11db --- /dev/null +++ b/lessons/klondike/cards/static/test_klondike.py @@ -0,0 +1,824 @@ +import pytest +import textwrap +import re + +@pytest.mark.level(1) +def test_import_klondike(): + import klondike + + +@pytest.mark.level(10) +def test_import_vytvor_balicek(): + from klondike import vytvor_balicek + + +@pytest.mark.level(11) +def test_vytvor_balicek_52(): + """Balíček by měl obsahovat 52 karet""" + from klondike import vytvor_balicek + assert len(vytvor_balicek()) == 52 + + +@pytest.mark.level(11) +def test_vytvor_balicek_bez_duplikatu(): + """Balíček by neměl obsahovat duplikáty""" + from klondike import vytvor_balicek + balicek = vytvor_balicek() + for karta in balicek: + assert balicek.count(karta) == 1 + + +@pytest.mark.level(12) +@pytest.mark.parametrize('hodnota', range(2, 14)) +def test_vytvor_balicek_pocet_hodnoty(hodnota): + """Balíček by měl obsahovat 4 karty dané hodnoty""" + from klondike import vytvor_balicek + balicek = vytvor_balicek() + pocet = 0 + for hodnota_karty, barva_karty, je_licem_nahoru in balicek: + if hodnota_karty == hodnota: + pocet = pocet + 1 + assert pocet == 4 + + +@pytest.mark.level(12) +@pytest.mark.parametrize('barva', ['Pi', 'Sr', 'Ka', 'Kr']) +def test_vytvor_balicek_pocet_barvy(barva): + """Balíček by měl obsahovat 13 karet dané barvy""" + from klondike import vytvor_balicek + balicek = vytvor_balicek() + pocet = 0 + for hodnota_karty, barva_karty, je_licem_nahoru in balicek: + if barva_karty == barva: + pocet = pocet + 1 + assert pocet == 13 + + +@pytest.mark.level(13) +def test_zamichani_balicku(): + """Každá hra by měla být jiná""" + from klondike import vytvor_balicek + balicek1 = vytvor_balicek() + balicek2 = vytvor_balicek() + + # Je šance 1 z 80658175170943878571660636856403766975289505440883277824000000000000, + # že dva náhodné balíčky budou stejné. + # Nejspíš je pravděpodobnější, že v průběhu testu odejde počítač, + # na kterém test běží, než aby se ty karty zamíchaly stejně. + assert balicek1 != balicek2, 'Karty nejsou zamíchané!' + +@pytest.mark.level(20) +def test_import_popis_popis_balicek(): + from klondike import popis_balicek + + +@pytest.mark.level(21) +def test_popis_balicek(): + from klondike import popis_balicek + karty = [ + (13, 'Pi', True), + (12, 'Sr', True), + (11, 'Ka', True), + (10, 'Kr', False) + ] + assert popis_balicek(karty) == '[K♠ ] [Q ♥] [J ♦] [???]' + + +@pytest.mark.level(22) +def test_popis_prazdny_balicek(): + from klondike import popis_balicek + assert popis_balicek([]) == '' + + +@pytest.mark.level(30) +def test_import_popis_vrchni_kartu(): + from klondike import popis_vrchni_kartu + + +@pytest.mark.level(31) +def test_popis_vrchni_kartu_jedna_karta(): + """Balíček se srdcovou dámou by se měl popsat jako tato karta""" + from klondike import popis_vrchni_kartu + karta = 12, 'Sr', True + assert popis_vrchni_kartu([karta]) == '[Q ♥]' + + +@pytest.mark.level(31) +def test_popis_vrchni_kartu_moc_karet(): + """Balíček se víc kartama by se měl popsat jako vrchní karta""" + from klondike import popis_vrchni_kartu + rubem_nahoru = 1, 'Sr', False + karta = 12, 'Sr', True + balicek = [rubem_nahoru, rubem_nahoru, rubem_nahoru, karta] + assert popis_vrchni_kartu(balicek) == '[Q ♥]' + + +@pytest.mark.level(31) +def test_popis_vrchni_kartu_rubem_nahoru(): + """Balíček s vrchní kartou rubem nahoru by se měl popsat jako [???]""" + from klondike import popis_vrchni_kartu + rubem_nahoru = 1, 'Sr', False + karta = 12, 'Sr', True + balicek = [karta, karta, karta, rubem_nahoru] + assert popis_vrchni_kartu(balicek) == '[???]' + +@pytest.mark.level(32) +def test_popis_vrchni_kartu_prazdneho_balicku(): + """Prázdný balíček se popisuje pomocí [ ]""" + from klondike import popis_vrchni_kartu + assert popis_vrchni_kartu([]) == '[ ]' + + +@pytest.mark.level(40) +def test_import_rozdej_sloupecky(): + from klondike import rozdej_sloupecky + + +@pytest.mark.level(41) +def test_rozdej_sloupecky_7(): + """Rozdaných sloupečků má být 7""" + from klondike import vytvor_balicek, rozdej_sloupecky + balicek = vytvor_balicek() + sloupecky = rozdej_sloupecky(balicek) + assert len(sloupecky) == 7 + + +@pytest.mark.level(41) +def test_rozdej_sloupecky_velikost_balicku(): + """V balíčku by měly chybět karty ze sloupečků""" + from klondike import vytvor_balicek, rozdej_sloupecky + balicek = vytvor_balicek() + sloupecky = rozdej_sloupecky(balicek) + + # Ceklový počet karet ve sloupečcích + velikost_vsech_sloupecku = 0 + for sloupecek in sloupecky: + velikost_vsech_sloupecku = velikost_vsech_sloupecku + len(sloupecek) + + # Kontrola počtu karet v balíčku + assert len(balicek) == 52 - velikost_vsech_sloupecku + + +@pytest.mark.level(41) +def test_rozdej_sloupecky_zvrchu_balicku(): + """Karty by měly být rozdané z konce balíčku""" + from klondike import vytvor_balicek, rozdej_sloupecky + balicek = vytvor_balicek() + kopie_puvodniho_balicku = list(balicek) + sloupecky = rozdej_sloupecky(balicek) + + assert balicek == kopie_puvodniho_balicku[:len(balicek)] + + +@pytest.mark.level(41) +def test_rozdej_sloupecky_nechybi_karty(): + """V balíčku a sloupečcích by měly být všechny karty""" + from klondike import vytvor_balicek, rozdej_sloupecky + from karty import otoc_kartu + balicek = vytvor_balicek() + kopie_puvodniho_balicku = list(balicek) + sloupecky = rozdej_sloupecky(balicek) + + vsechny_karty = list(balicek) + for sloupecek in sloupecky: + for karta in sloupecek: + vsechny_karty.append(otoc_kartu(karta, False)) + + vsechny_karty.sort() + kopie_puvodniho_balicku.sort() + + assert vsechny_karty == kopie_puvodniho_balicku + + +@pytest.mark.level(41) +def test_rozdej_sloupecky_balicek_rubem_nahoru(): + """Po rozdání sloupečků by měl celý balíček být rubem nahoru""" + from klondike import vytvor_balicek, rozdej_sloupecky + balicek = vytvor_balicek() + sloupecky = rozdej_sloupecky(balicek) + + for hodnota, barva, je_licem_nahoru in balicek: + assert not je_licem_nahoru + + +@pytest.mark.level(42) +@pytest.mark.parametrize('cislo_sloupce', range(7)) +def test_rozdej_sloupecky_posledni_licem_nahoru(cislo_sloupce): + """Poslední karta sloupečku je lícem nahoru""" + from klondike import vytvor_balicek, rozdej_sloupecky + balicek = vytvor_balicek() + sloupecky = rozdej_sloupecky(balicek) + posledni_karta = sloupecky[cislo_sloupce][-1] + hodnota, barva, je_licem_nahoru = posledni_karta + assert je_licem_nahoru + + +@pytest.mark.level(42) +@pytest.mark.parametrize('cislo_sloupce', range(7)) +def test_rozdej_sloupecky_ostatni_rubem_nahoru(cislo_sloupce): + """Karty pod první kartou sloupečku jsou rubem nahoru""" + from klondike import vytvor_balicek, rozdej_sloupecky + balicek = vytvor_balicek() + sloupecky = rozdej_sloupecky(balicek) + for karta in sloupecky[cislo_sloupce][:-1]: + hodnota, barva, je_licem_nahoru = karta + assert not je_licem_nahoru + + +@pytest.mark.level(43) +@pytest.mark.parametrize('cislo_sloupce', range(7)) +def test_rozdej_sloupecky_velikost(cislo_sloupce): + """Kontrola velikosti rozdaného sloupečku""" + from klondike import vytvor_balicek, rozdej_sloupecky + balicek = vytvor_balicek() + sloupecky = rozdej_sloupecky(balicek) + assert len(sloupecky[cislo_sloupce]) == cislo_sloupce + 1 + + +@pytest.mark.level(50) +def test_import_vypis_sloupecky(): + from klondike import vypis_sloupecky + + +def check_text(got, expected): + got = re.sub(' +\n', '\n', got) # odstraní mezery z konců řádků + print(got) + assert got.strip() == textwrap.dedent(expected).strip() + + +@pytest.mark.level(51) +def test_vypis_prazdne_sloupecky(capsys): + from klondike import vypis_sloupecky + vypis_sloupecky([[], [], [], [], [], [], [], []]) + out, err = capsys.readouterr() + check_text(out, "") + + +@pytest.mark.level(51) +def test_vypis_sloupecky_jedna_karta_rubem_nahoru(capsys): + from klondike import vypis_sloupecky + vypis_sloupecky([[(1, 'Pi', False)]] * 7) + out, err = capsys.readouterr() + check_text(out, "[???] [???] [???] [???] [???] [???] [???]") + + +@pytest.mark.level(51) +def test_vypis_sloupecky_po_jedne_karte_licem_nahoru(capsys): + from klondike import vypis_sloupecky + vypis_sloupecky([ + [(1, 'Pi', True)], + [(2, 'Sr', True)], + [(3, 'Ka', True)], + [(4, 'Kr', True)], + [(5, 'Pi', True)], + [(6, 'Sr', True)], + [(7, 'Ka', True)], + ]) + out, err = capsys.readouterr() + check_text(out, "[A♠ ] [2 ♥] [3 ♦] [4♣ ] [5♠ ] [6 ♥] [7 ♦]") + + +@pytest.mark.level(52) +def test_vypis_sloupecky_dvou_kartach(capsys): + from klondike import vypis_sloupecky + vypis_sloupecky([ + [(1, 'Pi', True), (7, 'Sr', True)], + [(2, 'Sr', True), (6, 'Ka', True)], + [(3, 'Ka', True), (5, 'Kr', False)], + [(4, 'Kr', False), (4, 'Pi', True)], + [(5, 'Pi', False), (3, 'Sr', True)], + [(6, 'Sr', True), (2, 'Ka', True)], + [(7, 'Ka', True), (1, 'Kr', True)], + ]) + out, err = capsys.readouterr() + check_text(out, """ + [A♠ ] [2 ♥] [3 ♦] [???] [???] [6 ♥] [7 ♦] + [7 ♥] [6 ♦] [???] [4♠ ] [3 ♥] [2 ♦] [A♣ ] + """) + + +@pytest.mark.level(52) +def test_vypis_sloupecky_vice_karet(capsys): + from klondike import vypis_sloupecky + vypis_sloupecky([ + [(1, 'Pi', True)], + [(2, 'Pi', True), (2, 'Sr', True)], + [(3, 'Pi', True), (3, 'Sr', True), (3, 'Ka', True)], + [(4, 'Pi', True), (4, 'Sr', True), (4, 'Ka', False), (4, 'Kr', True)], + [(5, 'Pi', True), (5, 'Sr', True), (5, 'Ka', True)], + [(6, 'Pi', True), (6, 'Sr', True)], + [(7, 'Pi', True)], + ]) + out, err = capsys.readouterr() + check_text(out, """ + [A♠ ] [2♠ ] [3♠ ] [4♠ ] [5♠ ] [6♠ ] [7♠ ] + [2 ♥] [3 ♥] [4 ♥] [5 ♥] [6 ♥] + [3 ♦] [???] [5 ♦] + [4♣ ] + """) + + +@pytest.mark.level(52) +def test_vypis_sloupecky_ruby(capsys): + """Kontrola výpisu sloupečků, kde jsou všechny karty rubem nahoru""" + from klondike import vypis_sloupecky + sloupecky = [ + [(13, 'Pi', False)] * 2, + [(13, 'Pi', False)] * 3, + [(13, 'Pi', False)] * 4, + [(13, 'Pi', False)] * 5, + [(13, 'Pi', False)] * 6, + [(13, 'Pi', False)] * 7, + [(13, 'Pi', False)] * 8, + ] + vypis_sloupecky(sloupecky) + out, err = capsys.readouterr() + check_text(out, """ + [???] [???] [???] [???] [???] [???] [???] + [???] [???] [???] [???] [???] [???] [???] + [???] [???] [???] [???] [???] [???] + [???] [???] [???] [???] [???] + [???] [???] [???] [???] + [???] [???] [???] + [???] [???] + [???] + """) + + +@pytest.mark.level(52) +def test_vypis_sloupecky_zacatek_hry(capsys): + """Kontrola výpisu sloupečků, kde jsou karty i rubem lícem nahoru""" + from klondike import vypis_sloupecky + sloupecky = [ + [(13, 'Pi', False)] * 0 + [(8, 'Kr', True)], + [(13, 'Pi', False)] * 1 + [(9, 'Ka', True)], + [(13, 'Pi', False)] * 2 + [(10, 'Sr', True)], + [(13, 'Pi', False)] * 3 + [(1, 'Ka', True)], + [(13, 'Pi', False)] * 4 + [(4, 'Pi', True)], + [(13, 'Pi', False)] * 5 + [(9, 'Kr', True)], + [(13, 'Pi', False)] * 6 + [(12, 'Sr', True)], + ] + vypis_sloupecky(sloupecky) + out, err = capsys.readouterr() + check_text(out, """ + [8♣ ] [???] [???] [???] [???] [???] [???] + [9 ♦] [???] [???] [???] [???] [???] + [X ♥] [???] [???] [???] [???] + [A ♦] [???] [???] [???] + [4♠ ] [???] [???] + [9♣ ] [???] + [Q ♥] + """) + + +@pytest.mark.level(52) +def test_vypis_sloupecky_rozehrana(capsys): + """Kontrola výpisu sloupečků rozehrané hry""" + from klondike import vypis_sloupecky + sloupecky = [ + [(13, 'Pi', False)] * 1 + [(8, 'Kr', True)], + [(13, 'Pi', False)] * 8 + [(9, 'Ka', True)], + [(13, 'Pi', False)] * 2 + [(10, 'Sr', True), (9, 'Kr', True), (8, 'Ka', True)], + [(13, 'Pi', False)] * 6 + [(3, 'Ka', True)], + [(13, 'Pi', False)] * 1 + [(4, 'Pi', True)], + [(13, 'Pi', False)] * 9 + [(9, 'Kr', True)], + [(13, 'Pi', False)] * 5 + [(12, 'Sr', True), (11, 'Pi', True)], + ] + vypis_sloupecky(sloupecky) + out, err = capsys.readouterr() + check_text(out, """ + [???] [???] [???] [???] [???] [???] [???] + [8♣ ] [???] [???] [???] [4♠ ] [???] [???] + [???] [X ♥] [???] [???] [???] + [???] [9♣ ] [???] [???] [???] + [???] [8 ♦] [???] [???] [???] + [???] [???] [???] [Q ♥] + [???] [3 ♦] [???] [J♠ ] + [???] [???] + [9 ♦] [???] + [9♣ ] + """) + + +@pytest.mark.level(60) +def test_import_presun_kartu(): + from klondike import presun_kartu + + +@pytest.mark.level(61) +def test_presun_kartu_licem_nahoru(): + """Kontrola přesunutí karty, co je na začátku lícem nahoru""" + from klondike import presun_kartu + zdroj = [ + (3, 'Kr', False), + (4, 'Sr', False), + (5, 'Kr', False), + ] + cil = [ + (11, 'Pi', True), + (12, 'Ka', True), + (13, 'Pi', True), + ] + presun_kartu(zdroj, cil, True) + assert zdroj == [ + (3, 'Kr', False), + (4, 'Sr', False), + ] + assert cil == [ + (11, 'Pi', True), + (12, 'Ka', True), + (13, 'Pi', True), + (5, 'Kr', True), + ] + presun_kartu(zdroj, cil, False) + assert zdroj == [ + (3, 'Kr', False), + ] + assert cil == [ + (11, 'Pi', True), + (12, 'Ka', True), + (13, 'Pi', True), + (5, 'Kr', True), + (4, 'Sr', False), + ] + + +@pytest.mark.level(61) +def test_presun_kartu_rubem_nahoru(): + """Kontrola přesunutí karty, co je na začátku rubem nahoru""" + from klondike import presun_kartu + zdroj = [ + (11, 'Pi', True), + (12, 'Ka', True), + (13, 'Pi', True), + ] + cil = [ + (3, 'Kr', False), + (4, 'Sr', False), + (5, 'Kr', False), + ] + presun_kartu(zdroj, cil, True) + assert zdroj == [ + (11, 'Pi', True), + (12, 'Ka', True), + ] + assert cil == [ + (3, 'Kr', False), + (4, 'Sr', False), + (5, 'Kr', False), + (13, 'Pi', True), + ] + presun_kartu(zdroj, cil, False) + assert zdroj == [ + (11, 'Pi', True), + ] + assert cil == [ + (3, 'Kr', False), + (4, 'Sr', False), + (5, 'Kr', False), + (13, 'Pi', True), + (12, 'Ka', False), + ] + + +@pytest.mark.level(70) +def test_import_presun_nekolik_karet(): + from klondike import presun_nekolik_karet + + +@pytest.mark.level(71) +def test_presun_jednu_kartu(): + """Zkontroluje přesunutí jedné karty pomocí presun_nekolik_karet""" + from klondike import presun_nekolik_karet + zdroj = [ + (3, 'Kr', False), + (4, 'Sr', False), + (5, 'Kr', False), + ] + cil = [ + (11, 'Pi', True), + (12, 'Ka', True), + (13, 'Pi', True), + ] + presun_nekolik_karet(zdroj, cil, 1) + assert zdroj == [ + (3, 'Kr', False), + (4, 'Sr', False), + ] + assert cil == [ + (11, 'Pi', True), + (12, 'Ka', True), + (13, 'Pi', True), + (5, 'Kr', False), + ] + + +@pytest.mark.level(71) +def test_presun_dve_karty(): + """Zkontroluje přesunutí dvou karet najednou""" + from klondike import presun_nekolik_karet + zdroj = [ + (3, 'Kr', False), + (4, 'Sr', False), + (5, 'Kr', False), + ] + cil = [ + (11, 'Pi', True), + (12, 'Ka', True), + (13, 'Pi', True), + ] + presun_nekolik_karet(zdroj, cil, 2) + assert zdroj == [ + (3, 'Kr', False), + ] + assert cil == [ + (11, 'Pi', True), + (12, 'Ka', True), + (13, 'Pi', True), + (4, 'Sr', False), + (5, 'Kr', False), + ] + + +@pytest.mark.level(71) +def test_presun_tam_a_zpet(): + """Zkontroluje přesouvání karet tam a zpátky""" + from klondike import presun_nekolik_karet + zdroj = [ + (3, 'Kr', False), + (4, 'Sr', False), + (5, 'Kr', False), + ] + cil = [ + (11, 'Pi', True), + (12, 'Ka', True), + (13, 'Pi', True), + ] + presun_nekolik_karet(zdroj, cil, 1) + assert zdroj == [ + (3, 'Kr', False), + (4, 'Sr', False), + ] + assert cil == [ + (11, 'Pi', True), + (12, 'Ka', True), + (13, 'Pi', True), + (5, 'Kr', False), + ] + presun_nekolik_karet(cil, zdroj, 2) + assert zdroj == [ + (3, 'Kr', False), + (4, 'Sr', False), + (13, 'Pi', True), + (5, 'Kr', False), + ] + assert cil == [ + (11, 'Pi', True), + (12, 'Ka', True), + ] + presun_nekolik_karet(zdroj, cil, 3) + assert zdroj == [ + (3, 'Kr', False), + ] + assert cil == [ + (11, 'Pi', True), + (12, 'Ka', True), + (4, 'Sr', False), + (13, 'Pi', True), + (5, 'Kr', False), + ] + presun_nekolik_karet(cil, zdroj, 4) + assert zdroj == [ + (3, 'Kr', False), + (12, 'Ka', True), + (4, 'Sr', False), + (13, 'Pi', True), + (5, 'Kr', False), + ] + assert cil == [ + (11, 'Pi', True), + ] + presun_nekolik_karet(zdroj, cil, 5) + assert zdroj == [ + ] + assert cil == [ + (11, 'Pi', True), + (3, 'Kr', False), + (12, 'Ka', True), + (4, 'Sr', False), + (13, 'Pi', True), + (5, 'Kr', False), + ] + + +@pytest.mark.level(80) +def test_import_udelej_hru(): + from klondike import udelej_hru + +@pytest.mark.level(81) +def test_udelej_hru_klice(): + """Hra by měl být slovník s klíči A až G a U až Z.""" + from klondike import udelej_hru + hra = udelej_hru() + assert sorted(hra) == list('ABCDEFGUVWXYZ') + + +@pytest.mark.level(81) +@pytest.mark.parametrize('pismenko', 'ABCDEFGUVWXYZ') +def test_pocty_karet(pismenko): + """Počty karet v jednotlivých sloupcích jsou dané.""" + from klondike import udelej_hru + hra = udelej_hru() + + POCTY = { + 'U': 24, + 'V': 0, 'W': 0, 'X': 0, 'Y': 0, 'Z': 0, + 'A': 1, 'B': 2, 'C': 3, 'D': 4, 'E': 5, 'F': 6, 'G': 7, + } + pozadovany_pocet = POCTY[pismenko] + + assert len(hra[pismenko]) == pozadovany_pocet + + +@pytest.mark.level(81) +def test_otoceni_karet_balicku(): + """Karty balíčku by měly být rubem nahoru""" + from klondike import udelej_hru + hra = udelej_hru() + for hodnota, barva, licem_nahoru in hra['U']: + assert not licem_nahoru + + +@pytest.mark.level(81) +@pytest.mark.parametrize('pismenko', 'ABCDEFG') +def test_otoceni_karet_sloupecku(pismenko): + """Karty sloupečků by měly být rubem nahoru, kromě té poslední""" + from klondike import udelej_hru + hra = udelej_hru() + sloupecek = hra[pismenko] + + # Poslední karta + posledni_karta = sloupecek[-1] + hodnota, barva, licem_nahoru = posledni_karta + assert licem_nahoru + + # Ostatní karty + for hodnota, barva, licem_nahoru in sloupecek[:-1]: + assert not licem_nahoru + + +@pytest.mark.level(81) +def test_zamichani_hry(): + """Každá hra by měla být jiná""" + from klondike import udelej_hru + hra1 = udelej_hru() + hra2 = udelej_hru() + + # Je šance 1 z 80658175170943878571660636856403766975289505440883277824000000000000, + # že dvě náhodné hry budou stejné. + # Nejspíš je pravděpodobnější, že v průběhu testu odejde počítač, + # na kterém test běží, než aby se ty karty zamíchaly stejně. + assert hra1 != hra2, 'Karty nejsou zamíchané!' + + +@pytest.mark.level(81) +def test_vsech_karet(): + """Hra by měla obsahovat všech 52 karet, bez duplikátů.""" + from klondike import udelej_hru + hra = udelej_hru() + + # Uděláme seznam dvojic (hodnota, barva), tedy karet s ignorovaným otočením + dvojice_z_hry = [] + for balicek in hra.values(): + for hodnota, barva, licem_nahoru in balicek: + dvojice_z_hry.append((hodnota, barva)) + # Seznam seřadíme -- na pořadí nezáleží + dvojice_z_hry.sort() + + # Uděláme seznam dvojic (hodnota, barva) všech karet, kteŕe ve hře mají být + pozadovane_dvojice = [] + for hodnota in range(1, 14): + for barva in 'Ka', 'Kr', 'Pi', 'Sr': + pozadovane_dvojice.append((hodnota, barva)) + # Tenhle seznam by měl být už seřazený, ale opatrnosti není nikdy dost + pozadovane_dvojice.sort() + + # Ty dva seznamy (ten ze hry a ten z testu) by měly být stejné + assert dvojice_z_hry == pozadovane_dvojice + + +@pytest.mark.level(90) +def test_import_vypis_hru(): + from klondike import vypis_hru + +@pytest.mark.level(91) +def test_ruby(capsys): + """Kontrola výpisu hry, kde jsou všechny karty rubem nahoru""" + from klondike import udelej_hru, vypis_hru + hra = udelej_hru() + hra = { + 'U': [(13, 'Pi', False)], + 'V': [], + 'W': [], + 'X': [], + 'Y': [], + 'Z': [], + 'A': [(13, 'Pi', False)] * 2, + 'B': [(13, 'Pi', False)] * 3, + 'C': [(13, 'Pi', False)] * 4, + 'D': [(13, 'Pi', False)] * 5, + 'E': [(13, 'Pi', False)] * 6, + 'F': [(13, 'Pi', False)] * 7, + 'G': [(13, 'Pi', False)] * 8, + } + vypis_hru(hra) + out, err = capsys.readouterr() + check_text(out, """ + U V W X Y Z + [???] [ ] [ ] [ ] [ ] [ ] + + A B C D E F G + [???] [???] [???] [???] [???] [???] [???] + [???] [???] [???] [???] [???] [???] [???] + [???] [???] [???] [???] [???] [???] + [???] [???] [???] [???] [???] + [???] [???] [???] [???] + [???] [???] [???] + [???] [???] + [???] + """) + + +@pytest.mark.level(91) +def test_zacatek_hry(capsys): + """Kontrola výpisu hry, kde jsou karty i rubem lícem nahoru""" + from klondike import vypis_hru + hra = { + 'U': [(13, 'Pi', False)], + 'V': [(8, 'Kr', True), (13, 'Pi', True)], + 'W': [], + 'X': [], + 'Y': [], + 'Z': [], + 'A': [(13, 'Pi', False)] * 0 + [(8, 'Kr', True)], + 'B': [(13, 'Pi', False)] * 1 + [(9, 'Ka', True)], + 'C': [(13, 'Pi', False)] * 2 + [(10, 'Sr', True)], + 'D': [(13, 'Pi', False)] * 3 + [(1, 'Ka', True)], + 'E': [(13, 'Pi', False)] * 4 + [(4, 'Pi', True)], + 'F': [(13, 'Pi', False)] * 5 + [(9, 'Kr', True)], + 'G': [(13, 'Pi', False)] * 6 + [(12, 'Sr', True)], + } + vypis_hru(hra) + out, err = capsys.readouterr() + check_text(out, """ + U V W X Y Z + [???] [K♠ ] [ ] [ ] [ ] [ ] + + A B C D E F G + [8♣ ] [???] [???] [???] [???] [???] [???] + [9 ♦] [???] [???] [???] [???] [???] + [X ♥] [???] [???] [???] [???] + [A ♦] [???] [???] [???] + [4♠ ] [???] [???] + [9♣ ] [???] + [Q ♥] + """) + + +@pytest.mark.level(91) +def test_rozehrana(capsys): + from klondike import vypis_hru + """Kontrola výpisu rozehrané hry""" + hra = { + 'U': [(13, 'Pi', False)], + 'V': [(8, 'Kr', True), (13, 'Pi', True)], + 'W': [(1, 'Pi', True)], + 'X': [(1, 'Kr', True)], + 'Y': [(1, 'Sr', True)], + 'Z': [(1, 'Ka', True), (2, 'Ka', True)], + 'A': [(13, 'Pi', False)] * 1 + [(8, 'Kr', True)], + 'B': [(13, 'Pi', False)] * 8 + [(9, 'Ka', True)], + 'C': [(13, 'Pi', False)] * 2 + [(10, 'Sr', True), (9, 'Kr', True), (8, 'Ka', True)], + 'D': [(13, 'Pi', False)] * 6 + [(3, 'Ka', True)], + 'E': [(13, 'Pi', False)] * 1 + [(4, 'Pi', True)], + 'F': [(13, 'Pi', False)] * 9 + [(9, 'Kr', True)], + 'G': [(13, 'Pi', False)] * 5 + [(12, 'Sr', True), (11, 'Pi', True)], + } + vypis_hru(hra) + out, err = capsys.readouterr() + check_text(out, """ + U V W X Y Z + [???] [K♠ ] [A♠ ] [A♣ ] [A ♥] [2 ♦] + + A B C D E F G + [???] [???] [???] [???] [???] [???] [???] + [8♣ ] [???] [???] [???] [4♠ ] [???] [???] + [???] [X ♥] [???] [???] [???] + [???] [9♣ ] [???] [???] [???] + [???] [8 ♦] [???] [???] [???] + [???] [???] [???] [Q ♥] + [???] [3 ♦] [???] [J♠ ] + [???] [???] + [9 ♦] [???] + [9♣ ] + """) diff --git a/lessons/klondike/decks/index.md b/lessons/klondike/decks/index.md new file mode 100644 index 00000000..7d5d8683 --- /dev/null +++ b/lessons/klondike/decks/index.md @@ -0,0 +1,223 @@ +# Klondike Solitaire: Balíčky + +Postupně tvoříme hru *Klondike Solitaire*, která bude nakonec fungovat takto: + +* Karty se určitým způsobem *rozdají* do několika balíčků, hromádek nebo + jiných skupin +* Dokud hráč *nevyhrál*: + * Hráč *udělá tah*: podle určitých pravidel přesune karty z jedné hromádky + na druhou + +Pro počítačovou verzi to bude potřeba doplnit o zobrazení stavu hry +a o načítání hráčova tahu: + +* Rozdej karty +* Dokud hráč nevyhrál: + * Zobraz stav hry + * Zeptej se hráče, kam chce hrát + * Je-li to možné: + * Proveď tah + * Jinak: + * Vynadej hráči, že daný tah nedává smysl +* Pogratuluj hráči + +(Hráč může i prohrát, ale na to může přijít sám a hru ukončit.) + +Minule jsme počítač naučil{{gnd('i', 'y', both='i')}} co to je *karta* +a jak vytvořit zamíchaný *balíček*. +Pojďme se konečně vrhnout na první krok výše: rozdávání. + + +## Rozdání sloupečků + +Karty se určitým způsobem *rozdají* do několika balíčků, hromádek nebo +jiných skupin. +Pro přehlednost si tyto skupiny označíme písmenky: + +* Dobírací balíčky `U`, `V`, ze kterých se berou karty. +* Cílové hromádky `W`-`Z`, kam se dávají seřazené karty. Cíl hry je do těchto + hromádek dát všechny karty. +* 7 sloupečků `A`-`G`, kde hráč může s kartami manipulovat. + +Prvotní rozdání karet spočívá v tom, že rozdáš karty do 7 sloupečků. +Nerozdané karty zůstanou v balíčku `U`; ostatní místa na karty budou prázdná: + +{{ figure(img=static('game.png'), alt="Ukázka sloupečků") }} + +```plain + U V W X Y Z + [???] [ ] [ ] [ ] [ ] [ ] + + A B C D E F G + [3♣ ] [???] [???] [???] [???] [???] [???] + [5 ♥] [???] [???] [???] [???] [???] + [6♣ ] [???] [???] [???] [???] + [5♠ ] [???] [???] [???] + [Q ♥] [???] [???] + [4♠ ] [???] + [3 ♦] +``` + +V N-tém sloupečku (počítáno od nuly) je N +karet rubem nahoru plus jedna karta lícem nahoru. +Karty do sloupečků se z balíčku rozdávají postupně: vždy se lízne +vrchní (poslední) karta z balíčku a dá se na konec sloupečku. + + +Napiš následující funkci: + +```python +def rozdej_sloupecky(balicek): + """Rozdá z daného balíčku 7 "sloupečků" -- seznamů karet + + Karty ve sloupečcích jsou odstraněny z balíčku. + Vrátí všechny sloupečky -- tedy seznam (nebo n-tici) sedmi seznamů. + """ +``` + +Například: + +```pycon +>>> balicek = priprav_balicek() +>>> sloupecky = rozdej_sloupecky(balicek) +>>> popis_seznam_karet(sloupecky[0]) +[3♣ ] +>>> popis_seznam_karet(sloupecky[1]) +[???] [5 ♥] +>>> popis_seznam_karet(sloupecky[2]) +[???] [???] [6♣ ] +>>> popis_seznam_karet(sloupecky[6]) +[???] [???] [???] [???] [???] [???] [3 ♦] +>>> len(balicek) # Z balíčku zmizely karty, které jsou ve sloupečcích +24 +``` + +Jak tahle funkce funguje? + +* Vytvoří prázdný seznam sloupečků +* Sedmkrat (pro N od 0 do 6): + * Vytvoří prázdný sloupeček (seznam) + * N-krát za sebou: + * „Lízne“ (`pop`) kartu zvrchu balíčku + * Dá líznutou kartu na vršek sloupečku (`append`) + * „Lízne“ (`pop`) kartu zvrchu balíčku + * Líznutou kartu otočí lícem nahoru (`otoc_kartu`) + a dá vršek sloupečku (`append`) + * Hotový sloupeček přidá do seznamu sloupečků +* Výsledné sloupečky vrátí + +Pro ověření spusť testy: + +* level 40: Funkce existuje +* level 41: Funkce vrací seznam sedmi seznamů +* level 42: + * V každém sloupečku je aspoň jedna karta + * Poslední karta je lícem nahoru +* level 43: V každém sloupečku je správný počet karet rubem nahoru + + +## Vypsání sloupečků + +Vzpomínáš si na základní schéma hry? + +* Rozdej karty +* Dokud hráč nevyhrál: + * Zobraz stav hry + * Zeptej se hráče, kam chce hrát + * Je-li to možné: + * Proveď tah + * Jinak: + * Vynadej hráči, že daný tah nedává smysl +* Pogratuluj hráči + +Rozdání balíčku a sloupečků už víceméně máš! +Pro teď přeskočíme zjišťování, jestli hráč vyhrál, a koukneme se na zobrazení +stavu hry. + +Například, pokud jsou sloupečky tyto: + +```python +sloupecky = [ + [(1, 'Pi', True), (7, 'Sr', True)], + [(2, 'Sr', True), (6, 'Ka', True)], + [(3, 'Ka', True), (5, 'Kr', False)], + [(4, 'Kr', False), (4, 'Pi', True)], + [(5, 'Pi', False), (3, 'Sr', True)], + [(6, 'Sr', True), (2, 'Ka', True)], + [(7, 'Ka', True), (1, 'Kr', True), (10, 'Ka', True)], +] +``` + +… můžeš je vypsat jednotlivě: + + +```pycon +>>> for sloupecek in sloupecky: +>>> print(popis_seznam_karet(sloupecek)) +[A♠ ] [7 ♥] +[2 ♥] [6 ♦] +[3 ♦] [???] +[???] [4♠ ] +[???] [3 ♥] +[6 ♥] [2 ♦] +[7 ♦] [A♣ ] [X ♦] +``` + +To ale není to, co chceme vypsat ve hře: tam se karty v jednom sloupečku +ukazují pod sebou. + +Budeš potřebovat na prvním řádku ukázat první karty ze všech sloupečků, +na druhém řádku druhé karty ze všech sloupečků, na třetím třetí, atd. +Pro příklad výše by tedy mělo vyjít: + + +```plain +[A♠ ] [2 ♥] [3 ♦] [???] [???] [6 ♥] [7 ♦] +[7 ♥] [6 ♦] [???] [4♠ ] [3 ♥] [2 ♦] [A♣ ] + [X ♦] +``` + +Znáš funkci, která vezme několik seznamů, a dá ti k dispozici napřed první +prvky těch seznamů, potom druhé, a tak dál? +Zkus ji použít! + +Pozor, bude tu potřeba pořádně se zamyslet. + +```python +def vypis_sloupecky(sloupecky): + """Vypíše sloupečky textově. + + Tato funkce je jen pro zobrazení, používá proto přímo funkci print() + a nic nevrací. + """ +``` + +* level 50: Funkce existuje +* level 51: Funkce vypisuje karty ze věch sloupečků +* level 52: Funkce funguje, když jsou sloupečky nestejně dlouhé. (Na prázdné místo patří 5 mezer.) + + +## Práce se sloupečky + +Aby sis v budoucnu ušetřil{{a}} práci, a aby sis procvičila seznamy, +zkus teď napsat dvě funkce, které přesunují karty mezi balíčky. + +Použij na to metody seznamů (`append`, `extend`, `pop`, příkaz `del`) +a pomocné funkce, které už máš (`otoc_kartu`). + +```python +def presun_kartu(sloupec_odkud, sloupec_kam, pozadovane_otoceni): + """Přesune vrchní kartu ze sloupce "odkud" do sloupce "kam". + Karta bude otocena lícem nebo rubem nahoru podle "pozadovane_otoceni". + """ + +def presun_nekolik_karet(sloupec_odkud, sloupec_kam, pocet): + """Přesune "pocet" vrchních karet ze sloupce "odkud" do sloupce "kam". + Karty se přitom neotáčí. + """ +``` + +* level 60: Funkce `presun_kartu` existuje +* level 61: Funkce `presun_kartu` funguje dle zadání +* level 70: Funkce `presun_nekolik_karet` existuje +* level 71: Funkce `presun_nekolik_karet` funguje dle zadání diff --git a/lessons/klondike/decks/info.yml b/lessons/klondike/decks/info.yml new file mode 100644 index 00000000..58f5b2c3 --- /dev/null +++ b/lessons/klondike/decks/info.yml @@ -0,0 +1,4 @@ +title: "Klondike: Balíčky" +style: md +attribution: Pro PyLadies Brno napsal Petr Viktorin, 2014-2019. +license: cc-by-sa-40 diff --git a/lessons/klondike/decks/static/game.png b/lessons/klondike/decks/static/game.png new file mode 100644 index 00000000..1d171a3c Binary files /dev/null and b/lessons/klondike/decks/static/game.png differ diff --git a/lessons/klondike/decks/static/klondike.png b/lessons/klondike/decks/static/klondike.png new file mode 100644 index 00000000..61d321e4 Binary files /dev/null and b/lessons/klondike/decks/static/klondike.png differ diff --git a/lessons/klondike/game/index.md b/lessons/klondike/game/index.md new file mode 100644 index 00000000..148f96ad --- /dev/null +++ b/lessons/klondike/game/index.md @@ -0,0 +1,279 @@ +# Klondike Solitaire: Hra + +Klondike Solitaire zbývá dát konečně dohromady kousky, které jsme +v několika posledních lekcích připravovali! + +V těchto materiálech najdeš hotové funkce, které je dobré si prohlédnout +a porozumět jim, ale pak si je můžeš zkopírovat do svého kódu. +Velké procvičení seznamů a slovníků přijde na konci. + +## Hra + +Vzpomínáš si na schéma hry? + +* Rozdej balíček a sloupečky karet +* Dokud hráč nevyhrál: + * Zobraz stav hry + * Zeptej se hráče, odkud a kam chce hrát + * Je-li to možné: + * Proveď tah + * Jinak: + * Vynadej hráči, že daný tah nedává smysl +* Pogratuluj hráči + +V Pythonu to bude vypadat následovně. +Program si ulož do modulu `hra.py`: + +```python +from klondike import udelej_hru, vypis_hru, presun_kartu, presun_nekolik_karet + +print() + +hra = udelej_hru() + +while not hrac_vyhral(hra): + vypis_hru(hra) + odkud, kam = nacti_tah() + try: + udelej_tah(hra, odkud, kam) + except ValueError as e: + print('Něco je špatně:', e) + +vypis_hru(hra) +print('Gratuluji!') +``` + +K tomu, abys doplnila funkce do této hry, budeš potřebovat namodelovat +onu `hru`. +Ta se skládá z několika balíčků/sloupečků, tedy seznamů karet. +Ve výpisu butou pojmenované A-Z: + +{{ figure(img=static('game.png'), alt="Ukázka sloupečků") }} + +* `U` je dobírací balíček, ze kterého se doplňuje `V`. +* `V` je balíček, ze kterého můžeš brát karty +* `W-Z` jsou cílové hromádky. Cílem hry je na ně přemístit všechny + karty. +* `A-G` jsou sloupečky, kde se karty dají přeskládávat. + +Těchto 13 pojmenovaných seznamů reprezentuje celý stav rozehrané hry. +Hru proto budeme reprezentovat slovníkem, kde klíče budou písmenka +a hodnoty pak jednotlivé seznamy. + +Následující funkce takovou hru vytvoří: + +```python +def udelej_hru(): + """Vrátí slovník reprezentující novou hru. + """ + balicek = vytvor_balicek() + + hra = { + 'U': balicek, + } + + # V-Z začínají jako prázdné seznamy + for pismenko in 'VWXYZ': + hra[pismenko] = [] + + # A-G jsou sloupečky + for pismenko, sloupec in zip('ABCDEFG', rozdej_sloupecky(balicek)): + hra[pismenko] = sloupec + + return hra +``` + +Další funkce, `vypis_hru`, hru vypíše do konzole pomocí `print`. +Výsledek bude něco jako: + +```plain + U V W X Y Z + [???] [ ] [ ] [ ] [ ] [ ] + + A B C D E F G + [3♣ ] [???] [???] [???] [???] [???] [???] + [5 ♥] [???] [???] [???] [???] [???] + [6♣ ] [???] [???] [???] [???] + [5♠ ] [???] [???] [???] + [Q ♥] [???] [???] + [4♠ ] [???] + [3 ♦] +``` + +V téhle funkci není nic moc objevného a testům záleží na každé mezeře, +takže si ji určitě zkopíruj: + +```python +def vypis_hru(hra): + """Vypíše hru textově. + + Tato funkce je jen pro zobrazení, používá proto přímo funkci print() + a nic nevrací. + """ + print() + print(' U V W X Y Z') + print('{} {} {} {} {} {}'.format( + popis_vrchni_kartu(hra['U']), + popis_vrchni_kartu(hra['V']), + popis_vrchni_kartu(hra['W']), + popis_vrchni_kartu(hra['X']), + popis_vrchni_kartu(hra['Y']), + popis_vrchni_kartu(hra['Z']), + )) + print() + print(' A B C D E F G') + vypis_sloupecky([ + hra['A'], hra['B'], hra['C'], hra['D'], hra['E'], hra['F'], hra['G'] + ]) + print() +``` + +Pro kontrolu můžeš pustit testy: + +* Level 70: Funkce `udelej_hru` existuje +* Level 71: Funkce `udelej_hru` funguje dle zadání +* Level 80: Funkce `vypis_hru` existuje +* Level 81: Funkce `vypis_hru` funguje dle zadání + + +## Načtení tahu + +Hra se bude ovládat zadáním dvou jmen balíčku: odkud a kam hráč chce kartu +přesunout. + +Tahle funkce není součást logiky hry. Dej ji do `hra.py`, hned za `import`. + +```python +def nacti_tah(): + while True: + tah = input('Tah? ') + try: + jmeno_zdroje, jmeno_cile = tah.upper() + except ValueError: + print('Tah zadávej jako dvě písmenka, např. UV') + else: + return jmeno_zdroje, jmeno_cile +``` + +## Zástupné funkce + +K úplné hře nám chybí ještě samotná logika hry: `hrac_vyhral` a `udelej_tah`. + +Aby ti hra aspoň trochu fungovala, vytvoř si zástupné funkce, +které nic nekontrolují a nenechají tě vyhrát. +Dej ji do `hra.py`, opět hned za `import`: + +```python +def hrac_vyhral(hra): + """Vrací True, pokud je hra vyhraná. + """ + return False + +def udelej_tah(hra, jmeno_odkud, jmeno_kam): + """Udělá tah z jednoho místa na druhé. + + Místa jsou označovány velkými písmeny (např. 'A', 'V' nebo 'X'). + + Není-li tah možný, vyhodí ValueError s popisem problému. + """ + presun_kartu(hra[jmeno_odkud], hra[jmeno_kam], True) +``` + +Obě bude ještě potřeba upravit, ale teď už si můžeš hru víceméně zahrát! +Zkus si to! + + +## Jiné rozhraní + +Celý tento projekt píšeš ve funkcích s daným jménem a s daným počtem a významem +argumentů. +To má dvě výhody. + +První z nich je testování: připravené testy importují tvé funkce a zkouší je, +takže si můžeš být jist{{a}}, že fungují. + +Druhá je zajímavější: máš-li logiku hry, funkce `udelej_hru` `udelej_tah` +a `hrac_vyhral`, napsané podle specifikací, může je použít i jakýkoli jiný +program – ne jen ten, který jsi napsal{{a}} ty. + +Jeden takový si můžeš vyzkoušet: + +* Nainstaluj si do virtuálního prostředí knihovnu `pyglet`, která umí ovládat + grafická okýnka: + + ```console + (venv)$ python -m pip install pyglet + ``` + +* Stáhni si do aktuálního adresáře soubory [ui.py] a [cards.png]. + + [ui.py]: {{ static('ui.py') }} + [cards.png]: {{ static('cards.png') }} + +* Hru spusť pomocí: + + ```console + (venv)$ python ui.py + ``` + +Hra považuje chyby `ValueError` za chyby uživatele, tedy tahy proti pravidlům. +Zobrazí je v terminálu a v titulku okýnka. +Ostatní chyby by ve správném programu neměly nastat; objeví se jako normální +chybové hlášky na terminálu. + +*Obrázky karet jsou z [Board Game Pack](https://kenney.nl/assets/boardgame-pack) +studia [kenney.nl](https://kenney.nl).* + + +## Logika hry + +Zbývá doplnit „pravidla hry“ do dvou funkcí, `hrac_vyhral` a `udelej_tah`. +To už bude na tobě. + +### hrac_vyhral + +Hráč vyhrál, pokud jsou všechny karty na cílových hromádkách `W`-`Z`. + +### udelej_tah + +Když tah není podle pravidel, funkce `udelej_tah` vyhodí `ValueError`. + +Možné tahy: +* `U`→`V`: + * V balíčku `U` musí něco být + * Přesouvá se jedna karta; otočí se lícem nahoru +* `V`→`U`: + * V balíčku U nesmí být nic + * Přesouvají se všechny karty, seřazené v opačném pořadí; + otočí se rubem nahoru (tj. volej dokola + `presun_kartu(hra['V'], hra['U'], False)` dokud ve V něco je) +* Balíček `V` nebo sloupeček `A`-`G` (zdroj) → hromádka `W`-`Z`: + * Přesouvá se jedna karta + * Je-li cílová hromádka prázdná: + * Musí to být eso + * Jinak: + * Přesouvaná karta musí mít stejnou barvu jako vrchní karta cílové hromádky + * Přesouvaná karta musí být o 1 vyšší než vrchní karta cílové hromádky + * Je-li zdroj po přesunu neprázdný, jeho vrchní karta se otočí lícem nahoru +* Balíček `V` → „cílový“ sloupeček `A`-`G` + * Přesouvá se jedna karta + * Přesouvaná karta musí pasovat\*⁾ na cílový sloupeček +* „Zdrojový“ sloupeček `A`-`G` → „cílový“ sloupeček `A`-`G` + * Přesouvá se několik karet + * (zkontroluj všechny možnosti: 1 až počet karet ve zdrojovém sloupečku; + vždy je max. jedna správná možnost) + * Všechny přesouvané karty musí být otočené lícem nahoru + * První z přesouvaných karet musí pasovat\*⁾ na cílový sloupeček +* Cíl `W`-`Z` → sloupeček `A`-`G` (nepovinné – jen v některých variantách hry) + * Přesouvá se jedna karta + * Přesouvaná karta musí pasovat\*⁾ na cílový sloupeček + +\*⁾ Kdy přesouvaná karta pasuje na sloupeček? +* Je-li sloupeček prázdný: + * Karta musí být král +* Jinak: + * Barva přesouvané karty musí být opačná než barva vrchní karty sloupečku, tedy: + * Červená (♥ nebo ♦) jde dát jen na černou (♠ nebo ♣) + * Černá (♠ nebo ♣) jde dát jen na červenou (♥ nebo ♦) + * A zároveň musí hodnota přesouvané karty být o 1 nižší než hodnota vrchní + karty sloupečku. diff --git a/lessons/klondike/game/info.yml b/lessons/klondike/game/info.yml new file mode 100644 index 00000000..15599682 --- /dev/null +++ b/lessons/klondike/game/info.yml @@ -0,0 +1,4 @@ +title: "Klondike: Hra" +style: md +attribution: Pro PyLadies Brno napsal Petr Viktorin, 2014-2019. +license: cc-by-sa-40 diff --git a/lessons/klondike/game/static/cards.png b/lessons/klondike/game/static/cards.png new file mode 100644 index 00000000..832778da Binary files /dev/null and b/lessons/klondike/game/static/cards.png differ diff --git a/lessons/klondike/game/static/game.png b/lessons/klondike/game/static/game.png new file mode 100644 index 00000000..1d171a3c Binary files /dev/null and b/lessons/klondike/game/static/game.png differ diff --git a/lessons/klondike/game/static/klondike.png b/lessons/klondike/game/static/klondike.png new file mode 100644 index 00000000..61d321e4 Binary files /dev/null and b/lessons/klondike/game/static/klondike.png differ diff --git a/lessons/klondike/game/static/ui.py b/lessons/klondike/game/static/ui.py new file mode 100644 index 00000000..7be6ce53 --- /dev/null +++ b/lessons/klondike/game/static/ui.py @@ -0,0 +1,176 @@ +from pathlib import Path +import traceback + +import pyglet + +from klondike import udelej_hru, udelej_tah + + +WINDOW_CAPTION = 'Klondike Solitaire' + +SUIT_NAMES = 'Kr', 'Ka', 'Pi', 'Sr' + +KEYS = { + pyglet.window.key.A: 'A', + pyglet.window.key.B: 'B', + pyglet.window.key.C: 'C', + pyglet.window.key.D: 'D', + pyglet.window.key.E: 'E', + pyglet.window.key.F: 'F', + pyglet.window.key.G: 'G', + pyglet.window.key.U: 'U', + pyglet.window.key.V: 'V', + pyglet.window.key.W: 'W', + pyglet.window.key.X: 'X', + pyglet.window.key.Y: 'Y', + pyglet.window.key.Z: 'Z', +} + + +image = pyglet.image.load('cards.png') +card_width = image.width // 14 +card_height = image.height // 4 + +card_pictures = {} +for suit_number, suit_name in enumerate(SUIT_NAMES): + for value in range(1, 14): + card_pictures[value, suit_name] = image.get_region( + card_width * value, card_height * suit_number, + card_width, card_height, + ) + +card_back_picture = image.get_region(0, card_height, card_width, card_height) +empty_slot_picture = image.get_region(0, 0, card_width, card_height) + +label = pyglet.text.Label('x', color=(0, 200, 100, 255), + anchor_x='center', anchor_y='center') + +window = pyglet.window.Window(resizable=True, caption=WINDOW_CAPTION) +press_queue = [] + + +def get_dimensions(): + card_width = window.width / (7*6+1) * 5 + card_height = card_width * 19/14 + margin_x = card_width / 5 + margin_y = margin_x * 2 + offset_y = margin_x + return card_width, card_height, margin_x, margin_y, offset_y + + +def draw_card(card, x, y, x_offset=0, y_offset=0, active=False): + card_width, card_height, margin_x, margin_y, offset_y = get_dimensions() + + if card == None: + pyglet.gl.glColor4f(0.5, 0.5, 0.5, 0.5) + picture = empty_slot_picture + else: + if active: + pyglet.gl.glColor4f(0.75, 0.75, 1, 1) + else: + pyglet.gl.glColor4f(1, 1, 1, 1) + value, suit, is_face_up = card + if is_face_up: + picture = card_pictures[value, suit] + else: + picture = card_back_picture + + picture.blit( + margin_x + (card_width + margin_x) * x + (x_offset * margin_x / 60), + window.height - (margin_y + card_height) * (y+1) - offset_y * y_offset, + width=card_width, height=card_height) + + +def draw_label(text, x, y, active): + card_width, card_height, margin_x, margin_y, offset_y = get_dimensions() + + label.x = x * (card_width + margin_x) + margin_x + card_width / 2 + label.y = window.height - y * (card_height + margin_y) - margin_y / 2 + label.text = text + if active: + label.color = 200, 200, 255, 255 + else: + label.color = 0, 200, 100, 255 + label.draw() + + +def draw_deck(letter, deck, x, y, x_offset=0, y_offset=0): + active = (letter in press_queue) + draw_label(letter, x, y, active) + draw_card(None, x, y) + for i, card in enumerate(deck): + draw_card(card, x, y, x_offset*i, y_offset*i, active) + + +@window.event +def on_draw(): + # Enable transparency for images + pyglet.gl.glEnable(pyglet.gl.GL_BLEND) + pyglet.gl.glBlendFunc(pyglet.gl.GL_SRC_ALPHA, pyglet.gl.GL_ONE_MINUS_SRC_ALPHA) + + # Green background + pyglet.gl.glClearColor(0, 0.25, 0.05, 1) + window.clear() + + # Get dimensions + card_width, card_height, margin_x, margin_y, offset_y = get_dimensions() + label.font_size = margin_y / 2 + + # Draw all the cards in the various decks + for x, letter in enumerate('UV'): + draw_deck(letter, game[letter], x, 0, x_offset=1) + + for x, letter in enumerate('WXYZ'): + draw_deck(letter, game[letter], x + 3, 0, x_offset=1) + + for x, letter in enumerate('ABCDEFG'): + draw_deck(letter, game[letter], x, 1, y_offset=1) + + +@window.event +def on_key_press(symbol, mod): + if symbol in KEYS: + press_queue.append(KEYS[symbol]) + handle_press_queue() + + +@window.event +def on_mouse_press(x, y, symbol, mod): + card_width, card_height, margin_x, margin_y, offset_y = get_dimensions() + if y > window.height - card_height - margin_y: + deck_names = 'UV WXYZ' + else: + deck_names = 'ABCDEFG' + deck_name = deck_names[int(x - margin_x/2) // int(card_width + margin_x)] + if deck_name.strip(): + press_queue.append(deck_name) + handle_press_queue() + + +def handle_press_queue(): + if press_queue == ['U']: + press_queue.append('V') + if len(press_queue) >= 2: + source = press_queue[0] + destination = press_queue[1] + press_queue.clear() + + try: + result = udelej_tah(game, source, destination) + except ValueError as e: + # Print *just* the error message + msg = f'{source}→{destination}: {e}' + window.set_caption(msg) + print(msg) + except Exception: + # Print the error message, but ignore the error (so the + # game can continue) + traceback.print_exc() + else: + print(f'{source}→{destination}: {result}') + window.set_caption(WINDOW_CAPTION) + + +game = udelej_hru() + +pyglet.app.run()