From 333085ee5370e648151bf0b90c89f205fb8a2afa Mon Sep 17 00:00:00 2001 From: pepaskopec-blip Date: Sun, 11 Oct 2026 14:37:14 +0200 Subject: [PATCH] Add a C# course with a side-by-side code runner. Each lesson explains a topic and runs the edited program next to the text through the local .NET SDK. Co-authored-by: Cursor --- README.md | 8 +- .../maturita/maturita/desktop/CSharpLab.kt | 128 ++++++ .../maturita/desktop/DesktopPlatform.kt | 9 + .../shared/src/org/maturita/maturita/App.kt | 47 +++ .../src/org/maturita/maturita/AppViewModel.kt | 20 + .../shared/src/org/maturita/maturita/Route.kt | 35 ++ .../src/org/maturita/maturita/data/Content.kt | 8 + .../maturita/maturita/data/ProgressStore.kt | 40 ++ .../maturita/maturita/platform/Platform.kt | 10 + .../org/maturita/maturita/ui/CodeLesson.kt | 158 ++++++++ .../src/org/maturita/maturita/ui/Screens.kt | 40 ++ content/README.md | 2 +- content/aplikace/novinky.txt | 6 + content/aplikace/predmety.md | 2 +- content/aplikace/texty.md | 23 ++ .../programovani/1-rocnik/01-ahoj-svete.en.md | 4 + .../programovani/1-rocnik/01-ahoj-svete.md | 42 ++ .../programovani/1-rocnik/02-komentare.en.md | 4 + content/programovani/1-rocnik/02-komentare.md | 35 ++ .../programovani/1-rocnik/03-promenne.en.md | 4 + content/programovani/1-rocnik/03-promenne.md | 36 ++ content/programovani/1-rocnik/04-typy.en.md | 4 + content/programovani/1-rocnik/04-typy.md | 40 ++ .../programovani/1-rocnik/05-prevody.en.md | 4 + content/programovani/1-rocnik/05-prevody.md | 36 ++ .../programovani/1-rocnik/06-operatory.en.md | 4 + content/programovani/1-rocnik/06-operatory.md | 37 ++ .../programovani/1-rocnik/07-retezce.en.md | 4 + content/programovani/1-rocnik/07-retezce.md | 36 ++ content/programovani/1-rocnik/08-vstup.en.md | 4 + content/programovani/1-rocnik/08-vstup.md | 34 ++ content/programovani/2-rocnik/01-if.en.md | 4 + content/programovani/2-rocnik/01-if.md | 43 ++ content/programovani/2-rocnik/02-switch.en.md | 4 + content/programovani/2-rocnik/02-switch.md | 43 ++ content/programovani/2-rocnik/03-while.en.md | 4 + content/programovani/2-rocnik/03-while.md | 38 ++ content/programovani/2-rocnik/04-for.en.md | 4 + content/programovani/2-rocnik/04-for.md | 34 ++ content/programovani/2-rocnik/05-pole.en.md | 4 + content/programovani/2-rocnik/05-pole.md | 35 ++ .../programovani/2-rocnik/06-foreach.en.md | 4 + content/programovani/2-rocnik/06-foreach.md | 35 ++ content/programovani/2-rocnik/07-metody.en.md | 4 + content/programovani/2-rocnik/07-metody.md | 37 ++ content/programovani/2-rocnik/08-navrat.en.md | 4 + content/programovani/2-rocnik/08-navrat.md | 38 ++ content/programovani/3-rocnik/01-trida.en.md | 4 + content/programovani/3-rocnik/01-trida.md | 40 ++ .../3-rocnik/02-konstruktor.en.md | 4 + .../programovani/3-rocnik/02-konstruktor.md | 44 +++ .../programovani/3-rocnik/03-vlastnosti.en.md | 4 + .../programovani/3-rocnik/03-vlastnosti.md | 50 +++ .../3-rocnik/04-zapouzdreni.en.md | 4 + .../programovani/3-rocnik/04-zapouzdreni.md | 49 +++ .../programovani/3-rocnik/05-dedicnost.en.md | 4 + content/programovani/3-rocnik/05-dedicnost.md | 52 +++ .../3-rocnik/06-polymorfismus.en.md | 4 + .../programovani/3-rocnik/06-polymorfismus.md | 50 +++ .../programovani/3-rocnik/07-rozhrani.en.md | 4 + content/programovani/3-rocnik/07-rozhrani.md | 50 +++ content/programovani/3-rocnik/08-enum.en.md | 4 + content/programovani/3-rocnik/08-enum.md | 50 +++ .../programovani/4-rocnik/01-vyjimky.en.md | 4 + content/programovani/4-rocnik/01-vyjimky.md | 43 ++ content/programovani/4-rocnik/02-list.en.md | 4 + content/programovani/4-rocnik/02-list.md | 39 ++ .../programovani/4-rocnik/03-slovnik.en.md | 4 + content/programovani/4-rocnik/03-slovnik.md | 39 ++ content/programovani/4-rocnik/04-linq.en.md | 4 + content/programovani/4-rocnik/04-linq.md | 36 ++ .../programovani/4-rocnik/05-soubory.en.md | 4 + content/programovani/4-rocnik/05-soubory.md | 33 ++ .../programovani/4-rocnik/06-generika.en.md | 4 + content/programovani/4-rocnik/06-generika.md | 37 ++ .../programovani/4-rocnik/07-delegaty.en.md | 4 + content/programovani/4-rocnik/07-delegaty.md | 34 ++ content/programovani/4-rocnik/08-async.en.md | 4 + content/programovani/4-rocnik/08-async.md | 33 ++ ios/Graduately/AppModel.swift | 20 + ios/Graduately/Data/Content.swift | 12 + ios/Graduately/Data/ProgressStore.swift | 40 ++ ios/Graduately/Route.swift | 53 +++ ios/Graduately/UI/Components.swift | 1 + ios/Graduately/UI/Screens.swift | 370 ++++++++++++++++++ scripts/build-content.py | 49 ++- 86 files changed, 2482 insertions(+), 5 deletions(-) create mode 100644 android/desktop/src/main/kotlin/org/maturita/maturita/desktop/CSharpLab.kt create mode 100644 android/shared/src/org/maturita/maturita/ui/CodeLesson.kt create mode 100644 content/programovani/1-rocnik/01-ahoj-svete.en.md create mode 100644 content/programovani/1-rocnik/01-ahoj-svete.md create mode 100644 content/programovani/1-rocnik/02-komentare.en.md create mode 100644 content/programovani/1-rocnik/02-komentare.md create mode 100644 content/programovani/1-rocnik/03-promenne.en.md create mode 100644 content/programovani/1-rocnik/03-promenne.md create mode 100644 content/programovani/1-rocnik/04-typy.en.md create mode 100644 content/programovani/1-rocnik/04-typy.md create mode 100644 content/programovani/1-rocnik/05-prevody.en.md create mode 100644 content/programovani/1-rocnik/05-prevody.md create mode 100644 content/programovani/1-rocnik/06-operatory.en.md create mode 100644 content/programovani/1-rocnik/06-operatory.md create mode 100644 content/programovani/1-rocnik/07-retezce.en.md create mode 100644 content/programovani/1-rocnik/07-retezce.md create mode 100644 content/programovani/1-rocnik/08-vstup.en.md create mode 100644 content/programovani/1-rocnik/08-vstup.md create mode 100644 content/programovani/2-rocnik/01-if.en.md create mode 100644 content/programovani/2-rocnik/01-if.md create mode 100644 content/programovani/2-rocnik/02-switch.en.md create mode 100644 content/programovani/2-rocnik/02-switch.md create mode 100644 content/programovani/2-rocnik/03-while.en.md create mode 100644 content/programovani/2-rocnik/03-while.md create mode 100644 content/programovani/2-rocnik/04-for.en.md create mode 100644 content/programovani/2-rocnik/04-for.md create mode 100644 content/programovani/2-rocnik/05-pole.en.md create mode 100644 content/programovani/2-rocnik/05-pole.md create mode 100644 content/programovani/2-rocnik/06-foreach.en.md create mode 100644 content/programovani/2-rocnik/06-foreach.md create mode 100644 content/programovani/2-rocnik/07-metody.en.md create mode 100644 content/programovani/2-rocnik/07-metody.md create mode 100644 content/programovani/2-rocnik/08-navrat.en.md create mode 100644 content/programovani/2-rocnik/08-navrat.md create mode 100644 content/programovani/3-rocnik/01-trida.en.md create mode 100644 content/programovani/3-rocnik/01-trida.md create mode 100644 content/programovani/3-rocnik/02-konstruktor.en.md create mode 100644 content/programovani/3-rocnik/02-konstruktor.md create mode 100644 content/programovani/3-rocnik/03-vlastnosti.en.md create mode 100644 content/programovani/3-rocnik/03-vlastnosti.md create mode 100644 content/programovani/3-rocnik/04-zapouzdreni.en.md create mode 100644 content/programovani/3-rocnik/04-zapouzdreni.md create mode 100644 content/programovani/3-rocnik/05-dedicnost.en.md create mode 100644 content/programovani/3-rocnik/05-dedicnost.md create mode 100644 content/programovani/3-rocnik/06-polymorfismus.en.md create mode 100644 content/programovani/3-rocnik/06-polymorfismus.md create mode 100644 content/programovani/3-rocnik/07-rozhrani.en.md create mode 100644 content/programovani/3-rocnik/07-rozhrani.md create mode 100644 content/programovani/3-rocnik/08-enum.en.md create mode 100644 content/programovani/3-rocnik/08-enum.md create mode 100644 content/programovani/4-rocnik/01-vyjimky.en.md create mode 100644 content/programovani/4-rocnik/01-vyjimky.md create mode 100644 content/programovani/4-rocnik/02-list.en.md create mode 100644 content/programovani/4-rocnik/02-list.md create mode 100644 content/programovani/4-rocnik/03-slovnik.en.md create mode 100644 content/programovani/4-rocnik/03-slovnik.md create mode 100644 content/programovani/4-rocnik/04-linq.en.md create mode 100644 content/programovani/4-rocnik/04-linq.md create mode 100644 content/programovani/4-rocnik/05-soubory.en.md create mode 100644 content/programovani/4-rocnik/05-soubory.md create mode 100644 content/programovani/4-rocnik/06-generika.en.md create mode 100644 content/programovani/4-rocnik/06-generika.md create mode 100644 content/programovani/4-rocnik/07-delegaty.en.md create mode 100644 content/programovani/4-rocnik/07-delegaty.md create mode 100644 content/programovani/4-rocnik/08-async.en.md create mode 100644 content/programovani/4-rocnik/08-async.md diff --git a/README.md b/README.md index db629d9..22b57c4 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,8 @@ Intel Mac, jiná architektura nebo úpravy kódu → [sestavení ze zdroje](#ses ročníky 2–4 po 8 lekcích (monitory, sestavení, RAID, servery, údržba) - **Technická grafika** — 4 ročníky AutoCADu, v každém 8 lekcí: výkres a souřadnice, úpravy a kóty, pohledy a tisk, 3D a maturitní zadání +- **Programování** — 4 ročníky C# po vzoru W3Schools. U každé lekce se kód + píše a spouští vedle výkladu, od výpisu po třídy, LINQ a async - **Český jazyk a literatura** — literatura (4 ročníky, 32 lekcí podle osnov středních škol), mluvnice (20 cvičení) a maturitní četba (82 knih: zápisky, kvíz, sestavení děje) @@ -92,7 +94,7 @@ Intel Mac, jiná architektura nebo úpravy kódu → [sestavení ze zdroje](#ses ### Použití -1. **Úvod** ukáže postup a mřížku předmětů. Otevřený předmět spustí cestu. Zamčené dlaždice zatím nic neotevřou: prezentační grafika, programování a programové vybavení. +1. **Úvod** ukáže postup a mřížku předmětů. Otevřený předmět spustí cestu. Zamčené dlaždice zatím nic neotevřou: prezentační grafika a programové vybavení. 2. U němčiny je učebnice (Neue Freunde, Aus aller Welt, Bei uns zu Hause) a čtyři ročníky. Ostatní otevřené předměty mají ročníky, lekce nebo knihy. 3. Z cesty se otevře výklad, poslech, čtení, psaní nebo kvíz. 4. **Statistiky**, **Nastavení** a **Hledat** jsou v postranním panelu na Macu. Na telefonu a v desktopové aplikaci jsou na záložkách, hledání navíc ikonou lupy. Zkratka je `Cmd/Ctrl+K`. @@ -263,6 +265,8 @@ Intel Mac, another architecture, or hacking on the code → years 2–4 have 8 lessons each (displays, assembly, RAID, servers, maintenance) - **Technical graphics** — 4 years of AutoCAD, 8 lessons each: the sheet and coordinates, editing and dimensions, views and plotting, 3D and the exam drawing +- **Programming** — 4 years of C# in the style of W3Schools. Each lesson has + code you edit and run beside the explanation, from output to classes, LINQ and async - **Czech language** — literature (4 years, 32 lessons from the secondary-school syllabus), grammar (20 exercises) and required reading (82 books: notes, quiz, plot ordering) @@ -283,7 +287,7 @@ Intel Mac, another architecture, or hacking on the code → ### Usage -1. **Welcome** shows progress and the subject grid. An open subject starts its path. Locked tiles do nothing yet: presentation graphics, programming and software. +1. **Welcome** shows progress and the subject grid. An open subject starts its path. Locked tiles do nothing yet: presentation graphics and software. 2. German has the textbook (Neue Freunde, Aus aller Welt, Bei uns zu Hause) and four years. The other open subjects have years, lessons or books. 3. A path opens an overview, listening, reading, writing or a quiz. 4. **Statistics**, **Settings** and **Search** sit in the sidebar on Mac. On the phone and the desktop app they are tabs, with search also on the magnifier. The shortcut is `Cmd/Ctrl+K`. diff --git a/android/desktop/src/main/kotlin/org/maturita/maturita/desktop/CSharpLab.kt b/android/desktop/src/main/kotlin/org/maturita/maturita/desktop/CSharpLab.kt new file mode 100644 index 0000000..ad7f5f1 --- /dev/null +++ b/android/desktop/src/main/kotlin/org/maturita/maturita/desktop/CSharpLab.kt @@ -0,0 +1,128 @@ +package org.maturita.maturita.desktop + +import org.maturita.maturita.platform.CSharpRun +import java.io.ByteArrayOutputStream +import java.io.File +import java.nio.charset.StandardCharsets +import java.util.concurrent.TimeUnit + +/** One cached console project. The student's program replaces Program.cs and `dotnet run` executes it. */ +object CSharpLab { + private val gate = Object() + + fun run(source: String, stdin: String): CSharpRun { + if (source.length > 100_000) return CSharpRun("fail", "Program je příliš dlouhý.") + val dotnet = findDotnet() ?: return CSharpRun("unavailable", "") + synchronized(gate) { + return try { + execute(dotnet, source, stdin) + } catch (e: Exception) { + CSharpRun("fail", e.message ?: "Spuštění selhalo.") + } + } + } + + private fun execute(dotnet: String, source: String, stdin: String): CSharpRun { + val dir = File(System.getProperty("user.home"), ".graduately/csharp-lab") + dir.mkdirs() + val csproj = File(dir, "Lab.csproj") + if (!csproj.isFile) { + csproj.writeText(projectFile(targetFramework(dotnet)), StandardCharsets.UTF_8) + val restored = command(dotnet, listOf("restore", dir.absolutePath), dir, "", 90) + if (restored.status != "ok") return restored + } + File(dir, "Program.cs").writeText(source, StandardCharsets.UTF_8) + val args = mutableListOf("run", "--project", dir.absolutePath, "--verbosity", "quiet") + if (File(dir, "obj").isDirectory) args += "--no-restore" + return command(dotnet, args, dir, stdin, 20) + } + + private fun command(dotnet: String, args: List, dir: File, stdin: String, seconds: Long): CSharpRun { + val process = ProcessBuilder(listOf(dotnet) + args) + .directory(dir) + .redirectErrorStream(true) + .apply { + environment().clear() + environment().putAll(environmentFor(dotnet)) + } + .start() + process.outputStream.use { it.write(stdin.toByteArray(StandardCharsets.UTF_8)) } + val output = ByteArrayOutputStream() + val reader = Thread { + process.inputStream.use { it.copyTo(output) } + } + reader.isDaemon = true + reader.start() + val finished = process.waitFor(seconds, TimeUnit.SECONDS) + if (!finished) { + process.descendants().forEach { it.destroyForcibly() } + process.destroyForcibly() + process.waitFor(2, TimeUnit.SECONDS) + reader.join(1000) + return CSharpRun("timeout", clip(output.toString(StandardCharsets.UTF_8))) + } + reader.join(2000) + val text = clip(output.toString(StandardCharsets.UTF_8)).trim() + return if (process.exitValue() == 0) CSharpRun("ok", text) else CSharpRun("fail", text) + } + + private fun clip(text: String): String = + if (text.length <= 16_000) text else text.take(16_000) + "\n…" + + private fun environmentFor(dotnet: String): Map { + val home = System.getProperty("user.home") + val root = File(dotnet).parent + val path = listOf(root, "/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin") + .joinToString(File.pathSeparator) + val env = mutableMapOf( + "PATH" to path, + "HOME" to home, + "USERPROFILE" to home, + "DOTNET_ROOT" to root, + "DOTNET_CLI_TELEMETRY_OPTOUT" to "1", + "DOTNET_NOLOGO" to "1", + "DOTNET_SKIP_FIRST_TIME_EXPERIENCE" to "1", + "TEMP" to System.getProperty("java.io.tmpdir"), + "TMP" to System.getProperty("java.io.tmpdir"), + ) + System.getenv("SystemRoot")?.let { env["SystemRoot"] = it } + System.getenv("ComSpec")?.let { env["ComSpec"] = it } + return env + } + + private fun targetFramework(dotnet: String): String { + val version = command(dotnet, listOf("--version"), File(System.getProperty("java.io.tmpdir")), "", 20) + val major = version.output.trim().substringBefore('.').filter { it.isDigit() } + return if (major.isEmpty()) "net8.0" else "net$major.0" + } + + private fun projectFile(framework: String) = """ + + + Exe + $framework + enable + disable + latest + + + """.trimIndent() + + private fun findDotnet(): String? { + val home = System.getProperty("user.home") + val programFiles = System.getenv("ProgramFiles") ?: "C:\\Program Files" + val names = listOf( + "/usr/local/share/dotnet/dotnet", + "/usr/share/dotnet/dotnet", + "/opt/homebrew/share/dotnet/dotnet", + "$home/.dotnet/dotnet", + "$programFiles\\dotnet\\dotnet.exe", + ) + names.firstOrNull { File(it).canExecute() }?.let { return it } + val path = System.getenv("PATH") ?: return null + return path.split(File.pathSeparator) + .map { File(it, if (System.getProperty("os.name").startsWith("Windows")) "dotnet.exe" else "dotnet") } + .firstOrNull { it.canExecute() } + ?.absolutePath + } +} diff --git a/android/desktop/src/main/kotlin/org/maturita/maturita/desktop/DesktopPlatform.kt b/android/desktop/src/main/kotlin/org/maturita/maturita/desktop/DesktopPlatform.kt index ced42c6..c61e57c 100644 --- a/android/desktop/src/main/kotlin/org/maturita/maturita/desktop/DesktopPlatform.kt +++ b/android/desktop/src/main/kotlin/org/maturita/maturita/desktop/DesktopPlatform.kt @@ -1,8 +1,10 @@ package org.maturita.maturita.desktop +import org.maturita.maturita.platform.CSharpRun import org.maturita.maturita.platform.Platform import org.maturita.maturita.platform.Prefs import org.maturita.maturita.platform.Speaker +import javax.swing.SwingUtilities class DesktopPlatform( override val commit: String, @@ -14,4 +16,11 @@ class DesktopPlatform( override val updater = DesktopUpdater(commit, install) override fun speaker(): Speaker? = DesktopSpeaker.create() + + override fun runCSharp(source: String, stdin: String, cb: (CSharpRun) -> Unit) { + Thread { + val result = CSharpLab.run(source, stdin) + SwingUtilities.invokeLater { cb(result) } + }.apply { isDaemon = true }.start() + } } diff --git a/android/shared/src/org/maturita/maturita/App.kt b/android/shared/src/org/maturita/maturita/App.kt index 4f2839a..df44197 100644 --- a/android/shared/src/org/maturita/maturita/App.kt +++ b/android/shared/src/org/maturita/maturita/App.kt @@ -23,6 +23,8 @@ import org.maturita.maturita.data.ColorMode import org.maturita.maturita.data.Palette import org.maturita.maturita.ui.BookListScreen import org.maturita.maturita.ui.BookScreen +import org.maturita.maturita.ui.CodeLessonScreen +import org.maturita.maturita.ui.CsYearsScreen import org.maturita.maturita.ui.CzechMapScreen import org.maturita.maturita.ui.GermanExercise import org.maturita.maturita.ui.HomeScreen @@ -584,6 +586,51 @@ private fun MaturitaAppBody(vm: AppViewModel) { } } is Route.Tg4Ex -> TgQuizScreen(vm, 4, r.id) + Route.CsYears -> CsYearsScreen(vm) + Route.CsMap -> LessonListScreen( + vm, vm.tr("cs_year1"), vm.tr("cs_sub"), + vm.content.cs.map { l -> + val id = l.int("id") + LessonRow(vm.tr(l.str("titleKey")), vm.progress.csDone(id), Route.CsLesson(id)) + }, + ) + is Route.CsLesson -> { + val l = vm.content.csLesson(r.id) + if (l != null) CodeLessonScreen(vm, l) { vm.markCs(r.id) } + } + Route.Cs2Map -> LessonListScreen( + vm, vm.tr("cs_year2"), vm.tr("cs2_sub"), + vm.content.cs2.map { l -> + val id = l.int("id") + LessonRow(vm.tr(l.str("titleKey")), vm.progress.cs2Done(id), Route.Cs2Lesson(id)) + }, + ) + is Route.Cs2Lesson -> { + val l = vm.content.cs2Lesson(r.id) + if (l != null) CodeLessonScreen(vm, l) { vm.markCs2(r.id) } + } + Route.Cs3Map -> LessonListScreen( + vm, vm.tr("cs_year3"), vm.tr("cs3_sub"), + vm.content.cs3.map { l -> + val id = l.int("id") + LessonRow(vm.tr(l.str("titleKey")), vm.progress.cs3Done(id), Route.Cs3Lesson(id)) + }, + ) + is Route.Cs3Lesson -> { + val l = vm.content.cs3Lesson(r.id) + if (l != null) CodeLessonScreen(vm, l) { vm.markCs3(r.id) } + } + Route.Cs4Map -> LessonListScreen( + vm, vm.tr("cs_year4"), vm.tr("cs4_sub"), + vm.content.cs4.map { l -> + val id = l.int("id") + LessonRow(vm.tr(l.str("titleKey")), vm.progress.cs4Done(id), Route.Cs4Lesson(id)) + }, + ) + is Route.Cs4Lesson -> { + val l = vm.content.cs4Lesson(r.id) + if (l != null) CodeLessonScreen(vm, l) { vm.markCs4(r.id) } + } Route.SciMap -> SciMapScreen(vm) Route.ChemMap -> LessonListScreen( vm, vm.tr("Chemie"), vm.tr("chem_sub"), diff --git a/android/shared/src/org/maturita/maturita/AppViewModel.kt b/android/shared/src/org/maturita/maturita/AppViewModel.kt index 956f666..f12a718 100644 --- a/android/shared/src/org/maturita/maturita/AppViewModel.kt +++ b/android/shared/src/org/maturita/maturita/AppViewModel.kt @@ -262,6 +262,26 @@ class AppViewModel(val content: Content, val platform: Platform) { refresh() } + fun markCs(id: Int) { + progress.markCs(id) + refresh() + } + + fun markCs2(id: Int) { + progress.markCs2(id) + refresh() + } + + fun markCs3(id: Int) { + progress.markCs3(id) + refresh() + } + + fun markCs4(id: Int) { + progress.markCs4(id) + refresh() + } + fun markMluv(n: Int) { progress.markMluv(n) refresh() diff --git a/android/shared/src/org/maturita/maturita/Route.kt b/android/shared/src/org/maturita/maturita/Route.kt index 1e6cdf8..254a296 100644 --- a/android/shared/src/org/maturita/maturita/Route.kt +++ b/android/shared/src/org/maturita/maturita/Route.kt @@ -118,6 +118,15 @@ sealed class Route(val page: String) { data object Tg4Map : Route("tg4map") data class Tg4Lesson(val id: Int) : Route("tg4unit$id") data class Tg4Ex(val id: Int) : Route("tg4ex$id") + data object CsYears : Route("csyears") + data object CsMap : Route("csmap") + data class CsLesson(val id: Int) : Route("csunit$id") + data object Cs2Map : Route("cs2map") + data class Cs2Lesson(val id: Int) : Route("cs2unit$id") + data object Cs3Map : Route("cs3map") + data class Cs3Lesson(val id: Int) : Route("cs3unit$id") + data object Cs4Map : Route("cs4map") + data class Cs4Lesson(val id: Int) : Route("cs4unit$id") data object CzechMap : Route("czechmap") data object Mluvnice : Route("mluvnice") data class MluvEx(val n: Int) : Route("mluve$n") @@ -263,6 +272,15 @@ sealed class Route(val page: String) { Tg4Map -> listOf(Subjects, TgYears, Tg4Map) is Tg4Lesson -> listOf(Subjects, TgYears, Tg4Map, this) is Tg4Ex -> listOf(Subjects, TgYears, Tg4Map, Tg4Lesson(id), this) + CsYears -> listOf(Subjects, CsYears) + CsMap -> listOf(Subjects, CsYears, CsMap) + is CsLesson -> listOf(Subjects, CsYears, CsMap, this) + Cs2Map -> listOf(Subjects, CsYears, Cs2Map) + is Cs2Lesson -> listOf(Subjects, CsYears, Cs2Map, this) + Cs3Map -> listOf(Subjects, CsYears, Cs3Map) + is Cs3Lesson -> listOf(Subjects, CsYears, Cs3Map, this) + Cs4Map -> listOf(Subjects, CsYears, Cs4Map) + is Cs4Lesson -> listOf(Subjects, CsYears, Cs4Map, this) CzechMap -> listOf(Subjects, CzechMap) Mluvnice -> listOf(Subjects, CzechMap, Mluvnice) is MluvEx -> listOf(Subjects, CzechMap, Mluvnice, this) @@ -320,6 +338,11 @@ fun routeFromPage(page: String): Route? = when (page) { "tg2map" -> Route.Tg2Map "tg3map" -> Route.Tg3Map "tg4map" -> Route.Tg4Map + "csyears" -> Route.CsYears + "csmap" -> Route.CsMap + "cs2map" -> Route.Cs2Map + "cs3map" -> Route.Cs3Map + "cs4map" -> Route.Cs4Map "czechmap" -> Route.CzechMap "mluvnice" -> Route.Mluvnice "readinglist" -> Route.ReadingList @@ -441,6 +464,18 @@ fun routeFromPage(page: String): Route? = when (page) { Regex("""tgex(\d+)""").matchEntire(page)?.let { return Route.TgEx(it.groupValues[1].toInt()) } + Regex("""cs4unit(\d+)""").matchEntire(page)?.let { + return Route.Cs4Lesson(it.groupValues[1].toInt()) + } + Regex("""cs3unit(\d+)""").matchEntire(page)?.let { + return Route.Cs3Lesson(it.groupValues[1].toInt()) + } + Regex("""cs2unit(\d+)""").matchEntire(page)?.let { + return Route.Cs2Lesson(it.groupValues[1].toInt()) + } + Regex("""csunit(\d+)""").matchEntire(page)?.let { + return Route.CsLesson(it.groupValues[1].toInt()) + } Regex("""lit4unit(\d+)""").matchEntire(page)?.let { return Route.Lit4Lesson(it.groupValues[1].toInt()) } diff --git a/android/shared/src/org/maturita/maturita/data/Content.kt b/android/shared/src/org/maturita/maturita/data/Content.kt index 5a82ac7..f48e8ae 100644 --- a/android/shared/src/org/maturita/maturita/data/Content.kt +++ b/android/shared/src/org/maturita/maturita/data/Content.kt @@ -60,6 +60,10 @@ class Content(root: JSONObject) { val tg2 = raw.arr("tg2") val tg3 = raw.arr("tg3") val tg4 = raw.arr("tg4") + val cs = raw.arr("cs") + val cs2 = raw.arr("cs2") + val cs3 = raw.arr("cs3") + val cs4 = raw.arr("cs4") val mat0 = raw.arr("mat0") val mat = raw.arr("mat") val mat2 = raw.arr("mat2") @@ -116,6 +120,10 @@ class Content(root: JSONObject) { fun tg2Lesson(id: Int) = tg2.firstOrNull { it.int("id") == id } fun tg3Lesson(id: Int) = tg3.firstOrNull { it.int("id") == id } fun tg4Lesson(id: Int) = tg4.firstOrNull { it.int("id") == id } + fun csLesson(id: Int) = cs.firstOrNull { it.int("id") == id } + fun cs2Lesson(id: Int) = cs2.firstOrNull { it.int("id") == id } + fun cs3Lesson(id: Int) = cs3.firstOrNull { it.int("id") == id } + fun cs4Lesson(id: Int) = cs4.firstOrNull { it.int("id") == id } fun mat0Lesson(id: Int) = mat0.firstOrNull { it.int("id") == id } fun matLesson(id: Int) = mat.firstOrNull { it.int("id") == id } fun mat2Lesson(id: Int) = mat2.firstOrNull { it.int("id") == id } diff --git a/android/shared/src/org/maturita/maturita/data/ProgressStore.kt b/android/shared/src/org/maturita/maturita/data/ProgressStore.kt index 1c29a6f..df6c24a 100644 --- a/android/shared/src/org/maturita/maturita/data/ProgressStore.kt +++ b/android/shared/src/org/maturita/maturita/data/ProgressStore.kt @@ -99,6 +99,14 @@ class ProgressStore(private val prefs: Prefs, content: Content) { fun markTg3(id: Int) { mark("tg3.$id") } fun tg4Done(id: Int) = done("tg4.$id") fun markTg4(id: Int) { mark("tg4.$id") } + fun csDone(id: Int) = done("cs.$id") + fun markCs(id: Int) { mark("cs.$id") } + fun cs2Done(id: Int) = done("cs2.$id") + fun markCs2(id: Int) { mark("cs2.$id") } + fun cs3Done(id: Int) = done("cs3.$id") + fun markCs3(id: Int) { mark("cs3.$id") } + fun cs4Done(id: Int) = done("cs4.$id") + fun markCs4(id: Int) { mark("cs4.$id") } fun mat0Done(id: Int) = done("mat0.$id") fun markMat0(id: Int) { mark("mat0.$id") } fun matDone(id: Int) = done("mat.$id") @@ -320,6 +328,38 @@ fun summarize(content: Content, p: ProgressStore): ProgressSum { sum.doneUnits += 1 } } + content.cs.forEach { l -> + sum.totalEx += 1 + sum.openUnits += 1 + if (p.csDone(l.int("id"))) { + sum.doneEx += 1 + sum.doneUnits += 1 + } + } + content.cs2.forEach { l -> + sum.totalEx += 1 + sum.openUnits += 1 + if (p.cs2Done(l.int("id"))) { + sum.doneEx += 1 + sum.doneUnits += 1 + } + } + content.cs3.forEach { l -> + sum.totalEx += 1 + sum.openUnits += 1 + if (p.cs3Done(l.int("id"))) { + sum.doneEx += 1 + sum.doneUnits += 1 + } + } + content.cs4.forEach { l -> + sum.totalEx += 1 + sum.openUnits += 1 + if (p.cs4Done(l.int("id"))) { + sum.doneEx += 1 + sum.doneUnits += 1 + } + } content.mat0.forEach { l -> sum.totalEx += 1 sum.openUnits += 1 diff --git a/android/shared/src/org/maturita/maturita/platform/Platform.kt b/android/shared/src/org/maturita/maturita/platform/Platform.kt index afd88fa..e275434 100644 --- a/android/shared/src/org/maturita/maturita/platform/Platform.kt +++ b/android/shared/src/org/maturita/maturita/platform/Platform.kt @@ -18,8 +18,18 @@ interface Platform { /** A speech engine for listening exercises, or null when there is none. */ fun speaker(): Speaker? + + /** + * Compiles and runs a C# program. [cb] runs on the UI thread. + * Status is ok, fail, timeout or unavailable. + */ + fun runCSharp(source: String, stdin: String, cb: (CSharpRun) -> Unit) { + cb(CSharpRun("unavailable", "")) + } } +data class CSharpRun(val status: String, val output: String) + /** The subset of Android's SharedPreferences the app uses. */ interface Prefs { fun getBoolean(key: String, fallback: Boolean): Boolean diff --git a/android/shared/src/org/maturita/maturita/ui/CodeLesson.kt b/android/shared/src/org/maturita/maturita/ui/CodeLesson.kt new file mode 100644 index 0000000..c51b2ce --- /dev/null +++ b/android/shared/src/org/maturita/maturita/ui/CodeLesson.kt @@ -0,0 +1,158 @@ +package org.maturita.maturita.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.maturita.maturita.AppViewModel +import org.maturita.maturita.data.J + +/** Explanation on one side, the program and its output on the other. */ +@Composable +fun CodeLessonScreen(vm: AppViewModel, lesson: J, onSuccess: () -> Unit) { + val starter = lesson.str("code") + var code by remember(lesson.str("key")) { mutableStateOf(starter) } + var stdin by remember(lesson.str("key")) { mutableStateOf("") } + var output by remember(lesson.str("key")) { mutableStateOf("") } + var running by remember(lesson.str("key")) { mutableStateOf(false) } + + fun run() { + if (running) return + running = true + output = vm.tr("cs_running") + vm.platform.runCSharp(code, stdin) { result -> + running = false + output = when (result.status) { + "unavailable" -> vm.tr("cs_unavailable") + "timeout" -> (result.output + "\n\n" + vm.tr("cs_timeout")).trim() + else -> result.output.ifEmpty { vm.tr("cs_empty") } + } + if (result.status == "ok") onSuccess() + } + } + + DetailScaffold(vm, vm.tr(lesson.str("titleKey")), vm.tr(lesson.str("subKey"))) { inner -> + BoxWithConstraints(Modifier.fillMaxSize().padding(inner).padding(12.dp)) { + if (maxWidth >= 840.dp) { + Row(Modifier.fillMaxSize(), horizontalArrangement = Arrangement.spacedBy(12.dp)) { + Explanation(vm, lesson, Modifier.weight(0.9f).fillMaxHeight()) + EditorColumn(vm, code, { code = it }, stdin, { stdin = it }, expand = true, Modifier.weight(1.15f).fillMaxHeight()) + OutputColumn(vm, output, running, { run() }, { code = starter }, expand = true, Modifier.weight(1f).fillMaxHeight()) + } + } else { + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(12.dp)) { + Explanation(vm, lesson, Modifier.fillMaxWidth()) + EditorColumn(vm, code, { code = it }, stdin, { stdin = it }, expand = false, Modifier.fillMaxWidth()) + OutputColumn(vm, output, running, { run() }, { code = starter }, expand = false, Modifier.fillMaxWidth()) + } + } + } + } +} + +@Composable +private fun Explanation(vm: AppViewModel, lesson: J, modifier: Modifier) { + Column(modifier.verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(14.dp)) { + lesson.arr("slides").forEach { slide -> + Text(slide.str("kicker"), color = MaterialTheme.colorScheme.primary, style = MaterialTheme.typography.labelLarge) + Text(slide.str("title"), style = MaterialTheme.typography.titleLarge) + slide.strs("lines").forEach { line -> + Text("• $line", style = MaterialTheme.typography.bodyLarge) + } + slide.strOrNull("tip")?.let { tip -> + Text(tip, color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodyMedium) + } + } + } +} + +@Composable +private fun EditorColumn( + vm: AppViewModel, + code: String, + onCode: (String) -> Unit, + stdin: String, + onStdin: (String) -> Unit, + expand: Boolean, + modifier: Modifier, +) { + Column(modifier, verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text(vm.tr("cs_code"), style = MaterialTheme.typography.titleMedium) + BasicTextField( + value = code, + onValueChange = onCode, + textStyle = codeStyle().copy(color = MaterialTheme.colorScheme.onSurface), + cursorBrush = SolidColor(MaterialTheme.colorScheme.primary), + modifier = Modifier + .then(if (expand) Modifier.weight(1f) else Modifier.height(240.dp)) + .fillMaxWidth() + .background(MaterialTheme.colorScheme.surfaceContainer) + .padding(12.dp) + .verticalScroll(rememberScrollState()), + ) + Text(vm.tr("cs_stdin"), style = MaterialTheme.typography.labelLarge, color = MaterialTheme.colorScheme.onSurfaceVariant) + BasicTextField( + value = stdin, + onValueChange = onStdin, + textStyle = codeStyle().copy(color = MaterialTheme.colorScheme.onSurface), + cursorBrush = SolidColor(MaterialTheme.colorScheme.primary), + modifier = Modifier.fillMaxWidth().background(MaterialTheme.colorScheme.surfaceContainer).padding(12.dp), + ) + } +} + +@Composable +private fun OutputColumn( + vm: AppViewModel, + output: String, + running: Boolean, + onRun: () -> Unit, + onReset: () -> Unit, + expand: Boolean, + modifier: Modifier, +) { + Column(modifier, verticalArrangement = Arrangement.spacedBy(8.dp)) { + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + FilledTonalButton(onClick = onRun, enabled = !running) { Text(vm.tr("cs_run")) } + OutlinedButton(onClick = onReset, enabled = !running) { Text(vm.tr("cs_reset")) } + } + Text(vm.tr("cs_output"), style = MaterialTheme.typography.titleMedium) + Text( + output.ifEmpty { vm.tr("cs_empty") }, + style = codeStyle(), + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier + .then(if (expand) Modifier.weight(1f) else Modifier.height(180.dp)) + .fillMaxWidth() + .background(MaterialTheme.colorScheme.surfaceContainer) + .padding(12.dp) + .verticalScroll(rememberScrollState()), + ) + } +} + +private fun codeStyle() = TextStyle(fontFamily = FontFamily.Monospace, fontSize = 14.sp, lineHeight = 20.sp) diff --git a/android/shared/src/org/maturita/maturita/ui/Screens.kt b/android/shared/src/org/maturita/maturita/ui/Screens.kt index bb4be71..761a024 100644 --- a/android/shared/src/org/maturita/maturita/ui/Screens.kt +++ b/android/shared/src/org/maturita/maturita/ui/Screens.kt @@ -36,6 +36,7 @@ import androidx.compose.material.icons.filled.Groups import androidx.compose.material.icons.filled.Lock import androidx.compose.material.icons.filled.Quiz import androidx.compose.material.icons.filled.Science +import androidx.compose.material.icons.filled.Terminal import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.outlined.Lightbulb import androidx.compose.material3.Card @@ -227,6 +228,7 @@ private fun SubjectIcon(icon: String, open: Boolean) { "bolt" -> Icon(Icons.Filled.Bolt, null, tint = tint, modifier = Modifier.size(26.dp)) "function" -> Icon(Icons.Filled.Functions, null, tint = tint, modifier = Modifier.size(26.dp)) "ruler" -> Icon(Icons.Filled.Architecture, null, tint = tint, modifier = Modifier.size(26.dp)) + "code" -> Icon(Icons.Filled.Terminal, null, tint = tint, modifier = Modifier.size(26.dp)) else -> Icon(Icons.Filled.Lock, null, tint = tint) } } @@ -350,6 +352,13 @@ private fun subjectSum(vm: AppViewModel, s: J): ProgressSum { vm.content.fyz3.count { vm.progress.fyz3Done(it.int("id")) } + vm.content.fyz4.count { vm.progress.fyz4Done(it.int("id")) } } + "csyears", "csmap", "cs2map", "cs3map", "cs4map" -> { + sum.totalEx = vm.content.cs.size + vm.content.cs2.size + vm.content.cs3.size + vm.content.cs4.size + sum.doneEx = vm.content.cs.count { vm.progress.csDone(it.int("id")) } + + vm.content.cs2.count { vm.progress.cs2Done(it.int("id")) } + + vm.content.cs3.count { vm.progress.cs3Done(it.int("id")) } + + vm.content.cs4.count { vm.progress.cs4Done(it.int("id")) } + } "tgyears", "tgmap", "tg2map", "tg3map", "tg4map" -> { sum.totalEx = vm.content.tg.size + vm.content.tg2.size + vm.content.tg3.size + vm.content.tg4.size sum.doneEx = vm.content.tg.count { vm.progress.tgDone(it.int("id")) } + @@ -679,6 +688,23 @@ private fun buildSearch(vm: AppViewModel): List { add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "tg4unit${l.int("id")}", extra = "autocad") add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "tg4ex${l.int("id")}") } + add(vm.tr("Programování"), vm.tr("search_lesson"), "csyears", extra = "csharp c#") + add(vm.tr("cs_year1"), vm.tr("search_lesson"), "csmap", extra = "rocnik") + add(vm.tr("cs_year2"), vm.tr("search_lesson"), "cs2map", extra = "rocnik") + add(vm.tr("cs_year3"), vm.tr("search_lesson"), "cs3map", extra = "rocnik") + add(vm.tr("cs_year4"), vm.tr("search_lesson"), "cs4map", extra = "rocnik") + vm.content.cs.forEach { l -> + add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "csunit${l.int("id")}", extra = "csharp") + } + vm.content.cs2.forEach { l -> + add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "cs2unit${l.int("id")}", extra = "csharp") + } + vm.content.cs3.forEach { l -> + add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "cs3unit${l.int("id")}", extra = "csharp") + } + vm.content.cs4.forEach { l -> + add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "cs4unit${l.int("id")}", extra = "csharp") + } add(vm.tr("Chemie"), vm.tr("search_lesson"), "chemmap", extra = "chemie") add(vm.tr("Biologie"), vm.tr("search_lesson"), "biomap", extra = "biologie") vm.content.chem.forEach { l -> @@ -878,6 +904,20 @@ fun MatYearsScreen(vm: AppViewModel) { } } +@Composable +fun CsYearsScreen(vm: AppViewModel) { + DetailScaffold(vm, vm.tr("Programování"), vm.tr("cs_years_sub")) { inner -> + LazyColumn(contentPadding = PaddingValues(top = inner.calculateTopPadding(), bottom = inner.calculateBottomPadding() + 16.dp)) { + item { + NavRow(title = vm.tr("cs_year1"), subtitle = vm.tr("cs_sub"), leading = { Text("1.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.CsMap) } + NavRow(title = vm.tr("cs_year2"), subtitle = vm.tr("cs2_sub"), leading = { Text("2.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.Cs2Map) } + NavRow(title = vm.tr("cs_year3"), subtitle = vm.tr("cs3_sub"), leading = { Text("3.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.Cs3Map) } + NavRow(title = vm.tr("cs_year4"), subtitle = vm.tr("cs4_sub"), leading = { Text("4.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.Cs4Map) } + } + } + } +} + @Composable fun TgYearsScreen(vm: AppViewModel) { DetailScaffold(vm, vm.tr("Technická grafika"), vm.tr("tg_years_sub")) { inner -> diff --git a/content/README.md b/content/README.md index da047be..f61e3a9 100644 --- a/content/README.md +++ b/content/README.md @@ -18,7 +18,7 @@ Skript vypíše počet lekcí, nebo chyby s cestou a číslem řádku | Složka | Obsah | |---|---| -| `pocitacove-site/`, `technicke-vybaveni/`, `technicka-grafika/`, `obcanska-nauka/`, `fyzika/` | `N-rocnik/`, v nich lekce | +| `pocitacove-site/`, `technicke-vybaveni/`, `technicka-grafika/`, `programovani/`, `obcanska-nauka/`, `fyzika/` | `N-rocnik/`, v nich lekce | | `prirodni-vedy/chemie`, `prirodni-vedy/biologie` | lekce | | `cestina/literatura/N-rocnik` | lekce literatury | | `cestina/mluvnice` | cvičení z mluvnice | diff --git a/content/aplikace/novinky.txt b/content/aplikace/novinky.txt index fb941b4..827a841 100644 --- a/content/aplikace/novinky.txt +++ b/content/aplikace/novinky.txt @@ -5,6 +5,12 @@ # cs: bullet # en: bullet +--- 2026-10-11 +cs: Programování: čtyři ročníky C#, od výpisu po LINQ a async +cs: Kód se píše v lekci a hned vedle se spustí +en: Programming: four years of C#, from output to LINQ and async +en: You write the code in the lesson and run it right beside the text + --- 2026-10-11 cs: Technická grafika: čtyři ročníky AutoCADu, od výkresu po 3D cs: V každé lekci je postup, zadání s rozměry a kvíz diff --git a/content/aplikace/predmety.md b/content/aplikace/predmety.md index 9266e4e..9ecbd76 100644 --- a/content/aplikace/predmety.md +++ b/content/aplikace/predmety.md @@ -13,5 +13,5 @@ | Základy přírodních věd | Základy přírodních věd | Fundamentals of Natural Sciences | flask | scimap | ano | | Technická grafika | Technická grafika | Technical Graphics | ruler | tgyears | ano | | Prezentační grafika | Prezentační grafika | Presentation Graphics | lock | | ne | -| Programování | Programování | Programming | lock | | ne | +| Programování | Programování | Programming | code | csyears | ano | | Programové vybavení | Programové vybavení | Software | lock | | ne | diff --git a/content/aplikace/texty.md b/content/aplikace/texty.md index d445708..3668457 100644 --- a/content/aplikace/texty.md +++ b/content/aplikace/texty.md @@ -243,6 +243,29 @@ | tg3_sub | Pohledy, řezy, bloky a tisk. | Views, sections, blocks and plotting. | | tg4_sub | 3D model a výkres k maturitě. | A 3D model and the exam drawing. | +# Programování + +| Klíč | Česky | Anglicky | +|---|---|---| +| cs_years_sub | C# od prvního výpisu po pokročilejší nástroje. Kód se píše a spouští vedle výkladu. | C# from the first line of output to the later tools. You write the code and run it beside the lesson. | +| cs_year1 | 1. ročník | Year 1 | +| cs_year2 | 2. ročník | Year 2 | +| cs_year3 | 3. ročník | Year 3 | +| cs_year4 | 4. ročník | Year 4 | +| cs_sub | Výpis, proměnné, typy, operátory a řetězce. | Output, variables, types, operators and strings. | +| cs2_sub | Podmínky, cykly, pole a metody. | Conditions, loops, arrays and methods. | +| cs3_sub | Třídy, dědičnost, rozhraní a výčty. | Classes, inheritance, interfaces and enums. | +| cs4_sub | Výjimky, kolekce, LINQ, soubory a async. | Exceptions, collections, LINQ, files and async. | +| cs_code | Kód | Code | +| cs_output | Výstup | Output | +| cs_stdin | Vstup pro Console.ReadLine | Input for Console.ReadLine | +| cs_run | Spustit | Run | +| cs_reset | Obnovit ukázku | Reset example | +| cs_running | Překládám… | Compiling… | +| cs_empty | Zatím nic. Upravte kód a spusťte ho. | Nothing yet. Edit the code and run it. | +| cs_timeout | Program běžel příliš dlouho a byl zastaven. | The program ran too long and was stopped. | +| cs_unavailable | Tady C# nespustíte. Na počítači s nainstalovaným .NET se výstup objeví vedle kódu. | C# cannot run here. On a computer with .NET installed the output appears beside the code. | + # Angličtina a němčina | Klíč | Česky | Anglicky | diff --git a/content/programovani/1-rocnik/01-ahoj-svete.en.md b/content/programovani/1-rocnik/01-ahoj-svete.en.md new file mode 100644 index 0000000..ee64874 --- /dev/null +++ b/content/programovani/1-rocnik/01-ahoj-svete.en.md @@ -0,0 +1,4 @@ +--- +nazev: Hello, world +popis: The first program: writing to the console. +--- diff --git a/content/programovani/1-rocnik/01-ahoj-svete.md b/content/programovani/1-rocnik/01-ahoj-svete.md new file mode 100644 index 0000000..c150fe8 --- /dev/null +++ b/content/programovani/1-rocnik/01-ahoj-svete.md @@ -0,0 +1,42 @@ +--- +id: cs-1-ahoj-svete +nazev: Ahoj světe +popis: První program: výpis na konzoli. +--- + +# Výklad + +## Výpis +*WriteLine* + +- Console.WriteLine vypíše text a odřádkuje. +- Console.Write vypíše text a zůstane na stejném řádku. +- Text se píše do uvozovek. Středník ukončuje příkaz. + +> Tip: Bez středníku se program nepřeloží. + +## Krátký a dlouhý zápis +*Main* + +- Starší učebnice mají třídu Program a metodu Main. +- Nový projekt stejný program zapíše několika řádky bez třídy. +- Obojí spustí stejný výpis. V lekcích používáme krátký zápis. + +> Tip: Krátký zápis je pořád C#. Není to jiný jazyk. + +## Vyzkoušejte +*Editor* + +- Vpravo je kód. Upravte ho a stiskněte Spustit. +- Výstup se objeví hned vedle. +- Obnovit ukázku vrátí původní program. + +> Tip: Změňte text v uvozovkách a spusťte znovu. + +# Kód + +``` +Console.WriteLine("Ahoj světe"); +Console.Write("Toto je "); +Console.WriteLine("C#"); +``` diff --git a/content/programovani/1-rocnik/02-komentare.en.md b/content/programovani/1-rocnik/02-komentare.en.md new file mode 100644 index 0000000..c80ebee --- /dev/null +++ b/content/programovani/1-rocnik/02-komentare.en.md @@ -0,0 +1,4 @@ +--- +nazev: Comments +popis: Notes the program does not run. +--- diff --git a/content/programovani/1-rocnik/02-komentare.md b/content/programovani/1-rocnik/02-komentare.md new file mode 100644 index 0000000..a54c0b2 --- /dev/null +++ b/content/programovani/1-rocnik/02-komentare.md @@ -0,0 +1,35 @@ +--- +id: cs-1-komentare +nazev: Komentáře +popis: Poznámky, které se nespouštějí. +--- + +# Výklad + +## Jednořádkový +*//* + +- Vše za // na stejném řádku počítač přeskočí. +- Komentář vysvětluje, proč tam řádek je, ne co je vidět. +- Kód bez komentáře pořád běží stejně. + +> Tip: Komentář není výpis. Na konzoli se neobjeví. + +## Více řádků +*/* */* + +- Blok začíná /* a končí */. +- Hodí se, když chcete dočasně vypnout několik řádků. +- Zapomenuté */ rozbije zbytek souboru. + +> Tip: Nevnořujte blokové komentáře do sebe. + +# Kód + +``` +// Tohle se nespustí. +Console.WriteLine("Tento řádek ano"); +/* +Console.WriteLine("A tento ne"); +*/ +``` diff --git a/content/programovani/1-rocnik/03-promenne.en.md b/content/programovani/1-rocnik/03-promenne.en.md new file mode 100644 index 0000000..e302009 --- /dev/null +++ b/content/programovani/1-rocnik/03-promenne.en.md @@ -0,0 +1,4 @@ +--- +nazev: Variables +popis: A named value you can change. +--- diff --git a/content/programovani/1-rocnik/03-promenne.md b/content/programovani/1-rocnik/03-promenne.md new file mode 100644 index 0000000..c17316f --- /dev/null +++ b/content/programovani/1-rocnik/03-promenne.md @@ -0,0 +1,36 @@ +--- +id: cs-1-promenne +nazev: Proměnné +popis: Pojmenovaná hodnota, kterou lze změnit. +--- + +# Výklad + +## Deklarace +*typ jméno* + +- Nejdřív typ, pak jméno, pak hodnota: int vek = 17; +- Jméno začíná písmenem. Mezery v něm být nesmí. +- Do stejné proměnné lze později uložit novou hodnotu stejného typu. + +> Tip: int drží celé číslo. string drží text. + +## Čtení +*jméno* + +- Jméno bez uvozovek je proměnná. Uvozovky by z něj udělaly text. +- WriteLine umí vypsat proměnnou i text spojený plusem. +- Hodnota se dosadí až při běhu, ne při psaní. + +> Tip: Změňte vek na 18 a spusťte znovu. + +# Kód + +``` +string jmeno = "Anna"; +int vek = 17; +jmeno = "Eva"; +Console.WriteLine(jmeno); +Console.WriteLine(vek); +Console.WriteLine(jmeno + " má " + vek + " let"); +``` diff --git a/content/programovani/1-rocnik/04-typy.en.md b/content/programovani/1-rocnik/04-typy.en.md new file mode 100644 index 0000000..0973087 --- /dev/null +++ b/content/programovani/1-rocnik/04-typy.en.md @@ -0,0 +1,4 @@ +--- +nazev: Data types +popis: int, double, bool, char and string. +--- diff --git a/content/programovani/1-rocnik/04-typy.md b/content/programovani/1-rocnik/04-typy.md new file mode 100644 index 0000000..bda4cec --- /dev/null +++ b/content/programovani/1-rocnik/04-typy.md @@ -0,0 +1,40 @@ +--- +id: cs-1-typy +nazev: Datové typy +popis: int, double, bool, char a string. +--- + +# Výklad + +## Čísla +*int a double* + +- int je celé číslo: 42. +- double je číslo s desetinnou tečkou: 3.14. +- V kódu je desetinná tečka, ne čárka. + +> Tip: 3,14 by C# přečetl jako dva údaje oddělené čárkou. + +## Text a ano/ne +*string, char, bool* + +- string je text v uvozovkách. +- char je jeden znak v apostrofech: 'A'. +- bool je jen true nebo false. + +> Tip: Apostrof a uvozovky nejsou totéž. 'A' je znak, "A" je text. + +# Kód + +``` +int cele = 42; +double desetinne = 3.14; +bool prsi = false; +char pismeno = 'A'; +string text = "ahoj"; +Console.WriteLine(cele); +Console.WriteLine(desetinne); +Console.WriteLine(prsi); +Console.WriteLine(pismeno); +Console.WriteLine(text); +``` diff --git a/content/programovani/1-rocnik/05-prevody.en.md b/content/programovani/1-rocnik/05-prevody.en.md new file mode 100644 index 0000000..291c616 --- /dev/null +++ b/content/programovani/1-rocnik/05-prevody.en.md @@ -0,0 +1,4 @@ +--- +nazev: Type conversion +popis: From double to int and from text to a number. +--- diff --git a/content/programovani/1-rocnik/05-prevody.md b/content/programovani/1-rocnik/05-prevody.md new file mode 100644 index 0000000..3dd2e2a --- /dev/null +++ b/content/programovani/1-rocnik/05-prevody.md @@ -0,0 +1,36 @@ +--- +id: cs-1-prevody +nazev: Převody typů +popis: Z double na int a z textu na číslo. +--- + +# Výklad + +## Ořez desetin +*(int)* + +- (int)3.9 je 3. Desetinná část se odřízne, nezaokrouhlí. +- Opačně int do double ztrátu nemá: 3 se stane 3.0. +- Převod napište před hodnotu do závorky. + +> Tip: Když potřebujete zaokrouhlit, (int) na to není. + +## Text na číslo +*Parse* + +- int.Parse("20") udělá z textu číslo 20. +- Když text číslo není, program spadne. To později chytíme výjimkou. +- Číslo na text udělá ToString nebo zápis do řetězce. + +> Tip: Vstup z konzole je vždy text, i když uživatel napíše číslici. + +# Kód + +``` +double cena = 19.9; +int cele = (int)cena; +Console.WriteLine(cele); +string vstup = "20"; +int cislo = int.Parse(vstup); +Console.WriteLine(cislo + 1); +``` diff --git a/content/programovani/1-rocnik/06-operatory.en.md b/content/programovani/1-rocnik/06-operatory.en.md new file mode 100644 index 0000000..e4c6901 --- /dev/null +++ b/content/programovani/1-rocnik/06-operatory.en.md @@ -0,0 +1,4 @@ +--- +nazev: Operators +popis: Arithmetic, remainder, comparison and logic. +--- diff --git a/content/programovani/1-rocnik/06-operatory.md b/content/programovani/1-rocnik/06-operatory.md new file mode 100644 index 0000000..b7c962a --- /dev/null +++ b/content/programovani/1-rocnik/06-operatory.md @@ -0,0 +1,37 @@ +--- +id: cs-1-operatory +nazev: Operátory +popis: Počítání, zbytek, porovnání a a zároveň. +--- + +# Výklad + +## Počet +*+ - * / %* + +- Sčítání, odčítání a násobení fungují, jak čekáte. +- Dělení dvou int je celočíselné: 17 / 5 je 3. +- % je zbytek po dělení: 17 % 5 je 2. + +> Tip: Když chcete desetinný podíl, jedno číslo udělejte double: 17 / 5.0. + +## Porovnání +*true a false* + +- == je rovnost. = je uložení. Nejsou totéž. +- && znamená a zároveň. || znamená nebo. +- ! otočí bool: !true je false. + +> Tip: Jedno = v podmínce je častá chyba. Podmínka chce ==. + +# Kód + +``` +int a = 17; +int b = 5; +Console.WriteLine(a + b); +Console.WriteLine(a / b); +Console.WriteLine(a % b); +Console.WriteLine(a > b); +Console.WriteLine(a > 10 && b < 10); +``` diff --git a/content/programovani/1-rocnik/07-retezce.en.md b/content/programovani/1-rocnik/07-retezce.en.md new file mode 100644 index 0000000..7229ab2 --- /dev/null +++ b/content/programovani/1-rocnik/07-retezce.en.md @@ -0,0 +1,4 @@ +--- +nazev: Strings +popis: Joining text, length and a slice. +--- diff --git a/content/programovani/1-rocnik/07-retezce.md b/content/programovani/1-rocnik/07-retezce.md new file mode 100644 index 0000000..044c950 --- /dev/null +++ b/content/programovani/1-rocnik/07-retezce.md @@ -0,0 +1,36 @@ +--- +id: cs-1-retezce +nazev: Řetězce +popis: Skládání textu, délka a výřez. +--- + +# Výklad + +## Interpolace +*$* + +- $"Ahoj, {jmeno}" dosadí hodnotu do složených závorek. +- Je přehlednější než spojování plusem. +- Do závorek může i výpočet: {vek + 1}. + +> Tip: Bez dolaru před uvozovkami se {jmeno} vypíše doslova. + +## Metody textu +*Length* + +- Length je počet znaků. +- ToUpper vrátí velká písmena a původní text nezmění. +- Substring(0, 2) vezme dva znaky od začátku. + +> Tip: První znak má index 0, ne 1. + +# Kód + +``` +string jmeno = "Anna"; +int vek = 17; +Console.WriteLine($"Ahoj, {jmeno}. Za rok ti bude {vek + 1}."); +Console.WriteLine(jmeno.Length); +Console.WriteLine(jmeno.ToUpper()); +Console.WriteLine(jmeno.Substring(0, 2)); +``` diff --git a/content/programovani/1-rocnik/08-vstup.en.md b/content/programovani/1-rocnik/08-vstup.en.md new file mode 100644 index 0000000..aaa9f2d --- /dev/null +++ b/content/programovani/1-rocnik/08-vstup.en.md @@ -0,0 +1,4 @@ +--- +nazev: Console input +popis: Console.ReadLine reads a line. +--- diff --git a/content/programovani/1-rocnik/08-vstup.md b/content/programovani/1-rocnik/08-vstup.md new file mode 100644 index 0000000..ea4349c --- /dev/null +++ b/content/programovani/1-rocnik/08-vstup.md @@ -0,0 +1,34 @@ +--- +id: cs-1-vstup +nazev: Vstup z konzole +popis: Console.ReadLine přečte řádek. +--- + +# Výklad + +## ReadLine +*text* + +- ReadLine počká na řádek a vrátí ho jako string. +- V této lekci řádek napište do pole Vstup a pak Spustit. +- Každé další ReadLine vezme další řádek vstupu. + +> Tip: Když vstup necháte prázdný, ReadLine dostane konec a program může spadnout. + +## Číslo ze vstupu +*Parse* + +- ReadLine nikdy nevrací int. Vrací text. +- int.Parse z něj číslo udělá. +- Napište do vstupu třeba 17. + +> Tip: Zkuste ve vstupu Anna a uvidíte chybu. Parse čeká číslo. + +# Kód + +``` +Console.WriteLine("Kolik ti je?"); +string radek = Console.ReadLine(); +int vek = int.Parse(radek); +Console.WriteLine($"Za rok ti bude {vek + 1}."); +``` diff --git a/content/programovani/2-rocnik/01-if.en.md b/content/programovani/2-rocnik/01-if.en.md new file mode 100644 index 0000000..177d905 --- /dev/null +++ b/content/programovani/2-rocnik/01-if.en.md @@ -0,0 +1,4 @@ +--- +nazev: If and else +popis: A branch that runs only sometimes. +--- diff --git a/content/programovani/2-rocnik/01-if.md b/content/programovani/2-rocnik/01-if.md new file mode 100644 index 0000000..c235df4 --- /dev/null +++ b/content/programovani/2-rocnik/01-if.md @@ -0,0 +1,43 @@ +--- +id: cs-2-if +nazev: If a else +popis: Větev, která se spustí jen někdy. +--- + +# Výklad + +## Podmínka +*if* + +- if (vek >= 18) spustí blok ve složených závorkách, jen když je podmínka true. +- else je větev pro opačný případ. +- else if přidá další podmínku mezi ně. + +> Tip: Za if není středník. Středník by podmínku oddělil od bloku. + +## Porovnání +*==* + +- Čísla porovnávejte >, <, >=, <=, == a !=. +- Text se porovnává ==. "Anna" == "anna" je false. +- Do podmínky patří bool, ne přiřazení. + +> Tip: Změňte vek a sledujte, která věta se vypíše. + +# Kód + +``` +int vek = 17; +if (vek >= 18) +{ + Console.WriteLine("plnoletý"); +} +else if (vek >= 15) +{ + Console.WriteLine("téměř"); +} +else +{ + Console.WriteLine("ještě ne"); +} +``` diff --git a/content/programovani/2-rocnik/02-switch.en.md b/content/programovani/2-rocnik/02-switch.en.md new file mode 100644 index 0000000..54377bb --- /dev/null +++ b/content/programovani/2-rocnik/02-switch.en.md @@ -0,0 +1,4 @@ +--- +nazev: Switch +popis: Several branches for one value. +--- diff --git a/content/programovani/2-rocnik/02-switch.md b/content/programovani/2-rocnik/02-switch.md new file mode 100644 index 0000000..d46a17c --- /dev/null +++ b/content/programovani/2-rocnik/02-switch.md @@ -0,0 +1,43 @@ +--- +id: cs-2-switch +nazev: Switch +popis: Víc větví podle jedné hodnoty. +--- + +# Výklad + +## Větve +*case* + +- switch vybere case, který se rovná hodnotě. +- break ukončí větev. Bez něj se překlad v klasickém zápisu zastaví. +- default je větev, když nesedí žádný case. + +> Tip: Na známku 1 až 5 se switch hodí víc než řada else if. + +## Nový zápis +*šipka* + +- case 1 => "výborně" je krátká větev bez break. +- Výraz switch umí hodnotu rovnou vrátit. +- V lekci je klasický zápis, ať je vidět break. + +> Tip: Přidejte case 5 a spusťte se znamkou 5. + +# Kód + +``` +int znamka = 2; +switch (znamka) +{ + case 1: + Console.WriteLine("výborně"); + break; + case 2: + Console.WriteLine("chvalitebně"); + break; + default: + Console.WriteLine("jiná známka"); + break; +} +``` diff --git a/content/programovani/2-rocnik/03-while.en.md b/content/programovani/2-rocnik/03-while.en.md new file mode 100644 index 0000000..cc1f71b --- /dev/null +++ b/content/programovani/2-rocnik/03-while.en.md @@ -0,0 +1,4 @@ +--- +nazev: While +popis: Repeat while a condition holds. +--- diff --git a/content/programovani/2-rocnik/03-while.md b/content/programovani/2-rocnik/03-while.md new file mode 100644 index 0000000..37455ca --- /dev/null +++ b/content/programovani/2-rocnik/03-while.md @@ -0,0 +1,38 @@ +--- +id: cs-2-while +nazev: While +popis: Opakování, dokud podmínka platí. +--- + +# Výklad + +## Smyčka +*while* + +- Tělo se opakuje, dokud je podmínka true. +- Když podmínku nikdy nezměníte, smyčka neskončí. +- Aplikace dlouhý běh po chvíli zastaví. + +> Tip: i = i + 1 nebo i++ posune čítač. Bez toho se i nemění. + +## Součet +*1 až 4* + +- Začněte součtem 0 a přičítejte i. +- 1+2+3+4 je 10. +- Podmínka i <= 4 pustí i číslo 4 dovnitř. + +> Tip: Změňte 4 na 10. Součet 1 až 10 je 55. + +# Kód + +``` +int i = 1; +int soucet = 0; +while (i <= 4) +{ + soucet = soucet + i; + i++; +} +Console.WriteLine(soucet); +``` diff --git a/content/programovani/2-rocnik/04-for.en.md b/content/programovani/2-rocnik/04-for.en.md new file mode 100644 index 0000000..757f660 --- /dev/null +++ b/content/programovani/2-rocnik/04-for.en.md @@ -0,0 +1,4 @@ +--- +nazev: For +popis: A loop with its counter in one place. +--- diff --git a/content/programovani/2-rocnik/04-for.md b/content/programovani/2-rocnik/04-for.md new file mode 100644 index 0000000..2e6afac --- /dev/null +++ b/content/programovani/2-rocnik/04-for.md @@ -0,0 +1,34 @@ +--- +id: cs-2-for +nazev: For +popis: Smyčka s čítačem na jednom místě. +--- + +# Výklad + +## Tři části +*start; podmínka; krok* + +- for (int i = 0; i < 3; i++) má začátek, podmínku a krok. +- i < 3 pustí 0, 1 a 2. Trojku už ne. +- Krok i++ se provede až po těle. + +> Tip: Když potřebujete přesný počet opakování, for je přehlednější než while. + +## Násobilka +*výpis* + +- V těle můžete počítat i * 2. +- WriteLine v těle vypíše jeden řádek na každé opakování. +- Čítač nemusí začínat nulou. + +> Tip: Přepište podmínku na i <= 5 a sledujte další řádek. + +# Kód + +``` +for (int i = 1; i <= 3; i++) +{ + Console.WriteLine($"{i} krát 2 je {i * 2}"); +} +``` diff --git a/content/programovani/2-rocnik/05-pole.en.md b/content/programovani/2-rocnik/05-pole.en.md new file mode 100644 index 0000000..a4664a1 --- /dev/null +++ b/content/programovani/2-rocnik/05-pole.en.md @@ -0,0 +1,4 @@ +--- +nazev: Arrays +popis: Several values of one type under one name. +--- diff --git a/content/programovani/2-rocnik/05-pole.md b/content/programovani/2-rocnik/05-pole.md new file mode 100644 index 0000000..4653b09 --- /dev/null +++ b/content/programovani/2-rocnik/05-pole.md @@ -0,0 +1,35 @@ +--- +id: cs-2-pole +nazev: Pole +popis: Víc hodnot stejného typu pod jedním jménem. +--- + +# Výklad + +## Index +*od nuly* + +- new int[] { 2, 4, 6 } je pole tří čísel. +- První prvek je znamky[0], poslední znamky[2]. +- Length je počet prvků, ne index posledního. + +> Tip: znamky[3] u pole délky 3 spadne. Poslední index je 2. + +## Zápis +*prvku* + +- Do prvku se dá uložit nová hodnota: znamky[1] = 5. +- Ostatní prvky zůstanou. +- Pole má pevnou délku. Přidat čtvrtý prvek nejde, na to je později List. + +> Tip: Vyměňte první známku a spusťte znovu. + +# Kód + +``` +int[] znamky = { 1, 3, 2 }; +Console.WriteLine(znamky[0]); +Console.WriteLine(znamky.Length); +znamky[1] = 5; +Console.WriteLine(znamky[1]); +``` diff --git a/content/programovani/2-rocnik/06-foreach.en.md b/content/programovani/2-rocnik/06-foreach.en.md new file mode 100644 index 0000000..3a4186e --- /dev/null +++ b/content/programovani/2-rocnik/06-foreach.en.md @@ -0,0 +1,4 @@ +--- +nazev: Foreach +popis: Visit every item without an index. +--- diff --git a/content/programovani/2-rocnik/06-foreach.md b/content/programovani/2-rocnik/06-foreach.md new file mode 100644 index 0000000..1c0a779 --- /dev/null +++ b/content/programovani/2-rocnik/06-foreach.md @@ -0,0 +1,35 @@ +--- +id: cs-2-foreach +nazev: Foreach +popis: Projít každý prvek bez indexu. +--- + +# Výklad + +## Projítí +*foreach* + +- foreach (int z in znamky) vezme prvky od prvního po poslední. +- z je kopie hodnoty. U int se zápisem do z pole nezmění. +- Když index nepotřebujete, foreach je kratší než for. + +> Tip: Součet ve smyčce přičítá aktuální z. + +## Texty +*string[]* + +- Pole může být i ze stringů. +- Stejný foreach funguje pro oba typy, jen se liší typ prvku. +- Pořadí je pořadí v závorce při vytvoření. + +> Tip: Přidejte do pole třetí jméno. + +# Kód + +``` +string[] jmena = { "Anna", "Eva", "Jan" }; +foreach (string jmeno in jmena) +{ + Console.WriteLine(jmeno); +} +``` diff --git a/content/programovani/2-rocnik/07-metody.en.md b/content/programovani/2-rocnik/07-metody.en.md new file mode 100644 index 0000000..3b6fe65 --- /dev/null +++ b/content/programovani/2-rocnik/07-metody.en.md @@ -0,0 +1,4 @@ +--- +nazev: Methods +popis: A named piece of the program you can call. +--- diff --git a/content/programovani/2-rocnik/07-metody.md b/content/programovani/2-rocnik/07-metody.md new file mode 100644 index 0000000..2c0164a --- /dev/null +++ b/content/programovani/2-rocnik/07-metody.md @@ -0,0 +1,37 @@ +--- +id: cs-2-metody +nazev: Metody +popis: Pojmenovaný kus programu, který lze zavolat. +--- + +# Výklad + +## Volání +*jméno()* + +- static void Pozdrav() je metoda bez výsledku. +- Zavolá se Pozdrav(); včetně závorek. +- Bez závorek by to bylo jen jméno, ne volání. + +> Tip: Metodu lze zavolat víckrát a pokaždé udělá totéž. + +## Proč +*jednou* + +- Kód, který se opakuje, patří do metody. +- Jméno metody říká, co dělá: Pozdrav, ne Metoda1. +- void znamená, že nevrací hodnotu. + +> Tip: Přidejte druhé volání Pozdrav(). + +# Kód + +``` +Pozdrav(); +Pozdrav(); + +static void Pozdrav() +{ + Console.WriteLine("Ahoj"); +} +``` diff --git a/content/programovani/2-rocnik/08-navrat.en.md b/content/programovani/2-rocnik/08-navrat.en.md new file mode 100644 index 0000000..8199f32 --- /dev/null +++ b/content/programovani/2-rocnik/08-navrat.en.md @@ -0,0 +1,4 @@ +--- +nazev: Parameters and return +popis: A method receives values and can return one. +--- diff --git a/content/programovani/2-rocnik/08-navrat.md b/content/programovani/2-rocnik/08-navrat.md new file mode 100644 index 0000000..2a9f1cc --- /dev/null +++ b/content/programovani/2-rocnik/08-navrat.md @@ -0,0 +1,38 @@ +--- +id: cs-2-navrat +nazev: Parametry a návrat +popis: Metoda dostane hodnoty a může jednu vrátit. +--- + +# Výklad + +## Parametr +*vstup* + +- int a v závorce je parametr. Volající dodá argument. +- Součet(2, 5) uloží 2 do a a 5 do b. +- Jména parametrů platí jen uvnitř metody. + +> Tip: Změňte argumenty a výsledek se změní, metoda ne. + +## return +*výstup* + +- int před jménem je typ výsledku. +- return hodnotu vrátí a metodu ukončí. +- Volající si výsledek může uložit: int s = Soucet(2, 5); + +> Tip: return 0 omylem na začátku by zbytek metody nespustil. + +# Kód + +``` +int s = Soucet(2, 5); +Console.WriteLine(s); +Console.WriteLine(Soucet(10, 1)); + +static int Soucet(int a, int b) +{ + return a + b; +} +``` diff --git a/content/programovani/3-rocnik/01-trida.en.md b/content/programovani/3-rocnik/01-trida.en.md new file mode 100644 index 0000000..1ac6f34 --- /dev/null +++ b/content/programovani/3-rocnik/01-trida.en.md @@ -0,0 +1,4 @@ +--- +nazev: Class and object +popis: Your own type and one concrete piece of it. +--- diff --git a/content/programovani/3-rocnik/01-trida.md b/content/programovani/3-rocnik/01-trida.md new file mode 100644 index 0000000..54fc345 --- /dev/null +++ b/content/programovani/3-rocnik/01-trida.md @@ -0,0 +1,40 @@ +--- +id: cs-3-trida +nazev: Třída a objekt +popis: Vlastní typ a jeho konkrétní kus. +--- + +# Výklad + +## Třída +*návod* + +- class Zak popisuje, co žák má: jméno a věk. +- new Zak() vytvoří objekt, jeden konkrétní kus. +- Dva new jsou dva objekty. Změna jednoho druhý nemění. + +> Tip: Třída sama o sobě není žák. Je to popis. + +## Pole objektu +*tečka* + +- z.Jmeno se čte a zapisuje přes tečku. +- Na začátek dáme veřejná pole, ať je vidět, komu patří. +- Později je schováme za vlastnost. + +> Tip: Vytvořte druhého žáka a vypište obě jména. + +# Kód + +``` +Zak z = new Zak(); +z.Jmeno = "Anna"; +z.Vek = 17; +Console.WriteLine($"{z.Jmeno}, {z.Vek}"); + +class Zak +{ + public string Jmeno; + public int Vek; +} +``` diff --git a/content/programovani/3-rocnik/02-konstruktor.en.md b/content/programovani/3-rocnik/02-konstruktor.en.md new file mode 100644 index 0000000..22b38d1 --- /dev/null +++ b/content/programovani/3-rocnik/02-konstruktor.en.md @@ -0,0 +1,4 @@ +--- +nazev: Constructor +popis: A method that fills the object immediately. +--- diff --git a/content/programovani/3-rocnik/02-konstruktor.md b/content/programovani/3-rocnik/02-konstruktor.md new file mode 100644 index 0000000..dcd171e --- /dev/null +++ b/content/programovani/3-rocnik/02-konstruktor.md @@ -0,0 +1,44 @@ +--- +id: cs-3-konstruktor +nazev: Konstruktor +popis: Metoda, která objekt hned naplní. +--- + +# Výklad + +## Jméno třídy +*new Zak* + +- Konstruktor se jmenuje jako třída a nemá návratový typ. +- new Zak("Anna", 17) ho zavolá. +- Prázdný new Zak() přestane fungovat, jakmile napíšete vlastní konstruktor. + +> Tip: Hodnoty patří do new, ne až na další řádky. + +## this +*tento objekt* + +- this.Jmeno je pole objektu. Jmeno bez this by v konstruktoru byl parametr. +- Když se jmenují stejně, this je nutný. +- Mimo konstruktor this často není potřeba. + +> Tip: Přidejte do výpisu věk z konstruktoru. + +# Kód + +``` +Zak z = new Zak("Anna", 17); +Console.WriteLine($"{z.Jmeno}, {z.Vek}"); + +class Zak +{ + public string Jmeno; + public int Vek; + + public Zak(string jmeno, int vek) + { + Jmeno = jmeno; + Vek = vek; + } +} +``` diff --git a/content/programovani/3-rocnik/03-vlastnosti.en.md b/content/programovani/3-rocnik/03-vlastnosti.en.md new file mode 100644 index 0000000..d20739a --- /dev/null +++ b/content/programovani/3-rocnik/03-vlastnosti.en.md @@ -0,0 +1,4 @@ +--- +nazev: Properties +popis: get and set instead of a bare field. +--- diff --git a/content/programovani/3-rocnik/03-vlastnosti.md b/content/programovani/3-rocnik/03-vlastnosti.md new file mode 100644 index 0000000..e9cea23 --- /dev/null +++ b/content/programovani/3-rocnik/03-vlastnosti.md @@ -0,0 +1,50 @@ +--- +id: cs-3-vlastnosti +nazev: Vlastnosti +popis: get a set místo holého pole. +--- + +# Výklad + +## get a set +*tečka zůstává* + +- public string Jmeno { get; set; } se zvenku používá jako pole. +- Uvnitř může set odmítnout špatnou hodnotu. +- get hodnotu vrátí, set ji uloží. + +> Tip: Zápis z.Vek = -1 může set zarazit, holé pole ne. + +## Kontrola +*set* + +- V příkladu set u věku záporné číslo neuloží. +- get pořád vrátí poslední platnou hodnotu. +- Tak se objekt nepokazí zvenku. + +> Tip: Zkuste z.Vek = -3 a vypište věk. Zůstane 17. + +# Kód + +``` +Zak z = new Zak("Anna", 17); +z.Vek = -3; +Console.WriteLine(z.Vek); + +class Zak +{ + public string Jmeno { get; set; } + private int vek; + public int Vek + { + get { return vek; } + set { if (value >= 0) vek = value; } + } + + public Zak(string jmeno, int vek) + { + Jmeno = jmeno; + Vek = vek; + } +} +``` diff --git a/content/programovani/3-rocnik/04-zapouzdreni.en.md b/content/programovani/3-rocnik/04-zapouzdreni.en.md new file mode 100644 index 0000000..08bb91c --- /dev/null +++ b/content/programovani/3-rocnik/04-zapouzdreni.en.md @@ -0,0 +1,4 @@ +--- +nazev: Encapsulation +popis: Private data and public methods. +--- diff --git a/content/programovani/3-rocnik/04-zapouzdreni.md b/content/programovani/3-rocnik/04-zapouzdreni.md new file mode 100644 index 0000000..27b5b89 --- /dev/null +++ b/content/programovani/3-rocnik/04-zapouzdreni.md @@ -0,0 +1,49 @@ +--- +id: cs-3-zapouzdreni +nazev: Zapouzdření +popis: Soukromá data a veřejné metody. +--- + +# Výklad + +## private +*schované* + +- private pole vidí jen kód té třídy. +- public metoda je dveře, kterými se s objektem mluví. +- Zvenku z.penize nenapíšete. Zavoláte Pridej. + +> Tip: Když je vše public, kdokoli rozbije pravidla objektu. + +## Metoda +*Pridej* + +- Pridej přičte a vrátí nový stav. +- Záporný vklad lze v metodě odmítnout. +- Výpis stavu jde přes Kolik, ne přes pole. + +> Tip: Zkuste Pridej(-10). Stav se nemá zmenšit. + +# Kód + +``` +Pokladna p = new Pokladna(); +p.Pridej(50); +p.Pridej(-10); +Console.WriteLine(p.Kolik()); + +class Pokladna +{ + private int penize; + + public void Pridej(int castka) + { + if (castka > 0) penize = penize + castka; + } + + public int Kolik() + { + return penize; + } +} +``` diff --git a/content/programovani/3-rocnik/05-dedicnost.en.md b/content/programovani/3-rocnik/05-dedicnost.en.md new file mode 100644 index 0000000..8e3d992 --- /dev/null +++ b/content/programovani/3-rocnik/05-dedicnost.en.md @@ -0,0 +1,4 @@ +--- +nazev: Inheritance +popis: A new class takes over a finished one. +--- diff --git a/content/programovani/3-rocnik/05-dedicnost.md b/content/programovani/3-rocnik/05-dedicnost.md new file mode 100644 index 0000000..6b1029a --- /dev/null +++ b/content/programovani/3-rocnik/05-dedicnost.md @@ -0,0 +1,52 @@ +--- +id: cs-3-dedicnost +nazev: Dědičnost +popis: Nová třída převezme hotovou. +--- + +# Výklad + +## base +*: Zak* + +- class Student : Zak znamená, že student je žák a má něco navíc. +- Dostane Jmeno a Vek, aniž by se psaly znovu. +- base(...) zavolá konstruktor předka. + +> Tip: Student umí to, co Zak, a ještě obor. + +## Je +*typ* + +- Student lze uložit do proměnné Zak. Pořád je to student. +- Opačně žák bez oboru studentem není. +- Na specialitu sáhnete, až když máte původní typ. + +> Tip: Vypište i obor. Patří studentovi, ne obecnému žákovi. + +# Kód + +``` +Student s = new Student("Eva", 18, "informatika"); +Console.WriteLine($"{s.Jmeno}, {s.Obor}"); + +class Zak +{ + public string Jmeno { get; set; } + public int Vek { get; set; } + public Zak(string jmeno, int vek) + { + Jmeno = jmeno; + Vek = vek; + } +} + +class Student : Zak +{ + public string Obor { get; set; } + public Student(string jmeno, int vek, string obor) : base(jmeno, vek) + { + Obor = obor; + } +} +``` diff --git a/content/programovani/3-rocnik/06-polymorfismus.en.md b/content/programovani/3-rocnik/06-polymorfismus.en.md new file mode 100644 index 0000000..61e4f62 --- /dev/null +++ b/content/programovani/3-rocnik/06-polymorfismus.en.md @@ -0,0 +1,4 @@ +--- +nazev: Polymorphism +popis: The same call, a different answer from the child. +--- diff --git a/content/programovani/3-rocnik/06-polymorfismus.md b/content/programovani/3-rocnik/06-polymorfismus.md new file mode 100644 index 0000000..f2b4bb7 --- /dev/null +++ b/content/programovani/3-rocnik/06-polymorfismus.md @@ -0,0 +1,50 @@ +--- +id: cs-3-polymorfismus +nazev: Polymorfismus +popis: Stejné volání, jiná odpověď potomka. +--- + +# Výklad + +## virtual a override +*přepsání* + +- virtual u předka dovolí potomkovi metodu vyměnit. +- override u potomka řekne, že jde o tu samou metodu. +- Bez virtual by potomek jen schoval jméno a přes typ předka by se volala stará verze. + +> Tip: Proměnná je Zvire, objekt je Pes. Zavolá se štěk, ne obecné zvíře. + +## Seznam +*víc tvarů* + +- Do pole Zvire lze dát psa i kočku. +- foreach volá Zvuk a každý objekt odpoví po svém. +- Nové zvíře přidáte třídou, ne úpravou smyčky. + +> Tip: Přidejte do pole kočku a třetí řádek výstupu bude mňau. + +# Kód + +``` +Zvire[] zvirata = { new Pes(), new Kocka() }; +foreach (Zvire z in zvirata) +{ + Console.WriteLine(z.Zvuk()); +} + +class Zvire +{ + public virtual string Zvuk() { return "..."; } +} + +class Pes : Zvire +{ + public override string Zvuk() { return "haf"; } +} + +class Kocka : Zvire +{ + public override string Zvuk() { return "mňau"; } +} +``` diff --git a/content/programovani/3-rocnik/07-rozhrani.en.md b/content/programovani/3-rocnik/07-rozhrani.en.md new file mode 100644 index 0000000..1f39bf6 --- /dev/null +++ b/content/programovani/3-rocnik/07-rozhrani.en.md @@ -0,0 +1,4 @@ +--- +nazev: Interface +popis: A contract: which methods a type must have. +--- diff --git a/content/programovani/3-rocnik/07-rozhrani.md b/content/programovani/3-rocnik/07-rozhrani.md new file mode 100644 index 0000000..0caebee --- /dev/null +++ b/content/programovani/3-rocnik/07-rozhrani.md @@ -0,0 +1,50 @@ +--- +id: cs-3-rozhrani +nazev: Rozhraní +popis: Smlouva: které metody typ musí mít. +--- + +# Výklad + +## interface +*I* + +- interface nemá hotový kód, jen hlavičky metod. +- class Pes : IMluvici slíbí, že Mluv napíše. +- Jedna třída může splnit víc rozhraní. Dědit může jen jednu třídu. + +> Tip: Jméno rozhraní se často píše s I na začátku: IMluvici. + +## Společný typ +*seznam* + +- Proměnná typu rozhraní udrží kohokoli, kdo smlouvu splnil. +- Pes a Radio nemají společného předka, ale oba umí Mluv. +- Smyčka jim rozumí přes smlouvu, ne přes původ. + +> Tip: Rozhraní neříká, jak metoda funguje. To napíše až třída. + +# Kód + +``` +IMluvici[] veci = { new Pes(), new Radio() }; +foreach (IMluvici v in veci) +{ + Console.WriteLine(v.Mluv()); +} + +interface IMluvici +{ + string Mluv(); +} + +class Pes : IMluvici +{ + public string Mluv() { return "haf"; } +} + +class Radio : IMluvici +{ + public string Mluv() { return "hudba"; } +} +``` diff --git a/content/programovani/3-rocnik/08-enum.en.md b/content/programovani/3-rocnik/08-enum.en.md new file mode 100644 index 0000000..4fb204b --- /dev/null +++ b/content/programovani/3-rocnik/08-enum.en.md @@ -0,0 +1,4 @@ +--- +nazev: Enum +popis: A fixed list of named values. +--- diff --git a/content/programovani/3-rocnik/08-enum.md b/content/programovani/3-rocnik/08-enum.md new file mode 100644 index 0000000..dec3916 --- /dev/null +++ b/content/programovani/3-rocnik/08-enum.md @@ -0,0 +1,50 @@ +--- +id: cs-3-enum +nazev: Výčet +popis: Pevný seznam pojmenovaných hodnot. +--- + +# Výklad + +## enum +*Den* + +- enum Den { Po, Ut, St } jsou tři pojmenované hodnoty. +- Místo magického čísla 1 píšete Den.Po. +- Překladač nepustí hodnotu, která ve výčtu není. + +> Tip: Čísla 0, 1, 2 by za měsíc nikdo nepřečetl. Jména ano. + +## switch +*přes výčet* + +- switch na enum má case pro každou hodnotu. +- Když přidáte čtvrtek a zapomenete case, překlad na to v novém zápisu upozorní. +- Výčet je typ. Není to string. + +> Tip: Změňte dnes na Den.St. + +# Kód + +``` +Den dnes = Den.Po; +switch (dnes) +{ + case Den.Po: + Console.WriteLine("pondělí"); + break; + case Den.Ut: + Console.WriteLine("úterý"); + break; + default: + Console.WriteLine("jiný den"); + break; +} + +enum Den +{ + Po, + Ut, + St +} +``` diff --git a/content/programovani/4-rocnik/01-vyjimky.en.md b/content/programovani/4-rocnik/01-vyjimky.en.md new file mode 100644 index 0000000..c4750a7 --- /dev/null +++ b/content/programovani/4-rocnik/01-vyjimky.en.md @@ -0,0 +1,4 @@ +--- +nazev: Exceptions +popis: An error you can catch and explain. +--- diff --git a/content/programovani/4-rocnik/01-vyjimky.md b/content/programovani/4-rocnik/01-vyjimky.md new file mode 100644 index 0000000..117182a --- /dev/null +++ b/content/programovani/4-rocnik/01-vyjimky.md @@ -0,0 +1,43 @@ +--- +id: cs-4-vyjimky +nazev: Výjimky +popis: Chyba, kterou lze chytit a vysvětlit. +--- + +# Výklad + +## try +*pokus* + +- try je kus, který může spadnout. +- catch (Exception e) ho chytí, místo aby spadl celý program. +- e.Message je text chyby. + +> Tip: Bez try by Parse na textu abc program ukončil. + +## finally +*vždy* + +- finally se spustí, ať try prošel, nebo ne. +- Hodí se na úklid. V příkladu jen vypíše, že končíme. +- catch může být víc, pro různé typy chyb. + +> Tip: Změňte abc na 5. catch se přeskočí, finally ne. + +# Kód + +``` +try +{ + int n = int.Parse("abc"); + Console.WriteLine(n); +} +catch (Exception e) +{ + Console.WriteLine("Chyba: " + e.Message); +} +finally +{ + Console.WriteLine("konec"); +} +``` diff --git a/content/programovani/4-rocnik/02-list.en.md b/content/programovani/4-rocnik/02-list.en.md new file mode 100644 index 0000000..0c8016d --- /dev/null +++ b/content/programovani/4-rocnik/02-list.en.md @@ -0,0 +1,4 @@ +--- +nazev: List +popis: A list that can grow. +--- diff --git a/content/programovani/4-rocnik/02-list.md b/content/programovani/4-rocnik/02-list.md new file mode 100644 index 0000000..16cbf92 --- /dev/null +++ b/content/programovani/4-rocnik/02-list.md @@ -0,0 +1,39 @@ +--- +id: cs-4-list +nazev: List +popis: Seznam, který umí růst. +--- + +# Výklad + +## Přidávání +*Add* + +- List začne prázdný. Add přidá na konec. +- Count je počet. Indexy jsou zase od nuly. +- Na rozdíl od pole délku dopředu neříkáte. + +> Tip: List potřebuje špičaté závorky s typem prvku. + +## Odebrání +*Remove* + +- Remove("Eva") odebere první Evu. +- RemoveAt(0) odebere podle indexu. +- foreach pak projde, co zůstalo. + +> Tip: Přidejte třetí jméno před odebráním. + +# Kód + +``` +List jmena = new List(); +jmena.Add("Anna"); +jmena.Add("Eva"); +jmena.Remove("Eva"); +foreach (string j in jmena) +{ + Console.WriteLine(j); +} +Console.WriteLine(jmena.Count); +``` diff --git a/content/programovani/4-rocnik/03-slovnik.en.md b/content/programovani/4-rocnik/03-slovnik.en.md new file mode 100644 index 0000000..40451f5 --- /dev/null +++ b/content/programovani/4-rocnik/03-slovnik.en.md @@ -0,0 +1,4 @@ +--- +nazev: Dictionary +popis: A value under a key. +--- diff --git a/content/programovani/4-rocnik/03-slovnik.md b/content/programovani/4-rocnik/03-slovnik.md new file mode 100644 index 0000000..4ff2289 --- /dev/null +++ b/content/programovani/4-rocnik/03-slovnik.md @@ -0,0 +1,39 @@ +--- +id: cs-4-slovnik +nazev: Slovník +popis: Hodnota pod klíčem. +--- + +# Výklad + +## Klíč +*jednou* + +- Dictionary má klíč text a hodnotu číslo. +- znamky["Anna"] = 1 uloží nebo přepíše. +- Stejný klíč je v slovníku jen jednou. + +> Tip: Pole je podle čísla. Slovník je podle jména, které si zvolíte. + +## Čtení +*ContainsKey* + +- Čtení chybějícího klíče spadne. +- ContainsKey napřed zjistí, jestli klíč je. +- foreach projde dvojice Key a Value. + +> Tip: Zeptejte se na jméno, které v slovníku není, až po ContainsKey. + +# Kód + +``` +Dictionary znamky = new Dictionary(); +znamky["Anna"] = 1; +znamky["Eva"] = 2; +znamky["Anna"] = 3; +if (znamky.ContainsKey("Eva")) +{ + Console.WriteLine(znamky["Eva"]); +} +Console.WriteLine(znamky["Anna"]); +``` diff --git a/content/programovani/4-rocnik/04-linq.en.md b/content/programovani/4-rocnik/04-linq.en.md new file mode 100644 index 0000000..0b2ec5a --- /dev/null +++ b/content/programovani/4-rocnik/04-linq.en.md @@ -0,0 +1,4 @@ +--- +nazev: LINQ +popis: Queries over a list. +--- diff --git a/content/programovani/4-rocnik/04-linq.md b/content/programovani/4-rocnik/04-linq.md new file mode 100644 index 0000000..de4a297 --- /dev/null +++ b/content/programovani/4-rocnik/04-linq.md @@ -0,0 +1,36 @@ +--- +id: cs-4-linq +nazev: LINQ +popis: Dotazy nad seznamem. +--- + +# Výklad + +## Where +*filtr* + +- Where nechá jen prvky, které splní podmínku. +- n => n >= 10 je krátká funkce: vezmi n a vrať, jestli je aspoň 10. +- Samotné Where ještě nic neprojde. ToList nebo foreach výsledek vezme. + +> Tip: Šipka => není porovnání. Je to zápis malé funkce. + +## Select +*převod* + +- Select z každého prvku udělá jiný. +- OrderBy seřadí. +- Dotaz lze řetězit: Where a pak Select. + +> Tip: Změňte 10 na 12 a dvojka z výpisu zmizí. + +# Kód + +``` +int[] cisla = { 4, 11, 7, 20 }; +var velka = cisla.Where(n => n >= 10).OrderBy(n => n); +foreach (int n in velka) +{ + Console.WriteLine(n); +} +``` diff --git a/content/programovani/4-rocnik/05-soubory.en.md b/content/programovani/4-rocnik/05-soubory.en.md new file mode 100644 index 0000000..f0b0642 --- /dev/null +++ b/content/programovani/4-rocnik/05-soubory.en.md @@ -0,0 +1,4 @@ +--- +nazev: Files +popis: Writing and reading text. +--- diff --git a/content/programovani/4-rocnik/05-soubory.md b/content/programovani/4-rocnik/05-soubory.md new file mode 100644 index 0000000..bb0133a --- /dev/null +++ b/content/programovani/4-rocnik/05-soubory.md @@ -0,0 +1,33 @@ +--- +id: cs-4-soubory +nazev: Soubory +popis: Zápis a čtení textu. +--- + +# Výklad + +## WriteAllText +*jednou* + +- File.WriteAllText zapíše celý soubor. Když existuje, přepíše ho. +- Cesta pozdrav.txt je vedle programu, v jeho složce. +- ReadAllText celý text vrátí jako string. + +> Tip: Na telefonu se soubor nespustí. Na počítači s .NET ano. + +## Řádky +*Append* + +- AppendAllText přidá na konec a soubor nesmaže. +- ReadAllLines vrátí pole řádků. +- Po skončení programu soubor ve složce laboratoře zůstane. + +> Tip: Spusťte dvakrát. Druhý zápis WriteAllText starý text nahradí. + +# Kód + +``` +File.WriteAllText("pozdrav.txt", "Ahoj ze souboru"); +string zpet = File.ReadAllText("pozdrav.txt"); +Console.WriteLine(zpet); +``` diff --git a/content/programovani/4-rocnik/06-generika.en.md b/content/programovani/4-rocnik/06-generika.en.md new file mode 100644 index 0000000..422ce16 --- /dev/null +++ b/content/programovani/4-rocnik/06-generika.en.md @@ -0,0 +1,4 @@ +--- +nazev: Generics +popis: One method for several types. +--- diff --git a/content/programovani/4-rocnik/06-generika.md b/content/programovani/4-rocnik/06-generika.md new file mode 100644 index 0000000..dfe9d7d --- /dev/null +++ b/content/programovani/4-rocnik/06-generika.md @@ -0,0 +1,37 @@ +--- +id: cs-4-generika +nazev: Generika +popis: Jedna metoda pro víc typů. +--- + +# Výklad + +## T +*typový parametr* + +- static void Vypis(T hodnota) přijme int i string. +- T se doplní při volání podle argumentu. +- Jedna metoda tak nenutí kopírovat kód pro každý typ. + +> Tip: Když metoda s T má umět jen porovnávat, T se omezí. Tady stačí výpis. + +## List +*už ho znáte* + +- List je generická třída. string je dosazený T. +- Stejný List drží čísla a metody jsou stejné. +- Generika hlídá typ. Do List string nedáte. + +> Tip: Zavolejte Vypis s číslem i s textem. + +# Kód + +``` +Vypis(7); +Vypis("ahoj"); + +static void Vypis(T hodnota) +{ + Console.WriteLine(hodnota); +} +``` diff --git a/content/programovani/4-rocnik/07-delegaty.en.md b/content/programovani/4-rocnik/07-delegaty.en.md new file mode 100644 index 0000000..d39c31f --- /dev/null +++ b/content/programovani/4-rocnik/07-delegaty.en.md @@ -0,0 +1,4 @@ +--- +nazev: Delegates +popis: A variable that holds a method. +--- diff --git a/content/programovani/4-rocnik/07-delegaty.md b/content/programovani/4-rocnik/07-delegaty.md new file mode 100644 index 0000000..b5b9652 --- /dev/null +++ b/content/programovani/4-rocnik/07-delegaty.md @@ -0,0 +1,34 @@ +--- +id: cs-4-delegaty +nazev: Delegáty +popis: Proměnná, do které uložíte metodu. +--- + +# Výklad + +## Func +*funkce* + +- Func je metoda, která dostane int a vrátí int. +- x => x * x je taková metoda bez jména. +- Do proměnné ji uložíte a zavoláte později. + +> Tip: Závorky pocet(4) metodu spustí. Bez nich byste ji jen předali dál. + +## Action +*bez výsledku* + +- Action nic nevrací. Hodí se na kroky. +- Metodu můžete předat jiné metodě jako argument. +- Tak se píše i Where z minulé lekce. + +> Tip: Vyměňte x * x za x + 1. + +# Kód + +``` +Func pocet = x => x * x; +Console.WriteLine(pocet(4)); +Action pozdrav = () => Console.WriteLine("ahoj"); +pozdrav(); +``` diff --git a/content/programovani/4-rocnik/08-async.en.md b/content/programovani/4-rocnik/08-async.en.md new file mode 100644 index 0000000..98da3e9 --- /dev/null +++ b/content/programovani/4-rocnik/08-async.en.md @@ -0,0 +1,4 @@ +--- +nazev: Async and await +popis: Work that waits without freezing the way you write it. +--- diff --git a/content/programovani/4-rocnik/08-async.md b/content/programovani/4-rocnik/08-async.md new file mode 100644 index 0000000..9baebaf --- /dev/null +++ b/content/programovani/4-rocnik/08-async.md @@ -0,0 +1,33 @@ +--- +id: cs-4-async +nazev: Async a await +popis: Práce, která počká, aniž by zamkla zbytek zápisu. +--- + +# Výklad + +## await +*počkej* + +- await Task.Delay čeká, ale zápis zůstane řádek za řádkem. +- Metoda s await má v hlavičce async a vrací Task. +- V krátkém programu smí await stát i na nejvyšší úrovni. + +> Tip: Delay(300) čeká 300 milisekund. Výpis hotovo přijde až po něm. + +## Task +*slib* + +- Task je slib, že práce doběhne. +- await slib rozbalí. Bez await byste měli slib, ne výsledek. +- Na síť a soubory se await hodí. Na 2 + 2 ne. + +> Tip: Dejte za první výpis ještě jeden await a druhý řádek. + +# Kód + +``` +Console.WriteLine("začátek"); +await Task.Delay(300); +Console.WriteLine("hotovo"); +``` diff --git a/ios/Graduately/AppModel.swift b/ios/Graduately/AppModel.swift index bfe0b40..774dddd 100644 --- a/ios/Graduately/AppModel.swift +++ b/ios/Graduately/AppModel.swift @@ -254,6 +254,26 @@ final class AppModel: ObservableObject { refresh() } + func markCs(_ id: Int) { + progress.markCs(id) + refresh() + } + + func markCs2(_ id: Int) { + progress.markCs2(id) + refresh() + } + + func markCs3(_ id: Int) { + progress.markCs3(id) + refresh() + } + + func markCs4(_ id: Int) { + progress.markCs4(id) + refresh() + } + func markMat0(_ id: Int) { progress.markMat0(id) refresh() diff --git a/ios/Graduately/Data/Content.swift b/ios/Graduately/Data/Content.swift index 4b1aa1e..4cb54aa 100644 --- a/ios/Graduately/Data/Content.swift +++ b/ios/Graduately/Data/Content.swift @@ -63,6 +63,10 @@ final class Content { let tg2: [J] let tg3: [J] let tg4: [J] + let cs: [J] + let cs2: [J] + let cs3: [J] + let cs4: [J] let mat0: [J] let mat: [J] let mat2: [J] @@ -133,6 +137,10 @@ final class Content { tg2 = raw.arr("tg2") tg3 = raw.arr("tg3") tg4 = raw.arr("tg4") + cs = raw.arr("cs") + cs2 = raw.arr("cs2") + cs3 = raw.arr("cs3") + cs4 = raw.arr("cs4") mat0 = raw.arr("mat0") mat = raw.arr("mat") mat2 = raw.arr("mat2") @@ -199,6 +207,10 @@ final class Content { func tg2Lesson(_ id: Int) -> J? { tg2.first { $0.int("id") == id } } func tg3Lesson(_ id: Int) -> J? { tg3.first { $0.int("id") == id } } func tg4Lesson(_ id: Int) -> J? { tg4.first { $0.int("id") == id } } + func csLesson(_ id: Int) -> J? { cs.first { $0.int("id") == id } } + func cs2Lesson(_ id: Int) -> J? { cs2.first { $0.int("id") == id } } + func cs3Lesson(_ id: Int) -> J? { cs3.first { $0.int("id") == id } } + func cs4Lesson(_ id: Int) -> J? { cs4.first { $0.int("id") == id } } func mat0Lesson(_ id: Int) -> J? { mat0.first { $0.int("id") == id } } func matLesson(_ id: Int) -> J? { mat.first { $0.int("id") == id } } func mat2Lesson(_ id: Int) -> J? { mat2.first { $0.int("id") == id } } diff --git a/ios/Graduately/Data/ProgressStore.swift b/ios/Graduately/Data/ProgressStore.swift index c7ea326..50d8033 100644 --- a/ios/Graduately/Data/ProgressStore.swift +++ b/ios/Graduately/Data/ProgressStore.swift @@ -102,6 +102,14 @@ final class ProgressStore { func markTg3(_ id: Int) { mark("tg3.\(id)") } func tg4Done(_ id: Int) -> Bool { done("tg4.\(id)") } func markTg4(_ id: Int) { mark("tg4.\(id)") } + func csDone(_ id: Int) -> Bool { done("cs.\(id)") } + func markCs(_ id: Int) { mark("cs.\(id)") } + func cs2Done(_ id: Int) -> Bool { done("cs2.\(id)") } + func markCs2(_ id: Int) { mark("cs2.\(id)") } + func cs3Done(_ id: Int) -> Bool { done("cs3.\(id)") } + func markCs3(_ id: Int) { mark("cs3.\(id)") } + func cs4Done(_ id: Int) -> Bool { done("cs4.\(id)") } + func markCs4(_ id: Int) { mark("cs4.\(id)") } func mat0Done(_ id: Int) -> Bool { done("mat0.\(id)") } func markMat0(_ id: Int) { mark("mat0.\(id)") } func matDone(_ id: Int) -> Bool { done("mat.\(id)") } @@ -328,6 +336,38 @@ func summarize(_ content: Content, _ p: ProgressStore) -> ProgressSum { sum.doneUnits += 1 } } + for l in content.cs { + sum.totalEx += 1 + sum.openUnits += 1 + if p.csDone(l.int("id")) { + sum.doneEx += 1 + sum.doneUnits += 1 + } + } + for l in content.cs2 { + sum.totalEx += 1 + sum.openUnits += 1 + if p.cs2Done(l.int("id")) { + sum.doneEx += 1 + sum.doneUnits += 1 + } + } + for l in content.cs3 { + sum.totalEx += 1 + sum.openUnits += 1 + if p.cs3Done(l.int("id")) { + sum.doneEx += 1 + sum.doneUnits += 1 + } + } + for l in content.cs4 { + sum.totalEx += 1 + sum.openUnits += 1 + if p.cs4Done(l.int("id")) { + sum.doneEx += 1 + sum.doneUnits += 1 + } + } for l in content.mat0 { sum.totalEx += 1 sum.openUnits += 1 diff --git a/ios/Graduately/Route.swift b/ios/Graduately/Route.swift index 61a8c0b..3e3b2bc 100644 --- a/ios/Graduately/Route.swift +++ b/ios/Graduately/Route.swift @@ -124,6 +124,15 @@ enum Route: Hashable { case tg4Map case tg4Lesson(Int) case tg4Ex(Int) + case csYears + case csMap + case csLesson(Int) + case cs2Map + case cs2Lesson(Int) + case cs3Map + case cs3Lesson(Int) + case cs4Map + case cs4Lesson(Int) case czechMap case mluvnice case mluvEx(Int) @@ -250,6 +259,15 @@ enum Route: Hashable { case .tg4Map: return "tg4map" case .tg4Lesson(let id): return "tg4unit\(id)" case .tg4Ex(let id): return "tg4ex\(id)" + case .csYears: return "csyears" + case .csMap: return "csmap" + case .csLesson(let id): return "csunit\(id)" + case .cs2Map: return "cs2map" + case .cs2Lesson(let id): return "cs2unit\(id)" + case .cs3Map: return "cs3map" + case .cs3Lesson(let id): return "cs3unit\(id)" + case .cs4Map: return "cs4map" + case .cs4Lesson(let id): return "cs4unit\(id)" case .czechMap: return "czechmap" case .mluvnice: return "mluvnice" case .mluvEx(let n): return "mluve\(n)" @@ -500,6 +518,24 @@ enum Route: Hashable { return [.tgYears, .tg4Map, .tg4Lesson(id)] case .tg4Ex(let id): return [.tgYears, .tg4Map, .tg4Lesson(id), .tg4Ex(id)] + case .csYears: + return [.csYears] + case .csMap: + return [.csYears, .csMap] + case .csLesson(let id): + return [.csYears, .csMap, .csLesson(id)] + case .cs2Map: + return [.csYears, .cs2Map] + case .cs2Lesson(let id): + return [.csYears, .cs2Map, .cs2Lesson(id)] + case .cs3Map: + return [.csYears, .cs3Map] + case .cs3Lesson(let id): + return [.csYears, .cs3Map, .cs3Lesson(id)] + case .cs4Map: + return [.csYears, .cs4Map] + case .cs4Lesson(let id): + return [.csYears, .cs4Map, .cs4Lesson(id)] case .czechMap: return [.czechMap] case .mluvnice: @@ -565,6 +601,11 @@ func routeFromPage(_ page: String) -> Route? { case "tg2map": return .tg2Map case "tg3map": return .tg3Map case "tg4map": return .tg4Map + case "csyears": return .csYears + case "csmap": return .csMap + case "cs2map": return .cs2Map + case "cs3map": return .cs3Map + case "cs4map": return .cs4Map case "czechmap": return .czechMap case "mluvnice": return .mluvnice case "readinglist": return .readingList @@ -698,6 +739,18 @@ func routeFromPage(_ page: String) -> Route? { if let m = page.wholeMatch(of: /tgex(\d+)/), let n = Int(m.1) { return .tgEx(n) } + if let m = page.wholeMatch(of: /cs4unit(\d+)/), let n = Int(m.1) { + return .cs4Lesson(n) + } + if let m = page.wholeMatch(of: /cs3unit(\d+)/), let n = Int(m.1) { + return .cs3Lesson(n) + } + if let m = page.wholeMatch(of: /cs2unit(\d+)/), let n = Int(m.1) { + return .cs2Lesson(n) + } + if let m = page.wholeMatch(of: /csunit(\d+)/), let n = Int(m.1) { + return .csLesson(n) + } if let m = page.wholeMatch(of: /lit4unit(\d+)/), let n = Int(m.1) { return .lit4Lesson(n) } diff --git a/ios/Graduately/UI/Components.swift b/ios/Graduately/UI/Components.swift index 2765396..a4bd6fe 100644 --- a/ios/Graduately/UI/Components.swift +++ b/ios/Graduately/UI/Components.swift @@ -680,6 +680,7 @@ func subjectSymbol(_ icon: String) -> String { case "bolt": return "bolt.fill" case "function": return "function" case "ruler": return "ruler.fill" + case "code": return "chevron.left.forwardslash.chevron.right" default: return "lock.fill" } } diff --git a/ios/Graduately/UI/Screens.swift b/ios/Graduately/UI/Screens.swift index 044065b..e2f1ebb 100644 --- a/ios/Graduately/UI/Screens.swift +++ b/ios/Graduately/UI/Screens.swift @@ -713,6 +713,12 @@ private func subjectSum(_ vm: AppModel, _ s: J) -> ProgressSum { + vm.content.mat2.filter { vm.progress.mat2Done($0.int("id")) }.count + vm.content.mat3.filter { vm.progress.mat3Done($0.int("id")) }.count + vm.content.mat4.filter { vm.progress.mat4Done($0.int("id")) }.count + case "csyears", "csmap", "cs2map", "cs3map", "cs4map": + sum.totalEx = vm.content.cs.count + vm.content.cs2.count + vm.content.cs3.count + vm.content.cs4.count + sum.doneEx = vm.content.cs.filter { vm.progress.csDone($0.int("id")) }.count + + vm.content.cs2.filter { vm.progress.cs2Done($0.int("id")) }.count + + vm.content.cs3.filter { vm.progress.cs3Done($0.int("id")) }.count + + vm.content.cs4.filter { vm.progress.cs4Done($0.int("id")) }.count case "tgyears", "tgmap", "tg2map", "tg3map", "tg4map": sum.totalEx = vm.content.tg.count + vm.content.tg2.count + vm.content.tg3.count + vm.content.tg4.count sum.doneEx = vm.content.tg.filter { vm.progress.tgDone($0.int("id")) }.count @@ -1042,6 +1048,23 @@ private func buildSearch(_ vm: AppModel) -> [Hit] { add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "tg4unit\(l.int("id"))", extra: "autocad") add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "tg4ex\(l.int("id"))") } + add(vm.tr("Programování"), vm.tr("search_lesson"), "csyears", extra: "csharp c#") + add(vm.tr("cs_year1"), vm.tr("search_lesson"), "csmap", extra: "rocnik") + add(vm.tr("cs_year2"), vm.tr("search_lesson"), "cs2map", extra: "rocnik") + add(vm.tr("cs_year3"), vm.tr("search_lesson"), "cs3map", extra: "rocnik") + add(vm.tr("cs_year4"), vm.tr("search_lesson"), "cs4map", extra: "rocnik") + for l in vm.content.cs { + add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "csunit\(l.int("id"))", extra: "csharp") + } + for l in vm.content.cs2 { + add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "cs2unit\(l.int("id"))", extra: "csharp") + } + for l in vm.content.cs3 { + add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "cs3unit\(l.int("id"))", extra: "csharp") + } + for l in vm.content.cs4 { + add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "cs4unit\(l.int("id"))", extra: "csharp") + } add(vm.tr("Chemie"), vm.tr("search_lesson"), "chemmap", extra: "chemie") add(vm.tr("Biologie"), vm.tr("search_lesson"), "biomap", extra: "biologie") for l in vm.content.chem { @@ -1776,6 +1799,47 @@ struct MatYearsScreen: View { } } +struct CsYearsScreen: View { + @ObservedObject var vm: AppModel + + var body: some View { + #if os(macOS) + let years: [(String, String, Route)] = [ + ("cs_year1", "cs_sub", .csMap), + ("cs_year2", "cs2_sub", .cs2Map), + ("cs_year3", "cs3_sub", .cs3Map), + ("cs_year4", "cs4_sub", .cs4Map), + ] + MacFillGrid(count: years.count, minWidth: 260, minHeight: 180) { i in + NavigationLink(value: years[i].2) { + MacLinkCard(title: vm.tr(years[i].0), subtitle: vm.tr(years[i].1), badge: "\(i + 1).") + } + .buttonStyle(PressScaleStyle()) + } + .navigationTitle(vm.tr("Programování")) + .navigationSubtitle(vm.tr("cs_years_sub")) + #else + List { + NavigationLink(value: Route.csMap) { + ListRowLabel(title: vm.tr("cs_year1"), subtitle: vm.tr("cs_sub")) + } + NavigationLink(value: Route.cs2Map) { + ListRowLabel(title: vm.tr("cs_year2"), subtitle: vm.tr("cs2_sub")) + } + NavigationLink(value: Route.cs3Map) { + ListRowLabel(title: vm.tr("cs_year3"), subtitle: vm.tr("cs3_sub")) + } + NavigationLink(value: Route.cs4Map) { + ListRowLabel(title: vm.tr("cs_year4"), subtitle: vm.tr("cs4_sub")) + } + } + .navigationTitle(vm.tr("Programování")) + .navigationSubtitle(vm.tr("cs_years_sub")) + .appListStyle() + #endif + } +} + struct TgYearsScreen: View { @ObservedObject var vm: AppModel @@ -2876,6 +2940,64 @@ struct RouteDestination: View { } case .fyz4Ex(let id): FyzQuizScreen(vm: vm, year: 4, id: id) + case .csYears: + CsYearsScreen(vm: vm) + case .csMap: + LessonListScreen( + vm: vm, + title: vm.tr("cs_year1"), + subtitle: vm.tr("cs_sub"), + rows: vm.content.cs.map { l in + let id = l.int("id") + return LessonRow(id: "c\(id)", title: vm.tr(l.str("titleKey")), done: vm.progress.csDone(id), destination: .csLesson(id)) + } + ) + case .csLesson(let id): + if let l = vm.content.csLesson(id) { + CodeLessonScreen(vm: vm, lesson: l, onSuccess: { vm.markCs(id) }) + } + case .cs2Map: + LessonListScreen( + vm: vm, + title: vm.tr("cs_year2"), + subtitle: vm.tr("cs2_sub"), + rows: vm.content.cs2.map { l in + let id = l.int("id") + return LessonRow(id: "c2\(id)", title: vm.tr(l.str("titleKey")), done: vm.progress.cs2Done(id), destination: .cs2Lesson(id)) + } + ) + case .cs2Lesson(let id): + if let l = vm.content.cs2Lesson(id) { + CodeLessonScreen(vm: vm, lesson: l, onSuccess: { vm.markCs2(id) }) + } + case .cs3Map: + LessonListScreen( + vm: vm, + title: vm.tr("cs_year3"), + subtitle: vm.tr("cs3_sub"), + rows: vm.content.cs3.map { l in + let id = l.int("id") + return LessonRow(id: "c3\(id)", title: vm.tr(l.str("titleKey")), done: vm.progress.cs3Done(id), destination: .cs3Lesson(id)) + } + ) + case .cs3Lesson(let id): + if let l = vm.content.cs3Lesson(id) { + CodeLessonScreen(vm: vm, lesson: l, onSuccess: { vm.markCs3(id) }) + } + case .cs4Map: + LessonListScreen( + vm: vm, + title: vm.tr("cs_year4"), + subtitle: vm.tr("cs4_sub"), + rows: vm.content.cs4.map { l in + let id = l.int("id") + return LessonRow(id: "c4\(id)", title: vm.tr(l.str("titleKey")), done: vm.progress.cs4Done(id), destination: .cs4Lesson(id)) + } + ) + case .cs4Lesson(let id): + if let l = vm.content.cs4Lesson(id) { + CodeLessonScreen(vm: vm, lesson: l, onSuccess: { vm.markCs4(id) }) + } case .tgYears: TgYearsScreen(vm: vm) case .tgMap: @@ -2970,3 +3092,251 @@ struct RouteDestination: View { } } } + +struct CodeLessonScreen: View { + @ObservedObject var vm: AppModel + let lesson: J + let onSuccess: () -> Void + @State private var code = "" + @State private var stdin = "" + @State private var output = "" + @State private var running = false + + var body: some View { + GeometryReader { geo in + if geo.size.width >= 840 { + HStack(alignment: .top, spacing: 16) { + explanation.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + editor.frame(maxWidth: .infinity, maxHeight: .infinity) + outputPane.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + } + } else { + ScrollView { + VStack(alignment: .leading, spacing: 16) { + explanation + editor.frame(minHeight: 240) + outputPane.frame(minHeight: 160) + } + } + } + } + .padding(16) + .detailScreen(vm.tr(lesson.str("titleKey")), subtitle: vm.tr(lesson.str("subKey"))) + .onAppear { + if code.isEmpty { code = lesson.str("code") } + } + } + + private var explanation: some View { + ScrollView { + VStack(alignment: .leading, spacing: 16) { + ForEach(Array(lesson.arr("slides").enumerated()), id: \.offset) { _, slide in + VStack(alignment: .leading, spacing: 8) { + Text(slide.str("kicker")) + .font(.subheadline.weight(.semibold)) + .foregroundStyle(Color.accentColor) + Text(slide.str("title")) + .font(.title3.bold()) + ForEach(slide.strs("lines"), id: \.self) { line in + Text("• \(line)") + .frame(maxWidth: .infinity, alignment: .leading) + } + if let tip = slide.strOrNull("tip") { + Text(tip) + .foregroundStyle(.secondary) + } + } + } + } + } + } + + private var editor: some View { + VStack(alignment: .leading, spacing: 8) { + Text(vm.tr("cs_code")).font(.headline) + TextEditor(text: $code) + .font(.system(.body, design: .monospaced)) + .scrollContentBackground(.hidden) + .padding(8) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(codeFill, in: RoundedRectangle(cornerRadius: 12)) + Text(vm.tr("cs_stdin")) + .font(.caption) + .foregroundStyle(.secondary) + TextField("", text: $stdin, axis: .vertical) + .font(.system(.body, design: .monospaced)) + .lineLimit(2...4) + .textFieldStyle(.roundedBorder) + } + } + + private var outputPane: some View { + VStack(alignment: .leading, spacing: 8) { + HStack { + Button(vm.tr("cs_run")) { run() } + .buttonStyle(.borderedProminent) + .disabled(running) + Button(vm.tr("cs_reset")) { code = lesson.str("code") } + .disabled(running) + } + Text(vm.tr("cs_output")).font(.headline) + ScrollView { + Text(output.isEmpty ? vm.tr("cs_empty") : output) + .font(.system(.body, design: .monospaced)) + .frame(maxWidth: .infinity, alignment: .topLeading) + .textSelection(.enabled) + } + .padding(12) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + .background(codeFill, in: RoundedRectangle(cornerRadius: 12)) + } + } + + private var codeFill: Color { + #if os(macOS) + Color(.textBackgroundColor).opacity(0.55) + #else + Color(.secondarySystemBackground) + #endif + } + + private func run() { + running = true + output = vm.tr("cs_running") + CSharpRunner.run(code, stdin: stdin) { status, text in + running = false + switch status { + case "unavailable": + output = vm.tr("cs_unavailable") + case "timeout": + output = (text + "\n\n" + vm.tr("cs_timeout")).trimmingCharacters(in: .whitespacesAndNewlines) + default: + output = text.isEmpty ? vm.tr("cs_empty") : text + } + if status == "ok" { onSuccess() } + } + } +} + +enum CSharpRunner { + static func run(_ source: String, stdin: String, done: @escaping (String, String) -> Void) { + #if os(macOS) + DispatchQueue.global(qos: .userInitiated).async { + let result = runMac(source, stdin: stdin) + DispatchQueue.main.async { done(result.0, result.1) } + } + #else + done("unavailable", "") + #endif + } + + #if os(macOS) + private static func runMac(_ source: String, stdin: String) -> (String, String) { + guard source.count <= 100_000 else { return ("fail", "Program je příliš dlouhý.") } + guard let dotnet = findDotnet() else { return ("unavailable", "") } + let dir = URL(fileURLWithPath: NSHomeDirectory()).appendingPathComponent(".graduately/csharp-lab") + let csproj = dir.appendingPathComponent("Lab.csproj") + if !FileManager.default.fileExists(atPath: csproj.path) { + try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) + let framework = targetFramework(dotnet) + let xml = """ + + + Exe + \(framework) + enable + disable + latest + + + """ + try? xml.write(to: csproj, atomically: true, encoding: .utf8) + let restored = command(dotnet, ["restore", dir.path], dir, "", 90) + if restored.0 != "ok" { return restored } + } + do { + try source.write(to: dir.appendingPathComponent("Program.cs"), atomically: true, encoding: .utf8) + } catch { + return ("fail", error.localizedDescription) + } + var args = ["run", "--project", dir.path, "--verbosity", "quiet"] + if FileManager.default.fileExists(atPath: dir.appendingPathComponent("obj").path) { + args.append("--no-restore") + } + return command(dotnet, args, dir, stdin, 20) + } + + private static func command(_ dotnet: String, _ args: [String], _ dir: URL, _ stdin: String, _ seconds: Double) -> (String, String) { + let proc = Process() + proc.executableURL = URL(fileURLWithPath: dotnet) + proc.arguments = args + proc.currentDirectoryURL = dir + var env = ProcessInfo.processInfo.environment + let root = (dotnet as NSString).deletingLastPathComponent + env["PATH"] = [root, "/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin"].joined(separator: ":") + env["DOTNET_ROOT"] = root + env["DOTNET_CLI_TELEMETRY_OPTOUT"] = "1" + env["DOTNET_NOLOGO"] = "1" + env["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = "1" + proc.environment = env + let out = Pipe() + let input = Pipe() + proc.standardOutput = out + proc.standardError = out + proc.standardInput = input + var collected = Data() + let lock = NSLock() + out.fileHandleForReading.readabilityHandler = { handle in + let chunk = handle.availableData + guard !chunk.isEmpty else { return } + lock.lock() + collected.append(chunk) + lock.unlock() + } + do { try proc.run() } catch { return ("fail", error.localizedDescription) } + if let data = stdin.data(using: .utf8) { input.fileHandleForWriting.write(data) } + try? input.fileHandleForWriting.close() + let group = DispatchGroup() + group.enter() + proc.terminationHandler = { _ in group.leave() } + if group.wait(timeout: .now() + seconds) == .timedOut { + proc.terminate() + kill(proc.processIdentifier, SIGKILL) + out.fileHandleForReading.readabilityHandler = nil + return ("timeout", clip(collected, lock)) + } + out.fileHandleForReading.readabilityHandler = nil + let rest = out.fileHandleForReading.readDataToEndOfFile() + lock.lock() + collected.append(rest) + lock.unlock() + let text = clip(collected, lock) + return proc.terminationStatus == 0 ? ("ok", text) : ("fail", text) + } + + private static func clip(_ data: Data, _ lock: NSLock) -> String { + lock.lock() + let copy = data + lock.unlock() + let text = String(data: copy, encoding: .utf8) ?? "" + let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines) + return trimmed.count <= 16_000 ? trimmed : String(trimmed.prefix(16_000)) + "\n…" + } + + private static func targetFramework(_ dotnet: String) -> String { + let version = command(dotnet, ["--version"], URL(fileURLWithPath: NSTemporaryDirectory()), "", 20) + let major = version.1.split(separator: ".").first.map(String.init) ?? "" + return major.allSatisfy(\.isNumber) && !major.isEmpty ? "net\(major).0" : "net8.0" + } + + private static func findDotnet() -> String? { + let names = [ + "/usr/local/share/dotnet/dotnet", + "/opt/homebrew/share/dotnet/dotnet", + "/usr/local/bin/dotnet", + NSHomeDirectory() + "/.dotnet/dotnet", + ] + return names.first { FileManager.default.isExecutableFile(atPath: $0) } + } + #endif +} diff --git a/scripts/build-content.py b/scripts/build-content.py index d6685cb..efcd131 100644 --- a/scripts/build-content.py +++ b/scripts/build-content.py @@ -116,9 +116,20 @@ def parse_doc(path: Path) -> Doc: para: Node | None = None table: Node | None = None in_comment = False - for n in range(i, len(lines)): + indexes = iter(range(i, len(lines))) + for n in indexes: ln = lines[n].rstrip() no = n + 1 + if ln.startswith("```"): + para = table = None + body: list[str] = [] + for n2 in indexes: + raw_line = lines[n2] + if raw_line.rstrip().startswith("```"): + break + body.append(raw_line) + nodes.append(Node("code", "\n".join(body).strip("\n"), no)) + continue if in_comment: if "-->" in ln: in_comment = False @@ -539,6 +550,36 @@ def lesson_files(directory: Path) -> list[Path]: return files +def code_of(p: Part | None) -> str: + if p is None: + return "" + for nd in p.nodes: + if nd.kind == "code": + return nd.text + return "" + + +def code_lessons(directory: Path, prefix: str) -> list[dict]: + """Lessons with an explanation and a C# program the student can edit and run.""" + out = [] + for i, path in enumerate(lesson_files(directory), start=1): + doc, en = parse_doc(path), en_doc(path) + load_translations(en) + lid = lesson_id(doc, f"{prefix}.{i}") + code = code_of(part(doc, "Kód", "Code")) + if not code.strip(): + fail(path, None, "lekce nemá ukázkový kód v sekci # Kód") + out.append({ + "id": i, + "key": lid, + "titleKey": keyed(f"{prefix}_unit{i}", doc, en, "nazev"), + "subKey": keyed(f"{prefix}_unit{i}_sub", doc, en, "popis"), + "slides": read_slides(doc, part(doc, "Výklad")), + "code": code, + }) + return out + + def std_lessons(directory: Path, prefix: str) -> list[dict]: """Lessons with slides and a choice quiz (IT, civics, literature, sciences, physics).""" out = [] @@ -1068,6 +1109,10 @@ STD = [ ("technicka-grafika/1-rocnik", "tg"), ("technicka-grafika/2-rocnik", "tg2"), ("technicka-grafika/3-rocnik", "tg3"), ("technicka-grafika/4-rocnik", "tg4"), ] +CODE = [ + ("programovani/1-rocnik", "cs"), ("programovani/2-rocnik", "cs2"), + ("programovani/3-rocnik", "cs3"), ("programovani/4-rocnik", "cs4"), +] MATH = [ ("matematika/0-opakovani", "mat0"), ("matematika/1-rocnik", "mat"), ("matematika/2-rocnik", "mat2"), ("matematika/3-rocnik", "mat3"), @@ -1091,6 +1136,8 @@ def build(content: Path = CONTENT) -> dict: payload[key] = std_lessons(content / d, key) for d, key in MATH: payload[key] = math_lessons(content / d, key) + for d, key in CODE: + payload[key] = code_lessons(content / d, key) for d, tag in SKILLS: for year in range(1, 5): payload[tag if year == 1 else f"{tag}{year}"] = skill_lessons(