diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 02ec9327..31eec6f0 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -28,18 +28,16 @@ jobs:
poetry install -E ghp-compiled
- name: Compile the courses
run: |
- for slug in pyladies mi-pyt meta lessons; do
- poetry run python -m naucse_render compile _compiled/$slug --slug $slug \
+ poetry run python -m naucse_render compile _compiled \
--edit-repo-url https://github.com/${{ github.repository }} \
- --edit-repo-branch main
- done
- - if: ${{ github.ref == 'refs/heads/main' }}
+ --edit-repo-branch ${{ github.ref_name }}
+ - if: ${{ startsWith(github.ref, 'refs/heads/') }}
name: Publish compiled courses
run: |
- git fetch origin compiled || :
- poetry run python -m ghp_import -m "Compiled" -b compiled --push _compiled/
+ git fetch origin compiled/${{ github.ref_name }} || :
+ poetry run python -m ghp_import -m "Compiled" -b compiled/${{ github.ref_name }} --push _compiled/
curl -H 'Content-Type: application/json' \
- --data '{"repository": "https://github.com/${{ github.repository }}", "branch": "compiled"}' \
+ --data '{"repository": "https://github.com/${{ github.repository }}", "branch": "compiled/${{ github.ref_name }}"}' \
https://hooks.nauc.se/trigger
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/courses/meta.yml b/courses/meta.yml
deleted file mode 100644
index d73fee7b..00000000
--- a/courses/meta.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-title: Jak přidat kurz na Nauč se Python
-description: Návod na přidání kurzu na Nauč se Python
-long_description: |
-
- V tomto meta-kurzu je kompletní návod na přidávání kurzů na Nauč se Python.
-
- Kurz je určený pro organizátory a k přidání kurzu jsou potřeba jen základní znalosti práce
- s gitem a GitHubem.
-canonical: true
-meta: true
-plan:
-- title: Přidání kurzu
- slug: adding-a-run
- materials:
- - lesson: meta/local-run
- - lesson: meta/submitting-a-run
-
-extra_lessons:
-- git/install
-- git/branching
-- git/git-collaboration-2in1
-- beginners/install
-- beginners/cmdline
-- beginners/venv-setup
-- intro/notebook
-- fast-track/yaml
diff --git a/courses/mi-pyt.yml b/courses/mi-pyt.yml
deleted file mode 100644
index 136b156a..00000000
--- a/courses/mi-pyt.yml
+++ /dev/null
@@ -1,88 +0,0 @@
-title: MI-PYT
-description: Základy již znáte a chcete se dozvědět o dalších možnostech využití Pythonu.
-long_description: |
- Díváte se na materiály předmětu MI-PYT (Pokročilý Python) na FIT ČVUT.
- Materiály jsou dostupné v dvojí podobě, interně pro studenty FITu
- na *Course Pages* a pro všechny veřejně zde.
-
- Tento kurz vzniká pod záštitou firmy Red Hat Czech, s.r.o.
-
-canonical: true
-vars:
- coach-present: false
- mi-pyt: true
-plan:
-- title: Weboví klienti & CLI – Requests a Click
- slug: requests-click
- materials:
- - lesson: fast-track/install
- - lesson: intro/requests
- - lesson: intro/click
-
-- title: Webové aplikace – Flask
- slug: flask
- materials:
- - lesson: intro/flask
- - lesson: intro/deployment
-
-- title: Moduly
- slug: distribution
- materials:
- - lesson: intro/distribution
-
-- title: Testování
- slug: testing
- materials:
- - lesson: intro/testing
-
-- title: Dokumentace – Sphinx
- slug: docs
- materials:
- - lesson: intro/docs
-
-- title: NumPy
- slug: numpy
- materials:
- - lesson: intro/notebook
- - lesson: intro/numpy
- - title: Tahák na NumPy
- url: https://pyvec.github.io/cheatsheets/numpy/numpy-cs.pdf
- type: cheatsheet
-
-- title: Analýza dat – Pandas
- slug: pandas
- materials:
- - lesson: intro/pandas
-
-- title: Cython
- slug: cython
- materials:
- - lesson: intro/cython
-
-- title: GUI – PyQT
- slug: qt
- materials:
- - lesson: intro/pyqt
-
-- title: Generátory a AsyncIO
- slug: async
- materials:
- - lesson: advanced/generators
- - lesson: intro/async
-
-- title: Magie
- slug: magic
- materials:
- - lesson: intro/magic
-
-- title: MicroPython
- slug: upy
- materials:
- - lesson: intro/micropython
-
-extra_lessons:
-- beginners/install
-- beginners/cmdline
-- beginners/testing
-- fast-track/http
-- git/git-collaboration-2in1
diff --git a/courses/pyladies.yml b/courses/pyladies.yml
deleted file mode 100644
index 4e037f57..00000000
--- a/courses/pyladies.yml
+++ /dev/null
@@ -1,152 +0,0 @@
-title: Začátečnický kurz
-description: Naučte se Python vážně od začátku. Žádné předchozí znalosti nejsou potřeba.
-long_description: |
-
- Zde najdeš materiály, které se používají na začátečnických kurzech PyLadies
- v Praze, Brně a Ostravě.
-
- 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,
- Windows i macOS.
-canonical: true
-vars:
- coach-present: false
-plan:
-- title: Instalace
- slug: install
- materials:
- - lesson: beginners/cmdline
- - lesson: beginners/install
- - lesson: beginners/venv-setup
- - lesson: beginners/first-steps
- - lesson: beginners/install-editor
- - lesson: git/install
- - title: Tahák na klávesnici (PDF)
- url: https://pyvec.github.io/cheatsheets/keyboard/keyboard-cs.pdf
- type: cheatsheet
-
-- title: První program
- slug: hello
- materials:
- - lesson: beginners/hello-world
- - lesson: beginners/print
- - lesson: beginners/variables
- - lesson: beginners/comparisons
- - lesson: beginners/and-or
-
-- title: Cykly
- slug: loops
- materials:
- - lesson: beginners/expressions
- - lesson: beginners/functions
- - lesson: beginners/basic-functions
- - lesson: intro/turtle
- - lesson: beginners/while
- - lesson: beginners/reassignment
- - title: Tahák s užitečnými funkcemi
- url: https://pyvec.github.io/cheatsheets/basic-functions/basic-functions-cs.pdf
- type: cheatsheet
-
-- title: Správa zdrojového kódu
- slug: git
- materials:
- - lesson: git/basics
- - lesson: git/branching
- - title: Gitový tahák
- url: https://pyvec.github.io/cheatsheets/basic-git/basic-git-cs.pdf
- type: cheatsheet
-
-- title: Řetězce
- slug: def-str
- materials:
- - lesson: beginners/str
- - lesson: beginners/str-index-slice
- - lesson: beginners/str-methods
- - lesson: beginners/fstring
- - title: Řetězcový tahák
- url: https://pyvec.github.io/cheatsheets/strings/strings-cs.pdf
- type: cheatsheet
-
-- title: Definice funkcí
- slug: def
- materials:
- - lesson: beginners/def
- - lesson: beginners/nested-traceback
- - lesson: beginners/local-variables
-
-- title: Testování
- slug: tests
- materials:
- - lesson: beginners/exceptions
- - lesson: beginners/modules
- - lesson: beginners/testing
- - lesson: beginners/circular-imports
- - title: Výjimkový tahák
- url: https://pyvec.github.io/cheatsheets/exceptions/exceptions-cs.pdf
- type: cheatsheet
-
-- title: Spolupráce a Open-Source
- slug: foss
- materials:
- - lesson: git/collaboration
- - lesson: git/ignoring
- - title: Gitový tahák
- url: https://pyvec.github.io/cheatsheets/basic-git/basic-git-cs.pdf
- type: cheatsheet
-
-- title: Seznamy
- slug: list
- materials:
- - lesson: beginners/list
- - lesson: beginners/tuple
- - lesson: beginners/nested-list
- - title: Tahák na seznamy
- url: https://pyvec.github.io/cheatsheets/lists/lists-cs.pdf
- type: cheatsheet
-
-- title: Sekvence a soubory
- slug: seq
- materials:
- - lesson: beginners/range
- - lesson: beginners/zip-enumerate
- - lesson: beginners/files
-
-- title: Grafika
- slug: pyglet
- materials:
- - lesson: intro/pyglet
- - lesson: projects/pong
- - title: Kód celé hry Pong
- type: link
- url: http://pyladies.cz/v1/s012-pyglet/pong.py
- - title: Tahák na Pyglet
- url: https://pyvec.github.io/cheatsheets/pyglet/pyglet-basics-cs.pdf
- type: cheatsheet
-
-- title: Slovníky
- slug: dict
- materials:
- - lesson: beginners/dict
- - lesson: intro/json
- - lesson: projects/github-api
- - lesson: beginners/dict-with-list-values
- - title: Slovníkový tahák
- url: https://pyvec.github.io/cheatsheets/dicts/dicts-cs.pdf
- type: cheatsheet
-
-- title: Třídy
- slug: class
- materials:
- - lesson: beginners/class
- - lesson: beginners/inheritance
-
-- title: Závěrečný projekt
- slug: asteroids
- materials:
- - lesson: projects/asteroids
- - title: Množinový tahák
- url: https://pyvec.github.io/cheatsheets/sets/sets-cs.pdf
- type: cheatsheet
- - title: Tahák na geometrii a fyziku 2D her
- url: https://pyvec.github.io/cheatsheets/game-physics/game-physics-cs.pdf
- type: cheatsheet
diff --git a/courses/tuesday-2022.yml b/courses/tuesday-2022.yml
new file mode 100644
index 00000000..6073a129
--- /dev/null
+++ b/courses/tuesday-2022.yml
@@ -0,0 +1,408 @@
+title: Začátečnický kurz PyLadies
+subtitle: Brno - podzim 2022 - úterý
+time: 18:00–20:00
+place: Fakulta Informatiky MUNI, Botanická 554/68a, místnost S505
+default_time:
+ start: '18:00'
+ end: '20:00'
+timezone: Europe/Prague
+description: Naučte se Python vážně od začátku. Žádné předchozí znalosti nejsou potřeba.
+long_description: |
+ Začátečnický kurz PyLadies.
+
+ Stránky samotných PyLadies jsou na [https://pyladies.cz][PyLadies].
+ Organizační informace hledej tam.
+
+ Jednotlivé lekce budou určeny naprostým začátečnicím.
+ Instrukce jsou uvedeny pro operační systémy Linux, Windows i macOS.
+
+ Materiály jsou dostupné jako text i jako videa se stejným obsahem natočené Petrem Viktorinem.
+ Vyber si medium, které je ti pohodlnější.
+
+ [PyLadies]: https://pyladies.cz/
+vars:
+ pyladies: true
+ coach-present: true
+ user-gender: f
+
+plan:
+- slug: preparation
+ title: 'Instalace'
+ serial: 0
+ materials:
+ - title: Textové materiály
+ url: null
+ - lesson: beginners/cmdline
+ - lesson: beginners/install
+ - lesson: beginners/venv-setup
+ - lesson: beginners/install-editor
+
+ - title: Videa ke shlédnutí
+ url: null
+
+ - url: https://youtu.be/GGMg8O4hE0c
+ title: (13:50) Příkazová řádka – Linux & macOS – video
+ - url: https://youtu.be/kriVWJmXpZc
+ title: (22:34) Příkazová řádka – Windows – video
+ - url: https://youtu.be/7Qi7cSkoBA0
+ title: (8:15) Instalace Pythonu – Linux – video
+ - url: https://youtu.be/Xr6liKJzRGA
+ title: (4:48) Instalace Pythonu – Windows – video
+ - url: https://youtu.be/AFVvpfQB0V0
+ title: (7:48) Nastavení prostředí – video
+ - url: https://youtu.be/JcXUCuneX04
+ title: (8:01) Instalace editoru – video
+
+
+ - title: Další odkazy
+ url: null
+
+ - title: Tahák na klávesnici (PDF)
+ url: https://pyvec.github.io/cheatsheets/keyboard/keyboard-cs.pdf
+ type: cheatsheet
+ - title: 'Projekty (🧮)'
+ url: https://projekty.pyladies.cz/session?course=pyladies-2022-brno-podzim-utery&session=preparation
+ type: homework
+
+- slug: expressions
+ title: 'Výrazy'
+ date: 2022-09-13
+ materials:
+ - title: Textové materiály
+ url: null
+ - lesson: beginners/first-steps
+ - lesson: beginners/hello-world
+ - lesson: beginners/print
+ - lesson: beginners/variables
+
+ - title: Videa ke shlédnutí
+ url: null
+
+ - url: https://youtu.be/0Poe25XdKIA
+ title: (7:23) První krůčky – video
+ - url: https://youtu.be/9aJcvOfleFs
+ title: (7:50) První program – video
+ - url: https://youtu.be/14-LVG9Edng
+ title: (12:38) Print a chybové hlášky – video
+ - url: https://youtu.be/-zKws24FmCg
+ title: (15:08) Proměnné – video
+
+ - title: Další odkazy
+ url: null
+
+ - title: 'Projekty (✊✌✋)'
+ url: https://projekty.pyladies.cz/session?course=pyladies-2022-brno-podzim-utery&session=expressions
+ type: homework
+
+- slug: conditions
+ title: 'Podmínky a funkce'
+ date: 2022-09-20
+ materials:
+ - title: Textové materiály
+ url: null
+ - lesson: beginners/expressions
+ - lesson: beginners/comparisons
+ - lesson: beginners/functions
+ - lesson: beginners/basic-functions
+
+ - title: Videa ke shlédnutí
+ url: null
+
+ - url: https://youtu.be/nNXDXuN6Smg
+ title: (8:32) Vyhodnocování výrazů – video
+ - url: https://youtu.be/Q1YJqWzfnck
+ title: (17:27) Porovnání – video
+ - url: https://youtu.be/w_d8VKS8i48
+ title: (17:26) Funkce – video
+ - url: https://youtu.be/udEOvGIuZrs
+ title: (15:51) Užitečné funkce – video
+
+ - title: Další odkazy
+ url: null
+
+ - title: Tahák na užitečné funkce (PDF)
+ url: https://pyvec.github.io/cheatsheets/basic-functions/basic-functions-cs.pdf
+ type: cheatsheet
+ - title: 'Projekty (✊✌✋)'
+ url: https://projekty.pyladies.cz/session?course=pyladies-2022-brno-podzim-utery&session=expressions
+ type: homework
+
+- slug: loops
+ title: 'Želva a cykly'
+ date: 2022-09-27
+ materials:
+ - title: Textové materiály
+ url: null
+ - lesson: intro/turtle
+ - lesson: beginners/while
+ - lesson: beginners/reassignment
+ - lesson: beginners/and-or
+
+ - title: Videa ke shlédnutí
+ url: null
+
+ - url: https://youtu.be/A__45ibnsnc
+ title: (33:02) Želva a cykly – video
+ - url: https://youtu.be/I_mkND45dB0
+ title: (6:57) Cyklus While – video
+ - url: https://youtu.be/fFh1LiksgdA
+ title: (9:28) Přepisování proměnných – video
+ - url: https://youtu.be/5Mc-cgoaM10
+ title: (7:58) Nebo anebo a – video
+
+ - title: Další odkazy
+ url: null
+
+ - title: "Projekty (\U0001f422 \U0001f0b2\U0001f0b1)"
+ url: https://projekty.pyladies.cz/session?course=pyladies-2022-brno-podzim-utery&session=loops
+ type: homework
+
+- slug: str
+ title: 'Řetězce'
+ date: 2022-10-04
+ materials:
+ - title: Textové materiály
+ url: null
+ - lesson: beginners/str
+ - lesson: beginners/str-index-slice
+ - lesson: beginners/str-methods
+ - lesson: beginners/fstring
+
+ - title: Videa ke shlédnutí
+ url: null
+
+ - url: https://youtu.be/xdZWIvryP20
+ title: (19:54) Zápis řetězců – video
+ - url: https://youtu.be/QOamCtzLPAo
+ title: (16:12) Výběr z řetězců – video
+ - url: https://youtu.be/I3CB2YihRh8
+ title: (9:19) Řetězcové funkce a metody – video
+ - url: https://youtu.be/2-r2e9aM1HU
+ title: (11:52) Šablony (formátovací řetězce) – video
+
+ - title: Další odkazy
+ url: null
+
+ - title: Řetězcový tahák (PDF)
+ url: https://pyvec.github.io/cheatsheets/strings/strings-cs.pdf
+ type: cheatsheet
+ - title: "Projekty (|‾)"
+ url: https://projekty.pyladies.cz/session?course=pyladies-2022-brno-podzim-utery&session=str
+ type: homework
+
+
+- slug: def
+ title: 'Vlastní funkce'
+ date: 2022-10-11
+ materials:
+ - title: Textové materiály
+ url: null
+ - lesson: beginners/def
+ - lesson: beginners/prefer-return
+ - lesson: beginners/nested-traceback
+ - lesson: beginners/local-variables
+ - lesson: beginners/recursion
+
+ - title: Videa ke shlédnutí
+ url: null
+
+ - url: https://youtu.be/6a-RjR9fNBY
+ title: (17:01) Definice funkcí – video
+ - url: https://youtu.be/4M6Yq9ZyObs
+ title: (8:54) Vrátit nebo vypsat? – video
+ - url: https://youtu.be/I_DpjBDQy_k
+ title: (3:58) Chybové hlášky ze zanořených funkcí – video
+ - url: https://youtu.be/gbjXnMmuAxw
+ title: (14:25) Lokální proměnné – video
+ - url: https://youtu.be/Vu6_jK8OiAI
+ title: (12:23) Rekurze – video
+
+ - title: Další odkazy
+ url: null
+
+ - title: Python Tutor
+ url: http://pythontutor.com/visualize.html#mode=edit
+ - title: "Projekty (⚁⚃⚅)"
+ url: https://projekty.pyladies.cz/session?course=pyladies-2022-brno-podzim-utery&session=def
+ type: homework
+
+- slug: opakovani
+ title: 'Opakování'
+ date: 2022-10-18
+
+- slug: exc
+ title: 'Chyby a moduly'
+ date: 2022-10-25
+ materials:
+ - title: Textové materiály
+ url: null
+ - lesson: beginners/exceptions
+ - lesson: beginners/modules
+ - lesson: beginners/circular-imports
+
+ - title: Videa ke shlédnutí
+ url: null
+
+ - url: https://youtu.be/s-P5mBjUJBw
+ title: (17:11) Výjimky – video
+ - url: https://youtu.be/LFp_x7OgCBU
+ title: (10:32) Moduly – video
+ - url: https://youtu.be/bZ4iiSDe_4g
+ title: (5:56) Cyklické importy – video
+
+ - title: Další odkazy
+ url: null
+
+ - title: Výjimkový tahák (PDF)
+ url: https://pyvec.github.io/cheatsheets/exceptions/exceptions-cs.pdf
+ type: cheatsheet
+ - title: "Projekty (×○×○)"
+ url: https://projekty.pyladies.cz/session?course=pyladies-2022-brno-podzim-utery&session=exc
+ type: homework
+
+
+- slug: test
+ title: 'Rozhraní a testy'
+ date: 2022-11-01
+ materials:
+ - title: Textové materiály
+ url: null
+ - lesson: beginners/interfaces
+ - lesson: beginners/testing
+ - lesson: beginners/main-module
+
+ - title: Videa ke shlédnutí
+ url: null
+
+ - url: https://youtu.be/xQ7J_R8VAJQ
+ title: (6:51) Rozhraní – video
+ - url: https://youtu.be/-KS_VRerZQ0
+ title: (15:25) Testování – video
+ - url: https://youtu.be/S6HyFIUyPTw
+ title: (10:12) Spouštěcí moduly – video
+ - url: https://youtu.be/s-KPxX5ZcMs
+ title: (3:53) Negativní testy – video
+
+ - title: Další odkazy
+ url: null
+
+ - title: "Projekty (✕✕✕)"
+ url: https://projekty.pyladies.cz/session?course=pyladies-2022-brno-podzim-utery&session=test
+ type: homework
+
+
+- slug: list
+ title: 'Seznamy a n-tice'
+ date: 2022-11-08
+ materials:
+ - title: Textové materiály
+ url: null
+ - lesson: beginners/list
+ - lesson: beginners/tuple
+ - lesson: beginners/nested-list
+
+ - title: Videa ke shlédnutí
+ url: null
+
+ - url: https://youtu.be/abIMJjb9Nxw
+ title: (11:10) Seznamy – Úvod – video
+ - url: https://youtu.be/-gINX_pOtQo
+ title: (32:52) Co všechno umí seznamy – video
+ - url: https://youtu.be/dObtA75Ouzk
+ title: (9:51) N-tice – video
+
+ - title: Další odkazy
+ url: null
+
+ - title: Tahák na seznamy (PDF)
+ url: https://pyvec.github.io/cheatsheets/lists/lists-cs.pdf
+ type: cheatsheet
+ - title: "Projekty – [\U0001f436\U0001f431\U0001f430\U0001f40d]"
+ url: https://projekty.pyladies.cz/session?course=pyladies-2022-brno-podzim-utery&session=list
+ type: homework
+
+
+- slug: file
+ title: 'Sekvence a soubory'
+ date: 2022-11-15
+ materials:
+ - title: Textové materiály
+ url: null
+ - lesson: beginners/range
+ - lesson: beginners/zip-enumerate
+ - lesson: beginners/files
+ - lesson: beginners/with
+
+ - title: Videa ke shlédnutí
+ url: null
+
+ - url: https://youtu.be/UtyIKgsnLmk
+ title: (5:02) Range – video
+ - url: https://youtu.be/JUCb7iiGsW4
+ title: (16:39) Iterátory n-tic (enum & zip) – video
+ - url: https://youtu.be/1X4TU_thg7s
+ title: (13:17) Soubory – video
+
+ - title: Další odkazy
+ url: null
+
+ - title: Projekty (⚔️)
+ url: https://projekty.pyladies.cz/session?course=pyladies-2022-brno-podzim-utery&session=file
+ type: homework
+
+
+- slug: dict
+ title: 'Slovníky'
+ date: 2022-11-23
+ materials:
+ - title: Textové materiály
+ url: null
+ - lesson: beginners/dict
+ - lesson: beginners/dict-with-list-values
+ - lesson: intro/json
+
+ - title: Videa ke shlédnutí
+ url: null
+
+ - url: https://youtu.be/OVnUhWwd2C0
+ title: (19:23) Slovníky – video
+ - url: https://youtu.be/pQ268T2IZCo
+ title: (4:42) Více hodnot v jednom záznamu slovníku – video
+ - url: https://youtu.be/nPUSH7gSI4o
+ title: (12:54) Kódování dat – JSON – video
+
+ - title: Další odkazy
+ url: null
+
+ - title: Slovníkový tahák (PDF)
+ url: https://pyvec.github.io/cheatsheets/dicts/dicts-cs.pdf
+ type: cheatsheet
+ - title: "Projekty (💾)"
+ url: https://projekty.pyladies.cz/session?course=pyladies-2022-brno-podzim-utery&session=dict
+ type: homework
+
+- slug: class
+ title: 'Třídy a dědičnost'
+ date: 2022-11-29
+ materials:
+ - title: Textové materiály
+ url: null
+ - lesson: beginners/class
+ - lesson: beginners/inheritance
+
+ - title: Videa ke shlédnutí
+ url: null
+
+ - url: https://youtu.be/x4iJDPOSv4A
+ title: (22:26) Třídy – video
+ - url: https://youtu.be/mqxMmSFiASw
+ title: (12:58) Dědičnost – video
+
+ - title: Další odkazy
+ url: null
+
+ - title: "Projekty (♟♚)"
+ url: https://projekty.pyladies.cz/session?course=pyladies-2022-brno-podzim-utery&session=class
+ type: homework
+ - title: Odkazy na další materiály
+ url: https://www.fi.muni.cz/IB111/sbirka/
diff --git a/lessons/beginners/circular-imports/index.md b/lessons/beginners/circular-imports/index.md
index e9454b63..cfc0e745 100644
--- a/lessons/beginners/circular-imports/index.md
+++ b/lessons/beginners/circular-imports/index.md
@@ -1,7 +1,10 @@
## Cyklické importy
-V domácích projektech budeš rozdělovat 1D Piškvorky na několik modulů.
-Výsledek bude vypadat třeba nějak takhle:
+V domácích úkolech budeš rozdělovat piškvorkový projekt na několik modulů.
+Tento text si doporučuju číst až když narazíš na příslušný úkol,
+abys věděl{{a}} o čem tu je řeč.
+
+Po rozdělení bude projekt vypadat třeba nějak takhle:
(Šipky mezi moduly znázorňují importování.)
```plain
@@ -15,16 +18,6 @@ Výsledek bude vypadat třeba nějak takhle:
└──────────────────┘ │ def tah_hrace │ └──────────────────┘
│ │
└───────────────┘
- ▲
- │
- │ ┌───────────────────╮
- │ │ test_piskvorky.py │
- │ ├───────────────────┤
- └─│ import piskvorky │
- ├───────────────────┤
- │ def test_... │
- │ │
- └───────────────────┘
```
Jenže funkce `tah_pocitace`
@@ -47,7 +40,7 @@ Můžeš importovat `ai` z `piskvorky` a zároveň
└───────────────┘
```
-Můžeme se na to podívat z hlediska Pythonu,
+Můžeš se na to podívat z pohledu Pythonu,
který příkazy v souborech vykonává.
Když má importovat soubor `piskvorky.py`, začne ho
zpracovávat řádek po řádku,
@@ -59,7 +52,7 @@ Brzy narazí na příkaz `import piskvorky`. Co teď?
Aby nenastala situace podobná nekonečné smyčce –
jeden soubor by importoval druhý, druhý zase první,
a tak stále dokola –
-udělá Python taková malý „podvod“:
+udělá Python takový malý „podvod“:
když zjistí, že soubor `piskvorky.py`
už importuje, zpřístupní v modulu `ai`
modul `piskvorky` tak, jak ho
@@ -67,7 +60,7 @@ má: nekompletní, bez většiny funkcí co v něm mají
být nadefinované.
A až potom, co dokončí import `ai.py`,
se vrátí k souboru `piskvorky.py`
-a pokračuje v provádění příkazů `def` které v něm jsou.
+a pokračuje v provádění příkazů `def`, které v něm jsou.
Takový nekompletní modul může být občas užitečný,
ale ve většině případů se chová skoro
nepředvídatelně a tudíž nebezpečně.
diff --git a/lessons/beginners/cmdline/index.md b/lessons/beginners/cmdline/index.md
index 09497006..86dd9647 100644
--- a/lessons/beginners/cmdline/index.md
+++ b/lessons/beginners/cmdline/index.md
@@ -13,45 +13,48 @@
{%- endmacro -%}
-{%- if var('pyladies') -%}
-{% set purpose = 'PyLadies' %}
-{% set dirname = 'pyladies' %}
-{%- else -%}
-{% set purpose = 'Python' %}
-{% set dirname = 'naucse-python' %}
-{%- endif -%}
-
# Příkazová řádka
V této lekci se seznámíme s *příkazovou řádkou* – černým okýnkem,
-které programátoři používají na zadávání příkazů.
-Na první pohled může vypadat nepřirozeně, ale dá se na ni zvyknout :)
+které programátoři používají na zadávání textových příkazů.
+
+Na většinu z toho co příkazová řádka umí můžeš použít i něco jiného – ikonku
+na ploše, speciální program nebo editor, webovou aplikaci – ale tyhle
+vychytávky mají dvě nevýhody:
+* často se liší mezi různými počítači, takže s „tvojí“ variantou ti bude moci
+ pomoct míň lidí, a
+* z příkazové řádky se dá jednoduše kopírovat text, což zjednodušuje
+ spolupráci přes e-mail nebo chat.
+Na první pohled to může vypadat nepřirozeně, ale dá se na to zvyknout.
-Příkazová řádka
-(respektive program, kterému se říká i *konzole* či *terminál*;
+I když to možná není úplně nejjednodušší způsob jak s programováním začít,
+dlouhodobě se ti základy práce s příkazovou řádkou určitě vyplatí.
+A i o tom tenhle kurz je.
+
+Příkazová řádka (respektive program, kterému se říká i *konzole* či *terminál*;
anglicky *command line*, *console*, *terminal*)
se na různých systémech otevírá různě:
* Windows (české): Start → napsat na klávesnici „cmd“ → Příkazový řádek
* Windows (anglické): Start → napsat na klávesnici „cmd“ → Command Prompt
* macOS (anglický): Applications → Utilities → Terminal
+* Linux (GNOME, čeština): Činnosti → hledat Terminál
+* Linux (GNOME, angličtina): Activities → hledat Terminál
* Linux (KDE): Hlavní Menu → hledat Konsole
-* Linux (GNOME): Super → hledat Terminál
-
-Nevíš-li si rady, zkus buď googlit,
-{% if var('coach-present') -%}
-nebo se jednoduše zeptat kouče.
-{%- else -%}
-nebo se zeptat e-mailem.
-{%- endif %}
+Nevíš-li si rady, zkus buď googlit, nebo se zeptat někoho zkušenějšího.
-Po otevření konzole tě uvítá řádek,
+Po otevření konzole tě uvítá *výzva* (angl. *prompt*): řádek,
kterým počítač vybízí k zadání příkazu.
-Podle systému bude končit buď znakem `$` nebo `>`,
-před nímž můžou být ještě další informace:
+Výzva končí na Unixových systémech (např. Linux a macOS) znakem `$`;
+na Windows znakem `>`.
+
+Před tím znakem `$` nebo `>` budou nejspíš ještě nějaké další
+informace, které jsou ovšem v těchto materiálech vynechané.
+A budou vynechané i ve většině ostatních návodů co najdeš na internetu.
+Na každém počítači totiž můžou být trochu jiné.
{% call sidebyside(titles=['Unix (Linux, macOS)', 'Windows']) %}
$
@@ -59,7 +62,8 @@ $
>
{% endcall %}
-Podle systému se potom liší i samotné příkazy, které budeš zadávat.
+Podle systému se potom liší i samotné příkazy, které budeš zadávat:
+Unixové systémy (Linux a macOS) rozumí jiným příkazům než Windows.
> [note] Velikost písma
> Je-li ve Windows moc malé písmo, klikni na ikonku okna a vyber Možnosti.
@@ -78,7 +82,7 @@ Podle systému se potom liší i samotné příkazy, které budeš zadávat.
## První příkaz
-Začneme jednoduchým příkazem.
+Začněme ale příkazem, který je všude stejný.
Napiš `whoami` (z angl. *who am I?* – kdo jsem?)
a stiskni Enter.
Objeví se přihlašovací jméno. Třeba u Heleny by to vypadalo takhle:
@@ -91,22 +95,22 @@ helena
pocitac\Helena
{% endcall %}
-
-
> [note]
-> Znak `$` nebo `>` je v ukázce jen proto, aby bylo jasné, že zadáváme
+> Znak `$` nebo `>` je v ukázce jen proto, aby bylo jasné že zadáváš
> příkaz do příkazové řádky.
> Vypíše ho počítač, většinou ještě s něčím před ním,
-> takže ho nepiš sama! Zadej jen `whoami` a Enter.
+> takže ho nepiš {{gnd('sám','sama')}}! Zadej jen `whoami` a Enter.
>
> Stejně tak počítač sám vypíše přihlašovací jméno.
## Aktuální adresář
-Příkazová řádka pracuje vždy v nějakém *adresáři* (neboli *složce*,
-angl. *directory*, *folder*).
-Ve kterém adresáři zrovna je, to nám poví příkaz, který se podle systému
+Příkazová řádka pracuje vždy v nějakém *adresáři* neboli *složce*
+(angl. *directory*, *folder*).
+Adresář a složka jsou synonyma; můžeš používat kterékoli z nich.
+
+Ve kterém adresáři zrovna jsi, to ti poví příkaz, který se podle systému
jmenuje `pwd` nebo `cd` (z angl. *print working directory* – vypiš pracovní
adresář, resp. *current directory* – aktuální adresář).
@@ -118,16 +122,23 @@ $ pwd
C:\Users\helena
{% endcall %}
+Aktuální adresář se většinou ukazuje i ve výzvě příkazové řádky, před znakem
+`$` nebo `>`.
+Ale je dobré `pwd`/`cd` znát, kdyby ses náhodou ztratil{{a}}.
+Občas totiž bývá vypsaný zkráceně.
+A taky třeba budeš v budoucnu muset pracovat na počítači který před `$`
+ukazuje něco jiného.
-Aktuální adresář se většinou ukazuje i před znakem `$` nebo `>`,
-ale je dobré `pwd`/`cd` znát, kdyby ses náhodou ztratil{{a}}
-(nebo musel{{a}} pracovat na počítači který před `$` ukazuje něco jiného).
+Něco jako aktuální adresář možná znáš z grafických programů,
+kterými vybíráš soubory: typicky mají v horní (nebo na Macu dolní)
+části uvedeno který adresář zrovna ukazují.
+Příkazová řádka umí soubory ukazovat taky – ale musíš si o to říct.
## Co v tom adresáři je?
Příkaz `ls` nebo `dir` (z angl. *list* – vyjmenovat, resp. *directory* – adresář)
-nám vypíše, co aktuální adresář obsahuje: všechny soubory,
+ti vypíše co aktuální adresář obsahuje: všechny soubory,
včetně podadresářů, které se v aktuálním adresáři nacházejí.
{% call sidebyside() %}
@@ -153,13 +164,13 @@ Music
Aktuální adresář se dá změnit pomocí příkazu `cd`
(z angl. *change directory* – změnit adresář).
Za `cd` se píše jméno adresáře, kam chceme přejít.
-Pokud máš adresář `Desktop` nebo `Plocha`, přejdi tam. Pak nezapomeň ověřit,
-že jsi na správném místě.
+Pokud máš adresář `Desktop` nebo `Plocha`, přejdi tam.
+Pak nezapomeň ověřit, že jsi na správném místě.
-Jsi-li na Linuxu nebo macOS, dej si pozor na velikost písmen: na těchto
+Používáš-li Linux nebo macOS, dej si pozor na velikost písmen: na těchto
systémech jsou `Desktop` a `desktop` dvě různá jména.
-Jsi-li na Windows, `cd` už jsi používal{{a}} – tento příkaz se chová různě
+Používáš-li Windows, `cd` už jsi používal{{a}} – tento příkaz se chová různě
podle toho, jestli něco napíšeš za něj nebo ne.
{% call sidebyside() %}
@@ -177,105 +188,220 @@ C:\Users\helena\Desktop
> například `D:` místo `C:`, je potřeba kromě `cd`
> zadat jméno disku s dvojtečkou jako zvláštní příkaz (např. `D:`).
+
## Vytvoření adresáře
-Co takhle si vytvořit adresář na {{ purpose }}? To se dělá příkazem `mkdir`
+Co takhle si zkusit vytvořit adresář? To se dělá příkazem `mkdir`
(z angl. *make directory* – vytvořit adresář).
Za tento příkaz napiš jméno adresáře, který chceš vytvořit – v našem případě
-`{{ dirname }}`:
-
+`zkouska`:
{% call sidebyside() %}
-$ mkdir {{ dirname }}
+$ mkdir zkouska
---
-> mkdir {{ dirname }}
+> mkdir zkouska
{% endcall %}
-Teď se můžeš podívat na Plochu nebo do nějakého grafickém programu na
-prohlížení adresářů: zjistíš, že adresář se opravdu vytvořil!
-
-## Úkol
-Zkus v nově vytvořeném adresáři `{{ dirname }}`
-vytvořit adresář `test`
-a zkontrolovat, že se opravdu vytvořil.
-
-Budou se hodit příkazy `cd`, `mkdir` a `ls` či `dir`.
+Když je adresář vytvořený, můžeš do něj přejít podobně jako jsi před chvílí
+{{gnd('přešel', 'přešla')}} na `Desktop` nebo `Plocha`:
-{% filter solution %}
{% call sidebyside() %}
-$ cd {{ dirname }}
-$ mkdir test
-$ ls
-test
+$ cd zkouska
---
-> cd {{ dirname }}
-> mkdir test
-> dir
-05/08/2014 07:28 PM
test
+> cd zkouska
{% endcall %}
-{% endfilter %}
+Vypiš si teď obsah aktuálního adresáře pomocí `ls` nebo `dir`.
+Jeden z vypsaných adresářů bude `zkouska`.
+
+
+## V grafickém hledátku
+
+Často nebudeš pracovat *jenom* s příkazovou řádkou.
+Vyplatí se umět aktuální adresář z příkazové řádky otevřít i v jiných
+programech.
+
+Otevři si prohlížeč souborů.
+Tenhle program je na každém systému jiný:
-## Úklid
+
+
+
Linux
+ {{ figure(
+ img=static('linux-file-browser.png'),
+ alt='Screenshot programu Nautilus na GNOME',
+ ) }}
+
+
+
macOS
+ {{ figure(
+ img=static('macos-file-browser.png'),
+ alt='Screenshot programu Finder na macOS',
+ ) }}
+
-Teď vytvořené adresáře zase smažeme.
+Možná umíš v tomhle programu klikáním „donavigovat“ do adresáře který je
+aktivní v příkazové řádce.
+V budoucnu to ale bude složitější, takže bude dobré si vyzkoušet kopírovat text
+z příkazové řádky a vložit ho do prohlížeče souborů.
-Nemůžeš ale smazat adresář, ve kterém jsi.
-Proto se vrátíme na `Desktop`.
-Ale nemůžeme použít `cd Desktop` – v aktuálním adresáři žádný `Desktop` není.
-Potřebuješ se dostat do *nadřazeného adresáře*: toho, který obsahuje
-adresář ve kterém právě jsi.
-Nadřazený adresář se značí dvěma tečkami:
+Bohužel se to dělá na každém systému jinak.
+A protože známá zkratka Ctrl+C a
+Ctrl+V dělají v příkazové řádce něco jiného než
+kopírování, nejspíš se to dělá jinak než jsi zvykl{{gnd('ý', 'á')}}.
+
+Nejdřív si pomocí příkazu `cd` nebo `pwd` nech vypsat celé jméno adresáře
+`zkouska`:
{% call sidebyside() %}
$ pwd
-/home/helena/Desktop/{{ dirname }}
-$ cd ..
-$ pwd
-/home/helena/Desktop
+/home/helena/Desktop/zkouska
---
> cd
-C:\Users\helena\Desktop\{{ dirname }}
-> cd ..
-> cd
-C:\Users\helena\Desktop
+C:\Users\helena\Desktop\zkouska
{% endcall %}
-Teď můžeš smazat vytvořený adresář `{{ dirname }}`.
-K tomu použij příkaz `rm` nebo `rmdir`
-(z *remove* – odstraň, resp. *remove directory* – odstraň adresář).
+### Kopírování z příkazové řádky
+
+Na **Linuxu** vyber text myší a pak buď:
+* pravým tlačítkem myši otevři menu a vyber *Kopírovat* nebo *Copy*, nebo
+* zmáčkni Ctrl+Shift+C.
+ (Pozor, v příkazové řádce musíš použít navíc Shift.)
+
+Na **macOS** vyber text myší a pak stiskni ⌘ Command+C.
+
+Na **Windows** v menu příkazové řádky (ikonce vlevo nahoře) vyber
+*Edit* → *Mark*, text vyber myší a zkopíruj pomocí Enter.
+
+### Otevření v prohlížeči souborů
+
+Na **Linuxu** záleží na programu, který používáš. Buď:
+* zmáčkni Ctrl+L a jméno adresáře vlož pomocí
+ Ctrl+V, nebo
+* vyber jméno adresáře v horní části, smaž ho a vlož nové pomocí
+ Ctrl+V.
+V obou případech potvrď pomocí Enter
+
+Na **macOS** vyber v menu *Go* → *Go to Folder*, vlož jméno adresáře pomocí
+⌘ Command+V a potvrď pomocí Enter.
+
+Na Windows klikni na jméno adresáře v horní části.
+Převede se tím na editovatelný text.
+Smaž ho a pomocí Ctrl+V místo něj vlož nové jméno.
+Potvrď pomocí Enter.
+
+
+Teď se můžeš podívat na Plochu nebo do nějakého grafickém programu na
+prohlížení adresářů: zjistíš, že se adresář opravdu vytvořil.
+
+### Vkládání do příkazové řádky
+
+Občas si otevřeš soubor v prohlížeči souborů a budeš do něj chtít přejít
+v příkazové řádce.
+Zkopírování jména adresáře doufám nebude problém; vkládání do příkazové řádky
+je ale občas jiné než v ostatních pogramech:
+
+* Linux: Ctrl+Shift+V
+* macOS: ⌘ Command+V
+* Windows: Menu *Edit* → *Paste*
+
+> [note]
+> Pokud jsou ve jménu mezery nebo jiné speciální znaky jako `*#$%^()><;"?`,
+> musíš ho v příkazové řádce ještě uzavřít do uvozovek: před a za jméno napiš
+> `"`, např:
+>
+> ```console
+> $ cd "můj super adresář"
+> ```
+>
+> Lepší je ale mezery a zvláštní znaky ve jménech souborů nepoužívat.
+
+
+## Pozorování změn
+
+Vyzkoušej si, že se v řádce projeví i změny, které na počítači
+uděláš jiným způsobem.
-> [warning] Pozor!
-> Příkazová řádka nepoužívá odpadkový koš!
-> Všechno se nadobro smaže. Takže si dobře překontroluj, že mažeš
-> správný adresář.
+V grafickém prohlížeči, který se „dívá“ na stejný adresář který máš aktivní
+v příkazové řádce, vytvoř nový soubor nebo adresář.
+Pak se pomocí příkazu `ls` nebo `dir` podívej, že se opravdu vytvořil.
+Potom ho v grafickém programu smaž – a v příkazové řádce se ujisti,
+že je opravdu smazaný.
-Na Unixu za tento příkaz musíš napsat ještě jedno slovo: `-rv` (minus,
-`r`, `v`).
-To je takzvaný *přepínač*, který příkazu říká, že má smazat celý adresář
-včetně všeho, co obsahuje (`r`),
-a že má informovat o tom co dělá (`v`).
+Na počítači máš jen jednu sadu souborů, se kterou umí manipulovat jak grafické
+programy tak příkazová řádka.
-Obdobně i na Windows je potřeba zadat přepínač, který říká, že má smazat
-adresář a veškerý jeho obsah. Tentokrát je to `/S` (lomítko, `S`).
-Příkaz `rmdir` se automaticky ujistí, jestli to co mažeš opravdu chceš smazat.
+
+## O úroveň nahoru
+
+A poslední věc: jsi-li teď v adresáři `Desktop/zkouska` (nebo `Plocha/zkouska`,
+`Desktop\zkouska` atp.), jak se dostat zpátky do `Desktop`?
+
+Příkaz `cd Desktop` fungovat nebude: tím bys počítači řekl{{a}}, ať se přepne
+do adresáře `Desktop` *v aktuálním adresáři*.
+Ale v adresáři `zkouska` žádný `Desktop` není!
+Je to naopak: `zkouska` je v `Desktop`.
+Odborně řečeno, adresář `Desktop` je *nadřazený* aktuálnímu adresáři.
+
+Nadřazený adresář má speciální jméno `..`, dvě tečky.
+Přejdi do něj zadáním `cd ..` a pak se ujisti, že jsi opravdu v `Desktop`:
{% call sidebyside() %}
+$ cd ..
$ pwd
/home/helena/Desktop
-$ rm -rv {{ dirname }}
-removed directory: ‘{{ dirname }}’
---
+> cd ..
> cd
C:\Users\helena\Desktop
-> rmdir /S {{ dirname }}
-{{ dirname }}, Are you sure ? Y
{% endcall %}
+Další `cd ..` by tě přesunulo do dalšího nadřazeného adresáře – v našem
+příkladu `helena`.
-## Shrnutí
-Tady je tabulka základních příkazů, se kterými si zatím vystačíme:
+## Konec
+
+Příkazů existuje samozřejmě daleko víc.
+
+Když se je naučíš, můžeš z příkazové řádky plnohodnotně ovládat
+počítač: vytvářet soubory, mazat je, pouštět programy, měnit nastavení
+a podobně.
+Vydalo by to ale vydalo na samostatný kurz, a tak tady skončíme.
+
+Vyzkoušej si ještě jeden příkaz, ten, který příkazovou řádku zavírá: `exit`.
+
+Příkaz `exit` funguje stejně na všech systémech.
+To samé platí pro překvapivě mnoho příkazů (kromě těch základních jako
+`cd`, `pwd` a `ls`).
+Proto ve zbytku těchto materiálů nebudu používat ukázky dvojmo podle
+operačního systému.
+
+A budu používat unixovskou výzvu `$`.
+S touto konvencí se setkáš i ve většině návodů na internetu.
+Používáš-li Windows, je dobré si na `$` zvyknout i když ve své
+řádce máš místo něj `>`.
+
+Zkus si tedy, co dělá příkaz `exit`:
+
+```console
+$ exit
+```
+
+A tím je úvod do příkazové řádky hotový.
+
+
+## Přehled
+
+Tady je tabulka základních příkazů, se kterými si do začátku vystačíš:
@@ -285,10 +411,10 @@ Tady je tabulka základních příkazů, se kterými si zatím vystačíme:
Příklad
-
cd
-
cd
+
cd adresář
+
cd adresář
změna adresáře
-
cd test
+
cd test cd ..
pwd
@@ -302,44 +428,6 @@ Tady je tabulka základních příkazů, se kterými si zatím vystačíme:
@@ -348,37 +436,5 @@ Tady je tabulka základních příkazů, se kterými si zatím vystačíme:
-Příkazů existuje samozřejmě daleko víc.
-Dokonce každý program, který máš na počítači nainstalovaný, jde spustit
-z příkazové řádky – a to většinou jen zadáním jeho jména.
-Zkus, jestli na tvém počítači bude fungovat `firefox`, `notepad`, `safari`
-nebo `gedit`.
-{% if var('coach-present') -%}
-Kdyby nefungoval ani jeden, zeptej se kouče ať najde nějaký, co u tebe fungovat
-bude.
-{%- endif %}
-
-Při učení Pythonu použiješ programy/příkazy jako `python` a `git`, které
-zanedlouho nainstalujeme.
-
-
-
-## Konec
-
-Nakonec vyzkoušej ještě jeden příkaz.
-Ten, který příkazovou řádku zavírá: `exit`.
-
-Jako většina příkazů (kromě pár z těch základních) funguje `exit`
-stejně na všech systémech.
-Proto už nebudu používat ukázku rozdělenou pro Unix a Windows.
-
-```console
-$ exit
-```
-
-Ve zbytku těchto materiálů budeme pro kód, který je potřeba zadat do
-příkazové řádky, používat unixovské `$`.
-S touto konvencí se setkáš i ve většině návodů na internetu.
-Používáš-li Windows, je dobré si na `$` zvyknout, i když ve své
-řádce máš místo něj `>`.
-
+Další příkazy jako `python` nebo `git` si vysvětlíme až budou potřeba,
+po tom, co si je nainstaluješ.
diff --git a/lessons/beginners/cmdline/info.yml b/lessons/beginners/cmdline/info.yml
index 31462de3..d1564f92 100644
--- a/lessons/beginners/cmdline/info.yml
+++ b/lessons/beginners/cmdline/info.yml
@@ -1,4 +1,4 @@
-title: Úvod do příkazové řádky
+title: Příkazová řádka
style: md
attribution:
- Pro PyLadies Brno napsal Petr Viktorin, 2014-2017.
diff --git a/lessons/beginners/cmdline/static/linux-file-browser.png b/lessons/beginners/cmdline/static/linux-file-browser.png
new file mode 100644
index 00000000..cfa18cf7
Binary files /dev/null and b/lessons/beginners/cmdline/static/linux-file-browser.png differ
diff --git a/lessons/beginners/cmdline/static/macos-file-browser.png b/lessons/beginners/cmdline/static/macos-file-browser.png
new file mode 100644
index 00000000..7b27bcf5
Binary files /dev/null and b/lessons/beginners/cmdline/static/macos-file-browser.png differ
diff --git a/lessons/beginners/cmdline/static/windows-file-browser.png b/lessons/beginners/cmdline/static/windows-file-browser.png
new file mode 100644
index 00000000..e3ef5e48
Binary files /dev/null and b/lessons/beginners/cmdline/static/windows-file-browser.png differ
diff --git a/lessons/beginners/comparisons/index.md b/lessons/beginners/comparisons/index.md
index 115c500b..c2115786 100644
--- a/lessons/beginners/comparisons/index.md
+++ b/lessons/beginners/comparisons/index.md
@@ -62,51 +62,54 @@ nebo pusť `python` z příkazové řádky.)
-Hodnoty provnání jsou takzvané *booleovské* hodnoty
-(angl. *boolean*, podle [G. Boolea](http://en.wikipedia.org/wiki/George_Boole)).
-V Pythonu je můžeš použít vždycky, když potřebuješ vědět, jestli něco platí
-nebo neplatí.
-Jsou jenom dvě – buď `True` (pravda), nebo `False` (nepravda).
-
-Jako všechny hodnoty, `True` a `False` můžeš přiřadit do proměnných:
+Podobně jako u jiných operátorů se pomocí `==`, `!=` atd. tvoří výrazy,
+které Python umí vyhodnotit.
```python
-pravda = 1 < 3
-print(pravda)
-
-nepravda = 1 == 3
-print(nepravda)
+vysledek = 3 > 0
+# ╰─┬─╯
+# ano (pravda, True)
+print(vysledek)
+
+vysledek = 3 == 0
+# ╰─┬──╯
+# ne (nepravda, False)
+print(vysledek)
```
+Výsledky provnání jsou takzvané *booleovské* hodnoty
+(angl. *boolean*, podle [G. Boolea](http://en.wikipedia.org/wiki/George_Boole)).
+Jmenují se `True` (pravda), nebo `False` (nepravda).
+V Pythonu je můžeš použít vždycky, když potřebuješ vědět, jestli něco platí
+nebo neplatí.
+
> [note]
> Všimni si, že rovnost zjistíš pomocí dvou rovnítek: `3 == 3`.
> Jedno rovnítko přiřazuje do proměnné; dvě rovnítka porovnávají.
-Slova True a False můžeš
-v programu použít i přímo,
+Slova `True` a `False` můžeš v programu použít i přímo,
jen si dej pozor na velikost písmen:
```python
print(True)
-print(False)
+uzivatel_je_administrator = False
```
## Podmínky
Teď oprášíme program na výpočet obvodu a obsahu.
-Otevři si v editoru nový soubor.
-Jestli ještě v adresáři, kde máš soubory ke kurzům Pythonu,
-nemáš adresář pro tuto lekci (třeba `02`), vytvoř si ho.
-Nový soubor ulož do něj pod jménem `if.py`.
+Otevři si v editoru nový soubor a ulož ho pod jménem `if.py`.
+Zkontroluj, že je v adresáři pro tuto lekci.
Do souboru pak napiš následující program:
```python
-strana = float(input('Zadej stranu čtverce v centimetrech: '))
+# Tento program počítá obvod a obsah čtverce.
+
+strana = float(input('Zadej stranu čtverce v centimetrech: '))
print('Obvod čtverce se stranou', strana, 'je', 4 * strana, 'cm')
-print('Obsah čtverce se stranou', strana, 'je', strana * strana, 'cm2')
-```
+print('Obsah čtverce se stranou', strana, 'je', strana * strana, 'cm2')```
Program spusť. Funguje?
@@ -117,7 +120,7 @@ Tady je vidět, jak počítač dělá přesně, co se mu řekne. Nepřemýšlí
Bylo by dobré uživateli, který zadá záporné číslo,
přímo říct, že zadal blbost. Jak na to?
-Nejdřív zkus nastavit proměnnou která bude `True`,
+Nejdřív zkus nastavit proměnnou, která bude `True`,
když uživatel zadal kladné číslo.
@@ -216,7 +219,7 @@ else:
Příkazy `if` se dají *zanořovat* (angl. *nest*).
V odsazeném (podmíněném) bloku kódu může být další `if` s dalším odsazeným
kódem.
-Třeba u tohoto programu, který rozdává nejapné rady do života:
+Třeba u tohoto programu, který rozdává naivní rady do života:
```python
stastna = input('Jsi šťastná?')
diff --git a/lessons/beginners/def/index.md b/lessons/beginners/def/index.md
index e519d89c..a4d59c0d 100644
--- a/lessons/beginners/def/index.md
+++ b/lessons/beginners/def/index.md
@@ -82,7 +82,7 @@ Potom následuje odsazené *tělo funkce* – příkazy, které funkce provádí
Tělo může začít *dokumentačním řetězcem* (angl. *docstring*), který popisuje
co funkce dělá.
To může být jakýkoli řetězec, ale tradičně se uvozuje třemi uvozovkami
-(i v případě že je jen jednořádkový).
+(i v případě, že je jen jednořádkový).
Příkazem `return` pak můžeš z funkce *vrátit* nějakou hodnotu.
@@ -142,6 +142,7 @@ Funkci zavolej a výsledek vypiš.
{% filter solution %}
```python
def obsah_obdelnika(a, b):
+ """Vrátí obsah obdélníka s danými stranami."""
return a * b
print('Obsah obdélníka se stranami 3 cm a 5 cm je', obsah_obdelnika(3, 5), 'cm2')
@@ -180,8 +181,8 @@ def ano_nebo_ne(otazka):
return True
elif odpoved == 'ne':
return False
-
- print('Nerozumím! Odpověz "ano" nebo "ne".')
+ else:
+ print('Nerozumím! Odpověz "ano" nebo "ne".')
# Příklad použití
if ano_nebo_ne('Chceš si zahrát hru? '):
@@ -194,96 +195,3 @@ else:
> Stejně jako `if` nebo `break` je `return` *příkaz*, ne funkce.
> Kolem „své“ hodnoty nepotřebuje závorky.
-
-### Vrátit nebo vypsat?
-
-Podívejme se teď na následující program, který vypíše obsah elipsy:
-
-```python
-from math import pi
-
-def obsah_elipsy(a, b):
- return pi * a * b
-
-print('Obsah elipsy s poloosami 3 a 5 je', obsah_elipsy(3, 5), 'cm2')
-```
-
-Takový program se teoreticky dá napsat i s procedurou, tedy funkcí, která nic
-nevrací.
-Procedura může výsledek třeba vypsat na obrazovku:
-
-```python
-from math import pi
-
-def obsah_elipsy(a, b):
- print('Obsah je', pi * a * b) # Pozor, `print` místo `return`!
-
-obsah_elipsy(3, 5)
-```
-
-Program takhle funguje, ale přichází o jednu z hlavních výhod funkcí:
-možnost vrácenou hodnotu použít i jinak jež jen v `print`.
-
-Funkci, která *vrací* výsledek, můžeš použít v dalších výpočtech:
-
-```python
-def objem_eliptickeho_valce(a, b, vyska):
- return obsah_elipsy(a, b) * vyska
-
-print(objem_eliptickeho_valce(3, 5, 3))
-```
-
-... ale s procedurou, která výsledek přímo vypíše, by to nešlo.
-Proto je dobré psát funkce, které spočítané hodnoty vrací,
-a zpracování výsledku (např. vypsání) nechat na kód mimo funkci.
-
-Další důvod proč hodnoty spíš vracet než vypisovat je ten, že jedna funkce se
-dá použít v různých situacích.
-Proceduru s `print` by nešlo rozumně použít tehdy, když nás příkazová
-řádka vůbec nezajímá – třeba v grafické hře, webové aplikaci, nebo pro ovládání
-robota.
-
-Podobně je to se vstupem: když použiju v rámci své funkce `input`, bude se
-moje funkce dát použít jen v situacích, kdy je u počítače klávesnice a za ní
-člověk.
-Proto je lepší funkcím potřebné informace předávat jako argumenty
-a volání `input` (nebo čtení textového políčka či měření čidlem robota)
-nemít ve funkci, ale vně, v kódu, který funkci volá:
-
-```python
-from math import pi
-
-def obsah_elipsy(a, b):
- """Vrátí obsah elipsy s poloosami daných délek"""
- # Jen samotný výpočet:
- return pi * a * b
-
-# print a input jsou "venku":
-x = float(input('Zadej délku poloosy 1: '))
-y = float(input('Zadej délku poloosy 2: '))
-print('Obsah je', obsah_elipsy(x, y))
-```
-
-Samozřejmě existují výjimky: procedura, která přímo vytváří textový výpis
-(např. tabulku), může používat `print`; funkce, která načítá textové informace
-(jako `ano_nebo_ne` výše), zase `input`.
-Když ale funkce něco *počítá*, nebo když si nejsi jist{{gnd('ý', 'á')}},
-je dobré ve funkci `print` ani `input` nemít.
-
-
-## None
-
-Když funkce neskončí příkazem `return`,
-automaticky se vrátí hodnota `None`.
-
-Je to hodnota zabudovaná přímo do Pythonu, podobně jako `True` nebo `False`,
-a znamená „nic“.
-
-```python
-def nic():
- """Tahle funkce nic nedělá """
-
-print(nic())
-```
-
-Procedury v Pythonu vracejí právě toto „nic“.
diff --git a/lessons/beginners/dict/index.md b/lessons/beginners/dict/index.md
index b875e373..b3d9d196 100644
--- a/lessons/beginners/dict/index.md
+++ b/lessons/beginners/dict/index.md
@@ -10,7 +10,7 @@ Představ si překladový slovník, třeba tenhle česko-anglický:
* **Knoflík**: Button
* **Myš**: Mouse
-Slovník v Pythonu obsahuje *záznamy*. Každý záznam přiřazuje
+Slovník v Pythonu obsahuje *záznamy*, a každý záznam přiřazuje
nějakému *klíči* nějakou *hodnotu*.
V našem příkladu je klíči *Jablko* přiřazena hodnota *Apple*,
klíči *Knoflík* náleží hodnota *Button*
@@ -90,7 +90,7 @@ u seznamů – příkazem `del`:
{'Jablko': 'Apple', 'Knoflík': 'Button', 'Myš': 'Mouse'}
```
-Když budeš chtít zjistit, kolik je ve slovníku záznamů,
+Když budeš chtít zjistit kolik je ve slovníku záznamů,
zeptáš se podobně jako na počet znaků řetězce nebo prvků seznamu.
Použiješ funkci `len()`.
@@ -134,8 +134,8 @@ Mouse
Většinou ale potřebuješ jak klíče tak hodnoty.
K tomu mají slovníky metodu `items`, která vrací iterátor dvojic.
-Často využiješ možnost každou dvojici přímo rozbalit v cyklu `for`,
-jako se to dělá se `zip` nebo `enumerate`:
+Často každou dvojici přímo rozbalíš v cyklu `for`, jako se to dělá se
+`zip` nebo `enumerate`:
```pycon
>>> for klic, hodnota in slovnik.items():
@@ -145,6 +145,15 @@ Knoflík: Button
Myš: Mouse
```
+> [note]
+> Existuje i metoda `keys()`, která vrací klíče.
+>
+> To, co `keys()`, `values()` a `items()` vrací, jsou speciální objekty,
+> které kromě použití ve `for` umožňují další
+> operace: například pracovat s klíči jako s množinou.
+> V [dokumentaci](https://docs.python.org/3.0/library/stdtypes.html#dictionary-view-objects)
+> Pythonu je to všechno popsáno.
+
V průběhu iterace (tedy v rámci `for` cyklu) nesmíš
do slovníku přidávat záznamy, ani záznamy odebírat:
diff --git a/lessons/beginners/exceptions/index.md b/lessons/beginners/exceptions/index.md
index e027349d..eee3813c 100644
--- a/lessons/beginners/exceptions/index.md
+++ b/lessons/beginners/exceptions/index.md
@@ -3,10 +3,11 @@
Pojďme si prohloubit znalosti o chybách, neboli odborně o *výjimkách*
(angl. *exceptions*).
-Vezmi následující funkci:
+Podívej se na následující funkci:
```python
def nacti_cislo():
+ """Získá od uživatele celé číslo a vrátí ho"""
odpoved = input('Zadej číslo: ')
return int(odpoved)
```
@@ -17,7 +18,7 @@ odpovídající chybovou hlášku.
```pycon
Traceback (most recent call last):
- File "ukazka.py", line 3, in nacti_cislo
+ File "ukazka.py", line 4, in nacti_cislo
cislo = int(odpoved)
ValueError: invalid literal for int() with base 10: 'cokolada'
```
@@ -35,6 +36,7 @@ mohlo by to fungovat nějak takhle:
```python
def nacti_cislo():
+ """Získá od uživatele celé číslo a vrátí ho"""
while True:
odpoved = input('Zadej číslo: ')
if obsahuje_jen_cislice(odpoved):
@@ -47,7 +49,7 @@ def nacti_cislo():
Kde ale vzít funkci `obsahuje_jen_cislice`?
Nemá smysl ji psát znovu – funkce `int` sama nejlíp pozná, co se dá převést na
číslo a co ne.
-A dokonce nám to dá vědět – chybou, kterou můžeš *zachytit*.
+A dokonce nám to dá vědět – výjimkou, kterou můžeš *zachytit*.
> [note]
> Ono „obsahuje_jen_cislice“ v Pythonu existuje. Dokonce několikrát.
@@ -63,7 +65,7 @@ A dokonce nám to dá vědět – chybou, kterou můžeš *zachytit*.
> s mezerou na začátku, např. s `' 3'`, ale funkce `isdecimal` takový řetězec
> odmítne.
>
-> Chceš-li zjistit jestli funkce `int` umí daný řetězec převést na číslo,
+> Chceš-li zjistit, jestli funkce `int` umí daný řetězec převést na číslo,
> nejlepší je použít přímo funkci `int`.
@@ -73,6 +75,7 @@ Pro zachycení chyby má Python příkaz `try`/`except`.
```python
def nacti_cislo():
+ """Získá od uživatele celé číslo a vrátí ho"""
while True:
odpoved = input('Zadej číslo: ')
try:
@@ -83,7 +86,7 @@ def nacti_cislo():
Jak to funguje?
Příkazy v bloku uvozeném příkazem `try` se normálně provádějí, ale když
-nastane uvedená výjimka, Python přeskočí zbytek bloku `try` a provede všechno
+nastane uvedená výjimka, Python přeskočí zbytek bloku `try` a provede všechno
v bloku `except`.
Pokud výjimka nenastala, přeskočí se celý blok `except`.
@@ -141,10 +144,10 @@ V našem příkladu to platí pro `ValueError` z funkce `int`: víš že uživ
nemusí vždy zadat číslo ve správném formátu a víš že správná
reakce na tuhle situaci je problém vysvětlit a zeptat se znovu.
-Co ale dělat, kdyš uživatel chce ukončit program a zmáčkne
+Co ale dělat, když uživatel chce ukončit program a zmáčkne
Ctrl+C?
Nebo když se mu porouchá klávesnice a selže funkce `input`?
-Nejlepší reakce na takovou nečekanou situaci ukončit program a informovat
+Nejlepší reakce na takovou nečekanou situaci je ukončit program a informovat
uživatele (nebo lépe, programátora), že (a kde) je něco špatně.
Neboli vypsat chybovou hlášku.
A to se stane normálně, bez `try`.
@@ -186,7 +189,7 @@ finally:
Občas se stane, že výjimku budeš potřebovat vyvolat {{gnd('sám', 'sama')}}.
-Často se to stává když píšeš nějakou obecnou funkci.
+Často se to stává, když píšeš nějakou obecnou funkci.
Třeba funkci na výpočet obsahu čtverce.
Co se stane, když někdo zavolá `obsah_ctverce(-5)`?
@@ -213,6 +216,7 @@ Za příkaz dáš druh výjimky a pak do závorek nějaký popis toho, co je šp
```python
def obsah_ctverce(strana):
+ """Vrátí obsah čtverce s danou délkou strany"""
if strana > 0:
return strana ** 2
else:
diff --git a/lessons/beginners/expressions/index.md b/lessons/beginners/expressions/index.md
index 8df1609e..8eeca545 100644
--- a/lessons/beginners/expressions/index.md
+++ b/lessons/beginners/expressions/index.md
@@ -37,17 +37,36 @@ vysledek = 9 / 4
vysledek = 2.25
```
-Funguje to i u složitých výrazů.
-Python se složitými výrazy nemá problém.
-Jen člověk, který program čte či píše, se v nich může lehce ztratit.
+Python dodržuje *prioritu operátorů*: např. násobení vyhodnotí dřív než
+sečítání.
+Záleží přitom vždy na operátoru (znaménku).
+Násobení čísel (`*`) a opakování řetězců (taky `*`) má vyšší prioritu;
+sečítání čísel (`+`) a spojování řetězců (taky `+`) ji má nižší.
+
+```python
+vysledek = 4 + 2 * 3
+# ╰─┬─╯
+vysledek = 4 + 6
+# ╰──┬──╯
+vysledek = 10
+
+vyzva = "Volejte třikrát: " + "Sláva! " * 3
+# ╰─────┬─────╯
+vyzva = "Volejte třikrát: " + "Sláva! Sláva! Sláva! "
+# ╰────────────────────┬──────────────────────╯
+vyzva = "Volejte třikrát: Sláva! Sláva! Sláva! "
+```
+
+Python nemá problém se složitými výrazy – vyhodnocování funguje vždy stejně.
+Jen člověk, který program čte či píše, se ve výrazech může lehce ztratit.
Když opravdu potřebuješ napsat složitý výraz, je dobré jej rozdělit na několik
menších nebo vysvětlit pomocí komentáře.
Je ale dobré mít povědomí o tom, jak složité výrazy „fungují“,
aby ses jich nemusel{{a}} bát.
Měl{{a}} bys být schopn{{gnd('ý', 'á')}} vysvětlit, co se stane,
-když se Pythonu zeptáš, kolik je -b + (b² +
-4ac)⁰·⁵ / (2a), abys pak věděl{{a}}, co za
+když se Pythonu zeptáš, kolik je (-b + (b² -
+4ac)⁰·⁵) / (2a), abys pak věděl{{a}}, co za
tebe Python dělá.
```python
@@ -56,37 +75,24 @@ b = 5
c = 3
-x = -b + (b ** 2 + 4 * a * c) ** 0.5 / (2 * a)
-# | | | | |
-x = -5 + (5 ** 2 + 4 * 2 * 3) ** 0.5 / (2 * 2)
-# ╰──┬─╯ ╰─┬─╯ ╰──┬──╯
-x = -5 + ( 25 + 8 * 3) ** 0.5 / 4
-# ╰────┬─╯
-x = -5 + ( 25 + 24 ) ** 0.5 / 4
-# ╰───────┬──────────╯
-x = -5 + 49 ** 0.5 / 4
-# ╰──────┬──────────╯
-x = -5 + 7.0 / 4
-# ╰─────────────┬────╯
-x = -5 + 1.75
-# ╰──────────────┬───────────────────╯
-x = -3.25
+x = (-b + (b ** 2 - 4 * a * c) ** 0.5) / (2 * a)
+# | | | | |
+x = (-5 + (5 ** 2 - 4 * 2 * 3) ** 0.5) / (2 * 2)
+# ╰──┬─╯ ╰─┬─╯ ╰──┬──╯
+x = (-5 + ( 25 - 8 * 3) ** 0.5) / 4
+# ╰───┬─╯
+x = (-5 + ( 25 - 24 ) ** 0.5) / 4
+# ╰───────┬──────────╯
+x = (-5 + 1 ** 0.5) / 4
+# ╰──────┬──────────╯
+x = (-5 + 1) / 4
+# ╰────────────┬──────╯
+x = -4 / 4
+# ╰────────────┬────────────╯
+x = -1.0
```
Výrazy se používají na více místech Pythonu než jen v přiřazování
do proměnných.
-Třeba podmínka u `if` je taky výraz a vyhodnocuje se stejně jako ostatní
-výrazy:
-
-```python
-strana = -5
-
-if strana <= 0:
- print("Strana musí být kladná!")
-```
-
-```python
-if strana <= 0:
-# ╰──────┬──╯
-if True :
-```
+Třeba pro porovnání dvou hodnot.
+Ale o tom zase příště.
diff --git a/lessons/beginners/files/index.md b/lessons/beginners/files/index.md
index aca37ba7..29521586 100644
--- a/lessons/beginners/files/index.md
+++ b/lessons/beginners/files/index.md
@@ -1,7 +1,7 @@
# Soubory
-Dnes se podíváme na to, jak v Pythonu číst z
-(a pak i zapisovat do) souborů.
+Teď se podívejme na to, jak v Pythonu číst ze souborů
+(a pak i jak do nich zapisovat).
Vytvoř si v editoru soubor `basnicka.txt` a napiš do něj libovolnou básničku.
Soubor ulož.
@@ -10,7 +10,7 @@ Soubor ulož.
> Na uložení souboru s básničkou doporučuji použít
> stejný editor, jaký používáš na Pythonní programy.
>
-> Používáš-li jiný editor než Atom, dej si při ukládání pozor na kódování:
+> Používáš-li jiný editor, dej si při ukládání pozor na kódování:
> * Nabízí-li ti editor při ukládání výběr kódování, vyber UTF-8.
> * Je-li k dispozici kódování „UTF-8 bez BOM”, použij to.
> * Pokud musíš použít Notepad, který výše uvedené možnosti nemá, pak v kódu
@@ -19,7 +19,6 @@ Soubor ulož.
> Ono [`utf-8`] je název standardního kódování.
> Zajišťuje, že se případné emoji nebo znaky s diakritikou do souboru uloží
> tak, aby se daly přečíst i na jiném počítači či operačním systému.
-> 🎉
[`utf-8`]: https://en.wikipedia.org/wiki/UTF-8
@@ -42,7 +41,7 @@ Co se tu děje?
Tak jako `int()` vrací čísla a `input()` řetězce, funkce
`open()` vrací hodnotu, která představuje *otevřený soubor*.
Tahle hodnota má vlastní metody.
-Tady používáme metodu `read()`, která
+Tady používáš metodu `read()`, která
najednou přečte celý obsah souboru a vrátí ho jako řetězec.
Nakonec metoda `close()` otevřený soubor zase zavře.
@@ -55,7 +54,7 @@ ji předtím otevřít a potom zavřít.
Bez zavření to sice na první pohled funguje taky,
ale pravděpodobně potom brzo něco zplesniví.
-Stejně tak je docela důležité soubor zavřít po tom,
+Stejně tak je docela důležité zavířít soubor – ideálně hned po tom,
co s ním přestaneš pracovat.
Bez zavření to na první pohled funguje, ale složitější programy se můžou dostat
do problémů.
@@ -77,7 +76,7 @@ print(obsah)
```
Příkaz `with` vezme otevřený soubor (který vrací funkce `open`)
-a přiřadí ho do proměnné `soubor`.
+a přiřadí ho do proměnné za `as` (tady `soubor`).
Pak následuje odsazený blok kódu, kde se souborem můžeš pracovat – v tomhle
případě pomocí metody `read` přečíst obsah jako řetězec.
Když se Python dostane na konec odsazeného bloku, soubor automaticky zavře.
@@ -87,14 +86,14 @@ V naprosté většině případů je pro otevírání souborů nejlepší použ
## Iterace nad soubory
-Otevřené soubory se, jako např. řetězce či `range`,
-dají použít s příkazem `for`.
-Tak jako `for i in range` poskytuje za sebou jdoucí čísla a `for c in 'abcd'`
-poskytuje jednotlivé znaky řetězce, `for radek in soubor` bude do proměnné
-`radek` dávat jednotlivé řádky čtené ze souboru.
+Otevřené soubory jsou iterovatelné – dají se, stejně jako např. řetězce či
+`range`, použít s příkazem `for`.
+Tak jako `for i in range(...)` poskytuje za sebou jdoucí čísla a
+`for znak in 'abcd'` poskytuje jednotlivé znaky řetězce, `for radek in soubor`
+bude v proměnné `radek` poskytovat jednotlivé *řádky* čtené ze souboru.
-Například můžeš básničku odsadit,
-aby se vyjímala v textu:
+Aby se básnička líp vyjímala v textu, pojďme ji odsadit –
+před každý řádek dát měkolik mezer:
```python
print('Slyšela jsem tuto básničku:')
@@ -109,8 +108,8 @@ print('Jak se ti líbí?')
```
-Když to zkusíš, zjistíš, že trochu nesedí
-řádkování. Zkusíš vysvětlit, proč tomu tak je?
+Když to zkusíš, zjistíš, že trochu nesedí řádkování.
+Zkusíš se zamyslet, proč tomu tak je?
{% filter solution %}
Každý řádek končí znakem nového řádku, `'\n'`,
@@ -121,9 +120,9 @@ výpisu vždycky odřádkovává – pokud nedostane argument `end=''`.
---
-¹ Proč to dělá? Kdyby `'\n'` na konci řádků nebylo,
+¹ *Proč to dělá? Kdyby `'\n'` na konci řádků nebylo,
nedalo by se např. dobře rozlišit, jestli poslední řádek
-končí na `'\n'`
+končí na `'\n'`.*
{% endfilter %}
@@ -154,13 +153,13 @@ print('Jak se ti líbí?')
Soubory se v Pythonu dají i zapisovat.
Pro zápis soubor otevři s pojmenovaným
-argumentem `mode='w'` (z angl. *mode*, mód a *write*, psát).
+argumentem `mode='w'` (z angl. *mode*, mód a * **w**rite*, psát).
Pokud soubor už existuje, otevřením s `mode='w'` se veškerý jeho obsah smaže.
Po zavření tak v souboru bude jen to, co do něj ve svém programu zapíšeš.
Informace pak do souboru zapiš známou funkcí `print`,
-a to s pojmenovaným argumentem `file`:
+ale s pojmenovaným argumentem `file`:
```python
with open('druha-basnicka.txt', mode='w', encoding='utf-8') as soubor:
diff --git a/lessons/beginners/first-steps/index.md b/lessons/beginners/first-steps/index.md
index eb080bfb..57858c9c 100644
--- a/lessons/beginners/first-steps/index.md
+++ b/lessons/beginners/first-steps/index.md
@@ -20,22 +20,32 @@ ale jsou psané v angličtině a pro trochu pokročilejší publikum.
Třemi „zobáčky“ `>>>` pak Python poprosí o instrukce.
Je to jako v příkazové řádce, ale místo příkazů jako `cd` a `mkdir` sem budeš psát příkazy Pythonu.
-Příkazy z příkazové řádky v Pythonu nefungují,
-ačkoli okýnko vypadá skoro stejně.
-Vyzkoušej si to. Za „zobáčky“ napiš `whoami` a zmáčkni Enter:
+Práci s příkazovým řádkem si můžeš představit jako telefonní konverzaci:
+nejdřív ses s počítačem bavil{{a}} pomocí příkazů jako `cd`.
+Příkaz `python` znamená „dej mi prosím k telefonu Python!“
+Následující „konverzace“ bude s úplně jiným programem, i když okýnko s textem
+vypadá skoro stejně.
+
+Vyzkoušej si, že příkazy z příkazové řádky v Pythonu nefungují:
```pycon
+>>> cd adresar
+ File "", line 1
+ cd adresar
+ ^
+SyntaxError: invalid syntax
>>> whoami
Traceback (most recent call last):
File "", line 1, in
NameError: name 'whoami' is not defined
```
-Tohle je *chybová hláška*, která se objeví vždycky,
+Tohle je *chybová hláška*, která se objeví vždycky
když Python nebude spokojený.
V průběhu kurzu jich uvidíš ještě spoustu,
takže si ji dobře prohlédni, ať ji příště poznáš.
+
## První příkaz
Třemi „zobáčky“ `>>>` Python prosí o instrukce.
@@ -67,26 +77,34 @@ Python proto používá symbol `*`.
20
```
-Symboly jako `+` a `*` se odborně nazývají *operátory*.
+Znaménka jako `+`, `-` a `*` se odborně nazývají *operátory*.
+
+> [style-note]
+> Mezery mezi čísly a operátorem nejsou nutné: `4*5` i `4 * 5` dělá
+> to samé co `4 * 5`.
+> Je ale zvykem psát kolem operátoru jednu mezeru z každé strany – tak jako
+> v těchto materiálech.
+> Kód je pak čitelnější.
-Operátor pro dělení je `/` – jako u násobení, znak `÷` by se psal špatně.
+Operátor pro dělení je `/`.
Při dělení může vzniknout necelé číslo, třeba dva a půl.
Python používá desetinnou *tečku*, ukáže se tedy `2.5`:
-``` python
+``` pycon
>>> 5 / 2
2.5
```
-Z důvodů, do kterých teď nebudeme zabíhat, se při dělení desetinná tečka
-objeví, i když vyjde číslo celé:
+Z důvodů do kterých teď nebudeme zabíhat Python rozlišuje mezi celými a
+reálnými čísly.
+Desetinná tečka se proto objeví i když vyjde číslo celé:
``` pycon
>>> 4 / 2
2.0
```
-Občas se hodí použít dělení se zbytkem, kdy výsledek zůstane jako celé číslo.
+Občas se hodí použít dělení se zbytkem, kdy podíl zůstane jako celé číslo.
Na to má Python operátory `//` (podíl) a `%` (zbytek):
``` pycon
@@ -96,44 +114,46 @@ Na to má Python operátory `//` (podíl) a `%` (zbytek):
1
```
-> [style-note]
-> Mezery mezi čísly a znamínkem nejsou nutné: `4*5` i `4 * 5` dělá
-> to samé co `4 * 5`.
-> Je ale zvykem psát kolem operátoru jednu mezeru z každé strany – tak jako
-> v těchto materiálech.
-> Kód je pak čitelnější.
+Časem zjistíš že dělení se zbytkem je v programování překvapivě užitečné.
+Jestli si děláš poznámky, `//` i `%` si do nich určitě přidej!
### Ukončení
Pokud ses dostal{{a}} až sem, gratuluji!
Python máš nejen nainstalovaný, ale taky ti funguje.
-Stačí ho už jen zavřít a pak opustit i samotnou příkazovou řádku.
-V Pythonu se to dělá pomocí `quit()`, s prázdnými závorkami na konci.
+Stačí ho už jen zavřít.
+To se dělá pomocí `quit()`, s prázdnými závorkami na konci.
>>> quit()
(venv)$
+Tím ukončíš konverzaci s Pythonem a „zavoláš k telefonu“
+zpátky příkazovou řádku.
Zobáčky `>>>` se změnily na výzvu
-příkazové řádky, která začíná `(venv)` a končí `$` nebo `>`.
-Teď fungují příkazy jako `whoami` a `cd`, ale příkazy Pythonu
-jako `1 + 2` fungovat nebudou, dokud Python opět nepustíš pomocí
-příkazu `python`.
+příkazové řádky (která začíná `(venv)` a končí `$` nebo `>`).
+Příkazy Pythonu, jako `1 + 2`, teď fungovat nebudou (dokud Python opět
+nepustíš ke slovu pomocí příkazu `python`).
+Zato příkazům jako `whoami` a `cd` teď počítač rozumět bude.
+
+Teď můžeš okno s příkazovou řádkou zavřít – buď zavři její okýnko,
+nebo na to můžeš použít následující textové příkazy.
-Ukončit virtuální prostředí můžeš příkazem `deactivate` –
-tentokrát bez závorek.
+Virtuální prostředí můžeš ukončit příkazem `deactivate` – tentokrát bez
+závorek:
```console
(venv)$ deactivate
```
-Příkazovou řádku můžeš nakonec zavřít příkazem `exit`.
+A celou příkazovou řádku můžeš zavřít příkazem `exit`:
```console
$ exit
```
Pro cvik si zkus Python znovu spustit – nejdřív otevři příkazovou řádku,
-pak aktivuj virtuální prostředí, potom spusť Python samotný.
+pak donaviguj do adresáře s virtuálním prostředím,
+aktivuj virtuální prostředí a nakonec spusť Python samotný.
diff --git a/lessons/beginners/fstring/index.md b/lessons/beginners/fstring/index.md
index 0cef666d..a827c29b 100644
--- a/lessons/beginners/fstring/index.md
+++ b/lessons/beginners/fstring/index.md
@@ -62,7 +62,8 @@ A aby bylo jasné, že jde o šablonu, před první uvozovky přidej navíc zna
f"Součet je {soucet}."
```
-Takový formátovací řetězec jde použít v Pythonu – jako jakýkoli jiný řetězec:
+Takový formátovací řetězec jde použít v Pythonu – jako jakýkoli jiný řetězec,
+s tím že jména v závorkách musí vždy odpovídat jménům proměnných:
```python
soucet = 3 + 4
diff --git a/lessons/beginners/functions/index.md b/lessons/beginners/functions/index.md
index f0c1cfc4..51a58515 100644
--- a/lessons/beginners/functions/index.md
+++ b/lessons/beginners/functions/index.md
@@ -78,9 +78,6 @@ delka = len(slovo) # Vypočítání délky
print(delka)
```
-To `len` je *funkce* (angl. *function*).
-Jak se takové funkce používají?
-
K tomu, abys funkci mohl{{a}} použít, potřebuješ znát její
*jméno* – tady `len`.
Za jméno funkce patří závorky,
@@ -176,7 +173,7 @@ print( 9 )
… a podobně.
-### Procedury
+### Funkce a procedury
Možná sis všiml{{a}}, že jednu funkci už voláš déle: `print("Ahoj!")`
je taky volání funkce.
@@ -210,6 +207,23 @@ Pár příkladů:
> odkaž ho prosím na tyto materiály.
+Kontrolní otázky:
+
+* Je `input` „normální“ funkce, nebo procedura?
+* Co bere funkce `input` jako argument?
+* Jaká je návratová hodnota funkce `input`?
+
+{% filter solution %}
+Funkce `input` vrací hodnotu, se kterou může program dál pracovat.
+Zařadil bych ji tedy mezi „normální“ funkce.
+
+Jako argument bere `input` otázku, na kterou se uživatele zeptá.
+
+Návratová hodnota funkce `input` je uživatelova odpověď.
+{% endfilter %}
+
+
+
## Argumenty
Argument je to, co funkci dáš k dispozici. Hodnota, se kterou funkce pracuje.
@@ -243,6 +257,9 @@ print()
{% filter solution %}
Funkce `print` zavolaná bez argumentů napíše prázdný řádek.
+
+(Je to přesně podle definice – funkce `print` všechny své argumenty vypíše
+na řádek.)
{% endfilter %}
diff --git a/lessons/beginners/hello-world/index.md b/lessons/beginners/hello-world/index.md
index 5a95bb77..9393bbc0 100644
--- a/lessons/beginners/hello-world/index.md
+++ b/lessons/beginners/hello-world/index.md
@@ -7,11 +7,11 @@
Psaní příkazů přímo v Pythonu, interaktivně,
má jednu velkou nevýhodu:
-to, co napíšeš, se ztratí, jakmile zavřeš okno příkazové řádky.
+to co napíšeš se ztratí jakmile zavřeš okno příkazové řádky.
Na jednoduché výpočty to nevadí, ale až budou tvoje programy složitější,
budeš je potřebovat nějak uložit.
-Otevři editor
+Otevři editor.
(Ten bys měl{{a}} mít nainstalovaný, jestli ne, instrukce jsou v [předchozí
lekci]({{ lesson_url('beginners/install-editor') }}).)
@@ -21,20 +21,27 @@ V něm vytvoř nový soubor, do kterého napiš následující text:
print("Ahoj světe!")
```
-Pak soubor ulož jako `ahoj.py`:
+Pak soubor ulož pod jménem `ahoj.py`:
* V adresáři, kde máš soubory ke kurzům Pythonu, si založ adresář pojmenovaný
- podle čísla lekce (např. `02`).
+ podle čísla lekce: `01`.
Měl by být vedle tvého virtuálního prostředí.
* Do něj pak soubor ulož pod jménem `ahoj.py`.
Pokud máš v ukládacím okýnku možnost zvolit *kódování*, zvol `UTF-8`.
Můžeš–li zvolit typ souboru, zvol `.py` nebo „všechny soubory“.
+Po uložení s příponou `.py` by měl editor program obarvit: text v uvozovkách
+by měl mít jinou barvu než slovo `print`.
+Barvy napovídají, jak bude Python souboru rozumět.
+Až si na ně zvykneš, můžou ti pomoci najít překlepy.
+
+
## Spuštění
Otevři si příkazovou řádku.
-Pomocí `cd` donaviguj do adresáře, kde máš soubory ke kurzům Pythonu.
+Pomocí `cd` donaviguj do adresáře, kde máš soubory ke kurzům Pythonu,
+tedy adresáře `venv` a `01`.
> [note]
> S příkazovou řádkou jsme se seznámil{{gnd('i', 'y', both='i')}}
@@ -47,15 +54,16 @@ Aktivuj si virtuální prostředí.
> Příkaz k tomu jsme si ukázali na konci
> [návodu na tvorbu virtuálního prostředí](../venv-setup/); končí `activate`.
+Pak pomocí `cd 01` zajdi do adresáře s programem.
-Pak a zadej tento příkaz:
+A pak zadej tento příkaz:
```console
(venv)$ python ahoj.py
```
Pokud se vypíše hláška `Ahoj světe!`, gratuluji!
-Napsal{{a}} jsi svůj první program v Pythonu!
+Napsal{{a}} a spustil{{a}} jsi svůj první program v Pythonu!
Jestli to nefunguje, zkontroluj, že:
diff --git a/lessons/beginners/install-editor/atom.md b/lessons/beginners/install-editor/atom.md
index 14eb4dc3..6af9f6a0 100644
--- a/lessons/beginners/install-editor/atom.md
+++ b/lessons/beginners/install-editor/atom.md
@@ -15,34 +15,4 @@ V jiných programovacích jazycích se totiž odsazuje i obarvuje jinak.
Proto jakmile v tomhle editoru vytvoříš nový soubor,
měl{{a}} bys ho co nejdřív uložit pod správným jménem.
-## Kontrola stylu zdrojového kódu
-
-Jedna věc nám v Atomu přeci jen chybí: plugin pro kontrolu správného
-stylu zdrojového kódu.
-
-Tak jako čeština má Python typografická pravidla.
-Například za čárkou se píše mezera, ale před ní ne.
-Jsou nepovinná, program bude fungovat i při jejich nedodržení,
-ale pomáhají psát přehledný kód, tak je dobré je dodržovat už od začátku.
-Pravidla pro Python jsou popsána v dokumentu
-[PEP8](https://www.python.org/dev/peps/pep-0008/).
-
-Aby sis je nemusel{{a}} všechny pamatovat, nainstaluj si plugin,
-který tě na jejich porušení upozorní.
-
-Nejprve je potřeba si nainstalovat speciální knihovnu, která se o kontrolu
-dokáže postarat. Do příkazové řádky zadej následující:
-
-```console
-$ python -m pip install flake8
-```
-
-A nyní si nainstaluj plugin do samotného editoru. V hlavní nabídce vyber
-„Soubor > Nastavení/File > Settings“ a v nabídce
-uprostřed okna vyber poslední položku
-„Instalovat/Install“. Do vyhledávacího pole zadej
-„linter-flake8“ a v seznamu nalezených pluginů klikni u položky stejného jména
-na tlačítko „Instalovat/Install“. Bude ještě potřeba
-schválit instalaci všech závislostí, na které se Atom postupně zeptá.
-
{% endblock %}
diff --git a/lessons/beginners/install-editor/index.md b/lessons/beginners/install-editor/index.md
index f098e4d0..7df57752 100644
--- a/lessons/beginners/install-editor/index.md
+++ b/lessons/beginners/install-editor/index.md
@@ -1,8 +1,7 @@
# Instalace editoru
-Editor, program na úpravu textu, je základní pomůcka
-každého programátora,
-takže je dobré do něj investovat trochu času.
+Editor, program na úpravu textu, je základní pomůckou každého programátora.
+A tak je dobré do něj investovat trochu času.
Je víceméně jedno, který programátorský editor budeš používat.
Pokud už nějaký oblíbený máš, stačí ho jen nastavit;
@@ -15,20 +14,21 @@ Stejně tak nejsou vhodné programy jako Word či Writer.
## Co programátorský editor umí
-Editor pro programátory nám umožňuje upravovat *prostý text* – písmenka.
+Editor pro programátory ti umožňí upravovat *prostý text* – písmenka.
Na rozdíl od programů jako Word, Writer či Pages neumožňuje text *formátovat*,
-tedy dělat nadpisy, obarvovat, zvětšovat font, vkládat obrázky a podobně.
+tedy dělat nadpisy, obarvovat, zvětšovat font pro jednotlivá slova,
+vkládat obrázky a podobně.
-Pomocí editoru budeme zadávat počítači příkazy, takže formátování nepotřebujeme.
+Pomocí editoru budeš zadávat počítači příkazy, takže formátování nepotřebuješ.
Porovnej {{ gnd('sám', 'sama') }}, jaký je rozdíl mezi následujícími příkazy
pro někoho, kdo se jimi má řídit:
* Nakresli mi beránka!
* Nakresli mi beránka!
-To, že neumí formátování, neznamená že jsou naše editory úplně „hloupé“
+Naše editory neumí formátování, ale neznamená že to jsou úplně „hloupé“
nástroje.
-Aby se nám programy upravovaly pohodlněji, mají několik vychytávek:
+Aby se ti programy upravovaly pohodlněji, mají několik vychytávek:
Podpora více souborů
: Větší projekty sestávají z více souborů, které můžeš mít v editoru
@@ -79,6 +79,10 @@ a nastavení.
Nabízí mnoho funkcí a má velkou základnu uživatelů a vývojářů,
takže se neustále vylepšuje.
+ V poslední době toho umí víc a víc – a dokonce i věci,
+ které ze začátku trochu překážejí.
+ Neboj se editor přizpůsobit nebo něco vypnout.
+
Na Linuxu budeš mít pravděpodobně už nainstalovaný Gedit nebo Kate.
Zkus se podívat do systémové nabídky, jestli jeden z nich máš (případně je
spusť z příkazové řádky jako `gedit`, resp. `kate`).
@@ -114,10 +118,10 @@ Na začátek ale nejsou moc vhodné.
Chceš-li takový editor přesto použít, měl{{a}} bys ho už poměrně dobře znát:
vědět, co za tebe dělá editor a jak to spravit, až něco udělá špatně.
-{% if var('coach-present') -%}
+
Koučové většinou znají jen jeden editor – ten, který používají –
takže nemusí být schopní s pokročilým IDE rychle pomoct.
-{%- endif %}
+
[PyCharm]: https://www.jetbrains.com/pycharm/
[Eclipse]: https://eclipse.org/
diff --git a/lessons/beginners/install-editor/notepad-plus-plus.md b/lessons/beginners/install-editor/notepad-plus-plus.md
index 1d44fcdc..f930fb3f 100644
--- a/lessons/beginners/install-editor/notepad-plus-plus.md
+++ b/lessons/beginners/install-editor/notepad-plus-plus.md
@@ -17,7 +17,7 @@ a nainstaluj.
{% block setup %}
Odsazování
-: V menu Nastavení zvol Předvolby a pak nastav
+: V menu Nastavení zvol Volby, potom Syntaxe a pak nastav
„Nastavení tabulátoru/Tab Settings“ na
„Zaměnit za mezery/Replace by Space“.
diff --git a/lessons/beginners/install-editor/others.md b/lessons/beginners/install-editor/others.md
index eb000d7f..66f53836 100644
--- a/lessons/beginners/install-editor/others.md
+++ b/lessons/beginners/install-editor/others.md
@@ -53,7 +53,7 @@ Editory často podporují instalaci pluginů, které mohou psaní kódu usnadnit
a pomoci s jeho kontrolou.
Jeden z neužitečnějších je plugin pro kontrolu správného stylu zdrojového kódu.
-Tak jako čeština má Python typografická providla.
+Tak jako čeština má Python typografická pravidla.
Například za čárkou se píše mezera, ale před ní ne.
Jsou nepovinná, program bude fungovat i při jejich nedodržení,
ale pomáhají psát přehledný kód, tak je dobré je dodržovat už od začátku.
diff --git a/lessons/beginners/install-editor/vscode.md b/lessons/beginners/install-editor/vscode.md
index 8f9fb894..65305212 100644
--- a/lessons/beginners/install-editor/vscode.md
+++ b/lessons/beginners/install-editor/vscode.md
@@ -1,7 +1,7 @@
{% set editor_name = 'Visual Studio Code' %} {% set editor_url = 'https://code.visualstudio.com' %}
{% extends lesson.slug + '/_base.md' %}
-{% block name_gen %}Visual Studio Code{% endblock %}
+{% block name_gen %}Instalace Visual Studio Code{% endblock %}
{% block install %}
## Stažení a instalace
@@ -9,10 +9,7 @@
Editor si můžeš stáhnout z jeho [domovské stránky](https://code.visualstudio.com/).
Vyber na ní zelené tlačítko Download a vyber instalátor pro svůj systém.
Dále se řiď instrukcemi instalátoru jako u každého jiného programu.
-{% endblock %}
-{% block setup %}
-Ve Visual Studio Code se nemusí nic nastavovat, funguje „od výroby“ tak, jak má.
### Odesílání telemetrických dat
@@ -27,5 +24,9 @@ Viz též [původni postup v angličtině](https://code.visualstudio.com/docs/s
[privacy]: https://privacy.microsoft.com/en-us/privacystatement
+{% endblock %}
+{% block setup %}
+
+Vše ostatní je již nainstalováno. Teď se jen pusť do samotného programování!
{% endblock %}
diff --git a/lessons/beginners/install/index.md b/lessons/beginners/install/index.md
index 7d812a57..0efaa35d 100644
--- a/lessons/beginners/install/index.md
+++ b/lessons/beginners/install/index.md
@@ -12,7 +12,7 @@ Vyber si stránku podle svého systému:
Pokud máš jiný systém než Linux, Windows nebo macOS,
nebo pokud ke svému počítači neznáš administrátorské heslo,
{% if var('coach-present') -%}
-poraď se s koučem hned, jinak se ptej, až bude něco nejasné.
+poraď se s koučem hned. Jinak se ptej, až bude něco nejasné.
{%- else -%}
napiš nám prosím e-mail. {# XXX vyřešit kam poslat samostudenty co mají problém #}
{%- endif %}
diff --git a/lessons/beginners/install/linux.md b/lessons/beginners/install/linux.md
index 0a3b6045..b7faee93 100644
--- a/lessons/beginners/install/linux.md
+++ b/lessons/beginners/install/linux.md
@@ -8,7 +8,7 @@ Nezalekni se – většinu sekcí pravděpodobně přeskočíš. :)
## Instalace Pythonu 3
Na Linuxu většinou Python 3 už bývá. Abys to zkontroloval{{a}}, spusť
-v [příkazové řádce]({{ lesson_url('beginners/cmdline') }}) příkaz:
+v příkazové řádce příkaz:
```console
$ python3 --version
@@ -108,6 +108,3 @@ alternativu, Virtualenv:
{% endfilter %}
Používáš-li jinou distribuci, doufám, že instalovat programy už umíš.
-
-Instaluješ-li Virtualenv, **zapamatuj si**, že ho budeš muset použít později
-při vytváření virtuálního prostředí.
diff --git a/lessons/beginners/install/macos.md b/lessons/beginners/install/macos.md
index 52407521..3aa4703e 100644
--- a/lessons/beginners/install/macos.md
+++ b/lessons/beginners/install/macos.md
@@ -4,7 +4,7 @@ Nainstaluj si nástroj [Homebrew](http://brew.sh), který řeší a zjednodušuj
instalaci aplikací a knihoven, které budeme potřebovat pro programování.
Jak na to?
-Spusť v [příkazové řádce]({{ lesson_url('beginners/cmdline') }}) příkaz:
+Spusť v příkazové řádce příkaz:
```console
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
diff --git a/lessons/beginners/install/windows.md b/lessons/beginners/install/windows.md
index 6d2940ad..ada6a187 100644
--- a/lessons/beginners/install/windows.md
+++ b/lessons/beginners/install/windows.md
@@ -1,6 +1,37 @@
# Instalace Pythonu pro Windows
-Běž na [stahovací stránku Pythonu](https://www.python.org/downloads/)
+Máš-li Windows 10 s nejnovější aktualizací, zadej do příkazové řádky `python3`.
+
+Pokud ještě Python nemáš nainstalovaný, otevře se ti automaticky stránka
+Microsoft Store. Python nainstaluj z ní.
+
+Jestli Python už nainstalovaný máš, ukáže se ti v příkazové řádce něco jako:
+
+```plain
+> python3
+Python 3.8.1 (...)
+Type "help", "copyright", "credits" or "license" for more information.
+>>>
+```
+
+Na prvním řádku výstupu je verze Pythonu.
+Zkontroluj si, že verze je 3.6 nebo novější (např. `Python 3.6.10`,
+`Python 3.7.4` nebo `Python 3.8.1`).
+
+Jestli ne, pokračuj sekcí níže.
+
+Jestli ano, Python máš nainstalovaný!
+Ukonči ho zkratkou Ctrl+Z a Enter, aby
+řádek, kam budeš psát další příkazy, nezačínal na „tři zobáčky“ (`>>>`).
+
+(Nebo okýnko s příkazovou řádkou zavři. Až ho budeš znovu potřebovat, můžeš
+otevřít nové.)
+
+
+## Starší Windows nebo existující Python
+
+Jestli zkratka `python` nefunguje, nebo jestli máš starší verzi Pythonu, běž na
+[stahovací stránku](https://www.python.org/downloads/)
a stáhni si instalátor nejnovější stabilní verze Pythonu.
Ověř si že je to verze **3.6.0 nebo novější** –
verze 3.6.0 má jistá vylepšení, která budeme v tomto kurzu používat.
diff --git a/lessons/beginners/interfaces/index.md b/lessons/beginners/interfaces/index.md
new file mode 100644
index 00000000..bbef45af
--- /dev/null
+++ b/lessons/beginners/interfaces/index.md
@@ -0,0 +1,115 @@
+# Rozhraní
+
+Už víš že funkce ti umožňují kousek kódu:
+
+* použít (zavolat) na více místech v programu, i když definice je jen jedna,
+* vyčlenit, aby detail (jako načtení čísla od uživatele) „nezavazel“ ve větším
+ programu, který tak může být přehlednější, a
+* pojmenovat, aby bylo jasné co kód dělá i bez toho, abys musel{{a}} číst
+ samotné tělo funkce.
+
+Další výhoda funkce je, že ji můžeš jednoduše vyměnit za jinou,
+lepší funkci – pokud má ta lepší funkce stejné *rozhraní* (angl. *interface*).
+
+Aby se ti líp představovalo, o čem budeme povídat, představ si elektrickou
+zásuvku ve zdi.
+Do takové zásuvky můžeš zapojit počítač, lampu, nabíječku na mobil, vysavač,
+nebo rádio.
+Zásuvka poskytuje elektrický proud; je jedno, jak ho použiješ.
+Stejně tak je jedno jestli je „druhý konec“ zásuvky připojený k solárnímu
+panelu nebo k atomové elektrárně.
+Zásuvka poskytuje elektrický proud, a jsou u ní důležité určité parametry
+(tvar, napětí, frekvence, maximální proud) na kterých se obě strany,
+poskytovatel proudu i spotřebič, shodly.
+
+
+# Funkce jako rozhraní
+
+Podívej se na tuhle hlavičku funkce.
+Víš z ní, co ta funkce dělá a jak ji použít?
+
+```python
+def ano_nebo_ne(otazka):
+ """Zeptá se uživatele na otázku a vrátí True nebo False dle odpovědi"""
+```
+
+Podobnou funkci už jsi napsal{{a}}; víš že „vevnitř“ volá `input` a ptá se na
+příkazové řádce.
+
+Co kdybys ale měla následující funkci?
+
+```python
+def ano_nebo_ne(otazka):
+ """Ukáže tlačítka "Ano" a "Ne" a až uživatel jedno zmáčkne, vrátí True
+ nebo False dle stisknutého tlačítka."""
+```
+
+
+
+Když zavoláš tuhle funkci, `ano_nebo_ne('Chutná ti čokoláda?')`, ukáže se
+okýnko se dvěma tlačítky.
+Když uživatel jedno zmáčkne, funkce vrátí True nebo False.
+
+Z hlediska programu se nic nemění: jediné co se změní je *definice funkce*;
+volání je pak stejné jako dřív.
+
+
+# Vyzkoušej si to!
+
+Najdi nějaký svůj program, který používá `ano_nebo_ne`, případně jen `print`
+a `input`.
+
+Stáhni si modul tkui.py
+do adresáře se svým programem.
+Naimportuj z něho funkce, které potřebuješ.
+Jsou k dispozici čtyři:
+
+```python
+from tkui import input, nacti_cislo, ano_nebo_ne, print
+```
+
+Tento import *přepíše* vestavěné funkce `input` a `print` variantami,
+které mají (téměř) stejné rozhraní – jen dělají něco trochu jinak.
+
+Případné vlastní definice funkcí `nacti_cislo` a `ano_nebo_ne` pak z programu
+vyndej, aby se použily ty naimportované.
+
+> [note]
+> Funkce `tkui.nacti_cislo` potřebuje Python verze 3.7 nebo vyšší.
+> Používáš-li Python 3.6, `nacti_cislo` nenahrazuj.
+
+Program by měl fungovat stejně jako dřív!
+
+Je to tím, že tyto funkce mají stejné rozhraní jako jejich dřívější protějšky,
+tedy:
+
+* jméno, kterým se funkce volá,
+* argumenty, které bere (např. `input` bere otázku jako řetězec; `print`
+ může brát více argumentů k vypsání), a
+* návratovou hodnotu, se kterou program pracuje dál (např. `input` vrací
+ řetězec; `print` nevrací nic smysluplného).
+
+Většina z těchto informací je přímo v hlavičce funkce.
+Ty ostatní je dobré popsat v dokumentačním řetězci, aby ten, kdo chce funkci
+použít, věděl jak na to.
+
+
+# Je to dobrý nápad?
+
+Modul `tkui` je jen ilustrační. Nedoporučuju ho používat.
+
+Příkazová řádka je dělaná tak, aby byla užitečná pro programátory.
+Až se naučíš základy a vytvoříš nějaký skvělý program, přijde čas
+k logice (tzv. *backendu*) přidat část, která bude lépe použitelná pro
+uživatele – tedy okýnko nebo webovou stránku (tzv. *frontend*).
+
+Udělat hezké a funkční *uživatelské* rozhraní je ovšem většinou celkem složité,
+a často se dělá až potom, co jsou samotné „vnitřnosti“ funkční a otestované.
+Doporučuju postupovat stejně, když se programování učíš: zůstaň u základních
+`print` a `input`, dokud nezvládneš samotné programování.
+A pak se můžeš naučit něco nového!
+
+Co si ale z této lekce odnes je koncept rozhraní: při zachování několika
+informací z hlavičky je možné vyměnit funkci za něco úplně jiného.
+A stejně tak je možné jednu funkci (třeba `input`) volat ze spousty různých
+programů, pokud znáš její rozhraní.
diff --git a/lessons/beginners/interfaces/info.yml b/lessons/beginners/interfaces/info.yml
new file mode 100644
index 00000000..c4c4128a
--- /dev/null
+++ b/lessons/beginners/interfaces/info.yml
@@ -0,0 +1,4 @@
+title: Rozhraní
+style: md
+attribution: Pro PyLadies Brno napsal Petr Viktorin, 2020.
+license: cc-by-sa-40
diff --git a/lessons/beginners/interfaces/static/tkui.py b/lessons/beginners/interfaces/static/tkui.py
new file mode 100644
index 00000000..69031ada
--- /dev/null
+++ b/lessons/beginners/interfaces/static/tkui.py
@@ -0,0 +1,162 @@
+"""Modul s funkcemi pro okýnkové otázky a odpovědi:
+
+input(otazka) -> str
+
+nacti_cislo(otazka) -> int
+
+ano_nebo_ne(otazka) -> bool
+
+print(argument0, argument1, argument2, ..., argument_n, sep='')
+
+"""
+
+from tkinter import Tk, LEFT, RIGHT, BOTTOM, TOP, W
+from tkinter.ttk import Label, Button, Entry
+
+# Spinbox byl přidán v Pythonu 3.7
+try:
+ from tkinter.ttk import Spinbox
+except ImportError:
+ Spinbox = None
+
+
+# Následující kód používá několik pokročilejších technik.
+# Není důležité *jak* je kód napsaný, ale že je možné ho napsat – a že daná
+# funkce má dané rozhraní :)
+#
+# Mimochodem; opravdové funkce input a print jsou ještě složitější; viz:
+# https://github.com/python/cpython/blob/ce105541f/Python/bltinmodule.c#L1931
+# https://github.com/python/cpython/blob/ce105541f/Python/bltinmodule.c#L1827
+
+# Funkce používají modul tkinter, který je zabudovaný v Pythonu, ale okýnka
+# s ním vytvořená nevypadají příliš profesionálně.
+# Budeš-li chtít začít psát "okýnkové" programy, doporučuji začít rovnou
+# s knihovnou jako Qt nebo GTK.
+# Na Qt máme mimochodem lekci v pokročilém kurzu, viz:
+# https://naucse.python.cz/course/mi-pyt/intro/pyqt/
+
+
+def input(otazka='odpověz'):
+ """Zeptá se uživatele na otázku a vrátí odpověď jako řetězec."""
+ root = Tk()
+ root.title(otazka)
+
+ button = Button(root, text="OK", command=root.quit)
+ button.pack(side=RIGHT)
+
+ entry = Entry(root)
+ entry.pack(side=LEFT)
+
+ root.mainloop()
+
+ value = entry.get()
+ root.destroy()
+
+ return value
+
+
+def nacti_cislo(otazka='Zadej číslo'):
+ """Zeptá se uživatele na otázku a vrátí odpověď jako celé číslo."""
+ if Spinbox == None:
+ raise NotImplementedError(
+ "nacti_cislo bohužel potřebuje Python verze 3.7 a výš"
+ )
+
+ root = Tk()
+ root.title(otazka)
+
+ entry = Spinbox(root, from_=0, to=100)
+ entry.set('0')
+ entry.pack(side=LEFT)
+
+ # Předbíháme: vnořená funkce může přistupovat
+ # k proměnným "entry" a "root", které jsou
+ # lokální pro "vnější" funkci (nacti_cislo)
+
+ def ok_pressed():
+ text = entry.get()
+ try:
+ value = int(text)
+ except ValueError:
+ entry.set('sem zadej číslo!')
+ else:
+ root.quit()
+
+ button = Button(root, text="OK", command=ok_pressed)
+ button.pack(side=RIGHT)
+
+ root.mainloop()
+
+ value = int(entry.get())
+ root.destroy()
+
+ return value
+
+
+def ano_nebo_ne(otazka='Ano nebo ne?'):
+ """Dá uživateli na výběr Ano/Ne a vrátí odpověď True nebo False."""
+ root = Tk()
+ root.title("Ano nebo ne?")
+
+ value = False
+
+ # Předbíháme: "nonlocal" umožňuje *měnit*
+ # lokální proměnnou z vnější funkce.
+ # (A definujeme tady funkci v rámci jiné funkce, takže problematika
+ # lokálních proměnných je tu ještě složitější než na kurzu.)
+
+ def yes():
+ nonlocal value
+ value = True
+ root.quit()
+
+ def no():
+ nonlocal value
+ value = False
+ root.quit()
+
+ label = Label(root, text=otazka)
+ label.pack(side=TOP, expand=True, padx=20, pady=10)
+
+ button = Button(root, text="Ano", command=yes)
+ button.pack(side=LEFT, expand=True, padx=10, pady=10)
+
+ button = Button(root, text="Ne", command=no)
+ button.pack(side=RIGHT, expand=True, padx=10, pady=10)
+
+ root.mainloop()
+ root.destroy()
+
+ return value
+
+
+
+# Předbíháme: hvězdička v *args umožní že "print" bere proměnný
+# počet argumentů; přes "args" se potom dá projít příkazem "for"
+def print(*args, sep=' ', end='', file=None, flush=False):
+ """Zobrazí dané argumenty."""
+ root = Tk()
+ root.title('print')
+
+ str_args = ''
+ for arg in args:
+ str_args = str_args + sep + str(arg)
+
+ label = Label(root, text=str_args[len(sep):] + end)
+ label.pack(anchor=W)
+
+ button = Button(root, text="OK", command=root.quit)
+ button.pack(side=BOTTOM)
+
+ root.bind('', (lambda e: root.quit()))
+ root.mainloop()
+ root.destroy()
+
+
+# A tady je trik, jak kousek kódu nespustit když se modul importuje.
+# Pro opravdové programy ale doporučuji spouštěcí modul, viz kurz.
+if __name__ == '__main__':
+ print(input())
+ print(ano_nebo_ne())
+ print('a', 'b', 'c', sep='; ', end='-')
+ print(nacti_cislo())
diff --git a/lessons/beginners/interfaces/static/yn.png b/lessons/beginners/interfaces/static/yn.png
new file mode 100644
index 00000000..b3a2a4d1
Binary files /dev/null and b/lessons/beginners/interfaces/static/yn.png differ
diff --git a/lessons/beginners/list/index.md b/lessons/beginners/list/index.md
index 8e796c1d..d378d95e 100644
--- a/lessons/beginners/list/index.md
+++ b/lessons/beginners/list/index.md
@@ -56,7 +56,7 @@ print(seznam)
Důležitá vlastnost seznamů je, že se dají *měnit*.
-Než vysvětlíme o co jde, připomeňme si jak fungují hodnoty, které se měnit
+Než vysvětlíme, o co jde, připomeňme si, jak fungují hodnoty, které se měnit
nedají – např. čísla, řetězce, `True`, `False`, `None`.
Vyzkoušej si následující kousek kódu. Co je na něm „špatně“?
@@ -98,7 +98,7 @@ print(kamaradka)
A jak jsou na tom seznamy?
Ty se měnit dají.
-Základní způsob jak změnit seznam je přidání
+Základní způsob, jak změnit seznam, je přidání
prvku na konec pomocí metody `append`.
Ta *nic nevrací* (resp. vrací `None`), ale „na místě” (angl. *in place*) změní
seznam, se kterým pracuje. Vyzkoušej si to:
diff --git a/lessons/beginners/local-variables/index.md b/lessons/beginners/local-variables/index.md
index f277713f..daa30297 100644
--- a/lessons/beginners/local-variables/index.md
+++ b/lessons/beginners/local-variables/index.md
@@ -93,10 +93,9 @@ Co je to přiřazení? Všechno, co *nastavuje* nějakou proměnnou. Například
> * Přiřazují i speciální přiřazovací operátory jako `+=`, `*=`, `:=`.
-## Zakrývání jména
+## Skrývání jména
Jak to funguje, když ve funkci přiřadíš do proměnné, která existuje i globálně?
-Pak tu máme problém.
Vytvoří se *úplně nová* lokální proměnná, která má stejné jméno jako
ta globální.
@@ -116,7 +115,7 @@ print('Venku: x =', x)
```
V tomto programu existují *dvě* proměnné jménem `x`.
-Jedna je globální. Jedna je lokální pro funkci `nastav_x`.
+Jedna je globální. Druhá je lokální pro funkci `nastav_x`.
Jmenují se stejně, ale jsou to dvě různé proměnné.
diff --git a/lessons/beginners/main-module/index.md b/lessons/beginners/main-module/index.md
new file mode 100644
index 00000000..bc633a34
--- /dev/null
+++ b/lessons/beginners/main-module/index.md
@@ -0,0 +1,166 @@
+# Spouštěcí moduly
+
+Automatické testy musí projít „bez dozoru“.
+V praxi se často automaticky spouští, případné chyby se automaticky
+oznamují (např. e-mailem) a fungující otestovaný kód se automaticky
+začne používat dál (nebo se rovnou vydá zákazníkům).
+
+Co to znamená pro nás?
+Funkce `input` v testech nefunguje. Nemá koho by se zeptala; „za klávesnicí“
+nemusí nikdo sedět.
+
+To může někdy „ztěžovat práci“. Ukážeme si to na složitějším projektu:
+na Kámen-Nůžky-Papír.
+
+Kód pro Kámen-Nůžky-Papír může, velice zjednodušeně, vypadat zhruba takto:
+
+```python
+import random # (příp. import jiných věcí, které budou potřeba)
+
+tah_pocitace = 'kámen'
+tah_hrace = input('Co chceš hrát (kámen, nůžky, papír)? ')
+
+# (tady reálně bude spousta zanořených ifů)
+if tah_hrace == 'papír':
+ print('Vyhrál{{a}} jsi!')
+else:
+ print('Nevyhrál{{a}} jsi...')
+
+```
+
+Když tenhle modul naimportuješ, Python v něm postupně, odshora dolů,
+provede všechny příkazy.
+
+První příkaz, `import`, jen zpřístupní nějaké proměnné a funkce;
+je-li importovaný modul správně napsaný, nemá vedlejší účinek.
+Definice funkcí (příkazy `def` a všechno v nich) podobně jen definují funkce.
+Ale zavoláním funkce `input` se spustí interakce: program potřebuje vstup
+od uživatele.
+
+Importuješ-li tenhle modul z testů, `input` selže a import se nepovede.
+
+> [note]
+> A kdybys modul importoval{{a}} odjinud – například bys chtěl{{a}} tuhle
+> funkčnost použít v nějaké jiné hře – uživatel si bude muset v rámci importu
+> zahrát Kámen-Nůžky-Papír!
+
+Volání funkce `input` je vedlejší efekt.
+Je potřeba ho odstranit.
+Importovatelné moduly by měly pouze dát k dispozici nějaké funkce nebo hodnoty.
+Dej tedy hru do funkce:
+
+```python
+# knp.py -- importovatelný modul
+
+import random # (příp. import jiných věci, které budou potřeba)
+
+def hrej_hru():
+ tah_pocitace = 'kámen'
+ tah_hrace = input('Co chceš hrát (kámen, nůžky, papír)? ')
+
+ # (tady reálně bude spousta zanořených ifů)
+ if tah_hrace == 'papír':
+ print('Vyhrál{{a}} jsi!')
+ else:
+ print('Nevyhrál{{a}} jsi...')
+
+```
+
+No jo, ale po takovém odstranění
+už nejde jednoduše spustit hra! Co s tím?
+
+Můžeš na to vytvořit nový modul, ve kterém bude jenom volání funkce:
+
+```python
+# hra.py -- spouštěcí modul
+
+import knp
+
+knp.hrej_hru()
+```
+
+Tenhle modul nebudeš moci testovat (protože nepřímo volá funkci `input`),
+ale můžeš ho spustit, když si budeš chtít zahrát.
+Protože k němu nemáš napsané testy, nepoznáš
+z nich, když se takový spouštěcí modul rozbije.
+Spouštěcí modul by proto měl být co nejjednodušší – jeden import a jedno volání.
+
+Původní modul teď můžeš importovat bez obav – ať už z testů nebo z jiných
+modulů.
+Pořád se ale, kvůli funkcím `input` a `print`, špatně testuje.
+Aby se testoval líp, můžeš kousek funkčnosti dát do jiné funkce:
+
+```python
+# knp.py -- importovatelný modul
+
+import random # (příp. import jiných věci, které budou potřeba)
+
+def vyhodnot(tah_pocitace, tah_hrace):
+ # (tady reálně bude spousta zanořených ifů)
+ if tah_hrace == 'papír':
+ return 'Vyhrál{{a}} jsi!'
+ else:
+ return 'Nevyhrál{{a}} jsi...'
+
+
+def hrej_hru():
+ tah_pocitace = 'kámen'
+ tah_hrace = input('Kam chceš hrát?')
+
+ vysledek = vyhodnot(tah_pocitace, tah_hrace)
+ print(vysledek)
+```
+
+A vida! Funkce `vyhodnot` teď neobsahuje ani `print` ani `input`.
+Půjde tedy docela jednoduše otestovat:
+
+```python
+# test_knp.py -- testy
+
+import knp
+
+def test_vyhry():
+ assert vyhodnot('kámen', 'papír') == 'Vyhrál{{a}} jsi!'
+ assert vyhodnot('papír', 'nůžky') == 'Vyhrál{{a}} jsi!'
+ assert vyhodnot('nůžky', 'kámen') == 'Vyhrál{{a}} jsi!'
+```
+
+Funkce `hrej_hru` ovšem tak dobře otestovat nejde.
+Musíš ji testovat ručně.
+Protože ale hlavní část programu (`vyhodnot`) jde pokrýt automatickými testy,
+ruční testování nemusí být tak důkladné.
+
+
+
+## Negativní testy
+
+Test `test_vyhry`, ukázaný výše, není úplný.
+Splnila by ho i funkce jako:
+
+```python
+def vyhodnot(tah_pocitace, tah_hrace):
+ return 'Vyhrál{{a}} jsi!'
+```
+
+Kromě „pozitivních“ výsledků je potřeba kontrolovat i ty „negativní“:
+ať už očekávaný negativní výsledek (jako prohru nebo remízu)
+nebo reakci programu na špatné nebo neočekávané podmínky.
+
+Co třeba má dělat volání `vyhodnot(8, 'kukačka')`?
+
+Testy, které kontrolují reakci na „špatný“ vstup,
+se jmenují *negativní testy*.
+Často kontrolují to, že nastane „rozumná“ výjimka.
+
+Na otestování výjimky použij příkaz `with` a funkci `raises` naimportovanou
+z modulu `pytest`.
+Jak příkaz `with` přesně funguje, se dozvíš později;
+teď stačí říct, že ověří, že odsazený blok kódu
+pod ním vyvolá danou výjimku:
+
+```python
+def test_spatneho_tahu():
+ """🤘 vs. 🖖 není správný vstup"""
+ with pytest.raises(ValueError):
+ vyhodnot('metal', 'spock')
+```
diff --git a/lessons/beginners/main-module/info.yml b/lessons/beginners/main-module/info.yml
new file mode 100644
index 00000000..c620c352
--- /dev/null
+++ b/lessons/beginners/main-module/info.yml
@@ -0,0 +1,4 @@
+title: Spouštěcí moduly a Negativní testy
+style: md
+attribution: Pro PyLadies Brno napsal Petr Viktorin, 2014-2020.
+license: cc-by-sa-40
diff --git a/lessons/beginners/modules/index.md b/lessons/beginners/modules/index.md
index 07dc2f44..0432d966 100644
--- a/lessons/beginners/modules/index.md
+++ b/lessons/beginners/modules/index.md
@@ -1,6 +1,6 @@
# Moduly
-Modul je v Pythonu něco, z čeho můžeme importovat.
+Modul je v Pythonu něco, z čeho můžeš importovat.
Třeba z modulu `math` můžeš importovat funkci `sqrt`:
```python
@@ -62,8 +62,7 @@ def popis_stav():
barva=barva_travy, pocet=pocet_kotatek)
```
-
-A pak v dalším souboru, třeba `vypis.py`, napiš:
+A pak do dalšího souboru, třeba `vypis.py`, napiš:
```python
import louka
@@ -71,7 +70,7 @@ import louka
print(louka.popis_stav())
```
-a pak spusť:
+A pak spusť `vypis.py`:
```console
$ python vypis.py
@@ -79,7 +78,7 @@ $ python vypis.py
Příkaz `import` hledá soubory (mimo jiné) v adresáři,
ve kterém je „hlavní modul” programu – tedy soubor,
-který spouštíš (u nás `vypis.py`).
+který spouštíš (tady `vypis.py`).
Oba soubory by proto měly být ve stejném adresáři.
@@ -112,17 +111,26 @@ a zadej v něm:
```
Výpis se objeví jen poprvé.
+Co víc, když teď soubor `louka.py` změníš, změny se do naimportovaného modulu
+nepromítnou.
+Aby se promítly, musíš Python zavřít a spustit znovu.
+(I proto je dobré pouštět programy ze souborů – při každém spuštění se
+znovu načte aktuální verze modulů.)
+
+Ale zpátky k volání `print`.
+Přijde ti trochu divné, že příkaz `import louka` něco vypíše na obrazovku?
-Když takhle modul při importu „dělá“ něco víc, než jen nastavuje proměnné
-a funkce, říká se, že má *vedlejší efekt* (angl. *side effect*).
+Když takhle modul při importu „dělá“ něco víc než jen nastavení proměnných
+a funkcí, říká se, že má *vedlejší efekt* (angl. *side effect*).
Vedlejší efekt může být vypsání něčeho na obrazovku nebo do souboru,
vykreslení okýnka na obrazovku, otázka na uživatele pomocí `input`, atp.
-V modulech připravených na importování se vedlejším efektům vyhýbáme:
+V modulech připravených na importování se vedlejším efektům vyhýbej:
úloha takového modulu je dát k dispozici *funkce*, které něco dělají,
ne to udělat přímo.
Všimni si například, že `import turtle` neukáže okýnko – to se objeví až po
zavolání `turtle.forward()`.
+Importem si programátor připravuje nástroje; teprve zavoláním funkce je používá.
Příkaz `print` proto radši z modulu zase smaž.
diff --git a/lessons/beginners/prefer-return/index.md b/lessons/beginners/prefer-return/index.md
new file mode 100644
index 00000000..f728876a
--- /dev/null
+++ b/lessons/beginners/prefer-return/index.md
@@ -0,0 +1,92 @@
+# Vrátit nebo vypsat?
+
+Podívejme se teď na následující program, který vypíše obsah elipsy:
+
+```python
+from math import pi
+
+def obsah_elipsy(a, b):
+ return pi * a * b
+
+print('Obsah elipsy s poloosami 3 a 5 je', obsah_elipsy(3, 5), 'cm2')
+```
+
+Takový program se teoreticky dá napsat i s procedurou, tedy funkcí, která nic
+nevrací.
+Procedura může výsledek třeba vypsat na obrazovku:
+
+```python
+from math import pi
+
+def obsah_elipsy(a, b):
+ print('Obsah je', pi * a * b) # Pozor, `print` místo `return`!
+
+obsah_elipsy(3, 5)
+```
+
+Program takhle funguje, ale přichází o jednu z hlavních výhod funkcí:
+možnost vrácenou hodnotu použít i jinak než jen v `print`.
+
+Funkci, která *vrací* výsledek, můžeš použít v dalších výpočtech:
+
+```python
+def objem_eliptickeho_valce(a, b, vyska):
+ return obsah_elipsy(a, b) * vyska
+
+print(objem_eliptickeho_valce(3, 5, 3))
+```
+
+... ale s procedurou, která výsledek přímo vypíše, by to nešlo.
+Proto je dobré psát funkce, které spočítané hodnoty vrací,
+a zpracování výsledku (např. vypsání) nechat na kód mimo funkci.
+
+Další důvod, proč hodnoty spíš vracet než vypisovat je ten, že jedna funkce se
+dá použít v různých situacích.
+Proceduru s `print` by nešlo rozumně použít tehdy, když nás příkazová
+řádka vůbec nezajímá – třeba v grafické hře, webové aplikaci nebo pro ovládání
+robota.
+
+Podobně je to se vstupem: když použiju v rámci své funkce `input`, bude se
+moje funkce dát použít jen v situacích, kdy je u počítače klávesnice a za ní
+člověk.
+Proto je lepší funkcím potřebné informace předávat jako argumenty
+a volání `input` (nebo čtení textového políčka či měření čidlem robota)
+nemít ve funkci, ale vně, v kódu, který funkci volá:
+
+```python
+from math import pi
+
+def obsah_elipsy(a, b):
+ """Vrátí obsah elipsy s poloosami daných délek"""
+ # Jen samotný výpočet:
+ return pi * a * b
+
+# print a input jsou "venku":
+x = float(input('Zadej délku poloosy 1: '))
+y = float(input('Zadej délku poloosy 2: '))
+print('Obsah je', obsah_elipsy(x, y))
+```
+
+Samozřejmě existují výjimky: procedura, která přímo vytváří textový výpis
+(např. tabulku), může používat `print`; funkce, která načítá textové informace
+(jako `ano_nebo_ne` výše), zase `input`.
+Když ale funkce něco *počítá*, nebo když si nejsi jist{{gnd('ý', 'á')}},
+je dobré ve funkci `print` ani `input` nemít.
+
+
+## None
+
+Když funkce neskončí příkazem `return`,
+automaticky se vrátí hodnota `None`.
+
+Je to hodnota zabudovaná přímo do Pythonu, podobně jako `True` nebo `False`,
+a znamená „nic“.
+
+```python
+def nic():
+ """Tahle funkce nic nedělá """
+
+print(nic())
+```
+
+Procedury v Pythonu vracejí právě toto „nic“.
diff --git a/lessons/beginners/prefer-return/info.yml b/lessons/beginners/prefer-return/info.yml
new file mode 100644
index 00000000..bf0634f2
--- /dev/null
+++ b/lessons/beginners/prefer-return/info.yml
@@ -0,0 +1,4 @@
+title: Vrátit nebo vypsat?
+style: md
+attribution: Pro PyLadies Brno napsal Petr Viktorin, 2014-2017.
+license: cc-by-sa-40
diff --git a/lessons/beginners/print/index.md b/lessons/beginners/print/index.md
index 4a498356..ef1a4e04 100644
--- a/lessons/beginners/print/index.md
+++ b/lessons/beginners/print/index.md
@@ -1,7 +1,6 @@
# Print a chybové hlášky
-Vytvoř v editoru nový soubor, ulož ho do adresáře pro dnešní lekci
-pod jménem `printing.py` a napiš do něj teď už známý příkaz:
+Otevři si v editoru svůj soubor `ahoj.py`, ve kterém máš napsáno:
```python
print("Ahoj světe!")
@@ -11,46 +10,69 @@ Program spusť:
* pokud ti už na začátku příkazové řádky nesvítí `(venv)`,
aktivuj si virtuální prostředí,
* pomocí `cd` donaviguj do adresáře s programem,
-* zadej `python printing.py`.
+* zadej `python ahoj.py`.
-Funguje? Doufám, že ano; za chvíli ho vylepšíme.
+Funguje? Doufám, že stále ano.
+Zkus přidat další řádek, aby program vypadal takhle:
-## Další příkazy
+```python
+print("Ahoj světe!")
+print("Jak se máš?")
+```
-Zkus do programu postupně, po jednom, přidávat další řádky.
-Po přidání každého dalšího `print` program znovu spusť a vyzkoušej, jestli
-funguje.
+Program spusť znovu.
+Zjistíš, že se vypsaly dva řádky.
-Abys nemusel{{a}} v příkazové řádce stále dokola psát `python printing.py`,
+Abys nemusel{{a}} v příkazové řádce stále dokola psát `python ahoj.py`,
zkus zmáčknout na klávesnici šipku nahoru, ↑.
Vrátíš se tak k předchozímu příkazu, který stačí „odklepnout“ pomocí
Enter.
-Úplně každý příkaz ti asi nebude fungovat hned napoprvé – kdyby program
-dělal něco divného, přeskoč na další sekci, *jak číst chyby*.
+## Jak funguje program
-```python
-print(1)
-print(1, 2, 3)
-print(1 + 1)
-print(3 * 8)
-print(10 - 2.2)
-print(3 + (4 + 6) * 8 / 2 - 1)
-print('*' * 80)
-print("Ahoj" + " " + "PyLadies!")
-print("Součet čísel 3 a 8 je", 3 + 8)
-print('Máma má mísu')
-print(V míse je maso.)
+Teď, když program běží, se můžeme podívat, co se při
+jeho spuštění vlastně děje.
+Je to zatím docela jednoduché: v souboru jsou příkazy,
+příkazy se provádějí jeden po druhém,
+odshora dolů.
+Program je jako recept na vaření nebo návod na skříňku z IKEA:
+seznam instrukcí, které říkají co je potřeba udělat.
+
+Zanedlouho budou tvoje programy mnohem složitější,
+ale základní myšlenka bude stále stejná:
+počítač „čte“ odshora dolů a provádí příkazy jeden po druhém.
+
+
+## Print: vypisování a výrazy
+
+A z jakých že instrukcí se náš „recept“ skládá?
+
+Můžeš použít jakýkoli příkaz, který napíšeš do interaktivního módu Pythonu
+(t.j. za `>>>` v příkazové řádce).
+U programu v souboru ovšem Python nevypisuje výsledek každého zadaného příkazu.
+Když do souboru napíšeš třeba řádek `1 + 2` (bez `print`), Python
+sice spočítá výsledek, ale nikde ho neukáže.
+Proč?
+Velké programy obsahují tisíce příkazů; kdyby se každý mezivýsledek vypisoval,
+nebylo by to vůbec přehledné.
+
+Proto když v programu chceš říct Pythonu aby nějaký výsledek vypsal,
+musíš použít `print`.
+Zkus si {{gnd('sám','sama')}} pustit `python` interaktivně a ověřit,
+že tenhle příkaz funguje stejně jako v programu:
+```pycon
+>>> print("ahoj!")
+ahoj!
+>>> quit()
```
-> [note] Řetězce
-> Proč jsou některé hodnoty v uvozovkách a některé ne?
-> Pokud chceš v Pythonu pracovat s textem, musíš ho obalit do uvozovek, aby Python
-> věděl, že se k němu má chovat jinak než například k číslům.
-> Více se dozvíš později, zatím si zapamatuj, že se takovýto text označuje označuje v programovací
-> hantýrce jako `řetězec`.
+To `print` (a i `quit`) je *funkce*.
+O funkcích se obecně budeme bavit později;
+teď stačí vědět, že když napíšeš `print` a za to něco do závorky,
+Python to vypíše.
+
## Jak číst chyby
@@ -61,12 +83,13 @@ Ale nevěš hlavu, stává se to všem programátorům.
Důležité je vědět, jak chybu najít.
A k tomu ti pomůžou chybové výpisy.
-Pokud program výše opíšeš přesně, vypíše po spuštění následující hlášku:
+Kdybys třeba v příkazu `print(Jak se máš?)` zapomněl{{a}} uvozovky,
+dostaneš následující hlášku:
- File "~/pyladies/02/printing.py", line 11
- print(V míse je maso.)
- ^
+ File "~/naucse-python/01/ahoj.py", line 11
+ print(Jak se máš?)
+ ^
SyntaxError: invalid syntax
@@ -80,6 +103,7 @@ a případně nějaké bližší upřesnění.
> [note] Pro zvídavé
> Jak se od téhle chyby liší ta, která nastane, když zkusíš sečíst číslo a řetězec?
> Nebo když zkusíš dělit nulou?
+> Nebo když zapomeneš jen uvozovku za textem?
Chybové hlášky můžou být ze začátku těžko pochopitelné,
zvyknout se na ně dá asi jenom praxí.
@@ -92,51 +116,49 @@ o pár řádků výš nebo níž:
Python občas nesdílí lidské představy o tom, kde přesně chyba *je*.
Ukáže jen, kde si jí sám *všimnul*.
-V našem případě je chyba v tom, že kolem řetězce *V míse je maso* nejsou uvozovky.
+V našem případě je chyba v tom, že kolem řetězce *Jak se máš?* nejsou uvozovky.
Přidej je a program znovu spusť.
Jestli funguje, gratuluji!
Jinak chybu opět oprav a opakuj, dokud to nebude fungovat :)
-## Jak funguje program
-
-Teď, když program běží, se můžeme podívat, co se při
-jeho spuštění vlastně děje.
-Je to zatím docela jednoduché: příkazy se provádějí jeden po druhém,
-odshora dolů.
-Program je jako recept na vaření: seznam instrukcí, které říkají co je potřeba
-udělat.
-
-Zanedlouho budou tvoje programy připomínat spíš recepty na
-čarodějné lektvary (*počkej do úplňku a pokud je Mars
-v konjunkci s Jupiterem, třikrát zamíchej*),
-ale základní myšlenka je stále stejná:
-počítač „čte“ odshora dolů a provádí příkazy jeden po druhém.
+## Další příkazy
+Zkus do programu postupně, po jednom, přidávat další řádky.
+Po přidání každého dalšího `print` program znovu spusť a vyzkoušej
+co dělá.
-## Print a výrazy
+```python
+print("Ahoj světe!")
+print("Jak se máš?")
-A z jakých že instrukcí se náš „recept“ skládá?
+print(1)
+print(1, 2, 3)
+print(1 + 1)
+print(3 * 8, 10 - 2.2)
+print(3 + (4 + 6) * 8 / 2 - 1)
+print("Součet čísel 3 a 8 je", 3 + 8)
+```
-Ten `print`, který tu celou dobu používáš, je *funkce*.
-O funkcích se ještě budeme bavit později,
-teď stačí vědět, že když napíšeš `print`
-a za to do závorky několik *výrazů* (angl. *expressions*)
-oddělených čárkou, hodnoty těchto výrazů se vypíšou.
+Proč jsou některé hodnoty v uvozovkách a některé ne?
+Pokud chceš v Pythonu pracovat s *textem*, musíš ho obalit do uvozovek,
+aby Python věděl, že se k němu má chovat jinak než k *instrukcím* jako `print`.
+Více se dozvíš později, zatím si zapamatuj, že se takovýto text označuje v programovací
+hantýrce jako `řetězec`.
+Obecně může být v závorkách za `print` několik *výrazů*
+(angl. *expressions*) oddělených čárkou.
A co že je ten výraz?
V našem programu máš několik příkladů:
výraz je číslo, řetězec nebo nějaká (třeba matematická) operace
složená z více výrazů.
Třeba výraz `3 + 8` sčítá výrazy `3` a `8`.
-V sekci o [proměnných]({{ lesson_url('beginners/variables') }}) se
-na výrazy a jejich hodnoty podíváme podrobněji.
-
> [style-note] Typografická vsuvka
> Všimni si stylu zápisu: jako v češtině se po otevírací závorce a před
> uzavírací závorkou nepíše mezera; na rozdíl od češtiny ale mezera není
> mezi `print` a závorkou.
+> Nejde o vsuvku.
> ```python
> print("Ahoj!")
> ```
@@ -152,3 +174,13 @@ na výrazy a jejich hodnoty podíváme podrobněji.
> print(2 + 8)
> print("Jedna a půl je", 1 + 1/2)
> ```
+>
+> Tyhle zásady jsou tu jen proto, aby se program dobře četl ostatním lidem.
+> Samotný Python mezery kolem slov ignoruje (když nejsou v uvozovkách).
+> Můžeš klidně napsat něco jako:
+>
+> ```
+> print ( "Ahoj světe!"
+> )
+> ```
+> Ale budoucí kolegové tě pak nejspíš nebudou mít rádi.
diff --git a/lessons/beginners/recursion/index.md b/lessons/beginners/recursion/index.md
index 2c76edec..4f261ab7 100644
--- a/lessons/beginners/recursion/index.md
+++ b/lessons/beginners/recursion/index.md
@@ -3,8 +3,8 @@
*Rekurze* (angl. *recursion*) je programátorská technika,
kdy funkce volá sebe sama.
-Taková rekurze skončí nekonečným voláním.
-Když zadáš tento program:
+Jednoduchá rekurze skončí nekonečným voláním.
+Zkus zadat tento program:
```python
def rekurzivni_funkce():
@@ -93,11 +93,13 @@ pruzkum(0)
* Zavolá funkci `pruzkum` s hloubkou 10 m:
* Vypíše `Rozhlížím se v hloubce 10 m`
* Zkontroluje, že `10 ≥ 30` (což neplatí)
- * Vypíše `Zanořuju se (na 10 m)`
+ * Vypíše `Zanořuju se (z 10 m)`
* Zavolá funkci `pruzkum` s hloubkou 20 m:
+ * Vypíše `Rozhlížím se v hloubce 20 m`
* Zkontroluje, že `20 ≥ 30` (což neplatí)
- * Vypíše `Zanořuju se (na 20 m)`
- * Zavolá funkci `pruzkum` s hloubkou 30 m:
+ * Vypíše `Zanořuju se (z 20 m)`
+ * Zavolá funkci `pruzkum` s hloubkou 30 m:
+ * Vypíše `Rozhlížím se v hloubce 30 m`
* Zkontroluje, že `30 ≥ 30` (což platí! konečně!)
* Vypíše `Už toho bylo dost!`
* a skončí
diff --git a/lessons/beginners/str/index.md b/lessons/beginners/str/index.md
index 17652bf2..b4ee2105 100644
--- a/lessons/beginners/str/index.md
+++ b/lessons/beginners/str/index.md
@@ -75,7 +75,7 @@ Podobně `4.0` a `4.000` jsou dva zápisy téhož čísla,
tak `'slovo'` a `"slovo"` pro Python označuje stejnou
hodnotu, skládající se ze stejných pěti písmen.
-Použité uvozovky nejsou součástí hodnoty – python si „nepamatuje“, jakým
+Použité uvozovky nejsou součástí hodnoty – Python si „nepamatuje“, jakým
způsobem byl řetězec uvozen.
Když má nějaký řetězec vypsat s uvozovkami, jedny si k tomu vybere – většinou
ty jednoduché:
@@ -150,7 +150,7 @@ Vtom vnuk křik': "Hleď!"
Ve výsledném řetězci pak ovšem žádné zpětné lomítko *není*.
Sekvence `\'` je jen způsob, jak v Pythonu zadat `'` – jediný znak.
Tomu je celkem důležité porozumět.
-Zkus si, jestli zvládneš předpovědět výsledek těchto výrazů:
+Zkus si, jestli zvládneš předpovědět výsledek těchto příkazů:
```pycon
>>> print(".\".")
@@ -170,9 +170,9 @@ Zkus si, jestli zvládneš předpovědět výsledek těchto výrazů:
{% endfilter %}
-Znaků, které se zadávají sekvencí se zpětným lomítkem, je více.
-Jedna ze zajímavějších je `\t`, představující tabulátor – jediný znak, který
-se, když ho vypíšeš, „roztáhne“ na víc mezer.
+Znaků které se zadávají sekvencí se zpětným lomítkem je více.
+Jedním ze zajímavějších je `\t`, představující tabulátor.
+Je to jen jeden znak, ale když ho vypíšeš „roztáhne“ se na víc mezer.
```pycon
>>> print("a\tb") # Výpis "pro lidi"
@@ -183,7 +183,7 @@ a b
3
```
-Se zpětným lomítkem se dá zadat jakýkoli znak – včetně *emoji* – podle jména
+Se zpětným lomítkem můžeš zadat jakýkoli znak – včetně *emoji* – podle jména
(`\N{…}`) nebo identifikačního čísla (`\x..`, `\u....`, `\U........`)
standardu Unicode.
Stačí přesné jméno nebo číslo znát (nebo třeba dohledat na internetu).
@@ -229,9 +229,9 @@ je zápis pro jedno zpětné lomítko.
Někdy potřebuješ řetězce, které obsahují více řádků.
Pythonní řetězce ale můžeš normálně napsat jen na *jeden* řádek.
(Python se tak snaží ulehčit hledání chyby, kdybys koncovou uvozovku
-zapoměl{{a}}.)
+zapomněl{{a}}.)
-Můžeš ale do řetězce znak pro nový řádek vložit pomocí sekvence `\n`:
+Znak pro nový řádek ale můžeš do řetězce vložit pomocí sekvence `\n`:
```pycon
>>> print('Haló haló!\nCo se stalo?')
@@ -254,7 +254,7 @@ jako jakýkoli jiný znak:
## Trojité uvozovky
-Kromě `\n` je i druhý způsob, jak zadat řetězec se znakem nového řádku:
+Kromě `\n` existuje i druhý způsob jak zadat řetězec se znakem nového řádku:
ohraničit ho *třemi* uvozovkami (jednoduchými nebo dvojitými)
na každé straně.
Dají se tak zadávat delší víceřádkové řetězce:
@@ -268,7 +268,20 @@ Prase kozu potrkalo!'''
Pozor na to, že pokud je tenhle řetězec
v odsazeném kódu, každý jeho řádek bude začínat
několika mezerami.
-(V dokumentačních řetězcích tohle nevadí, tam se s odsazením počítá.)
+
+```python
+cislo = 4
+
+if cislo > 0:
+ print("""
+ Výsledek porovnání:
+
+ Číslo je kladné.
+ """)
+```
+
+Víceřádkové řetězce se často používají jako dokumentační řetězce funkcí.
+U nich nevadí, že jsou na začátku řádků mezery.
```python
cislo = 4
@@ -311,6 +324,5 @@ if True:
{# 7, 8, 9, more #}
print(len('C:\new_dir'))
-
-print(len(f'{print}'))
```
+
diff --git a/lessons/beginners/testing/index.md b/lessons/beginners/testing/index.md
index 1370d806..49af20ee 100644
--- a/lessons/beginners/testing/index.md
+++ b/lessons/beginners/testing/index.md
@@ -2,7 +2,7 @@
Programátorská práce nespočívá jen v tom, program napsat.
Důležité je si i ověřit, že opravdu funguje, a případně ho pak opravit.
-Ověřování, že program funguje, se říká *testování* (angl. *testing*).
+Tomu ověřování že program funguje se říká *testování* (angl. *testing*).
Zatím jsi asi svoje programy testoval{{a}} tak, že jsi
je zkusil{{a}} spustit, něco zadal{{a}} a podíval{{a}} se,
@@ -18,7 +18,7 @@ Píšou jiné programy, které jejich výtvory testují za ně.
zkontrolují, že program funguje správně.
Spuštěním testů můžeš kdykoli ověřit, že kód funguje.
Když v otestovaném kódu v budoucnu uděláš nějakou změnu,
-testy ověří, že jsi nerozbil{{a}} nic, co dříve fungovalo.
+testy ověří, že jsi nerozbil{{a}} nic co dříve fungovalo.
## Instalace knihovny pytest
@@ -38,17 +38,24 @@ Knihovny se instalují do aktivního virtuálního prostředí.
Jak se dělá a spouští virtuální prostředí
ses naučil{{a}} při [instalaci Pythonu]({{ lesson_url('beginners/install') }}),
ale teprve teď to začíná být opravdu důležité.
-Ujisti se, že máš virtuální prostředí aktivované.
+Ujisti se, že máš virtuální prostředí aktivované – na začátku příkazové řádky
+máš `(venv)`.
Potom zadej následující příkaz.
(Je to příkaz příkazové řádky, podobně jako
`cd` nebo `mkdir`; nezadávej ho do Pythonu.)
> [warning] Opisuj opatrně!
-> Příkaz níže instaluje software z Internetu.
+> Příkaz níže instaluje software z internetu.
+> Nahrát takovou knihovnu na internet může kdokoli – hodný nebo zlý,
+> chytrý nebo hloupý.
> Za knihovnu `pytest` autoři tohoto kurzu ručí.
-> Jiné knihovny ale můžou dělat neplechu nebo být dokonce „zavirované“.
+> Jiné knihovny ale můžou dělat neplechu nebo být dokonce „zavirované“;
+> už při instalaci můžou něco pokazit.
> Dej si proto pozor a ve jménu `pytest` neudělej překlep!
+>
+> Nainstaluješ-li přesto omylem něco cos nechtěl{{a}}, dej co nejdřív vědět
+> zkušenějšímu programátorovi, aby zkontroloval jaký to mohlo mít efekt.
```console
(venv)$ python -m pip install pytest
@@ -79,22 +86,21 @@ Potom zadej následující příkaz.
Nejdříve si testování ukážeme na jednoduchém příkladu.
Tady je funkce `secti`, která umí sečíst
-dvě čísla, a další funkce, která testuje, jestli se
-`secti` pro určité hodnoty
-chová správně.
-
-Kód si opiš do souboru `test_secteni.py`,
-v novém prázdném adresáři.
-Pro `pytest` je (ve výchozím nastavení)
-důležité, aby jména jak souborů s testy, tak
-samotných testovacích funkcí, začínala na
+dvě čísla, a další funkce, která testuje jestli se
+`secti` pro určité hodnoty chová správně.
+
+Kód si opiš do souboru `test_secteni.py` v novém prázdném adresáři.
+Jméno je důležité: `pytest` ve výchozím nastavení předpokládá,
+že jména jak souborů s testy tak samotných testovacích funkcí začínají na
`test_`.
```python
def secti(a, b):
+ """Vrátí součet dvou čísel"""
return a + b
def test_secti():
+ """Otestuje funkci secti"""
assert secti(1, 2) == 3
```
@@ -170,144 +176,3 @@ Do `test_secteni.py` pak na začátek přidej `from secteni import secti`,
aby byla funkce testu k dispozici.
Test by měl opět projít.
-
-
-## Spouštěcí moduly
-
-Automatické testy musí projít „bez dozoru“.
-V praxi se často automaticky spouští, případné chyby se automaticky
-oznamují (např. e-mailem) a fungující otestovaný kód se automaticky
-začne používat dál (nebo se rovnou vydá zákazníkům).
-
-Co to znamená pro nás?
-Funkce `input` v testech nefunguje. Nemá koho by se zeptala; „za klávesnicí“
-nemusí nikdo sedět.
-
-To může někdy „ztěžovat práci“. Ukážeme si to na složitějším projektu:
-na 1D piškvorkách.
-
-> [note]
-{% if var('coach-present') -%}
-> Nemáš-li hotové 1D piškvorky, následující sekce budou jen teorietické.
-{% endif -%}
-> Učíš-li se z domu, dodělej si Piškvorky než budeš pokračovat dál!
-> Zadání najdeš (prozatím)
-> v [projektech pro PyLadies](http://pyladies.cz/v1/s004-strings/handout/handout4.pdf)
-> na straně 2.
-
-Kód pro 1D Piškvorky může rámcově vypadat zhruba takto:
-
-```python
-import random # (příp. import jiných věcí, které budou potřeba)
-
-def tah(pole, cislo_policka, symbol):
- """Vrátí pole s daným symbolem umístěným na danou pozici"""
- ...
-
-def tah_hrace(pole):
- """Zeptá se hráče kam chce hrát a vrátí pole se zaznamenaným tahem"""
- ...
- input('Kam chceš hrát? ')
- ...
-
-def piskvorky1d():
- """Spustí hru
-
- Vytvoří hrací pole a střídavě volá tah_hrace a tah_pocitace
- dokud někdo nevyhraje"""
- while ...:
- ...
- tah_hrace(...)
- ...
-
-# Puštění hry!
-piskvorky1d()
-```
-
-Když tenhle modul naimportuješ, Python v něm postupně, odshora dolů,
-provede všechny příkazy.
-
-První příkaz, `import`, jen zpřístupní nějaké proměnné a funkce;
-je-li importovaný modul správně napsaný, nemá vedlejší účinek.
-Definice funkcí (příkazy `def` a všechno v nich) podobně jen definují funkce.
-Ale zavoláním funkce `piskvorky1d` se spustí hra:
-funkce `piskvorky1d` zavolá funkci `tah_hrace()` a ta zavolá `input()`.
-
-Importuješ-li tenhle modul z testů, `input` selže a import se nepovede.
-
-> [note]
-> A kdybys modul importoval{{a}} odjinud – například bys chtěl{{a}} funkci
-> `tah` použít v nějaké jiné hře – uživatel si bude muset v rámci importu
-> zahrát Piškvorky!
-
-Volání funkce `piskvorky1d` je vedlejší efekt, a je potřeba ho odstranit.
-No jo, ale po takovém odstranění
-už nejde jednoduše spustit hra! Co s tím?
-
-Můžeš na to vytvořit nový modul.
-Pojmenuj ho `hra.py` a dej do něj jenom to odstraněné volání:
-
-```python
-import piskvorky
-
-piskvorky.piskvorky1d()
-```
-
-Tenhle modul nebudeš moci testovat (protože nepřímo volá funkci `input`),
-ale můžeš ho spustit, když si budeš chtít zahrát.
-Protože k němu nemáš napsané testy, nepoznáš
-z nich, když se takový spouštěcí modul rozbije.
-Měl by být proto nejjednodušší – jeden import a jedno volání.
-
-Původní modul teď můžeš importovat bez obav – ať už z testů nebo z jiných
-modulů.
-Test může vypadat třeba takhle:
-
-```python
-import piskvorky
-
-def test_tah_na_prazdne_pole():
- pole = piskvorky.tah_pocitace('--------------------')
- assert len(pole) == 20
- assert pole.count('x') == 1
- assert pole.count('-') == 19
-```
-
-## Pozitivní a negativní testy
-
-Testům, které kontrolují, že se program za správných podmínek chová správně,
-se říká *pozitivní testy*.
-Můžeš ale testovat i reakci programu na špatné nebo neočekávané podmínky.
-
-Testy, které kontrolují reakci na „špatný“ vstup,
-se jmenují *negativní testy*.
-Můžou kontrolovat nějaký negativní výsledek (např.
-že volání jako cislo_je_sude(7) vrátí `False`),
-a nebo to, že nastane „rozumná“ výjimka.
-
-Například funkce `tah_pocitace` by měla způsobit
-chybu (třeba `ValueError`), když je herní pole už plné.
-
-> [note]
-> Vyvolat výjimku je mnohem lepší než alternativy, např. kdyby takové volání
-> „tiše“ – bez oznámení – zablokovalo celý program.
-> Když kód pak použiješ ve větším programu,
-> můžeš si být jistá, že při špatném volání
-> dostaneš srozumitelnou chybu – tedy takovou,
-> která se co nejsnadněji opravuje.
-
-Na otestování výjimky použij příkaz `with` a funkci `raises` naimportovanou
-z modulu `pytest`.
-Jak příkaz `with` přesně funguje, se dozvíme později;
-teď stačí říct, že ověří, že odsazený blok kódu
-pod ním vyvolá danou výjimku:
-
-```python
-import pytest
-
-import piskvorky
-
-def test_tah_chyba():
- with pytest.raises(ValueError):
- piskvorky.tah_pocitace('oxoxoxoxoxoxoxoxoxox')
-```
diff --git a/lessons/beginners/testing/info.yml b/lessons/beginners/testing/info.yml
index a0ac6e1a..2fe54ecb 100644
--- a/lessons/beginners/testing/info.yml
+++ b/lessons/beginners/testing/info.yml
@@ -1,4 +1,4 @@
title: Testování
style: md
-attribution: Pro PyLadies Brno napsal Petr Viktorin, 2014-2017.
+attribution: Pro PyLadies Brno napsal Petr Viktorin, 2014-2020.
license: cc-by-sa-40
diff --git a/lessons/beginners/variables/index.md b/lessons/beginners/variables/index.md
index b85c9560..9f3bf17f 100644
--- a/lessons/beginners/variables/index.md
+++ b/lessons/beginners/variables/index.md
@@ -26,8 +26,22 @@ a obsah jako S = a².
> na základy programování si vystačíme s matematickými
> znalostmi ze základní školy.
+Pro připomenutí, obvod čtverce se stranou a
+se dá vypočítat jako O = 4a
+a obsah jako S = a².
+Řekněme, že náš čtverec má stranu a = 356 cm.
+
+
+Výsledky vypiš pomocí `print()`.
+Po spuštění by měl program vypsat neco jako:
+
+```
+Obvod čtverce se stranou 356 cm je 1424 cm
+Obsah čtverce se stranou 356 cm je 126736 cm2
+```
+
Výsledky by měl spočítat Python; číslo 1424 nebo 126736 přímo do programu nepiš.
-Jestli si nevíš rady, podívej se na program printing.py
+Jestli si nevíš rady, podívej se na program ahoj.py
z [lekce o `print`]({{ lesson_url('beginners/print') }}), kde jeden řádek dělá něco podobného.
Až budeš mít program hotový, nebo až budeš chtít vyzkoušet rozepsaný kousek,
@@ -38,7 +52,7 @@ spusť ho:
* zadej `python ctverec.py`.
Funguje? Jestli ne, oprav ho a zkus to znovu!
-Když už jsi v příkazové řádce ve správném adresáři, spuštění znamená zadat
+Když už jsi v příkazové řádce ve správném adresáři, spuštění znamená zadat
znovu příkaz `python ctverec.py`.
Abys to nemusel{{a}} celé psát, můžeš se k předchozímu příkazu vrátit
pomocí šipky nahoru, ↑.
@@ -74,7 +88,7 @@ Kdyby byl program delší (několikastránkový),
jak bys zajistil{{a}}, že jedno z těch čísel nepřehlédneš?
Existuje způsob, jak program napsat,
-aniž bys musela pokaždé přepisovat všechna čísla:
+aniž bys musel{{a}} pokaždé přepisovat všechna čísla:
stranu čtverce si „pojmenuješ“ a potom používáš jenom její jméno.
V Pythonu na pojmenovávání hodnot slouží *proměnné* (angl. *variables*).
Používají se takto:
@@ -170,7 +184,7 @@ Detaily si vysvětlíme později;
pro teď to budou kouzelná zaříkadla.
> [warning]
-> Pozor, záleží na typu hodnoty který chceš získat: text nebo číslo.
+> Pozor, záleží na typu hodnoty, který chceš získat: text nebo číslo.
> Vybírej pečlivě!
* Chceš-li načíst **text**, použij:
@@ -191,7 +205,7 @@ pro teď to budou kouzelná zaříkadla.
promenna = float(input('Zadej číslo: '))
```
-Místo textu `'Zadej …'` se dá napsat i jiná výzva.
+Místo textu `'Zadej …'` se dá napsat i jiná výzva.
A výsledek se samozřejmě dá uložit i do jiné proměnné než `promenna`.
Hotový program může vypadat takto:
diff --git a/lessons/beginners/venv-setup/index.md b/lessons/beginners/venv-setup/index.md
index 015e6f65..8e40898a 100644
--- a/lessons/beginners/venv-setup/index.md
+++ b/lessons/beginners/venv-setup/index.md
@@ -1,12 +1,14 @@
-{%- macro sidebyside(titles=['Unix', 'Windows']) -%}
+{%- macro sidebyside(titles=['Unix', 'Windows'], code=True) -%}
{%- endfor -%}
@@ -15,13 +17,15 @@
# Nastavení prostředí
-V této sekci si připravíš adresář, do kterého budeš ukládat soubory
-k začátečnickým kurzům Pythonu, a aktivuješ si virtuální prostředí.
+V této sekci si:
+* připravíš adresář, do kterého budeš ukládat soubory k začátečnickým kurzům
+ Pythonu a
+* aktivuješ virtuální prostředí.
## Příprava adresáře
-Programátoři vytváří spoustu souborů, a víc než u mnoha jiných uživatelů
-počítače záleží na tom, kde jsou ty soubory uložené.
+Programátoři vytváří spoustu souborů a víc než u mnoha jiných uživatelů
+počítače jim záleží na tom, kde jsou ty soubory uložené.
Níže uvedený postup zdaleka není jediná možnost, jak si organizovat soubory.
Když ale použiješ tenhle ozkoušený způsob,
@@ -46,8 +50,10 @@ Proto ho nedoporučuji vytvářet na Ploše.
> přestane fungovat *virtuální prostředí*, které za chvíli vytvoříme.
> Musel{{a}} bys ho smazat a vytvořit nové.
-Po vytvoření adresáře si poznamenej, kde přesně je.
-Budeš ho potřebovat na celý zbytek kurzu i na případné navazující kurzy.
+Tenhle adresář budeš potřebovat na celý zbytek kurzu i na případné
+navazující kurzy.
+Poznamenej si proto kde přesně je – zkopíruj si celé jeho jméno, které pak
+můžeš vložit do `cd` v příkazové řádce nebo do grafického prohlížeče souborů.
### Adresář pro každou lekci
@@ -58,19 +64,19 @@ mít obsah zorganizovaný.
Pro začátek si budeme tvořit nový podadresář pro každou lekci tohoto kurzu.
Aby byly tyhle adresáře hezky seřazené, budeme je číslovat:
-tahle první lekce bude mít číslo `01`,
-příště si vytvoříš adresář `02` a tak dále.
+tahle první lekce bude mít číslo `00`,
+příště si vytvoříš adresář `01` a tak dále.
Všechny budou v tvém novém adresáři, který jsi vytvořil{{a}} před chvilkou.
-Adresář `01` si vytvoř už teď.
+Adresář `00` si vytvoř už teď.
(Možná do něj dnes nic nedáš, ale hodí se ho mít jako ukázku pro příště.)
### Přepnutí
Pak otevři příkazovou řádku a příkazem `cd` přepni do adresáře,
-ve kterém jsi právě vytvořila `01` (t.j. ne přímo do `01`).
+ve kterém jsi právě vytvořil{{a}} `00` (t.j. ne přímo do `00`).
Například:
```console
@@ -81,7 +87,7 @@ Pak zkontroluj, že jsi na správném místě:
* Pomocí příkazu `pwd` (na Windows `cd`) zkontroluj,
že opravdu jsi v nově vytvořeném adresáři.
* Pomocí příkazu `ls` (na Windows `dir`) zkontroluj,
- že v něm je podadresář `01`.
+ že v něm je podadresář `00`.
Například:
@@ -90,47 +96,48 @@ $ pwd
/home/helena/{{rootname}}
$ ls
-01
+00
---
> cd
C:\Users\Helena\{{rootname}}
> dir
Directory of C:\Users\Helena\{{rootname}}
-05/08/2014 07:28 PM 01
+05/08/2014 07:28 PM 00
{% endcall %}
-{% if var('coach-present') -%}
-Výsledek pro kontrolu ukaž koučovi.
-{%- endif %}
-
## Virtuální prostředí
-Teď nainstalujeme *virtuální prostředí* pro Python.
+Teď si vytvoříš *virtuální prostředí* pro Python.
-Virtuální prostředí je něco, co nám zajistí, že se všechny počítače budou
+Virtuální prostředí je něco, co zajistí že se všechny počítače budou
chovat zhruba stejně.
-Až ho zprovozníme, nebudeme potřebovat instrukce zvlášť pro Linux,
+Až ho zprovozníme, nebudu už potřebovat instrukce zvlášť pro Linux,
zvlášť pro Windows a zvlášť pro macOS.
> [note]
-> V budoucnu využijeme druhou výhodu: každé virtuální prostředí je oddělené od
-> ostatních, takže když doinstalujeme nějakou knihovnu (rozšíření pro Python),
+> V budoucnu využiješ i druhou výhodu: každé virtuální prostředí je oddělené od
+> ostatních, takže když doinstaluješ nějakou knihovnu (rozšíření pro Python),
> projeví se to jen v jednom virtuálním prostředí.
-> Pokud by se při práci na projektu něco pokazilo, neohrozí to další projekty
-> ve tvém počítači.
+> Kdyby se pak při práci na projektu něco pokazilo, neohrozí to další
+> projekty ve tvém počítači.
Jak na to?
Na každém systému jinak!
-* normální **Linux** (pokud jsi přeskočil{{a}} instalaci Virtualenv):
+* **Linux**:
+
+ Podle toho jak máš Python nainstalovaný bude fungovat jeden z následujících
+ příkazů.
+ Bude je rychlejší vyzkoušet než popsat kdy je který správný,
+ takže nejdřív zkus:
```console
$ python3 -m venv venv
```
-* starší **Linux** (pokud jsi musel{{a}} instalovat Virtualenv):
+ A jestli dostaneš chybu `No module named venv`, zkus místo toho:
```console
$ virtualenv -p python3 venv
@@ -144,14 +151,25 @@ Na každém systému jinak!
* **Windows**:
+ Podle toho jak máš Python nainstalovaný bude fungovat jeden z následujících
+ příkazů.
+ Bude je rychlejší vyzkoušet než popsat kdy je který správný,
+ takže nejdřív zkus:
+
```doscon
> py -3 -m venv venv
```
+ A jestli dostaneš chybu `'py' is not recognized`, zkus místo toho:
+
+ ```doscon
+ > python3 -m venv venv
+ ```
+
Tím se ti vytvořil adresář `venv`, který virtuální prostředí obsahuje.
Můžeš se podívat dovnitř, ale neukládej tam své soubory a nikdy tam nic neměň!
-Zkontroluj si, že `01` a `venv` jsou pěkně vedle sebe:
+Zkontroluj si, že `00` a `venv` jsou pěkně vedle sebe:
{% call sidebyside(titles=['Unix', 'Windows']) %}
$ ls
@@ -160,30 +178,37 @@ venv
---
> dir
Directory of C:\Users\Helena\{{rootname}}
-05/08/2014 07:28 PM 01
+05/08/2014 07:28 PM 00
05/08/2014 07:38 PM venv
{% endcall %}
V grafickém prohlížeči souborů to vypadá např. takto:
{{ figure(
- img=static('dirs.png'),
- alt="(adresáře '01' a 'venv' vedle sebe)",
+ img=static('dirs-00.png'),
+ alt="(adresáře '00' a 'venv' vedle sebe)",
) }}
-{% if var('coach-present') -%}
-Výsledek pro kontrolu ukaž koučovi.
-{%- endif %}
-
### Aktivace virtuálního prostředí
Nakonec virtuální prostředí aktivuj:
-{% call sidebyside(titles=['Unix', 'Windows']) %}
+{% call sidebyside(titles=['Unix', 'Windows'], code=False) %}
+```console
$ source venv/bin/activate
+```
---
+```doscon
> venv\Scripts\activate
+```
+
+Jestli používáš příkazovou řádku ve Visual Studio Code,
+je příkaz pro Windows složitější:
+```doscon
+> &powershell -ExecutionPolicy bypass
+> venv/Scripts/Activate.ps1
+```
{% endcall %}
Po spuštění tohoto příkazu by se mělo na začátku příkazové řádky
@@ -191,10 +216,7 @@ Po spuštění tohoto příkazu by se mělo na začátku příkazové řádky
Tak poznáš, že je virtuální prostředí *aktivní*.
Aktivační příkaz si zapiš.
-Bude potřeba ho zadat vždycky, když pustíš příkazovou řádku,
-ve které budeš zkoušet své programy.
-{% if var('pyladies') %}
-Máš-li vytištěné domácí projekty,
-příkaz si poznač tam, ať ho do příště nezapomeneš :)
-{% endif %}
+Vždycky, když pustíš příkazovou řádku ve které budeš zkoušet své programy,
+budeš muset pomocí `cd` přepnout do `{{rootname}}` a zadat tento
+aktivační příkaz.
diff --git a/lessons/beginners/venv-setup/static/dirs-00.png b/lessons/beginners/venv-setup/static/dirs-00.png
new file mode 100644
index 00000000..0e3ed321
Binary files /dev/null and b/lessons/beginners/venv-setup/static/dirs-00.png differ
diff --git a/lessons/beginners/while/index.md b/lessons/beginners/while/index.md
index 3c7015a1..753426fa 100644
--- a/lessons/beginners/while/index.md
+++ b/lessons/beginners/while/index.md
@@ -36,14 +36,52 @@ while True:
Program se dá přerušit zmáčknutím
Ctrl+C.
-
-> [note]
-> Tahle klávesová zkratka vyvolá v programu chybu
-> a program se – jako po každé chybě – ukončí.
+Tahle klávesová zkratka vyvolá v programu chybu
+a program se – jako po každé chybě – ukončí.
+
+> [note] Pro macOS
+> I na Macu je to opravdu Ctrl+C, nikoli
+> ⌘ Cmd+C.
+
+> [note] Pro ostatní systémy
+> Ctrl+C je velice stará zkratka, zavedená ještě před
+> grafickými programy které ji začaly používat pro kopírování.
+> Když dnes používáš textové programy v okýnku,
+> musíš pro kopírování použít složitější zkratky:
+>
+> * Kopírovat:
+> Shift+Ctrl+C nebo
+> Ctrl+Insert
+>
+> * Vložit:
+> Shift+Ctrl+V nebo
+> Shift+Insert
+>
+> Případně můžeš najít příslušné operace v menu.
+> Na Windows se menu skrývá pod ikonkou v levém horním rohu okýnka;
+> funkce pro kopírování jsou v podmenu Edit.
+> Na starších Windows tam najdeš i *Mark*, způsob jak označit text.
+>
+> A na Linuxu jde jen označit text a pak ho (bez Ctrl+C)
+> vložit prostředním tlačítkem myši.
+
+## Break
A nakonec, existuje příkaz `break`, který z cyklu „vyskočí“:
začnou se hned vykonávat příkazy za cyklem.
+Cyklus `while` se dívá na podmínku jen na začátku každého průchodu cyklem.
+Občas ale chceš podmínku uprostřed nebo na konci.
+Třeba „zubařský“ program výše by se dal napsat i takto:
+
+* Opakuj:
+ * Zeptej se na odpověd
+ * Je-li odpověď „Ááá“, zavtipkuj a ukonči cyklus
+ * Vynadej uživateli. (Sem se program dostane jen při špatné odpovědi.)
+* Dokonči operaci
+
+V překladu do Pythonu využiješ kombinace `if` a `break`:
+
```python
while True:
odpoved = input('Řekni Ááá! ')
@@ -66,43 +104,3 @@ for i in range(10): # Vnější cyklus
break
print()
```
-
-Ale zpátky k `while`!
-Dokážeš napsat tenhle program?
-
-## Oko bere
-
-* Začínáš s 0 body.
-* Počítač v každém kole vypíše, kolik máš bodů,
- a zeptá se tě, jestli chceš pokračovat.
-* Pokud odpovíš „ne“, hra končí.
-* Pokud odpovíš „ano“, počítač „otočí kartu“
- (náhodně vybere číslo od 2 do 10), vypíše její hodnotu a přičte ji k bodům.
-* Pokud máš víc než 21 bodů, prohráváš.
-* Cílem hry je získat co nejvíc bodů, ideálně 21.
-
-{% filter solution %}
-```python
-from random import randrange
-
-soucet = 0
-while soucet < 21:
- print('Máš', soucet, 'bodů')
- odpoved = input('Otočit kartu? ')
- if odpoved == 'ano':
- karta = randrange(2, 11)
- print('Otočil{{a}} jsi', karta)
- soucet = soucet + karta
- elif odpoved == 'ne':
- break
- else:
- print('Nerozumím! Odpovídej "ano", nebo "ne"')
-
-if soucet == 21:
- print('Gratuluji! Vyhrál{{a}} jsi!')
-elif soucet > 21:
- print('Smůla!', soucet, 'bodů je moc!')
-else:
- print('Chybělo jen', 21 - soucet, 'bodů!')
-```
-{% endfilter %}
diff --git a/lessons/beginners/with/index.md b/lessons/beginners/with/index.md
new file mode 100644
index 00000000..36233c92
--- /dev/null
+++ b/lessons/beginners/with/index.md
@@ -0,0 +1,62 @@
+
+# Kontext: `with` a `finally`
+
+> [note]
+> Čteš-li tyto materiály poprvé, tuto sekci můžeš s klidným svědomím přeskočit.
+> Pokročilejším ale doporučuju vsadit věci do širšího kontextu.
+
+Příkaz `with` pracuje s tzv. *kontextem* (tady s kontextem *otevřeného
+souboru*), který má začátek a konec a při ukončení je potřeba něco udělat
+(tady zavřít soubor).
+
+Kontext je v podstatě zkratka pro `try`/`finally`. Pamatuješ si na `finally`?
+
+Toto:
+
+```python
+with open('basnicka.txt', encoding='utf-8') as soubor:
+ # Zpracování souboru
+ obsah = soubor.read()
+```
+
+je zkratka pro:
+
+```python
+soubor = open('basnicka.txt', encoding='utf-8')
+try:
+ # Zpracování souboru
+ obsah = soubor.read()
+finally:
+ # Ukončení kontextu
+ soubor.close()
+```
+
+Jak `with` tak `finally` zaručí, že se soubor vždy uzavře:
+když se zpracování povede, ale i když v něm nastane výjimka,
+nebo když z něj vyskočíš pomocí `return` nebo `break`:
+
+```python
+def nacti_cele_cislo(jmeno_souboru):
+ with open(jmeno_souboru, encoding='utf-8') as soubor:
+ return int(soubor.read())
+ # I když "return" vyskočí z funkce (nebo int() zbůsobí ValueError),
+ # soubor se zavře.
+
+
+# Pro vyzkoušení napiš do souboru 'cislo.txt' nějaké číslo.
+print(nacti_cele_cislo('cislo.txt') * 11)
+```
+
+
+## Kontrola výjimek
+
+Chování příkazu `with` závisí na objektu, se kterým jej použiješ.
+Pro soubor – výsledek funkce `open` – se soubor na konci bloku zavře.
+Podobných „samozavíracích“ objektů které se dají použít s `with` existuje
+v různých knihovnách víc.
+Typické jsou objekty, které se starají o připojení např. k jinému počítači
+nebo k databázi, kdy je po práci dobré spojení ukončit a „uklidit po sobě“.
+
+Z lekce o testování si možná pamatuješ `with pytest.raises(...):`.
+Výsledek `pytest.raises` na konci bloku `with` kontroluje, že v rámci bloku
+nastala správná výjimka.
diff --git a/lessons/beginners/with/info.yml b/lessons/beginners/with/info.yml
new file mode 100644
index 00000000..a13678b0
--- /dev/null
+++ b/lessons/beginners/with/info.yml
@@ -0,0 +1,4 @@
+title: "Kontext: with a finally"
+style: md
+attribution: Pro PyLadies Brno napsal Petr Viktorin, 2014-2021.
+license: cc-by-sa-40
diff --git a/lessons/fast-track/bool/index.md b/lessons/fast-track/bool/index.md
new file mode 100644
index 00000000..b15bd44d
--- /dev/null
+++ b/lessons/fast-track/bool/index.md
@@ -0,0 +1,160 @@
+# Porovnávání věcí
+
+Programátoři často porovnávají různé hodnoty. Pojďme se podívat jak na to.
+
+``` pycon
+>>> 5 > 2
+True
+>>> 5 > 8
+False
+>>> 5 < 8
+True
+```
+
+Když se Pythonu zeptáš, jestli je jedno číslo větší než druhé, odpoví ti
+`True` (pravda) nebo `False` (nepravda).
+
+Funguje to i se složitějšími výrazy:
+
+``` pycon
+>>> 5 > 3 * 2
+False
+```
+
+„Větší než“ a „menší než“ jsou značky známé z matematiky.
+Chceš-li se ale zeptat, jestli jsou dvě čísla stejná, je potřba použít
+trochu jiný zápis:
+
+``` pycon
+>>> 1 == 1
+True
+```
+
+Jedno rovnítko `=` používáme pro *přiřazení* hodnoty do proměnné.
+Když chceš zkontrolovat, jestli se věci navzájem *rovnají*, vždy, **vždy**
+musíš dát dvě rovnítka `==`.
+
+Další možnosti porovnávání jsou nerovnost (≠), větší nebo rovno (≤)
+a meší nebo rovno (≥).
+Většina lidí tyhle symboly nemá na klávesnici, a tak Python používá `!=`, `<=`
+a `>=`.
+
+``` pycon
+>>> 5 != 2
+True
+>>> 3 <= 2
+False
+>>> 6 >= 12 / 2
+True
+```
+
+Už jsi někdy slyšel{{a}} výraz „srovnávat jablka a hrušky“? Zkusme v Pythonu ekvivalent:
+
+``` pycon
+>>> 1 > 'krajta'
+Traceback (most recent call last):
+ File "", line 1, in
+TypeError: '>' not supported between instances of 'int' and 'str'
+```
+
+Stejně jako nelze srovnávat „jablka a hrušky“,
+Python není schopen porovnávat řetězce (`str`) a čísla (`int`).
+Místo toho zobrazí `TypeError` a říká nám, že tyto dva typy nelze porovnat.
+
+Co se stane, když v minulé ukázce zaměníš `>` za `==`?
+
+{% filter solution %}
+```pycon
+>>> 1 == 'krajta'
+False
+```
+
+Jablka a hrušky nemůžeš porovnávat, ale můžeš si potvrdit že jsou to dvě různé
+věci.
+{% endfilter %}
+
+
+## Logika
+
+Chceš zkusit ještě něco? Zadej tohle:
+
+``` pycon
+>>> 6 > 2 and 2 < 3
+True
+>>> 3 > 2 and 2 < 1
+False
+>>> 3 > 2 or 2 < 1
+True
+>>> not 3 > 2
+False
+```
+
+V Pythonu můžeš zkombinovat několik porovnání do jednoho!
+
+* Pokud použiješ operátor `and`, obě strany musí být pravdivé, aby byl celý výraz pravdivý.
+* Pokud použiješ operátor `or`, stačí aby jen jedna strana z porovnání byla pravdivá.
+* Operátor `not` “obrátí” výsledek porovnání.
+
+
+## Přítomnost
+
+Nebylo by pěkné zjistit, jestli tvoje číslo vyhrálo v loterii?
+Máš-li seznam, operátorem `in` se můžeš zeptat, jestli je v něm daný prvek:
+
+``` pycon
+>>> loterie = [3, 42, 12, 19, 30, 59]
+>>> 18 in loterie
+False
+>>> 42 in loterie
+True
+```
+
+Není to úplně porovnání, ale dostaneš stejný druh výsledku jako s `<` či `==`.
+
+
+## Pravdivostní hodnoty
+
+Právě ses dozvěděl{{a}} o novém typu objektu v Pythonu.
+Už známe typy řetězc, číslo, seznam nebo slovník; přidali jsme k nim
+*pravdivostní hodnotu*, nebo častěji anglicky *boolean*.
+
+Pravdivostní hodnoty jsou jenom dvě: `True` (pravda) nebo `False` (nepravda).
+
+Aby Python pochopil, že se jedná o tento typ,
+je potřeba dávat pozor na velikost písmen.
+`true`, `TRUE`, `tRUE` nebude fungovat – jedině `True` je správně.
+
+Jako každou hodnotu, i *boolean* můžeš uložit do proměnné:
+
+``` pycon
+>>> a = True
+>>> a
+True
+```
+
+Stejně tak můžeš uložit i výsledek porovnání:
+
+``` pycon
+>>> a = 2 > 5
+>>> a
+False
+```
+
+A všechno to můžeš použít v logických výrazech:
+
+``` pycon
+>>> a and True
+False
+```
+
+
+
+## Shrnutí
+
+V této sekci ses dozvěděl{{a}}:
+
+* V Pythonu můžeš **porovnávat** pomocí operátorů `>`, `>=`, `==` `<=`, `<`, `!=` a `in`
+* Operátory `and` a `or` umí **zkombinovat** dvě pravdivostní hodnoty.
+* Operátor `not` umí **obrátit** pravdivostní hodnotu.
+* **Boolean** (pravdivostní hodnota) je typ, který může mít jednu ze dvou
+ hodnot: `True` (pravda) nebo `False` (nepravda).
diff --git a/lessons/fast-track/bool/info.yml b/lessons/fast-track/bool/info.yml
new file mode 100644
index 00000000..f6696deb
--- /dev/null
+++ b/lessons/fast-track/bool/info.yml
@@ -0,0 +1,12 @@
+title: Porovnávání a logika
+style: md
+attribution:
+- Založeno na materiálech [DjangoGirls](https://djangogirls.org/).
+- Část této kapitoly je založena na kurzu [Geek Girls Carrots](https://github.com/ggcarrots/django-carrots).
+- |
+ Původní DjangoGirls tutoriál přeložila do češtiny skupina dobrovolníků.
+ Poděkování patří hlavně: Davidovi (dakf), Kristýně Kumpánové,
+ Veronice Gabrielové, Tomáši Ehrlichovi, Aničce Jaegerové,
+ Matějovi Stuchlíkovi, Filipovi Sivákovi a Juraji M. Bezručkovi.
+- Pro PyLadies CZ upravil Petr Viktorin, 2018.
+license: cc-by-sa-40
diff --git a/lessons/fast-track/class/index.md b/lessons/fast-track/class/index.md
new file mode 100644
index 00000000..c2075b3d
--- /dev/null
+++ b/lessons/fast-track/class/index.md
@@ -0,0 +1,161 @@
+# Třídy
+
+Už ses seznámila se spoustou *tříd* objektů, se kterými v Pythonu můžeš
+pracovat: s čísly, která se dají třeba sečítat a násobit; řetězci, která se
+dají převádět na velká písmenka; seznamy, které umíš seřadit; slovníky,
+ve kterých se dá vyhledávat.
+Až budeš ve studiu programování pokračovat dál, objevíš další třídy objektů:
+soubory, ze kterých se dá číst; Webová stránky, které se dají poslat do
+prohlížeče; tlačítka, která jdou zmáčknout, a tak dále.
+Tříd je nepřeberné množství.
+
+A podobně jako si můžeš nadefinovat funkci pomocí `def`, i novou třídu si můžeš
+vytvořit {{gnd('sám', 'sama')}}.
+Používá se to hlavně tehdy, když v programu potřebuješ hodně objektů, které
+mají společné chování.
+Celá čísla, objekty třídy `int`, mají různou hodnotu ale všechna jdou sčítat.
+Každý seznam, objekt třídy `list`, může mít jiný obsah, ale všechny seznamy
+jsou seřadit.
+
+Úkol pro tuto sekci bude vytvořit třídu *koťátek*, která můžou mít různá jména,
+ale všechna umí mňoukat a jíst.
+
+Začni mňoukáním:
+
+```python
+class Kotatko:
+ def zamnoukej(self):
+ print("Mňau!")
+```
+
+Tak jako se funkce definují pomocí `def`,
+třídy mají klíčové slovo `class`,
+za které napíšeš jméno třídy, dvojtečku a pak odsazené tělo třídy.
+Podobně jako `def` dělá funkce, příkaz
+`class` udělá novou třídu a přiřadí ji
+do proměnné daného jména (tady `Kotatko`).
+
+Třídy se tradičně pojmenovávají s velkým písmenem,
+aby se nepletly s „normálními“ hodnotami.
+
+> [note]
+> Základní třídy (`str`, `int` atd.)
+> velká písmena nemají, a to hlavně z historických
+> důvodů – původně to byly opravdu funkce.
+
+V těle třídy můžeš definovat metody, které vypadají
+úplně jako funkce – jen mají první argument `self`.
+Ten si ale vysvětlíme později – napřed zkus zamňoukat:
+
+```python
+# Vytvoření konkrétního objektu
+mourek = Kotatko()
+
+# Volání metody
+mourek.zamnoukej()
+```
+
+Když definuješ třídu (pomocí bloku `class`), neznamená to zatím, že v tvém
+programu je nějaké koťátko.
+Třída je jako recept nebo manuál: když si koupíš kuchařku, budeš teoreticky
+vědět jak upéct dort, jak bude takový dort vypadat a že se dá sníst.
+Ale neznamená to ještě, že máš samotný dort!
+
+Konkrétní objekt vytvoříš až zavoláním třídy: použiješ třídu jako funkci,
+`Kotatko()`, a výsledek je nový objekt tvé třídy, který už můžeš použít.
+
+Mňau!
+
+## Atributy
+
+U objektů vytvořených z „vlastních“ tříd můžeš nastavovat
+*atributy* – informace, které se uloží k danému objektu.
+Atributy se označují tak, že mezi hodnotu a jméno
+jejího atributu napíšeš tečku:
+
+```python
+mourek = Kotatko()
+mourek.jmeno = 'Mourek'
+print(mourek.jmeno)
+```
+
+Řetězec `'Mourek'` teď „patří“ konkrétnímu koťátku.
+Když vytvoříš další koťátko, můžeš ho pojmenovat jinak – nastavit mu
+atribut `jmeno` na jiný řetězec.
+
+```python
+micka = Kotatko()
+micka.jmeno = 'Micka'
+
+print(micka.jmeno)
+print(mourek.jmeno)
+```
+
+## Parametr `self`
+
+Teď se na chvíli vraťme k metodám. Konkrétně k parametru `self`.
+
+Každá metoda má přístup ke konkrétnímu objektu, na
+kterém pracuje, právě přes argument `self`.
+Teď, když máš koťátka pojmenovaná, můžeš v metodě `zamnoukej` použít `self`
+a dostat se tak ke jménu daného koťátka:
+
+```python
+class Kotatko:
+ def zamnoukej(self):
+ print("{}: Mňau!".format(self.jmeno))
+
+mourek = Kotatko()
+mourek.jmeno = 'Mourek'
+
+micka = Kotatko()
+micka.jmeno = 'Micka'
+
+mourek.zamnoukej()
+micka.zamnoukej()
+```
+
+Co se stalo? Výraz `mourek.zamnoukej` udělá *metodu*.
+Když ji pak zavoláš (`mourek.zamnoukej()`),
+objekt `mourek` se předá funkci `zamnoukej` jako první argument, `self` .
+
+Může taková metoda brát víc než jeden argument?
+Může – `self` se doplní na první místo,
+zbytek argumentů se vezme z volání metody.
+Třeba:
+
+```python
+class Kotatko:
+ def zamnoukej(self):
+ print("{}: Mňau!".format(self.jmeno))
+
+ def snez(self, jidlo):
+ print("{}: Mňau mňau! {} mi chutná!".format(self.jmeno, jidlo))
+
+mourek = Kotatko()
+mourek.jmeno = 'Mourek'
+mourek.snez('ryba')
+```
+
+## Shrnutí
+
+Třídy toho umí mnohem víc, ale základ: všechny objekty dané třídy mají nějaké
+společné chování (třeba koťátka umí mňoukat).
+A zároveň každý objekt má i vlastní informace, jen pro něj (třeba koťátko
+mňoukání).
+
+Vlastní třídu se vyplatí napsat, když v programu máš víc objektů s podobným
+chováním, anebo když jen potřebuješ mít nějakou sadu funkcí (resp. metod) pěkně
+pohromadě.
+
+
+A je to.
+*Jsi naprosto skvěl{{gnd('ý', 'á')}}!*
+Tohle byla složitá lekce, takže bys na sebe měl{{a}} být hrd{{gnd('ý', 'á')}}.
+My jsme na tebe velmi hrdí za to, že ses dostal{{a}} tak daleko!
+
+Běž si krátce odpočinout – protáhnout se, projít se,
+zavřít oči – než se pustíš do další kapitoly. :)
+
+🧁
+
diff --git a/lessons/fast-track/class/info.yml b/lessons/fast-track/class/info.yml
new file mode 100644
index 00000000..89b2ef0a
--- /dev/null
+++ b/lessons/fast-track/class/info.yml
@@ -0,0 +1,4 @@
+title: Třídy
+style: md
+attribution: Pro PyLadies Brno napsal Petr Viktorin, 2015-2019.
+license: cc-by-sa-40
diff --git a/lessons/fast-track/cmdline/index.md b/lessons/fast-track/cmdline/index.md
new file mode 100644
index 00000000..4e9b54cb
--- /dev/null
+++ b/lessons/fast-track/cmdline/index.md
@@ -0,0 +1,312 @@
+{%- macro sidebyside(titles=['Unix', 'Windows']) -%}
+
+{%- endmacro -%}
+
+{%- if var('pyladies') -%}
+{% set purpose = 'PyLadies' %}
+{% set dirname = 'pyladies' %}
+{%- else -%}
+{% set purpose = 'Python' %}
+{% set dirname = 'naucse-python' %}
+{%- endif -%}
+
+
+# Příkazová řádka
+
+Většina uživatelů ovládá počítač v *grafickém rozhraní* – myší nebo prstem
+kliká na ikonky, vybírá příkazy z menu a kouká na animace.
+Programátoři ale často ovládají počítač *textově*, v příkazové řádce:
+napíšou příkaz nebo otázku a přečtou si případnou odpověď.
+Někteří to nemají moc rádi (příkazy je potřeba si pamatovat), někteří si
+to užívají (textové příkazy lze jednoduše opakovat a automatizovat),
+ale fakt je, že bez základní znalosti příkazové řádky se programátor neobejde.
+
+Seznamme se tedy se způsobem, který programátoři používají na zadávání příkazů.
+
+Příkazová řádka (respektive program, kterému se říká i *konzole* či *terminál*;
+anglicky *command line*, *console*, *terminal*)
+se na různých systémech otevírá různě:
+
+* Windows (české): Start → napsat na klávesnici „cmd“ → Příkazový řádek
+* Windows (anglické): Start → napsat na klávesnici „cmd“ → Command Prompt
+* macOS (anglický): Applications → Utilities → Terminal
+* Linux (GNOME): Menu Aktivity (levý horní roh) → hledat Terminál
+* Linux (KDE): Hlavní Menu → hledat Konsole
+
+Nevíš-li si rady, zkus
+{% if var('coach-present') -%}
+se zeptat kouče.
+{%- else -%}
+buď googlit, nebo se zeptat e-mailem.
+{%- endif %}
+
+Po otevření konzole tě uvítá okýnko s řádkem textu,
+kterým počítač vybízí k zadání příkazu.
+Podle systému bude tento řádek končit buď znakem `$` nebo `>`,
+před nímž můžou být ještě další informace:
+
+{% call sidebyside(titles=['Unix (Linux, macOS)', 'Windows']) %}
+$
+---
+>
+{% endcall %}
+
+Podle systému se potom liší i samotné příkazy, které budeš zadávat.
+
+> [note] Velikost písma
+> Je-li ve Windows moc malé písmo, klikni na ikonku okna a vyber Možnosti.
+> V záložce Písmo si pak můžeš vybrat větší font.
+>
+>
+> {{ figure(
+ img=static('windows-cmd-properties.png'),
+ alt='Screenshot menu příkazové řádky',
+) }}
+>
+> Na ostatních systémech hledej v nastavení, nebo zkus
+> Ctrl++ a
+> Ctrl+- (příp. se Shift).
+
+
+
+## První příkaz
+
+Začneme jednoduchým příkazem.
+Napiš `whoami` (z angl. *who am I?* – kdo jsem?)
+a stiskni Enter.
+Objeví se přihlašovací jméno. Třeba u Heleny to vypadalo takhle:
+
+{% call sidebyside() %}
+$ whoami
+helena
+---
+> whoami
+pocitac\Helena
+{% endcall %}
+
+
+
+> [note]
+> Znak `$` nebo `>` je v ukázce jen proto, aby bylo jasné, že zadáváme
+> příkaz do příkazové řádky.
+> Vypíše ho počítač, většinou ještě s něčím před ním,
+> takže ho nepiš sama! Zadej jen `whoami` a Enter.
+>
+> Stejně tak počítač sám vypíše přihlašovací jméno.
+
+
+## Aktuální adresář
+
+Příkazová řádka pracuje vždy v nějakém *adresáři* (neboli *složce*,
+angl. *directory*, *folder*).
+
+Je to podobné, jako když si na počítači otevřeš prohlížeč souborů.
+Na každém počítači takový program vypadá trochu jinak, ale většinou máš
+nahoře jméno aktuálního adresáře a v hlavním okýnku seznam souborů,
+které v tom adresáři jsou:
+
+{{ figure(
+ img=static('dirs.png'),
+ alt='Screenshot prohlížeče souborů',
+) }}
+
+Podobně příkazová řádka je vždy „v“ nějakém *aktuálním adresáři*.
+Který to je, to bývá napsáno před znakem `$` nebo `>` (občas ve zkrácené podobě).
+Vždycky se ale dá vypsat příkazem, který se podle systému
+jmenuje `pwd` nebo `cd` (z angl. *print working directory* – vypiš pracovní
+adresář, resp. *current directory* – aktuální adresář).
+
+{% call sidebyside() %}
+$ pwd
+/home/helena/
+---
+> cd
+C:\Users\helena
+{% endcall %}
+
+U tebe se bude aktuální adresář nejspíš jmenovat trochu jinak.
+
+Tento adresář – ten, ve kterém příkazová řádka „začíná“ – je tvůj
+*domovský adresář*.
+Typicky obsahuje všechny tvoje soubory a nastavení.
+
+
+## Co v tom adresáři je?
+
+V prohlížeči souborů se ukazují soubory v aktuálním adresáři neustále.
+V příkazové řádce si o ně ale musíš „říct“ příkazem `ls` nebo `dir`
+(z angl. *list* – vyjmenovat, resp. *directory* – adresář).
+Ten vypíše, co aktuální adresář obsahuje: všechny soubory,
+včetně podadresářů, které se v aktuálním adresáři nacházejí.
+Na některých systémech ukáže jen jména, jinde i další informace.
+Například:
+
+{% call sidebyside() %}
+$ ls
+Applications
+Desktop
+Downloads
+Music
+…
+---
+> dir
+ Directory of C:\Users\helena
+05/08/2014 07:28 PM Applications
+05/08/2014 07:28 PM Desktop
+05/08/2014 07:28 PM Downloads
+05/08/2014 07:28 PM Music
+…
+{% endcall %}
+
+Na tvém počítači nejspíš budou jiné soubory, ale aspoň `Desktop` a `Music`
+(nebo `Plocha` a `Hudba`) na většině počítačů jsou.
+
+
+## Kopírování textu
+
+Z příkazové řádky se dá kopírovat text.
+Háček je ale v tom, že to nejde přes Ctrl+C – tahle
+zkratka tu znamená něco jiného.
+
+Zkus si zkopírovat jméno aktuálního adresáře.
+
+* Na **Linuxu** všech systémech text vyber myší, pak klikni pravým tlačítkem
+ myši a z menu vyber kopírování.
+ Případně funguje zkratka Ctrl+Insert.
+
+* Na **macOS** to je nejjednodušší: text vyber a zkopíruj pomocí
+ ⌘+C
+
+* Na **Windows** napřed klikni na ikonku okýnka, rozbal *Edit* a vyber
+ *Vybrat* (*Select*). Pak text vyber myší a zkopíruj klávesou
+ Enter.
+
+ (Na některých verzích Windows jde vybírat přímo myší, nemusíš přes menu.)
+
+Zkus zkopírované jméno adresáře vložit do grafického prohlížeče souborů.
+Měl{{a}} bys pak vidět obsah i tam.
+
+V dalších sekcích budeme potřebovat adresáře `Desktop` a `Music` (nebo `Plocha`
+a `Hudba`).
+Jestli je ve svém domovském adresáři nemáš, v grafickém prohlížeči si je
+vytvoř a v příkazové řádce zkontroluj, že je máš.
+
+{% call sidebyside() %}
+$ ls
+…
+Desktop
+Music
+…
+---
+> dir
+ Directory of C:\Users\helena
+…
+05/08/2014 07:28 PM Desktop
+05/08/2014 07:28 PM Music
+…
+{% endcall %}
+
+
+## Změna aktuálního adresáře
+
+Aktuální adresář se dá změnit pomocí příkazu `cd`
+(z angl. *change directory* – změnit adresář).
+Za `cd` se píše jméno adresáře, kam chceš přejít.
+
+> [note] Déjà vu?
+> Jsi-li na Windows, příkaz `cd` už jsi používal{{a}}.
+> Chová se ale různě podle toho, jestli něco napíšeš za něj nebo ne!
+
+Přejdi do adresáře `Desktop` (nebo `Plocha`).
+Pak si nový aktuální adresář vypiš, aby sis ověřil{{a}},
+že jsi na správném místě.
+
+{% call sidebyside() %}
+$ cd Desktop
+$ pwd
+/home/helena/Desktop
+---
+> cd Desktop
+> cd
+C:\Users\helena\Desktop
+{% endcall %}
+
+> [note] Velikost písmen
+> Jsi-li na Linuxu nebo macOS, dej si pozor na velikost písmen: na těchto
+> systémech jsou `Desktop` a `desktop` dvě různá jména.
+
+> [note] Windows a disky
+> Pokud přecházíš do adresáře na jiném disku,
+> například `D:` místo `C:`, je potřeba kromě `cd`
+> zadat jméno disku s dvojtečkou jako zvláštní příkaz (např. `D:`).
+
+
+## Cesta zpět
+
+Zkusíme teď místo do `Desktop` (nebo `Plocha`) přejít do `Music`
+(nebo `Hudba)`.
+
+Když zadáš `cd Music`, pravděpodobně uvidíš *chybu*: v aktuálním
+adresáři (`Desktop`) žádné `Music` není.
+
+Aby ses do něj dostal{{a}}, musíš nejdřív zpátky, do „nadřazeného“ adresáře.
+To dělá příkaz `cd ..` – `cd`, mezera, a dvě tečky.
+Zkus ho zadat a pak se podívat, jak se aktuální adresář změnil:
+
+{% call sidebyside() %}
+$ cd ..
+$ pwd
+/home/helena
+---
+> cd ..
+> cd
+C:\Users\helena
+{% endcall %}
+
+Z domovského adresáře už můžeš zadat `cd Music` (nebo `cd Hudba`) bez chyby.
+
+
+## Další příkazy
+
+Textových příkazů existuje daleko víc než `whoami` a `cd`.
+Z příkazové řádky můžeš vytvářet adresáře, měnit soubory, nebo si třeba přečíst
+e-mail.
+
+I „grafické“ programy, které máš na počítači nainstalované, jdou
+z příkazové řádky spustit – a to většinou jen zadáním jména.
+Zkus, jestli na tvém počítači bude fungovat `firefox`, `notepad`, `safari`
+nebo `gedit`.
+
+Při učení Pythonu si ale vystačíme s málem: s `cd`/`pwd` a několika příkazy,
+které zanedlouho nainstalujeme – například `python`.
+
+
+## Konec
+
+Nakonec vyzkoušej ještě jeden příkaz.
+Ten, který příkazovou řádku zavírá: `exit`.
+
+Příkaz `exit` funguje stejně na všech systémech.
+Proto už nebudu používat ukázku rozdělenou pro Unix a Windows.
+
+```console
+$ exit
+```
+
+Ve zbytku těchto materiálů budeme pro kód, který je potřeba zadat do
+příkazové řádky, používat unixovské `$`.
+S touto konvencí se setkáš i ve většině návodů na internetu.
+Používáš-li Windows, je dobré si na `$` zvyknout, i když ve své
+řádce máš místo něj `>`.
+
diff --git a/lessons/fast-track/cmdline/info.yml b/lessons/fast-track/cmdline/info.yml
new file mode 100644
index 00000000..e384bff9
--- /dev/null
+++ b/lessons/fast-track/cmdline/info.yml
@@ -0,0 +1,8 @@
+title: Úvod do příkazové řádky
+style: md
+attribution:
+- Pro PyLadies CZ napsal Petr Viktorin, 2014-2018.
+- |
+ Založeno na tutoriálu [Django Girls].
+ [Django Girls]: https://tutorial.djangogirls.org/en/intro_to_command_line/
+license: cc-by-sa-40
diff --git a/lessons/fast-track/cmdline/static/dirs.png b/lessons/fast-track/cmdline/static/dirs.png
new file mode 100644
index 00000000..5126a1ec
Binary files /dev/null and b/lessons/fast-track/cmdline/static/dirs.png differ
diff --git a/lessons/fast-track/cmdline/static/windows-cmd-properties.png b/lessons/fast-track/cmdline/static/windows-cmd-properties.png
new file mode 100644
index 00000000..ebb2040b
Binary files /dev/null and b/lessons/fast-track/cmdline/static/windows-cmd-properties.png differ
diff --git a/lessons/fast-track/conversion/index.md b/lessons/fast-track/conversion/index.md
new file mode 100644
index 00000000..6f265b87
--- /dev/null
+++ b/lessons/fast-track/conversion/index.md
@@ -0,0 +1,73 @@
+# Převádění typů
+
+Pojď zkusit něco nového: zjistit délku čísla stejným způsobem,
+jakým jsi zjišťoval{{a}} délku svého jména.
+Zadej `len(304023)` a stiskni Enter:
+
+``` pycon
+>>> len(304023)
+Traceback (most recent call last):
+ File "", line 1, in
+TypeError: object of type 'int' has no len()
+```
+
+Zobrazila se ti chyba!
+Ta říká, že objekty typu `int` (zkratka anglického *integer*, celé číslo)
+nemají délku.
+Co můžeš udělat teď?
+Možná můžeš zkusit napsat číslo jako řetězec?
+Řetězce mají délku, že?
+
+```pycon
+>>> len("304023")
+6
+```
+
+Jde to ale i bez uvozovek
+Existuje i funkce, která *převede* číslo na řetězec.
+Jmenuje se `str`:
+
+```pycon
+>>> str(304023)
+"304023"
+>>> len(str(304023))
+6
+```
+
+Když zadáváš číslo přímo, bude asi příjemnější použít uvozovky.
+Funkce `str` ale začne být užitečnější až budeš chtít výpočet
+čísla přenechat Pythonu:
+
+```pycon
+>>> str(304023 * 12345 * 83845)
+'314684030130075'
+>>> len(str(304023 * 12345 * 83845))
+15
+```
+
+Podobně jako `str` převádí na řetězce, funkce `int` převádí věci na celá čísla:
+
+```pycon
+>>> int("304023")
+```
+
+Číslo na text můžeš převést vždy, ale naopak to vždy nejde.
+Co se stane, když se pokusíš na číslo převést řetězec bez
+číslic – třeba `'ahoj'`?
+
+{% filter solution() %}
+Nastane chyba!
+
+``` pycon
+>>> int('ahoj')
+Traceback (most recent call last):
+ File "", line 1, in
+ValueError: invalid literal for int() with base 10: 'ahoj'
+```
+
+Hláška obsahuje užitečné informace, i když je k nim potřeba angličtina
+a pokročilejší znalost (infor)matiky.
+V doslovnějším překladu hláška zní *Chyba hodnoty: špatný zápis celého čísla
+v desítkové soustavě: `'ahoj'`*.
+Důležité je ale hlavně to `ValueError`, chyba hodnoty.
+{% endfilter %}
diff --git a/lessons/fast-track/conversion/info.yml b/lessons/fast-track/conversion/info.yml
new file mode 100644
index 00000000..32b55093
--- /dev/null
+++ b/lessons/fast-track/conversion/info.yml
@@ -0,0 +1,12 @@
+title: Převádění typů
+style: md
+attribution:
+- Založeno na materiálech [DjangoGirls](https://djangogirls.org/).
+- Část této kapitoly je založena na kurzu [Geek Girls Carrots](https://github.com/ggcarrots/django-carrots).
+- |
+ Původní DjangoGirls tutoriál přeložila do češtiny skupina dobrovolníků.
+ Poděkování patří hlavně: Davidovi (dakf), Kristýně Kumpánové,
+ Veronice Gabrielové, Tomáši Ehrlichovi, Aničce Jaegerové,
+ Matějovi Stuchlíkovi, Filipovi Sivákovi a Juraji M. Bezručkovi.
+- Pro PyLadies CZ upravil Petr Viktorin, 2018.
+license: cc-by-sa-40
diff --git a/lessons/fast-track/def/index.md b/lessons/fast-track/def/index.md
new file mode 100644
index 00000000..b6b0d401
--- /dev/null
+++ b/lessons/fast-track/def/index.md
@@ -0,0 +1,240 @@
+# Vlastní funkce
+
+Pamatuješ na funkce `len()`, `print()` nebo `randrange()` z modulu `random`?
+Jsou jako kouzelná zaříkadla z knihy vázané v kůži: když víš jak se jmenují
+a umíš je správně {# XXX: vyslovit #}napsat, něco pro tebe udělají.
+
+Teď postoupíme na další úroveň: vymyslíme si vlastní zaříkadla!
+Jak? Budeme kombinovat příkazy, které už známe.
+
+Třeba funkce, která tě pozdraví, by mohla:
+
+* Vypsat „ahoj!“
+* Vypsat „jak se máš?“
+
+Definice funkce v Pythonu začíná klíčovým slovem `def`,
+dále je uveden název a následují závorky (zatím prázdné).
+Pak je jako po `if` dvojtečka a odsazené příkazy – tentokrát
+příkazy, které má funkce provést.
+Napiš to do programu:
+
+```python
+def pozdrav():
+ print('Ahoj!')
+ print('Jak se máš?')
+```
+
+Tvoje první funkce je připravena!
+
+Když ale tenhle program spustíš, nic neudělá.
+To proto, že tohle je jen *definice* funkce.
+Python teď ví jak pozdravit – ale neřeklo se, že to má udělat!
+
+Na konec programu přidej volání.
+To už *není součást funkce*, ale pokračování samotného programu.
+Proto nesmí být odsazené:
+
+```python
+def pozdrav():
+ print('Ahoj!')
+ print('Jak se máš?')
+
+pozdrav()
+```
+
+Co se stane, když funkci zavoláš několikrát po sobě?
+
+```python
+def pozdrav():
+ print('Ahoj!')
+ print('Jak se máš?')
+
+pozdrav()
+pozdrav()
+pozdrav()
+```
+
+{% filter solution %}
+Každé volání spustí tělo funkce znovu.
+
+``` console
+(venv) $ python python_intro.py
+Ahoj!
+Jak se máš?
+Ahoj!
+Jak se máš?
+Ahoj!
+Jak se máš?
+```
+{% endfilter %}
+
+Co se stane, když volání dáš *nad* definici funkce, místo na konec programu?
+
+```python
+pozdrav()
+
+def pozdrav():
+ print('Ahoj!')
+ print('Jak se máš?')
+```
+
+{% filter solution %}
+``` pycon
+Traceback (most recent call last):
+ File "", line 1, in
+NameError: name 'pozdrav' is not defined
+```
+
+Python si postěžuje na `NameError` – nezná nic jménem `pozdrav`.
+
+Python totiž program čte odzhora dolů.
+Až příkazem `def` se „naučí" jak zdravit.
+Předtím, než se k příkazu `def` dostane, funkce neexistuje.
+{% endfilter %}
+
+## Parametry
+
+Tvoje funkce se dá volat jen jako `pozdrav()`.
+Funkce ale jako `len('slovo')` a `print(1 + 2)` umí navíc pracovat s hodnotou.
+
+Poďme teraz napisať funkciu, ktorá ťa pozdraví menom.
+(Uľahčíme si to použitím jazyka, ktorý nepoužíva piaty pád.)
+
+```python
+def pozdrav(meno):
+ print('Vítam ťa,', meno)
+
+pozdrav('Ola')
+pozdrav('Soňa')
+pozdrav('Hubert')
+pozdrav('Anička')
+```
+
+Jak to funguje?
+V definici funkce uvedeš závorkách *parametr* – jméno proměnné se kterou bude
+funkce pracovat.
+Hodnotu pro tenhle parametr pak zadáš při volání funkce.
+
+Zvládneš napsat program, který se zeptá na jméno a pak tě pozdraví?
+
+{% filter solution %}
+```python
+def pozdrav(meno):
+ print('Vitam ťa,', meno)
+
+pozdrav(input('Ako sa voláš? '))
+```
+{% endfilter %}
+
+Co se stane, když funkci zavoláš bez hodnoty pro parametr?
+
+{% filter solution %}
+``` python
+def pozdrav(meno):
+ print('Vitam ťa,', meno)
+
+pozdrav()
+```
+``` pycon
+Traceback (most recent call last):
+ File "", line 9, in
+TypeError: pozdrav() missing 1 required positional argument: 'meno'
+```
+
+Python si stěžuje na `TypeError` – funkce `pozdrav` nedostala povinný
+argument `meno`.
+{% endfilter %}
+
+Funkce může obsahovat jakýkoli kód.
+Třeba podmíněný příkaz, `if`.
+Příkazy po `if` je pak potřeba odsatit o *další* čtyři mezery:
+
+```python
+def pozdrav(meno):
+ print('Vitam ťa,', meno)
+ if meno == 'Ola':
+ print('Ty umíš programovať!')
+
+pozdrav('Hubert')
+pozdrav('Ola')
+pozdrav('Soňa')
+```
+
+
+## Vracení
+
+Další věc, kterou funkce jako `len` umí, je *vrátit* výsledek:
+
+``` python
+delka = len('Ola')
+print(delka) # napíše: 3
+```
+
+Jak na to, kdybys takovou funkci chtěl{{a}} napsat?
+V definici funkce můžeš použít příkaz `return`.
+Ten funkci okamžitě ukončí a vrátí danou hodnotu:
+
+```python
+def dvojnasobek(x):
+ return x * 2
+
+print(dvojnasobek(42))
+```
+
+Zkus se zamyslet, jak napsat funkci, která vrátí pátý pád nějakého jména. Třeba:
+
+* `paty_pad('Ola')` → 'Olo'
+* `paty_pad('Soňa')` → 'Soňo'
+* `paty_pad('Hubert')` → 'Huberte'
+
+Tohle je velice složitý úkol, tak si ho trochu zjednodušíme.
+Funkce by měla dělat tohle:
+
+* Pokud jméno je „Hubert“:
+ * vrátí `Huberte`
+* Pokud jméno končí na `a`:
+ * vrátí jméno s `o` místo posledního písmenka
+* Jinak:
+ * Vrátí původní jméno. (Uživatel si toho snad nevšimne.)
+
+``` python
+def paty_pad(jmeno):
+ if jmeno == 'Hubert':
+ return 'Huberte'
+ elif jmeno[-1] == 'a':
+ return jmeno[:-1] + 'o'
+ else:
+ return jmeno
+```
+
+Dokážeš změnit funkci `pozdrav`, aby zdravila v češtině?
+Můžeš na to použít funkci `paty_pad`.
+
+{% filter solution %}
+``` python
+def paty_pad(jmeno):
+ if jmeno == 'Hubert':
+ return 'Huberte'
+ elif jmeno[-1] == 'a':
+ return jmeno[:-1] + 'o'
+ else:
+ return jmeno
+
+def pozdrav(jmeno):
+ print('Vítam tě,', paty_pad(jmeno))
+
+pozdrav('Hubert')
+pozdrav('Ola')
+pozdrav('Soňa')
+```
+{% endfilter %}
+
+
+## Shrnutí
+
+Co bylo nového tentokrát?
+
+* **Funkce** umožňuje pojmenovat nějkolik příkazů, a pak je zavolat najednou.
+* **Parametry** funkce, hodnoty se kterými funkce pracuje,
+ se zadávají v závorkách.
+* `return` ukončí funkci a vrátí hodnotu
diff --git a/lessons/fast-track/def/info.yml b/lessons/fast-track/def/info.yml
new file mode 100644
index 00000000..c8a5b5ef
--- /dev/null
+++ b/lessons/fast-track/def/info.yml
@@ -0,0 +1,12 @@
+title: Vlastní funkce
+style: md
+attribution:
+- Založeno na materiálech [DjangoGirls](https://djangogirls.org/).
+- Část této kapitoly je založena na kurzu [Geek Girls Carrots](https://github.com/ggcarrots/django-carrots).
+- |
+ Původní DjangoGirls tutoriál přeložila do češtiny skupina dobrovolníků.
+ Poděkování patří hlavně: Davidovi (dakf), Kristýně Kumpánové,
+ Veronice Gabrielové, Tomáši Ehrlichovi, Aničce Jaegerové,
+ Matějovi Stuchlíkovi, Filipovi Sivákovi a Juraji M. Bezručkovi.
+- Pro PyLadies CZ upravil Petr Viktorin, 2018.
+license: cc-by-sa-40
diff --git a/lessons/fast-track/dict/index.md b/lessons/fast-track/dict/index.md
new file mode 100644
index 00000000..0a9a3ce4
--- /dev/null
+++ b/lessons/fast-track/dict/index.md
@@ -0,0 +1,143 @@
+# Slovníky
+
+Jiný typ hodnot, které v sobě mohou obsahovat další hodnoty, je *slovník*.
+Představ si překladový slovník, třeba tenhle česko-anglický:
+
+* **Jablko**: Apple
+* **Knoflík**: Button
+* **Myš**: Mouse
+
+Slovník v Pythonu obsahuje *záznamy*, a každý záznam přiřazuje
+nějakému *klíči* nějakou *hodnotu*.
+V našem příkladu je klíči *Jablko* přiřazena hodnota *Apple*,
+klíči *Knoflík* náleží hodnota *Button*
+a klíč *Myš* ukazuje na *Mouse*.
+
+V Pythonu by se takový slovník napsal následovně:
+
+``` pycon
+>>> slovnik = {'Jablko': 'Apple', 'Knoflík': 'Button', 'Myš': 'Mouse'}
+```
+
+Tyto klíče a hodnoty jsou slova – krátké texty, tedy řetězce,
+které je potřeba dát do uvozovek.
+Každý klíč je od své hodnoty oddělený dvojtečkou,
+jednotlivé dvojice se od sebe oddělují čárkou,
+a celý slovník je uzavřený ve složených závorkách.
+
+Když budeš chtít v takovém slovníku něco najít, potřebuješ vědět co hledat.
+Potřebuješ *klíč*.
+Pomocí hranatých závorek můžeš zjistit hodnotu, která danému klíči odpovídá:
+
+
+``` pycon
+>>> slovnik['Jablko']
+'Apple'
+```
+
+Je to podobné jako u seznamů, jen v hranatých závorkách není index
+(pořadí prvku) nebo rozmezí s dvojtečkou, ale právě klíč.
+
+> [note]
+> Naopak to nejde – slovník neumožňuje podle hodnoty přímo zjistit klíč.
+> Na překlad z angličtiny do češtiny bys potřeboval{{a}} druhý slovník.
+
+## Měnění slovníků
+
+Co se stane, když klíč ve slovníku není?
+
+``` pycon
+>>> slovnik['Pes']
+Traceback (most recent call last):
+ File "", line 1, in
+KeyError: 'Pes'
+```
+
+Python si postěžuje na `KeyError` – chybu klíče.
+
+Podobně jako seznamy se ale slovníky dají měnit.
+Nový záznam vytvoříš takhle:
+
+``` pycon
+>>> slovnik['Pes'] = 'Dog'
+>>> slovnik
+{'Jablko': 'Apple', 'Knoflík': 'Button', 'Myš': 'Mouse', 'Pes': 'Dog'}
+```
+
+> [note]
+> Na rozdíl od překladového slovníku nemusí být Pythonní slovník seřazený
+> podle abecedy.
+> Není to potřeba, počítač umí rychle vyhledávat i bez seřazení.
+
+Kdybys potřeboval{{a}} změnit už existující záznam, použij stejný příkaz.
+K jednomu klíči může patřit jen jedna hodnota.
+
+``` pycon
+>>> slovnik['Pes'] = 'Extension cord'
+>>> slovnik
+{'Jablko': 'Apple', 'Knoflík': 'Button', 'Myš': 'Mouse', 'Pes': 'Extension cord'}
+```
+
+{# XXX: Zmínit se o nehomogenních slovnících? #}
+
+Chceš-li ze zlovníku nějaký záznam smazat, dělá se to podobně jako
+u seznamů příkazem `del`:
+
+``` pycon
+>>> del slovnik['Pes']
+>>> slovnik
+{'Jablko': 'Apple', 'Knoflík': 'Button', 'Myš': 'Mouse'}
+```
+
+A když budeš chtít zjistit kolik je ve slovníku záznamů,
+zeptáš se podobně jako na počet znaků řetězce nebo prvků seznamu.
+Použiješ funkci `len()`.
+
+``` pycon
+>>> len(slovnik)
+3
+```
+
+{# XXX
+
+* Kontakty
+* Když číslo není číslo
+* Více čísel
+
+## K zamyšlení
+
+Ke každému klíči může patřit jen jedna hodnota.
+Jak bys zařídil{{a}}, aby hodnot víc?
+
+Zkus do Pythonní proměnné uložit tyto kontakty:
+
+* Katka:
+ * 4925219
+* Jirka:
+ * 7477058
+ * 3251156
+* Verča:
+ * 1019103
+
+{% filter solution %}
+Více hodnot se dá uložit do seznamu.
+Hodnoty budou seznamy čísel:
+
+```pycon
+>>> kontakty = {'Katka': ['4925219'], 'Jirka': ['7477058', '3251156'], 'Verča': ['1019103']}
+```
+{% endfilter %}
+
+Verča se přestěhovala do zahraničí a má nové číslo: `+897 3788509`.
+
+#}
+
+## Shrnutí
+
+Skvělé! Co víš o slovnících:
+
+* **Záznam** se skládá z **klíče** a **hodnoty**.
+* Ve slovníku se hledá pomocí **klíče**.
+* Záznamy se dají přepsat, přidat, nebo pomocí `del` smazat.
+
+Jsi připraven{{a}} na další část?
diff --git a/lessons/fast-track/dict/info.yml b/lessons/fast-track/dict/info.yml
new file mode 100644
index 00000000..c58c5373
--- /dev/null
+++ b/lessons/fast-track/dict/info.yml
@@ -0,0 +1,12 @@
+title: Slovníky
+style: md
+attribution:
+- Založeno na materiálech [DjangoGirls](https://djangogirls.org/).
+- Část této kapitoly je založena na kurzu [Geek Girls Carrots](https://github.com/ggcarrots/django-carrots).
+- |
+ Původní DjangoGirls tutoriál přeložila do češtiny skupina dobrovolníků.
+ Poděkování patří hlavně: Davidovi (dakf), Kristýně Kumpánové,
+ Veronice Gabrielové, Tomáši Ehrlichovi, Aničce Jaegerové,
+ Matějovi Stuchlíkovi, Filipovi Sivákovi a Juraji M. Bezručkovi.
+- Pro PyLadies CZ upravil Petr Viktorin, 2018.
+license: cc-by-sa-40
diff --git a/lessons/fast-track/files/index.md b/lessons/fast-track/files/index.md
new file mode 100644
index 00000000..942a554a
--- /dev/null
+++ b/lessons/fast-track/files/index.md
@@ -0,0 +1,169 @@
+# Soubory
+
+Dnes se podíváme na to, jak v Pythonu číst z
+(a pak i zapisovat do) souborů.
+
+Vytvoř si v editoru soubor `basnicka.txt` a napiš do něj libovolnou básničku.
+Soubor ulož.
+
+> [note]
+> Na uložení souboru s básničkou doporučuji použít
+> stejný editor, jaký používáš na Pythonní programy.
+>
+> Používáš-li jiný editor než Atom, dej si při ukládání pozor na kódování:
+> * Nabízí-li ti editor při ukládání výběr kódování, vyber UTF-8.
+> * Je-li k dispozici kódování „UTF-8 bez BOM”, použij to.
+> * Pokud musíš použít Notepad, který výše uvedené možnosti nemá, pak v kódu
+> níže použij místo `'utf-8'` nestandardní `'utf-8-sig'`.
+>
+> Ono [`utf-8`] je název standardního kódování.
+> Zajišťuje, že se případné emoji nebo znaky s diakritikou do souboru uloží
+> tak, aby se daly přečíst i na jiném počítači či operačním systému.
+> 🎉
+
+[`utf-8`]: https://en.wikipedia.org/wiki/UTF-8
+
+Potom napiš tento program:
+
+```python
+soubor = open('basnicka.txt', encoding='utf-8')
+obsah = soubor.read()
+soubor.close()
+
+print(obsah)
+```
+a spusť ho z adresáře, ve kterém je
+`basnicka.txt` (jinými slovy, aktuální adresář musí být ten, který
+obsahuje soubor s básničkou).
+
+Obsah souboru se vypíše!
+
+Co se tu děje?
+Tak jako `int()` vrací čísla a `input()` řetězce, funkce
+`open()` vrací hodnotu, která představuje *otevřený soubor*.
+Tahle hodnota má vlastní metody.
+Tady používáme metodu `read()`, která
+najednou přečte celý obsah souboru a vrátí ho jako řetězec.
+Nakonec metoda `close()` otevřený soubor zase zavře.
+
+
+## Automatické zavírání souborů
+
+Soubory se dají přirovnat k ledničce: abys něco
+mohl{{a}} z ledničky vzít, nebo dát dovnitř, musíš
+ji předtím otevřít a potom zavřít.
+Bez zavření to sice na první pohled funguje taky,
+ale pravděpodobně potom brzo něco zplesniví.
+
+Stejně tak je docela důležité soubor zavřít po tom,
+co s ním přestaneš pracovat.
+Bez zavření to na první pohled funguje, ale složitější programy se můžou dostat
+do problémů.
+Operační systémy mají limity na počet
+současně otevřených souborů, které se nezavíráním
+dají snadno překročit.
+Na Windows navíc nemůžeš soubor, který je stále
+otevřený, otevřít znovu.
+
+Na korektní zavření souboru ale programátoři často zapomenou.
+Proto Python poskytuje příkaz `with`, který soubory zavírá automaticky.
+Používá se takhle:
+
+```python
+with open('basnicka.txt', encoding='utf-8') as soubor:
+ obsah = soubor.read()
+
+print(obsah)
+```
+
+Příkaz `with` vezme otevřený soubor (který vrací funkce `open`)
+a přiřadí ho do proměnné `soubor`.
+Pak následuje odsazený blok kódu, kde se souborem můžeš pracovat – v tomhle
+případě pomocí metody `read` přečíst obsah jako řetězec.
+Když se Python dostane na konec odsazeného bloku, soubor automaticky zavře.
+
+V naprosté většině případů je pro otevírání souborů nejlepší použít `with`.
+
+
+## Iterace nad soubory
+
+Otevřené soubory se, jako např. řetězce či `range`,
+dají použít s příkazem `for`.
+Tak jako `for i in range` poskytuje za sebou jdoucí čísla a `for c in 'abcd'`
+poskytuje jednotlivé znaky řetězce, `for radek in soubor` bude do proměnné
+`radek` dávat jednotlivé řádky čtené ze souboru.
+
+Například můžeš básničku odsadit,
+aby se vyjímala v textu:
+
+```python
+print('Slyšela jsem tuto básničku:')
+print()
+
+with open('basnicka.txt', encoding='utf-8') as soubor:
+ for radek in soubor:
+ print(' ' + radek)
+
+print()
+print('Jak se ti líbí?')
+```
+
+
+Když to zkusíš, zjistíš, že trochu nesedí
+řádkování. Zkusíš vysvětlit, proč tomu tak je?
+
+{% filter solution %}
+Každý řádek končí znakem nového řádku, `'\n'`,
+který možná znáš ze [sekce o řetězcích](../../beginners/str/).
+Při procházení souboru Python tento znak nechává na konci řetězce `radek` ¹.
+Funkce `print` pak přidá další nový řádek, protože ta na konci
+výpisu vždycky odřádkovává – pokud nedostane argument `end=''`.
+
+---
+
+¹ Proč to dělá? Kdyby `'\n'` na konci řádků nebylo,
+nedalo by se např. dobře rozlišit, jestli poslední řádek
+končí na `'\n'`
+
+{% endfilter %}
+
+Ideální způsob, jak odřádkování spravit, je odstranit z konce řetězce
+bílé znaky (mezery a nové řádky) pomocí metody `rstrip`:
+
+
+```python
+print('Slyšela jsem tuto básničku:')
+print()
+
+with open('basnicka.txt', encoding='utf-8') as soubor:
+ for radek in soubor:
+ radek = radek.rstrip()
+ print(' ' + radek)
+
+print()
+print('Jak se ti líbí?')
+```
+
+
+## Psaní souborů
+
+> [warning] Pozor!
+> Pro Python není problém smazat obsah jakéhokoli souboru.
+> Psaní do souborů si zkoušej v adresáři, ve kterém nemáš uložené
+> důležité informace!
+
+Soubory se v Pythonu dají i zapisovat.
+Pro zápis soubor otevři s pojmenovaným
+argumentem `mode='w'` (z angl. *mode*, mód a *write*, psát).
+
+Pokud soubor už existuje, otevřením s `mode='w'` se veškerý jeho obsah smaže.
+Po zavření tak v souboru bude jen to, co do něj ve svém programu zapíšeš.
+
+Informace pak do souboru zapiš známou funkcí `print`,
+a to s pojmenovaným argumentem `file`:
+
+```python
+with open('druha-basnicka.txt', mode='w', encoding='utf-8') as soubor:
+ print('Naše staré hodiny', file=soubor)
+ print('Bijí', 2+2, 'hodiny', file=soubor)
+```
diff --git a/lessons/fast-track/files/info.yml b/lessons/fast-track/files/info.yml
new file mode 100644
index 00000000..15cabaf7
--- /dev/null
+++ b/lessons/fast-track/files/info.yml
@@ -0,0 +1,4 @@
+title: Soubory
+style: md
+attribution: Pro PyLadies Brno napsal Petr Viktorin, 2014-2018.
+license: cc-by-sa-40
diff --git a/lessons/fast-track/filesystem/index.md b/lessons/fast-track/filesystem/index.md
new file mode 100644
index 00000000..9fb64d6d
--- /dev/null
+++ b/lessons/fast-track/filesystem/index.md
@@ -0,0 +1,198 @@
+# Soubory a cesty
+
+Informace uložené v Pythonních proměnných – seznamech, slovnících, a tak dále – jsou dočasné.
+Jakmile Python ukončíš, zmizí.
+Chceš-li něco uložit na delší dobu, nebo třeba sdílet s jinými programy,
+můžeš si informace uložit do souboru.
+
+S počítačovými soubory (angl. *files*) už ses asi setkal{{a}}.
+Teď se na ně ale podívejme trochu podrobněji.
+
+Soubor je místo, kam se dají ukládat informace – *obsah*. Kromě obsahu mají soubory ještě další vlastnosti:
+
+* *jméno*, podle kterého se soubor dá najít,
+* informace o *vlastnictví* a *oprávnění*, které určují kdo může ze souboru
+ číst a kdo do něj může zapisovat,
+* informace o *časech* vytvoření, posledního zápisu, a podobně,
+* a další informace, na každém druhu operačního systému jiné.
+
+Tak jako je kapitola v knížce poskládaná z písmenek, obsah souboru je
+poskládaný z *bajtů* (angl. *bytes*) – malých čísel.
+Každá informace, kterou počítač umí zpracovat, se dá zakódovat do bajtů
+podobně jako se z písmenek skládá text.
+
+Soubory jsou většinou uloženy na disku (nebo podobném médiu), kde bývá místo
+na bilióny bajtů.
+Aby počítač poznal, kde na disku je který soubor, používá *souborový systém*
+(angl. *filesystem*).
+Ten plní podobnou funkci jako v knížce obsah, jména kapitol a čísla stránek.
+
+
+## Operační systémy
+
+Souborových systémů existuje spousta druhů.
+Experti nám snad prominou hrubé zjednodušení, když si je rozdělíme na dva
+druhy: ty pro Windows a ty pro Unix.
+
+Unix je operační systém, vytvořený v sedmdesátých letech, ze kterého vycházejí
+dnešní systémy Linux, macOS a další.
+Základní principy, o kterých bude řeč tady, se od dob Unixu většinou
+příliš nezměnily.
+A tak když v těchto materiákech uvidíš jméno „Unix“, jde o něco společné pro
+Linux i macOS.
+Hlavní rozdíly mezi Linuxem a macOS jsou v konvencích – např. na Linuxu se
+místo pro domovské adresáře jmenuje většinou `/home`, kdežto na macOS `/Users`.
+
+Další rozšířený operační systém, Windows, z Unixu nevychází.
+Některé věci se v něm, jak později uvidíme, chovají jinak.
+
+
+## Adresáře
+
+Na dnešních souborových systémech jsou soubory tříděny do *adresářů* neboli
+*složek* (angl. *directory*, *folder*).
+Adresář může obsahovat spoustu souborů nebo i jiných adresářů.
+
+A teď něco, co pro tebe může být nové: pro programátory jsou adresáře taky soubory.
+Souborů je dokonce spousta druhů: *normální soubory* s informacemi, adresáře
+(které obsahují další soubory), speciální soubory které můžou reprezentovat
+celý disk nebo spojení mezi počítači, odkazy na jiné soubory, a tak dále.
+Co je a co není soubor závisí na systému.
+Dnes se proto omezíme jen na dva druhy souborů, které najdeme jak na Windows
+tak na Unixu: normální datové soubory (ty, které si pod jménem „soubor“
+představí běžný uživatel) a adresáře.
+
+
+## Cesty
+
+Abys mohl/a najít nějaký soubor, potřebuješ znát jeho jméno a adresář,
+který ten soubor obsahuje.
+Abys pak mohl/a najít ten adresář, musíš opět znát jméno adresáře a adresář,
+který ho obsahuje.
+A tak dál, až se dostaneš ke *kořenovému adresáři* (angl. *root directory*),
+který (zjednodušeně řečeno) obsahuje celý souborový systém.
+Když napíšeš jména všech adresářů které takhle projdeš za sebe, dostaneš
+*cestu* (angl. *path*) k danému souboru.
+Taková cesta by mohla na Unixu být třeba:
+
+* Linux: `/home/janca/Documents/archiv.tar.gz`
+* macOS: `/Users/janca/Documents/archiv.tar.gz`
+
+To znamená, že začneš v kořenovém adresáři (který se na Linuxu jmenuje `/`),
+v něm hledáš adresář `home` nebo `Users` (ten tradičně obsahuje domovské
+adresáře uživatelů), v něm pak `janca` (podle uživatelského jména),
+v něm `Documents`, a v něm pak `archiv.tar.gz`.
+To už není adresář, ale normální soubor do kterého se dají zapsat informace.
+
+Obdobná cesta na Windows by mohla být třeba:
+`C:\Users\Jana\Documents\archiv.tar.gz`
+
+Tahle cesta začíná na disku `C:`.
+Windows mají na rozdíl od Unixu zvláštní souborový systém pro každý disk,
+a tak mají víc kořenových adresářů – třeba `C:\` a `D:\`.
+Dál je to podobné jako na Unixu, jen oddělovač adresářů je zpětné lomítko
+místo obyčejného.
+
+## Absolutní a relativní cesty
+
+Cesta, která začíná v konkrétním kořenovém adresáři, se nazývá *absolutní*
+cesta (angl. *absolute* path). Je jako úplná poštovní adresa:
+správně nadepsaný dopis můžu hodit do schránky kdekoli na světě a (teoreticky)
+vždy dojde k adresátovi.
+
+Když ale dopis do Česka házím do české schránky, můžu vynechat informaci
+o kontinentu a zemi.
+Je to tak kratší a jednodušší‚ ale z Austrálie by to nefungovalo.
+
+Na podobném principu jsou založeny *relativní cesty* (angl. *relative paths*).
+Když už jsi v domovském adresáři, stačí zadat cestu `Documents/archiv.tar.gz`,
+bez lomítek či jména disku na začátku.
+To znamená, že cesta nezačíná v kořenovém adresáři, ale v *aktuálním adresáři*
+(angl. *current directory*) – tam, kde právě jsi.
+Kdyby ses pomocí `cd` přepnul{{a}} jinam, tahle relativní cesta by přestala
+fungovat.
+
+Kde relativní cesta „začíná“, to záleží na kontextu – většinou jde o aktuální
+adresář, ale může to být třeba gitový repozitář
+(hlavní adresář nějakého projektu), adresář s programem který právě běží,
+a podobně.
+
+## Dvě tečky a jedna tečka
+
+Každý adresář obsahuje dva speciální záznamy: `..` a `.`.
+
+Jméno `.` (tečka) vždy označuje samotný adresář.
+Tudíž `/home/janca` je stejný adresář jako `/home/janca/.`,
+`/home/janca/././././.` a tak dál.
+To nezní moc užitečně – ale jen do té doby, než potřebuješ zadat jako
+relativní cestu samotný aktuální adresář.
+
+Jméno `..` (dvě tečky) označuje *nadřazený adresář*.
+Když se tohle jméno objeví v cestě, znamená to, že potřebujeme přejít
+o úroveň výš.
+Jsi-li v adresáři `/home/janca/Pictures/dovolena`, tak:
+
+* cesta `..` znamená adresář `/home/janca/Pictures`
+* cesta `../../programy/venv` znamená `/home/janca/programy/venv`
+* absolutní cesta `/home/janca/Documents/../Pictures/dovolena`
+ znamená `/home/janca/Pictures/dovolena`
+
+> [note]
+> Striktně řečeno, výše uvedené neplatí vždycky:
+> speciální soubory zvané *symbolické odkazy* (angl. *symlinks*) můžou počítač
+> při procházení cesty přesměrovat tak, že `Documents/../Pictures` bude jiný
+> soubor než `Pictures`.
+> Detaily jsou nad rámec těchto materiálů, nicméně je to důvod, proč Python
+> nebude automaticky nahrazovat `Documents/../Pictures` za `Pictures`.
+
+
+## Jména souborů
+
+Jméno souboru je řetězec. Nemůže to ale být jakýkoli řetězec.
+Různé systémy mají různou maximální délku jména (i když na tenhle limit dnes
+většinou nenarazíš).
+A navíc je omezen i obsah – jméno souboru nesmí obsahovat:
+
+* na Unixu oddělovač adresářů `/` ani speciální nulový znak,
+* na windows oddělovač `\`, znaky `<>:"/|?*` ani speciální znaky (např.
+ tabulátor, znak nového řádku).
+
+Nedoporučuji s názvy příliš experimentovat, protože některé validní znaky
+můžou v určitých kontextech mít zvláštní význam (např. `*` a `?` jako zástupné
+znaky), špatně se používají (např. mezery a nové řádky), působí problémy
+s kódováním (např. písmena s diakritikou nebo emoji), nebo naráží na problémy
+s tím, že Windows nerozlišují velikost písmen ale Unix ano.
+
+Programátoři by se tak měli omezit na:
+
+* malá písmena bez diakritiky,
+* číslice `0` - `9`,
+* pomlčku `-`,
+* podtržítko `_`, a
+* tečku jako oddělovač přípony.
+
+Možná sis všiml{{a}}, že normální lomítko, `/`, nesmí na Windows být ve jménu
+souboru.
+Spousta moderních programů (včetně většiny knihoven v Pythonu) toho využívá a
+dopředná lomítka automaticky zaměňuje za zpětná.
+Můžeš tak na všech systémech používat stejné relativní cesty: `Documents/archiv.tar.gz` většinou funguje i na Windows.
+
+
+## Přípony
+
+Hodně jmen souborů obsahuje tečku a za ní krátkou *příponu* (angl. *extension*),
+která tradičně indikuje formát souboru – způsob,
+kterým jsou v souboru zakódovány informace.
+Například soubor `hrad.jpeg` má příponu `.jpeg`.
+Ten kdo ví, že [JPEG](https://cs.wikipedia.org/wiki/JPEG) je způsob zakódování
+obrázku (zvlášť vhodný pro fotografie), si tak může domyslet že v souboru je
+nejspíš fotka hradu.
+
+Pythonisti zase poznají příponu `.py`.
+Python samotný ji vyžaduje: příkaz `import module` hledá soubor `module.py`.
+
+Přípony jsou zvlášť důležité na Windows, kde se podle nich vybírá program,
+kterým se soubor otevře.
+(Unix se oproti tomu dívá v prvé řadě na samotný obsah souboru.)
+
+Přípon se může objevit i víc: `archiv.tar.gz` nejspíš obsahuje několik souborů spojených dohromady ve formátu [tar](https://cs.wikipedia.org/wiki/Tar_%28informatika%29) a pak zkomprimovaných ve formátu [gzip](https://cs.wikipedia.org/wiki/Gzip).
diff --git a/lessons/fast-track/filesystem/info.yml b/lessons/fast-track/filesystem/info.yml
new file mode 100644
index 00000000..949fd528
--- /dev/null
+++ b/lessons/fast-track/filesystem/info.yml
@@ -0,0 +1,4 @@
+title: Souborové systémy
+style: md
+attribution: Petr Viktorin, 2018-2019
+license: cc-by-sa-40
diff --git a/lessons/fast-track/for/index.md b/lessons/fast-track/for/index.md
new file mode 100644
index 00000000..34a3c9d8
--- /dev/null
+++ b/lessons/fast-track/for/index.md
@@ -0,0 +1,98 @@
+# Cykly
+
+Programátoři se neradi opakují.
+Programování je o automatizaci: nebudeme zdravit každého člověka zvlášť,
+vezměme seznam padesáti lidí a pozdravíme je všechny najednou!
+
+(No, někteří programátoři asi nejsou moc sociálně nadaní.
+Ale jinde se ta automatizace fakt hodí!)
+
+Ještě si vzpomínáš na seznamy?
+Udělej si seznam jmen:
+
+```python
+jmena = ['Rachel', 'Monica', 'Phoebe', 'Ola', 'Ty']
+```
+
+Se seznamem pak budeš chtít udělat tohle:
+
+* Pro každé jméno ze seznamu jmen:
+ * pozdrav daným jménem
+
+V Pythonu se takový *cyklus* – opakování „pro každý prvek seznamu“ – píše
+pomocí příkazu `for`:
+
+``` python
+for jmeno in jmena:
+ pozdrav(jmeno)
+```
+
+Celý program bude tedy vypadat takto:
+
+```python
+def pozdrav(jmeno):
+ print('Vítam tě,', jmeno)
+
+jmena = ['Rachel', 'Monica', 'Phoebe', 'Ola', 'Ty']
+for jmeno in jmena:
+ pozdrav(jmeno)
+```
+
+A když ho spustíme:
+
+``` console
+$ python3 python_intro.py
+Vitam ťa, Rachel
+Vitam ťa, Monica
+Vitam ťa, Phoebe
+Vitam ťa, Ola
+Vitam ťa, Ty
+```
+
+Jak vidíš, vše, co jsi vložila dovnitř příkazu `for` s odsazením,
+se zopakuje pro každý prvek seznamu `jmena`.
+
+{# XXX: exercise? #}
+
+## Opakuj n-krát
+
+Cyklus `for` můžeš použít i s jinými hodnotami než se seznamy.
+
+Často se používá s funkcí `range()`.
+Když chceš něco 200-krát zopakovat, napiš:
+
+```python
+for i in range(200):
+ print("Už nikdy nebudu házet igelit do táboráku!")
+```
+
+Jak to funguje?
+`for i in range(X)` se dá přeložit jako „pro každé číslo
+od nuly do X“.
+Funkce `range` onu posloupnost čísel od nuly do X vytvoří.
+Do proměnné `i` Python postupně uloží každé číslo, podle toho po kolikáté
+cyklem prochází.
+
+```python
+for i in range(5):
+ print(i)
+```
+```
+0
+1
+2
+3
+4
+```
+
+Všimni si, že samotné `5` není zahrnuto ve výsledku:
+`range(5)` počítá od 0 do 4.
+Když počítáš od nuly a chceš pět čísel, skončíš u čtyřky.
+
+
+## Shrnutí
+
+Naučil{{a}} ses:
+
+* **Cyklus** je způsob, jak opakovat nějaký postup několikrát po sobě
+* `range` pomáhá když potřebuješ určitý konkrétní počet opakování.
diff --git a/lessons/fast-track/for/info.yml b/lessons/fast-track/for/info.yml
new file mode 100644
index 00000000..ca95e6f8
--- /dev/null
+++ b/lessons/fast-track/for/info.yml
@@ -0,0 +1,12 @@
+title: Cykly
+style: md
+attribution:
+- Založeno na materiálech [DjangoGirls](https://djangogirls.org/).
+- Část této kapitoly je založena na kurzu [Geek Girls Carrots](https://github.com/ggcarrots/django-carrots).
+- |
+ Původní DjangoGirls tutoriál přeložila do češtiny skupina dobrovolníků.
+ Poděkování patří hlavně: Davidovi (dakf), Kristýně Kumpánové,
+ Veronice Gabrielové, Tomáši Ehrlichovi, Aničce Jaegerové,
+ Matějovi Stuchlíkovi, Filipovi Sivákovi a Juraji M. Bezručkovi.
+- Pro PyLadies CZ upravil Petr Viktorin, 2018.
+license: cc-by-sa-40
diff --git a/lessons/fast-track/if/index.md b/lessons/fast-track/if/index.md
new file mode 100644
index 00000000..1a65384f
--- /dev/null
+++ b/lessons/fast-track/if/index.md
@@ -0,0 +1,153 @@
+# Podmínky
+
+Spoustu věcí v kódu budeš chtít provádět,
+jen pokud jsou splněny určité podmínky.
+Na to má Python *podmíněné příkazy*.
+
+Zkusíme teď postupně napsat program, který ověřuje tajné heslo.
+
+Pro začátek napiš program, který vypíše `True`, když zadáš slovo `čokoláda`.
+Když bude zadané heslo jiné, napíše `False`:
+
+```python
+heslo = input('Zadej heslo: ')
+print(heslo == 'čokoláda')
+```
+
+## Když – tak
+
+Vypsání `True` ale není moc zajímavé.
+Lepší program by dělal tohle:
+
+* Zeptá se na tajné heslo
+* Když je heslo správné:
+ * Pustí uživatele dovnitř
+
+Anglicky se „když“ řekne *if*. A to je i jméno Pythonního příkazu.
+Používá se takhle:
+
+```python
+heslo = input('Zadej heslo: ')
+if heslo == 'čokoláda':
+ print('Správně! Račte vstoupit.')
+```
+
+Podmíněný příkaz začíná `if`, pokračuje podmínkou (třeba porovnáním)
+a končí dvojtečkou.
+
+Po řádkem s `if` je příkaz *odsazený* – na začátku řádku jsou 4 mezery.
+Podle toho Python pozná, že tuhle část programu má provést,
+jen když je podmínka pravdivá.
+
+Ulož a spusť:
+
+``` console
+(venv) $ python python_intro.py
+Zadej heslo: čokoláda
+Správně! Můžeš vstoupit.
+(venv) $ python python_intro.py
+Zadej heslo: sezam
+```
+
+### Odsazování
+
+To, že jsou na začátku řádku potřeba čtyři mezery, neznamená že musíš
+4× zmáčknout mezerník.
+Některé editory odsazují automaticky (pokud napíšeš řádek s `if` správně).
+Ve všech správně nastavených editorech ale lze odsadit pomocí klávesy
+↹ Tab a kombinace ⇧ Shift+↹ Tab vrátí řádek o jednu úroveň odsazení zpátky.
+
+
+## Jinak
+
+V předchozím příkladu byl kód proveden pouze v případě, že podmínka byla splněna.
+Ještě lepší program by ale byl tenhle:
+
+* Zeptá se na tajné heslo
+* Když je heslo správné:
+ * Pustí uživatele dovnitř
+* Jinak (tedy pokud heslo nebylo správné):
+ * Spustí alarm
+
+K tomu má Python příkaz `else` – „jinak“:
+
+```python
+heslo = input('Zadej heslo: ')
+if heslo == 'čokoláda':
+ print('Správně! Račte vstoupit.')
+else:
+ print('POZOR! POZOR!')
+ print('NEOPRÁVNĚNÝ VSTUP!')
+```
+
+Funuje to?
+
+``` console
+(venv) $ python python_intro.py
+Zadej heslo: čokoláda
+Správně! Můžeš vstoupit.
+(venv) $ python python_intro.py
+Zadej heslo: sezam
+POZOR! POZOR!
+NEOPRÁVNĚNÝ VSTUP!
+```
+
+
+## Více možností
+
+Občas se stane, že se program musí rozhodnout mezi více možnostmi.
+K tomu slouží příkaz `elif` (zkratka znglického *else if* – „jinak, pokud“).
+
+Třeba takovýmhle postupem se dá okomentovat hlasitost hudby:
+
+* Zeptej se na hlasitost, zapamatuj si číselnou odpověď.
+* Když je hlasitost do 20:
+ * vypíše „Je to dost potichu.“
+* Jinak, když je hlasitost do 40:
+ * vypíše „Jako hudba na pozadí dobré.“
+* Jinak, když je hlasitost do 60:
+ * vypíše „Skvělé, slyším všechny detaily.“
+* Jinak, když je hlasitost do 80:
+ * vypíše „Dobré na párty.“
+* Jinak, když je hlasitost do 100:
+ * vypíše „Trochu moc nahlas!“
+* Jinak:
+ * vypíše „Krvácí mi uši!“
+
+V Pythonu by se to zapsalo takto:
+
+```python
+hlasitost = int(input('Jaká je nastavená hlasitost rádia? '))
+if hlasitost < 20:
+ print("Je to dost potichu.")
+elif hlasitost < 40:
+ print("Jako hudba na pozadí dobré.")
+elif hlasitost < 60:
+ print("Skvělé, slyším všechny detaily.")
+elif hlasitost < 80:
+ print("Dobré na party.")
+elif hlasitost < 100:
+ print("Trochu moc nahlas!")
+else:
+ print("Krvácí mi uši!")
+```
+
+``` console
+(venv) $ python python_intro.py
+Jaká je nastavená hlasitost rádia? 28
+Jako hudba v pozadí dobré.
+```
+
+Všimni si, že se vybere vždycky jedna alternativa.
+Když zadáš `28`, Python se dostane k `hlasitost < 40`, vypíše
+příslušnou hlášku a všechny další možnosti přeskočí.
+
+
+## Shrnutí
+
+Co jsi viděl{{a}} v této lekci?
+
+* Příkazy **if** (pokud), **elif** (jinak, pokud) a **else** (jinak)
+ podmiňují jiné příkazy.
+* **Odsazení** se používá pro podmíněné příkazy, které následují po
+ `if` apod..
diff --git a/lessons/fast-track/if/info.yml b/lessons/fast-track/if/info.yml
new file mode 100644
index 00000000..3aac54ef
--- /dev/null
+++ b/lessons/fast-track/if/info.yml
@@ -0,0 +1,12 @@
+title: Podmínky
+style: md
+attribution:
+- Založeno na materiálech [DjangoGirls](https://djangogirls.org/).
+- Část této kapitoly je založena na kurzu [Geek Girls Carrots](https://github.com/ggcarrots/django-carrots).
+- |
+ Původní DjangoGirls tutoriál přeložila do češtiny skupina dobrovolníků.
+ Poděkování patří hlavně: Davidovi (dakf), Kristýně Kumpánové,
+ Veronice Gabrielové, Tomáši Ehrlichovi, Aničce Jaegerové,
+ Matějovi Stuchlíkovi, Filipovi Sivákovi a Juraji M. Bezručkovi.
+- Pro PyLadies CZ upravil Petr Viktorin, 2018.
+license: cc-by-sa-40
diff --git a/lessons/fast-track/list/index.md b/lessons/fast-track/list/index.md
new file mode 100644
index 00000000..dd3682ca
--- /dev/null
+++ b/lessons/fast-track/list/index.md
@@ -0,0 +1,364 @@
+# Seznamy
+
+Vedle řetězců a celých čísel má Python další druhy hodnot.
+
+Teď se podíváme na jeden, který se nazývá *seznam* (anglicky *list*).
+To je hodnota, která v sobě obsahuje jiné hodnoty.
+
+{# Anglické termíny všude! #}
+
+Seznamy se zadávají tak, že dáš několik hodnot, oddělených čárkami,
+do hranatých závorek.
+Zkus si vytvořit třeba seznam čísel z loterie:
+
+``` pycon
+>>> [3, 42, 12, 19, 30, 59]
+[3, 42, 12, 19, 30, 59]
+```
+
+Abys s takovým seznamem mohl{{a}} pracovat,
+ulož si ho do proměnné:
+
+``` pycon
+>>> loterie = [3, 42, 12, 19, 30, 59]
+```
+
+Tak, a máš seznam! Co s ním ale můžeš dělat?
+Podívej se, kolik čísel v seznamu je.
+Dá se na to použít funkce, kterou už znáš.
+Tipneš si, která to je?
+
+{% filter solution %}
+``` pycon
+>>> len(loterie)
+6
+```
+
+Funkce `len()` umí zjistit nejen délku řetězce, ale i délku seznamu – tedy
+počet jeho prvků.
+{% endfilter %}
+
+Teď si zkus seznam seřadit. Na to existuje metoda `sort`:
+
+``` pycon
+>>> loterie.sort()
+```
+
+Tato metoda nic nevrátí, ale „potichu“ změní pořadí čísel v seznamu.
+Znovu si ho vypiš, ať vidíš co se stalo:
+
+``` pycon
+>>> loterie
+[3, 12, 19, 30, 42, 59]
+```
+
+Čísla v seznamu jsou nyní seřazena od nejnižší k nejvyšší hodnotě.
+
+Podobně funguje metoda `reverse`, která obrátí pořadí prvků.
+Vyzkoušej si ji!
+
+``` pycon
+>>> loterie.reverse()
+>>> loterie
+[59, 42, 30, 19, 12, 3]
+```
+
+## Přidávání do seznamu
+
+Podobně jako u řetězců se seznamy dají spojovat pomocí `+`:
+
+``` pycon
+>>> loterie + [5, 6, 7, 8]
+[59, 42, 30, 19, 12, 3, 5, 6, 7, 8]
+```
+
+Tím se vytvoří nový seznam, ten původní zůstává nezměněný:
+
+``` pycon
+>>> loterie
+[59, 42, 30, 19, 12, 3]
+```
+
+Pokud chceš něco přidat do původního seznamu, můžeš to provést pomocí metody
+`append`.
+Ale pozor! Tahle metoda potřebuje vědět co má do seznamu přidat.
+Nová hodnota se zadává do závorek:
+
+``` pycon
+>>> loterie.append(199)
+```
+
+Metoda opět nic nevrací, takže je potřeba seznam pro kontrolu vypsat:
+
+``` pycon
+>>> loterie
+[59, 42, 30, 19, 12, 3, 199]
+```
+
+## Vybírání prvků
+
+Když se budeš chtít na jednu věc ze seznamu podívat podrobněji,
+přijde vhod možnost vybrat si konkrétní prvek.
+Na to se v Pythonu používají hranaté závorky.
+
+{# XXX: MCQ #}
+
+Chceš-li vybrat prvek, zadej jméno seznamu a hned za ním hranaté závorky
+s pořadovým číslem prvku, který chceš:
+
+``` pycon
+>>> loterie[1]
+```
+
+Dostaneš první prvek?
+
+{% filter solution %}
+``` pycon
+>>> loterie
+[59, 42, 30, 19, 12, 3, 199]
+>>> loterie[1]
+42
+```
+
+Ne, dostaneš druhý prvek.
+
+Programátoři počítají od nuly.
+Chceš li tedy první prvek, popros Python o prvek číslo nula:
+
+``` pycon
+>>> loterie[0]
+42
+```
+
+Je to zpočátku divné, ale dá se na to zvyknout.
+{% endfilter %}
+
+Číslu prvku se také říká *index* a procesu vybírání prvků *indexování*.
+
+Zkus si indexování s dalšími indexy: 3, 100, 7, -1, -2, -6 nebo -100.
+Pokus se předpovědět výsledek před zadáním příkazu.
+Jak ti to půjde?
+
+{% filter solution %}
+``` pycon
+>>> loterie
+[59, 42, 30, 19, 12, 3, 199]
+
+>>> loterie[3]
+19
+```
+Index 3 označuje čtvrtý prvek.
+
+``` pycon
+>>> loterie[7]
+Traceback (most recent call last):
+ File "", line 1, in
+IndexError: list index out of range
+
+```
+Prvek s indexem 100 v seznamu není – nastane chyba.
+
+``` pycon
+>>> loterie[1000]
+Traceback (most recent call last):
+ File "", line 1, in
+IndexError: list index out of range
+```
+Prvek s indexem 7 v seznamu taky není.
+
+``` pycon
+>>> loterie[-1]
+199
+```
+Index -1 označuje *poslední* prvek.
+
+``` pycon
+>>> loterie[-2]
+3
+```
+Index -2 označuje předposlední prvek.
+
+``` pycon
+>>> loterie[-6]
+42
+```
+Index -6 označuje šestý prvek od konce.
+
+``` pycon
+>>> loterie[-100]
+Traceback (most recent call last):
+ File "", line 1, in
+IndexError: list index out of range
+```
+Stý prvek od konce v seznamu není. Nastane chyba.
+{% endfilter %}
+
+
+## Odstraňování
+
+Chceš-li ze seznamu něco odstranit, můžeš opět použít indexy.
+Tentokrát s příkazem `del`.
+Následujícím kódem odstraň počáteční číslo seznamu, tedy prvek číslo 0:
+
+``` pycon
+>>> del loterie[0]
+```
+
+Pak si seznam opět vypiš. Kousek chybí!
+
+``` pycon
+>>> loterie
+[42, 30, 19, 12, 3, 199]
+```
+
+Zkusíš odstranit poslední prvek?
+
+{% filter solution %}
+``` pycon
+>>> del loterie[-1]
+>>> loterie
+[42, 30, 19, 12, 3]
+```
+{% endfilter %}
+
+Občase se stane, že nechceš smazat prvek podle pozice, ale podle toho,
+co v seznamu je.
+K tomu slouží hodnota `remove`, která najde a odstraní danou hodnotu:
+
+```pycon
+>>> loterie
+[42, 3]
+>>> loterie.remove(3)
+>>> loterie
+[42]
+```
+
+
+## Řezání
+
+Ze seznamu se dá kromě jednoho prvku vybrat i prvků několik – část seznamu,
+takzvaný *podseznam*.
+
+Udělej si opět delší seznam čísel:
+
+``` pycon
+>>> cisla = ["První", "Druhý", "Třetí", "Čtvrtý"]
+```
+
+Budeš-li chtít vybrat prvky od druhého dál, dej do hranatých závorek číslo
+tohohle prvku, a za něj dvojtečku.
+
+``` pycon
+>>> cisla[1]
+'Druhý'
+>>> cisla[1:]
+['Druhý', 'Třetí"', 'Čtvrtý']
+```
+
+Vybráním podseznamu se seznam nemění, tak můžeš vybírat dál:
+
+```pycon
+>>> cisla
+['První', 'Druhý', 'Třetí', 'Čtvrtý']
+>>> cisla[1:]
+['Druhý', 'Třetí"', 'Čtvrtý']
+>>> cisla[2:]
+['Třetí', 'Čtvrtý']
+>>> cisla[3:]
+['Čtvrtý']
+>>> cisla[4:]
+[]
+```
+
+Budeš-li chtít vybrat prvky od začátku *až po* některý prvek, dej dvojtečku
+*před* číslo prvku, který už ve výsledku nechceš
+
+
+``` pycon
+>>> cisla[2]
+'Třetí'
+>>> cisla[:2]
+['První', 'Druhý']
+```
+
+Úkol: máš-li nějaký seznam, jak z něj vybereš všechny prvky kromě posledního?
+
+{% filter solution %}
+Poslední číslo má index -1, vyberu tedy prvky do -1:
+
+``` pycon
+>>> cisla[:-1]
+['První', 'Druhý', 'Třetí']
+```
+
+Taky v zápisu pro vybrání všeho kromě posledního prvku vidíš smajlík?
+\[:-1]
+
+{% endfilter %}
+
+Začátek a konec se dá kombinovat – číslo můžeš dát před i za dvojtečku:
+
+```pycon
+>>> cisla
+['První', 'Druhý', 'Třetí', 'Čtvrtý']
+>>> cisla[1:-1]
+['Druhý', 'Třetí']
+```
+
+Řezání funguje i pro příkaz `del`.
+Zkus vymazat prostřední dvě čísla:
+
+``` pycon
+>>> cisla
+['První', 'Druhý', 'Třetí', 'Čtvrtý']
+>>> del cisla[1:-1]
+>>> cisla
+['První', 'Čtvrtý']
+```
+
+
+## Řezání řetězců
+
+Hranaté závorky fungují i u řetězců, kde vybírají písmenka:
+
+``` pycon
+>>> jidlo = 'čokoláda'
+>>> jidlo[3]
+'o'
+>>> jidlo[1:4]
+'oko'
+```
+
+Řetězce se ale nedají měnit: `del`, `sort` nebo `append` fungují jen
+na seznamech.
+
+Úkol: Představ si, že máš v proměnné `jmeno` ženské jméno jako `'Ola'`,
+`'Krystýna'` nebo `'Růžena'`.
+Jak z něj vytvoříš druhý pád (např. bez `'Růženy'`)?
+
+{% filter solution %}
+Vezmi jméno až po poslední písmeno a přidej `'y'`. Například:
+``` python
+>>> jmeno = 'Růžena'
+>>> jmeno[:-1] + 'y'
+'Růženy'
+>>> jmeno = 'Krystýna'
+>>> jmeno[:-1] + 'y'
+'Krystýny'
+```
+{% endfilter %}
+
+
+## Shrnutí
+
+Uf! O seznamech toho bylo k naučení celkem hodně. Shrňme si, co už umíš:
+
+* **Seznam** je seřazená sekvence hodnot.
+* Pomocí **metod** se seznam dá řadit (`sort`) a obrátit (`reverse`),
+ nebo se do něj dá přidat (`append`) či odebrat (`remove`) prvek.
+* Prvky se dají **vybrat** nebo **odstranit** (`del`) podle indexu.
+* Číslování začíná **od nuly**, záporná čísla berou prvky od konce.
+* **Podseznam** je určitá část seznamu.
+* U **řetězců** funguje vybírání prvků a podřetězců podobně
+
+Jsi připraven{{a}} na další část?
diff --git a/lessons/fast-track/list/info.yml b/lessons/fast-track/list/info.yml
new file mode 100644
index 00000000..b540485b
--- /dev/null
+++ b/lessons/fast-track/list/info.yml
@@ -0,0 +1,12 @@
+title: Seznamy
+style: md
+attribution:
+- Založeno na materiálech [DjangoGirls](https://djangogirls.org/).
+- Část této kapitoly je založena na kurzu [Geek Girls Carrots](https://github.com/ggcarrots/django-carrots).
+- |
+ Původní DjangoGirls tutoriál přeložila do češtiny skupina dobrovolníků.
+ Poděkování patří hlavně: Davidovi (dakf), Kristýně Kumpánové,
+ Veronice Gabrielové, Tomáši Ehrlichovi, Aničce Jaegerové,
+ Matějovi Stuchlíkovi, Filipovi Sivákovi a Juraji M. Bezručkovi.
+- Pro PyLadies CZ upravil Petr Viktorin, 2018.
+license: cc-by-sa-40
diff --git a/lessons/fast-track/pyglet/index.md b/lessons/fast-track/pyglet/index.md
new file mode 100644
index 00000000..f379b9d9
--- /dev/null
+++ b/lessons/fast-track/pyglet/index.md
@@ -0,0 +1,348 @@
+# Grafika
+
+Teď si ukážeme, jak napsat grafickou aplikaci.
+
+Samotný Python obsahuje nástroje na kreslení obrázků,
+ale pro tvorbu her nejsou příliš vhodné.
+Použijeme proto *knihovnu* (nadstavbu) jménem Pyglet, která je přímo stavěná
+na interaktivní grafiku.
+
+Musíme si ji ale nejdřív zvlášť nainstalovat.
+Nejjistější je do příkazové řádky se zapnutým virtuálním prostředím
+zadat následující dva příkazy.
+(Existují i jednodušší způsoby, které ovšem vyžadují „správně“
+nastavený systém.)
+
+* Aktualizace nástroje `pip`, který umí instalovat knihovny pro Python:
+ ``` console
+ (venv)$ python -m pip install --upgrade pip
+ ```
+ (V překladu: **Python**e, spusť **m**odul **pip** a řekni mu,
+ ať na**instal**uje a kdyžtak aktualizuje (*upgrade*) knihovnu **pip**.)
+* Samotné nainstalování Pygletu:
+ ``` console
+ (venv)$ python -m pip install pyglet
+ ```
+ (V překladu: **Python**e, spusť **m**odul **pip** a řekni mu,
+ ať na**instal**uje knihovnu **pyglet**.)
+
+U mě vypadá instalace nějak takto:
+
+```console
+(venv)$ python -m pip install --upgrade pip
+Requirement already satisfied: pip in ./venv/lib/python3.6/site-packages (18.0)
+(venv)$ python -m pip install pyglet
+Collecting pyglet
+ Downloading pyglet-1.2.4-py3-none-any.whl (964kB)
+Installing collected packages: pyglet
+Successfully installed pyglet-1.2.4
+```
+
+Důležité je `Successfully installed`, resp. `Requirement already satisfied`
+na konci.
+To znamená že je knihovna připravená k použití!
+
+
+## Kostra programu
+
+Teď zkus v editoru vytvořit nový soubor, uložit ho jako `grafika.py`
+a napsat do něj následující program:
+
+```python
+import pyglet
+window = pyglet.window.Window()
+pyglet.app.run()
+print('Hotovo!')
+```
+
+Spusť ho. Mělo by se objevit černé okýnko.
+
+> [note] Okýnko není černé?
+> Na některých počítačích (často s macOS a některými druhy Linuxu) se stává,
+> že okýnko není černé, ale je v něm nějaký „nepořádek“.
+> To nevadí.
+> Než do okýnka začneme kreslit, nepořádek uklidíme.
+
+> [note] AttributeError?
+> Jestli dostaneš chybu
+> `AttributeError: module 'pyglet' has no attribute 'window'`, zkontroluj si,
+> zě jsi soubor pojmenoval{{a}} `grafika.py` a ne `pyglet.py`.
+> Soubor v editoru ulož jako `grafika.py`, případný soubor `pyglet.py` smaž,
+> a zkus to znovu.
+
+> [note] Jiná chyba?
+> Grafika je choulostivá záležitost – používáš systém se spoustou částí,
+> které se můžou pokazit (Python, Pyglet, OpenGL, grafická karta a
+> ovladač pro ni, operační systém, …).
+>
+> Jestli ti to nefunguje, nejlepší bude se poradit s odborníkem.
+
+Hotovo? Pojďme si vysvětlit, co se v tomhle programu děje.
+
+Příkaz `import pyglet` ti zpřístupní grafickou knihovnu, tak jako třeba
+`import random` ti zpřístupní funkce okolo náhodných čísel.
+
+Zavolání `pyglet.window.Window()` vytvoří nové *okýnko* na obrazovce.
+Vrátí objekt, kterým pak tohle okýnko můžeš ovládat; ten se uloží
+do proměnné `window`.
+
+Zavolání `pyglet.app.run()` pak spustí aplikaci.
+Co to znamená?
+
+Jednoduché programy, které jsi zatím psal{{a}}, jsou popisy procesu – podobně
+jako třeba recepty k vaření.
+Sled kroků, které Python postupně vykoná od prvního po poslední.
+Občas se něco opakuje a některé kroky se dají „zabalit“ do funkce,
+ale vždycky jsme zatím popisovali jeden postup od začátku po konec.
+
+Programy pro složitější aplikace vypadají spíš než jako recept jako příručka
+automechanika.
+Popisují, co se má stát v jaké situaci.
+Třeba program pro textový editor by mohl vypadat takhle:
+
+*
Když uživatel zmáčkne písmenko na klávesnici, přidej ho do dokumentu.
+*
Když uživatel zmáčkne ⌫ Backspace, poslední písmenko umaž.
+*
Když uživatel zmáčkne tlačítko Uložit, zapiš soubor na disk.
+
+I takový program se dá napsat i jako „recept“ – ale ten recept je pro všechny
+aplikace stejný:
+
+* Pořád dokola:
+ * Počkej, než se něco zajímavého stane (zmáčknutí klávesy, tlačítka, …)
+ * Zareaguj na nastalou situaci
+
+A to je přesně to, co dělá `pyglet.app.run()`.
+Zpracovává *události*, situace na které je potřeba zareagovat.
+V tvém programu zatím reaguje na zavírací tlačítko okýnka a na klávesu
+Esc tím, že okno zavře a ukončí se.
+
+Tvůj programátorský úkol teď bude popsat, jaké další události jsou zajímavé
+a jak na ně reagovat.
+
+
+## Obsluha událostí
+
+Nejjednodušší událost, kterou můžeš obsloužit, je psaní textu na klávesnici.
+
+Zkus do programu těsně nad řádek `pyglet.app.run()` dát následující kód:
+
+``` python
+@window.event
+def on_text(text):
+ print(text)
+```
+
+Co to je?
+Je to definice funkce, ale na začátku má *dekorátor* – tu řádku začínající
+zavináčem.
+Dekorátor `window.event` je způsob, jak Pygletu říct, že má tuto funkci
+spustit, když se něco zajímavého stane.
+
+Co zajímavého?
+To Pyglet zjistí podle jména funkce: `on_text` reaguje na text.
+Vždycky, když uživatel zmáčkne klávesu, Pyglet zavolá tvoji funkci!
+
+A co udělá tvoje funkce? Zavolá `print`. To už znáš.
+Zadaný text se vypíše na konzoli, ze které program spouštíš.
+
+> [note]
+> Funkce `print()` vypisuje texty stále na stejné místo jako předtím.
+> To, že je otevřené grafické okýnko, neznamená že se najednou začne
+> všechno psát do něj.
+
+
+## Kreslení
+
+Jak psát do okýnka?
+To je trochu složitější než do konzole.
+Text tu může mít různé barvy, velikosti, druhy písma,
+může být všelijak posunutý nebo natočený…
+
+Všechny tyhle *atributy* písma je potřeba (i se samotným textem) uložit
+do objektu `Label` („popisek“).
+Zkus to – dej následující kód pod řádek s `window = `:
+
+```python
+label = pyglet.text.Label("Ahoj!", x=10, y=20)
+```
+
+V proměnné `label` teď budeš mít máš popisek s textem `"Ahoj"`, který patří
+na pozici (10, 20) – 10 bodů od pravého okraje okna, 20 od spodního.
+
+> [note]
+> Ostatní vlastnosti kromě pozice tu nejsou zadané, tak se použijí
+> rozumné „výchozí“ hodnoty: bílá barva, malý ale čitelný font.
+
+Popisek se ale sám nevypíše.
+Podobně jako pro vypsání textu do konzole je potřeba zavolat `print`,
+pro nakreslení textu je potřeba reagovat na událost
+*vykreslení okna* – `on_draw`.
+
+Dej pod funkci `on_text` tento kód:
+
+```python
+@window.event
+def on_draw():
+ window.clear()
+ label.draw()
+```
+
+Tuhle funkci Pyglet zavolá vždycky, když je potřeba nakreslit obsah okýnka.
+U animací (filmů nebo her) to často bývá třeba 60× za sekundu
+(„[60 FPS](https://cs.wikipedia.org/wiki/Sn%C3%ADmkov%C3%A1_frekvence)“).
+
+Funkce dělá dvě věci:
+* Smaže celé okýnko (nabarví ho na černo)
+* Vykreslí text
+
+V okně teď bude vidět pozdrav!
+
+
+Zkus ještě změnit `on_text` tak, aby se zadaný text místo na konzoli
+ukázal v okýnku.
+To se dělá přiřazením do *atributu* `label.text`:
+
+```python
+@window.event
+def on_text(text):
+ print('Starý text:', label.text)
+ label.text = text
+ print('Nový text:', label.text)
+```
+
+Zvládneš v této funkci nový text přidat ke starému,
+aby program fungoval jako jednoduchý textový editor?
+
+{% filter solution %}
+```python
+@window.event
+def on_text(text):
+ label.text = label.text + text
+```
+{% endfilter %}
+
+
+## Další události
+
+Na jaké další události se dá reagovat?
+Všechny jsou popsané v [dokumentaci Pygletu](https://pyglet.readthedocs.io/en/latest/modules/window.html#pyglet.window.Window.on_activate).
+Tady uvádím pár zajímavých.
+
+### Stisk klávesy
+
+Klávesy, které nezadávají text (šipky, Backspace nebo
+Enter, atp.) se dají rozpoznat v události `on_key_press`.
+
+Funkce `on_key_press` má dva argumenty: první je kód klávesy,
+který můžeš porovnat s konstantou z [pyglet.window.key](https://pyglet.readthedocs.io/en/latest/modules/window_key.html#key-constants).
+Druhý určuje stisknuté modifikátory jako Shift nebo Ctrl.
+
+``` python
+@window.event
+def on_key_press(key_code, modifier):
+ if key_code == pyglet.window.key.BACKSPACE:
+ label.text = label.text[:-1]
+
+ if key_code == pyglet.window.key.ENTER:
+ print('Zadaná zpráva:', label.text)
+ window.close()
+```
+
+Na macOS budeš možná muset zaměňit `BACKSPACE` za `DELETE`. {# XXX: je to tak? #}
+(Nebo si doma nastuduj [způsob](https://pyglet.readthedocs.io/en/latest/programming_guide/keyboard.html#motion-events), jak to dělat automaticky a správně.)
+
+
+### Kliknutí myši
+
+Při obsluze události `on_mouse_press` dostaneš informace o pozici
+kliknutí (x-ovou a x-ovou souřadnici)
+a navíc informaci o stisknutém tlačítku myši a modifikátoru.
+
+Takhle se třeba popisek přesune na místo kliknutí:
+
+```python
+@window.event
+def on_mouse_press(x, y, button, modifier):
+ label.x = x
+ label.y = y
+```
+
+## Animace
+
+Trochu jiný druh události je *tiknutí hodin*: něco, co se provádí pravidelně.
+
+Takhle vznikají animace, když se velice často – třeba 60× za sekundu – něco
+na obrazovce změní a překreslí.
+
+Jak na to v Pygletu?
+Opět je třeba nadefinovat funkci, ale tentokrát nebude stačit jen
+`window.event`.
+Pyglet potřebuje vědět, jak často má funkci volat.
+To mu pověz zavoláním `pyglet.clock.schedule_interval` se dvěma argumenty:
+jménem funkce a časem mezi jednotlivými voláními – ¹/₆₀ vteřiny.
+Celé to opět napiš před řádek `pyglet.app.run()`:
+
+```python
+def tik(dt):
+ label.x = label.x + 1
+
+pyglet.clock.schedule_interval(tik, 1/60)
+```
+
+Pyglet teď každou šedesátinu vteřiny posune text o 1 pixel doprava.
+
+
+## Celý program
+
+Pro případ, že by ses ztratil{{a}} nebo nevěděla,
+kam který kousek kódu patří, uvádím výsledný ukázkový program.
+
+```python
+import pyglet
+window = pyglet.window.Window()
+label = pyglet.text.Label("Ahoj!", x=10, y=20)
+
+
+@window.event
+def on_draw():
+ window.clear()
+ label.draw()
+
+
+@window.event
+def on_text(text):
+ label.text = label.text + text
+
+
+@window.event
+def on_key_press(key_code, modifier):
+ if key_code == pyglet.window.key.BACKSPACE:
+ label.text = label.text[:-1]
+
+ if key_code == pyglet.window.key.ENTER:
+ print('Zadaná zpráva:', label.text)
+ window.close()
+
+
+@window.event
+def on_mouse_press(x, y, button, modifier):
+ label.x = x
+ label.y = y
+
+def tik(dt):
+ label.x = label.x + 1
+
+pyglet.clock.schedule_interval(tik, 1/60)
+
+pyglet.app.run()
+```
+
+Tolik k trénovací grafické aplikaci.
+Co ses naučil{{a}}?
+
+* Funkce `pyglet.window.Window()` z modulu `pyglet` vytvoří okýnko.
+* Dekorátor `@window.event` označuje funkci, kterou Pyglet zavolá
+ v reakci na určitou událost: vstup z klávesnice (`on_text`),
+ vykreslení (`on_draw`), stisk klávesy (`on_key_press`), atp.
+* Voláním `pyglet.app.run()` říkáš Pygletu, že je vše nastavené
+ a má spustit aplikaci.
diff --git a/lessons/fast-track/pyglet/info.yml b/lessons/fast-track/pyglet/info.yml
new file mode 100644
index 00000000..75daef24
--- /dev/null
+++ b/lessons/fast-track/pyglet/info.yml
@@ -0,0 +1,5 @@
+title: Úvod do Pygletu
+style: md
+attribution:
+- Pro PyLadies Brno napsal Petr Viktorin, 2015-2018.
+license: cc-by-sa-40
diff --git a/lessons/fast-track/random/index.md b/lessons/fast-track/random/index.md
new file mode 100644
index 00000000..7bbd6809
--- /dev/null
+++ b/lessons/fast-track/random/index.md
@@ -0,0 +1,63 @@
+# Náhoda
+
+Občas je potřeba vybrat náhodnou hodnotu.
+Na to není v Pythonu funkce k dispozici přímo, ale dá se zpřístupnit
+pomocí příkazu `import`:
+
+```pycon
+>>> from random import randrange
+>>> randrange(6)
+3
+```
+
+Neboli:
+
+* Z modulu `random` (který obsahuje funkce kolem náhodných hodnot)
+ zpřístupni (`import`) funkci `randrange` (která umí vybírat náhodná čísla).
+* Vyber náhodné číslo ze šesti možností.
+
+Volání funkce `randrange` několikrát opakuj.
+Jaká čísla můžeš dostat?
+
+{% filter solution %}
+Čísla od 0 do 5 – šestku ne.
+Programátoři totiž počítají od nuly, a když počítáš šest čísel od nuly,
+dostaneš se jen k pětce.
+
+Když budeš chtít „házet kostkou“ – vybírat čísla od 1 do 6 – můžeš napsat:
+```pycon
+>>> from random import randrange
+>>> randrange(6) + 1
+4
+```
+{% endfilter %}
+
+Modulů jako `random`, ze kterých se dají *naimportovat* užitečná rozšiření,
+je spousta – na práci s textem, kreslení obrázků, práci se soubory nebo dny
+v kalendáři, kompresi dat, posílání e-mailů, stahování z internetu…
+Stačí jen vědět (nebo umět najít), jak se ten správný modul a funkce jmenuje.
+A kdyby nestačilo to, co má Python zabudované v sobě, další rozšiřující moduly
+se dají doinstalovat.
+
+## Náhodný výběr
+
+Když už jsme u náhody, zkusme si ještě vylosovat náhodné číslo v loterii.
+Na výběr ze seznamu má modul `random` funkci `choice`:
+
+```pycon
+>>> from random import choice
+>>> loterie = [3, 42, 12, 19, 30, 59]
+>>> choice(loterie)
+12
+```
+
+Podobně se dá vybrat náhodná karta z ruky, náhodný účastník kurzu,
+náhodná barva – cokoli, co umíš dát do seznamu.
+
+
+## Shrnutí
+
+* Příkaz **import** ti dá k dispozici funkčnost, která není k dispozici přímo
+ v Pythonu.
+* Modul **random** obsahuje funkce **randrange** (náhodné číslo) a **choice**
+ (náhodný prvek seznamu).
diff --git a/lessons/fast-track/random/info.yml b/lessons/fast-track/random/info.yml
new file mode 100644
index 00000000..80fe54e5
--- /dev/null
+++ b/lessons/fast-track/random/info.yml
@@ -0,0 +1,5 @@
+title: Náhoda
+style: md
+attribution:
+- Pro PyLadies Brno napsal Petr Viktorin, 2019.
+license: cc-by-sa-40
diff --git a/lessons/fast-track/repl/index.md b/lessons/fast-track/repl/index.md
new file mode 100644
index 00000000..3727f7cf
--- /dev/null
+++ b/lessons/fast-track/repl/index.md
@@ -0,0 +1,112 @@
+# Interaktivní režim Pythonu
+
+Chceš-li si začít hrát s Pythonem, otevři *příkazový řádek* a aktivuj virtuální prostředí. Zkontroluj si, že na začátku příkazové řádky ti svítí `(venv)`.
+
+Je-li tomu tak, nezbývá než – konečně – pustit Python. K tomu použij příkaz `python`:
+
+``` console
+$ python
+Python 3.6.6 (...)
+Type "help", "copyright", "credits" or "license" for more information.
+>>>
+```
+
+Příkaz vypíše několik informací. Z prvního řádku se můžeš ujistit, že používáš Python 3. (Vidíš-li číslo jako `2.7.11`, něco je špatně – popros o radu kouče.)
+
+Třemi „zobáčky“ ``>>>` pak Python poprosí o instrukce. Je to jako v příkazové řádce, ale místo příkazů jako `cd` a `mkdir` sem budeš psát příkazy Pythonu.
+
+Jako první instrukci použijeme Pythonu jako kalkulačku.
+Za tři zobáčky napiš třeba `2 + 3` a zmáčkni Enter.
+
+``` pycon
+>>> 2 + 3
+5
+```
+
+Zobrazila se ti správná odpověď?
+Pokud ano, gratuluji! První příkaz v Pythonu máš za sebou.
+
+Zkusíš i odečítání?
+
+A jak je to s násobením?
+{# XXX: Jak zapsat násobení? `4 x 5` `4 . 5` `4 × 5` `4 * 5` -#}
+Na kalkulačce bys zadala `4 × 5`, což se na klávesnici píše špatně.
+Python proto používá symbol `*`.
+
+``` pycon
+>>> 4 * 5
+20
+```
+
+Symboly jako `+` a `*` se odborně nazývají *operátory*.
+
+Operátor pro dělení je `/`.
+
+Při dělení může vzniknout necelé číslo, třeba dva a půl.
+Python používá desetinnou *tečku*, ukáže se tedy `2.5`:
+
+``` pycon
+>>> 5 / 2
+2.5
+```
+
+Z důvodů, do kterých teď nebudeme zabíhat, se při dělení desetinná tečka
+objeví i když vyjde číslo celé:
+``` pycon
+>>> 4 / 2
+2.0
+```
+
+Občas se hodí použít dělení se zbytkem.
+Výsledek tak zůstane jako celé číslo.
+Na to má Python operátory `//` (podíl) a `%` (zbytek):
+
+``` pycon
+>>> 5 // 2
+2
+>>> 5 % 2
+1
+```
+
+
+{# XXX:
+Kolik je
+?
+#}
+
+## Chyby
+
+Někdy se stane, že zadáš něco špatně – tak, že tomu Python nebude rozumět.
+Třeba když omylem použiješ neexistující operátor `%%`, dostaneš
+jako odpověď *chybovou hlášku*:
+
+```pycon
+>>> 5 %% 2
+ File "", line 1
+ 5 %% 2
+ ^
+SyntaxError: invalid syntax
+```
+
+Stává se to dost často – začátečníkům i profesionálním programátorům.
+Chybových hlášek se nemusíš bát.
+I když je něco špatně, stačí na dalším řádku zadat opravený příkaz.
+
+V hlášce ti Python říká, že něco nepochopil nebo je něco jinak špatně.
+Snaží se co nejlíp naznačit, *co* a *kde* je špatně (ale je to jen hloupý
+stroj, tak mu to občas nevyjde).
+V chybové hlášce nahoře si všimni šipečky, `^`, která ukazuje na místo kde
+si Python chyby všiml.
+
+Orientace v chybových hláškách patří k základním schopnostem programátorů.
+Až příště nějakou chybu dostaneš, zkus se zamyslet co ti v ní Python chce říct.
+
+
+### Shrnutí
+
+Co ses zatím naučil{{a}}?
+
+* **Interaktivní režim Pythonu** umožňuje zadávat příkazy (kód) pro
+ Python a zobrazuje výsledky/odpovědi.
+* **Čísla** se používají na matematiku a práci s textem.
+* **Operátor** jako `+` a `*` kombinuje hodnoty a vytvoří výsledek.
diff --git a/lessons/fast-track/repl/info.yml b/lessons/fast-track/repl/info.yml
new file mode 100644
index 00000000..31c415c7
--- /dev/null
+++ b/lessons/fast-track/repl/info.yml
@@ -0,0 +1,12 @@
+title: Interaktivní režim Pythonu
+style: md
+attribution:
+- Založeno na materiálech [DjangoGirls](https://djangogirls.org/).
+- Část této kapitoly je založena na kurzu [Geek Girls Carrots](https://github.com/ggcarrots/django-carrots).
+- |
+ Původní DjangoGirls tutoriál přeložila do češtiny skupina dobrovolníků.
+ Poděkování patří hlavně: Davidovi (dakf), Kristýně Kumpánové,
+ Veronice Gabrielové, Tomáši Ehrlichovi, Aničce Jaegerové,
+ Matějovi Stuchlíkovi, Filipovi Sivákovi a Juraji M. Bezručkovi.
+- Pro PyLadies CZ upravil Petr Viktorin, 2018.
+license: cc-by-sa-40
diff --git a/lessons/fast-track/requests-weather/index.md b/lessons/fast-track/requests-weather/index.md
new file mode 100644
index 00000000..8f3ed740
--- /dev/null
+++ b/lessons/fast-track/requests-weather/index.md
@@ -0,0 +1,337 @@
+# Stahování z internetu a API
+
+Část těchto materiálů pochází z [jiného kurzu PyLadies](https://naucse.python.cz/2019/brno-jaro-knihovny/beginners/kurzovni-listek/).
+
+## Requests
+
+Začneme seznámením s knihovnou [requests]. Je to knihovna určená pro HTTP
+požadavky na straně klienta. Poskytuje mnohem pohodlnější rozhraní než
+standardní knihovna Pythonu.
+
+[requests]: http://docs.python-requests.org/en/master/
+
+Prvním krokem by měla být instalace ve virtuálním prostředí:
+
+```console
+(venv) $ python -m pip install requests
+```
+
+První pokus je ideální provádět v interaktivní konzoli Pythonu. Začneme tím, že
+si naimportujeme modul `requests`. Komunikace přes protokol HTTP používá model
+požadavek/odpověď (*request*/*response*). Klient tedy nejprve pošle požadavek,
+a server potom odpovídá. Takto se střídají, dokud klient nemá vše, co
+potřebuje, nebo nedojde k chybě.
+
+Pro začátek se podíváme na stránku `https://example.com`.
+
+```pycon
+>>> import requests
+>>> response = requests.get("https://example.com/")
+>>> response
+
+```
+
+Takto vypsaná odpověď není příliš užitečná. To naštěstí není zase takový
+problém. V proměnné `response` teď máme object, který má potřebná data uložená
+v různých atributech.
+
+Zkuste si vypsat, co obsahují atributy `response.text`, `response.status_code`.
+Taky vyzkoušejte zavolat metodu `response.json()`. Existuje jich mnohem více,
+ale tyto jsou docela zajímavé a
+relativně často užívané.
+
+Pojďme se tedy podívat, co dělají zmíněné jednotlivé atributy:
+
+Atribut `text` obsahuje tělo odpovědi, tak jak nám ze serveru přišla. Pro
+většinu stránek to bude kód v jazyku HTML, nebo v data v různých formátech.
+
+Každá odpověď od serveru obsahuje číselný kód, který popisuje výsledek akce.
+Tento kód si můžete přečíst z atributu `status_code`. `1xx` jsou informační
+zprávy, na které moc často nenarazíte. `2xx` jsou úspěšné odpovědi. Někdy se
+může stát, že server místo odpovědi, kterou chcete, odešle *přesměrování*. To
+má podobu odpovědi s kódem `3xx`. Přímo tuto odpověď neuvidíte, protože
+knihovna `requests` ví, že je to přesměrování a proto automaticky půjde na
+adresu, kam vás server poslal.
+
+Ke každému číselnému kódu existuje i texotvý popis. Ty najdete třeba na
+[Wikipedii](), nebo můžete použít .
+
+> [note]
+> je velice užitečná služba, pokud si potřebujete
+> vyzkoušet komunikaci přes HTTP. Bude vám odpovídat na všemožné požadavky
+> podle toho, jak si řeknete. Podívejte se v prohlížeči a uvidíte docela pěkný
+> seznam všech možností (akorát v angličtině)
+
+Nakonec nám zůstává metoda `json()`. JSON je datový formát, který používá mnoho
+různých webových služeb. Proto `requests` nabízí tuto zkratku, jak se k datům
+dostat. Ale pozor! Pokud v odpovědit nejsou data v tomto formátu, dostanete
+chybu!
+
+
+## Kurzy měn
+
+Začneme zvolna - zkusíme si stáhnout aktuální kurzy měn, které poskyuje [Česká
+národní banka](https://www.cnb.cz/) na adrese:
+
+Výstup pro lidi:
+
+https://www.cnb.cz/cs/financni-trhy/devizovy-trh/kurzy-devizoveho-trhu/kurzy-devizoveho-trhu/
+
+Výstup pro vývojáře:
+
+https://www.cnb.cz/cs/financni-trhy/devizovy-trh/kurzy-devizoveho-trhu/kurzy-devizoveho-trhu/denni_kurz.txt
+
+
+
+
+# Příklad: Jaké bude počasí v Brně?
+
+Vyzkoušíme si napsat program, který nám dokáže zjistit předpověď počasí v námi
+vybraném městě.
+
+Co k tomu budeme potřebovat? Znalosti o proudění vzduchu, historická data,
+srážky... tak ty to nejsou. Ta už pro nás naštěstí připravili jiní lidé a tyto
+informace volně poskytují na internetu. Zbývá tedy se jich akorát správně doptat.
+
+
+## OpenWeathermap API
+
+Existuje mnoho služeb pro vývojáře, které poskytují data o počasí ve strojově
+čitelné formě. Jedním z nich je například [OpenWeatherMap](https://openweathermap.org/)
+
+### Přístup ke službě
+
+Data jsou přístupná pro kohokoli volně, jen je třeba poskytovateli dát vědět, že
+je používáte zrovna vy. Častým způsobem této indentifikace je pomocí tzv.
+*tokenu*, což není nic jiného, než náhodně vygenerovaný řetězec znaků, který
+nahrazuje zadávání uživatelského jména a hesla. Každý uživatel má token jiný.
+
+Zařiď si tedy účet na https://home.openweathermap.org
+
+
+Na webu si udělej registraci (Sign Up) - stačí zatrhnout potvrzení, že jste
+starší 16 let a že souhlasíte s podmínkami použití této služby.
+
+Po odeslání pak na stránce *API keys* najdeš v kolonce *Key* řetězec podobný
+tomuto (může to trvat několik minut, než ti pak reálně povolí přístup k datům):
+```
+1faf9fd2f2d64a383e7c0011fa127956
+```
+
+Tento řetězec použijeme pro všechny tvé požadavky na získání dat. Kvůli limitům
+používání této služby si ale nechej vygenerovat vlastní token. Uvedený výše je
+už neplatný.
+
+## Dotaz na počasí
+
+V [dokumentaci](https://openweathermap.org/forecast5#JSON) k API se podíváme,
+jak má požadavek vypadat a jaké parametry můžeme předat.
+
+```python
+import requests
+
+token = '1faf9fd2f2d64a383e7c0011fa127956'
+url = 'http://api.openweathermap.org/data/2.5/forecast'
+
+parametry = {
+ 'APIKEY': token,
+ 'q': 'brno',
+ 'units': 'metric'
+}
+
+odpoved = requests.get(url, params=parametry)
+```
+
+Server poskytuje data ve formátu JSON, který je velmi rozšířený a knihovna `requests` pro něj má metodu, která odpověď převede na slovník.
+
+```python
+predpoved = odpoved.json()
+```
+
+V dokumentaci se dočteme s jakou strukturou máme tu čest. Nejsnazší je však si
+ji rovnou vypsat. Vypadá přibližně takto:
+
+```
+{'cod': '200',
+ 'message': 0.0094,
+ 'cnt': 40,
+ 'list': [{'dt': 1557122400,
+ 'main': {'temp': 4.95,
+ 'temp_min': 4.05,
+ 'temp_max': 4.95,
+ 'pressure': 1015.8,
+ 'sea_level': 1015.8,
+ 'grnd_level': 958.41,
+ 'humidity': 74,
+ 'temp_kf': 0.9},
+ 'weather': [{'id': 600,
+ 'main': 'Snow',
+ 'description': 'light snow',
+ 'icon': '13d'}],
+ 'clouds': {'all': 90},
+ 'wind': {'speed': 5.63, 'deg': 341.687},
+ 'snow': {'3h': 0.125},
+ 'sys': {'pod': 'd'},
+ 'dt_txt': '2019-05-06 06:00:00'},
+ {'dt': 1557133200,
+ 'main': {'temp': 8.92,
+ 'temp_min': 8.25,
+ 'temp_max': 8.92,
+ 'pressure': 1015.93,
+ 'sea_level': 1015.93,
+ 'grnd_level': 959,
+ 'humidity': 57,
+ 'temp_kf': 0.67},
+ 'weather': [{'id': 804,
+ 'main': 'Clouds',
+ 'description': 'overcast clouds',
+ 'icon': '04d'}],
+ 'clouds': {'all': 94},
+ 'wind': {'speed': 5.99, 'deg': 344.69},
+ 'sys': {'pod': 'd'},
+ 'dt_txt': '2019-05-06 09:00:00'},
+ ...
+```
+
+Nás budou nejvíce zajímat klíče `temp` (údaj o teplotě) a `dt_txt` (tzv. časové
+razítko).
+
+Vypíšeme si je jednoduše pod sebe.
+
+```python
+for vzorek in predpoved['list']:
+ datum = vzorek["dt_txt"]
+ teplota = vzorek['main']['temp']
+
+ print(f'{datum} {teplota}')
+```
+
+Takto dostaneme:
+
+```
+2019-05-06 06:00:00 6.24
+2019-05-06 09:00:00 9.69
+2019-05-06 12:00:00 9.96
+2019-05-06 15:00:00 9.64
+2019-05-06 18:00:00 6.2
+2019-05-06 21:00:00 3
+2019-05-07 00:00:00 0.62
+...
+```
+
+V řadě číslech se ale moc dobře neorientuje. Proto si z nich uděláme jednoduchý
+textový graf. Zkus si výstup upravit tak, aby se za každý stupeň vypsala jedna
+tečka (3 stupně `...`, 10 stupňů `..........`).
+
+> [note]
+> Pro zjednodušení se teďka nebudeme trápit s mrazy (zápornou teplotou) - nad
+> tím se můžeš zamyslet potom doma.
+
+{% filter solution %}
+```python
+for vzorek in predpoved['list']:
+ (...)
+ sloupek = '.' * int(teplota)
+
+ print(f'{datum} {sloupek} {teplota}')
+```
+{% endfilter %}
+
+Výsledek bude vypadat nějak takto:
+```
+2019-05-05 15:00:00 ....... 7.44
+2019-05-05 18:00:00 ..... 5.26
+2019-05-05 21:00:00 .... 4.41
+2019-05-06 00:00:00 ... 3.68
+2019-05-06 03:00:00 .. 2.55
+2019-05-06 06:00:00 .... 4.85
+2019-05-06 09:00:00 ........ 8.65
+2019-05-06 12:00:00 ......... 9.15
+2019-05-06 15:00:00 ......... 9.88
+2019-05-06 18:00:00 ..... 5.92
+2019-05-06 21:00:00 .. 2.11
+2019-05-07 00:00:00 0.39
+2019-05-07 03:00:00 -0.33
+2019-05-07 06:00:00 ..... 5.25
+2019-05-07 09:00:00 ......... 9.21
+...
+```
+
+
+## Přidáváme obrázky
+
+Zatím je naše předpověď složena stále jen z běžných ASCII znaků. Pojďme si tam
+přidat i obrázky oblohy.
+
+Součástí předpovědi je tento údaj ve formě textu, např. `Clear`, `Rain`, `Snow`,
+`Clouds`. My se s tím ale nespokojíme a nahradíme si ho obrázky. Můžeme využít
+například ty z Unicode tabulky http://xahlee.info/comp/unicode_weather_symbols.html
+
+> [note]
+> Písma v příkazové řádce ve Windows stále emoji umí jen ve velmi omezené míře.
+> Změň si ho dočasně na `MS Gothic`, pokud ho máš nainstalované.
+> Můžeš použít třeba tyto vyzkoušené znaky z UNICODE tabulky:
+> ```
+> SNOWFLAKE
+> CLOUD
+> UMBRELLA WITH RAIN DROPS
+> FLOWER
+> WHITE SMILING FACE
+> BLACK SMILING FACE
+> ```
+
+
+
+Chceme tedy řetězec `Snow` přeložit na `❄` a napíšeme si na to funkci.
+
+```python
+def ziskej_obrazek(pocasi):
+ mapovani = {
+ 'Snow': '\N{SNOWFLAKE}',
+ 'Rain': '\N{UMBRELLA WITH RAIN DROPS}',
+ 'Clouds': '\N{WHITE SUN WITH SMALL CLOUD}',
+ 'Clear': '\N{SUN WITH FACE}'
+ }
+
+ return mapovani.get(pocasi, '?')
+```
+
+Po zakomponování do kódu:
+```python
+for vzorek in predpoved['list']:
+ datum = vzorek["dt_txt"]
+ teplota = vzorek['main']['temp']
+ sloupek = '.' * int(teplota)
+ pocasi = ziskej_obrazek(vzorek['weather'][0]['main'])
+
+ print(f'{datum} {pocasi} {sloupek} {teplota} \N{DEGREE CELSIUS}')
+```
+
+
+Finální podoba předpovědi:
+```
+2019-05-06 09:00:00 🌤 ........ 8.76 ℃
+2019-05-06 12:00:00 ☔ ......... 9.37 ℃
+2019-05-06 15:00:00 ☔ ......... 9.4 ℃
+2019-05-06 18:00:00 ☔ ...... 6.3 ℃
+2019-05-06 21:00:00 🌤 ... 3 ℃
+2019-05-07 00:00:00 🌤 0.62 ℃
+2019-05-07 03:00:00 🌞 -0.54 ℃
+2019-05-07 06:00:00 🌞 ..... 5.25 ℃
+2019-05-07 09:00:00 🌞 ......... 9.65 ℃
+2019-05-07 12:00:00 ☔ .......... 10.72 ℃
+2019-05-07 15:00:00 ☔ .......... 10.12 ℃
+2019-05-07 18:00:00 ☔ ....... 7.42 ℃
+2019-05-07 21:00:00 ☔ ..... 5.72 ℃
+2019-05-08 00:00:00 🌤 ... 3.52 ℃
+2019-05-08 03:00:00 🌤 .. 2.25 ℃
+2019-05-08 06:00:00 🌤 ...... 6.39 ℃
+```
+
+Služba OpenWeatherMap umí zjistit předpověď počasí nejen pro města, ale libovolné
+místo na Zemi, zadané pomocí GPS souřadnic. Pro převod názvu (např. hory) na
+souřadnice se používá tzv. geocoding. Poskytovatelů této služby je opět mnoho.
+Jedním z nich je například https://locationiq.com/
+
+API můžeme kombinovat dohromady: Název místa → GPS souřadnice→ OpenWeatherMap →
+teploty.
diff --git a/lessons/fast-track/requests-weather/info.yml b/lessons/fast-track/requests-weather/info.yml
new file mode 100644
index 00000000..6ddb2a2c
--- /dev/null
+++ b/lessons/fast-track/requests-weather/info.yml
@@ -0,0 +1,5 @@
+title: Stahování z internetu a API
+style: md
+attribution:
+ - Pro naucse.python.cz napsal Martin Pavlásek, část převzata od Lubomíra Sedláře, 2019.
+license: cc-by-sa-40
diff --git a/lessons/fast-track/script/index.md b/lessons/fast-track/script/index.md
new file mode 100644
index 00000000..35c9d05f
--- /dev/null
+++ b/lessons/fast-track/script/index.md
@@ -0,0 +1,187 @@
+# Ulož to!
+
+Zatím jsi psal{{a}} všechny programy v konzoli v interaktivním režimu Pythonu,
+ve kterém Python vždy po napsání příkazu odpověděl výsledkem.
+Když Python opustíš (nebo vypneš počítač),
+všechno co jsi zatím naprogramoval{{a}} se ztratí.
+
+Větší programy jsou trvanlivější: ukládají se do souborů a dají se kdykoli
+spustit znovu.
+
+Je čas si to vyzkoušet. Budeš potřebovat:
+
+1. Ukončit interaktivní režim Pythonu
+2. Otevřít editor kódu
+3. Uložit kód do nového souboru
+4. Spustit kód ze souboru!
+
+Jako první krok vypni Python. Existuje na to funkce `exit()`:
+
+``` pycon
+>>> exit()
+```
+
+Tak se dostaneš zpět do příkazové řádky. Pamatuješ na ni?
+Už neuvidíš `>>>`, ale řádek končící `$` nebo `>`.
+Budou tu fungovat příkazy jako `cd` a `mkdir`,
+ale ne příkazy Pythonu, jako `1 + 1`.
+
+
+Doufám, že máš nainstalovaný [textový editor](../../beginners/install-editor/).
+Ten teď otevři, udělej si nový soubor a do něj napiš tento příkaz:
+
+```python
+print('Hello, PyLadies!')
+```
+
+Nový soubor ulož pod nějakým popisným názvem: `python_intro.py`.
+Ulož si jej do adresáře, kam si budeš dávat soubory k tomuto workshopu.
+Jméno musí končit na `.py`: tahle přípona říká editoru nebo i
+operačnímu systému, že jde o program v Pythonu a Python ho může spustit.
+
+> [note] Obarvování
+> Po uložení by se text měl obarvit.
+> V interaktivním režimu Pythonu mělo vše stejnou barvu,
+> ale nyní bys měla vidět, že jméno funkce `print` je vysázeno jinou barvou než
+> řetězec v závorkách.
+> Barvy nevolíš {{gnd('sám', 'sama')}}, vybírá je editor na základě toho,
+> jak potom Python kódu porozumí.
+>
+> Nazývá se to "zvýrazňování syntaxe" a je to užitečná funkce.
+> Chce to trochu praxe, ale barvy můžou napovědět
+> že ti chybí uvozovka za řetězcem
+> nebo máš překlep v klíčovém slově jako `del`.
+> To je jeden z důvodů, proč používáme programátorské editory :)
+
+Pokud máš soubor uložen, je čas jej spustit!
+Pomocí dovedností, které jsi se naučil{{a}} v sekci
+o příkazové řádce, *změň adresář* na ten, kam jsi soubor uložil{{a}}.
+{% if var('coach-present') -%}
+(Pokud nevíš jak dál, požádej o pomoc kouče.)
+{% endif %}
+
+Nyní pomocí Pythonu spusť kód v souboru: zadej příkaz `python`, mezeru
+a jméno souboru ke spuštění.
+(Je to podobné jako příkaz `cd` pro konkrétní adresář –
+cd jmeno_adresare.)
+
+``` console
+(venv) $ python python_intro.py
+Hello, PyLadies!
+```
+
+Funguje? Vidíš text?
+Jesli ano, právě jsi spustil{{a}} svůj první opravdový program v Pythonu!
+Cítíš se úžasně?
+
+
+## Výstup
+
+Funkce `print()`, kterou jsi použil{{a}}, umí něco *vypsat* na obrazovku.
+V konzoli se hodnoty výrazů vypisovaly automaticky, abys je mohl{{a}}
+průběžně kontrolovat, ale programy v souborech bývají složitější a výpisy
+z každého kroku by byly nepřehledné.
+Proto na vypsání potřebuješ `print()`.
+Zkus si s následujícím programem:
+
+``` python
+jmeno = 'Ola'
+
+'Já jsem ' + jmeno # Tohle Python nevypíše
+
+print(jmeno * 8) # Tohle jo!
+```
+
+Pak program spusť:
+
+``` console
+(venv) $ python python_intro.py
+OlaOlaOlaOlaOlaOlaOlaOla
+```
+
+Co se stalo?
+Když spustíš soubor s programem, Python jej prochází odshora dolů a postupně,
+řádek po řádku, plní jednotlivé příkazy:
+* proměnnou `jmeno` nastaví na řetězec `'Ola'`,
+* spojí řetězce `'Já jsem '` a `'Ola'`, ale výsledek zahodí,
+* zopakuje řetězec `'Ola'` osmkrát a výsledek vypíše pomocí `print()`.
+
+Do závorek funkce `print()` můžeš dát i víc hodnot oddělených čárkami.
+Zkus obsah souboru změnit na následující program a znovu ho spustit:
+
+``` python
+jmeno = 'Amálka'
+vek = 5
+print('Já jsem', jmeno, 'a je mi', vek)
+
+print('Za rok mi bude', vek + 1)
+```
+
+## Vstup
+
+Další užitečná funkce je `input()`, která se umí zeptat na otázku.
+Odpověď pak vrátí jako řetězec, který si můžeš uložit do proměnné:
+
+``` python
+jmeno = input('Jak se jmenuješ? ')
+
+print(jmeno, 'umí programovat!')
+```
+
+Když program spustíš, zeptá se na otázku.
+Tu zadej na klávesnici *přímo do příkazové řádky*.
+Python ji načte a použije jako výsledek funkce `input`,
+který uloží do proměnné `jmeno`.
+A v rámci dalšího řádku programu ho vytiskne.
+
+``` console
+(venv) $ python python_intro.py
+Jak se jmenuješ? Ola
+Ola umí programovat!
+(venv) $ python python_intro.py
+Jak se jmenuješ? Princezna
+Princezna umí programovat!
+```
+
+Funkce `input()` z klávesnice vždycky načítá text: když uživatel zadá `1234`,
+Python to bere jako řetězec číslic 1, 2, 3, 4.
+
+Když budeš chtít z klávesnice načíst spíš číslo než text, musíš použít funkci,
+která umí převést řetězec na číslo:
+
+``` python
+letopocet = int(input('Jaký je letos rok? '))
+
+print('Loni byl rok', letopocet - 1)
+```
+
+
+## Komentáře
+
+Všiml{{a}} sis u jednoho z předchozích programu poznámek za „mřížkou“ (`#`)?
+
+``` python
+jmeno = 'Ola'
+
+'Já jsem ' + jmeno # Tohle Python nevypíše
+
+print(jmeno * 8) # Tohle jo!
+```
+
+To jsou takzvané *komentáře*.
+Jsou určené jen pro lidi: Python je úplně ignoruje.
+
+Teď, když své programy ukládáš na disk a můžeš se k nim vracet,
+je důležité aby byly *čitelné*: aby z nich nejen počítače, ale i lidi
+poznali, co mají ty instrukce dělat.
+Vždycky když napíšeš nějaký složitější kus kódu k němu zkus přidat komentář
+s vysvětlivkou.
+Až se k programu za pár dní nebo měsíců vrátíš, poděkuješ si!
+
+
+## Shrnutí
+
+* Příkaz **python** pustí uložený soubor jako program v Pythonu.
+* Funkce **print** vypisuje hodnoty.
+* Funkce **input** načítá řetězce, které uživatel zadá na klávesnici.
+* **Komentáře** můžou zpřehlednit složitější kód. Python je ignoruje.
diff --git a/lessons/fast-track/script/info.yml b/lessons/fast-track/script/info.yml
new file mode 100644
index 00000000..94b7f371
--- /dev/null
+++ b/lessons/fast-track/script/info.yml
@@ -0,0 +1,12 @@
+title: Ulož to!
+style: md
+attribution:
+- Založeno na materiálech [DjangoGirls](https://djangogirls.org/).
+- Část této kapitoly je založena na kurzu [Geek Girls Carrots](https://github.com/ggcarrots/django-carrots).
+- |
+ Původní DjangoGirls tutoriál přeložila do češtiny skupina dobrovolníků.
+ Poděkování patří hlavně: Davidovi (dakf), Kristýně Kumpánové,
+ Veronice Gabrielové, Tomáši Ehrlichovi, Aničce Jaegerové,
+ Matějovi Stuchlíkovi, Filipovi Sivákovi a Juraji M. Bezručkovi.
+- Pro PyLadies CZ upravil Petr Viktorin, 2018.
+license: cc-by-sa-40
diff --git a/lessons/fast-track/str/index.md b/lessons/fast-track/str/index.md
new file mode 100644
index 00000000..4a7bb498
--- /dev/null
+++ b/lessons/fast-track/str/index.md
@@ -0,0 +1,215 @@
+# Řetězce
+
+Čísla jsou pro počítače dost užitečná (ostatně slovo *počítač* to naznačuje),
+ale Python umí pracovat i s jinými druhy informací.
+Třeba s textem.
+
+Zkus si to: zadej své jméno do uvozovek, jak vidíš níže:
+
+``` pycon
+>>> 'Ola'
+'Ola'
+```
+
+Nyní jsi vytvořil{{a}} svůj první *řetězec*!
+Řetězec (angl. *string*) je programátorský termín pro *text* – posloupnost
+znaků (písmenek),
+které mohou být zpracovány počítačem.
+
+Když řetězec zadáváš, musíš ho vždy uzavřít do uvozovek (apostrofů).
+Jinak by Python nepoznal, co je text se kterým má pracovat a co jsou instrukce
+které má provést.
+To je pro počítač docela důležité – lidem podobné věci dojdou z kontextu,
+ale počítač je hloupé zařízení.
+
+{{ figure(
+ img=static('quote-comic.svg'),
+ alt='(Ilustrační komiks. Člověk říká robotovi: "Řekni Pavlovi, ať mi zavolá!". Robot odpoví: "PAVLOVI AŤ MI ZAVOLÁ!")',
+) }}
+
+Řetězce se dají spojovat – „sečítat“ – pomocí `+`. Zkus toto:
+
+``` pycon
+>>> 'Já jsem ' + 'Ola'
+'Já jsem Ola'
+```
+
+Pozor na mezeru! Když zadáš `'Já jsem' + 'Ola'`, spojí se ti dvě slova dohromady.
+Počítač považuje i mezeru za *znak*; chová se k ní stejně jako k jakémukoli
+písmenku.
+Když nedáš mezeru do uvozovek, nebude součástí řetězce.
+
+Mezery mezi řetězcem a operátorem (`+`) naopak nehrají roli.
+Všechny následující příkazy dělají to samé:
+
+``` pycon
+>>> 'Já jsem ' + 'Ola'
+'Já jsem Ola'
+>>> 'Já jsem '+'Ola'
+'Já jsem Ola'
+>>> 'Já jsem ' + 'Ola'
+'Já jsem Ola'
+```
+
+Je ale zvykem psát kolem operátoru jednu mezeru z každé strany – tak jako
+v těchto materiálech.
+Kód je pak čitelnější.
+
+Zkus si dát do uvozovek i mezeru samotnou – sečti tři řetězce:
+
+``` pycon
+>>> 'Já jsem' + ' ' + 'Ola'
+'Já jsem Ola'
+```
+
+Kromě „sečítání“ můžeš řetězce i opakovat – násobit číslem:
+
+``` pycon
+>>> 'Ola' * 3
+'OlaOlaOla'
+```
+
+## Uvozování
+
+A co když budeš chtít dát dovnitř do svého řetězce apostrof?
+Můžeš kolem řetězce použít dvojité uvozovky:
+
+``` pycon
+>>> "To bych řek', že jsou pořádně praštěný!"
+"To bych řek', že jsou pořádně praštěný!"
+```
+
+Pythonu je jedno, se kterým druhem uvozovek řetězec zadáš.
+Podstatná jsou jen písmenka uvnitř.
+Když Python řetězec vypisuje, může si vybrat jiný druh uvozovek
+než jsi použil{{a}} ty:
+
+``` pycon
+>>> "Ola"
+'Ola'
+```
+
+## Funkce a metody
+
+Už umíš řetězce „sčítat“ (`'Ahoj ' + 'Olo!'`)
+a „násobit“ (`'la' * 3`).
+Na všechny ostatní věci, které se s textem dají dělat,
+ale na klávesnici není dost symbolů.
+Proto jsou některé operace pojmenované slovně – třeba takzvané *funkce*.
+
+Chceš-li znát počet písmen ve svém jméně, zavolej funkci `len`.
+Napiš `len` (bez uvozovek), pak kulaté závorky, a do těch závorek
+své jméno jako řetězec (v uvozovkách).
+Tím funkci `len` *zavoláš* na řetězec se svým jménem:
+
+``` pycon
+>>> len('Ola')
+3
+```
+
+Existuje funkce `type`, která zjistí jestli je něco číslo nebo řetězec.
+Jak bys ji zavolal{{a}} na číslo `123` nebo řetězec `'abc'`?
+
+{% filter solution %}
+``` pycon
+>>> type(123)
+
+>>> type('abc')
+
+```
+{% endfilter %}
+
+Kromě funkcí existují *metody*, které se zapisují trochu jinak.
+
+Chceš-li vidět své jméno velkými písmeny, zavolej metodu `upper`.
+Napiš řetězec, pak tečku, jméno metody `upper` (bez uvozovek) a prázdné
+závorky:
+
+``` pycon
+>>> 'Ola'.upper()
+'OLA'
+```
+
+Řetězce mají i metodu `lower`. Zkus ji zavolat na své jméno.
+
+{% filter solution %}
+``` pycon
+>>> 'Ola'.lower()
+'ola'
+```
+{% endfilter %}
+
+Co je metoda (kterou voláš s tečkou, jako `'Ola'.upper()`) a co je funkce
+(kde vložíš informaci do závorek jako `len('Ola')`),
+to si budeš muset u každé nové funkce/metody zapamatovat nebo vyhledat.
+
+
+{# XXX: Move elsewhere? #}
+## Skládání výrazů
+
+Volání funkce nebo metody můžeš použít jako jinou hodnotu.
+
+Nech Python spočítat matematický výraz `(1 + 3) / 2`:
+
+```pycon
+>>> 8 / (1 + 3)
+2.0
+```
+
+Python napřed sečte `1 + 3` a vyjde mu 4.
+Čtverku doplní místo `1 + 3` do původního příkladu, a dostane `8 / 4`.
+To vydělí a dostane `2.0`.
+
+Neboli: `8 / (1 + 3)` = `8 / 4` = `2.0`
+
+Zkus se zamyslet, jak Python zpracuje tyto výrazy:
+
+```pycon
+>>> len('Ola') + 1
+4
+```
+
+```pycon
+>>> 'Já jsem ' + 'Ola'.upper()
+'Já jsem OLA'
+```
+
+```pycon
+>>> len('Ola' * 3)
+9
+```
+
+```pycon
+>>> len('Ola'.upper())
+3
+```
+
+{% filter solution() %}
+`len('Ola') + 1` → `3 + 1` → `4`
+
+`'Já jsem ' + 'Ola'.upper()` → `'Já jsem ' + 'OLA'` → `'Já jsem OLA'`
+
+`len('Ola' * 3)` → `len('OlaOlaOla')` → `9`
+
+`len('Ola'.upper())` → `len('OLA')` → `3`
+{% endfilter %}
+
+
+Podobné skládání je v programování velice časté.
+Většinu základních stavebních bloků se začátečník naučí za pár
+týdnů či měsíců – a pak po celou svou progrmátorskou kariéru objevuje nové způsoby,
+jak je poskládat do složitějších a složitějších konstrukcí.
+
+### Shrnutí
+
+OK, dost bylo řetězců. Co ses zatím naučil{{a}}:
+
+* **Řetězce** se používají na práci s textem.
+* **Operátory** `+` a `*` se používají na spojování a opakování řetězců.
+* **Funkce** a **metody** jako `len()` a `upper()` provádí na řetězcích
+ nějaké akce.
+* **Výrazy** se dají skládat dohromady.
+
+Čísla, řetězce a operátory a funkce jsou základy většiny programovacích jazyků.
+
+Připraven{{a}} na něco dalšího? Vsadíme se, že ano!
diff --git a/lessons/fast-track/str/info.yml b/lessons/fast-track/str/info.yml
new file mode 100644
index 00000000..2356c8de
--- /dev/null
+++ b/lessons/fast-track/str/info.yml
@@ -0,0 +1,12 @@
+title: Řetězce
+style: md
+attribution:
+- Založeno na materiálech [DjangoGirls](https://djangogirls.org/).
+- Část této kapitoly je založena na kurzu [Geek Girls Carrots](https://github.com/ggcarrots/django-carrots).
+- |
+ Původní DjangoGirls tutoriál přeložila do češtiny skupina dobrovolníků.
+ Poděkování patří hlavně: Davidovi (dakf), Kristýně Kumpánové,
+ Veronice Gabrielové, Tomáši Ehrlichovi, Aničce Jaegerové,
+ Matějovi Stuchlíkovi, Filipovi Sivákovi a Juraji M. Bezručkovi.
+- Pro PyLadies CZ upravil Petr Viktorin, 2018.
+license: cc-by-sa-40
diff --git a/lessons/fast-track/str/static/quote-comic.svg b/lessons/fast-track/str/static/quote-comic.svg
new file mode 100644
index 00000000..cd80509d
--- /dev/null
+++ b/lessons/fast-track/str/static/quote-comic.svg
@@ -0,0 +1,466 @@
+
+
+
+
diff --git a/lessons/fast-track/tuple/index.md b/lessons/fast-track/tuple/index.md
new file mode 100644
index 00000000..094325be
--- /dev/null
+++ b/lessons/fast-track/tuple/index.md
@@ -0,0 +1,100 @@
+# N-tice
+
+Už víš, že pomocí `return` lze z funkce vracet hodnotu:
+
+``` python
+def dvojnasobek(x):
+ return x * 2
+```
+
+Jak ale napsat funkci, která vrátí dvě hodnoty?
+Chci třeba napsat funkci, která spočítá podíl a zbytek po dělení.
+
+Dvě hodnoty se dají vrátit jako seznam:
+
+``` python
+def podil_a_zbytek(a, b):
+ podil = a // b
+ zbytek = a % b
+
+ return [podil, zbytek]
+
+print(podil_a_zbytek(5, 2))
+```
+
+Lepší je ale vrátit *dvojici* čísel – dvě čísla oddělená čárkou:
+
+``` python
+def podil_a_zbytek(a, b):
+ podil = a // b
+ zbytek = a % b
+
+ return podil, zbytek
+
+print(podil_a_zbytek(5, 2))
+```
+
+Tomuhle se říká dvojice – a podobně se tvoří trojice, čtveřice, pětice,
+šestice, prostě n-tice (angl. *tuple*) hodnot.
+Funguje podobně jako seznam, ale nedá se měnit – např. se do ní nedají
+přidávat další prvky pomocí `append`.
+Když mám trojici, vždycky zůstane jako trojice.
+
+Když máš n-tici, můžeš ji přiřazením *rozbalit* (angl. *unpack*)
+do několika proměnných:
+
+``` python
+podil, zbytek = podil_a_zbytek(5, 2)
+
+print(podil)
+print(zbytek)
+```
+
+N-tice mají spoustu využití, například:
+
+* Bod v prostoru má 3 souřadnice – trojice čísel!
+* Hrací karta má barvu a hodotu – dvojice čísla a řetězce, např. `(2, 'piky')`
+
+Občas je potřeba dát n-tice do seznamu, např. abys uložil{{a}}
+informace o celém balíčku hracích karet.
+V podobných případech je potřeba každou n-tici uzavřít do závorek,
+aby bylo jasné kde začíná a kde končí.
+Tady je seznam dvojic:
+
+```python
+ruka = [(2, 'piky'), (10, 'kříže'), (8, 'káry')]
+```
+
+Když takový seznam máš, můžeš ho projít v cyklu `for` s pomocí rozbalování:
+
+``` python
+for hodnota, barva in ruka:
+ print('Hraju', hodnota, 'a jsou to', barva)
+```
+
+## Zip
+
+N-tice, respektive sekvenci n-tic, vrací funkce `zip`,
+která umožňuje projít zároveň několik seznamů,
+jejichž prvky si navzájem odpovídají:
+
+``` python
+veci = ['tráva', 'slunce', 'mrkev', 'řeka']
+barvy = ['zelená', 'žluté', 'oranžová', 'modrá']
+mista = ['na zemi', 'nahoře', 'na talíři', 'za zídkou']
+
+for vec, barva, misto in zip(veci, barvy, mista):
+ print(barva, vec, 'je', misto)
+```
+
+V tomhle cyklu dostaneš napřed trojici prvních prvků ze všech tří seznamů,
+pak trojici všech druhých prvků, pak třetích, a tak dále.
+
+## Shrnutí
+
+Co ses dozvěděl{{a}} tentokrát?
+
+* Pomocí *n-tice* se dá spojit několik hodnot do jedné.
+* N-tice se dají rozbalit do několika proměnných.
+* Funkce `zip` vrací sekvenci n-tic, ve kterých jsou prvky
+ z několika seznamů.
diff --git a/lessons/fast-track/tuple/info.yml b/lessons/fast-track/tuple/info.yml
new file mode 100644
index 00000000..f3110652
--- /dev/null
+++ b/lessons/fast-track/tuple/info.yml
@@ -0,0 +1,5 @@
+title: N-tice a rozbalování
+style: md
+attribution:
+- Pro PyLadies CZ napsal Petr Viktorin, 2018.
+license: cc-by-sa-40
diff --git a/lessons/fast-track/variables/index.md b/lessons/fast-track/variables/index.md
new file mode 100644
index 00000000..a0c1f8e9
--- /dev/null
+++ b/lessons/fast-track/variables/index.md
@@ -0,0 +1,143 @@
+# Proměnné
+
+Důležitým konceptem v programování jsou *proměnné*.
+Proměnná není nic jiného než *pojmenování* něčeho,
+co budeme chtít použít později.
+Programátoři proměnné používají k ukládání dat,
+aby byl jejich kód čitelnější a nemuseli si pamatovat konkrétní hodnoty.
+
+Řekněme, že chceš řetězec se svým jménem pojmenovat jako `jmeno`.
+To se zapíše takto:
+
+``` pycon
+>>> jmeno = 'Ola'
+```
+
+Proměnná `jmeno` teď bude mít hodnotu `'Ola'`.
+
+Jak sis mohl{{a}} všimnout, tenhle příkaz nic nevrátil – Python nevypsal
+žádný výsledek.
+Jak pak zjistíš, že proměnná skutečně existuje?
+
+Zadej samotné jméno proměnné (tedy `jmeno`, bez uvozovek) a stiskni
+Enter:
+
+``` pycon
+>>> jmeno
+'Ola'
+```
+
+Zkus si nastavit i jinou proměnnou – třeba svoji oblíbenou barvu:
+
+``` pycon
+>>> barva = 'modrá'
+>>> barva
+'modrá'
+```
+
+Kdykoli můžeš do proměnné přiřadit znovu a změnit tak co se pod
+daným jménem skrývá:
+
+``` pycon
+>>> jmeno
+'Ola'
+>>> jmeno = "Soňa"
+>>> jmeno
+'Soňa'
+```
+
+Můžeš ji také předat funkci nebo použít ve výrazu.
+Python za jméno proměnné dosadí aktuální hodnotu:
+
+``` pycon
+>>> len(jmeno)
+4
+>>> jmeno * 4
+'SoňaSoňaSoňaSoňa'
+```
+
+Super, ne?
+
+Proměnná může obsahovat cokoliv, například i čísla.
+Zkus tohle:
+
+``` pycon
+>>> sirka = 4
+>>> delka = 6
+>>> sirka * delka
+24
+```
+
+Ale co když použiješ nesprávné jméno? Dokážeš odhadnout, co se stane?
+
+{% filter solution %}
+``` pycon
+>>> mesto = "Tokyo"
+>>> mmesto
+Traceback (most recent call last):
+ File "", line 1, in
+NameError: name 'mmesto' is not defined
+```
+{% endfilter %}
+
+Chyba!
+
+Python má různé typy chyb. Tato se nazývá `NameError`.
+Python ti vrátí tuto chybu, pokud se pokusíš použít proměnnou,
+která dosud nebyla nastavena.
+Často jde o překlep.
+Když tedy uvidíš `NameError` zkontroluj jestli jsi neudělal{{a}} překlep,
+když jsi proměnnou nastavovala nebo použila.
+
+
+## Jména proměnných
+Profesionální programátoři pojmenovávají proměnné anglicky,
+aby jim rozumělo co nejvíc kolegů po celém světě.
+Začátečníkům ale doporučujeme češtinu – je tak jasnější, která jména
+si můžeš zvolit {{gnd('sám', 'sama')}} (např. `barva`) a která jsou
+z Pythonu (např. `upper`).
+Nevýhoda je, že si časem budeš muset odvyknout.
+
+
+Každopádně je dobré nepoužívat diakritiku a vyhnout se velkým pímenům:
+místo Jméno použij jen `jmeno`.
+
+
+Vyzkoušej si:
+Která z těchto jmen ti Python dovolí použít jako proměnnou?
+
+* `tlacitko5`
+* `5tlacitko`
+* `tlačítko`
+* `oblibena barva`
+* `oblibena-barva`
+* `oblibenaBarva`
+
+{% filter solution %}
+
+* `tlacitko5` ano.
+* `5tlacitko` ne: jména musí začínat písmenkem.
+* `tlačítko` ano, ale diakritice (`č`, `í`) je lepší se vyhnout.
+* `oblibena barva` ne: to není jedno jméno, ale dvě!
+* `oblibena-barva` taky ne: to Python bere jako odečtení dvou proměnných
+ (`oblibena` mínus `barva`).
+* `oblibenaBarva` ano, ale velkým písmenům je lepší se vyhnout.
+{% endfilter %}
+
+Ve složitějších jménech proměnných se používá podtržítko.
+Např. `oblibena_barva` bude Python považovat za jedno slovo, název jedné
+proměnné, ale člověk vidí slova dvě.
+
+``` pycon
+>>> oblibena_barva = 'žlutá'
+>>> oblibena_barva.upper()
+'ŽLUTÁ'
+```
+
+
+## Shrnutí
+
+* **Proměnné** jsou jména pro hodnoty.
+* Přiřazením (`=`) můžeš proměnnou nastavit na jakoukoli hodnotu.
+* Proměnné pojmenováváme **malými písmenky** bez diakritiky.
+* Na oddělení slov v rámci jména můžeme použít **podtržítko**.
diff --git a/lessons/fast-track/variables/info.yml b/lessons/fast-track/variables/info.yml
new file mode 100644
index 00000000..1177b57a
--- /dev/null
+++ b/lessons/fast-track/variables/info.yml
@@ -0,0 +1,12 @@
+title: Proměnné
+style: md
+attribution:
+- Založeno na materiálech [DjangoGirls](https://djangogirls.org/).
+- Část této kapitoly je založena na kurzu [Geek Girls Carrots](https://github.com/ggcarrots/django-carrots).
+- |
+ Původní DjangoGirls tutoriál přeložila do češtiny skupina dobrovolníků.
+ Poděkování patří hlavně: Davidovi (dakf), Kristýně Kumpánové,
+ Veronice Gabrielové, Tomáši Ehrlichovi, Aničce Jaegerové,
+ Matějovi Stuchlíkovi, Filipovi Sivákovi a Juraji M. Bezručkovi.
+- Pro PyLadies CZ upravil Petr Viktorin, 2018.
+license: cc-by-sa-40
diff --git a/lessons/git/basics/index.md b/lessons/git/basics/index.md
index 1e609c3b..101cbaac 100644
--- a/lessons/git/basics/index.md
+++ b/lessons/git/basics/index.md
@@ -315,6 +315,7 @@ A pro úplnost se znovu koukni, co říká
revize.
```ansi
+␛[36m$␛[0m git status
On branch master
Changes to be committed:
(use "git restore --staged ..." to unstage)
diff --git a/lessons/git/install/index.md b/lessons/git/install/index.md
index 75e3ddf5..d5dfb685 100644
--- a/lessons/git/install/index.md
+++ b/lessons/git/install/index.md
@@ -43,6 +43,7 @@ Dál pokračuj obecným [nastavením](#config) níže.
Jdi na stránku [git-scm.org](https://git-scm.org), stáhni si
Git a nainstaluj si ho.
+Při instalaci vyber tyto volby:
Při instalaci se ujisti, že jsou vybrány tyto volby:
diff --git a/lessons/intro/async/index.md b/lessons/intro/async/index.md
index b65766c9..8c432687 100644
--- a/lessons/intro/async/index.md
+++ b/lessons/intro/async/index.md
@@ -235,6 +235,9 @@ způsoby:
úlohy „vyčerpaly“), nebo
* `loop.run_until_complete(task)` – tahle funkce skončí hned, jakmile je hotová
daná úloha, a vrátí její výsledek.
+* Od Pythonu 3.7 můžete použít jednoduché `asyncio.run(task)`, aniž byste museli
+ explicitně pracovat s určitou smyčkou událostí. Jedná se ale o API, které se
+ v budoucnu může změnit.
Nakonec je smyčku potřeba uzavřít (`loop.close()`), což např. dá použitým
knihovnám možnost korektně uzavřít zbylá síťová spojení.
@@ -347,6 +350,173 @@ async def demo_task():
print(await task) # task is finished at this point; retreive its result
+loop = asyncio.get_event_loop()
+print('Coroutine:')
+result = loop.run_until_complete(demo_coro())
+print('Task:')
+result = loop.run_until_complete(demo_task())
+loop.close()
+```
+
+Futures
+-------
+
+Jak už bylo řečeno, knihovna `asyncio` je uvnitř založená na *futures*.
+Copak to je?
+
+`Future` je objekt, který reprezentuje budoucí výsledek nějaké operace.
+Poté, co tato operace skončí, se výsledek dá zjistit pomocí metody `result()`;
+jestli je operace hotová se dá zjistit pomocí `done()`.
+`Future` je taková „krabička“ na vrácenou hodnotu – než tam něco
+tu hodnotu dá, musíme počkat; poté je hodnota stále k dispozici.
+Tohle čekání se dělá pomocí `await` (nebo `loop.run_until_complete`).
+
+```python
+import asyncio
+
+
+async def set_future(fut):
+ """Sets the value of a Future, after a delay"""
+ print('set_future: sleeping...')
+ await asyncio.sleep(1)
+ print('set_future: setting future')
+ fut.set_result(123)
+ print('set_future done.')
+
+
+async def get_future(fut):
+ """Receives the value of a Future, once it's ready"""
+ print('get_future: waiting for future...')
+ await fut
+ print('get_future: getting result')
+ result = fut.result()
+ print('get_future: done')
+ return result
+
+
+future = asyncio.Future()
+
+
+# Schedule the "set_future" task (explained later)
+asyncio.ensure_future(set_future(future))
+
+
+# Run the "get_future" coroutine until complete
+loop = asyncio.get_event_loop()
+result = loop.run_until_complete(get_future(future))
+loop.close()
+
+print('Result is', result)
+```
+
+Do `Future` se dá vložit i výjimka.
+To se využívá v případě, že úloha, která má `Future` naplnit, selže.
+Metoda `result()` potom tuto výjimku způsobí v kódu, který by výsledek
+zpracovával.
+
+Na `Future` se navíc dají navázat funkce, které se zavolají jakmile je
+výsledek k dispozici.
+Dá se tak implementovat *callback* styl programování (který možná znáte
+např. z Node.js). Pomocí *futures & callbacks* se před nástupem
+generátorů programovalo pro knihovny jako `Twisted`.
+
+Podobně jako `yield` se `await` dá použít jako výraz, jehož
+hodnota je výsledek dané `Future`.
+Funkci `get_future` z příkladu výše tak lze napsat stručněji:
+
+```python
+async def get_future(fut):
+ """Receives the value of a Future, once it's ready"""
+ return (await fut)
+```
+
+Další vlastnost `Future` je ta, že se dá „zrušit“: pomocí `Future.cancel()`
+signalizujeme úloze, která má připravit výsledek, že už ten výsledek
+nepotřebujeme.
+Po zrušení bude `result()` způsobovat `CancelledError`.
+
+
+Async funkce a Task
+-------------------
+
+Používání `Future` (nebo *callback* funkcí) je poněkud těžkopádné.
+V `asyncio` se `Future` používají hlavně proto, že je na ně jednoduché
+navázat existující knihovny.
+Aplikační kód je ale lepší psát pomocí asynchronních funkcí, tak jako
+v příkladu výše.
+
+Asynchronní funkce se dají kombinovat pomocí `await` podobně jako generátory
+pomocí `yield from`.
+Nevýhoda asynchronních funkcí spočívá v tom, že na každé zavolání takové funkce
+lze použít jen jeden `await`.
+Na rozdíl od `Future` se výsledek nikam neukládá;
+jen se po skončení jednou předá.
+
+```python
+import asyncio
+
+async def add(a, b):
+ await asyncio.sleep(1) # schedule a "sleep" and wait for it to finish
+ return a + b
+
+async def demo():
+ coroutine = add(2, 3)
+ result = await coroutine # schedule "add" and wait for it to finish
+ print('The result is:', result)
+
+loop = asyncio.get_event_loop()
+result = loop.run_until_complete(demo())
+loop.close()
+```
+
+Nevýhoda čistých *coroutines* spočívá v tom, že na každé zavolání
+takové funkce lze použít jen jeden `await`.
+Výsledek se nikam neukládá, jen se po skončení jednou předá.
+Druhý `await` pro stejné zavolání asynchronní funkce skončí s chybou.
+Zkuste si to – v kódu výše přidejte daší řádek s `await coroutine`:
+
+```python
+async def demo():
+ coroutine = asyncio.ensure_future(add(2, 3))
+ print('The result is:', (await coroutine))
+ print('The result is:', (await coroutine)) # OK!
+```
+
+Tenhle problém můžeme vyřešit tak, že asynchronní funkci „zabalíme“
+jako úlohu, *Task*.
+V Pythonu 3.7 se Task tvoří pomocí `asyncio.create_task`;
+pro kompatibilitu se staršími verzemi ale použijeme ekvivalentní
+`asyncio.ensure_future`.
+Task se chová stejně jako *coroutine* – lze použít v `await` nebo
+`run_until_complete`, ale navíc:
+
+* výsledek je k dispozici kdykoli po ukončení funkce (např. pro druhý `await`) a
+* úloha se naplánuje hned po zavolání `ensure_future`.
+
+Druhou vlastnost je lepší ukázat na příkladu:
+
+```python
+import asyncio
+
+async def print_and_wait():
+ print('Async function starting')
+ await asyncio.sleep(0.5)
+ print('Async function done')
+ return 'result'
+
+async def demo_coro():
+ coroutine = print_and_wait()
+ await asyncio.sleep(1)
+ print('Awaiting coroutine')
+ print(await coroutine) # schedule coroutine and wait for it to finish
+
+async def demo_task():
+ task = asyncio.ensure_future(print_and_wait()) # schedule the task
+ await asyncio.sleep(1)
+ print('Awaiting task')
+ print(await task) # task is finished at this point; retreive its result
+
+
loop = asyncio.get_event_loop()
print('Coroutine:')
result = loop.run_until_complete(demo_coro())
@@ -416,6 +586,61 @@ async with database.transaction_context():
```
+Komunikace
+----------
+
+Ono `io` v `asyncio` naznačuje, že je tato knihovna dělaná především na
+vstup a výstup – konkrétně na komunikaci přes síť (případně s jinými procesy).
+
+Ke komunikaci používá `asyncio` tři úrovně abstrakce: `Transport`, `Protocol`
+a `Stream`.
+V krátkosti si je tu popíšeme; detaily pak najdete v dokumentaci (je pro nás
+totiž mnohem důležitější abyste pochopili principy, než abyste uměli konkrétní
+API, které lze dohledat v dokumentaci).
+
+Transporty a protokoly jsou postaveny na konceptech knihovny `Twisted`.
+
+`Transport` zajišťuje samotné posílání bajtů mezi počítači (transportní vrstvu), kdežto
+`Protocol` implementuje nějaký aplikační protokol.
+`Transport` většinou nepíšeme sami, použijeme existující.
+V `asyncio` jsou zabudované transporty pro TCP, UDP a SSL.
+`Protocol` je pak použit pro implementaci konkrétních protokolů jako
+`HTTP`, `FTP` a podobně.
+V dokumentaci najdete podrobnější popis včetně [příkladů][transport-proto-examples].
+
+[transport-proto-examples]: https://docs.python.org/3/library/asyncio-protocol.html#tcp-echo-server-protocol
+
+Kromě toho existuje i „Stream API“ založené na asynchronních funkcích.
+Většinou platí, že operace *otevření*, *čtení*, *flush* a *zavření* Streamu
+jsou asynchronní funkce (v dokumentaci označované jako *coroutines*), a je
+tedy nutné je použít s `await`; oproti tomu *zápis* asynchronní není – data
+se uloží do bufferu a pošlou se, až to bude možné.
+
+Typicky ale místo čistého `asyncio` použijeme existující knihovnu.
+Tady je příklad z knihovny `aiohttp`, která implementuje server a klienta
+pro HTTP:
+
+```python
+import asyncio
+import aiohttp
+
+async def main(url):
+ # Use a a session
+ session = aiohttp.ClientSession()
+ async with session:
+
+ # Get the response (acts somewhat like a file; needs to be closed)
+ async with session.get(url) as response:
+
+ # Fetch the whole text
+ html = await response.text()
+ print(html)
+
+loop = asyncio.get_event_loop()
+loop.run_until_complete(main('http://python.cz'))
+loop.close()
+```
+
A další
-------
@@ -443,7 +668,7 @@ Alternativní smyčky událostí
Jak bylo zmíněno na začátku, hlavní cíl `asyncio` je definovat společné rozhraní
pro různé asynchronní knihovny, aby bylo možné např. kombinovat knihovny pro
Tornado se smyčkou událostí v Twisted.
-Samotné `asyncio` je jen jedna z mnoha implementací tohoto rozhraní.
+Samotná knihovna `asyncio` je jen jedna z mnoha implementací tohoto rozhraní.
Zajímavá je například knihovna [uvloop], která je asi 2-4× rychlejší než `asyncio`
(ale má závislosti, které se pro součást standardní knihovny nehodí).
@@ -488,59 +713,3 @@ asyncio.ensure_future(update_time())
loop.run_forever()
```
-
-
-Komunikace
-----------
-
-Ono `io` v `asyncio` naznačuje, že je tato knihovna dělaná především na
-vstup a výstup – konkrétně na komunikaci přes síť (případně s jinými procesy).
-
-Ke komunikaci používá `asyncio` tři úrovně abstrakce: `Transport`, `Protocol`
-a `Stream`.
-V krátkosti si je tu popíšeme; detaily pak najdete v dokumentaci (je pro nás
-totiž mnohem důležitější abyste pochopili principy, než abyste uměli konkrétní
-API, které lze dohledat v dokumentaci).
-
-Transporty a protokoly jsou postaveny na konceptech knihovny `Twisted`.
-
-`Transport` zajišťuje samotné posílání bajtů mezi počítači (transportní vrstvu), kdežto
-`Protocol` implementuje nějaký aplikační protokol.
-`Transport` většinou nepíšeme sami, použijeme existující.
-V `asyncio` jsou zabudované transporty pro TCP, UDP a SSL.
-`Protocol` je pak použit pro implementaci konkrétních protokolů jako
-`HTTP`, `FTP` a podobně.
-V dokumentaci najdete podrobnější popis včetně [příkladů][transport-proto-examples].
-
-[transport-proto-examples]: https://docs.python.org/3/library/asyncio-protocol.html#tcp-echo-server-protocol
-
-Kromě toho existuje i „Stream API“ založené na asynchronních funkcích.
-Většinou platí, že operace *otevření*, *čtení*, *flush* a *zavření* Streamu
-jsou asynchronní funkce (v dokumentaci označované jako *coroutines*), a je
-tedy nutné je použít s `await`; oproti tomu *zápis* asynchronní není – data
-se uloží do bufferu a pošlou se, až to bude možné.
-
-Typicky ale místo čistého `asyncio` použijeme existující knihovnu.
-Tady je příklad z knihovny `aiohttp`, která implementuje server a klienta
-pro HTTP:
-
-```python
-import asyncio
-import aiohttp
-
-async def main(url):
- # Use a a session
- session = aiohttp.ClientSession()
- async with session:
-
- # Get the response (acts somewhat like a file; needs to be closed)
- async with session.get(url) as response:
-
- # Fetch the whole text
- html = await response.text()
- print(html)
-
-loop = asyncio.get_event_loop()
-loop.run_until_complete(main('http://python.cz'))
-loop.close()
-```
diff --git a/lessons/intro/pathlib/index.md b/lessons/intro/pathlib/index.md
new file mode 100644
index 00000000..78dfc032
--- /dev/null
+++ b/lessons/intro/pathlib/index.md
@@ -0,0 +1,621 @@
+
+# Cesty a soubory s Pathlib
+
+Základní práci se soubory – čtení z nich a psaní do nich – rozebírá
+[lekce o souborech](naucse:page?lesson=beginners/files). Pro zopakování:
+
+``` python
+# Otevření textového souboru "basnicka.txt" pro čtení
+with open('basnicka.txt', encoding='utf-8') as soubor:
+ # Přečtení obsahu
+ contents = soubor.read()
+
+# Velikost souboru
+print(len(soubor))
+```
+
+Jméno souboru, případně cesta k němu, se tradičně zadává jako řetězec.
+Jednotlivé adresáře jsou odděleny lomítkem (případně na Windows zpětným lomítkem);
+fungují tu absolutní i relativní cesty.
+
+Pro prosté otvírání známých souborů to stačí.
+Když ale potřebuješ s cestami k souborům pracovat víc,
+řetězce jsou docela nepohodlné.
+A navíc je problém pamatovat na všechny různé případy, které můžou nastat.
+
+Zkus pro příkad napsat funkce, které dostanou cestu k souboru a:
+
+* `vrat_casti` rozdělí cestu na jednotlivé adresáře (a vrátí je jako seznam),
+* `vrat_priponu` vrátí příponu souboru.
+
+Na mém Linuxovém počítači cesty vypadají jako
+`/home/janca/Documents/archiv.tar.gz`, takže bych mohl napsat něco jako:
+
+```python
+def vrat_casti(path):
+ """Vrátí seznam komponentů cesty (jednotlivých adresářů/souborů)"""
+ return path.split('/')
+
+def vrat_priponu(path):
+ """Vrátí příponu souboru"""
+ parts = path.split('.')
+ return parts[-1]
+```
+
+Pro mou cestu to funguje:
+
+```pycon
+>>> retezcova_cesta = '/home/janca/Documents/archiv.tar.gz'
+
+>>> vrat_casti(retezcova_cesta)
+['', 'home', 'janca', 'Documents', 'archiv.tar.gz']
+>>> vrat_pripona(retezcova_cesta)
+'gz'
+```
+
+Ale pro jinou cestu na jiném počítači už ne:
+
+```pycon
+>>> retezcova_cesta = 'C:\\Users\\Jana\\Programy\\superprojekt\\README'
+
+>>> vrat_casti(retezcova_cesta)
+['C:\\Users\\Jana\\Programy\\superprojekt\\README']
+>>> vrat_priponu(retezcova_cesta)
+'C:\Users\Jana\Programy\superprojekt\README'
+```
+
+> [note]
+> To, že programátoři používali na cesty řetězce a nepromýšleli všechny možné
+> podivnosti souborových systémů, je hlavní důvod proč si ještě dnes spousta
+> programů neporadí s diakritikou nebo mezerami v názvech souborů.
+
+Jde to líp? Samozřejmě!
+
+
+## Knihovna pathlib
+
+Od verze 3.4 obsahuje Python knihovnu `pathlib`, jejíž třída `Path` reprezentuje
+cestu k souboru a umožňuje s takovými cestami jednoduše a bezpečně manipulovat.
+
+```pycon
+>>> from pathlib import Path
+
+>>> # Cesta, která na Windows i Unixu funguje podobně:
+>>> cesta = Path('/home/janca/Documents/archiv.tar.gz')
+>>> cesta.parts
+('/', 'home', 'janca', 'Documents', 'archiv.tar.gz')
+>>> cesta.suffix
+'.gz'
+```
+
+Ukázka s cestou pro Windows (která by na Unixu nefungovala):
+
+> [note]
+> Pouštíš-li ukázku na Windows, můžeš místo `PureWindowsPath` použít rovnou
+> `Path`.
+
+```pycon
+>>> from pathlib import PureWindowsPath
+
+>>> win_cesta = PureWindowsPath('C:\\Users\\Jana\\Programy\\superprojekt\\README')
+>>> win_cesta.parts
+('C:\\', 'Users', 'Jana', 'Programy', 'superprojekt', 'README')
+>>> win_cesta.suffix
+''
+```
+
+Ukažme si teď něco z toho, co `pathlib` umožňuje.
+Nebude to všechno – další možnosti najdeš [na taháku] nebo v angličtině
+v [dokumentaci].
+
+[dokumentaci]: https://docs.python.org/3/library/pathlib.html
+[na taháku]: https://pyvec.github.io/cheatsheets/pathlib/pathlib-cs.pdf
+
+
+## Tvoření cest
+
+Cesty v `pathlib` se tvoří zavoláním třídy `Path`.
+Na Windows se tím vytvoří `WindowsPath`, na Unixu `PosixPath`.
+
+Obě považují dopředná lomítka za oddělovač adresářů,
+takže následující bude fungovat na všech systémech:
+
+```pycon
+>>> docs_cesta = Path('/home/janca/Documents')
+>>> docs_cesta
+PosixPath('/home/janca/Documents')
+```
+
+Už při vytvoření cesty se tato *normalizuje*, zjednoduší bez změny významu.
+Víc lomítek za sebou se spojí do jednoho, zbytečné adresáře nebo lomítka na
+konci se vynechají.
+
+```pycon
+>>> Path('/tmp//foo/./bar/')
+PosixPath('/tmp/foo/bar')
+```
+
+Když chci k takové cestě něco připojit, použiju operátor `/` (který by se měl
+používat na dělení, ale psst!):
+
+```pycon
+>>> docs_cesta / 'archiv.tar.gz'
+PosixPath('/home/janca/Documents/archiv.tar.gz')
+```
+
+Přidávat se takhle dají řetězcové cesty, nebo i další `Path`:
+
+```pycon
+>>> Path('/') / 'home/janca' / Path('archiv.tar.gz')
+PosixPath('/home/janca/archiv.tar.gz')
+```
+
+Pozor ale na to, že absolutní cesta (s lomítkem nebo jménem disku na začátku)
+znamená, že procházení začíná znovu od kořenového adresáře.
+Když k něčemu připojím absolutní cestu, předchozí cesta se zahodí.
+
+```pycon
+>>> Path('/home/janca') / '/tmp/foo'
+PosixPath('/tmp/foo')
+```
+
+Občas lomítko není pohodlné.
+V takových případech jde použít metoda `joinpath`, která má stejný efekt:
+
+```pycon
+>>> Path('/').joinpath('home', 'janca/archiv.tar.gz')
+PosixPath('/home/janca/archiv.tar.gz')
+```
+
+
+## Atributy
+
+Cesty v pathlib mají spoustu užitečných atributů – vlastností, ke kterým se
+dostaneš pomocí tečky:
+
+```pycon
+>>> # Příklady ukážeme opět na téhle cestě:
+>>> cesta = Path('/home/janca/Documents/archiv.tar.gz')
+>>> cesta
+PosixPath('/home/janca/Documents/archiv.tar.gz')
+
+>>> # jméno
+>>> cesta.name
+'archiv.tar.gz'
+
+>>> # Přípona (poslední)
+>>> cesta.suffix
+'.gz'
+
+>>> # Věchny přípony
+>>> cesta.suffixes
+['.tar', '.gz']
+
+>>> # "kořen" jména (bez poslední přípony)
+>>> cesta.stem
+'archiv.tar'
+
+>>> # "rodič" – adresář, který tuto cestu obsahuje
+>>> cesta.parent
+PosixPath('/home/janca/Documents')
+
+>>> cesta.parent.parent
+PosixPath('/home/janca')
+>>> cesta.parent.parent.parent.parent
+PosixPath('/')
+```
+
+Všechny "předky" -- rodiče, prarodiče, atd. -- nabízí atribut "parents".
+Výsledek je ale *iterátor*; aby se ukázaly jednotlivé hodnoty,
+je potřeba ho projít cyklem `for`, převést na seznam, atp.
+
+```pycon
+>>> cesta.parents
+
+
+>>> list(cesta.parents)
+[PosixPath('/home/janca/Documents'),
+ PosixPath('/home/janca'),
+ PosixPath('/home'),
+ PosixPath('/')]
+
+>>> # Je cesta absolutní?
+>>> cesta.is_absolute()
+True
+>>> Path('foo/archiv.zip').is_absolute()
+False
+
+>>> # Jaká by byla relativní vzhledem k jiné, nadřazené cestě?
+>>> relativni_cesta = cesta.relative_to('/home/janca')
+>>> relativni_cesta
+PosixPath('Documents/archiv.tar.gz')
+
+>>> # Spojením té nadřazené cesty a této relativní dostanu zpátky původní cestu
+>>> Path('/home/janca') / relativni_cesta
+PosixPath('/home/janca/Documents/archiv.tar.gz')
+
+>>> # Přepsání jména souboru (poslední části cesty)
+>>> cesta.with_name('hrad.jpeg')
+PosixPath('/home/janca/Documents/hrad.jpeg')
+
+>>> # Přepsání koncovky
+>>> cesta.with_suffix('.bz2')
+PosixPath('/home/janca/Documents/archiv.tar.bz2')
+
+>>> # Pokud existující koncovka není, `with_suffix` ji přidá
+>>> Path('myproject/README').with_suffix('.xz')
+PosixPath('myproject/README.xz')
+```
+
+
+## Zkoumání disku
+
+Všechno uvedené výše jsou čistě „textové“ operace – pracují jen se jmény.
+Soubor `archiv.zip` (ani jiné) počítači mít, aby ses dostal{{a}} k příponě
+nebo ke jménům nadřazených adresářů.
+
+> [note]
+> Dokonce si můžeš vyzkoušet, jak by to fungovalo na jiném systému – místo `Path`
+> naimportuj a použij `PureWindowsPath` nebo `PurePosixPath`, které reprezentují
+> Windowsové, resp. Unixové cesty.
+
+Zamysli se: k čemu se hodí umět pojmenovat soubor, který neexistuje?
+{% filter solution %}
+Jméno potřebuješ třeba když chceš soubor vytvořit.
+{% endfilter %}
+
+Teď se dostaneme k operacím pro které je potřeba mít přístup k souborovému
+systému.
+
+Nejdříve dvě funkce, které vrací cesty k užitečným adresářům:
+
+```pycon
+>>> # Aktuální adresář
+>>> Path.cwd()
+PosixPath('/home/janca/pyladies/barvy')
+
+>>> # Můj domovský adresář
+>>> Path.home()
+PosixPath('/home/janca')
+```
+
+A základní otázky – existuje daný soubor?
+Je to normální soubor nebo adresář?
+
+```pycon
+>>> # Existuje na té ukázkové cestě nějaký soubor?
+>>> cesta.exists()
+False
+
+>>> # Existuje můj domovský adresář?
+>>> Path.home().exists()
+True
+
+>>> # A je to vůbec adresář?
+>>> Path.home().is_dir()
+True
+
+>>> # Je to normální datový soubor?
+>>> Path.home().is_file()
+False
+```
+
+
+## Ukázka
+
+Abychom měli všichni stejné podmínky, stáhni si na další experimenty
+[archiv s testovacími soubory](static/archiv.tar.gz).
+Dej si ho do aktuálního adresáře (`Path.cwd()`), a pak ho rozbal pomocí
+`tarfile`:
+
+```pycon
+>>> import tarfile
+
+>>> cesta_k_archivu = Path("archiv.tar.gz")
+
+>>> # Co je v archivu?
+>>> tarfile.open(cesta_k_archivu, 'r|gz').getnames()
+['soubory',
+ 'soubory/hrad.jpeg',
+ 'soubory/hrad.attribution',
+ 'soubory/.gitignore',
+ 'soubory/kolecko.png',
+ 'soubory/texty',
+ 'soubory/texty/vodnik.txt',
+ 'soubory/texty/lidove',
+ 'soubory/texty/lidove/pes.txt',
+ 'soubory/texty/lidove/holka.txt',
+ 'soubory/texty/vladimir.txt',
+ 'soubory/texty/cizojazycne',
+ 'soubory/texty/cizojazycne/iroha.txt',
+ 'soubory/texty/cizojazycne/witch.txt',
+ 'soubory/hlad.txt',
+ 'soubory/hraz.attribution',
+ 'soubory/ententyky.txt',
+ 'soubory/hraz.jpeg',
+ 'soubory/README']
+
+>>> # Extrakce archivu. (Kdybys to zkoušel/a pro jiné archivy, vždy před
+>>> # rozbalením zkontroluj cesty všech souborů v archivu -- ať se rozbalením
+>>> # nepřepíše nějaký důležitý soubor!)
+>>> tarfile.open(cesta_k_archivu, 'r|gz').extractall()
+```
+
+Rozbalením archivu vznikl `./soubory/` (tedy: adresář `soubory` v aktuálním
+adresáři).
+Pojď se mu kouknout na zoubek:
+
+```pycon
+>>> zaklad = Path('./soubory')
+>>> zaklad
+PosixPath('soubory')
+
+>>> print('Je to adresář?', zaklad.is_dir())
+Je to adresář? True
+>>> print('Je to normální soubor?', zaklad.is_file())
+Je to normální soubor? False
+```
+
+Podle informací o archivu je v soubory nějaký `ententyky.txt` – podle přípony
+soubor s textem.
+
+```pycon
+>>> ententyky = zaklad / 'ententyky.txt'
+>>> print('Je to adresář?', ententyky.is_dir())
+Je to adresář? False
+>>> print('Je to normální soubor?', ententyky.is_file())
+Je to normální soubor? True
+```
+
+Objekty `Path` lze používat v naprosté většině situací, kdy jde použít cesta
+jako řetězec.
+Například pro funkci `open`:
+
+```python
+with open(ententyky, encoding='utf-8') as file:
+ print(file.read())
+```
+
+`Path` ale má `open` i jako metodu:
+
+```python
+with ententyky.open(encoding='utf-8') as file:
+ print(file.read())
+```
+
+A protože je čtení celého textového obsahu souboru docela užitečné,
+existuje i zkratka která soubor otevře, přečte a zavře najednou:
+
+```python
+print(ententyky.read_text())
+```
+
+(Větší soubory je ale lepší otevřít ve `with` a zpracovávat třeba po řádcích,
+aby se obsah nemusel do paměti počítače načíst celý najednou.)
+
+Existuje i `write_text`:
+
+```python
+cesta = Path.cwd() / 'pisnicka.txt'
+cesta.write_text('Holka modrooká\nNesedávej u potoka!')
+```
+
+{#
+## Zpátky na stromy... tedy řetězce
+
+Ve většině případů jde Path použít tam, kde se cesta dá zadat jako řetězec. Na ale vždy – pathlib existuje teprve od roku 2014, a některé Pythonní knihovny stále ještě vyžadují řetězce.
+
+Jedna z výjimek je IPython.display.Image, která umí v Notebooku vykreslit obrázek.
+
+```pycon
+>>> from IPython.display import Image
+```
+
+Image potřebuje (aspoň začátkem roku 2018) řetězcovou cestu.
+Příkaz `Image(base / 'hrad.jpeg')` mi skončil chybou typu –
+`TypeError: a bytes-like object is required, not 'PosixPath'`.
+
+V takových případech ale stačí Path převést na řetězec.
+
+```pycon
+>>> str(base / 'hrad.jpeg')
+'soubory/hrad.jpeg'
+
+>>> Image(str(base / 'hrad.jpeg'))
+
+>>> # Informace o autorství obrázku (díky, Millenium187!)
+>>> print(base.joinpath('hrad.attribution').read_text())
+
+"hrad.jpg" is (c) 2011, Wikipedia user Millenium187
+ https://commons.wikimedia.org/wiki/User:Millenium187
+Here used under the Creative Commons Attribution-Share Alike 3.0 Unported license.
+
+See: https://commons.wikimedia.org/wiki/File:Hrad_%C5%A0pilberk_II.jpg
+```
+#}
+
+## A co adresáře?
+
+I s adresáři umí `pathlib` pracovat.
+Nejzákladnější operace je získání cest k obsaženým souborům:
+
+```pycon
+>>> zaklad.iterdir()
+
+```
+
+Metoda iterdir opět vrací *iterátor* – objekt, přes který musíš „projít“
+(cyklem for, převedením na seznam ap.), abys z něj dostal{{a}} obsah.
+
+```pycon
+>>> list(zaklad.iterdir())
+[PosixPath('soubory/hrad.jpeg'),
+ PosixPath('soubory/hrad.attribution'),
+ PosixPath('soubory/.gitignore'),
+ PosixPath('soubory/kolecko.png'),
+ PosixPath('soubory/texty'),
+ PosixPath('soubory/hlad.txt'),
+ PosixPath('soubory/hraz.attribution'),
+ PosixPath('soubory/ententyky.txt'),
+ PosixPath('soubory/hraz.jpeg'),
+ PosixPath('soubory/README')]
+
+>>> for cesta in zaklad.iterdir():
+>>> print(cesta)
+soubory/hrad.jpeg
+soubory/hrad.attribution
+soubory/.gitignore
+soubory/kolecko.png
+soubory/texty
+soubory/hlad.txt
+soubory/hraz.attribution
+soubory/ententyky.txt
+soubory/hraz.jpeg
+soubory/README
+```
+
+{#
+
+## Glob Glob
+
+Zajímavější operace je ale `glob`, která vyfiltruje soubory, které odpovídají
+určité šabloně.
+
+V šabloně můžeš použít `*`, které odpovídá 0 a více písmenům
+(v rámci jména jednoho souboru):
+
+```pycon
+>>> # Soubory končící na ".txt"
+>>> list(base.glob('*.txt'))
+[PosixPath('soubory/hlad.txt'), PosixPath('soubory/ententyky.txt')]
+
+>>> # Soubory, které mají ve jméně tečku
+>>> list(base.glob('*.*'))
+[PosixPath('soubory/hrad.jpeg'),
+ PosixPath('soubory/hrad.attribution'),
+ PosixPath('soubory/.gitignore'),
+ PosixPath('soubory/kolecko.png'),
+ PosixPath('soubory/hlad.txt'),
+ PosixPath('soubory/hraz.attribution'),
+ PosixPath('soubory/ententyky.txt'),
+ PosixPath('soubory/hraz.jpeg')]
+```
+
+… nebo ?, což odpovídá jednomu písmenu:
+
+```pycon
+>>> # Slovo na čtyři, první je `h` a třetí `a`
+>>> list(base.glob('h?a?.*'))
+[PosixPath('soubory/hrad.jpeg'),
+ PosixPath('soubory/hrad.attribution'),
+ PosixPath('soubory/hlad.txt'),
+ PosixPath('soubory/hraz.attribution'),
+ PosixPath('soubory/hraz.jpeg')]
+```
+
+Případně jde použít výčet písmen v hranatých závorkách, viz modul fnmatch.
+
+```pycon
+>>> list(base.glob('h?a[zd].????'))
+[PosixPath('soubory/hrad.jpeg'), PosixPath('soubory/hraz.jpeg')]
+>>> list(base.glob('[!hv]*'))
+[PosixPath('soubory/.gitignore'),
+ PosixPath('soubory/kolecko.png'),
+ PosixPath('soubory/texty'),
+ PosixPath('soubory/ententyky.txt'),
+ PosixPath('soubory/README')]
+```
+
+Poslední speciální kombinace je `**`.
+Dvě hvězdičky odpovídají základnímu adresáři a všem jeho podadresářům,
+pod-podadresářům, pod-pod-podadresářům atd.
+
+```pycon
+>>> list(base.glob('**'))
+[PosixPath('soubory'),
+ PosixPath('soubory/texty'),
+ PosixPath('soubory/texty/lidove'),
+ PosixPath('soubory/texty/cizojazycne')]
+```
+
+S pomocí ** se často hledají soubory s danou příponou:
+
+```pycon
+>>> list(base.glob('**/*.txt'))
+[PosixPath('soubory/hlad.txt'),
+ PosixPath('soubory/ententyky.txt'),
+ PosixPath('soubory/texty/vodnik.txt'),
+ PosixPath('soubory/texty/vladimir.txt'),
+ PosixPath('soubory/texty/lidove/pes.txt'),
+ PosixPath('soubory/texty/lidove/holka.txt'),
+ PosixPath('soubory/texty/cizojazycne/iroha.txt'),
+ PosixPath('soubory/texty/cizojazycne/witch.txt')]
+```
+
+#}
+
+
+## Strom adresářů – rekurze
+
+Adresáře, podadresáře a soubory v nich tvoří strukturu, na kterou se často
+používají rekurzivní funkce.
+
+Tady je funkce `vypis_soubory`, která ypíše všechny soubory v daném adresáři.
+Před každé jméno dá odrážku `-`, aby to líp vypadalo:
+
+```python
+from pathlib import Path
+
+def vypis_soubory(odrazka, adresar):
+ """Vypíše odrážkový seznam jmen souborů v daném adresáři"""
+ for soubor in adresar.iterdir():
+ print(odrazka, soubor.name)
+
+vypis_soubory('-', Path.cwd())
+```
+
+Odrážka se dá zadat:
+
+```python
+vypis_soubory('*', Path.cwd())
+vypis_soubory(' *', Path.cwd())
+```
+
+Tahle funkce se dá změnit, aby vypsala i obsahy *podadresářů*.
+Jak?
+Poté, co vypíše jméno nějakého podadresáře, zavolá funkci která vypíše
+obsah toho podadresáře.
+Takovou funkci ale už máš napsanou – stačí trochu změnit odrážku, aby bylo
+poznat co je podadresář.
+
+```python
+from pathlib import Path
+
+def vypis_soubory(odrazka, adresar):
+ """Vypíše odrážkový seznam jmen souborů v daném adresáři i podadresářích"""
+ for soubor in adresar.iterdir():
+ print(odrazka, soubor.name)
+ if soubor.is_dir():
+ vypis_soubory(' ' + odrazka, soubor)
+
+vypis_soubory('-', Path.cwd())
+```
+
+Podobně lze například spočítat soubory v nějakém adresáři (i všech
+podadresářích).
+
+```python
+from pathlib import Path
+
+def spocitej_normalni_soubory(adresar):
+ """Vrátí počet normálních souborů v daném adresáři i všech podadresářích"""
+ pocet = 0
+ for soubor in adresar.iterdir():
+ if soubor.is_dir():
+ pocet = pocet + spocitej_normalni_soubory(soubor)
+ elif soubor.is_file():
+ pocet = pocet + 1
+ return pocet
+
+print(spocitej_normalni_soubory(Path.cwd()))
+```
diff --git a/lessons/intro/pathlib/info.yml b/lessons/intro/pathlib/info.yml
new file mode 100644
index 00000000..c47a9350
--- /dev/null
+++ b/lessons/intro/pathlib/info.yml
@@ -0,0 +1,5 @@
+title: Adresáře, soubory a cesty
+style: md
+attribution:
+- Pro PyLadies CZ napsal Petr Viktorin, 2018-2019.
+license: cc-by-sa-40
diff --git a/lessons/intro/pathlib/static/archiv.tar.gz b/lessons/intro/pathlib/static/archiv.tar.gz
new file mode 100644
index 00000000..7df60954
Binary files /dev/null and b/lessons/intro/pathlib/static/archiv.tar.gz differ
diff --git a/lessons/intro/testing/index.md b/lessons/intro/testing/index.md
index 0cb5494f..51936a15 100644
--- a/lessons/intro/testing/index.md
+++ b/lessons/intro/testing/index.md
@@ -758,6 +758,22 @@ script:
- python setup.py test
```
+Uvedený příklad je pro Python 3.6.
+Pro Python 3.7 je třeba nastavit ještě speciální specifické volby,
+jelikož je tato verze příliš nová:
+
+```yaml
+language: python
+python:
+- '3.7'
+dist: xenial
+sudo: required
+install:
+- python setup.py install
+script:
+- python setup.py test
+```
+
Uvedený příklad je pro Python 3.6.
Pro Python 3.7 je třeba nastavit novější verzi Ubuntu:
diff --git a/lessons/intro/turtle/index.md b/lessons/intro/turtle/index.md
index 81080a84..e6ab9278 100644
--- a/lessons/intro/turtle/index.md
+++ b/lessons/intro/turtle/index.md
@@ -25,7 +25,7 @@ forward(50)
```
Ukáže se okýnko se šipkou, které nezavírej.
-Dej ho tak, abys viděla i příkazovou řádku
+Dej ho tak, abys viděl{{a}} i příkazovou řádku
i nové okýnko.
## A kde je ta želva?
@@ -211,6 +211,7 @@ Tolik kódu! Tohle musí jít nějak zjednodušit!
Jde.
Pojďme se naučit, jak v Pythonu nějakou činnost opakovat.
+## Opakování
## Jak opakovat – a neopakovat *se*
@@ -246,7 +247,7 @@ Zkus napsat ještě jeden vzorový program, který v češtině zní:
* Pro každý pozdrav z výčtu: „Ahoj“, “Hello”, “Hola”, ”Hei”, "SYN":
* Vypiš pozdrav a za ním vykřičník.
-A v Pythonu:
+V Pythonu se tento program zapíše jako:
```python
for pozdrav in 'Ahoj', 'Hello', 'Hola', 'Hei', 'SYN':
@@ -352,7 +353,7 @@ for i in range(100):
* Zopakuj 100krát:
* Vypiš `'Nikdy nebudu odsazovat o tři mezery!'`
-Python píše hlášky, jednu za druhou, a u toho si v promněnné i
+Python píše hlášky, jednu za druhou, a u toho si v proměnné i
počítá, jak už je daleko.
> [style-note]
@@ -372,10 +373,56 @@ počítá, jak už je daleko.
> print('Nikdy nebudu odsazovat o tři mezery!')
> ```
+## Čtverec II
+
+A teď znovu nakresli čtverec, tentokrát lépe – s použitím cyklu!
+
+Čtverec se kreslí následovně:
+
+* Čtyřikrát:
+ * Popojdi dopředu (a kresli přitom čáru)
+ * Otoč se o 90°
+
+
+
+{% filter solution %}
+```python
+from turtle import forward, left, exitonclick
+
+for i in range(4):
+ forward(50)
+ left(90)
+
+exitonclick()
+```
+{% endfilter %}
+
## Dlouhá přerušovaná čára
Už víš, že pomocí `penup` a `pendown` lze nakreslit přerušenou čáru:
+{% filter solution %}
+
+```python
+from turtle import forward, penup, pendown, exitonclick
+
+forward(30)
+penup() # od teď želva nekreslí
+forward(5)
+pendown() # od teď želva zase kreslí
+forward(30)
+
+exitonclick()
+```
+{% endfilter %}
+
+## Přerušovaná čára
+
+Funkce `penup` a `pendown`
+z modulu `turtle` řeknou želvě,
+aby přestala, resp. začala kreslit.
+Zkus si to:
+
```python
from turtle import forward, penup, pendown, exitonclick
@@ -406,7 +453,6 @@ exitonclick()
```
{% endfilter %}
-
Pak zkus zařídit, aby jednotlivé čárky byly postupně
větší a větší.
@@ -436,30 +482,6 @@ exitonclick()
{% endfilter %}
-## Čtverec II
-
-A teď znovu nakresli čtverec, tentokrát lépe – s použitím cyklu!
-
-Čtverec se kreslí následovně:
-
-* Čtyřikrát:
- * Popojdi dopředu (a kresli přitom čáru)
- * Otoč se o 90°
-
-
-
-{% filter solution %}
-```python
-from turtle import forward, left, exitonclick
-
-for i in range(4):
- forward(50)
- left(90)
-
-exitonclick()
-```
-{% endfilter %}
-
### Tři čtverce
Nakonec nakresli 3 čtverce, každý otočený o 20°.
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()
diff --git a/lessons/micropython/ampy/index.md b/lessons/micropython/ampy/index.md
new file mode 100644
index 00000000..ca03472d
--- /dev/null
+++ b/lessons/micropython/ampy/index.md
@@ -0,0 +1,67 @@
+## Práce se soubory
+
+Jak začneš psát trochu složitější programy,
+mohlo by se stát, že tě konzole MicroPythonu začne trochu štvát.
+Špatně se v ní opravují chyby a automatické odsazování funguje jen většinou.
+Pojďme se podívat, jak naštvání předejít.
+
+Nejdřív si do virtuálního prostředí nainstaluj program Ampy od Adafruitu.
+
+```console
+(env)$ python -m pip install adafruit-ampy
+```
+
+Doporučuji si větší kousky kódu – a určitě takové,
+ve kterých je nějaký cyklus, podmínka či funkce –
+psát v textovém editoru a do modulu pak posílat celý soubor.
+
+Zkus si to. Do souboru `blikajici_led.py` dej následující kód:
+
+```python
+from machine import Pin
+from time import sleep
+pin_diody = Pin(14, Pin.OUT)
+while True:
+ pin_diody.value(0)
+ sleep(1/2)
+ pin_diody.value(1)
+ sleep(1/2)
+```
+
+Potom zavři konzoli (`picocom`, PuTTY nebo `screen`).
+
+Ke spuštění budeš potřebovat znát port:
+
+* Linux: port používáš v příkazu `picocom`, např. `/dev/ttyUSB0`
+* Windows: port používáš v PuTTY, např. `COM13`
+* macOS: port používáš v příkazu `screen`, např. `/dev/tty.usbmodem*`
+
+`ampy` spusť následujícím příkazem, jen za `PORT` doplň svůj port:
+
+```console
+(venv)$ ampy -p PORT run blikajici_led.py
+```
+
+Program by měl blikat diodou.
+Využívá k tomu funkci `time.sleep()`, která počká daný počet vteřin –
+tedy `time.sleep(1/2)` zastaví program na půl sekundy.
+
+Podobně je možné na destičku soubory i nahrávat, jen je potřeba místo
+`run` použít `put`.
+
+```console
+(venv)$ ampy -p PORT put blikajici_led.py
+```
+
+Pokud navíc budeš chtít, aby se program na destičce automaticky spouštěl, musí
+se soubor s programem na destičce jmenovat `main.py`. `ampy` umí soubor při
+kopírování i přejmenovat, když mu při kopírování zadáš i druhé (nové) jméno.
+
+```console
+(venv)$ ampy -p PORT put blikajici_led.py main.py
+```
+
+Po úspěšném kopírování máš na destičce nahraný náš program ze souboru
+`blikajici_led.py` do souboru `main.py`. Teď už bude tvůj program fungovat
+i bez počítače, takže stačí destičku připojit např. k powerbance
+a dioda se rozbliká.
diff --git a/lessons/micropython/ampy/info.yml b/lessons/micropython/ampy/info.yml
new file mode 100644
index 00000000..1f94aeba
--- /dev/null
+++ b/lessons/micropython/ampy/info.yml
@@ -0,0 +1,6 @@
+title: Práce se soubory
+style: md
+attribution:
+- Pro PyLadies Brno napsal Petr Viktorin, 2016-2017.
+- Upravil Lumír Balhar, 2018.
+license: cc-by-sa-40
diff --git a/lessons/micropython/flashing/index.md b/lessons/micropython/flashing/index.md
new file mode 100644
index 00000000..1af76c20
--- /dev/null
+++ b/lessons/micropython/flashing/index.md
@@ -0,0 +1,29 @@
+## Flashování
+
+Na našich destičkách je MicroPython už nahraný, ale kdyby sis koupil{{a}}
+vlastní NodeMCU nebo chtěl{{a}} firmware aktualizovat, budeš ho potřebovat umět
+nahrát.
+
+K tomu je potřeba nástroj `esptool`, který se dá nainstalovat pomocí:
+
+```console
+(env)$ python -m pip install esptool
+```
+
+Po instalaci esptool si stáhni nejnovější stabilní firmware pro ESP8266
+z [micropython.org/download](http://micropython.org/download#esp8266) a zadej:
+
+```console
+(env)$ esptool.py --port /dev/ttyUSB0 --baud 460800 write_flash 0 esp8266-20161110-v1.8.6.bin
+```
+
+Hodnotu pro `--port` opět doplň podle svého systému – např. `/dev/tty.wchusbserial1420` na Macu, `COM3` na Windows.
+
+> [note]
+> Destiček s čipem ESP8266 se vyrábí celá řada různých typů a některé mohou
+> potřebovat odlišné nastavení při flashování.
+> Popis všech možností nastavení je k nalezení v [dokumentaci k esptool](https://github.com/espressif/esptool#usage).
+
+Je-li na desce nahraný MicroPython, tento příkaz by měl fungovat. U jiného
+firmware, (případně u poškozeného MicroPythonu), je potřeba při zapojování
+destičky do USB držet tlačítko FLASH.
diff --git a/lessons/micropython/flashing/info.yml b/lessons/micropython/flashing/info.yml
new file mode 100644
index 00000000..f03e0df4
--- /dev/null
+++ b/lessons/micropython/flashing/info.yml
@@ -0,0 +1,6 @@
+title: Flashování
+style: md
+attribution:
+- Pro PyLadies Brno napsal Petr Viktorin, 2016-2017.
+- Upravil Lumír Balhar, 2018.
+license: cc-by-sa-40
diff --git a/lessons/micropython/input/index.md b/lessons/micropython/input/index.md
new file mode 100644
index 00000000..307d5e4b
--- /dev/null
+++ b/lessons/micropython/input/index.md
@@ -0,0 +1,33 @@
+## Vstup
+
+MicroPython na malé destičce obsahuje některé
+moduly, které jinde nenajdeš. Ten hlavní se jmenuje
+`machine` a zpřístupňuje základní funkce zařízení. Zkus si:
+
+```python
+from machine import Pin
+pin = Pin(0, Pin.IN)
+print(pin.value())
+```
+
+Zmáčkni a drž tlačítko `FLASH` vedle USB konektoru.
+Přitom pusť `print(pin.value())` znovu.
+Jak se hodnota změní?
+
+Jak tomuhle kódu rozumět?
+Třída `Pin` ti umožňuje ovládat jednotlivé
+„nožičky”, kterými zařízení komunikuje s vnějším
+světem: buď na nich nastavovat napětí, nebo zkoumat
+jestli na nich nějaké napětí je.
+
+`Pin(0, Pin.IN)` vytvoří objekt třídy Pin,
+který bude načítat data z „nožičky” číslo 0.
+(`IN` znamená načítání – informace jdou *do* procesoru).
+Funkce `pin.value()` změří napětí na dané
+„nožičce” a vrátí buď 1 nebo 0 podle toho, jestli nějaké naměřila.
+
+No a „nožička” číslo 0 je připojená k tlačítku `FLASH`,
+kterým se tak dá ono napětí ovládat.
+Informace o tom, která nožička je kam připojená,
+máš na [taháku](https://pyvec.github.io/cheatsheets/micropython/nodemcu-cs.pdf) –
+můžeš si zkontrolovat, že Pin(0) u sebe má poznámku FLASH.
diff --git a/lessons/micropython/input/info.yml b/lessons/micropython/input/info.yml
new file mode 100644
index 00000000..899c2d25
--- /dev/null
+++ b/lessons/micropython/input/info.yml
@@ -0,0 +1,6 @@
+title: Vstup
+style: md
+attribution:
+- Pro PyLadies Brno napsal Petr Viktorin, 2016-2017.
+- Upravil Lumír Balhar, 2018.
+license: cc-by-sa-40
diff --git a/lessons/micropython/install/index.md b/lessons/micropython/install/index.md
new file mode 100644
index 00000000..628a62b4
--- /dev/null
+++ b/lessons/micropython/install/index.md
@@ -0,0 +1,20 @@
+## Instalace
+
+Nejdříve propoj modul s počítačem přes USB kabel,
+jako kdybys připojoval{{a}} třeba mobil.
+
+> [note]
+> Je potřeba použít kvalitní datový kabel.
+> Nekvalitní kabely (např. spousta kabelů k
+> nabíječkám) jsou často nepoužitelné.
+
+Dál postupuj podle operačního systému na svém počítači:
+
+* [Linux]({{ subpage_url('linux') }})
+* [macOS]({{ subpage_url('macos') }})
+* [Windows]({{ subpage_url('windows') }})
+
+Kdyby něco nefungovalo, poraď se s koučem.
+Původní (anglický) návod k této části je na
+stránkách MicroPythonu.
+
diff --git a/lessons/micropython/install/info.yml b/lessons/micropython/install/info.yml
new file mode 100644
index 00000000..4184d6b9
--- /dev/null
+++ b/lessons/micropython/install/info.yml
@@ -0,0 +1,13 @@
+title: Instalace MicroPythonu
+style: md
+attribution:
+- Pro PyLadies Brno napsal Petr Viktorin, 2016-2017.
+- Upravil Lumír Balhar, 2018.
+license: cc-by-sa-40
+subpages:
+ linux:
+ subtitle: Linux
+ macos:
+ subtitle: macOS
+ windows:
+ subtitle: Windows
diff --git a/lessons/micropython/install/linux.md b/lessons/micropython/install/linux.md
new file mode 100644
index 00000000..9c1a0a23
--- /dev/null
+++ b/lessons/micropython/install/linux.md
@@ -0,0 +1,88 @@
+# Instalace pro Linux
+
+Na správně nastaveném počítači stačí zadat:
+
+```console
+$ picocom -b 115200 --flow n /dev/ttyUSB0
+```
+
+Pokud příkaz neskončí s chybou, stiskni tlačítko `RST` na modulu.
+Měly by se nakonec objevit tři zobáčky, `>>>`.
+
+Většina počítačů ale na komunikaci s malými zařízeními nastavená není.
+Skončí-li příkaz `picocom` s chybou,
+oprav ji podle následujícího návodu a zkus to znova.
+(Možná bude potřeba vyřešit víc než jednu chybu.)
+
+## Nenainstalovaný picocom
+
+Nemáš-li příkaz `picocom` nainstalovaný,
+je potřeba ho nainstalovat (např.
+`sudo dnf install picocom` nebo
+`sudo apt-get install picocom`).
+
+## Neexistující soubor
+
+Pokud `picocom` skončil s chybou
+`No such file or directory`, pravděpodobně
+je potřeba k zařízení přistupovat přes jiný soubor.
+Použij příkaz `dmesg | tail`, který vypíše něco jako:
+
+
+$ dmesg | tail
+[703169.886296] ch341 1-1.1:1.0: device disconnected
+[703176.972781] usb 1-1.1: new full-speed USB device number 45 using ehci-pci
+[703177.059448] usb 1-1.1: New USB device found, idVendor=1a86, idProduct=7523
+[703177.059454] usb 1-1.1: New USB device strings: Mfr=0, Product=2, SerialNumber=0
+[703177.059457] usb 1-1.1: Product: USB2.0-Serial
+[703177.060474] ch341 1-1.1:1.0: ch341-uart converter detected
+[703177.062781] usb 1-1.1: ch341-uart converter now attached to ttyUSB0
+
+
+
+Co znamenají ta čísla (`0` a `255`), na to už jistě přijdeš sám/sama.
+Jen při experimentování nezapomeň zavolat
+`np.write()`, tím se informace pošlou do LED pásku.
+
+Zvládneš naprogramovat semafor?
diff --git a/lessons/micropython/rgb_leds/info.yml b/lessons/micropython/rgb_leds/info.yml
new file mode 100644
index 00000000..49716d46
--- /dev/null
+++ b/lessons/micropython/rgb_leds/info.yml
@@ -0,0 +1,12 @@
+title: Barevná světýlka
+style: md
+attribution:
+- Pro PyLadies Brno napsal Petr Viktorin, 2016-2017.
+- Upravil Lumír Balhar, 2018.
+license: cc-by-sa-40
+css: |
+ .highlight { background-color: hsla( 0, 100%, 50%, 0.1); }
+ .highlight-nocolor{ background-color: hsla( 60, 100%, 50%, 0.75); }
+ .highlight-red { background-color: hsla( 0, 100%, 50%, 0.25); }
+ .highlight-green { background-color: hsla(113, 100%, 50%, 0.25); }
+ .highlight-blue { background-color: hsla(236, 100%, 50%, 0.25); }
diff --git a/lessons/micropython/servo/index.md b/lessons/micropython/servo/index.md
new file mode 100644
index 00000000..e5c9826c
--- /dev/null
+++ b/lessons/micropython/servo/index.md
@@ -0,0 +1,75 @@
+## Servomotor
+
+Čas na další součástku! Tentokrát to bude *servomotor*.
+
+Servomotor je součástka, která má v sobě zabudovaný
+ovladač, se kterým si naše zařízení může povídat
+jednoduchým „elektronickým jazykem” – *protokolem*.
+Motorku můžeš posílat impulzy a podle délky impulzu
+se servomotor natočí.
+Při krátkých impulzech se natočí víc na jednu stranu,
+při dlouhých na druhou.
+Impulzy musíš posílat neustále, jinak se servomotor
+vypne.
+
+Na rozdíl od bzučítka, kde o výšce tónu rozhodovala
+frekvence (`freq`) – kolikrát za vteřinu
+se ozve lupnutí – a LED, kde o intenzitě rozhodovala
+střída (`duty`) – poměr mezi dobou kdy
+dioda svítí a kdy nesvítí, u servomotoru rozhoduje
+tzv. *šířka pulzu*: jak dlouho se napětí udrží
+na 3,3 V, než se přepne zpátky na 0 V.
+
+
+
+V praxi to znamená, že můžeš nastavit `freq`
+na 50 Hz, a `duty` měnit cca od 35
+(úplně vlevo) přes 77 (uprostřed) po 120 (úplně vpravo).
+
+Dost ale teorie, pojďme si to vyzkoušet! Napřed musíš motorek zapojit:
+
+* hnědý drát (zem) na `G`,
+* červený drát (napájení) na `3V` a
+* oranžový drát (data) na `D4`.
+
+Nožička `D4` odpovídá `Pin(2)`, takže kód k otáčení motorku je:
+
+```python
+from machine import Pin, PWM
+
+pin_motorku = Pin(2, Pin.OUT)
+pwm = PWM(pin_motorku, freq=50, duty=77)
+pwm.duty(35)
+```
+
+Zkus motorkem otáčet nastavováním `duty` na 35 do 120.
+Kdyby se náhodou stalo, že se modul restartuje a
+konzole přestane fungovat, zkus ho odpojit a znovu
+připojit. Kdyby to nepomohlo, motorek ti dneska
+nebude fungovat. Za chvíli si řekneme proč; zatím (jsi-li na kurzu)
+se přidej do dvojice k někomu, komu to funguje.
+
+## Poznámka o napájení
+
+K tomu, aby se otočil motor, je potřeba mnohem víc
+energie, než k rozsvícení světýlka.
+Z USB z počítače té energie dostaneš docela málo,
+proto můžou být s motorkem problémy.
+
+Jak to řešit, až si přestaneš hrát a budeš chtít motorkem otáčet „doopravdy”?
+
+Servo a destičku můžeš napájet zvlášť:
+například servo z baterií a destičku dál z USB.
+V tomhle případě je důležité:
+
+* Napětí baterie musí odpovídat tomu, co zvládne tvůj servomotor
+* Všechny připojené součástky musí mít propojenou zem (`GND` na destičce,
+ hnědý drát servomotoru, `-` baterie).
+* `+` baterie naopak nesmí být připojeno k destičce.
+
+Zapojení pak bude následující:
+
+* `-` baterie na hnědý drát (zem) serva *a zároveň* na `GND` desky
+* `+` baterie na červený drát (napájení) serva
+* `D4` desky na oranžový drát (data) serva
+
diff --git a/lessons/micropython/servo/info.yml b/lessons/micropython/servo/info.yml
new file mode 100644
index 00000000..799546a6
--- /dev/null
+++ b/lessons/micropython/servo/info.yml
@@ -0,0 +1,6 @@
+title: Servomotor
+style: md
+attribution:
+- Pro PyLadies Brno napsal Petr Viktorin, 2016-2017.
+- Upravil Lumír Balhar, 2018.
+license: cc-by-sa-40
diff --git a/lessons/micropython/socket/index.md b/lessons/micropython/socket/index.md
new file mode 100644
index 00000000..855dc085
--- /dev/null
+++ b/lessons/micropython/socket/index.md
@@ -0,0 +1,33 @@
+## Komunikace
+
+Pro komunikaci po síti můžete použít nízkoúrovňovou knihovnu `socket`,
+nebo protokol pro „internet of things“ (jako MQTT), ale
+MicroPython pro ESP8266 má zabudouvanou i knihovnu pro HTTP:
+ořezanou verzi známých Requests.
+Následující kód stáhne data ze stránky
+[api.thingspeak.com/channels/1417/field/2/last.txt](http://api.thingspeak.com/channels/1417/field/2/last.txt),
+kde se objevuje poslední barva tweetnutá s hashtagem `#cheerlights`.
+
+Výslednou hodnotu lze použít jako barvu modul v LED pásku.
+
+```python
+import urequests
+
+url = 'http://api.thingspeak.com/channels/1417/field/2/last.txt'
+
+def download_color():
+ response = urequests.get(url)
+ text = response.text
+
+ if text and text[0] == '#':
+ color = text[1:7]
+
+ red = int(color[0:2], 16)
+ green = int(color[2:4], 16)
+ blue = int(color[4:6], 16)
+
+ return red, green, blue
+ return 0, 0, 0
+```
+
+Opravdové projekty používají lehčí protokoly než HTTP, například MQTT.
diff --git a/lessons/micropython/socket/info.yml b/lessons/micropython/socket/info.yml
new file mode 100644
index 00000000..7d65a113
--- /dev/null
+++ b/lessons/micropython/socket/info.yml
@@ -0,0 +1,6 @@
+title: Komunikace
+style: md
+attribution:
+- Pro PyLadies Brno napsal Petr Viktorin, 2016-2017.
+- Upravil Lumír Balhar, 2018.
+license: cc-by-sa-40
diff --git a/lessons/micropython/switch/index.md b/lessons/micropython/switch/index.md
new file mode 100644
index 00000000..8a5232a1
--- /dev/null
+++ b/lessons/micropython/switch/index.md
@@ -0,0 +1,12 @@
+## Další tlačítko
+
+Teď si vezmi tlačítko a připoj ho k modulu:
+`GND` vždycky na `G`, `VCC` vždycky na `3V` a
+`OUT` na `D1`.
+
+Tlačítko funguje tak, že `OUT` spojí buď s `VCC` (`3V`)
+nebo `GND`, podle toho, jestli je tlačítko stisknuté.
+(A navíc to taky teda svítí, ale to je teď vedlejší.)
+
+Zkus si, jestli se zvládneš MicroPythonu zeptat, jestli je tlačítko zapnuté.
+Mělo by to být podobné jako u příkladu s tlačítkem `FLASH`.
diff --git a/lessons/micropython/switch/info.yml b/lessons/micropython/switch/info.yml
new file mode 100644
index 00000000..00df727f
--- /dev/null
+++ b/lessons/micropython/switch/info.yml
@@ -0,0 +1,6 @@
+title: Další tlačítko
+style: md
+attribution:
+- Pro PyLadies Brno napsal Petr Viktorin, 2016-2017.
+- Upravil Lumír Balhar, 2018.
+license: cc-by-sa-40
diff --git a/lessons/micropython/thermometer/index.md b/lessons/micropython/thermometer/index.md
new file mode 100644
index 00000000..089262cc
--- /dev/null
+++ b/lessons/micropython/thermometer/index.md
@@ -0,0 +1,61 @@
+# Teploměr
+
+Poslední součástkou, kterou si dnes ukážeme, bude jednoduchý teploměr DS18B20.
+Tento teploměr se vyrábí v několika provedeních a je velmi populární především
+pro jednoduchost použití a velmi nízkou cenu.
+
+Stejně jako si MicroPython pomocí speciálního „jazyka” rozumí s LED páskem,
+ovládá i „jazyk” pro komunikaci s teploměrem a řadou dalších zařízení.
+Tento „jazyk“, protokol sběrnice OneWire, má navíc tu výhodu, že se na jednu
+nožičku destičky dá připojit hned několik teploměrů a číst teploty
+z každého z nich.
+
+
+## Zapojení
+
+> [warning]
+> Po zapojení drž teploměr na chvíli mezi prsty.
+> Pokud je zapojený špatně, začne se velmi rychle zahřívat.
+> V takovém případě jej okamžitě odpoj.
+
+Otoč teploměr tak, aby jeho „břicho” směřovalo směrem od tebe.
+Následně propoj nožičky teploměru s destičkou takto:
+
+* Levou nožičku propoj s `GND`
+* Prostřední nožičku propoj s `D4`
+* Pravou nožičku propoj s `3V3`
+
+# Měření
+
+Pokud je vše zapojeno správně, přistup k měření teploty.
+
+```python
+from time import sleep
+from machine import Pin
+import onewire
+from ds18x20 import DS18X20
+
+
+pin = Pin(2, Pin.IN) # D4
+ow = DS18X20(onewire.OneWire(pin))
+sensory = ow.scan()
+
+ow.convert_temp()
+sleep(1)
+teplota = ow.read_temp(sensory[0])
+print("Teplota je", teplota)
+```
+
+Tento kód nejdříve opět připraví nožičku (pin) pro komunikaci a následně na ní
+připraví komunikační protokol OneWire a teploměr DS18X20.
+Prvním krokem k teplotě je nalezení všech dostupných teploměrů na dané
+sběrnici, což nám zajistí metoda `ow.scan()`,
+která nám vrátí seznam identifikátorů nalezených teploměrů.
+
+Metoda `ow.convert_temp()` pak pošle všem teploměrům příkaz, aby změřily
+teplotu.
+Po tomhle rozkazu musíš alespoň vteřinu počkat a následně můžeš
+teplotu z čidla přečíst.
+
+Zkus teploměr na chvíli chytit mezi prsty, zahřát ho tak, a změřit teplotu
+znovu.
diff --git a/lessons/micropython/thermometer/info.yml b/lessons/micropython/thermometer/info.yml
new file mode 100644
index 00000000..309ea2db
--- /dev/null
+++ b/lessons/micropython/thermometer/info.yml
@@ -0,0 +1,6 @@
+title: Teploměr
+style: md
+attribution:
+- Pro PyLadies CZ napsal Lumír Balhar 2018.
+- Upravil Petr Viktorin, 2019.
+license: cc-by-sa-40
diff --git a/lessons/micropython/webrepl/index.md b/lessons/micropython/webrepl/index.md
new file mode 100644
index 00000000..a6c1ffda
--- /dev/null
+++ b/lessons/micropython/webrepl/index.md
@@ -0,0 +1,49 @@
+## WebREPL
+
+ESP8266 byl původně navržen i jako čip pro WiFi a i s MicroPythonem se umí připojit k síti.
+Dokonce se přes WiFi dá i ovládat.
+
+Otevři si stránku [micropython.org/webrepl](http://micropython.org/webrepl/),
+přes kterou budeš po připojení s destičkou komunikovat.
+
+Poté se buď připoj k existující WiFi síti (Eduroam fungovat nebude) nebo
+použij destičku jako samostatný *access point*:
+
+```python
+
+# Existující síť:
+
+ESSID = ...
+PASSWORD = ...
+
+import network
+wlan = network.WLAN(network.STA_IF)
+wlan.active(True)
+if not wlan.isconnected():
+ print('connecting to network...')
+ wlan.connect(ESSID, PASSWORD)
+ while not wlan.isconnected():
+ pass
+print('network config:', wlan.ifconfig())
+
+# AP:
+
+ESSID = ...
+PASSWORD = ...
+CHANNEL = 3
+
+import network
+ap_if = network.WLAN(network.AP_IF)
+ap_if.active(True)
+ap_if.config(essid=ESSID, password=PASSWORD, authmode=network.AUTH_WEP, channel=CHANNEL)
+print('network config:', ap_if.ifconfig())
+
+# Nastavení WebREPL:
+
+import webrepl_setup
+```
+
+S počítačem se připoj na stejnou síť a na stránce webrepl otevřené výše
+se připoj k IP vypsané z `ifconfig()`. Měl{{a}} bys dostat konzoli, jako přes
+USB. Pomocí WebREPL lze nejen zadávat interaktivní příkazy, ale i nahrávat
+soubory.
diff --git a/lessons/micropython/webrepl/info.yml b/lessons/micropython/webrepl/info.yml
new file mode 100644
index 00000000..095424de
--- /dev/null
+++ b/lessons/micropython/webrepl/info.yml
@@ -0,0 +1,6 @@
+title: WebREPL
+style: md
+attribution:
+- Pro PyLadies Brno napsal Petr Viktorin, 2016-2017.
+- Upravil Lumír Balhar, 2018.
+license: cc-by-sa-40
diff --git a/lessons/projects/klondike/index.md b/lessons/projects/klondike/index.md
new file mode 100644
index 00000000..f5d52b80
--- /dev/null
+++ b/lessons/projects/klondike/index.md
@@ -0,0 +1,604 @@
+# Klondike Solitaire
+
+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:
+
+```plain
+ U V W X Y Z
+ [???] [ ] [ ] [ ] [ ] [ ]
+
+ A B C D E F G
+ [3♣ ] [???] [???] [???] [???] [???] [???]
+ [5 ♥] [???] [???] [???] [???] [???]
+ [6♣ ] [???] [???] [???] [???]
+ [5♠ ] [???] [???] [???]
+ [Q ♥] [???] [???]
+ [4♠ ] [???]
+ [3 ♦]
+```
+
+## Schéma hry
+
+Hra funguje takto:
+
+* 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
+
+
+## Karta
+
+Karta bude trojice (hodnota, barva, je_licem_nahoru) – viz sraz.
+Následující funkce (v souboru [`karty.py`]) nám zjednoduší práci:
+
+```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 byly všechny karty jedno číslo nebo písmeno, se desítka
+ se vypisuje jako "X".
+
+ Aby se dalo rychle odlišit červené (♥♦) karty od černých (♣♠),
+ mají červené mezeru před symbolem a černé za ním.
+ """
+```
+
+```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í.)
+ """
+```
+
+Funkce najdeš v souboru [`karty.py`]. Projdi si je; rozumíš jim?
+
+Testy k nim jsou v [`test_karty.py`] – ty procházet nemusíš, jestli nechceš.
+
+[`karty.py`]: {{ static('karty.py') }}
+[`test_karty.py`]: {{ static('test_karty.py') }}
+
+
+## Testy a úkoly
+
+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 se přeskočí. Výpis by měl končit nějak takto:
+
+ collected N items / N deselected
+ === N deselected in 0.01 seconds ===
+
+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') }}
+
+
+## Popis balíčku
+
+Jako první věc ve hře potřebujeme rozdat *balíček* karet.
+Co je to ale takový balíček?
+Jak se dá balíček karet reprezentovat pomocí řetězců, čísel, seznamů,
+n-tic a podobně?
+
+Způsobů, jak takový balíček karet reprezentovat, je více.
+Abychom měli projekt všichni stejný (a aby k němu mohly být testy),
+je v těchto materiálech tento úkol už vyřešený.
+
+Balíček karet bude *seznam* karet – tedy seznam trojic.
+To dává smysl – karet v balíčku může být různý počet (klidně 0),
+kar se z něj dají brát nebo do něj přidávat, balíček se dá zamíchat nebo
+seřadit.
+
+Balíček bude například:
+
+```python
+balicek = [(4, 'Pi', True), (4, 'Sr', True), (4, 'Ka', False), (4, 'Kr', True)]
+prazdny_balicek = []
+```
+
+Napiš následující funkci, která balíček popíše:
+
+```python
+def popis_balicku(balicek):
+ """Vrátí popis daného balíčku karet -- tedy vrchní karty, která je vidět"""
+```
+
+* level 10: Funkce existuje
+* level 11: Funkce vrátí popis poslední karty. (Bude se hodit funkce `popis_kartu` z modulu `karty`.)
+* level 12: Funkce popíše prázdný balíček jako `[ ]` (3 mezery v hranatých závorkách).
+
+
+## Vytvoření balíčku
+
+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
+
+ Karty jsou otočené rubem nahoru (nejsou vidět).
+ """
+```
+
+* level 20: Funkce existuje
+* level 21: V balíčku je 52 karet, žádné se neopakují.
+* level 22: V balíčku jsou všechny požadované karty.
+* level 23: Balíček je zamíchaný.
+
+
+## Rozepsání balíčku
+
+Když výsledek funkce `vytvor_balicek` vypíšeš, je docela nepřehledný.
+Funkce `popis_balicku` tomu příliš nepomáhá, protože popisuje jen vrchní kartu.
+Aby se ti s balíčkem lépe pracovalo, vytvoř následující funkci:
+
+```python
+def popis_seznam_karet(karty):
+ """Vrátí popis všech karet v balíčku. Jednotlivé karty odděluje mezerami.
+ """
+```
+
+Nezapomeň využít funkci `popis_kartu`!
+
+Například:
+
+```pycon
+>>> karty = [
+ (13, 'Pi', True),
+ (12, 'Sr', True),
+ (11, 'Ka', True),
+ (10, 'Kr', False),
+ ]
+
+>>> popis_seznam_karet(karty)
+[A♠ ] [2 ♥] [3 ♦] [???]
+```
+
+* level 25: Funkce existuje
+* level 26: Funkce správně popisuje balíček
+* level 27: Funkce umí popsat i prázdný balíček
+
+
+## Rozdání sloupečků
+
+Teď zkus rozdat 7 sloupečků karet, tedy konečně první krok hry.
+
+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.
+
+{{ figure(img=static('klondike.png'), alt="Ukázka sloupečků") }}
+
+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 sedmi seznamů.
+ """
+```
+
+Například:
+
+```pycon
+>>> balicek = priprav_balicek()
+>>> sloupecky = rozdej_sloupecky(balicek)
+24
+>>> 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
+```
+
+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í
+
+Testy:
+
+* level 30: Funkce existuje
+* level 31: Funkce vrací seznam sedmi seznamů
+* level 32:
+ * V každém sloupečku je aspoň jedna karta
+ * Poslední karta je lícem nahoru
+* level 33: 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 balíček a sloupečky karet
+* 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č zjišťování, jestli hráč vyhrál, a podívej se na vypsání
+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!
+
+```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 40: Funkce existuje
+* level 41: Funkce vypisuje karty ze věch sloupečků
+* level 42: 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:
+
+```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 50: Funkce `presun_kartu` existuje
+* level 51: Funkce `presun_kartu` funguje dle zadání
+* level 60: Funkce `presun_nekolik_karet` existuje
+* level 61: Funkce `presun_nekolik_karet` funguje dle zadání
+
+
+## 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
+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:
+
+```plain
+ U V W X Y Z
+ [???] [ ] [ ] [ ] [ ] [ ]
+
+ A B C D E F G
+ [3♣ ] [???] [???] [???] [???] [???] [???]
+ [5 ♥] [???] [???] [???] [???] [???]
+ [6♣ ] [???] [???] [???] [???]
+ [5♠ ] [???] [???] [???]
+ [Q ♥] [???] [???]
+ [4♠ ] [???]
+ [3 ♦]
+```
+
+* `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 hodloty 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
+```
+
+A takhle se hra dá vypsat:
+
+```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_balicku(hra['U']),
+ popis_balicku(hra['V']),
+ popis_balicku(hra['W']),
+ popis_balicku(hra['X']),
+ popis_balicku(hra['Y']),
+ popis_balicku(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`.
+
+```
+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 nám hra aspoň trochu fungovala, vytvoř si zástupné funkce,
+které nic nekontrolují a nenechají tě vyhrát:
+
+```
+def hrac_vyhral(hra):
+ """Vrací True, pokud je hra vyhraná.
+ """
+ return False
+
+def udelej_tah(hra, jmeno_odkud, jmeno_kam):
+ 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`:
+
+ ```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
+ ```
+
+
+*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) → cíl `W`-`Z`:
+ * Přesouvá se jedna karta
+ * Je-li cíl prázdný:
+ * Musí to být eso
+ * Jinak:
+ * Přesouvaná karta musí mít stejnou barvu jako vrchní karta cíle
+ * Přesouvaná karta musí být o 1 vyšší než vrchní karta cíle
+ * 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 ♦)
+ * Hodnota přesouvané karty musí být o 1 nižší než hodnota vrchní karty sloupečku
diff --git a/lessons/projects/klondike/info.yml b/lessons/projects/klondike/info.yml
new file mode 100644
index 00000000..75c0ffae
--- /dev/null
+++ b/lessons/projects/klondike/info.yml
@@ -0,0 +1,7 @@
+title: Klondike Solitaire
+style: md
+attribution:
+ - "Pro PyLadies Brno napsal Petr Viktorin, 2019."
+ - "Obrázky karet jsou z [Board Game Pack](https://kenney.nl/assets/boardgame-pack)
+studia [kenney.nl](https://kenney.nl)."
+license: cc-by-sa-40
diff --git a/lessons/projects/klondike/static/cards.png b/lessons/projects/klondike/static/cards.png
new file mode 100644
index 00000000..832778da
Binary files /dev/null and b/lessons/projects/klondike/static/cards.png differ
diff --git a/lessons/projects/klondike/static/karty.py b/lessons/projects/klondike/static/karty.py
new file mode 100644
index 00000000..d868fb78
--- /dev/null
+++ b/lessons/projects/klondike/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 byly všechny karty jedno číslo nebo písmeno, se desítka
+ se vypisuje jako "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/projects/klondike/static/klondike.png b/lessons/projects/klondike/static/klondike.png
new file mode 100644
index 00000000..61d321e4
Binary files /dev/null and b/lessons/projects/klondike/static/klondike.png differ
diff --git a/lessons/projects/klondike/static/test_karty.py b/lessons/projects/klondike/static/test_karty.py
new file mode 100644
index 00000000..aff5a74c
--- /dev/null
+++ b/lessons/projects/klondike/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/projects/klondike/static/test_klondike.py b/lessons/projects/klondike/static/test_klondike.py
new file mode 100644
index 00000000..ac028abb
--- /dev/null
+++ b/lessons/projects/klondike/static/test_klondike.py
@@ -0,0 +1,821 @@
+import pytest
+import textwrap
+import re
+
+
+@pytest.mark.level(10)
+def test_import_popis_balicku():
+ from klondike import popis_balicku
+
+
+@pytest.mark.level(11)
+def test_popis_balicku_jedna_karta():
+ """Balíček se srdcovou dámou by se měl popsat jako tato karta"""
+ from klondike import popis_balicku
+ karta = 12, 'Sr', True
+ assert popis_balicku([karta]) == '[Q ♥]'
+
+
+@pytest.mark.level(11)
+def test_popis_balicku_moc_karet():
+ """Balíček se víc kartama by se měl popsat jako vrchní karta"""
+ from klondike import popis_balicku
+ rubem_nahoru = 1, 'Sr', False
+ karta = 12, 'Sr', True
+ balicek = [rubem_nahoru, rubem_nahoru, rubem_nahoru, karta]
+ assert popis_balicku(balicek) == '[Q ♥]'
+
+
+@pytest.mark.level(11)
+def test_popis_balicku_rubem_nahoru():
+ """Balíček s vrchní kartou rubem nahoru by se měl popsat jako [???]"""
+ from klondike import popis_balicku
+ rubem_nahoru = 1, 'Sr', False
+ karta = 12, 'Sr', True
+ balicek = [karta, karta, karta, rubem_nahoru]
+ assert popis_balicku(balicek) == '[???]'
+
+@pytest.mark.level(12)
+def test_popis_prazdneho_balicku():
+ """Prázdný balíček se popisuje pomocí [ ]"""
+ from klondike import popis_balicku
+ assert popis_balicku([]) == '[ ]'
+
+
+@pytest.mark.level(20)
+def test_import_vytvor_balicek():
+ from klondike import vytvor_balicek
+
+
+@pytest.mark.level(21)
+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(21)
+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(22)
+@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(22)
+@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(23)
+def test_zamichani():
+ """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(25)
+def test_import_popis_seznam_karet():
+ from klondike import popis_seznam_karet
+
+
+@pytest.mark.level(26)
+def test_popis_seznam_karet():
+ from klondike import popis_seznam_karet
+ karty = [
+ (13, 'Pi', True),
+ (12, 'Sr', True),
+ (11, 'Ka', True),
+ (10, 'Kr', False)
+ ]
+ assert popis_seznam_karet([]) == ''
+
+
+@pytest.mark.level(27)
+def test_popis_prazdny_seznam_karet():
+ from klondike import popis_seznam_karet
+ assert popis_seznam_karet([]) == ''
+
+
+@pytest.mark.level(30)
+def test_import_rozdej_sloupecky():
+ from klondike import rozdej_sloupecky
+
+
+@pytest.mark.level(31)
+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(31)
+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(31)
+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(31)
+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(31)
+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(32)
+@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(32)
+@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(33)
+@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(40)
+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(41)
+def test_vypis_prazdne_sloupecky(capsys):
+ from klondike import vypis_sloupecky
+ vypis_sloupecky([[], [], [], [], [], [], [], []])
+ out, err = capsys.readouterr()
+ check_text(out, "")
+
+
+@pytest.mark.level(41)
+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(41)
+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(42)
+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(42)
+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(42)
+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(42)
+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(42)
+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(50)
+def test_import_presun_kartu():
+ from klondike import presun_kartu
+
+
+@pytest.mark.level(51)
+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(51)
+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(60)
+def test_import_presun_nekolik_karet():
+ from klondike import presun_nekolik_karet
+
+
+@pytest.mark.level(61)
+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(61)
+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(61)
+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(70)
+def test_import_udelej_hru():
+ from klondike import udelej_hru
+
+@pytest.mark.level(71)
+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(71)
+@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(71)
+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(71)
+@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(71)
+def test_zamichani():
+ """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(71)
+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(80)
+def test_import_vypis_hru():
+ from klondike import vypis_hru
+
+@pytest.mark.level(81)
+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(81)
+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(81)
+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/projects/klondike/static/ui.py b/lessons/projects/klondike/static/ui.py
new file mode 100644
index 00000000..7be6ce53
--- /dev/null
+++ b/lessons/projects/klondike/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()
diff --git a/lessons/projects/webpiskvorky/index.md b/lessons/projects/webpiskvorky/index.md
new file mode 100644
index 00000000..d8ecced2
--- /dev/null
+++ b/lessons/projects/webpiskvorky/index.md
@@ -0,0 +1,241 @@
+# Webový server - rozhraní k piškvorkám
+
+Tato lekce navazuje na dřívější úkoly - 1D piškvorky. Pokud je nemáš udělané,
+můžeš použít ukázkové následující soubory.
+
+Soubor `util.py` musí obsahovat funkci `tah(pole, index, symbol)`, která se
+pokusí umístit hráčův symbol do zadaného hracího pole a vrací nový stav hracího
+pole (neúspěch je indikován vyvoláním výjimky `ValueError`). Může vypadat
+například takto:
+{% filter solution %}
+```python
+# util.py
+def tah(pole, index, symbol):
+ if index >= len(pole) or index < 0:
+ raise ValueError
+ if pole[index] != '-':
+ raise ValueError
+ if symbol not in ('x', 'o'):
+ raise ValueError
+
+ return pole[:index] + symbol + pole[index + 1:]
+
+
+def vyhodnot(pole):
+ if 'xxx' in pole:
+ return 'x'
+ if 'ooo' in pole:
+ return 'o'
+ if '-' not in pole:
+ return '!'
+ return '-'
+```
+{% endfilter %}
+
+Podobně s `ai.py`. Ten musí obsahovat funkci `tah_pocitace(pole, symbol)`, který
+se pokusí umístit zadaný symbol do hracího pole, vrací nový stav hracího pole.
+použít velmi naivní strategii:
+{% filter solution %}
+```python
+# ai.py
+from random import randrange
+from util import tah
+
+def tah_pocitace(pole, symbol):
+ delka = len(pole)
+
+ while True:
+ try:
+ index = randrange(0, delka)
+ return tah(pole, index, symbol)
+ except ValueError:
+ pass
+```
+{% endfilter %}
+
+
+# První seznámení se serverem
+
+Pro webový server dneska použijeme knihovnu `flask`. Již tradičně, nejprve si ji
+nainstalujeme:
+
+```bash
+(venv) $ python -m pip install flask
+```
+
+Začneme s jednoduchou kostrou:
+```python
+# webpiskvorky.py
+from flask import Flask
+
+app = Flask(__name__)
+
+@app.route('/')
+def hra():
+ jmeno_zvirete = 'Andulka na bidýlku'
+
+ return f'Ahoj, jmenuju se {jmeno_zvirete}.'
+```
+
+## Spuštění serveru
+
+Aplikace ve Flasku se spouští trochu jinak, než programy, které jsme si doposud
+napsali. Nejprve je nutné nastavit několik proměnných prostředí a to se dělá na
+různých počítačích jiným způsobem.
+
+V linuxu/Mac:
+```bash
+$ export FLASK_APP=webpiskvorky.py
+$ export FLASK_DEBUG=1
+```
+
+Pokud pracuješ na Windows:
+```bash
+> set FLASK_APP=webpiskvorky.py
+> set FLASK_DEBUG=1
+```
+
+Tak, tím máme nachystané prostředí a konečně můžeme server spustit:
+```bash
+(venv) $ flask run
+ * Serving Flask app "webpiskvorky.py" (lazy loading)
+ * Environment: production
+ WARNING: Do not use the development server in a production environment.
+ Use a production WSGI server instead.
+ * Debug mode: on
+ * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
+ * Restarting with stat
+ * Debugger is active!
+ * Debugger PIN: 279-218-554
+```
+
+Otevři si v prohlížeči stránku http://127.0.0.1:5000 a mělo by se ti zobrazi
+prázdná stránka pouze s textem
+```
+Ahoj, jmenuju se Andulka na bidýlku.
+```
+
+## Šablona
+
+Psaní obsahu stránky přímo v pythonu je docela nepohodlné. Proto využijeme tzv.
+šablon. To je soubor, do kterého ve kterém se dají nahradit proměnné za jejich
+hodnoty.
+
+Ve stejné složce, jako se nachází soubor `webpiskvorky.py` vytvoříme novou
+složku `templates` a v ní následující `piskvorky.html`:
+
+{% raw %}
+Soubor `templates/piskvorky.html`:
+```html
+
+
+
+
+
+
+
+ Ahoj, jmenuju se {{ jmeno }}
+
+
+```
+{% endraw %}
+
+Nyní upravíme naši funkci tak, abychom šabloně předali proměnné a výsledný obsah
+vrátili jako odpověd serveru.
+
+```python
+from flask import Flask, render_template
+
+app = Flask(__name__)
+
+@app.route('/')
+def hra():
+ jmeno_zvirete = 'Andulka na bidýlku'
+
+ return render_template('piskvorky.html', jmeno=jmeno_zvirete)
+```
+
+Pojmenovaný argument funkce `render_template` uvnitř šablony vezme jako název
+proměnné uvnitř šablony. Hodnotu proměnné v šabloně vypíšeme pomocí dvojitých
+složených závorek.
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Výsledná aplikace
+
+{% filter solution %}
+```python
+# webpiskvorky.py
+
+# Spouštění (v příkazové řádce):
+# export FLASK_APP=webpiskvorky.py
+# export FLASK_DEBUG=1
+# flask run
+
+# (na Windows "set" místo "export")
+
+from flask import Flask, render_template, request
+
+from util import tah
+from ai import tah_pocitace
+
+app = Flask(__name__)
+
+@app.route('/')
+def hra():
+ if 'pole' in request.args:
+ pole = request.args['pole']
+ else:
+ pole = '-' * 20
+ if 'cislo' in request.args:
+ cislo_policka = int(request.args['cislo'])
+ pole = tah(pole, cislo_policka, 'x')
+ pole = tah_pocitace(pole, 'o')
+
+ return render_template(
+ 'hra.html',
+ ocislovane_pole=enumerate(pole),
+ pole=pole,
+ )
+```
+
+{% raw %}
+```html
+
+
+
+ Piškvorky
+
+
+
+
Piškvorky
+
+ Reset
+
+
+```
+{% endraw %}
+
+{% endfilter %}
diff --git a/lessons/projects/webpiskvorky/info.yml b/lessons/projects/webpiskvorky/info.yml
new file mode 100644
index 00000000..4675ef60
--- /dev/null
+++ b/lessons/projects/webpiskvorky/info.yml
@@ -0,0 +1,4 @@
+title: Webový server - rozhraní k piškvorkám
+style: md
+attribution: Pro PyLadies Brno napsal Martin Pavlásek, 2019
+license: cc-by-sa-40
diff --git a/lessons/snake/drawing/index.md b/lessons/snake/drawing/index.md
index 31350daa..431a0abc 100644
--- a/lessons/snake/drawing/index.md
+++ b/lessons/snake/drawing/index.md
@@ -1,7 +1,7 @@
# Nakresli mi hada
-Většina videoher má vlastní svět – spoustu čísel, textů, seznamů a jiných
-datových objektů, které popisují všechno, co ve hře je – celý *stav* hry.
+Většina videoher má celý herní svět uložený jako spoustu čísel, textů, seznamů
+a jiných datových objektů, které popisují všechno, co ve hře je.
Tenhle stav se časem mění, ať už automaticky nebo podle akcí hráče.
A docela často – většinou zhruba šedesátkrát za vteřinu – se stav hry převede
na obrázek, který se hráčovi ukáže.
@@ -14,7 +14,7 @@ musí o hře „pamatovat“, aby mohl aktuální stav zobrazit?
Bude potřebovat například aktuální polohu všech částí hada: kde má začátek?
Kroutí se doprava nebo doleva? Jak je dlouhý?
-Naopak barvu hada se stavu ukložit nepotřebuješ – každý had v téhle hře bude
+Naopak barvu hada ve stavu uložit nepotřebuješ – každý had v téhle hře bude
stejný.
Napadne tě, jak polohu hada zapsat pomocí čísel, seznamů a dalších základních
@@ -35,7 +35,7 @@ Každý bod v rovině (třeba na obrazovce!)
je možné popsat dvěmi čísly: x-ovou a y-ovou souřadnicí.
Ta x-ová říká, jak moc vlevo je bod od nějakého počátku,
ta y-ová udává jak moc je nahoře.
-My za onen „počátek“ zvolíme roh okýnka, ve kterém se bude plazit náš had.
+My za onen „počátek“ zvolíme roh okýnka, ve kterém se bude náš had plazit.
Na rozdíl od školní geometrie se had bude plazit po čtverečkové mřížce.
Je to jako na šachovnici – když jde pěšec na D5, D značí, jak moc je to
@@ -61,14 +61,82 @@ seznam souřadnic bude seznam dvojic čísel.
Had z obrázku výše bude v Pythonu vypadat takto:
```python
-snake = [(1, 2), (2, 2), (3, 2), (3, 3), (3, 4), (3, 5), (4, 5)]
+had = [(1, 2), (2, 2), (3, 2), (3, 3), (3, 4), (3, 5), (4, 5)]
```
Tohle je reprezentace hada – to, co je z hlediska hry potřeba o konkrétním
hadovi vědět.
Počítačům (a programátorům?) to takhle stačí.
-My si to ale zkusme zobrazit barevně, ať hadovi rozumí i hráč naší budoucí hry.
+My si to ale zkusme zobrazit barevně, jako obrázek, ať hadovi rozumí
+i hráč naší budoucí hry.
+Ale předtím trochu štábní kultury.
+
+## Profi software
+
+Teď, když začínáš psát profesionální software, přijdou dvě změny oproti
+tomu, jak jsi programy psal{{a}} v rozcvičce.
+
+První změna bude angličtina. Pro jména proměnných, funkcí a podobně
+se skoro vždy používá univerzálnější jazyk než je čeština, aby se pak do
+projektu mohl zapojit kdokoli z celého světa.
+Proměnná s hadem proto ponese jméno `snake`:
+
+```python
+snake = [(1, 2), (2, 2), (3, 2), (3, 3), (3, 4), (3, 5), (4, 5)]
+```
+
+Komentáře budu pro přehlednost psát dál v češtině.
+Umíš-li anglicky dobře, přelož si do angličtiny i ty!
+
+Druhou změnou bude použití vlastní třídy.
+To v tenhle moment příliš nedává smysl: hadí hra by se dala začít psát
+jen se zabudovanými třídami (seznamy, čísly atd.), ale až se dostaneme trochu
+dál, zjistl{{a}} bys, že se třídou bude všechno jednodušší.
+V reálném projektu bys pak třídu zavedla: provedl{{a}} bys *refactoring*,
+vylepšení struktury projektu bez změny funkčnosti.
+
+Takové přepsání existujícího projektu je ovšem docela náchylné na chyby,
+zvlášť pokud programu nerozumíš na sto procent.
+Pro tenhle hadí projekt proto trošku zašvindlujeme a využijeme toho,
+že autor tohohle textu ví jak ten příběh skončí.
+Se třídou to nakonec bude jednodušší, věř mi.
+
+Třída se bude jmenovat ``GameState`` (stav hry) a bude obsahovat
+stav hry a metody pro funkčnost okolo.
+První metoda se bude jmenovat `initialize` a nastaví úvodní stav hry – tedy
+pozici hada.
+
+```python
+class GameState:
+ def initialize(self):
+ self.snake = [(1, 2), (2, 2), (3, 2), (3, 3), (3, 4), (3, 5), (4, 5)]
+
+# Vytvoření konkrétního objektu
+state = GameState()
+state.initialize()
+
+# Teď můžeš se stavem hry pracovat -- například vypsat seznam souřadnic hada
+print(state.snake)
+```
+
+Všimni si, že v rámci třídy je používá `self`, ale venku `state`.
+Mělo by to tak být i ve zbytku programu, který napíšeš.
+Podobně jsi v třídě `Kotatko` používal{{a}} `self`, ale venku `mourek`
+nebo `micka`.
+
+V editoru si otevři nový soubor, ulož ho jako `had.py` a napiš do něj
+tuhle kostru programu.
+Budeme ji dál rozvíjet.
+
+Program spusť (`cd` do adresáře s programem; `python had.py`). Funguje?
+(Je docela důležité, aby fungoval – nevidíš-li výpis seznamu,
+nečti dál a program radši oprav.)
+
+> [note]
+> Použij prosím pro třídu opravdu jméno `GameState` a atribut `snake` (a
+> později i další) pojmenuj podle materiálů.
+> Bude se ti to hodit.
## Logické a zobrazovací souřadnice
@@ -98,15 +166,34 @@ se souřadnicemi (10, 20).
## Sázení čtverečku
Na to, abychom hada vykreslili, použijeme okýnko z Pygletu.
-Tady je základní kostra programu Pyglet, které už bys měl{{a}} rozumět.
+Tady je základní kostra Pygletí aplikace, které už bys měl{{a}} rozumět:
-Udělej si nový, prázdný adresář na hadí hru, a kostru si
-zkopíruj do souboru `ui.py`.
-Budeme ji dál rozvíjet.
+```python
+import pyglet
+
+window = pyglet.window.Window()
+
+@window.event
+def on_draw():
+ window.clear()
+
+pyglet.app.run()
+```
+
+Kostru Pygletí aplikace připiš do svého programu.
+Řádek s importem patří podle konvencí úplně na začátek; zbytek připiš
+*za* svůj odsavadní kód.
```python
import pyglet
+class GameState:
+ def initialize(self):
+ self.snake = [(1, 2), (2, 2), (3, 2), (3, 3), (3, 4), (3, 5), (4, 5)]
+
+state = GameState()
+state.initialize()
+
window = pyglet.window.Window()
@window.event
@@ -116,34 +203,35 @@ def on_draw():
pyglet.app.run()
```
+
Stáhni si soubor [green.png]({{ static('green.png') }}) – zelený čtvereček –
a dej ho do adresáře, kam píšeš kód.
-Pod `import pyglet` přidej řádek, který tento obrázek načte.
+Před `window = ...` přidej řádek, který tento obrázek načte.
```python
green_image = pyglet.image.load('green.png')
```
Potom zkus dovnitř do funkce `on_draw` přidat vykreslení obrázku na souřadnice
-(40, 50), velikosti 10.
+(40, 50) s velikostí 10.
```python
green_image.blit(40, 50, width=10, height=10)
```
-Program spusť (`cd` do nového adresáře; `python ui.py`). Funguje?
-(Je docela důležité, aby fungoval – nevidíš-li zelený čtvereček,
+Program spusť (`cd` do adresáře s programem; `python had.py`). Funguje?
+(Je opět důležité, aby fungoval – nevidíš-li zelený čtvereček,
nečti dál a program radši oprav.)
Jak vidíš, čtvereček je docela malý.
Budeme radši používat čtverečky větší, řekněme 64 pixelů.
To číslo je „střelené od boku“.
-V programu ho použijeme několikrát, a možná ho později budeš chtít upravit.
-Uložíme si ho proto do *konstanty* – proměnné, kterou nebudeme měnit.
-Konstanty se tradičně pojmenovávají velkými písmeny, a píšou se hned za řádek
+V programu ho použijeme několikrát a možná ho později budeš chtít upravit.
+Uložíme si ho proto do *konstanty* (proměnné, kterou nebudeme měnit).
+Konstanty se tradičně pojmenovávají velkými písmeny a píšou se hned za řádek
`import` (i když to není technicky nutné).
Přidej tedy za `import` řádek:
@@ -158,8 +246,11 @@ TILE_SIZE = 64
width=TILE_SIZE, height=TILE_SIZE)
```
-Povedlo se? Máš čtvereček?
-Jestli ne, zkus si program celý, řádek po řádce, projít a zkontrolovat.
+Kód je teď trochu delší, ale až budeš chtít velikost čtverečku změnit,
+stačí to udělat na jednom místě.
+
+Povedlo se? Máš větší čtvereček?
+Jestli ne, zkus si program celý, řádek po řádku, projít a zkontrolovat.
Nebo ho porovnej se vzorovým řešením (což je rychlejší varianta, ale míň
se naučíš).
@@ -168,6 +259,14 @@ se naučíš).
import pyglet
TILE_SIZE = 64
+
+class GameState:
+ def initialize(self):
+ self.snake = [(1, 2), (2, 2), (3, 2), (3, 3), (3, 4), (3, 5), (4, 5)]
+
+state = GameState()
+state.initialize()
+
green_image = pyglet.image.load('green.png')
window = pyglet.window.Window()
@@ -185,33 +284,53 @@ pyglet.app.run()
## Sázení hada
-Zkus teď na začátek programu – těsně pod `import` a konstantu – přidat
-definici hada:
+Nechceme ale zobrazovat čtvereček, ale hada.
+Vlastně budeme chtít zobrazit celý stav hry – had bude jen ta nejdůležitější
+část.
+Udělej si proto ve třídě `GameState` novou metodu `draw` a vykreslení čtverečku
+(volání `green_image.blit`) zatím dej do ní.
```python
-snake = [(1, 2), (2, 2), (3, 2), (3, 3), (3, 4), (3, 5), (4, 5)]
+...
+class GameState:
+ def initialize(self):
+ self.snake = [(1, 2), (2, 2), (3, 2), (3, 3), (3, 4), (3, 5), (4, 5)]
+
+ def draw(self):
+ green_image.blit(4 * TILE_SIZE, 5 * TILE_SIZE,
+ width=TILE_SIZE, height=TILE_SIZE)
+...
+@window.event
+def on_draw():
+ window.clear()
+ state.draw()
```
-A ve funkci `draw` vykresli všechny čtverečky hada.
+Změněný program by měl dělat to samé co předtím: vyktreslit čtvereček.
+Teď se ale kreslí v metodě `draw`, která má lepší přístup k souřadnicím hada.
+Zkus ho vykreslit!
+
Vzpomeň si, že seznam dvojic můžeš „projít“ pomocí cyklu `for` a „rozbalení“
n-tice:
```python
-for x, y in snake:
- ...
+ def draw(self):
+ for x, y in self.snake:
+ ...
+ # (Sem dej kód na vykreslení čtverečku se souřadnicemi x, y)
```
-Funguje to? Vidíš v tom – aspoň zhruba – hada
-(i když je poskládaný ze čtverečků)?
+Zvládneš to?
+Ve výsledku by měl být vidět – aspoň zhruba – had poskládaný ze čtverečků.
{{ figure(
img=static('coords-blocks.svg'),
alt="Had na „šachovnici“ a ukázka programu",
) }}
-Jestli ne, nezoufej, zkontroluj si to znovu, poptej se na radu.
-Řešení využij až jako krajní možnost, jak pokračovat dál – nebo na kontrolu
-správného řešení.
+Jestli to nefunguje, nezoufej, zkontroluj si to znovu, poptej se na radu.
+Ukázkové řešení využij až jako krajní možnost, jak pokračovat dál.
+Anebo pro kontrolu!
{% filter solution %}
```python
@@ -219,7 +338,17 @@ import pyglet
TILE_SIZE = 64
-snake = [(1, 2), (2, 2), (3, 2), (3, 3), (3, 4), (3, 5), (4, 5)]
+class GameState:
+ def initialize(self):
+ self.snake = [(1, 2), (2, 2), (3, 2), (3, 3), (3, 4), (3, 5), (4, 5)]
+
+ def draw(self):
+ for x, y in self.snake:
+ green_image.blit(x * TILE_SIZE, y * TILE_SIZE,
+ width=TILE_SIZE, height=TILE_SIZE)
+
+state = GameState()
+state.initialize()
green_image = pyglet.image.load('green.png')
@@ -228,9 +357,7 @@ window = pyglet.window.Window()
@window.event
def on_draw():
window.clear()
- for x, y in snake:
- green_image.blit(x * TILE_SIZE, y * TILE_SIZE,
- width=TILE_SIZE, height=TILE_SIZE)
+ state.draw()
pyglet.app.run()
```
@@ -239,27 +366,48 @@ pyglet.app.run()
## Krmení
-
Aby bylo ve hře co dělat, budeme potřebovat pro hada krmení.
-Stáhni si do adresáře s projektem obrázek `apple.png` (ať už jednoduchý
-čtvereček nebo detailnější obrázek), a zkus vykreslit jídlo třeba
-na následující souřadnice:
+Stáhni si do adresáře s projektem obrázek
+[apple.png]({{ static('apple.png') }}) a do metody `initialize`
+přidej souřadnice pro jídlo:
```python
-food = [(2, 0), (5, 1), (1, 4)]
+ self.food = [(2, 0), (5, 1), (1, 4)]
```
+Pak zkus v metodě `draw` na tyhle souřadnice vykreslit jablíčka.
+
+Budeš na to potřebovat několik věcí:
+* načíst obrázek `apple_image` podobně jako načítáš zelený čtvereček –
+ nastavuješ proměnnou `green_image` a
+* několikrát vykreslit obrázek `apple_image` podobně jako vykresluješ
+ zelené čtverečky – voláním `green_image.blit` v cyklu
+
+
{% filter solution %}
```python
import pyglet
TILE_SIZE = 64
-snake = [(1, 2), (2, 2), (3, 2), (3, 3), (3, 4), (3, 5), (4, 5)]
-food = [(2, 0), (5, 1), (1, 4)]
+class GameState:
+ def initialize(self):
+ self.snake = [(1, 2), (2, 2), (3, 2), (3, 3), (3, 4), (3, 5), (4, 5)]
+ self.food = [(2, 0), (5, 1), (1, 4)]
+
+ def draw(self):
+ for x, y in self.snake:
+ green_image.blit(x * TILE_SIZE, y * TILE_SIZE,
+ width=TILE_SIZE, height=TILE_SIZE)
+ for x, y in self.food:
+ apple_image.blit(x * TILE_SIZE, y * TILE_SIZE,
+ width=TILE_SIZE, height=TILE_SIZE)
-red_image = pyglet.image.load('apple.png')
+state = GameState()
+state.initialize()
+
+apple_image = pyglet.image.load('apple.png')
green_image = pyglet.image.load('green.png')
window = pyglet.window.Window()
@@ -267,23 +415,18 @@ window = pyglet.window.Window()
@window.event
def on_draw():
window.clear()
- for x, y in snake:
- green_image.blit(x * TILE_SIZE, y * TILE_SIZE,
- width=TILE_SIZE, height=TILE_SIZE)
- for x, y in food:
- red_image.blit(x * TILE_SIZE, y * TILE_SIZE,
- width=TILE_SIZE, height=TILE_SIZE)
+ state.draw()
pyglet.app.run()
```
{% endfilter %}
-Používáš-li detailnější obrázek, možná si všimneš, že má trošičku „zubaté“ hrany.
-To je dáno způsobem, jakým v Pygletu obrázek vykreslujeme.
-Úplné vysvětlení by zabralo příliš času, proto ukážu jen řešení.
-Až se naučíš grafiku víc do hloubky, pochopíš co se tu děje :)
-
-Do funkce `on_draw`, hned za `clear`, dej následující dva řádky:
+Možná si všimneš, že obrázek má ve hře trošičku „zubaté“ hrany.
+To je dáno způsobem, jakým v Pygletu vykreslujeme.
+Úplné vysvětlení by se do tohoto návodu nevešlo, potřebuje trochu hlubší
+znalosti počítačové grafiky.
+Proto uvedu jen řešení.
+Do funkce `on_draw`, hned za `clear`, dej následující tři řádky:
```python
# Lepší vykreslování (pro nás zatím kouzelné zaříkadlo)
@@ -310,7 +453,8 @@ V archivuje spousta „kousků“ hada, které můžeme vykreslovat místo zele
Kousky vypadají následovně.
Všimni si pojmenování – každý kousek hada buď spojuje dvě strany obrázku,
nebo stranu obrázku s hlavou či ocasem.
-Obrázek se jmenuje odkud-kam.png.
+Podle toho, odkud kam se had na daném políčku plazí, se obrázek se jmenuje
+odkud-kam.png.
{{ figure(
img=static('snake-tiles.png'),
@@ -319,11 +463,11 @@ Obrázek se jmenuje odkud-kam.png.
> [note]
> Co jsou taková ta divná „hadí vajíčka”?
-> To je pro přímad, že by had byl jen jedno políčko dlouhý – a tedy měl hlavu
+>
+> To je pro případ, že by had byl jen jedno políčko dlouhý – a tedy měl hlavu
> i ocas na stejném políčku.
-> V naší hře se do takového stavu nedostaneme (had bude začínat s délkou 2),
-> ale může se stát, že se do něj dostaneš omylem při vývoji hry.
-> Když jsou obrázky k dispozici, lépe pak zjišťuješ, co je špatně.
+> V dodělané hře se do takového stavu nedostaneme (had bude začínat s délkou 2),
+> ale než hru dokončíme, budou tyhle obrázky užitečné.
Pojďme si teď tyhle obrázky *načíst*.
Šlo by to dělat postupně, třeba takhle:
@@ -338,76 +482,49 @@ bottom_top = pyglet.image.load('snake-tiles/bottom-top.png')
Ale obrázků je spousta, tímhle způsobem by to bylo zdlouhavé a nejspíš bys
na některý zapomněl{{a}}.
-Proto Pythonu řekneme, aby nám dal všechny soubory s koncovkou `.png` v daném
-adresáři.
-Na to se dá použít třída `Path` z modulu [`pathlib`](https://docs.python.org/3/library/pathlib.html).
-Zkus si napsat (do nového souboru, třeba `experiment.py`) tento kód
-a spustit ho.
-Dokážeš vysvětlit, co dělá?
-
-```python
-from pathlib import Path
+Proto si obrázky načteme automaticky, v cyklu, a dáme je do slovníku.
-TILES_DIRECTORY = Path('snake-tiles')
+Program bude vypadat takhle:
-for path in TILES_DIRECTORY.glob('*.png'):
- print(path)
-```
+* Začni s prázdným slovníkem.
+* Pro každý *začátek* (`bottom`, `end`, `left`, `right`, `top`):
+ * Pro každý *konec* (`bottom`, `end`, `left`, `right`, `top`, `dead`, `tongue`):
+ * Budeme načítat obrázek „začátek-konec“; tento
+ klíč si dej do proměnné
+ * Načti obrázek klíč.png
+ * Ulož obrázek do slovníku pod klíč.
-My z každého souboru potřebujeme nejlépe jméno, tedy místo
-`snake-tiles/right-head.png` jenom `right-head`.
-Na to naštěstí existuje atribut `stem` (*kořen*, t.j. jméno bez přípony).
-Místo `print(path)` použij:
+Neboli, přeloženo do Pythonu:
```python
- print(path.stem)
+snake_tiles = {}
+for start in ['bottom', 'end', 'left', 'right', 'top']:
+ for end in ['bottom', 'end', 'left', 'right', 'top', 'dead', 'tongue']:
+ key = start + '-' + end
+ image = pyglet.image.load('snake-tiles/' + key + '.png')
+ snake_tiles[key] = image
```
-Funguje? Máš vypsané všechny možné kousky hada?
-
-Teď budeme chtít načíst obrázky do *slovníku*.
-*Klíče* slovníku, podle kterých budeme vyhledávat, budou jména, která jsi
-právě vypsal{{a}}.
-*Hodnoty* pak budou pygletí obrázky, které ve hře můžeš rovnou vykreslit.
-
-Začni s prázdným slovníkem, `{}`, a v cyklu `for` do něj postupně přidávej
-záznamy.
-Pak slovník vypiš.
-
-Až to budeš mít, měl by výpis vypadat asi takhle:
+Tenhle kód dej poblíž místu, kde načítáš ostatní obrázky (`pyglet.image.load`).
+Pak celý slovník pro kontrolu vypiš: `print(snake_tiles)`.
+Výpis bude vypadat dost nepřehledně, ale třeba v něm poznáš slovník –
+*{klíč: hodnota, klíč: hodnota, ...}*:
```
{'right-tongue': , 'top-tongue': ,
'right-top': , 'left-bottom': ,
- 'tail-left': , 'bottom-tongue': ,
+ 'end-left': , 'bottom-tongue': ,
'left-top': , 'bottom-bottom': ,
...
```
-{% filter solution %}
-```python
-from pathlib import Path
-
-import pyglet
-
-TILES_DIRECTORY = Path('snake-tiles')
-
-snake_tiles = {}
-for path in TILES_DIRECTORY.glob('*.png'):
- snake_tiles[path.stem] = pyglet.image.load(path)
-
-print(snake_tiles)
-```
-{% endfilter %}
-
## Housenka
A teď zkus načtení obrázků začlenit do programu s hadem!
-Všechny importy patří nahoru, konstanty pod ně, a dál pak zbytek kódu.
-Vypisovat načtený slovník ve hře nemusíš, zato místo `green_image`
-pak ve vykreslovací funkci použij třeba `snake_tiles['tail-head']`.
+Ve vykreslovací funkci použij místo `green_image` jeden z obrázků,
+třeba `snake_tiles['end-end']`.
Místo čtverečků se teď objeví kuličky – místo hada budeš mít „housenku“.
@@ -418,34 +535,45 @@ Místo čtverečků se teď objeví kuličky – místo hada budeš mít „hous
{% filter solution %}
```python
-from pathlib import Path
-
import pyglet
TILE_SIZE = 64
-TILES_DIRECTORY = Path('snake-tiles')
-snake = [(1, 2), (2, 2), (3, 2), (3, 3), (3, 4), (3, 5), (4, 5)]
-food = [(2, 0), (5, 1), (1, 4)]
+class GameState:
+ def initialize(self):
+ self.snake = [(1, 2), (2, 2), (3, 2), (3, 3), (3, 4), (3, 5), (4, 5)]
+ self.food = [(2, 0), (5, 1), (1, 4)]
+
+ def draw(self):
+ for x, y in self.snake:
+ snake_tiles['end-end'].blit(x * TILE_SIZE, y * TILE_SIZE,
+ width=TILE_SIZE, height=TILE_SIZE)
+ for x, y in self.food:
+ apple_image.blit(x * TILE_SIZE, y * TILE_SIZE,
+ width=TILE_SIZE, height=TILE_SIZE)
+
+state = GameState()
+state.initialize()
-red_image = pyglet.image.load('apple.png')
+apple_image = pyglet.image.load('apple.png')
+green_image = pyglet.image.load('green.png')
snake_tiles = {}
-for path in TILES_DIRECTORY.glob('*.png'):
- snake_tiles[path.stem] = pyglet.image.load(path)
+for start in ['bottom', 'end', 'left', 'right', 'top']:
+ for end in ['bottom', 'end', 'left', 'right', 'top', 'dead', 'tongue']:
+ key = start + '-' + end
+ image = pyglet.image.load('snake-tiles/' + key + '.png')
+ snake_tiles[key] = image
window = pyglet.window.Window()
@window.event
def on_draw():
window.clear()
+ # Lepší vykreslování (pro nás zatím kouzelné zaříkadlo)
pyglet.gl.glEnable(pyglet.gl.GL_BLEND)
pyglet.gl.glBlendFunc(pyglet.gl.GL_SRC_ALPHA, pyglet.gl.GL_ONE_MINUS_SRC_ALPHA)
- for x, y in snake:
- snake_tiles['tail-head'].blit(
- x * TILE_SIZE, y * TILE_SIZE, width=TILE_SIZE, height=TILE_SIZE)
- for x, y in food:
- red_image.blit(
- x * TILE_SIZE, y * TILE_SIZE, width=TILE_SIZE, height=TILE_SIZE)
+
+ state.draw()
pyglet.app.run()
```
@@ -453,106 +581,58 @@ pyglet.app.run()
## Jak vybrat čtverečky?
-
Místo toho, aby byl všude stejný kousek hada,
-ale budeme chtít vybrat vždycky ten správný.
+budeme chtít vybrat vždycky ten správný.
Jak na to?
-
Podle čeho ho vybrat?
-Vytvoř soubor `smery.py` a napiš do něj:
-
-```python
-snake = [(1, 2), (2, 2), (3, 2), (3, 3), (3, 4), (3, 5), (4, 5)]
-
-for x, y in snake:
- print(x, y)
-```
-
-Tenhle kód vypisuje souřadnice:
-
-```
-1 2
-2 2
-3 2
-3 3
-3 4
-3 5
-4 5
-```
+Obrázky s kousky hada jsou pojmenovány
+odkud-kam.
+To není náhoda – ukazuje to, co potřebuješ vědět, abys mohl{{a}} ten správný
+kousek vybrat.
-Zkus vymyslet, jak by se tenhle kód dal změnit, aby vypisoval ke každé
-souřadnici *směr* k předchozímu a následujícímu políčku – tedy odkud a kam
-každý kousek hada „vede“.
-Takhle:
+Když máš hada podle následujícího obrázku, na políčko (3, 2) patří
+kousek, na kterém se had plazí zleva nahoru – tedy `snake_tiles['left-top']`
-{#
-snake = [(1, 2), (2, 2), (3, 2), (3, 3), (3, 4), (3, 5), (4, 5)]
+{{ figure(
+ img=static('tile-selection.svg'),
+ alt="Had na „šachovnici“ se souřadnicemi. Políčko (3, 2) je zvýrazněné a vedou z něj šipky doleva a nahoru, kudy had pokračuje.",
+) }}
-def direction(a, b):
- if a is None:
- return 'end'
- if b is None:
- return 'end'
- x1, y1 = a
- x2, y2 = b
- if x1 == x2 - 1:
- return 'left'
- elif x1 == x2 + 1:
- return 'right'
- elif y1 == y2 - 1:
- return 'bottom'
- elif y1 == y2 + 1:
- return 'top'
- return 'end'
-
-for a, b, c in zip([None] + snake, snake, snake[1:] + [None]):
- x, y = b
- u = direction(a, b)
- v = direction(c, b)
- print(x, y, u, v)
-#}
+Pro každé z políček hada budeš potřebovat vědět:
+* x-ovou a y-ovou souřadnici políčka a
+* odkud a kam se had plazí – směr k *předchozímu* a *následujícímu* políčku.
+Do programu se pak dají hodnoty zadat nějak takto:
+```python
+ for ... in ...: # čím bude cyklus procházet? Samotným self.snake?
+ x = ...
+ y = ...
+ before = ... # Směr k předchozímu políčku ('left' nebo 'top' nebo ...)
+ after = ... # Směr k následujícímu políčku
+
+ key = before + '-' + after # název obrázku políčka
+ snake_tiles[key].blit(x * TILE_SIZE, y * TILE_SIZE,
+ width=TILE_SIZE, height=TILE_SIZE)
```
-1 2 tail right
-2 2 left right
-3 2 left top
-3 3 bottom top
-3 4 bottom top
-3 5 bottom right
-4 5 left head
-```
+
+Ty vynechané `...` je ale potřeba doplnit!
Toto je **těžký úkol**.
-Nepředpokládám, že ho zvládneš vyřešit hned, i když všechny potřebné informace
-a nástroje k tomu znáš.
+I když všechny potřebné informace a nástroje k tomu teď teoreticky znáš,
+je potřeba je správným způsobem poskládat dohromady.
+Tohle skládání dohromady, *návrh algoritmů*, je nejsložitější programátorská
+disciplína.
+
Zkus nad tím ale přemýšlet, nech si to rozležet v hlavě třeba přes noc,
vrať se k materiálům k předchozím lekcím (hlavně k úvodu do Pythonu),
zkoušej a objevuj… A časem na to přijdeš.
-
-Až se to stane, zkus své řešení co nejvíc *zjednodušit* a pak ho zakomponovat
-do vykreslovací funkce.
-To by už nemělo být příliš složité:
-
-```python
- for ??? in ???snake???:
- ...
- x = ???
- y = ???
- odkud = ???
- kam = ???
- ...
-
- snake_tiles[odkud + '-' + kam].blit(
- x * TILE_SIZE, y * TILE_SIZE, width=TILE_SIZE, height=TILE_SIZE)
-```
-
-Soubor `smery.py` po vyřešení nemaž, bude se ti pak hodit.
-
-Odměnou za vyřešení tohoto úkolu ti bude had místo housenky.
+Odměnou za vyřešení ti bude had místo housenky.
Než na to přijdeš, zbytek programu ti neuteče.
Housenka je úplně stejně hratelná jako had, jen jinak vypadá.
-Klidně přejdi na další část – logiku hry – s housenkou.
+Klidně přejdi na [psaní logiky hry](../logic) s housenkou.
+
+Nebo se [necháš poddat](../tile-selection)?
diff --git a/lessons/snake/drawing/static/screenshot-dir.png b/lessons/snake/drawing/static/screenshot-dir.png
index 62382a32..ca4f1694 100644
Binary files a/lessons/snake/drawing/static/screenshot-dir.png and b/lessons/snake/drawing/static/screenshot-dir.png differ
diff --git a/lessons/snake/drawing/static/snake-tiles.png b/lessons/snake/drawing/static/snake-tiles.png
index 6b7b1c8d..05182e7b 100644
Binary files a/lessons/snake/drawing/static/snake-tiles.png and b/lessons/snake/drawing/static/snake-tiles.png differ
diff --git a/lessons/snake/drawing/static/snake-tiles.zip b/lessons/snake/drawing/static/snake-tiles.zip
index 7faf9e41..dbcccc23 100644
Binary files a/lessons/snake/drawing/static/snake-tiles.zip and b/lessons/snake/drawing/static/snake-tiles.zip differ
diff --git a/lessons/snake/drawing/static/snake-tiles/bottom-head.png b/lessons/snake/drawing/static/snake-tiles/bottom-end.png
similarity index 100%
rename from lessons/snake/drawing/static/snake-tiles/bottom-head.png
rename to lessons/snake/drawing/static/snake-tiles/bottom-end.png
diff --git a/lessons/snake/drawing/static/snake-tiles/tail-bottom.png b/lessons/snake/drawing/static/snake-tiles/end-bottom.png
similarity index 100%
rename from lessons/snake/drawing/static/snake-tiles/tail-bottom.png
rename to lessons/snake/drawing/static/snake-tiles/end-bottom.png
diff --git a/lessons/snake/drawing/static/snake-tiles/tail-dead.png b/lessons/snake/drawing/static/snake-tiles/end-dead.png
similarity index 100%
rename from lessons/snake/drawing/static/snake-tiles/tail-dead.png
rename to lessons/snake/drawing/static/snake-tiles/end-dead.png
diff --git a/lessons/snake/drawing/static/snake-tiles/tail-head.png b/lessons/snake/drawing/static/snake-tiles/end-end.png
similarity index 100%
rename from lessons/snake/drawing/static/snake-tiles/tail-head.png
rename to lessons/snake/drawing/static/snake-tiles/end-end.png
diff --git a/lessons/snake/drawing/static/snake-tiles/tail-left.png b/lessons/snake/drawing/static/snake-tiles/end-left.png
similarity index 100%
rename from lessons/snake/drawing/static/snake-tiles/tail-left.png
rename to lessons/snake/drawing/static/snake-tiles/end-left.png
diff --git a/lessons/snake/drawing/static/snake-tiles/tail-right.png b/lessons/snake/drawing/static/snake-tiles/end-right.png
similarity index 100%
rename from lessons/snake/drawing/static/snake-tiles/tail-right.png
rename to lessons/snake/drawing/static/snake-tiles/end-right.png
diff --git a/lessons/snake/drawing/static/snake-tiles/tail-tongue.png b/lessons/snake/drawing/static/snake-tiles/end-tongue.png
similarity index 100%
rename from lessons/snake/drawing/static/snake-tiles/tail-tongue.png
rename to lessons/snake/drawing/static/snake-tiles/end-tongue.png
diff --git a/lessons/snake/drawing/static/snake-tiles/tail-top.png b/lessons/snake/drawing/static/snake-tiles/end-top.png
similarity index 100%
rename from lessons/snake/drawing/static/snake-tiles/tail-top.png
rename to lessons/snake/drawing/static/snake-tiles/end-top.png
diff --git a/lessons/snake/drawing/static/snake-tiles/left-head.png b/lessons/snake/drawing/static/snake-tiles/left-end.png
similarity index 100%
rename from lessons/snake/drawing/static/snake-tiles/left-head.png
rename to lessons/snake/drawing/static/snake-tiles/left-end.png
diff --git a/lessons/snake/drawing/static/snake-tiles/right-head.png b/lessons/snake/drawing/static/snake-tiles/right-end.png
similarity index 100%
rename from lessons/snake/drawing/static/snake-tiles/right-head.png
rename to lessons/snake/drawing/static/snake-tiles/right-end.png
diff --git a/lessons/snake/drawing/static/snake-tiles/top-head.png b/lessons/snake/drawing/static/snake-tiles/top-end.png
similarity index 100%
rename from lessons/snake/drawing/static/snake-tiles/top-head.png
rename to lessons/snake/drawing/static/snake-tiles/top-end.png
diff --git a/lessons/snake/drawing/static/tile-selection.svg b/lessons/snake/drawing/static/tile-selection.svg
new file mode 100644
index 00000000..7685e96d
--- /dev/null
+++ b/lessons/snake/drawing/static/tile-selection.svg
@@ -0,0 +1,499 @@
+
+
+
+
diff --git a/lessons/snake/handling/index.md b/lessons/snake/handling/index.md
new file mode 100644
index 00000000..276e21a6
--- /dev/null
+++ b/lessons/snake/handling/index.md
@@ -0,0 +1,97 @@
+# Vylepšení ovládání Hada
+
+Možná si všimneš, zvlášť jestli jsi už nějakou verzi hada hrál{{a}},
+že ovládání tvé nové hry je trošku frustrující.
+A možná není úplně jednoduché přijít na to, proč.
+
+Můžou za to (hlavně) dva důvody:
+
+1. Když zmáčkneš dvě šipky rychle za sebou, v dalším „tahu“
+ hada se projeví jen ta druhá.
+2. Když se had plazí doleva a hráč zmáčkne šipku doprava,
+ had se otočí a hlavou si narazí do krku.
+
+Pojďme je vyřešit.
+
+## Fronta pokynů
+
+Když zmáčkneš dvě šipky rychle za sebou, v dalším „tahu“ hada se projeví jen
+ta druhá.
+
+Z pohledu programu to chování dává smysl – po stisknutí šipky se uloží
+její směr, a při „tahu“ hada se použije poslední uložený směr.
+S tímhle chováním je ale složité hada rychle otáčet: hráč si musí pohlídat,
+aby pro každý „tah“ hada nezmáčkl víc než jednu šipku.
+Lepší by bylo, kdyby se ukládaly *všechny* stisknuté klávesy, a had by
+v každém tahu reagoval maximálně jednu.
+Další by si „schoval“ na další tahy.
+
+Takovou „frontu“ stisků kláves lze uchovávat v seznamu.
+Přidej si na to do stavu hry seznam (v metodě `__init__`):
+
+```python
+ self.queued_directions = []
+```
+
+Tuhle frontu plň po každém stisku klávesy, metodou `append`.
+Je potřeba změnit většinu funkce `on_key_press` – místo změny
+atributu se nový směr přidá do seznamu.
+Abys nemusel{{a}} psát čtyřikrát `append`,
+můžeš uložit nový směr do pomocné proměnné:
+
+```python
+@window.event
+def on_key_press(key_code, modifier):
+ if key_code == pyglet.window.key.LEFT:
+ new_direction = -1, 0
+ if key_code == pyglet.window.key.RIGHT:
+ new_direction = 1, 0
+ if key_code == pyglet.window.key.DOWN:
+ new_direction = 0, -1
+ if key_code == pyglet.window.key.UP:
+ new_direction = 0, 1
+ state.queued_directions.append(new_direction)
+```
+
+A zpátky k logice. V metodě `move` místo
+`dir_x, dir_y = self.snake_direction` z fronty vyber první nepoužitý prvek.
+Nezapomeň ho pak z fronty smazat, ať se dostane i na další:
+
+```python
+ if self.queued_directions:
+ new_direction = self.queued_directions[0]
+ del self.queued_directions[0]
+ self.snake_direction = new_direction
+```
+
+Zkontroluj, že to funguje.
+
+### Zpátky ni krok
+
+Když hráč zmáčkne šipku opačného směru, než se had právě plazí, had se otočí a
+hlavou si narazí do krku.
+
+Z pohledu programu to opět dává smysl: plazí-li se had doleva,
+políčko napravo od hlavy je plné.
+Když tedy had začne plazit doprava, narazí na políčko s hadem a hráč prohrává.
+Z pohledu hry (a biologie!) ale narážení do krku moc smyslu nedává.
+Lepší by bylo obrácení směru úplně ignorovat.
+
+A jak poznat opačný směr?
+Když se had plazí doprava, `(1, 0)`, tak je opačný směr doleva, `(-1, 0)`.
+Když se plazí dolů, `(0, -1)`, tak naopak je nahoru, `(0, 1)`.
+Obecně, k (x, y) je opačný směr
+(-x, -y).
+
+Zatím ale pracujeme s celými n-ticemi, takže je potřeba obě
+na x a y „rozbalit“.
+Kód tedy bude vypadat takto:
+
+```python
+ old_x, old_y = self.snake_direction
+ new_x, new_y = new_direction
+ if (old_x, old_y) != (-new_x, -new_y):
+ self.snake_direction = new_direction
+```
+
+Dej ho místo puvodního `self.snake_direction = new_direction`.
diff --git a/lessons/snake/handling/info.yml b/lessons/snake/handling/info.yml
new file mode 100644
index 00000000..3a4c15e3
--- /dev/null
+++ b/lessons/snake/handling/info.yml
@@ -0,0 +1,4 @@
+title: Vylepšené ovládání
+style: md
+attribution: Pro PyLadies CZ napsal Petr Viktorin, 2019.
+license: cc-by-sa-40
diff --git a/lessons/snake/import/index.md b/lessons/snake/import/index.md
new file mode 100644
index 00000000..b4244eb1
--- /dev/null
+++ b/lessons/snake/import/index.md
@@ -0,0 +1,84 @@
+# Import a náhoda
+
+V Pythonu je spousta funkčnosti k dispozici přímo – funkce jako `print`, `len`
+nebo `int` můžeš rovnou použít.
+Ještě víc věcí je v Pythonu sice k dispozici, ale jen když si „o ně řekneš“.
+Jsou sdružené do *modulů* – souborů funkcí (a dalších věcí), které spolu nějak
+souvisí.
+
+Například když chceme pracovat s náhodnými hodnotami, můžeš využít modul
+`random`.
+Naimportuj z něj funkci `randrange`:
+
+```pycon
+>>> from random import randrange
+```
+
+Jakmile to uděláš, funkce `randrange` ti bude k dispozici.
+Můžeš ji zavolat, a dostat tak náhodné číslo:
+
+```pycon
+>>> randrange(6)
+3
+>>> randrange(6)
+1
+>>> randrange(6)
+2
+>>> randrange(6)
+4
+>>> randrange(6)
+5
+>>> randrange(6)
+3
+>>> randrange(6)
+0
+>>> randrange(6)
+3
+>>> randrange(6)
+1
+```
+
+Argument funkce `randrange` udává, kolik možných výsledků může vrátit.
+Funkce pak vrací čísla od nuly, takže `randrange(6)` může vrátit od 0, 1, 2,
+3, 4 nebo 5. Šestku už ne.
+
+
+## Náhoda a seznamy
+
+Naimportuj si ještě dvě funkce:
+
+```pycon
+>>> from random import choice, shuffle
+```
+
+První z nich, `choice`, umí vybrat náhodný prvek ze seznamu:
+
+```pycon
+>>> loterie = [3, 42, 12, 19, 30, 59]
+>>> choice(loterie)
+12
+>>> choice(loterie)
+30
+```
+
+Druhá, `shuffle`, umožní seznam náhodně zamíchat.
+Podobně jako metoda `sort`, `shuffle` nic nevrací – jen potichu změní pořadí:
+
+```pycon
+>>> loterie = [3, 42, 12, 19, 30, 59]
+>>> shuffle(loterie)
+>>> loterie
+[12, 59, 19, 42, 3, 30]
+>>> shuffle(loterie)
+>>> loterie
+[59, 3, 30, 19, 12, 42]
+```
+
+## Shrnutí
+
+Tohle byla docela krátká sekce – ale důležitá!
+
+* **Import** nám může zpřístupnit funkce z **modulů**,
+ které nejsou k dispozici přímo v Pythonu.
+* Modul `random` obsahuje funkce na výběr náhodných čísel nebo náhodných
+ prvků ze seznamu.
diff --git a/lessons/snake/import/info.yml b/lessons/snake/import/info.yml
new file mode 100644
index 00000000..7c79a401
--- /dev/null
+++ b/lessons/snake/import/info.yml
@@ -0,0 +1,4 @@
+title: Import a náhoda
+style: md
+attribution: Pro Hadí workshop napsal Petr Viktorin, 2018.
+license: cc-by-sa-40
diff --git a/lessons/snake/logic/index.md b/lessons/snake/logic/index.md
index 94951ad5..db5bcdee 100644
--- a/lessons/snake/logic/index.md
+++ b/lessons/snake/logic/index.md
@@ -1,8 +1,8 @@
# Logika hry
-Už umíš vykreslit „fotku“ hada.
-Hadí videohra ale nebude jen statický obrázek.
-Had se bude hýbat!
+Už umíš vykreslit hada ze seznamu souřadnic.
+Hadí videohra ale nebude jen „fotka“.
+Seznam se bude měnit a had se bude hýbat!
+
+
diff --git a/lessons/snake/tile-selection/index.md b/lessons/snake/tile-selection/index.md
new file mode 100644
index 00000000..4c9fbcd6
--- /dev/null
+++ b/lessons/snake/tile-selection/index.md
@@ -0,0 +1,304 @@
+# Vybírání kousků hada
+
+Místo toho, aby byl všude stejný kousek hada,
+budeme chtít vybrat vždycky ten správný.
+
+Jak na to?
+Podle čeho ho vybrat?
+
+Obrázky s kousky hada jsou pojmenovány
+odkud-kam.
+To není náhoda – ukazuje to, co potřebuješ vědět, abys mohl{{a}} ten správný
+kousek vybrat.
+
+Když máš hada podle následujícího obrázku, na políčko (3, 2) patří
+kousek, na kterém se had plazí zleva nahoru – tedy `snake_tiles['left-top']`
+
+{{ figure(
+ img=static('tile-selection.svg'),
+ alt="Had na „šachovnici“ se souřadnicemi. Políčko (3, 2) je zvýrazněné a vedou z něj šipky doleva a nahoru, kudy had pokračuje.",
+) }}
+
+Na koncích hada je ve jménech obrázků místo směru `end`.
+
+Pro každé z políček budeš potřebovat zjistit, odkud a kam na něm had leze –
+tedy směr k *předchozí* a *následující* souřadnici:
+
+
+
+
Souřadnice
+
Předchozí
+
Směr k předchozí
+
Následující
+
Směr k následující
+
+
+ {% set data = [
+ (1, 2, 'end', 'right'),
+ (2, 2, 'left', 'right'),
+ (3, 2, 'left', 'top'),
+ (3, 3, 'bottom', 'top'),
+ (3, 4, 'bottom', 'top'),
+ (3, 5, 'bottom', 'right'),
+ (4, 5, 'left', 'end'),
+ ] %}
+ {% for x, y, bef, aft in data %}
+
+
+Toto je **těžký úkol**.
+I když všechny potřebné informace a nástroje k tomu teď teoreticky znáš,
+je potřeba je správným způsobem poskládat dohromady.
+Tohle skládání dohromady, *návrh algoritmů*, je nejsložitější programátorská
+disciplína.
+
+Zkus nad tím ale přemýšlet, nech si to rozležet v hlavě třeba přes noc,
+vrať se k materiálům k předchozím lekcím (hlavně k úvodu do Pythonu),
+zkoušej a objevuj… A časem na to přijdeš.
+
+Jestli nemáš čas, koukněme se na to, jak se dá zařídit.
+
+
+## Tohle není evangelium
+
+Popisuju tady jedno možné řešení.
+Existuje spousta jiných správných způsobů, jak vybírat políčka hada.
+Možná ti dokonce jiné řešení přijde jednodušší – a možná to bude dokonce
+*tvoje* řešení!
+
+
+## Jednodušší podproblémy
+
+Složité problémy programátoři většinou vhodně rozdělí na více jednodušších
+problémů.
+Každý pak vyřeší zvlášť a pak spojí dohromady.
+
+Jaké jednodušší úkoly by se daly najít tady?
+
+1. Projít všechny souřadnice, a pro každou z nich políčko zjistit
+ předchozí i následující souřadnici.
+2. Když mám dvě souřadnice, zjistit směr od jedné ke druhé.
+
+Řešením prvního z nich v tabulce výše „vyplníš“ sloupečky se souřadnicemi.
+Řešením druhého pak z těchto informací dostaneš *směry*, části jména obrázku.
+
+Budeš potřebovat vyřešit oba dva problémy.
+Ten druhý je ale jednodušší, tak se pojďme zaměřit na něj.
+
+## Zjistit směr
+
+Potřebuješ počítači říct, jak ze souřadnic dvou políček, které jsou vedle sebe,
+zjistit směr od jednoho ke druhému.
+
+Například směr od (3, 2) k (2, 2) je *doleva*.
+Směr od (3, 2) k (3, 3) je *nahoru*.
+(Viz obrázek, nebo třetí řádek tabulky výše.)
+
+{{ figure(
+ img=static('tile-selection.svg'),
+ alt="Had na „šachovnici“ se souřadnicemi. Políčko (3, 2) je zvýrazněné a vedou z něj šipky doleva a nahoru, kudy had pokračuje.",
+) }}
+
+V Pythonu to napíšeš jako *funkci*, která bere dva argumenty (souřadnice)
+a vrátí anglické jméno směru – řetězec, který se dá použít ve jméně souboru
+s obrázkem.
+
+Až bude tahle funkce hotová, měla by se dát použít následovně:
+
+```pycon
+>>> direction((3, 2), (2, 2))
+'left'
+>>> direction((3, 3), (3, 2))
+'bottom'
+>>> direction((3, 3), 'end')
+'end'
+```
+
+Na koncích hada bude potřeba jako druhou souřadnici použít místo dvojice čísel
+něco jiného.
+Řetězec `'end'` funguje dobře, ale stejně tak by se dalo použít cokoli jiného,
+co není souřadnice: `False`, `-1`, nebo třeba `[]`.
+(Zkušený Pythonista by použil hodnotu `None`.)
+
+Jak takovou funkci napsat?
+Když si pořádně prohlédneš první tři sloupce tabulky výše, možná přijdeš na to,
+jak se od sebe liší souřadnice, které jsou *nalevo* od sebe. Nebo *nahoru*.
+
+* Jak zjistit směr mezi dvěma souřadnicemi:
+ * Když ta druhá není souřadnice:
+ * výsledek je `'end'`
+ * Když se x té první rovná x+1 druhé:
+ * výsledek je `'left'`
+ * Když se x té první rovná x-1 druhé:
+ * výsledek je `'right'`
+ * Když se y té první rovná y+1 druhé:
+ * výsledek je `'bottom'`
+ * Když se y té první rovná y-1 druhé:
+ * výsledek je `'top'`
+
+Zkušený programátor v tento moment zbystří a zeptá se: „ale co když neplatí
+ani jedna z těch podmínek“?
+Taková situace ve hře nemůže nastat (nebo ano?), ale přesto je dobré ji
+podchytit a na konec postupu přidat třeba „Jinak je výsledek `'end'`”.
+
+Vymyslet tenhle postup je složitější část řešení našeho problému.
+Zbytek je jen téměř doslovný překlad z češtiny do Pythonu:
+
+```python
+def direction(a, b):
+ if b == 'end':
+ return 'end'
+
+ # Rozložení souřadnic na čísla (x, y) – to je v češtině "samozřejmé"
+ x_a, y_a = a
+ x_b, y_b = b
+
+ # ... a logika pokračuje
+ if x_a == x_b + 1:
+ return 'left'
+ elif x_a == x_b - 1:
+ return 'right'
+ elif y_a == y_b + 1:
+ return 'bottom'
+ elif y_a == y_b - 1:
+ return 'top'
+ else:
+ return 'end'
+
+# Vyzkoušení
+print('tohle by mělo být "left":', direction((3, 2), (2, 2)))
+print('tohle by mělo být "bottom":', direction((3, 3), (3, 2)))
+print('tohle by mělo být "top":', direction((3, 2), (3, 3)))
+print('tohle by mělo být "right":', direction((1, 1), (2, 1)))
+print('tohle by mělo být "end":', direction((3, 3), 'end'))
+print('tohle by mělo být "end":', direction((3, 3), (80, 80)))
+```
+
+
+## Projít všechny souřadnice
+
+Teď se vraťme k prvnímu problému: jak projít všechny políčka hada,
+a u každého zjisti políčko předchozí a následující?
+
+Protože rozdíl mezi souřadnicemi jako (1, 2) a (2, 2) není na první pohled
+moc čitelný, kousky hada si označím písmenky.
+Budu psát A místo (1, 2); B místo (2, 2); atd.:
+
+{{ figure(
+ img=static('lettered.svg'),
+ alt="Had na „šachovnici“. Každý kousek hada má písmenko: A, B, C, ..., G",
+) }}
+
+Takového hada nakreslím následovně:
+
+* Nakreslím políčko A (k čemuž potřebuju vědět, že je to začátek a po něm je B)
+* Nakreslím políčko B (k čemuž potřebuju vědět, že před ním je A a po něm B)
+* Nakreslím políčko C (k čemuž potřebuju vědět, že před ním je B a po něm D)
+* … a tak dál:
+
+
+
+Jak na to?
+První řádek tabulky, seznam [A, B, C, D, E, F, G] už máš – to jsou souřadnice
+hada, `snake`.
+Kdyz se ti k tomu podaří připravit seznamy s druhým řádkem,
+[×, A, B, C, D, E, F], a třetím, [B, C, D, E, F, G, ×], můžeš je pak spojit
+pomocí funkce `zip`.
+Vzpomínáš na ni?
+Prochází několik „opovídajících si“ seznamů a dá n-tici
+prvních prvků, pak n-tici druhých prvků, pak třetích…
+
+Náš příklad byl:
+
+```python
+veci = ['tráva', 'slunce', 'mrkev', 'řeka']
+barvy = ['zelená', 'žluté', 'oranžová', 'modrá']
+mista = ['na zemi', 'nahoře', 'na talíři', 'za zídkou']
+
+for vec, barva, misto in zip(veci, barvy, mista):
+ print(barva, vec, 'je', misto)
+```
+
+Ale stejně tak můžeš použít:
+
+```python
+snake = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
+prevs = ['x', 'A', 'B', 'C', 'D', 'E', 'F']
+nexts = ['B', 'C', 'D', 'E', 'F', 'G', 'x']
+
+for coords, prev, next in zip(snake, prevs, nexts):
+ print('na políčku', coords, 'had leze z', prev, 'do', next)
+```
+
+Ty dva další seznamy je ale potřeba „vyrobit“ z prvního:
+vybrat správný kousek a na správnou stranu doplnit „chybějící“ prvek:
+
+```python
+snake = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
+prevs = ['end'] + snake[:-1]
+nexts = snake[1:] + ['end']
+
+for coords, prev, next in zip(snake, prevs, nexts):
+ print('na políčku', coords, 'had leze z', prev, 'do', next)
+```
+
+Anebo, s „opravdovými“ souřadnicemi a funkcí `direction`:
+
+```python
+snake = [(1, 2), (2, 2), (3, 2), (3, 3), (3, 4), (3, 5), (4, 5)]
+
+for coords, prev, next in zip(snake, ['end'] + snake[:-1], snake[1:] + ['end']):
+ before = direction(coords, prev) # směr z aktuálního políčka na předchozí
+ after = direction(coords, next) # směr z aktuálního políčka na následující
+ key = before + '-' + after
+ print('na', coords, 'vykreslit:', key)
+```
+
+Jestli jsi {{gnd('došel', 'došla')}} až sem, doufám, že nebudeš mít příliš
+velké problémy s „transplantací“ tohoto kódu do svojí hry.
diff --git a/lessons/snake/tile-selection/info.yml b/lessons/snake/tile-selection/info.yml
new file mode 100644
index 00000000..6e82e53d
--- /dev/null
+++ b/lessons/snake/tile-selection/info.yml
@@ -0,0 +1,4 @@
+title: Vybírání kousků hada
+style: md
+attribution: Pro PyLadies CZ napsal Petr Viktorin, 2018-2019.
+license: cc-by-sa-40
diff --git a/lessons/snake/tile-selection/static/lettered.svg b/lessons/snake/tile-selection/static/lettered.svg
new file mode 100644
index 00000000..3968befe
--- /dev/null
+++ b/lessons/snake/tile-selection/static/lettered.svg
@@ -0,0 +1,566 @@
+
+
+
+
diff --git a/lessons/snake/tile-selection/static/snake-tiles/bottom-bottom.png b/lessons/snake/tile-selection/static/snake-tiles/bottom-bottom.png
new file mode 100644
index 00000000..241b23e5
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/bottom-bottom.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/bottom-dead.png b/lessons/snake/tile-selection/static/snake-tiles/bottom-dead.png
new file mode 100644
index 00000000..dbd6daf0
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/bottom-dead.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/bottom-end.png b/lessons/snake/tile-selection/static/snake-tiles/bottom-end.png
new file mode 100644
index 00000000..96448bd0
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/bottom-end.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/bottom-left.png b/lessons/snake/tile-selection/static/snake-tiles/bottom-left.png
new file mode 100644
index 00000000..5bccfc61
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/bottom-left.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/bottom-right.png b/lessons/snake/tile-selection/static/snake-tiles/bottom-right.png
new file mode 100644
index 00000000..e0b4a51c
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/bottom-right.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/bottom-tongue.png b/lessons/snake/tile-selection/static/snake-tiles/bottom-tongue.png
new file mode 100644
index 00000000..200554af
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/bottom-tongue.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/bottom-top.png b/lessons/snake/tile-selection/static/snake-tiles/bottom-top.png
new file mode 100644
index 00000000..241b23e5
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/bottom-top.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/end-bottom.png b/lessons/snake/tile-selection/static/snake-tiles/end-bottom.png
new file mode 100644
index 00000000..fa20ad75
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/end-bottom.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/end-dead.png b/lessons/snake/tile-selection/static/snake-tiles/end-dead.png
new file mode 100644
index 00000000..ac1028e6
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/end-dead.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/end-end.png b/lessons/snake/tile-selection/static/snake-tiles/end-end.png
new file mode 100644
index 00000000..982c2088
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/end-end.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/end-left.png b/lessons/snake/tile-selection/static/snake-tiles/end-left.png
new file mode 100644
index 00000000..d9a5a338
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/end-left.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/end-right.png b/lessons/snake/tile-selection/static/snake-tiles/end-right.png
new file mode 100644
index 00000000..0c7fd1fe
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/end-right.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/end-tongue.png b/lessons/snake/tile-selection/static/snake-tiles/end-tongue.png
new file mode 100644
index 00000000..982c2088
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/end-tongue.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/end-top.png b/lessons/snake/tile-selection/static/snake-tiles/end-top.png
new file mode 100644
index 00000000..a03e2c8e
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/end-top.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/left-bottom.png b/lessons/snake/tile-selection/static/snake-tiles/left-bottom.png
new file mode 100644
index 00000000..5bccfc61
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/left-bottom.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/left-dead.png b/lessons/snake/tile-selection/static/snake-tiles/left-dead.png
new file mode 100644
index 00000000..9d9604a7
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/left-dead.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/left-end.png b/lessons/snake/tile-selection/static/snake-tiles/left-end.png
new file mode 100644
index 00000000..70181010
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/left-end.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/left-left.png b/lessons/snake/tile-selection/static/snake-tiles/left-left.png
new file mode 100644
index 00000000..ec4699b5
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/left-left.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/left-right.png b/lessons/snake/tile-selection/static/snake-tiles/left-right.png
new file mode 100644
index 00000000..ec4699b5
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/left-right.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/left-tongue.png b/lessons/snake/tile-selection/static/snake-tiles/left-tongue.png
new file mode 100644
index 00000000..f2ae95b6
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/left-tongue.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/left-top.png b/lessons/snake/tile-selection/static/snake-tiles/left-top.png
new file mode 100644
index 00000000..bfef05b0
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/left-top.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/right-bottom.png b/lessons/snake/tile-selection/static/snake-tiles/right-bottom.png
new file mode 100644
index 00000000..e0b4a51c
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/right-bottom.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/right-dead.png b/lessons/snake/tile-selection/static/snake-tiles/right-dead.png
new file mode 100644
index 00000000..19bc01e5
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/right-dead.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/right-end.png b/lessons/snake/tile-selection/static/snake-tiles/right-end.png
new file mode 100644
index 00000000..37c525b2
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/right-end.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/right-left.png b/lessons/snake/tile-selection/static/snake-tiles/right-left.png
new file mode 100644
index 00000000..ec4699b5
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/right-left.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/right-right.png b/lessons/snake/tile-selection/static/snake-tiles/right-right.png
new file mode 100644
index 00000000..ec4699b5
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/right-right.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/right-tongue.png b/lessons/snake/tile-selection/static/snake-tiles/right-tongue.png
new file mode 100644
index 00000000..26ed562b
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/right-tongue.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/right-top.png b/lessons/snake/tile-selection/static/snake-tiles/right-top.png
new file mode 100644
index 00000000..5d530d10
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/right-top.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/top-bottom.png b/lessons/snake/tile-selection/static/snake-tiles/top-bottom.png
new file mode 100644
index 00000000..241b23e5
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/top-bottom.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/top-dead.png b/lessons/snake/tile-selection/static/snake-tiles/top-dead.png
new file mode 100644
index 00000000..b283030c
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/top-dead.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/top-end.png b/lessons/snake/tile-selection/static/snake-tiles/top-end.png
new file mode 100644
index 00000000..07495282
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/top-end.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/top-left.png b/lessons/snake/tile-selection/static/snake-tiles/top-left.png
new file mode 100644
index 00000000..bfef05b0
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/top-left.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/top-right.png b/lessons/snake/tile-selection/static/snake-tiles/top-right.png
new file mode 100644
index 00000000..5d530d10
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/top-right.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/top-tongue.png b/lessons/snake/tile-selection/static/snake-tiles/top-tongue.png
new file mode 100644
index 00000000..98372b52
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/top-tongue.png differ
diff --git a/lessons/snake/tile-selection/static/snake-tiles/top-top.png b/lessons/snake/tile-selection/static/snake-tiles/top-top.png
new file mode 100644
index 00000000..241b23e5
Binary files /dev/null and b/lessons/snake/tile-selection/static/snake-tiles/top-top.png differ
diff --git a/lessons/snake/tile-selection/static/tile-selection.svg b/lessons/snake/tile-selection/static/tile-selection.svg
new file mode 100644
index 00000000..7685e96d
--- /dev/null
+++ b/lessons/snake/tile-selection/static/tile-selection.svg
@@ -0,0 +1,499 @@
+
+
+
+
diff --git a/lessons/snake/toroid/index.md b/lessons/snake/toroid/index.md
new file mode 100644
index 00000000..5b1751d4
--- /dev/null
+++ b/lessons/snake/toroid/index.md
@@ -0,0 +1,101 @@
+# Nekonečná klec
+
+Místo konce hry při naražení do okraje okýnka můžeš nechat hada „projít“
+a objevit se na druhé straně.
+
+Z pohledu logiky hry to není tak složité, jak to může znít.
+Stačí v `move` místo ukončení hry správně nastavit příslušnou hodnotu.
+Je ale potřeba si dát pozor kde použít `new_x` a kde `new_y`, kde `width` a kde
+`height`, a kde přičíst nebo odečíst jedničku, aby při číslování od nuly
+všechno sedělo.
+Zkus to!
+
+{% filter solution %}
+```python
+ # Kontrola vylezení z hrací plochy
+ if new_x < 0:
+ new_x = self.width - 1
+ if new_y < 0:
+ new_y = self.height - 1
+ if new_x >= self.width:
+ new_x = 0
+ if new_y >= self.height:
+ new_y = 0
+```
+{% endfilter %}
+
+Jestli ale vykresluješ hada (místo housenky), narazíš teď na problém
+s vybíráním správných dílků – okraj herní plochy hada vizuálně rozdělí
+na dva menší.
+Řešení tohoto problému nechávám na čtenáři – s tím, že je to hodně těžký
+problém.
+
+
+## Zbytkové řešení
+
+Jde logiku vylézání z okýnka vyřešit jednodušeji? Jde!
+Matematikové vymysleli operaci, která se jmenuje *zbytek po dělení*.
+Ta dělá přesně to, co tu potřebuješ – zbytek po dělení nové souřadnice velikostí
+hřiště dá souřadnici, která leží v hřišti.
+Když byla předchozí souřadnice o jedna větší než maximum,
+zbytek po dělení bude nula; když byla -1, dostaneme maximum.
+
+Python moužívá pro zbytek po dělení operátor `%`. Zkus si to:
+
+``` pycon
+>>> 6 % 10 # Zbytek po dělení šesti desíti
+6
+>>> 10 % 10
+0
+>>> -1 % 10
+9
+```
+
+Celý kód pro kontrolu a ošetření vylézání z hrací plochy tak jde
+nahradit dvěma řádky:
+
+```python
+ new_x = new_x % self.width
+ new_y = new_y % self.height
+```
+
+Podobné matematické „zkratky“ umí programátorům často usnadnit život.
+Jen přijít na ně nebývá jednoduché.
+Ale nevěš hlavu: neláká-li tě studovat informatiku na škole, věz, že to jde
+i bez „zkratek“. Jen občas trochu krkoloměji.
+
+> [note]
+> To, že existuje přesně operace kterou potřebujeme, není až tak úplně náhoda.
+> Ona matematická jednoduchost je spíš *důvod*, proč se hrací plocha
+> u spousty starých her chová právě takhle.
+> Odborně se tomu „takhle“ říká
+> [toroidální topologie](https://en.wikipedia.org/wiki/Torus#Topology).
+
+> [note] Pro matematiky
+> Zkušení matematici si teď možná stěžují na nutnost definovat zbytek po
+> dělení záporného čísla. Proto dodám, že ho Python schválně
+> [definuje vhodně](https://docs.python.org/3/reference/expressions.html#binary-arithmetic-operations)
+> pro tento účel; `a % b` má vždy stejné znaménko jako `b`.
+
+
+{# XXX
+
+## Vykreslování
+
+> Volné chvilce se pokus problém opravit.
+> Doporučuji se vrátit k „abstraktní“ funkci, která jen vypisuje souřadnice
+> a směry:
+>
+> ```
+> 1 2 tail right
+> 2 2 left right
+> 3 2 left top
+> 3 3 bottom top
+> 3 4 bottom top
+> 3 5 bottom right
+> 4 5 left head
+> ```
+> Jdeš-li podle návodu, tuhle funkci máš uloženou v souboru `smery.py`
+> Oprav nejdřív tu, a řešení „transplantuj“ do hry.
+#}
+
diff --git a/lessons/snake/toroid/info.yml b/lessons/snake/toroid/info.yml
new file mode 100644
index 00000000..c22d27aa
--- /dev/null
+++ b/lessons/snake/toroid/info.yml
@@ -0,0 +1,4 @@
+title: Rozšíření Hada – Nekonečná klec
+style: md
+attribution: Pro PyLadies CZ napsal Petr Viktorin, 2018.
+license: cc-by-sa-40