mirror of
https://github.com/pepaskopec-blip/Graduately.git
synced 2026-10-27 15:14:35 +00:00
Add interactive mathematics for elementary review and four secondary-school years.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
524269edd7
commit
0e24e8c649
38 changed files with 9507 additions and 11 deletions
|
|
@ -69,6 +69,8 @@ Intel Mac, jiná architektura nebo úpravy kódu → [sestavení ze zdroje](#ses
|
|||
- **Č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)
|
||||
- **Matematika** — opakování základní školy a 4 ročníky, 40 lekcí;
|
||||
příklady se píšou a kontrolují, u geometrie jde i rýsovat na mřížku
|
||||
- **Fyzika** — 4 ročníky, 32 lekcí podle osnov středních škol
|
||||
(mechanika, teplo a vlny, elektřina a magnetismus, optika až astrofyzika)
|
||||
- **Základy přírodopisných věd** — chemie a biologie, v každé 8 lekcí
|
||||
|
|
@ -82,7 +84,7 @@ Intel Mac, jiná architektura nebo úpravy kódu → [sestavení ze zdroje](#ses
|
|||
### Použití
|
||||
|
||||
1. **Pokračuj** otevře mapu předmětů.
|
||||
2. Otevřený předmět (vlajka, Wi‑Fi, čip, česká vlajka, váhy, atom, baňka) vede na učební cestu.
|
||||
2. Otevřený předmět (vlajka, Wi‑Fi, čip, česká vlajka, váhy, funkce, atom, baňka) vede na učební cestu.
|
||||
3. Uzly cvičení / lekcí otevřou obsah; zamčené nic nedělají.
|
||||
4. **Hledat** (lupa), **Statistiky** (graf) a **Nastavení** (ozubené kolo) jsou vpravo nahoře.
|
||||
|
||||
|
|
@ -270,6 +272,8 @@ Intel Mac, another architecture, or hacking on the code →
|
|||
- **Czech language** — literature (4 years, 32 lessons from the secondary-school
|
||||
syllabus), grammar (20 exercises) and required reading
|
||||
(82 books: notes, quiz, plot ordering)
|
||||
- **Mathematics** — elementary-school review and 4 years, 40 lessons;
|
||||
examples are typed and checked, and some geometry is drawn on a grid
|
||||
- **Physics** — 4 years, 32 lessons from the secondary-school syllabus
|
||||
(mechanics, heat and waves, electricity and magnetism, optics through astrophysics)
|
||||
- **Natural sciences** — chemistry and biology, eight lessons each
|
||||
|
|
@ -284,7 +288,7 @@ Intel Mac, another architecture, or hacking on the code →
|
|||
### Usage
|
||||
|
||||
1. **Continue** opens the subject map.
|
||||
2. An open subject (flag, Wi‑Fi, chip, Czech flag, scales, atom, flask) opens its path.
|
||||
2. An open subject (flag, Wi‑Fi, chip, Czech flag, scales, function, atom, flask) opens its path.
|
||||
3. Exercise / lesson nodes open content; locked nodes do nothing.
|
||||
4. **Search** (magnifier), **Statistics** (chart) and **Settings** (gear) sit in the top-right.
|
||||
|
||||
|
|
|
|||
|
|
@ -191,6 +191,31 @@ class AppViewModel(app: Application) : AndroidViewModel(app) {
|
|||
refresh()
|
||||
}
|
||||
|
||||
fun markMat0(id: Int) {
|
||||
progress.markMat0(id)
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun markMat(id: Int) {
|
||||
progress.markMat(id)
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun markMat2(id: Int) {
|
||||
progress.markMat2(id)
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun markMat3(id: Int) {
|
||||
progress.markMat3(id)
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun markMat4(id: Int) {
|
||||
progress.markMat4(id)
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun markFyz4(id: Int) {
|
||||
progress.markFyz4(id)
|
||||
refresh()
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ import org.maturita.maturita.ui.HwQuizScreen
|
|||
import org.maturita.maturita.ui.HwYearsScreen
|
||||
import org.maturita.maturita.ui.LessonListScreen
|
||||
import org.maturita.maturita.ui.FyzQuizScreen
|
||||
import org.maturita.maturita.ui.MathPracticeScreen
|
||||
import org.maturita.maturita.ui.MatYearsScreen
|
||||
import org.maturita.maturita.ui.FyzYearsScreen
|
||||
import org.maturita.maturita.ui.LitYearsScreen
|
||||
import org.maturita.maturita.ui.LessonRow
|
||||
|
|
@ -350,6 +352,77 @@ private fun MaturitaAppBody(vm: AppViewModel) {
|
|||
}
|
||||
}
|
||||
is Route.Lit4Ex -> LitQuizScreen(vm, 4, r.id)
|
||||
Route.MatYears -> MatYearsScreen(vm)
|
||||
Route.Mat0Map -> LessonListScreen(
|
||||
vm, vm.tr("mat0_year"), vm.tr("mat0_sub"),
|
||||
vm.content.mat0.map { l ->
|
||||
val id = l.int("id")
|
||||
LessonRow(vm.tr(l.str("titleKey")), vm.progress.mat0Done(id), Route.Mat0Lesson(id))
|
||||
},
|
||||
)
|
||||
is Route.Mat0Lesson -> {
|
||||
val l = vm.content.mat0Lesson(r.id)
|
||||
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
|
||||
vm.replace(Route.Mat0Ex(r.id))
|
||||
}
|
||||
}
|
||||
is Route.Mat0Ex -> MathPracticeScreen(vm, 0, r.id)
|
||||
Route.MatMap -> LessonListScreen(
|
||||
vm, vm.tr("mat_year1"), vm.tr("mat_sub"),
|
||||
vm.content.mat.map { l ->
|
||||
val id = l.int("id")
|
||||
LessonRow(vm.tr(l.str("titleKey")), vm.progress.matDone(id), Route.MatLesson(id))
|
||||
},
|
||||
)
|
||||
is Route.MatLesson -> {
|
||||
val l = vm.content.matLesson(r.id)
|
||||
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
|
||||
vm.replace(Route.MatEx(r.id))
|
||||
}
|
||||
}
|
||||
is Route.MatEx -> MathPracticeScreen(vm, 1, r.id)
|
||||
Route.Mat2Map -> LessonListScreen(
|
||||
vm, vm.tr("mat_year2"), vm.tr("mat2_sub"),
|
||||
vm.content.mat2.map { l ->
|
||||
val id = l.int("id")
|
||||
LessonRow(vm.tr(l.str("titleKey")), vm.progress.mat2Done(id), Route.Mat2Lesson(id))
|
||||
},
|
||||
)
|
||||
is Route.Mat2Lesson -> {
|
||||
val l = vm.content.mat2Lesson(r.id)
|
||||
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
|
||||
vm.replace(Route.Mat2Ex(r.id))
|
||||
}
|
||||
}
|
||||
is Route.Mat2Ex -> MathPracticeScreen(vm, 2, r.id)
|
||||
Route.Mat3Map -> LessonListScreen(
|
||||
vm, vm.tr("mat_year3"), vm.tr("mat3_sub"),
|
||||
vm.content.mat3.map { l ->
|
||||
val id = l.int("id")
|
||||
LessonRow(vm.tr(l.str("titleKey")), vm.progress.mat3Done(id), Route.Mat3Lesson(id))
|
||||
},
|
||||
)
|
||||
is Route.Mat3Lesson -> {
|
||||
val l = vm.content.mat3Lesson(r.id)
|
||||
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
|
||||
vm.replace(Route.Mat3Ex(r.id))
|
||||
}
|
||||
}
|
||||
is Route.Mat3Ex -> MathPracticeScreen(vm, 3, r.id)
|
||||
Route.Mat4Map -> LessonListScreen(
|
||||
vm, vm.tr("mat_year4"), vm.tr("mat4_sub"),
|
||||
vm.content.mat4.map { l ->
|
||||
val id = l.int("id")
|
||||
LessonRow(vm.tr(l.str("titleKey")), vm.progress.mat4Done(id), Route.Mat4Lesson(id))
|
||||
},
|
||||
)
|
||||
is Route.Mat4Lesson -> {
|
||||
val l = vm.content.mat4Lesson(r.id)
|
||||
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
|
||||
vm.replace(Route.Mat4Ex(r.id))
|
||||
}
|
||||
}
|
||||
is Route.Mat4Ex -> MathPracticeScreen(vm, 4, r.id)
|
||||
Route.FyzYears -> FyzYearsScreen(vm)
|
||||
Route.FyzMap -> LessonListScreen(
|
||||
vm, vm.tr("fyz_year1"), vm.tr("fyz_sub"),
|
||||
|
|
|
|||
|
|
@ -50,6 +50,22 @@ sealed class Route(val page: String) {
|
|||
data object BioMap : Route("biomap")
|
||||
data class BioLesson(val id: Int) : Route("biounit$id")
|
||||
data class BioEx(val id: Int) : Route("bioex$id")
|
||||
data object MatYears : Route("matyears")
|
||||
data object Mat0Map : Route("mat0map")
|
||||
data class Mat0Lesson(val id: Int) : Route("mat0unit$id")
|
||||
data class Mat0Ex(val id: Int) : Route("mat0ex$id")
|
||||
data object MatMap : Route("matmap")
|
||||
data class MatLesson(val id: Int) : Route("matunit$id")
|
||||
data class MatEx(val id: Int) : Route("matex$id")
|
||||
data object Mat2Map : Route("mat2map")
|
||||
data class Mat2Lesson(val id: Int) : Route("mat2unit$id")
|
||||
data class Mat2Ex(val id: Int) : Route("mat2ex$id")
|
||||
data object Mat3Map : Route("mat3map")
|
||||
data class Mat3Lesson(val id: Int) : Route("mat3unit$id")
|
||||
data class Mat3Ex(val id: Int) : Route("mat3ex$id")
|
||||
data object Mat4Map : Route("mat4map")
|
||||
data class Mat4Lesson(val id: Int) : Route("mat4unit$id")
|
||||
data class Mat4Ex(val id: Int) : Route("mat4ex$id")
|
||||
data object FyzYears : Route("fyzyears")
|
||||
data object FyzMap : Route("fyzmap")
|
||||
data class FyzLesson(val id: Int) : Route("fyzunit$id")
|
||||
|
|
@ -83,7 +99,10 @@ sealed class Route(val page: String) {
|
|||
this is Lit3Lesson || this is Lit3Ex || this is Lit4Lesson || this is Lit4Ex ||
|
||||
this is ChemLesson || this is ChemEx || this is BioLesson || this is BioEx ||
|
||||
this is FyzLesson || this is FyzEx || this is Fyz2Lesson || this is Fyz2Ex ||
|
||||
this is Fyz3Lesson || this is Fyz3Ex || this is Fyz4Lesson || this is Fyz4Ex
|
||||
this is Fyz3Lesson || this is Fyz3Ex || this is Fyz4Lesson || this is Fyz4Ex ||
|
||||
this is Mat0Lesson || this is Mat0Ex || this is MatLesson || this is MatEx ||
|
||||
this is Mat2Lesson || this is Mat2Ex || this is Mat3Lesson || this is Mat3Ex ||
|
||||
this is Mat4Lesson || this is Mat4Ex
|
||||
|
||||
/** Full stack from the practice root to this screen, so back always goes up one level. */
|
||||
fun stack(): List<Route> = when (this) {
|
||||
|
|
@ -135,6 +154,22 @@ sealed class Route(val page: String) {
|
|||
BioMap -> listOf(Subjects, SciMap, BioMap)
|
||||
is BioLesson -> listOf(Subjects, SciMap, BioMap, this)
|
||||
is BioEx -> listOf(Subjects, SciMap, BioMap, BioLesson(id), this)
|
||||
MatYears -> listOf(Subjects, MatYears)
|
||||
Mat0Map -> listOf(Subjects, MatYears, Mat0Map)
|
||||
is Mat0Lesson -> listOf(Subjects, MatYears, Mat0Map, this)
|
||||
is Mat0Ex -> listOf(Subjects, MatYears, Mat0Map, Mat0Lesson(id), this)
|
||||
MatMap -> listOf(Subjects, MatYears, MatMap)
|
||||
is MatLesson -> listOf(Subjects, MatYears, MatMap, this)
|
||||
is MatEx -> listOf(Subjects, MatYears, MatMap, MatLesson(id), this)
|
||||
Mat2Map -> listOf(Subjects, MatYears, Mat2Map)
|
||||
is Mat2Lesson -> listOf(Subjects, MatYears, Mat2Map, this)
|
||||
is Mat2Ex -> listOf(Subjects, MatYears, Mat2Map, Mat2Lesson(id), this)
|
||||
Mat3Map -> listOf(Subjects, MatYears, Mat3Map)
|
||||
is Mat3Lesson -> listOf(Subjects, MatYears, Mat3Map, this)
|
||||
is Mat3Ex -> listOf(Subjects, MatYears, Mat3Map, Mat3Lesson(id), this)
|
||||
Mat4Map -> listOf(Subjects, MatYears, Mat4Map)
|
||||
is Mat4Lesson -> listOf(Subjects, MatYears, Mat4Map, this)
|
||||
is Mat4Ex -> listOf(Subjects, MatYears, Mat4Map, Mat4Lesson(id), this)
|
||||
FyzYears -> listOf(Subjects, FyzYears)
|
||||
FyzMap -> listOf(Subjects, FyzYears, FyzMap)
|
||||
is FyzLesson -> listOf(Subjects, FyzYears, FyzMap, this)
|
||||
|
|
@ -181,6 +216,12 @@ fun routeFromPage(page: String): Route? = when (page) {
|
|||
"scimap" -> Route.SciMap
|
||||
"chemmap" -> Route.ChemMap
|
||||
"biomap" -> Route.BioMap
|
||||
"matyears" -> Route.MatYears
|
||||
"mat0map" -> Route.Mat0Map
|
||||
"matmap" -> Route.MatMap
|
||||
"mat2map" -> Route.Mat2Map
|
||||
"mat3map" -> Route.Mat3Map
|
||||
"mat4map" -> Route.Mat4Map
|
||||
"fyzyears" -> Route.FyzYears
|
||||
"fyzmap" -> Route.FyzMap
|
||||
"fyz2map" -> Route.Fyz2Map
|
||||
|
|
@ -229,6 +270,36 @@ fun routeFromPage(page: String): Route? = when (page) {
|
|||
Regex("""bioex(\d+)""").matchEntire(page)?.let {
|
||||
return Route.BioEx(it.groupValues[1].toInt())
|
||||
}
|
||||
Regex("""mat0unit(\d+)""").matchEntire(page)?.let {
|
||||
return Route.Mat0Lesson(it.groupValues[1].toInt())
|
||||
}
|
||||
Regex("""mat0ex(\d+)""").matchEntire(page)?.let {
|
||||
return Route.Mat0Ex(it.groupValues[1].toInt())
|
||||
}
|
||||
Regex("""mat4unit(\d+)""").matchEntire(page)?.let {
|
||||
return Route.Mat4Lesson(it.groupValues[1].toInt())
|
||||
}
|
||||
Regex("""mat4ex(\d+)""").matchEntire(page)?.let {
|
||||
return Route.Mat4Ex(it.groupValues[1].toInt())
|
||||
}
|
||||
Regex("""mat3unit(\d+)""").matchEntire(page)?.let {
|
||||
return Route.Mat3Lesson(it.groupValues[1].toInt())
|
||||
}
|
||||
Regex("""mat3ex(\d+)""").matchEntire(page)?.let {
|
||||
return Route.Mat3Ex(it.groupValues[1].toInt())
|
||||
}
|
||||
Regex("""mat2unit(\d+)""").matchEntire(page)?.let {
|
||||
return Route.Mat2Lesson(it.groupValues[1].toInt())
|
||||
}
|
||||
Regex("""mat2ex(\d+)""").matchEntire(page)?.let {
|
||||
return Route.Mat2Ex(it.groupValues[1].toInt())
|
||||
}
|
||||
Regex("""matunit(\d+)""").matchEntire(page)?.let {
|
||||
return Route.MatLesson(it.groupValues[1].toInt())
|
||||
}
|
||||
Regex("""matex(\d+)""").matchEntire(page)?.let {
|
||||
return Route.MatEx(it.groupValues[1].toInt())
|
||||
}
|
||||
Regex("""fyz4unit(\d+)""").matchEntire(page)?.let {
|
||||
return Route.Fyz4Lesson(it.groupValues[1].toInt())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,3 +63,656 @@ fun hasUmlaut(s: String?): Boolean {
|
|||
return s.any { it == 'ä' || it == 'ö' || it == 'ü' || it == 'ß' ||
|
||||
it == 'Ä' || it == 'Ö' || it == 'Ü' }
|
||||
}
|
||||
|
||||
// Same rules as src/math_check.c. Do not route numbers through normalizeAnswer.
|
||||
|
||||
private data class MFrac(val n: Long = 0, val d: Long = 1)
|
||||
|
||||
private class MTerm(
|
||||
var ok: Boolean = true,
|
||||
var a: MFrac = MFrac(),
|
||||
var b: MFrac = MFrac(),
|
||||
var rad: Long = 0,
|
||||
var p: MFrac = MFrac(),
|
||||
)
|
||||
|
||||
private fun mGcd(a0: Long, b0: Long): Long {
|
||||
var a = if (a0 < 0) -a0 else a0
|
||||
var b = if (b0 < 0) -b0 else b0
|
||||
while (b != 0L) {
|
||||
val t = a % b
|
||||
a = b
|
||||
b = t
|
||||
}
|
||||
return if (a == 0L) 1 else a
|
||||
}
|
||||
|
||||
private fun mMul(a: Long, b: Long): Long? = try {
|
||||
Math.multiplyExact(a, b)
|
||||
} catch (_: ArithmeticException) {
|
||||
null
|
||||
}
|
||||
|
||||
private fun mAddL(a: Long, b: Long): Long? = try {
|
||||
Math.addExact(a, b)
|
||||
} catch (_: ArithmeticException) {
|
||||
null
|
||||
}
|
||||
|
||||
private fun mSet(n0: Long, d0: Long): MFrac? {
|
||||
if (d0 == 0L) return null
|
||||
var n = n0
|
||||
var d = d0
|
||||
if (d < 0) {
|
||||
n = -n
|
||||
d = -d
|
||||
}
|
||||
val g = mGcd(n, d)
|
||||
return MFrac(n / g, d / g)
|
||||
}
|
||||
|
||||
private fun mAddF(x: MFrac, y: MFrac): MFrac? {
|
||||
val xn = mMul(x.n, y.d) ?: return null
|
||||
val yn = mMul(y.n, x.d) ?: return null
|
||||
val d = mMul(x.d, y.d) ?: return null
|
||||
val s = mAddL(xn, yn) ?: return null
|
||||
return mSet(s, d)
|
||||
}
|
||||
|
||||
private fun mMulF(x: MFrac, y: MFrac): MFrac? {
|
||||
val n = mMul(x.n, y.n) ?: return null
|
||||
val d = mMul(x.d, y.d) ?: return null
|
||||
return mSet(n, d)
|
||||
}
|
||||
|
||||
private fun mDivF(x: MFrac, y: MFrac): MFrac? {
|
||||
if (y.n == 0L) return null
|
||||
return mMulF(x, MFrac(y.d, y.n))
|
||||
}
|
||||
|
||||
private fun mSameF(x: MFrac, y: MFrac) = x.n == y.n && x.d == y.d
|
||||
|
||||
private fun mPow(b: Long, e: Int): Long {
|
||||
var r = 1L
|
||||
var i = 0
|
||||
while (i < e) {
|
||||
r *= b
|
||||
i++
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
private fun mSqrtReduce(n0: Long): Pair<Long, Long>? {
|
||||
if (n0 < 0 || n0 > 1_000_000) return null
|
||||
var n = n0
|
||||
var out = 1L
|
||||
var f = 2L
|
||||
while (f * f <= n) {
|
||||
var c = 0
|
||||
while (n % f == 0L) {
|
||||
n /= f
|
||||
c++
|
||||
}
|
||||
if (c >= 2) out *= mPow(f, c / 2)
|
||||
if (c % 2 == 1) n *= f
|
||||
f = if (f == 2L) 3 else f + 2
|
||||
if (f > 10000) break
|
||||
}
|
||||
return out to n
|
||||
}
|
||||
|
||||
private fun mNorm(t: MTerm) {
|
||||
if (!t.ok) return
|
||||
if (t.rad > 1) {
|
||||
val s = mSqrtReduce(t.rad) ?: run { t.ok = false; return }
|
||||
t.b = mMulF(t.b, MFrac(s.first, 1)) ?: run { t.ok = false; return }
|
||||
t.rad = s.second
|
||||
}
|
||||
if (t.rad <= 1) {
|
||||
t.a = mAddF(t.a, t.b) ?: run { t.ok = false; return }
|
||||
t.b = MFrac()
|
||||
t.rad = 0
|
||||
}
|
||||
if (t.b.n == 0L) t.rad = 0
|
||||
}
|
||||
|
||||
private fun mFail() = MTerm(ok = false)
|
||||
|
||||
private fun mAdd(x: MTerm, y: MTerm): MTerm {
|
||||
if (!x.ok || !y.ok) return mFail()
|
||||
val o = MTerm()
|
||||
o.a = mAddF(x.a, y.a) ?: return mFail()
|
||||
o.p = mAddF(x.p, y.p) ?: return mFail()
|
||||
if (x.rad == 0L && y.rad == 0L) {
|
||||
o.b = MFrac()
|
||||
o.rad = 0
|
||||
} else if (x.rad == 0L) {
|
||||
o.b = y.b
|
||||
o.rad = y.rad
|
||||
} else if (y.rad == 0L || x.rad == y.rad) {
|
||||
o.b = mAddF(x.b, y.b) ?: return mFail()
|
||||
o.rad = x.rad
|
||||
} else return mFail()
|
||||
mNorm(o)
|
||||
return o
|
||||
}
|
||||
|
||||
private fun mScale(x: MTerm, f: MFrac): MTerm {
|
||||
if (!x.ok) return mFail()
|
||||
val o = MTerm()
|
||||
o.a = mMulF(x.a, f) ?: return mFail()
|
||||
o.b = mMulF(x.b, f) ?: return mFail()
|
||||
o.p = mMulF(x.p, f) ?: return mFail()
|
||||
o.rad = x.rad
|
||||
mNorm(o)
|
||||
return o
|
||||
}
|
||||
|
||||
private fun mMulT(x: MTerm, y: MTerm): MTerm {
|
||||
if (!x.ok || !y.ok) return mFail()
|
||||
if ((x.p.n != 0L && (y.p.n != 0L || y.rad != 0L)) ||
|
||||
(y.p.n != 0L && (x.p.n != 0L || x.rad != 0L))
|
||||
) return mFail()
|
||||
if (x.rad != 0L && y.rad != 0L && x.rad != y.rad) return mFail()
|
||||
val o = MTerm()
|
||||
o.a = mMulF(x.a, y.a) ?: return mFail()
|
||||
if (x.rad == 0L && y.rad == 0L) {
|
||||
o.b = MFrac()
|
||||
o.rad = 0
|
||||
} else if (x.rad == 0L) {
|
||||
o.b = mMulF(x.a, y.b) ?: return mFail()
|
||||
o.rad = y.rad
|
||||
} else if (y.rad == 0L) {
|
||||
o.b = mMulF(y.a, x.b) ?: return mFail()
|
||||
o.rad = x.rad
|
||||
} else {
|
||||
val ab = mMulF(x.a, y.b) ?: return mFail()
|
||||
val ba = mMulF(x.b, y.a) ?: return mFail()
|
||||
val cross = mAddF(ab, ba) ?: return mFail()
|
||||
val sq = mMul(x.rad, y.rad) ?: return mFail()
|
||||
val bb = mMulF(x.b, y.b) ?: return mFail()
|
||||
val extra = mMulF(bb, MFrac(sq, 1)) ?: return mFail()
|
||||
o.a = mAddF(o.a, extra) ?: return mFail()
|
||||
o.b = cross
|
||||
o.rad = x.rad
|
||||
}
|
||||
o.p = if (x.p.n != 0L) x.p else y.p
|
||||
if ((y.a.n != 1L || y.a.d != 1L || y.b.n != 0L || y.rad != 0L || y.p.n != 0L) && x.p.n != 0L) {
|
||||
o.p = mMulF(x.p, y.a) ?: return mFail()
|
||||
}
|
||||
if ((x.a.n != 1L || x.a.d != 1L || x.b.n != 0L || x.rad != 0L || x.p.n != 0L) &&
|
||||
y.p.n != 0L && x.p.n == 0L
|
||||
) {
|
||||
o.p = mMulF(y.p, x.a) ?: return mFail()
|
||||
}
|
||||
mNorm(o)
|
||||
return o
|
||||
}
|
||||
|
||||
private fun mDivT(x: MTerm, y: MTerm): MTerm {
|
||||
if (!x.ok || !y.ok || y.p.n != 0L) return mFail()
|
||||
if (y.rad == 0L) {
|
||||
val f = mDivF(MFrac(1, 1), y.a) ?: return mFail()
|
||||
return mScale(x, f)
|
||||
}
|
||||
if (x.p.n != 0L) return mFail()
|
||||
val yb = mMulF(y.b, MFrac(y.rad, 1)) ?: return mFail()
|
||||
val bb = mMulF(y.b, yb) ?: return mFail()
|
||||
val aa = mMulF(y.a, y.a) ?: return mFail()
|
||||
val den = mAddF(aa, MFrac(-bb.n, bb.d)) ?: return mFail()
|
||||
val num = MTerm()
|
||||
num.a = y.a
|
||||
num.b = MFrac(-y.b.n, y.b.d)
|
||||
num.rad = y.rad
|
||||
val inv = mDivF(MFrac(1, 1), den) ?: return mFail()
|
||||
return mMulT(x, mScale(num, inv))
|
||||
}
|
||||
|
||||
private sealed class MTok {
|
||||
class Num(val n: Long, val d: Long) : MTok()
|
||||
object Sqrt : MTok()
|
||||
object Pi : MTok()
|
||||
object Add : MTok()
|
||||
object Sub : MTok()
|
||||
object Mul : MTok()
|
||||
object Div : MTok()
|
||||
object Lp : MTok()
|
||||
object Rp : MTok()
|
||||
}
|
||||
|
||||
private fun mLex(s: String): List<MTok>? {
|
||||
val toks = ArrayList<MTok>()
|
||||
var i = 0
|
||||
fun peek(k: Int) = if (i + k < s.length) s[i + k] else null
|
||||
while (i < s.length) {
|
||||
while (i < s.length && s[i].isWhitespace()) i++
|
||||
if (i >= s.length) break
|
||||
val ch = s[i]
|
||||
if (ch.isDigit() || ((ch == '.' || ch == ',') && peek(1)?.isDigit() == true)) {
|
||||
var whole = 0L
|
||||
var saw = false
|
||||
while (i < s.length && s[i].isDigit()) {
|
||||
saw = true
|
||||
whole = whole * 10 + (s[i] - '0')
|
||||
i++
|
||||
}
|
||||
if (i < s.length && (s[i] == '.' || s[i] == ',') && i + 1 < s.length && s[i + 1].isDigit()) {
|
||||
i++
|
||||
var frac = 0L
|
||||
var div = 1L
|
||||
while (i < s.length && s[i].isDigit()) {
|
||||
frac = frac * 10 + (s[i] - '0')
|
||||
div *= 10
|
||||
i++
|
||||
}
|
||||
val n = mAddL(mMul(whole, div) ?: return null, frac) ?: return null
|
||||
toks.add(MTok.Num(n, div))
|
||||
} else if (saw) {
|
||||
toks.add(MTok.Num(whole, 1))
|
||||
} else return null
|
||||
continue
|
||||
}
|
||||
when (ch) {
|
||||
'+' -> toks.add(MTok.Add)
|
||||
'-', '\u2212', '\u2013' -> toks.add(MTok.Sub)
|
||||
'*', '\u00D7', '\u00B7' -> toks.add(MTok.Mul)
|
||||
'/', ':' -> toks.add(MTok.Div)
|
||||
'(' -> toks.add(MTok.Lp)
|
||||
')' -> toks.add(MTok.Rp)
|
||||
'\u03C0' -> toks.add(MTok.Pi)
|
||||
'\u221A' -> toks.add(MTok.Sqrt)
|
||||
else -> {
|
||||
if (ch in 'A'..'Z' || ch in 'a'..'z') {
|
||||
val start = i
|
||||
while (i < s.length && (s[i] in 'A'..'Z' || s[i] in 'a'..'z')) i++
|
||||
when (s.substring(start, i).lowercase()) {
|
||||
"pi" -> toks.add(MTok.Pi)
|
||||
"sqrt" -> toks.add(MTok.Sqrt)
|
||||
else -> return null
|
||||
}
|
||||
continue
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
return toks
|
||||
}
|
||||
|
||||
private class MParse(val t: List<MTok>) {
|
||||
var i = 0
|
||||
fun peek() = if (i < t.size) t[i] else null
|
||||
fun take(): MTok? = if (i < t.size) t[i++] else null
|
||||
fun at(k: Int) = if (k in t.indices) t[k] else null
|
||||
|
||||
fun expr(): MTerm? {
|
||||
var left = term() ?: return null
|
||||
while (true) {
|
||||
val p = peek()
|
||||
if (p !is MTok.Add && p !is MTok.Sub) break
|
||||
take()
|
||||
val r = term() ?: return null
|
||||
left = if (p is MTok.Sub) mAdd(left, mScale(r, MFrac(-1, 1))) else mAdd(left, r)
|
||||
if (!left.ok) return null
|
||||
}
|
||||
return left
|
||||
}
|
||||
|
||||
fun term(): MTerm? {
|
||||
var left = unary() ?: return null
|
||||
while (true) {
|
||||
val p = peek()
|
||||
if (p !is MTok.Mul && p !is MTok.Div) break
|
||||
take()
|
||||
val r = unary() ?: return null
|
||||
left = if (p is MTok.Div) mDivT(left, r) else mMulT(left, r)
|
||||
if (!left.ok) return null
|
||||
}
|
||||
return left
|
||||
}
|
||||
|
||||
fun unary(): MTerm? {
|
||||
if (peek() is MTok.Sub) {
|
||||
take()
|
||||
val v = unary() ?: return null
|
||||
return mScale(v, MFrac(-1, 1))
|
||||
}
|
||||
if (peek() is MTok.Add) {
|
||||
take()
|
||||
return unary()
|
||||
}
|
||||
return factor()
|
||||
}
|
||||
|
||||
fun factor(): MTerm? {
|
||||
var left = primary() ?: return null
|
||||
while (true) {
|
||||
when (peek()) {
|
||||
is MTok.Sqrt, is MTok.Pi, is MTok.Lp -> {
|
||||
val r = primary() ?: return null
|
||||
left = mMulT(left, r)
|
||||
if (!left.ok) return null
|
||||
}
|
||||
is MTok.Num -> return null
|
||||
else -> return left
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun primary(): MTerm? {
|
||||
when (val p = take()) {
|
||||
is MTok.Num -> {
|
||||
val n = p.n
|
||||
val d = p.d
|
||||
if (peek() is MTok.Div && at(i + 1) is MTok.Num) {
|
||||
take()
|
||||
val n2 = take() as? MTok.Num ?: return null
|
||||
if (n2.n <= 0 || n2.d != 1L || d != 1L) return null
|
||||
val o = MTerm()
|
||||
o.a = mSet(n, n2.n) ?: return null
|
||||
return o
|
||||
}
|
||||
if (d == 1L && n >= 0 && peek() is MTok.Num) {
|
||||
val saved = i
|
||||
val n2 = take() as MTok.Num
|
||||
val n3tok = at(i + 1)
|
||||
if (peek() is MTok.Div && n3tok is MTok.Num && n2.d == 1L && n3tok.d == 1L && n2.n >= 0 && n3tok.n > 0) {
|
||||
take()
|
||||
take()
|
||||
val frac = mSet(n2.n, n3tok.n) ?: return null
|
||||
val whole = MTerm()
|
||||
whole.a = MFrac(n, 1)
|
||||
val part = MTerm()
|
||||
part.a = frac
|
||||
return mAdd(whole, part)
|
||||
}
|
||||
i = saved
|
||||
}
|
||||
val o = MTerm()
|
||||
o.a = mSet(n, d) ?: return null
|
||||
return o
|
||||
}
|
||||
is MTok.Pi -> {
|
||||
val o = MTerm()
|
||||
o.p = MFrac(1, 1)
|
||||
return o
|
||||
}
|
||||
is MTok.Sqrt -> {
|
||||
if (peek() is MTok.Lp) {
|
||||
take()
|
||||
val inner = expr() ?: return null
|
||||
if (take() !is MTok.Rp) return null
|
||||
return mSqrtTerm(inner)
|
||||
}
|
||||
val num = peek()
|
||||
if (num is MTok.Num && num.d == 1L && num.n >= 0) {
|
||||
take()
|
||||
val inner = MTerm()
|
||||
inner.a = MFrac(num.n, 1)
|
||||
return mSqrtTerm(inner)
|
||||
}
|
||||
return null
|
||||
}
|
||||
is MTok.Lp -> {
|
||||
val inner = expr() ?: return null
|
||||
if (take() !is MTok.Rp) return null
|
||||
return inner
|
||||
}
|
||||
else -> return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun mSqrtTerm(inner: MTerm): MTerm? {
|
||||
if (!inner.ok || inner.p.n != 0L || inner.rad != 0L || inner.b.n != 0L) return null
|
||||
if (inner.a.n < 0 || inner.a.d != 1L) return null
|
||||
val s = mSqrtReduce(inner.a.n) ?: return null
|
||||
val o = MTerm()
|
||||
o.b = MFrac(s.first, 1)
|
||||
o.rad = s.second
|
||||
mNorm(o)
|
||||
return if (o.ok) o else null
|
||||
}
|
||||
|
||||
private fun mStripUnits(raw: String): String {
|
||||
var s = raw.trim()
|
||||
val units = listOf("procenta", "procent", "cm", "mm", "dm", "km", "m")
|
||||
var cut = true
|
||||
while (cut) {
|
||||
cut = false
|
||||
s = s.trimEnd()
|
||||
if (s.endsWith("°") || s.endsWith("%") || s.endsWith(".")) {
|
||||
s = s.dropLast(1)
|
||||
cut = true
|
||||
continue
|
||||
}
|
||||
for (u in units) {
|
||||
if (s.length <= u.length) continue
|
||||
if (s.lowercase().endsWith(u)) {
|
||||
val keep = s.dropLast(u.length)
|
||||
if (keep.lastOrNull()?.isLetter() == true) continue
|
||||
s = keep
|
||||
cut = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return s.trim()
|
||||
}
|
||||
|
||||
private fun mPrep(raw: String): String {
|
||||
var s = raw.trim()
|
||||
val pairs = listOf('(' to ')', '[' to ']', '{' to '}')
|
||||
while (s.length >= 2 && pairs.any { it.first == s.first() && it.second == s.last() }) {
|
||||
s = s.substring(1, s.length - 1).trim()
|
||||
}
|
||||
return mStripUnits(s).trim()
|
||||
}
|
||||
|
||||
private fun mParseTerm(s: String): MTerm? {
|
||||
val text = mPrep(s)
|
||||
if (text.isEmpty()) return null
|
||||
val toks = mLex(text) ?: return null
|
||||
if (toks.isEmpty()) return null
|
||||
val p = MParse(toks)
|
||||
val v = p.expr() ?: return null
|
||||
if (p.i != toks.size || !v.ok) return null
|
||||
return v
|
||||
}
|
||||
|
||||
private fun mExact(x: MTerm, y: MTerm) =
|
||||
x.ok && y.ok && mSameF(x.a, y.a) && mSameF(x.b, y.b) && x.rad == y.rad && mSameF(x.p, y.p)
|
||||
|
||||
private fun mDouble(t: MTerm): Double {
|
||||
var v = t.a.n.toDouble() / t.a.d + t.p.n.toDouble() / t.p.d * Math.PI
|
||||
if (t.b.n != 0L && t.rad > 0) v += t.b.n.toDouble() / t.b.d * Math.sqrt(t.rad.toDouble())
|
||||
return v
|
||||
}
|
||||
|
||||
private fun mClose(x: MTerm, y: MTerm): Boolean {
|
||||
if (!x.ok || !y.ok) return false
|
||||
if (mExact(x, y)) return true
|
||||
if (x.p.n != 0L || y.p.n != 0L) return false
|
||||
val diff = Math.abs(mDouble(x) - mDouble(y))
|
||||
return if (x.b.n != 0L || y.b.n != 0L) diff <= 0.001 else diff <= 0.0005
|
||||
}
|
||||
|
||||
private fun mFold(s: String): String {
|
||||
val out = StringBuilder()
|
||||
for (ch in s.lowercase()) {
|
||||
when (ch) {
|
||||
'á', 'à', 'ä', 'â' -> out.append('a')
|
||||
'č', 'ć' -> out.append('c')
|
||||
'ď' -> out.append('d')
|
||||
'é', 'ě', 'ë', 'ê' -> out.append('e')
|
||||
'í', 'ï' -> out.append('i')
|
||||
'ň' -> out.append('n')
|
||||
'ó', 'ö', 'ô' -> out.append('o')
|
||||
'ř' -> out.append('r')
|
||||
'š', 'ś' -> out.append('s')
|
||||
'ť' -> out.append('t')
|
||||
'ú', 'ů', 'ü' -> out.append('u')
|
||||
'ý' -> out.append('y')
|
||||
'ž', 'ź' -> out.append('z')
|
||||
else -> if (ch.isLetter() || ch.isDigit()) out.append(ch)
|
||||
}
|
||||
}
|
||||
return out.toString()
|
||||
}
|
||||
|
||||
private fun mSplit(raw: String): List<String>? {
|
||||
val s = raw.trim()
|
||||
if (s.isEmpty()) return null
|
||||
val semi = s.contains(';')
|
||||
val parts = ArrayList<String>()
|
||||
var rest = s
|
||||
while (rest.isNotEmpty()) {
|
||||
val idx = if (semi) rest.indexOf(';') else rest.indexOf(", ")
|
||||
val step = if (semi) 1 else 2
|
||||
val piece: String
|
||||
val next: String
|
||||
if (idx < 0) {
|
||||
piece = rest.trim()
|
||||
next = ""
|
||||
} else {
|
||||
piece = rest.substring(0, idx).trim()
|
||||
next = rest.substring(idx + step)
|
||||
}
|
||||
if (piece.isEmpty()) return null
|
||||
parts.add(piece)
|
||||
if (idx < 0) break
|
||||
rest = next
|
||||
}
|
||||
return parts.ifEmpty { null }
|
||||
}
|
||||
|
||||
fun mathAnswerOk(spec: String?, user: String?): Boolean {
|
||||
if (spec == null || user == null) return false
|
||||
val bodySpec = spec.trim()
|
||||
if (bodySpec.startsWith("word:")) {
|
||||
val opts = bodySpec.removePrefix("word:").split('|').map { mFold(it) }
|
||||
return opts.contains(mFold(user))
|
||||
}
|
||||
if (bodySpec.startsWith("set:") || bodySpec.startsWith("pair:")) {
|
||||
val ordered = bodySpec.startsWith("pair:")
|
||||
val body = bodySpec.removePrefix(if (ordered) "pair:" else "set:")
|
||||
val want = mSplit(body) ?: return false
|
||||
val got = mSplit(user) ?: return false
|
||||
if (want.size != got.size || want.isEmpty()) return false
|
||||
val wt = want.map { mParseTerm(it) ?: return false }
|
||||
val gt = got.map { mParseTerm(it) ?: return false }
|
||||
if (ordered) return wt.indices.all { mClose(wt[it], gt[it]) }
|
||||
val used = BooleanArray(gt.size)
|
||||
for (w in wt) {
|
||||
var hit = gt.indices.firstOrNull { !used[it] && mExact(w, gt[it]) }
|
||||
if (hit == null) hit = gt.indices.firstOrNull { !used[it] && mClose(w, gt[it]) }
|
||||
if (hit == null) return false
|
||||
used[hit] = true
|
||||
}
|
||||
return true
|
||||
}
|
||||
if (!bodySpec.startsWith("num:")) return false
|
||||
val tw = mParseTerm(bodySpec.removePrefix("num:")) ?: return false
|
||||
val tg = mParseTerm(user) ?: return false
|
||||
return mClose(tw, tg)
|
||||
}
|
||||
|
||||
fun mathIsDraw(spec: String?): Boolean {
|
||||
if (spec == null) return false
|
||||
return spec.startsWith("square:") || spec.startsWith("mid:") || spec.startsWith("right:") ||
|
||||
spec.startsWith("para:") || spec.startsWith("points:")
|
||||
}
|
||||
|
||||
fun mathDrawNeed(spec: String?): Int {
|
||||
if (spec == null) return 0
|
||||
if (spec.startsWith("square:")) return 2
|
||||
if (spec.startsWith("mid:") || spec.startsWith("right:") || spec.startsWith("para:")) return 1
|
||||
if (spec.startsWith("points:")) {
|
||||
return spec.removePrefix("points:").split(';').count { !it.startsWith("fix:") }
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
private fun mParseXY(s: String): Pair<Int, Int>? {
|
||||
val bits = s.trim().split(',')
|
||||
if (bits.size != 2) return null
|
||||
val x = bits[0].trim().toIntOrNull() ?: return null
|
||||
val y = bits[1].trim().toIntOrNull() ?: return null
|
||||
return x to y
|
||||
}
|
||||
|
||||
fun mathDrawGiven(spec: String?): List<Pair<Int, Int>> {
|
||||
if (spec == null) return emptyList()
|
||||
if (spec.startsWith("points:")) {
|
||||
return spec.removePrefix("points:").split(';').mapNotNull { part ->
|
||||
if (!part.startsWith("fix:")) null else mParseXY(part.removePrefix("fix:"))
|
||||
}
|
||||
}
|
||||
val body: String
|
||||
val n: Int
|
||||
when {
|
||||
spec.startsWith("para:") -> { body = spec.removePrefix("para:"); n = 3 }
|
||||
spec.startsWith("square:") -> { body = spec.removePrefix("square:"); n = 2 }
|
||||
spec.startsWith("right:") -> { body = spec.removePrefix("right:"); n = 2 }
|
||||
spec.startsWith("mid:") -> { body = spec.removePrefix("mid:"); n = 2 }
|
||||
else -> return emptyList()
|
||||
}
|
||||
return body.split(';').take(n).mapNotNull { mParseXY(it) }
|
||||
}
|
||||
|
||||
private fun mOnBoard(p: Pair<Int, Int>) = p.first in 0..10 && p.second in 0..8
|
||||
|
||||
fun mathDrawOk(spec: String?, pts: List<Pair<Int, Int>>): Boolean {
|
||||
if (spec == null || mathDrawNeed(spec) != pts.size) return false
|
||||
if (pts.any { !mOnBoard(it) }) return false
|
||||
if (pts.map { "${it.first},${it.second}" }.toSet().size != pts.size) return false
|
||||
val g = mathDrawGiven(spec)
|
||||
if (spec.startsWith("mid:")) {
|
||||
if (g.size != 2 || (g[0].first + g[1].first) % 2 != 0 || (g[0].second + g[1].second) % 2 != 0) return false
|
||||
return pts[0] == (g[0].first + g[1].first) / 2 to (g[0].second + g[1].second) / 2
|
||||
}
|
||||
if (spec.startsWith("para:")) {
|
||||
if (g.size != 3) return false
|
||||
return pts[0] == g[1].first + g[2].first - g[0].first to g[1].second + g[2].second - g[0].second
|
||||
}
|
||||
if (spec.startsWith("right:")) {
|
||||
if (g.size != 2) return false
|
||||
val c = pts[0]
|
||||
if (c == g[0] || c == g[1]) return false
|
||||
val ux = g[0].first - c.first
|
||||
val uy = g[0].second - c.second
|
||||
val vx = g[1].first - c.first
|
||||
val vy = g[1].second - c.second
|
||||
return ux * vx + uy * vy == 0 && (ux != 0 || uy != 0) && (vx != 0 || vy != 0)
|
||||
}
|
||||
if (spec.startsWith("square:")) {
|
||||
if (g.size != 2 || pts.size != 2) return false
|
||||
val a = g[0]
|
||||
val b = g[1]
|
||||
val dx = b.first - a.first
|
||||
val dy = b.second - a.second
|
||||
if (dx == 0 && dy == 0) return false
|
||||
val c1 = b.first - dy to b.second + dx
|
||||
val d1 = a.first - dy to a.second + dx
|
||||
val c2 = b.first + dy to b.second - dx
|
||||
val d2 = a.first + dy to a.second - dx
|
||||
fun same(p: Pair<Int, Int>, q: Pair<Int, Int>, r: Pair<Int, Int>, s: Pair<Int, Int>) =
|
||||
(p == r && q == s) || (p == s && q == r)
|
||||
if ((!mOnBoard(c1) || !mOnBoard(d1)) && (!mOnBoard(c2) || !mOnBoard(d2))) return false
|
||||
return same(pts[0], pts[1], c1, d1) || same(pts[0], pts[1], c2, d2)
|
||||
}
|
||||
if (spec.startsWith("points:")) {
|
||||
val want = spec.removePrefix("points:").split(';').mapNotNull { part ->
|
||||
if (part.startsWith("fix:")) null else mParseXY(part)
|
||||
}
|
||||
if (want.size != pts.size) return false
|
||||
val used = BooleanArray(pts.size)
|
||||
for (w in want) {
|
||||
val j = pts.indices.firstOrNull { !used[it] && pts[it] == w } ?: return false
|
||||
used[j] = true
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,11 @@ class Content(root: JSONObject) {
|
|||
val fyz2 = raw.arr("fyz2")
|
||||
val fyz3 = raw.arr("fyz3")
|
||||
val fyz4 = raw.arr("fyz4")
|
||||
val mat0 = raw.arr("mat0")
|
||||
val mat = raw.arr("mat")
|
||||
val mat2 = raw.arr("mat2")
|
||||
val mat3 = raw.arr("mat3")
|
||||
val mat4 = raw.arr("mat4")
|
||||
val mluvnice = raw.arr("mluvnice")
|
||||
val books = raw.arr("books")
|
||||
val changelog = raw.arr("changelog")
|
||||
|
|
@ -64,6 +69,11 @@ class Content(root: JSONObject) {
|
|||
fun fyz2Lesson(id: Int) = fyz2.firstOrNull { it.int("id") == id }
|
||||
fun fyz3Lesson(id: Int) = fyz3.firstOrNull { it.int("id") == id }
|
||||
fun fyz4Lesson(id: Int) = fyz4.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 }
|
||||
fun mat3Lesson(id: Int) = mat3.firstOrNull { it.int("id") == id }
|
||||
fun mat4Lesson(id: Int) = mat4.firstOrNull { it.int("id") == id }
|
||||
fun mluv(n: Int) = mluvnice.firstOrNull { it.int("id") == n }
|
||||
fun book(id: String) = books.firstOrNull { it.str("id") == id }
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,16 @@ class ProgressStore(context: Context) {
|
|||
fun markFyz3(id: Int) { prefs.edit().putBoolean("fyz3.$id", true).apply() }
|
||||
fun fyz4Done(id: Int) = prefs.getBoolean("fyz4.$id", false)
|
||||
fun markFyz4(id: Int) { prefs.edit().putBoolean("fyz4.$id", true).apply() }
|
||||
fun mat0Done(id: Int) = prefs.getBoolean("mat0.$id", false)
|
||||
fun markMat0(id: Int) { prefs.edit().putBoolean("mat0.$id", true).apply() }
|
||||
fun matDone(id: Int) = prefs.getBoolean("mat.$id", false)
|
||||
fun markMat(id: Int) { prefs.edit().putBoolean("mat.$id", true).apply() }
|
||||
fun mat2Done(id: Int) = prefs.getBoolean("mat2.$id", false)
|
||||
fun markMat2(id: Int) { prefs.edit().putBoolean("mat2.$id", true).apply() }
|
||||
fun mat3Done(id: Int) = prefs.getBoolean("mat3.$id", false)
|
||||
fun markMat3(id: Int) { prefs.edit().putBoolean("mat3.$id", true).apply() }
|
||||
fun mat4Done(id: Int) = prefs.getBoolean("mat4.$id", false)
|
||||
fun markMat4(id: Int) { prefs.edit().putBoolean("mat4.$id", true).apply() }
|
||||
|
||||
fun mluvDone(n: Int) = prefs.getBoolean("mluv.$n", false)
|
||||
fun markMluv(n: Int) { prefs.edit().putBoolean("mluv.$n", true).apply() }
|
||||
|
|
@ -216,6 +226,46 @@ fun summarize(content: Content, p: ProgressStore): ProgressSum {
|
|||
sum.doneUnits += 1
|
||||
}
|
||||
}
|
||||
content.mat0.forEach { l ->
|
||||
sum.totalEx += 1
|
||||
sum.openUnits += 1
|
||||
if (p.mat0Done(l.int("id"))) {
|
||||
sum.doneEx += 1
|
||||
sum.doneUnits += 1
|
||||
}
|
||||
}
|
||||
content.mat.forEach { l ->
|
||||
sum.totalEx += 1
|
||||
sum.openUnits += 1
|
||||
if (p.matDone(l.int("id"))) {
|
||||
sum.doneEx += 1
|
||||
sum.doneUnits += 1
|
||||
}
|
||||
}
|
||||
content.mat2.forEach { l ->
|
||||
sum.totalEx += 1
|
||||
sum.openUnits += 1
|
||||
if (p.mat2Done(l.int("id"))) {
|
||||
sum.doneEx += 1
|
||||
sum.doneUnits += 1
|
||||
}
|
||||
}
|
||||
content.mat3.forEach { l ->
|
||||
sum.totalEx += 1
|
||||
sum.openUnits += 1
|
||||
if (p.mat3Done(l.int("id"))) {
|
||||
sum.doneEx += 1
|
||||
sum.doneUnits += 1
|
||||
}
|
||||
}
|
||||
content.mat4.forEach { l ->
|
||||
sum.totalEx += 1
|
||||
sum.openUnits += 1
|
||||
if (p.mat4Done(l.int("id"))) {
|
||||
sum.doneEx += 1
|
||||
sum.doneUnits += 1
|
||||
}
|
||||
}
|
||||
sum.totalEx += content.mluvnice.size
|
||||
sum.openUnits += 1
|
||||
val md = content.mluvnice.count { p.mluvDone(it.int("id")) }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package org.maturita.maturita.ui
|
||||
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -12,6 +14,7 @@ import androidx.compose.foundation.layout.Spacer
|
|||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
|
|
@ -45,18 +48,28 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.drawText
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.rememberTextMeasurer
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import org.maturita.maturita.AppViewModel
|
||||
import org.maturita.maturita.data.J
|
||||
import org.maturita.maturita.data.answerAccepts
|
||||
import org.maturita.maturita.data.mathAnswerOk
|
||||
import org.maturita.maturita.data.mathDrawGiven
|
||||
import org.maturita.maturita.data.mathDrawNeed
|
||||
import org.maturita.maturita.data.mathDrawOk
|
||||
import org.maturita.maturita.data.mathIsDraw
|
||||
import org.maturita.maturita.data.answerIncomplete
|
||||
import org.maturita.maturita.data.hasUmlaut
|
||||
import org.maturita.maturita.data.netTxtEq
|
||||
|
|
@ -863,6 +876,198 @@ fun SciQuizScreen(vm: AppViewModel, area: String, id: Int) {
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MathPracticeScreen(vm: AppViewModel, year: Int, id: Int) {
|
||||
val lesson = when (year) {
|
||||
0 -> vm.content.mat0Lesson(id)
|
||||
2 -> vm.content.mat2Lesson(id)
|
||||
3 -> vm.content.mat3Lesson(id)
|
||||
4 -> vm.content.mat4Lesson(id)
|
||||
else -> vm.content.matLesson(id)
|
||||
} ?: return
|
||||
val problems = lesson.arr("problems")
|
||||
var texts by remember(year, id) { mutableStateOf(List(problems.size) { "" }) }
|
||||
var placed by remember(year, id) { mutableStateOf(List(problems.size) { emptyList<Pair<Int, Int>>() }) }
|
||||
var marks by remember(year, id) { mutableStateOf(List<Boolean?>(problems.size) { null }) }
|
||||
var revealed by remember(year, id) { mutableStateOf(false) }
|
||||
var feedback by remember(year, id) { mutableStateOf("") }
|
||||
var kind by remember(year, id) { mutableStateOf("") }
|
||||
ExerciseScaffold(
|
||||
vm,
|
||||
vm.tr(lesson.str("exTitleKey")),
|
||||
vm.tr(lesson.str("quizHeadKey")),
|
||||
vm.tr("check"),
|
||||
{
|
||||
var all = problems.isNotEmpty()
|
||||
val next = MutableList<Boolean?>(problems.size) { null }
|
||||
problems.forEachIndexed { i, problem ->
|
||||
val spec = problem.str("answer")
|
||||
val good = if (mathIsDraw(spec)) mathDrawOk(spec, placed[i]) else mathAnswerOk(spec, texts[i])
|
||||
next[i] = good
|
||||
if (!good) all = false
|
||||
}
|
||||
marks = next
|
||||
revealed = true
|
||||
if (all) {
|
||||
feedback = vm.tr("feedback_ok")
|
||||
kind = "ok"
|
||||
when (year) {
|
||||
0 -> vm.markMat0(id)
|
||||
2 -> vm.markMat2(id)
|
||||
3 -> vm.markMat3(id)
|
||||
4 -> vm.markMat4(id)
|
||||
else -> vm.markMat(id)
|
||||
}
|
||||
} else {
|
||||
feedback = vm.tr("feedback_retry_short")
|
||||
kind = "bad"
|
||||
}
|
||||
},
|
||||
feedback,
|
||||
kind,
|
||||
) {
|
||||
Text(
|
||||
vm.tr("math_quiz_intro"),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
problems.forEachIndexed { i, problem ->
|
||||
val spec = problem.str("answer")
|
||||
val border = when (marks.getOrNull(i)) {
|
||||
true -> Color(0xFF2E7D32)
|
||||
false -> MaterialTheme.colorScheme.error
|
||||
else -> MaterialTheme.colorScheme.outlineVariant
|
||||
}
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 10.dp)
|
||||
.border(1.5.dp, border, MaterialTheme.shapes.medium)
|
||||
.padding(12.dp),
|
||||
) {
|
||||
Text(problem.str("prompt"), style = MaterialTheme.typography.bodyLarge)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
if (mathIsDraw(spec)) {
|
||||
Text(
|
||||
vm.tr("math_draw_hint"),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
Spacer(Modifier.height(6.dp))
|
||||
MathBoard(mathDrawGiven(spec), placed[i], mathDrawNeed(spec), marks.getOrNull(i)) { next ->
|
||||
placed = placed.toMutableList().also { it[i] = next }
|
||||
}
|
||||
Row {
|
||||
OutlinedButton(onClick = {
|
||||
val next = placed[i].dropLast(1)
|
||||
placed = placed.toMutableList().also { it[i] = next }
|
||||
}) { Text(vm.tr("math_undo")) }
|
||||
Spacer(Modifier.width(8.dp))
|
||||
OutlinedButton(onClick = {
|
||||
placed = placed.toMutableList().also { it[i] = emptyList() }
|
||||
}) { Text(vm.tr("math_clear")) }
|
||||
}
|
||||
} else {
|
||||
OutlinedTextField(
|
||||
value = texts[i],
|
||||
onValueChange = { value -> texts = texts.toMutableList().also { it[i] = value } },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
placeholder = { Text(vm.tr("math_answer_ph")) },
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
if (revealed) {
|
||||
Spacer(Modifier.height(6.dp))
|
||||
Text(
|
||||
problem.str("hint"),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MathBoard(
|
||||
given: List<Pair<Int, Int>>,
|
||||
user: List<Pair<Int, Int>>,
|
||||
need: Int,
|
||||
mark: Boolean?,
|
||||
onChange: (List<Pair<Int, Int>>) -> Unit,
|
||||
) {
|
||||
val measurer = rememberTextMeasurer()
|
||||
val accent = MaterialTheme.colorScheme.primary
|
||||
val ink = MaterialTheme.colorScheme.onSurface
|
||||
val grid = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
val paper = MaterialTheme.colorScheme.surfaceVariant
|
||||
val ok = Color(0xFF2E7D32)
|
||||
val bad = MaterialTheme.colorScheme.error
|
||||
Canvas(
|
||||
Modifier
|
||||
.size(316.dp, 260.dp)
|
||||
.pointerInput(given, user, need) {
|
||||
detectTapGestures { pos ->
|
||||
val cell = size.width.toFloat() / (10f + 36f / 28f)
|
||||
val pad = cell * 18f / 28f
|
||||
val cx = (pos.x - pad) / cell
|
||||
val cy = (size.height.toFloat() - pad - pos.y) / cell
|
||||
val ix = kotlin.math.round(cx)
|
||||
val iy = kotlin.math.round(cy)
|
||||
if (kotlin.math.abs(cx - ix) > 0.45f || kotlin.math.abs(cy - iy) > 0.45f) return@detectTapGestures
|
||||
val x = ix.toInt()
|
||||
val y = iy.toInt()
|
||||
if (x !in 0..10 || y !in 0..8) return@detectTapGestures
|
||||
val hit = x to y
|
||||
if (given.any { it == hit }) return@detectTapGestures
|
||||
val next = user.toMutableList()
|
||||
val idx = next.indexOf(hit)
|
||||
if (idx >= 0) next.removeAt(idx)
|
||||
else if (next.size < need) next.add(hit)
|
||||
else return@detectTapGestures
|
||||
onChange(next)
|
||||
}
|
||||
},
|
||||
) {
|
||||
val cell = size.width / (10f + 36f / 28f)
|
||||
val pad = cell * 18f / 28f
|
||||
fun at(x: Int, y: Int) = Offset(pad + x * cell, size.height - pad - y * cell)
|
||||
drawRect(paper)
|
||||
for (i in 0..10) {
|
||||
val x = pad + i * cell
|
||||
drawLine(grid.copy(alpha = 0.55f), Offset(x, pad), Offset(x, size.height - pad), strokeWidth = 1f)
|
||||
}
|
||||
for (i in 0..8) {
|
||||
val y = pad + i * cell
|
||||
drawLine(grid.copy(alpha = 0.55f), Offset(pad, y), Offset(size.width - pad, y), strokeWidth = 1f)
|
||||
}
|
||||
drawLine(ink, Offset(pad, size.height - pad), Offset(size.width - pad, size.height - pad), strokeWidth = 2.4f)
|
||||
drawLine(ink, Offset(pad, size.height - pad), Offset(pad, pad), strokeWidth = 2.4f)
|
||||
if (given.size >= 2) {
|
||||
drawLine(accent, at(given[0].first, given[0].second), at(given[1].first, given[1].second), strokeWidth = 3.5f)
|
||||
if (given.size >= 3) {
|
||||
drawLine(accent, at(given[0].first, given[0].second), at(given[2].first, given[2].second), strokeWidth = 3.5f)
|
||||
}
|
||||
}
|
||||
val names = listOf("A", "B", "C", "D")
|
||||
val label = TextStyle(color = ink, fontSize = 13.sp)
|
||||
given.forEachIndexed { i, g ->
|
||||
if (i >= names.size) return@forEachIndexed
|
||||
val pt = at(g.first, g.second)
|
||||
drawCircle(accent, radius = 7f, center = pt)
|
||||
drawText(measurer, names[i], topLeft = Offset(pt.x + 8f, pt.y - 22f), style = label)
|
||||
}
|
||||
val dot = if (mark == false) bad else ok
|
||||
user.forEachIndexed { i, u ->
|
||||
val pt = at(u.first, u.second)
|
||||
drawCircle(dot, radius = 7f, center = pt)
|
||||
drawText(measurer, "${i + 1}", topLeft = Offset(pt.x + 8f, pt.y - 22f), style = label)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FyzQuizScreen(vm: AppViewModel, year: Int, id: Int) {
|
||||
val lesson = when (year) {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import androidx.compose.material.icons.Icons
|
|||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.filled.MenuBook
|
||||
import androidx.compose.material.icons.filled.Bolt
|
||||
import androidx.compose.material.icons.filled.Functions
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.FormatListNumbered
|
||||
|
|
@ -224,6 +225,7 @@ private fun SubjectIcon(icon: String, open: Boolean) {
|
|||
"cz" -> CzechFlag(28.dp)
|
||||
"flask" -> Icon(Icons.Filled.Science, null, tint = tint, modifier = Modifier.size(26.dp))
|
||||
"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))
|
||||
else -> Icon(Icons.Filled.Lock, null, tint = tint)
|
||||
}
|
||||
}
|
||||
|
|
@ -316,6 +318,14 @@ private fun subjectSum(vm: AppViewModel, s: J): ProgressSum {
|
|||
sum.doneEx = vm.content.chem.count { vm.progress.chemDone(it.int("id")) } +
|
||||
vm.content.bio.count { vm.progress.bioDone(it.int("id")) }
|
||||
}
|
||||
"matyears", "mat0map", "matmap", "mat2map", "mat3map", "mat4map" -> {
|
||||
sum.totalEx = vm.content.mat0.size + vm.content.mat.size + vm.content.mat2.size + vm.content.mat3.size + vm.content.mat4.size
|
||||
sum.doneEx = vm.content.mat0.count { vm.progress.mat0Done(it.int("id")) } +
|
||||
vm.content.mat.count { vm.progress.matDone(it.int("id")) } +
|
||||
vm.content.mat2.count { vm.progress.mat2Done(it.int("id")) } +
|
||||
vm.content.mat3.count { vm.progress.mat3Done(it.int("id")) } +
|
||||
vm.content.mat4.count { vm.progress.mat4Done(it.int("id")) }
|
||||
}
|
||||
"fyzyears", "fyzmap", "fyz2map", "fyz3map", "fyz4map" -> {
|
||||
sum.totalEx = vm.content.fyz.size + vm.content.fyz2.size + vm.content.fyz3.size + vm.content.fyz4.size
|
||||
sum.doneEx = vm.content.fyz.count { vm.progress.fyzDone(it.int("id")) } +
|
||||
|
|
@ -556,6 +566,32 @@ private fun buildSearch(vm: AppViewModel): List<Hit> {
|
|||
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "lit4unit${l.int("id")}", extra = "literatura")
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "lit4ex${l.int("id")}")
|
||||
}
|
||||
add(vm.tr("Matematika"), vm.tr("search_lesson"), "matyears", extra = "matematika")
|
||||
add(vm.tr("mat0_year"), vm.tr("search_lesson"), "mat0map", extra = "zakladka")
|
||||
add(vm.tr("mat_year1"), vm.tr("search_lesson"), "matmap", extra = "rocnik")
|
||||
add(vm.tr("mat_year2"), vm.tr("search_lesson"), "mat2map", extra = "rocnik")
|
||||
add(vm.tr("mat_year3"), vm.tr("search_lesson"), "mat3map", extra = "rocnik")
|
||||
add(vm.tr("mat_year4"), vm.tr("search_lesson"), "mat4map", extra = "rocnik")
|
||||
vm.content.mat0.forEach { l ->
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "mat0unit${l.int("id")}", extra = "matematika")
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "mat0ex${l.int("id")}")
|
||||
}
|
||||
vm.content.mat.forEach { l ->
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "matunit${l.int("id")}", extra = "matematika")
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "matex${l.int("id")}")
|
||||
}
|
||||
vm.content.mat2.forEach { l ->
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "mat2unit${l.int("id")}", extra = "matematika")
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "mat2ex${l.int("id")}")
|
||||
}
|
||||
vm.content.mat3.forEach { l ->
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "mat3unit${l.int("id")}", extra = "matematika")
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "mat3ex${l.int("id")}")
|
||||
}
|
||||
vm.content.mat4.forEach { l ->
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "mat4unit${l.int("id")}", extra = "matematika")
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "mat4ex${l.int("id")}")
|
||||
}
|
||||
add(vm.tr("Fyzika"), vm.tr("search_lesson"), "fyzyears", extra = "fyzika")
|
||||
add(vm.tr("fyz_year1"), vm.tr("search_lesson"), "fyzmap", extra = "rocnik")
|
||||
add(vm.tr("fyz_year2"), vm.tr("search_lesson"), "fyz2map", extra = "rocnik")
|
||||
|
|
@ -713,6 +749,21 @@ fun LitYearsScreen(vm: AppViewModel) {
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MatYearsScreen(vm: AppViewModel) {
|
||||
DetailScaffold(vm, vm.tr("Matematika"), vm.tr("mat_years_sub")) { inner ->
|
||||
LazyColumn(contentPadding = PaddingValues(top = inner.calculateTopPadding(), bottom = inner.calculateBottomPadding() + 16.dp)) {
|
||||
item {
|
||||
NavRow(title = vm.tr("mat0_year"), subtitle = vm.tr("mat0_sub"), leading = { Text("ZŠ", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.Mat0Map) }
|
||||
NavRow(title = vm.tr("mat_year1"), subtitle = vm.tr("mat_sub"), leading = { Text("1.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.MatMap) }
|
||||
NavRow(title = vm.tr("mat_year2"), subtitle = vm.tr("mat2_sub"), leading = { Text("2.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.Mat2Map) }
|
||||
NavRow(title = vm.tr("mat_year3"), subtitle = vm.tr("mat3_sub"), leading = { Text("3.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.Mat3Map) }
|
||||
NavRow(title = vm.tr("mat_year4"), subtitle = vm.tr("mat4_sub"), leading = { Text("4.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.Mat4Map) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FyzYearsScreen(vm: AppViewModel) {
|
||||
DetailScaffold(vm, vm.tr("Fyzika"), vm.tr("fyz_years_sub")) { inner ->
|
||||
|
|
|
|||
|
|
@ -6,6 +6,12 @@
|
|||
# cs: bullet
|
||||
# en: bullet
|
||||
|
||||
--- 2026-10-06
|
||||
cs: Matematika: opakování základní školy a čtyři ročníky, 40 lekcí
|
||||
cs: Příklady se píšou a kontrolují, u geometrie se rýsuje na mřížku
|
||||
en: Mathematics: elementary review and four years, 40 lessons
|
||||
en: Examples are typed and checked, and some geometry is drawn on a grid
|
||||
|
||||
--- 2026-10-06
|
||||
cs: Fyzika: čtyři ročníky, 32 lekcí podle osnov středních škol
|
||||
cs: Od mechaniky přes teplo a elektřinu až po optiku, jádro a astrofyziku
|
||||
|
|
|
|||
|
|
@ -198,6 +198,31 @@ final class AppModel: ObservableObject {
|
|||
refresh()
|
||||
}
|
||||
|
||||
func markMat0(_ id: Int) {
|
||||
progress.markMat0(id)
|
||||
refresh()
|
||||
}
|
||||
|
||||
func markMat(_ id: Int) {
|
||||
progress.markMat(id)
|
||||
refresh()
|
||||
}
|
||||
|
||||
func markMat2(_ id: Int) {
|
||||
progress.markMat2(id)
|
||||
refresh()
|
||||
}
|
||||
|
||||
func markMat3(_ id: Int) {
|
||||
progress.markMat3(id)
|
||||
refresh()
|
||||
}
|
||||
|
||||
func markMat4(_ id: Int) {
|
||||
progress.markMat4(id)
|
||||
refresh()
|
||||
}
|
||||
|
||||
func markMluv(_ n: Int) {
|
||||
progress.markMluv(n)
|
||||
refresh()
|
||||
|
|
|
|||
|
|
@ -58,3 +58,658 @@ func hasUmlaut(_ s: String?) -> Bool {
|
|||
guard let s else { return false }
|
||||
return s.contains(where: { "äöüÄÖÜß".contains($0) })
|
||||
}
|
||||
|
||||
// MARK: - Mathematics
|
||||
// Same rules as src/math_check.c. Do not route numbers through normalizeAnswer.
|
||||
|
||||
private struct MFrac { var n: Int64 = 0; var d: Int64 = 1 }
|
||||
private struct MTerm {
|
||||
var ok = true
|
||||
var a = MFrac()
|
||||
var b = MFrac()
|
||||
var rad: Int64 = 0
|
||||
var p = MFrac()
|
||||
}
|
||||
|
||||
private func mGcd(_ a0: Int64, _ b0: Int64) -> Int64 {
|
||||
var a = a0 < 0 ? -a0 : a0
|
||||
var b = b0 < 0 ? -b0 : b0
|
||||
while b != 0 {
|
||||
let t = a % b
|
||||
a = b
|
||||
b = t
|
||||
}
|
||||
return a == 0 ? 1 : a
|
||||
}
|
||||
|
||||
private func mMul(_ a: Int64, _ b: Int64) -> Int64? {
|
||||
let r = a.multipliedReportingOverflow(by: b)
|
||||
return r.overflow ? nil : r.partialValue
|
||||
}
|
||||
|
||||
private func mSet(_ f: inout MFrac, _ n: Int64, _ d: Int64) -> Bool {
|
||||
if d == 0 { return false }
|
||||
var n = n, d = d
|
||||
if d < 0 { n = -n; d = -d }
|
||||
let g = mGcd(n, d)
|
||||
f.n = n / g
|
||||
f.d = d / g
|
||||
return true
|
||||
}
|
||||
|
||||
private func mAddF(_ x: MFrac, _ y: MFrac) -> MFrac? {
|
||||
guard let xn = mMul(x.n, y.d), let yn = mMul(y.n, x.d),
|
||||
let d = mMul(x.d, y.d) else { return nil }
|
||||
let s = xn.addingReportingOverflow(yn)
|
||||
if s.overflow { return nil }
|
||||
var o = MFrac()
|
||||
return mSet(&o, s.partialValue, d) ? o : nil
|
||||
}
|
||||
|
||||
private func mMulF(_ x: MFrac, _ y: MFrac) -> MFrac? {
|
||||
guard let n = mMul(x.n, y.n), let d = mMul(x.d, y.d) else { return nil }
|
||||
var o = MFrac()
|
||||
return mSet(&o, n, d) ? o : nil
|
||||
}
|
||||
|
||||
private func mDivF(_ x: MFrac, _ y: MFrac) -> MFrac? {
|
||||
if y.n == 0 { return nil }
|
||||
return mMulF(x, MFrac(n: y.d, d: y.n))
|
||||
}
|
||||
|
||||
private func mSameF(_ x: MFrac, _ y: MFrac) -> Bool { x.n == y.n && x.d == y.d }
|
||||
|
||||
private func mSqrtReduce(_ n0: Int64) -> (out: Int64, rad: Int64)? {
|
||||
if n0 < 0 { return nil }
|
||||
if n0 > 1_000_000 { return nil }
|
||||
var n = n0
|
||||
var out: Int64 = 1
|
||||
var f: Int64 = 2
|
||||
while f * f <= n {
|
||||
var c = 0
|
||||
while n % f == 0 { n /= f; c += 1 }
|
||||
if c >= 2 { out *= mPow(f, c / 2) }
|
||||
if c % 2 == 1 { n *= f }
|
||||
if f == 2 { f = 3 } else { f += 2 }
|
||||
if f > 10000 { break }
|
||||
}
|
||||
return (out, n)
|
||||
}
|
||||
|
||||
private func mPow(_ b: Int64, _ e: Int) -> Int64 {
|
||||
var r: Int64 = 1
|
||||
var i = 0
|
||||
while i < e { r *= b; i += 1 }
|
||||
return r
|
||||
}
|
||||
|
||||
private func mNorm(_ t: inout MTerm) {
|
||||
if !t.ok { return }
|
||||
if t.rad > 1, let s = mSqrtReduce(t.rad) {
|
||||
guard let nb = mMulF(t.b, MFrac(n: s.out, d: 1)) else { t.ok = false; return }
|
||||
t.b = nb
|
||||
t.rad = s.rad
|
||||
}
|
||||
if t.rad <= 1 {
|
||||
guard let na = mAddF(t.a, t.b) else { t.ok = false; return }
|
||||
t.a = na
|
||||
t.b = MFrac()
|
||||
t.rad = 0
|
||||
}
|
||||
if t.b.n == 0 { t.rad = 0 }
|
||||
}
|
||||
|
||||
private func mAdd(_ x: MTerm, _ y: MTerm) -> MTerm {
|
||||
if !x.ok || !y.ok { return MTerm(ok: false) }
|
||||
var o = MTerm()
|
||||
guard let a = mAddF(x.a, y.a), let p = mAddF(x.p, y.p) else { return MTerm(ok: false) }
|
||||
o.a = a
|
||||
o.p = p
|
||||
if x.rad == 0 && y.rad == 0 {
|
||||
o.b = MFrac(); o.rad = 0
|
||||
} else if x.rad == 0 {
|
||||
o.b = y.b; o.rad = y.rad
|
||||
} else if y.rad == 0 || x.rad == y.rad {
|
||||
guard let b = mAddF(x.b, y.b) else { return MTerm(ok: false) }
|
||||
o.b = b; o.rad = x.rad
|
||||
} else { return MTerm(ok: false) }
|
||||
mNorm(&o)
|
||||
return o
|
||||
}
|
||||
|
||||
private func mScale(_ x: MTerm, _ f: MFrac) -> MTerm {
|
||||
if !x.ok { return MTerm(ok: false) }
|
||||
var o = MTerm()
|
||||
guard let a = mMulF(x.a, f), let b = mMulF(x.b, f), let p = mMulF(x.p, f) else {
|
||||
return MTerm(ok: false)
|
||||
}
|
||||
o.a = a; o.b = b; o.rad = x.rad; o.p = p
|
||||
mNorm(&o)
|
||||
return o
|
||||
}
|
||||
|
||||
private func mMulT(_ x: MTerm, _ y: MTerm) -> MTerm {
|
||||
if !x.ok || !y.ok { return MTerm(ok: false) }
|
||||
if (x.p.n != 0 && (y.p.n != 0 || y.rad != 0)) ||
|
||||
(y.p.n != 0 && (x.p.n != 0 || x.rad != 0)) { return MTerm(ok: false) }
|
||||
if x.rad != 0 && y.rad != 0 && x.rad != y.rad { return MTerm(ok: false) }
|
||||
var o = MTerm()
|
||||
guard let aa = mMulF(x.a, y.a) else { return MTerm(ok: false) }
|
||||
o.a = aa
|
||||
if x.rad == 0 && y.rad == 0 {
|
||||
o.b = MFrac(); o.rad = 0
|
||||
} else if x.rad == 0 {
|
||||
guard let b = mMulF(x.a, y.b) else { return MTerm(ok: false) }
|
||||
o.b = b; o.rad = y.rad
|
||||
} else if y.rad == 0 {
|
||||
guard let b = mMulF(y.a, x.b) else { return MTerm(ok: false) }
|
||||
o.b = b; o.rad = x.rad
|
||||
} else {
|
||||
guard let ab = mMulF(x.a, y.b), let ba = mMulF(x.b, y.a),
|
||||
let cross = mAddF(ab, ba),
|
||||
let sq = mMul(x.rad, y.rad) else { return MTerm(ok: false) }
|
||||
guard let bb = mMulF(x.b, y.b), let extra = mMulF(bb, MFrac(n: sq, d: 1)),
|
||||
let na = mAddF(o.a, extra) else { return MTerm(ok: false) }
|
||||
o.a = na; o.b = cross; o.rad = x.rad
|
||||
}
|
||||
if x.p.n != 0 { o.p = x.p } else { o.p = y.p }
|
||||
if y.a.n != 1 || y.a.d != 1 || y.b.n != 0 || y.rad != 0 || y.p.n != 0 {
|
||||
if x.p.n != 0 {
|
||||
guard let p = mMulF(x.p, y.a) else { return MTerm(ok: false) }
|
||||
o.p = p
|
||||
}
|
||||
}
|
||||
if x.a.n != 1 || x.a.d != 1 || x.b.n != 0 || x.rad != 0 || x.p.n != 0 {
|
||||
if y.p.n != 0 && x.p.n == 0 {
|
||||
guard let p = mMulF(y.p, x.a) else { return MTerm(ok: false) }
|
||||
o.p = p
|
||||
}
|
||||
}
|
||||
mNorm(&o)
|
||||
return o
|
||||
}
|
||||
|
||||
private func mDivT(_ x: MTerm, _ y: MTerm) -> MTerm {
|
||||
if !x.ok || !y.ok || y.p.n != 0 { return MTerm(ok: false) }
|
||||
if y.rad == 0 {
|
||||
guard let f = mDivF(MFrac(n: 1, d: 1), y.a) else { return MTerm(ok: false) }
|
||||
return mScale(x, f)
|
||||
}
|
||||
if x.p.n != 0 { return MTerm(ok: false) }
|
||||
guard let yb = mMulF(y.b, MFrac(n: y.rad, d: 1)),
|
||||
let bb = mMulF(y.b, yb),
|
||||
let aa = mMulF(y.a, y.a),
|
||||
let den = mAddF(aa, MFrac(n: -bb.n, d: bb.d)) else { return MTerm(ok: false) }
|
||||
var num = MTerm()
|
||||
num.a = y.a
|
||||
num.b = MFrac(n: -y.b.n, d: y.b.d)
|
||||
num.rad = y.rad
|
||||
guard let inv = mDivF(MFrac(n: 1, d: 1), den) else { return MTerm(ok: false) }
|
||||
return mMulT(x, mScale(num, inv))
|
||||
}
|
||||
|
||||
private enum MTok {
|
||||
case num(Int64, Int64)
|
||||
case sqrt, pi, add, sub, mul, div, lp, rp
|
||||
}
|
||||
|
||||
private func mLex(_ s: String) -> [MTok]? {
|
||||
var toks: [MTok] = []
|
||||
var i = s.startIndex
|
||||
func peek(_ k: Int) -> Character? {
|
||||
var j = i
|
||||
var n = 0
|
||||
while n < k && j < s.endIndex { j = s.index(after: j); n += 1 }
|
||||
return j < s.endIndex ? s[j] : nil
|
||||
}
|
||||
while i < s.endIndex {
|
||||
while i < s.endIndex && s[i].isWhitespace { i = s.index(after: i) }
|
||||
if i >= s.endIndex { break }
|
||||
let ch = s[i]
|
||||
if ch.isNumber || ((ch == "." || ch == ",") && (peek(1)?.isNumber == true)) {
|
||||
var whole: Int64 = 0
|
||||
var saw = false
|
||||
while i < s.endIndex && s[i].isNumber {
|
||||
saw = true
|
||||
whole = whole * 10 + Int64(s[i].wholeNumberValue ?? 0)
|
||||
i = s.index(after: i)
|
||||
}
|
||||
if i < s.endIndex && (s[i] == "." || s[i] == ",") && (s.index(after: i) < s.endIndex && s[s.index(after: i)].isNumber) {
|
||||
i = s.index(after: i)
|
||||
var frac: Int64 = 0
|
||||
var div: Int64 = 1
|
||||
while i < s.endIndex && s[i].isNumber {
|
||||
frac = frac * 10 + Int64(s[i].wholeNumberValue ?? 0)
|
||||
div *= 10
|
||||
i = s.index(after: i)
|
||||
}
|
||||
let n = whole * div + frac
|
||||
toks.append(.num(n, div))
|
||||
} else if saw {
|
||||
toks.append(.num(whole, 1))
|
||||
} else { return nil }
|
||||
continue
|
||||
}
|
||||
if ch == "+" { toks.append(.add); i = s.index(after: i); continue }
|
||||
if ch == "-" || ch == "−" || ch == "–" { toks.append(.sub); i = s.index(after: i); continue }
|
||||
if ch == "*" || ch == "×" || ch == "·" { toks.append(.mul); i = s.index(after: i); continue }
|
||||
if ch == "/" || ch == ":" { toks.append(.div); i = s.index(after: i); continue }
|
||||
if ch == "(" { toks.append(.lp); i = s.index(after: i); continue }
|
||||
if ch == ")" { toks.append(.rp); i = s.index(after: i); continue }
|
||||
if ch == "π" { toks.append(.pi); i = s.index(after: i); continue }
|
||||
if ch == "√" { toks.append(.sqrt); i = s.index(after: i); continue }
|
||||
if ch.isLetter {
|
||||
var j = i
|
||||
while j < s.endIndex && s[j].isLetter { j = s.index(after: j) }
|
||||
let w = s[i..<j].lowercased()
|
||||
if w == "pi" { toks.append(.pi) }
|
||||
else if w == "sqrt" { toks.append(.sqrt) }
|
||||
else { return nil }
|
||||
i = j
|
||||
continue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return toks
|
||||
}
|
||||
|
||||
private struct MParse {
|
||||
let t: [MTok]
|
||||
var i = 0
|
||||
func peek() -> MTok? { i < t.count ? t[i] : nil }
|
||||
mutating func take() -> MTok? {
|
||||
if i >= t.count { return nil }
|
||||
defer { i += 1 }
|
||||
return t[i]
|
||||
}
|
||||
mutating func expr() -> MTerm? {
|
||||
guard var left = term() else { return nil }
|
||||
while let p = peek() {
|
||||
if case .add = p { _ = take() }
|
||||
else if case .sub = p { _ = take() }
|
||||
else { break }
|
||||
guard let r = term() else { return nil }
|
||||
if case .sub = p { left = mAdd(left, mScale(r, MFrac(n: -1, d: 1))) }
|
||||
else { left = mAdd(left, r) }
|
||||
if !left.ok { return nil }
|
||||
}
|
||||
return left
|
||||
}
|
||||
mutating func term() -> MTerm? {
|
||||
guard var left = unary() else { return nil }
|
||||
while let p = peek() {
|
||||
if case .mul = p { _ = take() }
|
||||
else if case .div = p { _ = take() }
|
||||
else { break }
|
||||
guard let r = unary() else { return nil }
|
||||
if case .div = p { left = mDivT(left, r) }
|
||||
else { left = mMulT(left, r) }
|
||||
if !left.ok { return nil }
|
||||
}
|
||||
return left
|
||||
}
|
||||
mutating func unary() -> MTerm? {
|
||||
if case .sub = peek() {
|
||||
_ = take()
|
||||
guard let v = unary() else { return nil }
|
||||
return mScale(v, MFrac(n: -1, d: 1))
|
||||
}
|
||||
if case .add = peek() {
|
||||
_ = take()
|
||||
return unary()
|
||||
}
|
||||
return factor()
|
||||
}
|
||||
mutating func factor() -> MTerm? {
|
||||
guard var left = primary() else { return nil }
|
||||
while true {
|
||||
guard let p = peek() else { break }
|
||||
switch p {
|
||||
case .sqrt, .pi, .lp:
|
||||
guard let r = primary() else { return nil }
|
||||
left = mMulT(left, r)
|
||||
if !left.ok { return nil }
|
||||
case .num:
|
||||
return nil
|
||||
default:
|
||||
return left
|
||||
}
|
||||
}
|
||||
return left
|
||||
}
|
||||
mutating func primary() -> MTerm? {
|
||||
guard let p = take() else { return nil }
|
||||
switch p {
|
||||
case .num(let n, let d):
|
||||
if case .div = peek(), case .num = t[safe: i + 1] {
|
||||
_ = take()
|
||||
if case .num(let n2, let d2) = take() {
|
||||
if n2 <= 0 || d2 != 1 || d != 1 { return nil }
|
||||
var o = MTerm()
|
||||
if !mSet(&o.a, n, n2) { return nil }
|
||||
return o
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if d == 1, case .num(let n2, let d2) = peek(), n >= 0 {
|
||||
_ = take()
|
||||
if case .div = peek(), case .num(let n3, let d3) = t[safe: i + 1], d2 == 1, d3 == 1, n2 >= 0, n3 > 0 {
|
||||
_ = take()
|
||||
_ = take()
|
||||
var frac = MFrac()
|
||||
if !mSet(&frac, n2, n3) { return nil }
|
||||
var whole = MTerm()
|
||||
whole.a = MFrac(n: n, d: 1)
|
||||
var part = MTerm()
|
||||
part.a = frac
|
||||
return mAdd(whole, part)
|
||||
}
|
||||
i -= 1
|
||||
}
|
||||
var o = MTerm()
|
||||
if !mSet(&o.a, n, d) { return nil }
|
||||
return o
|
||||
case .pi:
|
||||
var o = MTerm()
|
||||
o.p = MFrac(n: 1, d: 1)
|
||||
return o
|
||||
case .sqrt:
|
||||
if case .lp = peek() {
|
||||
_ = take()
|
||||
guard let inner = expr(), case .rp = take() else { return nil }
|
||||
return mSqrtTerm(inner)
|
||||
}
|
||||
if case .num(let n, let d) = peek(), d == 1, n >= 0 {
|
||||
_ = take()
|
||||
return mSqrtTerm(MTerm(a: MFrac(n: n, d: 1)))
|
||||
}
|
||||
return nil
|
||||
case .lp:
|
||||
guard let inner = expr(), case .rp = take() else { return nil }
|
||||
return inner
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension Array {
|
||||
subscript(safe i: Int) -> Element? { indices.contains(i) ? self[i] : nil }
|
||||
}
|
||||
|
||||
private func mSqrtTerm(_ inner: MTerm) -> MTerm? {
|
||||
if !inner.ok || inner.p.n != 0 || inner.rad != 0 || inner.b.n != 0 { return nil }
|
||||
if inner.a.n < 0 || inner.a.d != 1 { return nil }
|
||||
guard let s = mSqrtReduce(inner.a.n) else { return nil }
|
||||
var o = MTerm()
|
||||
o.b = MFrac(n: s.out, d: 1)
|
||||
o.rad = s.rad
|
||||
mNorm(&o)
|
||||
return o.ok ? o : nil
|
||||
}
|
||||
|
||||
private func mPrep(_ raw: String) -> String {
|
||||
var s = raw.trimmingCharacters(in: .whitespaces)
|
||||
var again = true
|
||||
while again {
|
||||
again = false
|
||||
if s.count >= 2 {
|
||||
let pairs: [(Character, Character)] = [("(", ")"), ("[", "]"), ("{", "}")]
|
||||
if let last = s.last, pairs.contains(where: { $0.0 == s.first && $0.1 == last }) {
|
||||
s = String(s.dropFirst().dropLast()).trimmingCharacters(in: .whitespaces)
|
||||
again = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
s = mStripUnits(s).trimmingCharacters(in: .whitespaces)
|
||||
return s
|
||||
}
|
||||
|
||||
private func mParseTerm(_ s: String) -> MTerm? {
|
||||
let s = mPrep(s)
|
||||
if s.isEmpty { return nil }
|
||||
guard let toks = mLex(s) else { return nil }
|
||||
if toks.isEmpty { return nil }
|
||||
var p = MParse(t: toks)
|
||||
guard let v = p.expr(), p.i == toks.count, v.ok else { return nil }
|
||||
return v
|
||||
}
|
||||
|
||||
private func mExact(_ x: MTerm, _ y: MTerm) -> Bool {
|
||||
x.ok && y.ok && mSameF(x.a, y.a) && mSameF(x.b, y.b) && x.rad == y.rad && mSameF(x.p, y.p)
|
||||
}
|
||||
|
||||
private func mDouble(_ t: MTerm) -> Double {
|
||||
var v = Double(t.a.n) / Double(t.a.d) + Double(t.p.n) / Double(t.p.d) * Double.pi
|
||||
if t.b.n != 0 && t.rad > 0 {
|
||||
v += Double(t.b.n) / Double(t.b.d) * (Double(t.rad)).squareRoot()
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
private func mClose(_ x: MTerm, _ y: MTerm) -> Bool {
|
||||
if !x.ok || !y.ok { return false }
|
||||
if mExact(x, y) { return true }
|
||||
if x.p.n != 0 || y.p.n != 0 { return false }
|
||||
let diff = abs(mDouble(x) - mDouble(y))
|
||||
if x.b.n != 0 || y.b.n != 0 { return diff <= 0.001 }
|
||||
return diff <= 0.0005
|
||||
}
|
||||
|
||||
private func mFold(_ s: String) -> String {
|
||||
var out = ""
|
||||
for ch in s.lowercased() {
|
||||
switch ch {
|
||||
case "á", "à", "ä", "â": out.append("a")
|
||||
case "č", "ć": out.append("c")
|
||||
case "ď": out.append("d")
|
||||
case "é", "ě", "ë", "ê": out.append("e")
|
||||
case "í", "ï": out.append("i")
|
||||
case "ň": out.append("n")
|
||||
case "ó", "ö", "ô": out.append("o")
|
||||
case "ř": out.append("r")
|
||||
case "š", "ś": out.append("s")
|
||||
case "ť": out.append("t")
|
||||
case "ú", "ů", "ü": out.append("u")
|
||||
case "ý": out.append("y")
|
||||
case "ž", "ź": out.append("z")
|
||||
default:
|
||||
if ch.isLetter || ch.isNumber { out.append(ch) }
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
private func mStripUnits(_ raw: String) -> String {
|
||||
var s = raw.trimmingCharacters(in: .whitespaces)
|
||||
let units = ["procenta", "procent", "cm", "mm", "dm", "km", "m"]
|
||||
var cut = true
|
||||
while cut {
|
||||
cut = false
|
||||
while s.last?.isWhitespace == true { s.removeLast() }
|
||||
if s.hasSuffix("°") || s.hasSuffix("%") || s.hasSuffix(".") {
|
||||
s.removeLast()
|
||||
cut = true
|
||||
continue
|
||||
}
|
||||
for u in units {
|
||||
guard s.count > u.count else { continue }
|
||||
if s.lowercased().hasSuffix(u) {
|
||||
let keep = s.dropLast(u.count)
|
||||
if keep.last?.isLetter == true { continue }
|
||||
s = String(keep)
|
||||
cut = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return s.trimmingCharacters(in: .whitespaces)
|
||||
}
|
||||
|
||||
private func mSplit(_ s: String) -> [String]? {
|
||||
let s = s.trimmingCharacters(in: .whitespaces)
|
||||
if s.isEmpty { return nil }
|
||||
let semi = s.contains(";")
|
||||
var parts: [String] = []
|
||||
var rest = Substring(s)
|
||||
while !rest.isEmpty {
|
||||
let end: Substring.Index
|
||||
let step: Int
|
||||
if semi {
|
||||
if let i = rest.firstIndex(of: ";") { end = i; step = 1 }
|
||||
else { end = rest.endIndex; step = 0 }
|
||||
} else if let r = rest.range(of: ", ") {
|
||||
end = r.lowerBound
|
||||
step = 2
|
||||
} else {
|
||||
end = rest.endIndex
|
||||
step = 0
|
||||
}
|
||||
let piece = rest[..<end].trimmingCharacters(in: .whitespaces)
|
||||
if piece.isEmpty { return nil }
|
||||
parts.append(piece)
|
||||
if step == 0 { break }
|
||||
rest = rest[rest.index(end, offsetBy: step)...]
|
||||
}
|
||||
return parts.isEmpty ? nil : parts
|
||||
}
|
||||
|
||||
func mathAnswerOk(_ spec: String?, _ user: String?) -> Bool {
|
||||
guard var spec, let user else { return false }
|
||||
spec = spec.trimmingCharacters(in: .whitespaces)
|
||||
if spec.hasPrefix("word:") {
|
||||
let opts = spec.dropFirst(5).split(separator: "|").map { mFold(String($0)) }
|
||||
return opts.contains(mFold(user))
|
||||
}
|
||||
if spec.hasPrefix("set:") || spec.hasPrefix("pair:") {
|
||||
let ordered = spec.hasPrefix("pair:")
|
||||
let body = String(spec.dropFirst(ordered ? 5 : 4))
|
||||
guard let want = mSplit(body), let got = mSplit(user) else { return false }
|
||||
if want.count != got.count || want.isEmpty { return false }
|
||||
var wt: [MTerm] = []
|
||||
var gt: [MTerm] = []
|
||||
for w in want {
|
||||
guard let t = mParseTerm(w) else { return false }
|
||||
wt.append(t)
|
||||
}
|
||||
for g in got {
|
||||
guard let t = mParseTerm(g) else { return false }
|
||||
gt.append(t)
|
||||
}
|
||||
if ordered {
|
||||
for i in wt.indices where !mClose(wt[i], gt[i]) { return false }
|
||||
return true
|
||||
}
|
||||
var used = Array(repeating: false, count: gt.count)
|
||||
for w in wt {
|
||||
var hit = gt.indices.first { !used[$0] && mExact(w, gt[$0]) }
|
||||
if hit == nil { hit = gt.indices.first { !used[$0] && mClose(w, gt[$0]) } }
|
||||
guard let hit else { return false }
|
||||
used[hit] = true
|
||||
}
|
||||
return true
|
||||
}
|
||||
let body = spec.hasPrefix("num:") ? String(spec.dropFirst(4)) : spec
|
||||
guard let tw = mParseTerm(body), let tg = mParseTerm(user) else { return false }
|
||||
return mClose(tw, tg)
|
||||
}
|
||||
|
||||
func mathIsDraw(_ spec: String?) -> Bool {
|
||||
guard let spec else { return false }
|
||||
return spec.hasPrefix("square:") || spec.hasPrefix("mid:") || spec.hasPrefix("right:")
|
||||
|| spec.hasPrefix("para:") || spec.hasPrefix("points:")
|
||||
}
|
||||
|
||||
func mathDrawNeed(_ spec: String?) -> Int {
|
||||
guard let spec else { return 0 }
|
||||
if spec.hasPrefix("square:") { return 2 }
|
||||
if spec.hasPrefix("mid:") || spec.hasPrefix("right:") || spec.hasPrefix("para:") { return 1 }
|
||||
if spec.hasPrefix("points:") {
|
||||
let body = spec.dropFirst(7)
|
||||
return body.split(separator: ";").filter { !$0.hasPrefix("fix:") }.count
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
private func mParseXY(_ s: Substring) -> (Int, Int)? {
|
||||
let t = s.trimmingCharacters(in: .whitespaces)
|
||||
let bits = t.split(separator: ",")
|
||||
guard bits.count == 2, let x = Int(bits[0].trimmingCharacters(in: .whitespaces)),
|
||||
let y = Int(bits[1].trimmingCharacters(in: .whitespaces)) else { return nil }
|
||||
return (x, y)
|
||||
}
|
||||
|
||||
func mathDrawGiven(_ spec: String?) -> [(Int, Int)] {
|
||||
guard let spec else { return [] }
|
||||
if spec.hasPrefix("points:") {
|
||||
return spec.dropFirst(7).split(separator: ";").compactMap { part -> (Int, Int)? in
|
||||
guard part.hasPrefix("fix:") else { return nil }
|
||||
return mParseXY(part.dropFirst(4))
|
||||
}
|
||||
}
|
||||
let body: Substring
|
||||
let n: Int
|
||||
if spec.hasPrefix("para:") { body = spec.dropFirst(5); n = 3 }
|
||||
else if spec.hasPrefix("square:") { body = spec.dropFirst(7); n = 2 }
|
||||
else if spec.hasPrefix("right:") { body = spec.dropFirst(6); n = 2 }
|
||||
else if spec.hasPrefix("mid:") { body = spec.dropFirst(4); n = 2 }
|
||||
else { return [] }
|
||||
return body.split(separator: ";").prefix(n).compactMap { mParseXY($0) }
|
||||
}
|
||||
|
||||
private func mOnBoard(_ p: (Int, Int)) -> Bool {
|
||||
p.0 >= 0 && p.0 <= 10 && p.1 >= 0 && p.1 <= 8
|
||||
}
|
||||
|
||||
func mathDrawOk(_ spec: String?, _ pts: [(Int, Int)]) -> Bool {
|
||||
guard let spec, mathDrawNeed(spec) == pts.count else { return false }
|
||||
if pts.contains(where: { !mOnBoard($0) }) { return false }
|
||||
if Set(pts.map { "\($0.0),\($0.1)" }).count != pts.count { return false }
|
||||
let g = mathDrawGiven(spec)
|
||||
if spec.hasPrefix("mid:") {
|
||||
guard g.count == 2, (g[0].0 + g[1].0) % 2 == 0, (g[0].1 + g[1].1) % 2 == 0 else { return false }
|
||||
return pts[0] == ((g[0].0 + g[1].0) / 2, (g[0].1 + g[1].1) / 2)
|
||||
}
|
||||
if spec.hasPrefix("para:") {
|
||||
guard g.count == 3 else { return false }
|
||||
return pts[0] == (g[1].0 + g[2].0 - g[0].0, g[1].1 + g[2].1 - g[0].1)
|
||||
}
|
||||
if spec.hasPrefix("right:") {
|
||||
guard g.count == 2 else { return false }
|
||||
let c = pts[0]
|
||||
if c == g[0] || c == g[1] { return false }
|
||||
let ux = g[0].0 - c.0, uy = g[0].1 - c.1
|
||||
let vx = g[1].0 - c.0, vy = g[1].1 - c.1
|
||||
return ux * vx + uy * vy == 0 && (ux != 0 || uy != 0) && (vx != 0 || vy != 0)
|
||||
}
|
||||
if spec.hasPrefix("square:") {
|
||||
guard g.count == 2, pts.count == 2 else { return false }
|
||||
let a = g[0], b = g[1]
|
||||
let dx = b.0 - a.0, dy = b.1 - a.1
|
||||
if dx == 0 && dy == 0 { return false }
|
||||
let c1 = (b.0 - dy, b.1 + dx), d1 = (a.0 - dy, a.1 + dx)
|
||||
let c2 = (b.0 + dy, b.1 - dx), d2 = (a.0 + dy, a.1 - dx)
|
||||
func same(_ p: (Int, Int), _ q: (Int, Int), _ r: (Int, Int), _ s: (Int, Int)) -> Bool {
|
||||
(p == r && q == s) || (p == s && q == r)
|
||||
}
|
||||
if !mOnBoard(c1) || !mOnBoard(d1) {
|
||||
if !mOnBoard(c2) || !mOnBoard(d2) { return false }
|
||||
}
|
||||
return same(pts[0], pts[1], c1, d1) || same(pts[0], pts[1], c2, d2)
|
||||
}
|
||||
if spec.hasPrefix("points:") {
|
||||
let want = spec.dropFirst(7).split(separator: ";").compactMap { part -> (Int, Int)? in
|
||||
if part.hasPrefix("fix:") { return nil }
|
||||
return mParseXY(part)
|
||||
}
|
||||
if want.count != pts.count { return false }
|
||||
var used = Array(repeating: false, count: pts.count)
|
||||
for w in want {
|
||||
guard let j = pts.indices.first(where: { !used[$0] && pts[$0] == w }) else { return false }
|
||||
used[j] = true
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,11 @@ final class Content {
|
|||
let fyz2: [J]
|
||||
let fyz3: [J]
|
||||
let fyz4: [J]
|
||||
let mat0: [J]
|
||||
let mat: [J]
|
||||
let mat2: [J]
|
||||
let mat3: [J]
|
||||
let mat4: [J]
|
||||
let mluvnice: [J]
|
||||
let books: [J]
|
||||
let changelog: [J]
|
||||
|
|
@ -90,6 +95,11 @@ final class Content {
|
|||
fyz2 = raw.arr("fyz2")
|
||||
fyz3 = raw.arr("fyz3")
|
||||
fyz4 = raw.arr("fyz4")
|
||||
mat0 = raw.arr("mat0")
|
||||
mat = raw.arr("mat")
|
||||
mat2 = raw.arr("mat2")
|
||||
mat3 = raw.arr("mat3")
|
||||
mat4 = raw.arr("mat4")
|
||||
mluvnice = raw.arr("mluvnice")
|
||||
books = raw.arr("books")
|
||||
changelog = raw.arr("changelog")
|
||||
|
|
@ -112,6 +122,11 @@ final class Content {
|
|||
func fyz2Lesson(_ id: Int) -> J? { fyz2.first { $0.int("id") == id } }
|
||||
func fyz3Lesson(_ id: Int) -> J? { fyz3.first { $0.int("id") == id } }
|
||||
func fyz4Lesson(_ id: Int) -> J? { fyz4.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 } }
|
||||
func mat3Lesson(_ id: Int) -> J? { mat3.first { $0.int("id") == id } }
|
||||
func mat4Lesson(_ id: Int) -> J? { mat4.first { $0.int("id") == id } }
|
||||
func mluv(_ n: Int) -> J? { mluvnice.first { $0.int("id") == n } }
|
||||
func book(_ id: String) -> J? { books.first { $0.str("id") == id } }
|
||||
|
||||
|
|
|
|||
|
|
@ -67,6 +67,16 @@ final class ProgressStore {
|
|||
func markFyz3(_ id: Int) { defaults.set(true, forKey: "fyz3.\(id)") }
|
||||
func fyz4Done(_ id: Int) -> Bool { defaults.bool(forKey: "fyz4.\(id)") }
|
||||
func markFyz4(_ id: Int) { defaults.set(true, forKey: "fyz4.\(id)") }
|
||||
func mat0Done(_ id: Int) -> Bool { defaults.bool(forKey: "mat0.\(id)") }
|
||||
func markMat0(_ id: Int) { defaults.set(true, forKey: "mat0.\(id)") }
|
||||
func matDone(_ id: Int) -> Bool { defaults.bool(forKey: "mat.\(id)") }
|
||||
func markMat(_ id: Int) { defaults.set(true, forKey: "mat.\(id)") }
|
||||
func mat2Done(_ id: Int) -> Bool { defaults.bool(forKey: "mat2.\(id)") }
|
||||
func markMat2(_ id: Int) { defaults.set(true, forKey: "mat2.\(id)") }
|
||||
func mat3Done(_ id: Int) -> Bool { defaults.bool(forKey: "mat3.\(id)") }
|
||||
func markMat3(_ id: Int) { defaults.set(true, forKey: "mat3.\(id)") }
|
||||
func mat4Done(_ id: Int) -> Bool { defaults.bool(forKey: "mat4.\(id)") }
|
||||
func markMat4(_ id: Int) { defaults.set(true, forKey: "mat4.\(id)") }
|
||||
|
||||
func mluvDone(_ n: Int) -> Bool { defaults.bool(forKey: "mluv.\(n)") }
|
||||
func markMluv(_ n: Int) { defaults.set(true, forKey: "mluv.\(n)") }
|
||||
|
|
@ -223,6 +233,46 @@ func summarize(_ content: Content, _ p: ProgressStore) -> ProgressSum {
|
|||
sum.doneUnits += 1
|
||||
}
|
||||
}
|
||||
for l in content.mat0 {
|
||||
sum.totalEx += 1
|
||||
sum.openUnits += 1
|
||||
if p.mat0Done(l.int("id")) {
|
||||
sum.doneEx += 1
|
||||
sum.doneUnits += 1
|
||||
}
|
||||
}
|
||||
for l in content.mat {
|
||||
sum.totalEx += 1
|
||||
sum.openUnits += 1
|
||||
if p.matDone(l.int("id")) {
|
||||
sum.doneEx += 1
|
||||
sum.doneUnits += 1
|
||||
}
|
||||
}
|
||||
for l in content.mat2 {
|
||||
sum.totalEx += 1
|
||||
sum.openUnits += 1
|
||||
if p.mat2Done(l.int("id")) {
|
||||
sum.doneEx += 1
|
||||
sum.doneUnits += 1
|
||||
}
|
||||
}
|
||||
for l in content.mat3 {
|
||||
sum.totalEx += 1
|
||||
sum.openUnits += 1
|
||||
if p.mat3Done(l.int("id")) {
|
||||
sum.doneEx += 1
|
||||
sum.doneUnits += 1
|
||||
}
|
||||
}
|
||||
for l in content.mat4 {
|
||||
sum.totalEx += 1
|
||||
sum.openUnits += 1
|
||||
if p.mat4Done(l.int("id")) {
|
||||
sum.doneEx += 1
|
||||
sum.doneUnits += 1
|
||||
}
|
||||
}
|
||||
sum.totalEx += content.mluvnice.count
|
||||
sum.openUnits += 1
|
||||
let md = content.mluvnice.filter { p.mluvDone($0.int("id")) }.count
|
||||
|
|
|
|||
|
|
@ -56,6 +56,22 @@ enum Route: Hashable {
|
|||
case bioMap
|
||||
case bioLesson(Int)
|
||||
case bioEx(Int)
|
||||
case matYears
|
||||
case mat0Map
|
||||
case mat0Lesson(Int)
|
||||
case mat0Ex(Int)
|
||||
case matMap
|
||||
case matLesson(Int)
|
||||
case matEx(Int)
|
||||
case mat2Map
|
||||
case mat2Lesson(Int)
|
||||
case mat2Ex(Int)
|
||||
case mat3Map
|
||||
case mat3Lesson(Int)
|
||||
case mat3Ex(Int)
|
||||
case mat4Map
|
||||
case mat4Lesson(Int)
|
||||
case mat4Ex(Int)
|
||||
case fyzYears
|
||||
case fyzMap
|
||||
case fyzLesson(Int)
|
||||
|
|
@ -127,6 +143,22 @@ enum Route: Hashable {
|
|||
case .bioMap: return "biomap"
|
||||
case .bioLesson(let id): return "biounit\(id)"
|
||||
case .bioEx(let id): return "bioex\(id)"
|
||||
case .matYears: return "matyears"
|
||||
case .mat0Map: return "mat0map"
|
||||
case .mat0Lesson(let id): return "mat0unit\(id)"
|
||||
case .mat0Ex(let id): return "mat0ex\(id)"
|
||||
case .matMap: return "matmap"
|
||||
case .matLesson(let id): return "matunit\(id)"
|
||||
case .matEx(let id): return "matex\(id)"
|
||||
case .mat2Map: return "mat2map"
|
||||
case .mat2Lesson(let id): return "mat2unit\(id)"
|
||||
case .mat2Ex(let id): return "mat2ex\(id)"
|
||||
case .mat3Map: return "mat3map"
|
||||
case .mat3Lesson(let id): return "mat3unit\(id)"
|
||||
case .mat3Ex(let id): return "mat3ex\(id)"
|
||||
case .mat4Map: return "mat4map"
|
||||
case .mat4Lesson(let id): return "mat4unit\(id)"
|
||||
case .mat4Ex(let id): return "mat4ex\(id)"
|
||||
case .fyzYears: return "fyzyears"
|
||||
case .fyzMap: return "fyzmap"
|
||||
case .fyzLesson(let id): return "fyzunit\(id)"
|
||||
|
|
@ -254,6 +286,38 @@ enum Route: Hashable {
|
|||
return [.sciMap, .bioMap, .bioLesson(id)]
|
||||
case .bioEx(let id):
|
||||
return [.sciMap, .bioMap, .bioLesson(id), .bioEx(id)]
|
||||
case .matYears:
|
||||
return [.matYears]
|
||||
case .mat0Map:
|
||||
return [.matYears, .mat0Map]
|
||||
case .mat0Lesson(let id):
|
||||
return [.matYears, .mat0Map, .mat0Lesson(id)]
|
||||
case .mat0Ex(let id):
|
||||
return [.matYears, .mat0Map, .mat0Lesson(id), .mat0Ex(id)]
|
||||
case .matMap:
|
||||
return [.matYears, .matMap]
|
||||
case .matLesson(let id):
|
||||
return [.matYears, .matMap, .matLesson(id)]
|
||||
case .matEx(let id):
|
||||
return [.matYears, .matMap, .matLesson(id), .matEx(id)]
|
||||
case .mat2Map:
|
||||
return [.matYears, .mat2Map]
|
||||
case .mat2Lesson(let id):
|
||||
return [.matYears, .mat2Map, .mat2Lesson(id)]
|
||||
case .mat2Ex(let id):
|
||||
return [.matYears, .mat2Map, .mat2Lesson(id), .mat2Ex(id)]
|
||||
case .mat3Map:
|
||||
return [.matYears, .mat3Map]
|
||||
case .mat3Lesson(let id):
|
||||
return [.matYears, .mat3Map, .mat3Lesson(id)]
|
||||
case .mat3Ex(let id):
|
||||
return [.matYears, .mat3Map, .mat3Lesson(id), .mat3Ex(id)]
|
||||
case .mat4Map:
|
||||
return [.matYears, .mat4Map]
|
||||
case .mat4Lesson(let id):
|
||||
return [.matYears, .mat4Map, .mat4Lesson(id)]
|
||||
case .mat4Ex(let id):
|
||||
return [.matYears, .mat4Map, .mat4Lesson(id), .mat4Ex(id)]
|
||||
case .fyzYears:
|
||||
return [.fyzYears]
|
||||
case .fyzMap:
|
||||
|
|
@ -321,6 +385,12 @@ func routeFromPage(_ page: String) -> Route? {
|
|||
case "scimap": return .sciMap
|
||||
case "chemmap": return .chemMap
|
||||
case "biomap": return .bioMap
|
||||
case "matyears": return .matYears
|
||||
case "mat0map": return .mat0Map
|
||||
case "matmap": return .matMap
|
||||
case "mat2map": return .mat2Map
|
||||
case "mat3map": return .mat3Map
|
||||
case "mat4map": return .mat4Map
|
||||
case "fyzyears": return .fyzYears
|
||||
case "fyzmap": return .fyzMap
|
||||
case "fyz2map": return .fyz2Map
|
||||
|
|
@ -381,6 +451,36 @@ func routeFromPage(_ page: String) -> Route? {
|
|||
if let m = page.wholeMatch(of: /bioex(\d+)/), let n = Int(m.1) {
|
||||
return .bioEx(n)
|
||||
}
|
||||
if let m = page.wholeMatch(of: /mat0unit(\d+)/), let n = Int(m.1) {
|
||||
return .mat0Lesson(n)
|
||||
}
|
||||
if let m = page.wholeMatch(of: /mat0ex(\d+)/), let n = Int(m.1) {
|
||||
return .mat0Ex(n)
|
||||
}
|
||||
if let m = page.wholeMatch(of: /mat4unit(\d+)/), let n = Int(m.1) {
|
||||
return .mat4Lesson(n)
|
||||
}
|
||||
if let m = page.wholeMatch(of: /mat4ex(\d+)/), let n = Int(m.1) {
|
||||
return .mat4Ex(n)
|
||||
}
|
||||
if let m = page.wholeMatch(of: /mat3unit(\d+)/), let n = Int(m.1) {
|
||||
return .mat3Lesson(n)
|
||||
}
|
||||
if let m = page.wholeMatch(of: /mat3ex(\d+)/), let n = Int(m.1) {
|
||||
return .mat3Ex(n)
|
||||
}
|
||||
if let m = page.wholeMatch(of: /mat2unit(\d+)/), let n = Int(m.1) {
|
||||
return .mat2Lesson(n)
|
||||
}
|
||||
if let m = page.wholeMatch(of: /mat2ex(\d+)/), let n = Int(m.1) {
|
||||
return .mat2Ex(n)
|
||||
}
|
||||
if let m = page.wholeMatch(of: /matunit(\d+)/), let n = Int(m.1) {
|
||||
return .matLesson(n)
|
||||
}
|
||||
if let m = page.wholeMatch(of: /matex(\d+)/), let n = Int(m.1) {
|
||||
return .matEx(n)
|
||||
}
|
||||
if let m = page.wholeMatch(of: /fyz4unit(\d+)/), let n = Int(m.1) {
|
||||
return .fyz4Lesson(n)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -609,6 +609,7 @@ func subjectSymbol(_ icon: String) -> String {
|
|||
case "people": return "person.2.fill"
|
||||
case "flask": return "flask.fill"
|
||||
case "bolt": return "bolt.fill"
|
||||
case "function": return "function"
|
||||
default: return "lock.fill"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -646,6 +646,255 @@ struct SciQuizScreen: View {
|
|||
}
|
||||
}
|
||||
|
||||
private func mathLesson(_ vm: AppModel, year: Int, id: Int) -> J? {
|
||||
switch year {
|
||||
case 0: return vm.content.mat0Lesson(id)
|
||||
case 2: return vm.content.mat2Lesson(id)
|
||||
case 3: return vm.content.mat3Lesson(id)
|
||||
case 4: return vm.content.mat4Lesson(id)
|
||||
default: return vm.content.matLesson(id)
|
||||
}
|
||||
}
|
||||
|
||||
private func markMathLesson(_ vm: AppModel, year: Int, id: Int) {
|
||||
switch year {
|
||||
case 0: vm.markMat0(id)
|
||||
case 2: vm.markMat2(id)
|
||||
case 3: vm.markMat3(id)
|
||||
case 4: vm.markMat4(id)
|
||||
default: vm.markMat(id)
|
||||
}
|
||||
}
|
||||
|
||||
struct MathPracticeScreen: View {
|
||||
@ObservedObject var vm: AppModel
|
||||
let year: Int
|
||||
let id: Int
|
||||
@State private var text: [Int: String] = [:]
|
||||
@State private var points: [Int: [(Int, Int)]] = [:]
|
||||
@State private var marks: [Int: Bool] = [:]
|
||||
@State private var revealed = false
|
||||
@State private var feedback = ""
|
||||
@State private var kind = ""
|
||||
|
||||
var body: some View {
|
||||
if let lesson = mathLesson(vm, year: year, id: id) {
|
||||
let problems = lesson.arr("problems")
|
||||
ExerciseScaffold(
|
||||
vm: vm,
|
||||
title: vm.tr(lesson.str("exTitleKey")),
|
||||
sub: vm.tr(lesson.str("quizHeadKey")),
|
||||
action: vm.tr("check"),
|
||||
onAction: { check(problems) },
|
||||
feedback: feedback,
|
||||
kind: kind
|
||||
) {
|
||||
Text(vm.tr("math_quiz_intro"))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
.padding(.bottom, 4)
|
||||
ForEach(problems.indices, id: \.self) { i in
|
||||
mathCard(problems[i], index: i)
|
||||
}
|
||||
}
|
||||
.onChange(of: year) { _, _ in reset() }
|
||||
.onChange(of: id) { _, _ in reset() }
|
||||
}
|
||||
}
|
||||
|
||||
private func reset() {
|
||||
text = [:]
|
||||
points = [:]
|
||||
marks = [:]
|
||||
revealed = false
|
||||
feedback = ""
|
||||
kind = ""
|
||||
}
|
||||
|
||||
private func check(_ problems: [J]) {
|
||||
var all = !problems.isEmpty
|
||||
var next: [Int: Bool] = [:]
|
||||
for i in problems.indices {
|
||||
let spec = problems[i].str("answer")
|
||||
let good = mathIsDraw(spec)
|
||||
? mathDrawOk(spec, points[i] ?? [])
|
||||
: mathAnswerOk(spec, text[i] ?? "")
|
||||
next[i] = good
|
||||
if !good { all = false }
|
||||
}
|
||||
marks = next
|
||||
revealed = true
|
||||
if all {
|
||||
feedback = vm.tr("feedback_ok")
|
||||
kind = "ok"
|
||||
markMathLesson(vm, year: year, id: id)
|
||||
} else {
|
||||
feedback = vm.tr("feedback_retry_short")
|
||||
kind = "bad"
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func mathCard(_ problem: J, index i: Int) -> some View {
|
||||
let spec = problem.str("answer")
|
||||
let p = vm.palette
|
||||
let border: Color = {
|
||||
guard let mark = marks[i] else { return p.surface2 }
|
||||
return mark ? p.success : p.error
|
||||
}()
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(problem.str("prompt"))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
if mathIsDraw(spec) {
|
||||
Text(vm.tr("math_draw_hint"))
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
MathGrid(
|
||||
given: mathDrawGiven(spec),
|
||||
user: points[i] ?? [],
|
||||
need: mathDrawNeed(spec),
|
||||
mark: marks[i],
|
||||
palette: p
|
||||
) { next in
|
||||
points[i] = next
|
||||
}
|
||||
HStack {
|
||||
Button(vm.tr("math_undo")) {
|
||||
var pts = points[i] ?? []
|
||||
if !pts.isEmpty {
|
||||
pts.removeLast()
|
||||
points[i] = pts
|
||||
}
|
||||
}
|
||||
Button(vm.tr("math_clear")) { points[i] = [] }
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
} else {
|
||||
TextField(vm.tr("math_answer_ph"), text: Binding(
|
||||
get: { text[i] ?? "" },
|
||||
set: { text[i] = $0 }
|
||||
))
|
||||
.textFieldStyle(.roundedBorder)
|
||||
#if os(iOS)
|
||||
.textInputAutocapitalization(.never)
|
||||
.keyboardType(.numbersAndPunctuation)
|
||||
#endif
|
||||
.autocorrectionDisabled()
|
||||
}
|
||||
if revealed {
|
||||
Text(problem.str("hint"))
|
||||
.font(.footnote)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
.padding(12)
|
||||
.background(p.surface0, in: RoundedRectangle(cornerRadius: 12))
|
||||
.overlay(RoundedRectangle(cornerRadius: 12).stroke(border, lineWidth: 1.5))
|
||||
}
|
||||
}
|
||||
|
||||
private struct MathGrid: View {
|
||||
let given: [(Int, Int)]
|
||||
let user: [(Int, Int)]
|
||||
let need: Int
|
||||
let mark: Bool?
|
||||
let palette: Palette
|
||||
var onChange: ([(Int, Int)]) -> Void
|
||||
|
||||
private let cell: CGFloat = 28
|
||||
private let pad: CGFloat = 18
|
||||
private let gx = 10
|
||||
private let gy = 8
|
||||
private var width: CGFloat { pad * 2 + CGFloat(gx) * cell }
|
||||
private var height: CGFloat { pad * 2 + CGFloat(gy) * cell }
|
||||
|
||||
private func screen(_ x: Int, _ y: Int) -> CGPoint {
|
||||
CGPoint(x: pad + CGFloat(x) * cell, y: height - pad - CGFloat(y) * cell)
|
||||
}
|
||||
|
||||
private func snap(_ loc: CGPoint) -> (Int, Int)? {
|
||||
let cx = (loc.x - pad) / cell
|
||||
let cy = (height - pad - loc.y) / cell
|
||||
let ix = Int(cx.rounded())
|
||||
let iy = Int(cy.rounded())
|
||||
if ix < 0 || ix > gx || iy < 0 || iy > gy { return nil }
|
||||
if abs(cx - CGFloat(ix)) > 0.45 || abs(cy - CGFloat(iy)) > 0.45 { return nil }
|
||||
return (ix, iy)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Canvas { ctx, _ in
|
||||
let rect = CGRect(x: 0, y: 0, width: width, height: height)
|
||||
ctx.fill(Path(rect), with: .color(palette.surface1))
|
||||
var grid = Path()
|
||||
for i in 0...gx {
|
||||
let x = pad + CGFloat(i) * cell
|
||||
grid.move(to: CGPoint(x: x, y: pad))
|
||||
grid.addLine(to: CGPoint(x: x, y: height - pad))
|
||||
}
|
||||
for i in 0...gy {
|
||||
let y = pad + CGFloat(i) * cell
|
||||
grid.move(to: CGPoint(x: pad, y: y))
|
||||
grid.addLine(to: CGPoint(x: width - pad, y: y))
|
||||
}
|
||||
ctx.stroke(grid, with: .color(palette.subtext.opacity(0.55)), lineWidth: 1)
|
||||
var axes = Path()
|
||||
axes.move(to: CGPoint(x: pad, y: height - pad))
|
||||
axes.addLine(to: CGPoint(x: width - pad, y: height - pad))
|
||||
axes.move(to: CGPoint(x: pad, y: height - pad))
|
||||
axes.addLine(to: CGPoint(x: pad, y: pad))
|
||||
ctx.stroke(axes, with: .color(palette.text), lineWidth: 1.6)
|
||||
if given.count >= 2 {
|
||||
var seg = Path()
|
||||
seg.move(to: screen(given[0].0, given[0].1))
|
||||
seg.addLine(to: screen(given[1].0, given[1].1))
|
||||
if given.count >= 3 {
|
||||
seg.move(to: screen(given[0].0, given[0].1))
|
||||
seg.addLine(to: screen(given[2].0, given[2].1))
|
||||
}
|
||||
ctx.stroke(seg, with: .color(palette.accent), lineWidth: 2)
|
||||
}
|
||||
let names = ["A", "B", "C", "D"]
|
||||
for (i, g) in given.enumerated() where i < names.count {
|
||||
let pt = screen(g.0, g.1)
|
||||
ctx.fill(Path(ellipseIn: CGRect(x: pt.x - 5.5, y: pt.y - 5.5, width: 11, height: 11)), with: .color(palette.accent))
|
||||
ctx.draw(
|
||||
Text(names[i]).font(.system(size: 13, weight: .semibold)).foregroundStyle(palette.text),
|
||||
at: CGPoint(x: pt.x + 8, y: pt.y - 8),
|
||||
anchor: .bottomLeading
|
||||
)
|
||||
}
|
||||
let dot = mark == false ? palette.error : palette.success
|
||||
for (i, u) in user.enumerated() {
|
||||
let pt = screen(u.0, u.1)
|
||||
ctx.fill(Path(ellipseIn: CGRect(x: pt.x - 5.5, y: pt.y - 5.5, width: 11, height: 11)), with: .color(dot))
|
||||
ctx.draw(
|
||||
Text("\(i + 1)").font(.system(size: 12, weight: .bold)).foregroundStyle(palette.text),
|
||||
at: CGPoint(x: pt.x + 8, y: pt.y - 8),
|
||||
anchor: .bottomLeading
|
||||
)
|
||||
}
|
||||
}
|
||||
.frame(width: width, height: height)
|
||||
.contentShape(Rectangle())
|
||||
.gesture(
|
||||
DragGesture(minimumDistance: 0).onEnded { value in
|
||||
guard let hit = snap(value.location) else { return }
|
||||
if given.contains(where: { $0.0 == hit.0 && $0.1 == hit.1 }) { return }
|
||||
var next = user
|
||||
if let idx = next.firstIndex(where: { $0.0 == hit.0 && $0.1 == hit.1 }) {
|
||||
next.remove(at: idx)
|
||||
} else if next.count < need {
|
||||
next.append(hit)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
onChange(next)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct FyzQuizScreen: View {
|
||||
@ObservedObject var vm: AppModel
|
||||
let year: Int
|
||||
|
|
|
|||
|
|
@ -699,6 +699,13 @@ private func subjectSum(_ vm: AppModel, _ s: J) -> ProgressSum {
|
|||
sum.totalEx = vm.content.chem.count + vm.content.bio.count
|
||||
sum.doneEx = vm.content.chem.filter { vm.progress.chemDone($0.int("id")) }.count
|
||||
+ vm.content.bio.filter { vm.progress.bioDone($0.int("id")) }.count
|
||||
case "matyears", "mat0map", "matmap", "mat2map", "mat3map", "mat4map":
|
||||
sum.totalEx = vm.content.mat0.count + vm.content.mat.count + vm.content.mat2.count + vm.content.mat3.count + vm.content.mat4.count
|
||||
sum.doneEx = vm.content.mat0.filter { vm.progress.mat0Done($0.int("id")) }.count
|
||||
+ vm.content.mat.filter { vm.progress.matDone($0.int("id")) }.count
|
||||
+ 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 "fyzyears", "fyzmap", "fyz2map", "fyz3map", "fyz4map":
|
||||
sum.totalEx = vm.content.fyz.count + vm.content.fyz2.count + vm.content.fyz3.count + vm.content.fyz4.count
|
||||
sum.doneEx = vm.content.fyz.filter { vm.progress.fyzDone($0.int("id")) }.count
|
||||
|
|
@ -935,6 +942,32 @@ private func buildSearch(_ vm: AppModel) -> [Hit] {
|
|||
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "lit4unit\(l.int("id"))", extra: "literatura")
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "lit4ex\(l.int("id"))")
|
||||
}
|
||||
add(vm.tr("Matematika"), vm.tr("search_lesson"), "matyears", extra: "matematika")
|
||||
add(vm.tr("mat0_year"), vm.tr("search_lesson"), "mat0map", extra: "zakladka")
|
||||
add(vm.tr("mat_year1"), vm.tr("search_lesson"), "matmap", extra: "rocnik")
|
||||
add(vm.tr("mat_year2"), vm.tr("search_lesson"), "mat2map", extra: "rocnik")
|
||||
add(vm.tr("mat_year3"), vm.tr("search_lesson"), "mat3map", extra: "rocnik")
|
||||
add(vm.tr("mat_year4"), vm.tr("search_lesson"), "mat4map", extra: "rocnik")
|
||||
for l in vm.content.mat0 {
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "mat0unit\(l.int("id"))", extra: "matematika")
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "mat0ex\(l.int("id"))")
|
||||
}
|
||||
for l in vm.content.mat {
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "matunit\(l.int("id"))", extra: "matematika")
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "matex\(l.int("id"))")
|
||||
}
|
||||
for l in vm.content.mat2 {
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "mat2unit\(l.int("id"))", extra: "matematika")
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "mat2ex\(l.int("id"))")
|
||||
}
|
||||
for l in vm.content.mat3 {
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "mat3unit\(l.int("id"))", extra: "matematika")
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "mat3ex\(l.int("id"))")
|
||||
}
|
||||
for l in vm.content.mat4 {
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "mat4unit\(l.int("id"))", extra: "matematika")
|
||||
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "mat4ex\(l.int("id"))")
|
||||
}
|
||||
add(vm.tr("Fyzika"), vm.tr("search_lesson"), "fyzyears", extra: "fyzika")
|
||||
add(vm.tr("fyz_year1"), vm.tr("search_lesson"), "fyzmap", extra: "rocnik")
|
||||
add(vm.tr("fyz_year2"), vm.tr("search_lesson"), "fyz2map", extra: "rocnik")
|
||||
|
|
@ -1512,6 +1545,51 @@ struct LitYearsScreen: View {
|
|||
}
|
||||
}
|
||||
|
||||
struct MatYearsScreen: View {
|
||||
@ObservedObject var vm: AppModel
|
||||
|
||||
var body: some View {
|
||||
#if os(macOS)
|
||||
let years: [(String, String, Route, String)] = [
|
||||
("mat0_year", "mat0_sub", .mat0Map, "ZŠ"),
|
||||
("mat_year1", "mat_sub", .matMap, "1."),
|
||||
("mat_year2", "mat2_sub", .mat2Map, "2."),
|
||||
("mat_year3", "mat3_sub", .mat3Map, "3."),
|
||||
("mat_year4", "mat4_sub", .mat4Map, "4."),
|
||||
]
|
||||
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: years[i].3)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
.navigationTitle(vm.tr("Matematika"))
|
||||
.navigationSubtitle(vm.tr("mat_years_sub"))
|
||||
#else
|
||||
List {
|
||||
NavigationLink(value: Route.mat0Map) {
|
||||
ListRowLabel(title: vm.tr("mat0_year"), subtitle: vm.tr("mat0_sub"))
|
||||
}
|
||||
NavigationLink(value: Route.matMap) {
|
||||
ListRowLabel(title: vm.tr("mat_year1"), subtitle: vm.tr("mat_sub"))
|
||||
}
|
||||
NavigationLink(value: Route.mat2Map) {
|
||||
ListRowLabel(title: vm.tr("mat_year2"), subtitle: vm.tr("mat2_sub"))
|
||||
}
|
||||
NavigationLink(value: Route.mat3Map) {
|
||||
ListRowLabel(title: vm.tr("mat_year3"), subtitle: vm.tr("mat3_sub"))
|
||||
}
|
||||
NavigationLink(value: Route.mat4Map) {
|
||||
ListRowLabel(title: vm.tr("mat_year4"), subtitle: vm.tr("mat4_sub"))
|
||||
}
|
||||
}
|
||||
.navigationTitle(vm.tr("Matematika"))
|
||||
.navigationSubtitle(vm.tr("mat_years_sub"))
|
||||
.appListStyle()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
struct FyzYearsScreen: View {
|
||||
@ObservedObject var vm: AppModel
|
||||
|
||||
|
|
@ -2321,6 +2399,88 @@ struct RouteDestination: View {
|
|||
}
|
||||
case .bioEx(let id):
|
||||
SciQuizScreen(vm: vm, area: "bio", id: id)
|
||||
case .matYears:
|
||||
MatYearsScreen(vm: vm)
|
||||
case .mat0Map:
|
||||
LessonListScreen(
|
||||
vm: vm,
|
||||
title: vm.tr("mat0_year"),
|
||||
subtitle: vm.tr("mat0_sub"),
|
||||
rows: vm.content.mat0.map { l in
|
||||
let id = l.int("id")
|
||||
return LessonRow(id: "m0\(id)", title: vm.tr(l.str("titleKey")), done: vm.progress.mat0Done(id), destination: .mat0Lesson(id))
|
||||
}
|
||||
)
|
||||
case .mat0Lesson(let id):
|
||||
if let l = vm.content.mat0Lesson(id) {
|
||||
SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), subtitle: vm.tr(l.str("subKey")), slides: l.arr("slides"), next: .mat0Ex(id))
|
||||
}
|
||||
case .mat0Ex(let id):
|
||||
MathPracticeScreen(vm: vm, year: 0, id: id)
|
||||
case .matMap:
|
||||
LessonListScreen(
|
||||
vm: vm,
|
||||
title: vm.tr("mat_year1"),
|
||||
subtitle: vm.tr("mat_sub"),
|
||||
rows: vm.content.mat.map { l in
|
||||
let id = l.int("id")
|
||||
return LessonRow(id: "m1\(id)", title: vm.tr(l.str("titleKey")), done: vm.progress.matDone(id), destination: .matLesson(id))
|
||||
}
|
||||
)
|
||||
case .matLesson(let id):
|
||||
if let l = vm.content.matLesson(id) {
|
||||
SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), subtitle: vm.tr(l.str("subKey")), slides: l.arr("slides"), next: .matEx(id))
|
||||
}
|
||||
case .matEx(let id):
|
||||
MathPracticeScreen(vm: vm, year: 1, id: id)
|
||||
case .mat2Map:
|
||||
LessonListScreen(
|
||||
vm: vm,
|
||||
title: vm.tr("mat_year2"),
|
||||
subtitle: vm.tr("mat2_sub"),
|
||||
rows: vm.content.mat2.map { l in
|
||||
let id = l.int("id")
|
||||
return LessonRow(id: "m2\(id)", title: vm.tr(l.str("titleKey")), done: vm.progress.mat2Done(id), destination: .mat2Lesson(id))
|
||||
}
|
||||
)
|
||||
case .mat2Lesson(let id):
|
||||
if let l = vm.content.mat2Lesson(id) {
|
||||
SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), subtitle: vm.tr(l.str("subKey")), slides: l.arr("slides"), next: .mat2Ex(id))
|
||||
}
|
||||
case .mat2Ex(let id):
|
||||
MathPracticeScreen(vm: vm, year: 2, id: id)
|
||||
case .mat3Map:
|
||||
LessonListScreen(
|
||||
vm: vm,
|
||||
title: vm.tr("mat_year3"),
|
||||
subtitle: vm.tr("mat3_sub"),
|
||||
rows: vm.content.mat3.map { l in
|
||||
let id = l.int("id")
|
||||
return LessonRow(id: "m3\(id)", title: vm.tr(l.str("titleKey")), done: vm.progress.mat3Done(id), destination: .mat3Lesson(id))
|
||||
}
|
||||
)
|
||||
case .mat3Lesson(let id):
|
||||
if let l = vm.content.mat3Lesson(id) {
|
||||
SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), subtitle: vm.tr(l.str("subKey")), slides: l.arr("slides"), next: .mat3Ex(id))
|
||||
}
|
||||
case .mat3Ex(let id):
|
||||
MathPracticeScreen(vm: vm, year: 3, id: id)
|
||||
case .mat4Map:
|
||||
LessonListScreen(
|
||||
vm: vm,
|
||||
title: vm.tr("mat_year4"),
|
||||
subtitle: vm.tr("mat4_sub"),
|
||||
rows: vm.content.mat4.map { l in
|
||||
let id = l.int("id")
|
||||
return LessonRow(id: "m4\(id)", title: vm.tr(l.str("titleKey")), done: vm.progress.mat4Done(id), destination: .mat4Lesson(id))
|
||||
}
|
||||
)
|
||||
case .mat4Lesson(let id):
|
||||
if let l = vm.content.mat4Lesson(id) {
|
||||
SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), subtitle: vm.tr(l.str("subKey")), slides: l.arr("slides"), next: .mat4Ex(id))
|
||||
}
|
||||
case .mat4Ex(let id):
|
||||
MathPracticeScreen(vm: vm, year: 4, id: id)
|
||||
case .fyzYears:
|
||||
FyzYearsScreen(vm: vm)
|
||||
case .fyzMap:
|
||||
|
|
|
|||
|
|
@ -455,6 +455,20 @@ def profile_items(rows) -> list[dict]:
|
|||
return out
|
||||
|
||||
|
||||
def math_items(rows) -> list[dict]:
|
||||
out = []
|
||||
for row in as_list(rows):
|
||||
row = as_list(row)
|
||||
if len(row) < 3:
|
||||
continue
|
||||
out.append({
|
||||
"prompt": as_str(row[0]) or "",
|
||||
"answer": as_str(row[1]) or "",
|
||||
"hint": as_str(row[2]) or "",
|
||||
})
|
||||
return out
|
||||
|
||||
|
||||
def slides_items(rows) -> list[dict]:
|
||||
out = []
|
||||
for row in as_list(rows):
|
||||
|
|
@ -1116,6 +1130,28 @@ def main() -> int:
|
|||
fyz3_lessons = lit_pack("fyz3")
|
||||
fyz4_lessons = lit_pack("fyz4")
|
||||
|
||||
def math_pack(prefix):
|
||||
lessons = []
|
||||
for i in range(1, 9):
|
||||
fn_u = f"build_{prefix}_unit{i}_page"
|
||||
fn_e = f"build_{prefix}_unit{i}_exercise_page"
|
||||
lessons.append({
|
||||
"id": i,
|
||||
"titleKey": f"{prefix}_unit{i}",
|
||||
"subKey": f"{prefix}_unit{i}_sub",
|
||||
"exTitleKey": f"{prefix}_ex{i}_title",
|
||||
"quizHeadKey": f"{prefix}_quiz{i}_head",
|
||||
"slides": slides_items(A(f"{fn_u}.slides")),
|
||||
"problems": math_items(A(f"{fn_e}.qs")),
|
||||
})
|
||||
return lessons
|
||||
|
||||
mat0_lessons = math_pack("mat0")
|
||||
mat_lessons = math_pack("mat")
|
||||
mat2_lessons = math_pack("mat2")
|
||||
mat3_lessons = math_pack("mat3")
|
||||
mat4_lessons = math_pack("mat4")
|
||||
|
||||
on_lessons = []
|
||||
for i in range(1, 18):
|
||||
on_lessons.append({
|
||||
|
|
@ -1563,7 +1599,7 @@ def main() -> int:
|
|||
{"key": "Český jazyk a literatura", "open": True, "target": "czechmap", "icon": "cz"},
|
||||
{"key": "Občanská nauka", "open": True, "target": "onyears", "icon": "people"},
|
||||
{"key": "English", "open": False, "target": None, "icon": "lock"},
|
||||
{"key": "Matematika", "open": False, "target": None, "icon": "lock"},
|
||||
{"key": "Matematika", "open": True, "target": "matyears", "icon": "function"},
|
||||
{"key": "Fyzika", "open": True, "target": "fyzyears", "icon": "bolt"},
|
||||
{"key": "Základy Přírodopisných věd", "open": True, "target": "scimap", "icon": "flask"},
|
||||
{"key": "Technická grafika", "open": False, "target": None, "icon": "lock"},
|
||||
|
|
@ -1593,6 +1629,11 @@ def main() -> int:
|
|||
"fyz2": fyz2_lessons,
|
||||
"fyz3": fyz3_lessons,
|
||||
"fyz4": fyz4_lessons,
|
||||
"mat0": mat0_lessons,
|
||||
"mat": mat_lessons,
|
||||
"mat2": mat2_lessons,
|
||||
"mat3": mat3_lessons,
|
||||
"mat4": mat4_lessons,
|
||||
"on2": on2_lessons,
|
||||
"on3": on3_lessons,
|
||||
"on4": on4_lessons,
|
||||
|
|
@ -1635,6 +1676,7 @@ def main() -> int:
|
|||
print(f" chem lessons: {sum(1 for L in chem_lessons if L['slides'])}")
|
||||
print(f" bio lessons: {sum(1 for L in bio_lessons if L['slides'])}")
|
||||
print(f" fyz lessons: {sum(1 for L in fyz_lessons + fyz2_lessons + fyz3_lessons + fyz4_lessons if L['slides'])}")
|
||||
print(f" math lessons: {sum(1 for L in mat0_lessons + mat_lessons + mat2_lessons + mat3_lessons + mat4_lessons if L['slides'])}")
|
||||
print(f" on2 lessons: {sum(1 for L in on2_lessons if L['slides'])}")
|
||||
print(f" on3 lessons: {sum(1 for L in on3_lessons if L['slides'])}")
|
||||
print(f" on4 lessons: {sum(1 for L in on4_lessons if L['slides'])}")
|
||||
|
|
@ -1705,6 +1747,17 @@ def main() -> int:
|
|||
missing.append(f"{label}{L['id']} quiz")
|
||||
if not all(q.get("expl") for q in L["quiz"]):
|
||||
missing.append(f"{label}{L['id']} expl")
|
||||
for label, block in (
|
||||
("mat0", mat0_lessons), ("mat", mat_lessons),
|
||||
("mat2", mat2_lessons), ("mat3", mat3_lessons), ("mat4", mat4_lessons),
|
||||
):
|
||||
for L in block:
|
||||
if len(L["slides"]) != 3:
|
||||
missing.append(f"{label}{L['id']} slides")
|
||||
if not L["problems"]:
|
||||
missing.append(f"{label}{L['id']} problems")
|
||||
if not all(p.get("prompt") and p.get("answer") and p.get("hint") for p in L["problems"]):
|
||||
missing.append(f"{label}{L['id']} answer")
|
||||
for m in mluvnice:
|
||||
if m["type"] in ("typed", "reveal") and not m.get("items"):
|
||||
missing.append(f"mluv{m['id']}")
|
||||
|
|
|
|||
2040
scripts/gen-math.py
Normal file
2040
scripts/gen-math.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -20,6 +20,7 @@
|
|||
#define HW_SUBJ 2 /* index of "Technické vybavení" */
|
||||
#define CZ_SUBJ 3 /* index of "Český jazyk a literatura" */
|
||||
#define ON_SUBJ 4 /* index of "Občanská nauka" */
|
||||
#define MATH_SUBJ 6 /* index of "Matematika" */
|
||||
#define PHY_SUBJ 7 /* index of "Fyzika" */
|
||||
#define SCI_SUBJ 8 /* index of "Základy Přírodopisných věd" */
|
||||
#define CSS_FILE "style.css"
|
||||
|
|
@ -67,6 +68,11 @@ extern char *app_progress_fyz;
|
|||
extern char *app_progress_fyz2;
|
||||
extern char *app_progress_fyz3;
|
||||
extern char *app_progress_fyz4;
|
||||
extern char *app_progress_mat0;
|
||||
extern char *app_progress_mat;
|
||||
extern char *app_progress_mat2;
|
||||
extern char *app_progress_mat3;
|
||||
extern char *app_progress_mat4;
|
||||
#define PROGRESS_DIR app_progress_dir
|
||||
#define PROGRESS_U1 app_progress_u1
|
||||
#define PROGRESS_U2 app_progress_u2
|
||||
|
|
@ -146,6 +152,7 @@ extern char *app_progress_fyz4;
|
|||
#define LIT_N 8
|
||||
#define SCI_N 8
|
||||
#define FYZ_N 8
|
||||
#define MATH_N 8
|
||||
#define HW_SLIDES 3
|
||||
#define HW2_SLIDES 6
|
||||
#define HW3_SLIDES 2
|
||||
|
|
@ -345,6 +352,11 @@ typedef struct {
|
|||
int n_options;
|
||||
int correct;
|
||||
} ChoiceQ;
|
||||
typedef struct {
|
||||
const char *prompt;
|
||||
const char *answer;
|
||||
const char *hint;
|
||||
} MathItem;
|
||||
typedef struct {
|
||||
const ChoiceQ *qs;
|
||||
int n;
|
||||
|
|
@ -979,6 +991,37 @@ extern NetLesson fyz_lessons[FYZ_N];
|
|||
extern NetLesson fyz2_lessons[FYZ_N];
|
||||
extern NetLesson fyz3_lessons[FYZ_N];
|
||||
extern NetLesson fyz4_lessons[FYZ_N];
|
||||
gboolean math_answer_ok(const char *spec, const char *user);
|
||||
gboolean math_is_draw(const char *spec);
|
||||
int math_draw_need(const char *spec);
|
||||
int math_draw_fixed(const char *spec, int *xs, int *ys, int cap);
|
||||
gboolean math_draw_ok(const char *spec, const int *xs, const int *ys, int n);
|
||||
void math_load_progress(void);
|
||||
void math_rail_theme_reset(void);
|
||||
void math_lessons_apply_lang(void);
|
||||
void progress_for_math(ProgressSum *out);
|
||||
GtkWidget *math_unit_page(int year, NetLesson *L, const char *title_key,
|
||||
const char *sub_key, const NetSlide *slides,
|
||||
guint n_slides);
|
||||
GtkWidget *math_practice_page(int year, int lesson_id, const char *back_page,
|
||||
const char *title_key, const char *heading_key,
|
||||
const MathItem *items, int n);
|
||||
GtkWidget *build_matyears_page(void);
|
||||
GtkWidget *build_mat0map_page(void);
|
||||
GtkWidget *build_matmap_page(void);
|
||||
GtkWidget *build_mat2map_page(void);
|
||||
GtkWidget *build_mat3map_page(void);
|
||||
GtkWidget *build_mat4map_page(void);
|
||||
void add_mat0_pages(GtkStack *stack);
|
||||
void add_mat_pages(GtkStack *stack);
|
||||
void add_mat2_pages(GtkStack *stack);
|
||||
void add_mat3_pages(GtkStack *stack);
|
||||
void add_mat4_pages(GtkStack *stack);
|
||||
extern NetLesson mat0_lessons[MATH_N];
|
||||
extern NetLesson mat_lessons[MATH_N];
|
||||
extern NetLesson mat2_lessons[MATH_N];
|
||||
extern NetLesson mat3_lessons[MATH_N];
|
||||
extern NetLesson mat4_lessons[MATH_N];
|
||||
void hw_lessons_apply_lang(void);
|
||||
GtkWidget *build_hwyears_page(void);
|
||||
GtkWidget *build_hwmap_page(void);
|
||||
|
|
@ -1068,6 +1111,8 @@ void draw_civics_icon(GtkDrawingArea *area, cairo_t *cr,
|
|||
int width, int height, gpointer data);
|
||||
void draw_science_icon(GtkDrawingArea *area, cairo_t *cr,
|
||||
int width, int height, gpointer data);
|
||||
void draw_math_icon(GtkDrawingArea *area, cairo_t *cr,
|
||||
int width, int height, gpointer data);
|
||||
void draw_physics_icon(GtkDrawingArea *area, cairo_t *cr,
|
||||
int width, int height, gpointer data);
|
||||
void draw_czech_flag(GtkDrawingArea *area, cairo_t *cr,
|
||||
|
|
|
|||
11
src/i18n.c
11
src/i18n.c
|
|
@ -90,12 +90,12 @@ const TrEntry tr_ui[] = {
|
|||
"Cvičení, nápovědy a vokabeltraining. Aplikace si pamatuje, kde jste skončili.",
|
||||
"Exercises, hints and vocabulary drills. The app remembers where you left off."},
|
||||
{"welcome_feat1_link", "Začít procvičovat", "Start practicing"},
|
||||
{"welcome_feat2_title", "Sedm otevřených předmětů", "Seven open subjects"},
|
||||
{"welcome_feat2_title", "Osm otevřených předmětů", "Eight open subjects"},
|
||||
{"welcome_feat2_body",
|
||||
"Deutsch, sítě, hardware, čeština, občanská nauka, fyzika a přírodní vědy. Ostatní předměty čekají na svůj obsah.",
|
||||
"German, networks, hardware, Czech, civics, physics and natural sciences. The other subjects are still locked."},
|
||||
"Deutsch, sítě, hardware, čeština, občanská nauka, matematika, fyzika a přírodní vědy. Ostatní předměty čekají na svůj obsah.",
|
||||
"German, networks, hardware, Czech, civics, mathematics, physics and natural sciences. The other subjects are still locked."},
|
||||
{"welcome_feat2_link", "Jaké předměty jsou otevřené?", "Which subjects are open?"},
|
||||
{"welcome_stat_value", "7", "7"},
|
||||
{"welcome_stat_value", "8", "8"},
|
||||
{"welcome_stat_label", "otevřené\npředměty", "open\nsubjects"},
|
||||
{"continue", "Pokračuj", "Continue"},
|
||||
{"roadmap_title", "Učební plán", "Learning path"},
|
||||
|
|
@ -1619,6 +1619,7 @@ const TrEntry tr_ui[] = {
|
|||
{"lit_fuks_plot_title", "Sestavte děj", "Order the plot"},
|
||||
{"lit_fuks_plot_sub", "Přetáhněte části příběhu do správného pořadí.",
|
||||
"Drag the story parts into the correct order."},
|
||||
#include "math_i18n.inc"
|
||||
{NULL, NULL, NULL}
|
||||
};
|
||||
|
||||
|
|
@ -2603,6 +2604,7 @@ void on4_lessons_apply_lang(void);
|
|||
void lit_lessons_apply_lang(void);
|
||||
void sci_lessons_apply_lang(void);
|
||||
void fyz_lessons_apply_lang(void);
|
||||
void math_lessons_apply_lang(void);
|
||||
void net_rail_theme_reset(void);
|
||||
|
||||
void apply_language(void) {
|
||||
|
|
@ -2624,5 +2626,6 @@ void apply_language(void) {
|
|||
lit_lessons_apply_lang();
|
||||
sci_lessons_apply_lang();
|
||||
fyz_lessons_apply_lang();
|
||||
math_lessons_apply_lang();
|
||||
search_apply_lang();
|
||||
}
|
||||
|
|
|
|||
28
src/icons.c
28
src/icons.c
|
|
@ -261,6 +261,34 @@ void draw_physics_icon(GtkDrawingArea *area, cairo_t *cr,
|
|||
cairo_stroke(cr);
|
||||
}
|
||||
|
||||
/* Axes and a rising curve for Matematika. */
|
||||
void draw_math_icon(GtkDrawingArea *area, cairo_t *cr,
|
||||
int width, int height, gpointer data) {
|
||||
const double s = MIN(width, height);
|
||||
const double left = width * 0.22;
|
||||
const double right = width * 0.82;
|
||||
const double bottom = height * 0.78;
|
||||
const double top = height * 0.22;
|
||||
const Rgb c = mix_rgb(color_from_hex(app_theme.on_accent),
|
||||
color_from_hex(app_theme.accent2), 0.22);
|
||||
|
||||
(void)area;
|
||||
(void)data;
|
||||
cairo_set_source_rgb(cr, c.r, c.g, c.b);
|
||||
cairo_set_line_width(cr, MAX(1.6, s * 0.07));
|
||||
cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
|
||||
cairo_move_to(cr, left, top);
|
||||
cairo_line_to(cr, left, bottom);
|
||||
cairo_line_to(cr, right, bottom);
|
||||
cairo_stroke(cr);
|
||||
|
||||
cairo_move_to(cr, left + s * 0.06, bottom - s * 0.08);
|
||||
cairo_curve_to(cr, left + s * 0.28, bottom - s * 0.02,
|
||||
width * 0.48, height * 0.62,
|
||||
right - s * 0.04, top + s * 0.06);
|
||||
cairo_stroke(cr);
|
||||
}
|
||||
|
||||
/* Open book glyph, painted in the given colour. */
|
||||
static void book_paint(cairo_t *cr, int width, int height, Rgb c) {
|
||||
const double w = width;
|
||||
|
|
|
|||
14
src/main.c
14
src/main.c
|
|
@ -390,6 +390,9 @@ static gboolean run_smoke_test(gpointer data) {
|
|||
"czechmap",
|
||||
"lityears", "litmap", "lit2map", "lit3map", "lit4map",
|
||||
"scimap", "chemmap", "biomap",
|
||||
"matyears", "mat0map", "matmap", "mat2map", "mat3map", "mat4map",
|
||||
"mat0unit1", "mat0ex1", "matunit1", "matex1",
|
||||
"mat4unit8", "mat4ex8",
|
||||
"fyzyears", "fyzmap", "fyz2map", "fyz3map", "fyz4map",
|
||||
"fyzunit1", "fyzex1", "fyzunit8", "fyzex8",
|
||||
"fyz2unit1", "fyz2ex1", "fyz3unit1", "fyz4unit1",
|
||||
|
|
@ -532,6 +535,17 @@ void activate(GtkApplication *app, gpointer user_data) {
|
|||
add_on3_pages(main_stack);
|
||||
gtk_stack_add_named(main_stack, build_on4map_page(), "on4map");
|
||||
add_on4_pages(main_stack);
|
||||
gtk_stack_add_named(main_stack, build_matyears_page(), "matyears");
|
||||
gtk_stack_add_named(main_stack, build_mat0map_page(), "mat0map");
|
||||
add_mat0_pages(main_stack);
|
||||
gtk_stack_add_named(main_stack, build_matmap_page(), "matmap");
|
||||
add_mat_pages(main_stack);
|
||||
gtk_stack_add_named(main_stack, build_mat2map_page(), "mat2map");
|
||||
add_mat2_pages(main_stack);
|
||||
gtk_stack_add_named(main_stack, build_mat3map_page(), "mat3map");
|
||||
add_mat3_pages(main_stack);
|
||||
gtk_stack_add_named(main_stack, build_mat4map_page(), "mat4map");
|
||||
add_mat4_pages(main_stack);
|
||||
gtk_stack_add_named(main_stack, build_fyzyears_page(), "fyzyears");
|
||||
gtk_stack_add_named(main_stack, build_fyzmap_page(), "fyzmap");
|
||||
add_fyz_pages(main_stack);
|
||||
|
|
|
|||
469
src/mat_year0.c
Normal file
469
src/mat_year0.c
Normal file
|
|
@ -0,0 +1,469 @@
|
|||
#include "graduately.h"
|
||||
|
||||
NetLesson mat0_lessons[MATH_N] = {
|
||||
{ .n_slides = 3, .unit_page = "mat0unit1", .ex_page = "mat0ex1" },
|
||||
{ .n_slides = 3, .unit_page = "mat0unit2", .ex_page = "mat0ex2" },
|
||||
{ .n_slides = 3, .unit_page = "mat0unit3", .ex_page = "mat0ex3" },
|
||||
{ .n_slides = 3, .unit_page = "mat0unit4", .ex_page = "mat0ex4" },
|
||||
{ .n_slides = 3, .unit_page = "mat0unit5", .ex_page = "mat0ex5" },
|
||||
{ .n_slides = 3, .unit_page = "mat0unit6", .ex_page = "mat0ex6" },
|
||||
{ .n_slides = 3, .unit_page = "mat0unit7", .ex_page = "mat0ex7" },
|
||||
{ .n_slides = 3, .unit_page = "mat0unit8", .ex_page = "mat0ex8" },
|
||||
};
|
||||
|
||||
static GtkWidget *build_mat0_unit1_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Pravidlo", "Co se počítá dřív",
|
||||
"Tip: násobení a dělení jsou stejně silné a jdou zleva doprava.",
|
||||
{
|
||||
"Nejprve se vyřeší závorky, zevnitř ven.",
|
||||
"Pak jsou mocniny a odmocniny.",
|
||||
"Násobení a dělení mají přednost před sčítáním a odčítáním.",
|
||||
"Sčítání a odčítání se také provádějí zleva doprava.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Bez závorek", "Násobení má přednost",
|
||||
"Tip: 2 + 3 · 4 není 20.",
|
||||
{
|
||||
"Ve výrazu 2 + 3 · 4 se nejdřív násobí: 3 · 4 = 12.",
|
||||
"Teprve potom se přičtou dvě: 2 + 12 = 14.",
|
||||
"Ve výrazu 20 − 6 / 2 se nejdřív dělí: 6 / 2 = 3.",
|
||||
"Pak 20 − 3 = 17.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Závorky", "Závorka změní pořadí",
|
||||
"Tip: závorka se chová jako jeden počet.",
|
||||
{
|
||||
"(8 − 3) · 2 začíná v závorce: 5 · 2 = 10.",
|
||||
"3 · (4 + 1) je 3 · 5 = 15.",
|
||||
"Bez závorky by 3 · 4 + 1 vyšlo 13.",
|
||||
"Závorku proto pište vždy, když má sčítání předběhnout násobení.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(0, &mat0_lessons[0], "mat0_unit1", "mat0_unit1_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat0_unit1_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Spočítejte 2 + 3 · 4.", "num:14", "Násobení má přednost: 3 · 4 = 12 a 2 + 12 = 14. Výsledek je 14."},
|
||||
{"Spočítejte (8 − 3) · 2.", "num:10", "Nejdřív závorka: 8 − 3 = 5, potom 5 · 2 = 10. Výsledek je 10."},
|
||||
{"Spočítejte 20 − 6 / 2.", "num:17", "Nejdřív dělení: 6 / 2 = 3, potom 20 − 3 = 17. Výsledek je 17."},
|
||||
{"Spočítejte 3 · (4 + 1).", "num:15", "Nejdřív závorka: 4 + 1 = 5, potom 3 · 5 = 15. Výsledek je 15."},
|
||||
};
|
||||
|
||||
return math_practice_page(0, 0, "mat0unit1",
|
||||
"mat0_ex1_title", "mat0_quiz1_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat0_unit2_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Součet", "Společný jmenovatel",
|
||||
"Tip: zlomek v základním tvaru má nesoudělný čitatel a jmenovatel.",
|
||||
{
|
||||
"Sčítat a odčítat lze zlomky se stejným jmenovatelem.",
|
||||
"Jinak se jmenovatelé rozšíří na nejmenší společný násobek.",
|
||||
"1/2 + 1/3 má společný jmenovatel 6: 3/6 + 2/6 = 5/6.",
|
||||
"3/4 − 1/4 = 2/4 = 1/2.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Součin", "Čitatel s čitatelem",
|
||||
"Tip: před násobením jde často krátit křížem.",
|
||||
{
|
||||
"Zlomky se násobí čitatel čitatelem a jmenovatel jmenovatelem.",
|
||||
"2/3 · 3/5 = 6/15 = 2/5.",
|
||||
"Krátit se smí jen čitatel se jmenovatelem, ne dvě horní čísla mezi sebou.",
|
||||
"Smíšené číslo se před počtem převede na zlomek.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Podíl", "Dělit znamená násobit převráceným",
|
||||
"Tip: převrácený zlomek k a/b je b/a.",
|
||||
{
|
||||
"Dělení zlomkem je násobení jeho převrácenou hodnotou.",
|
||||
"(3/4) : (1/2) = 3/4 · 2/1 = 6/4 = 3/2.",
|
||||
"Výsledek se uvede v základním tvaru.",
|
||||
"Nulou se dělit nesmí, proto jmenovatel nesmí vyjít 0.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(0, &mat0_lessons[1], "mat0_unit2", "mat0_unit2_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat0_unit2_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Spočítejte 1/2 + 1/3. Zlomek v základním tvaru.", "num:5/6", "Společný jmenovatel je 6: 3/6 + 2/6 = 5/6. Výsledek je 5/6."},
|
||||
{"Spočítejte 3/4 − 1/4.", "num:1/2", "Jmenovatel už je stejný: 2/4 = 1/2. Výsledek je 1/2."},
|
||||
{"Spočítejte 2/3 · 3/5.", "num:2/5", "6/15 se zkrátí třemi na 2/5. Výsledek je 2/5."},
|
||||
{"Spočítejte (3/4) : (1/2).", "num:3/2", "Násobí se převráceným zlomkem: 3/4 · 2/1 = 3/2. Výsledek je 3/2."},
|
||||
};
|
||||
|
||||
return math_practice_page(0, 1, "mat0unit2",
|
||||
"mat0_ex2_title", "mat0_quiz2_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat0_unit3_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Zápis", "Čárka i tečka",
|
||||
"Tip: 0,5 je totéž co 1/2.",
|
||||
{
|
||||
"Desetinná čárka odděluje celou část od desetin, setin a tisícin.",
|
||||
"0,1 je jedna desetina, 0,01 je jedna setina.",
|
||||
"Při sčítání se čárky píšou pod sebe.",
|
||||
"2,5 + 1,5 = 4.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Násobení", "Čárka se posouvá",
|
||||
"Tip: násobení deseti posune čárku o jedno místo doprava.",
|
||||
{
|
||||
"Počet desetinných míst součinu je součet počtů míst činitelů.",
|
||||
"0,2 · 5 = 1, protože dvě desetiny pětkrát jsou jedna celá.",
|
||||
"1,25 − 0,5 = 0,75, což je 3/4.",
|
||||
"Výsledek lze psát desetinně i zlomkem.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Dělení", "Dělit desetinným číslem",
|
||||
"Tip: dělitel se nejdřív upraví na celé číslo.",
|
||||
{
|
||||
"3,6 : 0,3 se rozšíří deseti na 36 : 3 = 12.",
|
||||
"Čárka se v děliteli i děleném posune o stejný počet míst.",
|
||||
"Dělení deseti posune čárku o jedno místo doleva.",
|
||||
"Nulou se ani u desetinných čísel dělit nesmí.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(0, &mat0_lessons[2], "mat0_unit3", "mat0_unit3_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat0_unit3_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Spočítejte 2,5 + 1,5.", "num:4", "Pět desetin a pět desetin je jedna celá, celkem 4. Výsledek je 4."},
|
||||
{"Spočítejte 0,2 · 5.", "num:1", "Dvě desetiny pětkrát jsou 10 desetin, tedy 1. Výsledek je 1."},
|
||||
{"Spočítejte 3,6 : 0,3.", "num:12", "Posun čárky dá 36 : 3 = 12. Výsledek je 12."},
|
||||
{"Spočítejte 1,25 − 0,5.", "num:3/4", "1,25 − 0,50 = 0,75, tedy 3/4. Výsledek je 3/4."},
|
||||
};
|
||||
|
||||
return math_practice_page(0, 2, "mat0unit3",
|
||||
"mat0_ex3_title", "mat0_quiz3_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat0_unit4_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Základ", "Jedno procento je setina",
|
||||
"Tip: pište jen číslo, znak procenta ne.",
|
||||
{
|
||||
"1 % ze základu je jedna setina základu.",
|
||||
"p % ze základu z je (p/100) · z.",
|
||||
"20 % ze 150 je 0,2 · 150 = 30.",
|
||||
"Základ, procentová část a počet procent jsou tři veličiny.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Kolik procent", "Část dělená základem",
|
||||
"Tip: výsledek 0,25 znamená 25 %.",
|
||||
{
|
||||
"Počet procent je (část / základ) · 100.",
|
||||
"15 ze 60 je 15/60 = 1/4, tedy 25 %.",
|
||||
"Do odpovědi se píše 25, ne 0,25 a ne 25 %.",
|
||||
"Zlomek se na procenta násobí stem.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Sleva a základ", "Dopočítání celku",
|
||||
"Tip: sleva 10 % nechá z ceny 90 %.",
|
||||
{
|
||||
"Po slevě 10 % zbývá 90 % původní ceny.",
|
||||
"400 − 10 % je 0,9 · 400 = 360.",
|
||||
"Když 20 % základu je 18, je 1 % rovno 18/20.",
|
||||
"Celý základ je pak 100 takových dílů: 90.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(0, &mat0_lessons[3], "mat0_unit4", "mat0_unit4_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat0_unit4_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Kolik je 20 % ze 150? Napište jen číslo.", "num:30", "20 % je 0,2 a 0,2 · 150 = 30. Výsledek je 30."},
|
||||
{"15 je kolik procent z 60? Napište jen číslo, bez znaku %.", "num:25", "15/60 = 1/4 = 25 %. Výsledek je 25."},
|
||||
{"Cena 400 se sníží o 10 %. Jaká bude nová cena?", "num:360", "Zůstane 90 %: 0,9 · 400 = 360. Výsledek je 360."},
|
||||
{"20 % základu je 18. Jak velký je základ?", "num:90", "1 % je 18/20 = 0,9 a 100 % je 90. Výsledek je 90."},
|
||||
};
|
||||
|
||||
return math_practice_page(0, 3, "mat0unit4",
|
||||
"mat0_ex4_title", "mat0_quiz4_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat0_unit5_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Poměr", "Díly celku",
|
||||
"Tip: poměr 2 : 3 má dohromady 5 dílů.",
|
||||
{
|
||||
"Poměr a : b říká, na kolik stejných dílů se celek rozdělí.",
|
||||
"Počet dílů je a + b.",
|
||||
"První část je a/(a + b) z celku.",
|
||||
"Z 20 v poměru 2 : 3 připadá na první část 8.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Úměra", "Součin vnějších a vnitřních členů",
|
||||
"Tip: 3 : 5 = x : 20 znamená 3/5 = x/20.",
|
||||
{
|
||||
"V úměře a : b = c : d platí a · d = b · c.",
|
||||
"Z 3/5 = x/20 vyjde x = 3/5 · 20 = 12.",
|
||||
"Stejný poměr lze rozšiřovat i krátit.",
|
||||
"Neznámý člen se dopočítá jedním násobením a jedním dělením.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Měřítko", "Mapa a skutečnost",
|
||||
"Tip: 1 : 1000 znamená, že 1 cm na mapě je 10 m ve skutečnosti.",
|
||||
{
|
||||
"Měřítko 1 : k zmenšuje: skutečnost je k-krát větší než plán.",
|
||||
"5 cm na mapě 1 : 1000 je 5000 cm, tedy 50 m.",
|
||||
"Měřítko 5 : 1 je zvětšení, obraz je pětkrát větší.",
|
||||
"4 cm v měřítku 5 : 1 odpovídá obrazu 20 cm.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(0, &mat0_lessons[4], "mat0_unit5", "mat0_unit5_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat0_unit5_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Číslo 20 rozdělte v poměru 2 : 3. Jak velká je první část?", "num:8", "Dílů je 5 a první část jsou 2 z nich: 2/5 · 20 = 8. Výsledek je 8."},
|
||||
{"Na mapě 1 : 1000 jsou 5 cm. Kolik metrů je to ve skutečnosti?", "num:50", "5 cm · 1000 = 5000 cm = 50 m. Výsledek je 50."},
|
||||
{"Platí 3 : 5 = x : 20. Kolik je x?", "num:12", "x = 3/5 · 20 = 12. Výsledek je 12."},
|
||||
{"Úsečka 4 cm se zobrazí v měřítku 5 : 1. Jak dlouhý je obraz v centimetrech?", "num:20", "Měřítko 5 : 1 zvětšuje pětkrát: 4 · 5 = 20. Výsledek je 20."},
|
||||
};
|
||||
|
||||
return math_practice_page(0, 4, "mat0unit5",
|
||||
"mat0_ex5_title", "mat0_quiz5_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat0_unit6_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Číselná osa", "Vpravo roste, vlevo klesá",
|
||||
"Tip: přičíst kladné číslo znamená posun doprava.",
|
||||
{
|
||||
"Kladná čísla leží vpravo od nuly, záporná vlevo.",
|
||||
"−3 + 8 je posun z −3 o 8 kroků doprava, tedy na 5.",
|
||||
"Odčítání záporného čísla je totéž co přičítání kladného.",
|
||||
"6 − (−2) = 6 + 2 = 8.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Násobení", "Znaménka",
|
||||
"Tip: dvě záporná znaménka dají kladný součin.",
|
||||
{
|
||||
"Součin kladného a záporného čísla je záporný.",
|
||||
"Součin dvou záporných čísel je kladný.",
|
||||
"(−4) · (−5) = 20.",
|
||||
"Stejné pravidlo platí pro dělení.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Absolutní hodnota", "Vzdálenost od nuly",
|
||||
"Tip: absolutní hodnota nikdy není záporná.",
|
||||
{
|
||||
"|a| je vzdálenost čísla a od nuly na ose.",
|
||||
"|−7| = 7 a |7| = 7.",
|
||||
"|0| = 0.",
|
||||
"Absolutní hodnota smaže znaménko, ale číslo jinak nezmění.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(0, &mat0_lessons[5], "mat0_unit6", "mat0_unit6_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat0_unit6_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Spočítejte −3 + 8.", "num:5", "Z −3 se jde o 8 doprava, na 5. Výsledek je 5."},
|
||||
{"Spočítejte (−4) · (−5).", "num:20", "Součin dvou záporných čísel je kladný: 20. Výsledek je 20."},
|
||||
{"Spočítejte |−7|.", "num:7", "Vzdálenost −7 od nuly je 7. Výsledek je 7."},
|
||||
{"Spočítejte 6 − (−2).", "num:8", "Odčítat −2 znamená přičíst 2: 6 + 2 = 8. Výsledek je 8."},
|
||||
};
|
||||
|
||||
return math_practice_page(0, 5, "mat0unit6",
|
||||
"mat0_ex6_title", "mat0_quiz6_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat0_unit7_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Rovnováha", "Stejně na obě strany",
|
||||
"Tip: co uděláte vlevo, udělejte i vpravo.",
|
||||
{
|
||||
"Rovnice říká, že levá strana se rovná pravé.",
|
||||
"K oběma stranám lze přičíst stejné číslo.",
|
||||
"Obě strany lze vynásobit stejným nenulovým číslem.",
|
||||
"Cílem je osamostatnit neznámou.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Kroky", "Nejdřív číslo, pak koeficient",
|
||||
"Tip: 2x − 4 = 10 se nejdřív zbaví čtyřky.",
|
||||
{
|
||||
"x + 5 = 12 se odečtením pěti změní na x = 7.",
|
||||
"3x = 15 se vydělí třemi: x = 5.",
|
||||
"2x − 4 = 10 dá po přičtení čtyř 2x = 14, tedy x = 7.",
|
||||
"Zkouška dosadí výsledek do původní rovnice.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Dělení neznámé", "Zlomek s x",
|
||||
"Tip: x/2 = 6 se násobí dvěma.",
|
||||
{
|
||||
"x/2 je totéž co (1/2) · x.",
|
||||
"Násobení obou stran dvěma dá x = 12.",
|
||||
"Násobit nulou se nesmí, rovnice by ztratila význam.",
|
||||
"U lineární rovnice s nenulovým koeficientem je řešení jedno.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(0, &mat0_lessons[6], "mat0_unit7", "mat0_unit7_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat0_unit7_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Vyřešte x + 5 = 12. Napište x.", "num:7", "Od obou stran se odečte 5. Výsledek je 7."},
|
||||
{"Vyřešte 3x = 15. Napište x.", "num:5", "Obě strany se vydělí 3. Výsledek je 5."},
|
||||
{"Vyřešte 2x − 4 = 10. Napište x.", "num:7", "2x = 14, takže x = 7. Výsledek je 7."},
|
||||
{"Vyřešte x/2 = 6. Napište x.", "num:12", "Obě strany se násobí 2. Výsledek je 12."},
|
||||
};
|
||||
|
||||
return math_practice_page(0, 6, "mat0unit7",
|
||||
"mat0_ex7_title", "mat0_quiz7_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat0_unit8_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Obdélník", "Obvod a obsah",
|
||||
"Tip: obvod je součet všech stran, obsah je součin dvou sousedních.",
|
||||
{
|
||||
"Obdélník a × b má obvod 2 · (a + b).",
|
||||
"Obdélník 5 × 3 má obvod 2 · 8 = 16.",
|
||||
"Obsah je a · b. Obdélník 6 × 4 má obsah 24.",
|
||||
"Jednotky obvodu jsou délkové, jednotky obsahu čtvereční.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Trojúhelník", "Základna a výška",
|
||||
"Tip: výška je kolmá na základnu, ne délka šikmé strany.",
|
||||
{
|
||||
"Součet vnitřních úhlů trojúhelníku je 180°.",
|
||||
"Třetí úhel je 180° minus součet dvou známých.",
|
||||
"Úhly 60° a 50° doplňuje 70°.",
|
||||
"Obsah je (základna · výška) / 2. Pro 8 a 5 je to 20.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Čtverec", "Doplnění na mřížce",
|
||||
"Tip: osa y míří nahoru. Klepnutím na bod ho odeberete.",
|
||||
{
|
||||
"Čtverec má čtyři shodné strany a čtyři pravé úhly.",
|
||||
"Druhé dva vrcholy vzniknou posunutím strany o stejnou délku kolmo.",
|
||||
"U vodorovné strany leží zbývající vrcholy přímo nad ní, nebo pod ní.",
|
||||
"Na mřížce jsou platné jen oba vrcholy téže strany.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(0, &mat0_lessons[7], "mat0_unit8", "mat0_unit8_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat0_unit8_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Obdélník má strany 5 a 3. Jaký je jeho obvod?", "num:16", "2 · (5 + 3) = 16. Výsledek je 16."},
|
||||
{"Obdélník má strany 6 a 4. Jaký je jeho obsah?", "num:24", "Obsah je 6 · 4 = 24. Výsledek je 24."},
|
||||
{"Trojúhelník má základnu 8 a výšku 5. Jaký je jeho obsah?", "num:20", "Obsah je (8 · 5) / 2 = 20. Výsledek je 20."},
|
||||
{"Dva úhly trojúhelníku jsou 60° a 50°. Jak velký je třetí úhel ve stupních?", "num:70", "Součet je 180°, takže třetí úhel je 70°. Výsledek je 70."},
|
||||
{"Na mřížce je strana čtverce od A = [2; 3] k B = [5; 3]. Doplňte zbývající dva vrcholy.", "square:2,3;5,3", "Strana má délku 3. Druhé dva vrcholy jsou [2; 6] a [5; 6], nebo [2; 0] a [5; 0]."},
|
||||
};
|
||||
|
||||
return math_practice_page(0, 7, "mat0unit8",
|
||||
"mat0_ex8_title", "mat0_quiz8_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
void add_mat0_pages(GtkStack *stack) {
|
||||
typedef GtkWidget *(*MathBuilder)(void);
|
||||
static const struct {
|
||||
const char *unit_name;
|
||||
const char *ex_name;
|
||||
MathBuilder build_unit;
|
||||
MathBuilder build_ex;
|
||||
} pages[] = {
|
||||
{"mat0unit1", "mat0ex1", build_mat0_unit1_page, build_mat0_unit1_exercise_page},
|
||||
{"mat0unit2", "mat0ex2", build_mat0_unit2_page, build_mat0_unit2_exercise_page},
|
||||
{"mat0unit3", "mat0ex3", build_mat0_unit3_page, build_mat0_unit3_exercise_page},
|
||||
{"mat0unit4", "mat0ex4", build_mat0_unit4_page, build_mat0_unit4_exercise_page},
|
||||
{"mat0unit5", "mat0ex5", build_mat0_unit5_page, build_mat0_unit5_exercise_page},
|
||||
{"mat0unit6", "mat0ex6", build_mat0_unit6_page, build_mat0_unit6_exercise_page},
|
||||
{"mat0unit7", "mat0ex7", build_mat0_unit7_page, build_mat0_unit7_exercise_page},
|
||||
{"mat0unit8", "mat0ex8", build_mat0_unit8_page, build_mat0_unit8_exercise_page},
|
||||
};
|
||||
|
||||
for (guint i = 0; i < G_N_ELEMENTS(pages); i++) {
|
||||
gtk_stack_add_named(stack, pages[i].build_unit(), pages[i].unit_name);
|
||||
gtk_stack_add_named(stack, pages[i].build_ex(), pages[i].ex_name);
|
||||
}
|
||||
}
|
||||
468
src/mat_year1.c
Normal file
468
src/mat_year1.c
Normal file
|
|
@ -0,0 +1,468 @@
|
|||
#include "graduately.h"
|
||||
|
||||
NetLesson mat_lessons[MATH_N] = {
|
||||
{ .n_slides = 3, .unit_page = "matunit1", .ex_page = "matex1" },
|
||||
{ .n_slides = 3, .unit_page = "matunit2", .ex_page = "matex2" },
|
||||
{ .n_slides = 3, .unit_page = "matunit3", .ex_page = "matex3" },
|
||||
{ .n_slides = 3, .unit_page = "matunit4", .ex_page = "matex4" },
|
||||
{ .n_slides = 3, .unit_page = "matunit5", .ex_page = "matex5" },
|
||||
{ .n_slides = 3, .unit_page = "matunit6", .ex_page = "matex6" },
|
||||
{ .n_slides = 3, .unit_page = "matunit7", .ex_page = "matex7" },
|
||||
{ .n_slides = 3, .unit_page = "matunit8", .ex_page = "matex8" },
|
||||
};
|
||||
|
||||
static GtkWidget *build_mat_unit1_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Dělitel", "Čísla, která dělí beze zbytku",
|
||||
"Tip: prvočíslo má právě dva dělitele, jedničku a sebe.",
|
||||
{
|
||||
"Dělitel čísla a je celé číslo, kterým a dělí beze zbytku.",
|
||||
"Číslo 12 má dělitele 1, 2, 3, 4, 6 a 12, tedy šest dělitelů.",
|
||||
"Prvočíslo nelze rozložit na součin dvou menších celých čísel větších než 1.",
|
||||
"Jednička prvočíslo není.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • NSD a NSN", "Společný dělitel a násobek",
|
||||
"Tip: NSD · NSN dvou čísel je jejich součin.",
|
||||
{
|
||||
"Největší společný dělitel je největší číslo, které dělí obě čísla.",
|
||||
"NSD(24, 36) = 12.",
|
||||
"Nejmenší společný násobek je nejmenší kladné číslo, které je násobkem obou.",
|
||||
"NSN(6, 8) = 24.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Rozklad", "Součin mocnin prvočísel",
|
||||
"Tip: společné prvočinitele berte v nejmenší mocnině pro NSD.",
|
||||
{
|
||||
"Každé celé číslo větší než 1 má jediný rozklad na prvočísla.",
|
||||
"72 = 2³ · 3².",
|
||||
"V NSD se bere nižší exponent, v NSN vyšší.",
|
||||
"Rozklad usnadní krácení zlomků i hledání společného jmenovatele.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(1, &mat_lessons[0], "mat_unit1", "mat_unit1_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat_unit1_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Jaký je největší společný dělitel čísel 24 a 36?", "num:12", "Společní dělitelé jsou 1, 2, 3, 4, 6 a 12. Výsledek je 12."},
|
||||
{"Jaký je nejmenší společný násobek čísel 6 a 8?", "num:24", "Násobky 6 jsou 6, 12, 18, 24, … a 24 je první z nich dělitelné osmi. Výsledek je 24."},
|
||||
{"Spočítejte 2³ · 3².", "num:72", "8 · 9 = 72. Výsledek je 72."},
|
||||
{"Kolik kladných dělitelů má číslo 12?", "num:6", "Jsou to 1, 2, 3, 4, 6 a 12. Výsledek je 6."},
|
||||
};
|
||||
|
||||
return math_practice_page(1, 0, "matunit1",
|
||||
"mat_ex1_title", "mat_quiz1_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat_unit2_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Mocnina", "Opakované násobení",
|
||||
"Tip: aⁿ je a násobené sebou n-krát.",
|
||||
{
|
||||
"2⁵ = 2 · 2 · 2 · 2 · 2 = 32.",
|
||||
"10³ = 1000, tři nuly za jedničkou.",
|
||||
"Každé číslo na první je ono samo a a⁰ = 1 pro a ≠ 0.",
|
||||
"Záporný exponent znamená převrácenou hodnotu mocniny.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Pravidla", "Stejný základ",
|
||||
"Tip: (aᵐ)ⁿ = aᵐ·ⁿ.",
|
||||
{
|
||||
"aᵐ · aⁿ = aᵐ⁺ⁿ.",
|
||||
"aᵐ : aⁿ = aᵐ⁻ⁿ pro a ≠ 0.",
|
||||
"(a · b)ⁿ = aⁿ · bⁿ.",
|
||||
"(2³)² = 2⁶ = 64.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Odmocnina", "Druhá odmocnina",
|
||||
"Tip: √a je nezáporné číslo, jehož druhá mocnina je a.",
|
||||
{
|
||||
"√81 = 9, protože 9² = 81.",
|
||||
"√0 = 0 a √1 = 1.",
|
||||
"Ze záporného čísla se v reálných číslech druhá odmocnina nedělá.",
|
||||
"√(a²) = |a|, ne vždy a.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(1, &mat_lessons[1], "mat_unit2", "mat_unit2_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat_unit2_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Spočítejte 2⁵.", "num:32", "Pět dvojkových činitelů je 32. Výsledek je 32."},
|
||||
{"Spočítejte 10³.", "num:1000", "10 · 10 · 10 = 1000. Výsledek je 1000."},
|
||||
{"Spočítejte √81.", "num:9", "9 · 9 = 81. Výsledek je 9."},
|
||||
{"Spočítejte (2³)².", "num:64", "(8)² = 64, nebo 2⁶ = 64. Výsledek je 64."},
|
||||
};
|
||||
|
||||
return math_practice_page(1, 1, "matunit2",
|
||||
"mat_ex2_title", "mat_quiz2_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat_unit3_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Dosazení", "Písmeno je číslo",
|
||||
"Tip: nejdřív závorky, potom mocniny a násobení.",
|
||||
{
|
||||
"Do výrazu se za proměnnou dosadí dané číslo.",
|
||||
"2(x + 3) při x = 4 je 2 · 7 = 14.",
|
||||
"3x − 2x + 5 se nejdřív sloučí na x + 5.",
|
||||
"Při x = 2 je x + 5 = 7.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Vzorce", "Druhá mocnina dvojčlenu",
|
||||
"Tip: (a + b)² není a² + b².",
|
||||
{
|
||||
"(a + b)² = a² + 2ab + b².",
|
||||
"(a − b)² = a² − 2ab + b².",
|
||||
"a² − b² = (a − b)(a + b).",
|
||||
"Pro a = 3 a b = 1 je (a + b)² = 16.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Zkrácení", "Rozdíl čtverců",
|
||||
"Tip: krátit lze jen pro x, která nejsou kořenem jmenovatele.",
|
||||
{
|
||||
"x² − 9 = (x − 3)(x + 3).",
|
||||
"(x² − 9)/(x − 3) = x + 3 pro x ≠ 3.",
|
||||
"Při x = 5 je hodnota 8.",
|
||||
"Dosazení x = 3 by dělilo nulou, výraz tam není definován.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(1, &mat_lessons[2], "mat_unit3", "mat_unit3_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat_unit3_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Do výrazu 2(x + 3) dosažte x = 4.", "num:14", "2 · (4 + 3) = 14. Výsledek je 14."},
|
||||
{"Do výrazu (a + b)² dosažte a = 3 a b = 1.", "num:16", "4² = 16. Rozepsáno 9 + 6 + 1 = 16. Výsledek je 16."},
|
||||
{"Do výrazu 3x − 2x + 5 dosažte x = 2.", "num:7", "Výraz je x + 5, při x = 2 je 7. Výsledek je 7."},
|
||||
{"Do výrazu (x² − 9)/(x − 3) dosažte x = 5.", "num:8", "Pro x ≠ 3 je výraz roven x + 3, tedy 8. Výsledek je 8."},
|
||||
};
|
||||
|
||||
return math_practice_page(1, 2, "matunit3",
|
||||
"mat_ex3_title", "mat_quiz3_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat_unit4_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Převod", "Neznámé na jednu stranu",
|
||||
"Tip: člen s x se převádí s opačným znaménkem.",
|
||||
{
|
||||
"2x + 1 = 9 se odečtením jedné změní na 2x = 8.",
|
||||
"x = 4.",
|
||||
"Členy s x patří obvykle vlevo, čísla vpravo.",
|
||||
"Po každé úpravě zůstává rovnost zachovaná.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Obě strany", "Stejný člen vlevo i vpravo",
|
||||
"Tip: 5x − 3 = 2x + 9 se zbaví 2x odečtením.",
|
||||
{
|
||||
"5x − 2x = 9 + 3.",
|
||||
"3x = 12 a x = 4.",
|
||||
"Zkouška: levá strana 5 · 4 − 3 = 17, pravá 8 + 9 = 17.",
|
||||
"Když koeficient u x vyjde 0, rovnice buď nemá řešení, nebo jich má nekonečně mnoho.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Závorka a zlomek", "Nejdřív roznásobit",
|
||||
"Tip: x/2 + 1 = 5 se nejdřív zbaví jedničky.",
|
||||
{
|
||||
"x/2 + 1 = 5 dá x/2 = 4, tedy x = 8.",
|
||||
"3(x − 2) = 12 se vydělí třemi: x − 2 = 4.",
|
||||
"Nebo se závorka roznásobí: 3x − 6 = 12.",
|
||||
"Obě cesty dají x = 6.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(1, &mat_lessons[3], "mat_unit4", "mat_unit4_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat_unit4_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Vyřešte 2x + 1 = 9. Napište x.", "num:4", "2x = 8, takže x = 4. Výsledek je 4."},
|
||||
{"Vyřešte 5x − 3 = 2x + 9. Napište x.", "num:4", "3x = 12, takže x = 4. Výsledek je 4."},
|
||||
{"Vyřešte x/2 + 1 = 5. Napište x.", "num:8", "x/2 = 4, takže x = 8. Výsledek je 8."},
|
||||
{"Vyřešte 3(x − 2) = 12. Napište x.", "num:6", "x − 2 = 4, takže x = 6. Výsledek je 6."},
|
||||
};
|
||||
|
||||
return math_practice_page(1, 3, "matunit4",
|
||||
"mat_ex4_title", "mat_quiz4_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat_unit5_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Dráha", "Rychlost krát čas",
|
||||
"Tip: jednotky času a rychlosti musí patřit k sobě.",
|
||||
{
|
||||
"Při stálé rychlosti je dráha s = v · t.",
|
||||
"60 km/h po dobu 2,5 h urazí 150 km.",
|
||||
"2,5 h je dvě a půl hodiny, ne dvě hodiny a padesát minut.",
|
||||
"Z dráhy a času se rychlost dopočítá jako s/t.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Práce a směs", "Sčítají se výkony, ne časy",
|
||||
"Tip: kdo práci udělá za 3 hodiny, udělá za hodinu třetinu.",
|
||||
{
|
||||
"První pracovník udělá za hodinu 1/6 práce, druhý 1/3.",
|
||||
"Společně udělají 1/6 + 2/6 = 1/2 práce za hodinu, tedy celou za 2 hodiny.",
|
||||
"U směsi se sčítá množství čisté látky.",
|
||||
"2 kg dvacetiprocentní a 3 kg desetiprocentní směsi obsahují 0,4 + 0,3 = 0,7 kg, tedy 14 %.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Sleva", "Nová cena je část základu",
|
||||
"Tip: po slevě 20 % zbývá 80 % původní ceny.",
|
||||
{
|
||||
"Když je nová cena 160 a sleva byla 20 %, je 160 rovno 80 % původní ceny.",
|
||||
"Původní cena je 160 / 0,8 = 200.",
|
||||
"Sleva v korunách je pak 40.",
|
||||
"Nepleťte si slevu 20 % s tím, že se k nové ceně přičte 20 %.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(1, &mat_lessons[4], "mat_unit5", "mat_unit5_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat_unit5_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Auto jede 2,5 hodiny rychlostí 60 km/h. Kolik kilometrů ujede?", "num:150", "s = v · t = 60 · 2,5 = 150. Výsledek je 150."},
|
||||
{"První pracovník práci udělá za 6 hodin, druhý za 3 hodiny. Za kolik hodin ji udělají společně?", "num:2", "Za hodinu spolu udělají 1/2 práce, celou tedy za 2 hodiny. Výsledek je 2."},
|
||||
{"Smíchají se 2 kg směsi s 20 % látky a 3 kg směsi s 10 % látky. Kolik procent látky má směs? Napište jen číslo.", "num:14", "Čisté látky je 0,7 kg z 5 kg, tedy 14 %. Výsledek je 14."},
|
||||
{"Po slevě 20 % stojí zboží 160. Jaká byla původní cena?", "num:200", "160 je 80 % původní ceny, základ je 200. Výsledek je 200."},
|
||||
};
|
||||
|
||||
return math_practice_page(1, 4, "matunit5",
|
||||
"mat_ex5_title", "mat_quiz5_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat_unit6_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Trojúhelník", "Součet 180°",
|
||||
"Tip: rovnoramenný trojúhelník má dvě ramena a dva úhly u základny shodné.",
|
||||
{
|
||||
"Součet vnitřních úhlů každého trojúhelníku je 180°.",
|
||||
"V rovnoramenném trojúhelníku jsou úhly při základně shodné.",
|
||||
"Když jsou úhly při základně 70°, je úhel proti základně 40°.",
|
||||
"Rovnostranný trojúhelník má všechny strany shodné a každý úhel 60°.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Vnější úhel", "Součet dvou vzdálených vnitřních",
|
||||
"Tip: vnější úhel a sousední vnitřní úhel dávají 180°.",
|
||||
{
|
||||
"Vnější úhel vznikne prodloužením jedné strany.",
|
||||
"Je roven součtu dvou vnitřních úhlů, které s ním nesousedí.",
|
||||
"Vnitřní úhly 40° a 60° dávají vnější úhel 100° u zbývajícího vrcholu.",
|
||||
"Součet vnějšího úhlu a přilehlého vnitřního je 180°.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Mnohoúhelník", "Pravidelný šestiúhelník",
|
||||
"Tip: součet vnitřních úhlů n-úhelníku je (n − 2) · 180°.",
|
||||
{
|
||||
"Trojúhelník má součet 180°, čtyřúhelník 360°.",
|
||||
"Obecný vzorec je (n − 2) · 180°.",
|
||||
"Pravidelný mnohoúhelník má všechny strany i úhly shodné.",
|
||||
"Pravidelný šestiúhelník má vnitřní úhel 720° / 6 = 120°.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(1, &mat_lessons[5], "mat_unit6", "mat_unit6_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat_unit6_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Vnější úhel trojúhelníku je součet vnitřních úhlů 40° a 60°. Jak je velký ve stupních?", "num:100", "Vnější úhel se rovná součtu dvou nesousedních vnitřních úhlů: 100°. Výsledek je 100."},
|
||||
{"Rovnoramenný trojúhelník má úhly při základně 70°. Jak velký je úhel proti základně?", "num:40", "180° − 140° = 40°. Výsledek je 40."},
|
||||
{"Rovnostranný trojúhelník má stranu 6. Jaký je jeho obvod?", "num:18", "Tři shodné strany: 3 · 6 = 18. Výsledek je 18."},
|
||||
{"Jak velký je jeden vnitřní úhel pravidelného šestiúhelníku ve stupních?", "num:120", "Součet je (6 − 2) · 180° = 720° a jeden úhel je 120°. Výsledek je 120."},
|
||||
};
|
||||
|
||||
return math_practice_page(1, 5, "matunit6",
|
||||
"mat_ex6_title", "mat_quiz6_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat_unit7_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Kružnice", "Obvod a obsah",
|
||||
"Tip: u výsledku s π nechte π ve výrazu a pište ho jako pi.",
|
||||
{
|
||||
"Kružnice s poloměrem r má obvod 2πr.",
|
||||
"Obsah kruhu je πr².",
|
||||
"Pro r = 7 je obvod 14π.",
|
||||
"Pro r = 3 je obsah 9π.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Lichoběžník", "Dvě základny a výška",
|
||||
"Tip: obsah je průměr základen krát výška.",
|
||||
{
|
||||
"Lichoběžník má právě jednu dvojici rovnoběžných stran, základny a a c.",
|
||||
"Obsah je (a + c) · v / 2.",
|
||||
"Základny 4 a 6 a výška 3 dávají obsah 15.",
|
||||
"Výška je kolmá vzdálenost základen.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Pythagoras", "Pravoúhlý trojúhelník",
|
||||
"Tip: přepona je nejdelší strana a leží proti pravému úhlu.",
|
||||
{
|
||||
"V pravoúhlém trojúhelníku je a² + b² = c².",
|
||||
"c je přepona, a a b jsou odvěsny.",
|
||||
"Trojúhelník 5, 12, 13 je pravoúhlý, protože 25 + 144 = 169.",
|
||||
"Přepona je 13.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(1, &mat_lessons[6], "mat_unit7", "mat_unit7_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat_unit7_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Jaký je obvod kružnice s poloměrem 7? Nechte výsledek v násobcích π.", "num:14*pi", "Obvod je 2πr = 14π. Výsledek je 14π."},
|
||||
{"Jaký je obsah kruhu s poloměrem 3? Nechte výsledek v násobcích π.", "num:9*pi", "Obsah je πr² = 9π. Výsledek je 9π."},
|
||||
{"Lichoběžník má základny 4 a 6 a výšku 3. Jaký je jeho obsah?", "num:15", "(4 + 6) · 3 / 2 = 15. Výsledek je 15."},
|
||||
{"Pravoúhlý trojúhelník má odvěsny 5 a 12. Jak dlouhá je přepona?", "num:13", "5² + 12² = 25 + 144 = 169 = 13². Výsledek je 13."},
|
||||
};
|
||||
|
||||
return math_practice_page(1, 6, "matunit7",
|
||||
"mat_ex7_title", "mat_quiz7_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat_unit8_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Body", "Vzdálenost na mřížce",
|
||||
"Tip: u svislé nebo vodorovné úsečky stačí rozdíl souřadnic.",
|
||||
{
|
||||
"Bod v rovině má souřadnice [x; y].",
|
||||
"Vzdálenost [x₁; y₁] a [x₂; y₂] je √((x₂ − x₁)² + (y₂ − y₁)²).",
|
||||
"A = [1; 1] a B = [5; 1] mají vzdálenost 4.",
|
||||
"Osa y na naší mřížce míří nahoru.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • SSS", "Trojúhelník ze tří stran",
|
||||
"Tip: třetí vrchol je průsečík dvou kružnic.",
|
||||
{
|
||||
"Konstrukce SSS použije tři strany.",
|
||||
"Z bodu A se rýsuje kružnice o poloměru |AC|, z bodu B o poloměru |BC|.",
|
||||
"Kružnice se protnou ve dvou bodech, pokud trojúhelník existuje.",
|
||||
"Trojúhelníková nerovnost: součet dvou stran je větší než třetí.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Příklad", "Strany 4, 3 a 5",
|
||||
"Tip: 3² + 4² = 5², pravý úhel je u bodu A.",
|
||||
{
|
||||
"A = [1; 1], B = [5; 1], takže |AB| = 4.",
|
||||
"Hledáme C tak, aby |AC| = 3 a |BC| = 5.",
|
||||
"Na mřížce od 0 do 10 a od 0 do 8 leží jen C = [1; 4].",
|
||||
"Druhý průsečík [1; −2] je pod mřížkou.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(1, &mat_lessons[7], "mat_unit8", "mat_unit8_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat_unit8_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Jaká je vzdálenost bodů A = [1; 1] a B = [5; 1]?", "num:4", "Body mají stejné y, vzdálenost je 5 − 1 = 4. Výsledek je 4."},
|
||||
{"Jaký je obsah pravoúhlého trojúhelníku s odvěsnami 3 a 4?", "num:6", "Obsah je (3 · 4) / 2 = 6. Výsledek je 6."},
|
||||
{"Jaký je obvod trojúhelníku se stranami 3, 4 a 5?", "num:12", "3 + 4 + 5 = 12. Výsledek je 12."},
|
||||
{"A = [1; 1] a B = [5; 1] jsou dané. Zakreslete C, pro které je |AC| = 3 a |BC| = 5.", "points:1,4;fix:1,1;fix:5,1", "Kružnice se na mřížce protnou v [1; 4]. Bod [1; −2] už na ní není."},
|
||||
};
|
||||
|
||||
return math_practice_page(1, 7, "matunit8",
|
||||
"mat_ex8_title", "mat_quiz8_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
void add_mat_pages(GtkStack *stack) {
|
||||
typedef GtkWidget *(*MathBuilder)(void);
|
||||
static const struct {
|
||||
const char *unit_name;
|
||||
const char *ex_name;
|
||||
MathBuilder build_unit;
|
||||
MathBuilder build_ex;
|
||||
} pages[] = {
|
||||
{"matunit1", "matex1", build_mat_unit1_page, build_mat_unit1_exercise_page},
|
||||
{"matunit2", "matex2", build_mat_unit2_page, build_mat_unit2_exercise_page},
|
||||
{"matunit3", "matex3", build_mat_unit3_page, build_mat_unit3_exercise_page},
|
||||
{"matunit4", "matex4", build_mat_unit4_page, build_mat_unit4_exercise_page},
|
||||
{"matunit5", "matex5", build_mat_unit5_page, build_mat_unit5_exercise_page},
|
||||
{"matunit6", "matex6", build_mat_unit6_page, build_mat_unit6_exercise_page},
|
||||
{"matunit7", "matex7", build_mat_unit7_page, build_mat_unit7_exercise_page},
|
||||
{"matunit8", "matex8", build_mat_unit8_page, build_mat_unit8_exercise_page},
|
||||
};
|
||||
|
||||
for (guint i = 0; i < G_N_ELEMENTS(pages); i++) {
|
||||
gtk_stack_add_named(stack, pages[i].build_unit(), pages[i].unit_name);
|
||||
gtk_stack_add_named(stack, pages[i].build_ex(), pages[i].ex_name);
|
||||
}
|
||||
}
|
||||
468
src/mat_year2.c
Normal file
468
src/mat_year2.c
Normal file
|
|
@ -0,0 +1,468 @@
|
|||
#include "graduately.h"
|
||||
|
||||
NetLesson mat2_lessons[MATH_N] = {
|
||||
{ .n_slides = 3, .unit_page = "mat2unit1", .ex_page = "mat2ex1" },
|
||||
{ .n_slides = 3, .unit_page = "mat2unit2", .ex_page = "mat2ex2" },
|
||||
{ .n_slides = 3, .unit_page = "mat2unit3", .ex_page = "mat2ex3" },
|
||||
{ .n_slides = 3, .unit_page = "mat2unit4", .ex_page = "mat2ex4" },
|
||||
{ .n_slides = 3, .unit_page = "mat2unit5", .ex_page = "mat2ex5" },
|
||||
{ .n_slides = 3, .unit_page = "mat2unit6", .ex_page = "mat2ex6" },
|
||||
{ .n_slides = 3, .unit_page = "mat2unit7", .ex_page = "mat2ex7" },
|
||||
{ .n_slides = 3, .unit_page = "mat2unit8", .ex_page = "mat2ex8" },
|
||||
};
|
||||
|
||||
static GtkWidget *build_mat2_unit1_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Předpis", "y = ax + b",
|
||||
"Tip: a je směrnice, b je úsek na ose y.",
|
||||
{
|
||||
"Lineární funkce má předpis f(x) = ax + b.",
|
||||
"Grafem je přímka.",
|
||||
"b je hodnota f(0), tedy průsečík s osou y.",
|
||||
"Pro f(x) = 2x − 3 je průsečík s osou y číslo −3.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Směrnice", "O kolik y vzroste",
|
||||
"Tip: směrnice je podíl změny y a změny x.",
|
||||
{
|
||||
"Mezi body [x₁; y₁] a [x₂; y₂] je a = (y₂ − y₁)/(x₂ − x₁).",
|
||||
"Body [1; 2] a [3; 8] dávají směrnici (8 − 2)/(3 − 1) = 3.",
|
||||
"Kladná směrnice znamená růst, záporná pokles.",
|
||||
"Nulová směrnice je vodorovná přímka.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Hodnota a kořen", "Dosazení a rovnice",
|
||||
"Tip: nulový bod je x, pro které je f(x) = 0.",
|
||||
{
|
||||
"f(4) u funkce 2x − 3 je 8 − 3 = 5.",
|
||||
"Rovnice 2x − 6 = 0 má řešení x = 3.",
|
||||
"To je průsečík přímky y = 2x − 6 s osou x.",
|
||||
"Lineární funkce s a ≠ 0 má právě jeden nulový bod.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(2, &mat2_lessons[0], "mat2_unit1", "mat2_unit1_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat2_unit1_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Funkce f(x) = 2x − 3. Kolik je f(4)?", "num:5", "2 · 4 − 3 = 5. Výsledek je 5."},
|
||||
{"Jaký je průsečík funkce f(x) = 2x − 3 s osou y?", "num:-3", "f(0) = −3. Výsledek je -3."},
|
||||
{"Jaká je směrnice přímky body [1; 2] a [3; 8]?", "num:3", "(8 − 2)/(3 − 1) = 3. Výsledek je 3."},
|
||||
{"Vyřešte 2x − 6 = 0. Napište x.", "num:3", "2x = 6, takže x = 3. Výsledek je 3."},
|
||||
};
|
||||
|
||||
return math_practice_page(2, 0, "mat2unit1",
|
||||
"mat2_ex1_title", "mat2_quiz1_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat2_unit2_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Dvojice", "Jedno x a jedno y",
|
||||
"Tip: výsledek pište x;y, nejdřív x.",
|
||||
{
|
||||
"Soustava dvou lineárních rovnic hledá dvojici [x; y], která vyhovuje oběma.",
|
||||
"Řešením je průsečík dvou přímek.",
|
||||
"Sčítací metoda sečte rovnice tak, aby jedna neznámá zmizela.",
|
||||
"Dosazovací metoda vyjádří jednu neznámou a dosadí ji do druhé rovnice.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Sčítání", "Opačné koeficienty",
|
||||
"Tip: x + y = 10 a x − y = 2 se sčítají na 2x = 12.",
|
||||
{
|
||||
"Součet dá 2x = 12, tedy x = 6.",
|
||||
"Z x + y = 10 je y = 4.",
|
||||
"Zkouška ve druhé rovnici: 6 − 4 = 2.",
|
||||
"Odečtení rovnic se použije, když má neznámá v obou stejný koeficient.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Odečtení", "Stejný koeficient",
|
||||
"Tip: rovnice lze před odečtením násobit.",
|
||||
{
|
||||
"2x + y = 8 a x + y = 5 se odečtou na x = 3.",
|
||||
"Pak y = 2.",
|
||||
"3x + y = 10 a x + y = 6 dají 2x = 4, tedy x = 2 a y = 4.",
|
||||
"x + y = 7 a x − y = 1 dají x = 4 a y = 3.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(2, &mat2_lessons[1], "mat2_unit2", "mat2_unit2_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat2_unit2_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Vyřešte soustavu x + y = 10 a x − y = 2. Napište x a y oddělené středníkem.", "pair:6;4", "Sečtení dá 2x = 12, x = 6 a y = 4. Napište 6;4."},
|
||||
{"Vyřešte soustavu 2x + y = 8 a x + y = 5. Napište x a y oddělené středníkem.", "pair:3;2", "Odečtení dá x = 3 a y = 2. Napište 3;2."},
|
||||
{"Vyřešte soustavu x + y = 7 a x − y = 1. Napište x a y oddělené středníkem.", "pair:4;3", "Sečtení dá 2x = 8, x = 4 a y = 3. Napište 4;3."},
|
||||
{"Vyřešte soustavu 3x + y = 10 a x + y = 6. Napište x a y oddělené středníkem.", "pair:2;4", "Odečtení dá 2x = 4, x = 2 a y = 4. Napište 2;4."},
|
||||
};
|
||||
|
||||
return math_practice_page(2, 1, "mat2unit2",
|
||||
"mat2_ex2_title", "mat2_quiz2_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat2_unit3_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Tvar", "ax² + bx + c = 0",
|
||||
"Tip: kořeny pište oddělené středníkem, pořadí nehraje roli.",
|
||||
{
|
||||
"Kvadratická rovnice má a ≠ 0.",
|
||||
"Kořeny rovnice x² − 5x + 6 = 0 jsou 2 a 3, protože (x − 2)(x − 3) = 0.",
|
||||
"x² − 4 = 0 má kořeny −2 a 2.",
|
||||
"Součin je nula, právě když je nula aspoň jeden činitel.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Diskriminant", "D = b² − 4ac",
|
||||
"Tip: D > 0 znamená dva různé reálné kořeny.",
|
||||
{
|
||||
"Pro ax² + bx + c = 0 je D = b² − 4ac.",
|
||||
"U x² − 2x − 3 je a = 1, b = −2, c = −3 a D = 4 + 12 = 16.",
|
||||
"D = 0 znamená jeden dvojnásobný kořen.",
|
||||
"D < 0 znamená, že v reálných číslech řešení není.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Vzorec", "Kořen je (−b ± √D) / (2a)",
|
||||
"Tip: dvojnásobný kořen x² + 6x + 9 = 0 je −3.",
|
||||
{
|
||||
"x = (−b ± √D) / (2a).",
|
||||
"x² + 6x + 9 = (x + 3)², takže kořen −3 je dvojnásobný.",
|
||||
"U dvojnásobného kořene stačí napsat číslo jednou.",
|
||||
"Zkouška dosadí kořen zpět do rovnice.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(2, &mat2_lessons[2], "mat2_unit3", "mat2_unit3_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat2_unit3_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Najděte kořeny rovnice x² − 5x + 6 = 0. Oddělte je středníkem.", "set:2;3", "(x − 2)(x − 3) = 0. Napište 2;3."},
|
||||
{"Najděte kořeny rovnice x² − 4 = 0. Oddělte je středníkem.", "set:-2;2", "x² = 4, takže x = −2 nebo x = 2. Napište -2;2."},
|
||||
{"Jaký je diskriminant rovnice x² − 2x − 3 = 0?", "num:16", "D = (−2)² − 4 · 1 · (−3) = 4 + 12 = 16. Výsledek je 16."},
|
||||
{"Jaký je dvojnásobný kořen rovnice x² + 6x + 9 = 0?", "num:-3", "(x + 3)² = 0, kořen je −3. Výsledek je -3."},
|
||||
};
|
||||
|
||||
return math_practice_page(2, 2, "mat2unit3",
|
||||
"mat2_ex3_title", "mat2_quiz3_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat2_unit4_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Obor", "Jmenovatel nesmí být nula",
|
||||
"Tip: podmínku pište dřív, než krátíte.",
|
||||
{
|
||||
"Lomený výraz je podíl dvou mnohočlenů.",
|
||||
"Není definován tam, kde je jmenovatel nula.",
|
||||
"(x + 2)/(x − 2) nemá smysl pro x = 2.",
|
||||
"Po krácení se vyloučené hodnoty do oboru nevracejí.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Krácení", "Rozdíl čtverců",
|
||||
"Tip: x² − 1 = (x − 1)(x + 1).",
|
||||
{
|
||||
"(x² − 1)/(x − 1) = x + 1 pro x ≠ 1.",
|
||||
"Při x = 4 je hodnota 5.",
|
||||
"(x² − 4)/(x + 2) = x − 2 pro x ≠ −2.",
|
||||
"Při x = 5 je hodnota 3.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Násobení", "Čitatel s čitatelem",
|
||||
"Tip: x ve jmenovateli se zkrátí jen pro x ≠ 0.",
|
||||
{
|
||||
"Zlomky se násobí stejně jako číselné.",
|
||||
"(2x/4) · (6/x) = 12x/(4x) = 3 pro x ≠ 0.",
|
||||
"6x/(3x) = 2 pro x ≠ 0.",
|
||||
"Než se zkrátí písmeno, ověří se, že není nula.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(2, &mat2_lessons[3], "mat2_unit4", "mat2_unit4_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat2_unit4_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Do výrazu (x² − 1)/(x − 1) dosažte x = 4.", "num:5", "Pro x ≠ 1 je výraz x + 1, tedy 5. Výsledek je 5."},
|
||||
{"Pro které x nemá výraz (x + 2)/(x − 2) smysl?", "num:2", "Jmenovatel x − 2 je nula právě při x = 2. Výsledek je 2."},
|
||||
{"Upravte (2x/4) · (6/x) pro x ≠ 0.", "num:3", "12x/(4x) = 3. Výsledek je 3."},
|
||||
{"Do výrazu (x² − 4)/(x + 2) dosažte x = 5.", "num:3", "Pro x ≠ −2 je výraz x − 2, tedy 3. Výsledek je 3."},
|
||||
};
|
||||
|
||||
return math_practice_page(2, 3, "mat2unit4",
|
||||
"mat2_ex4_title", "mat2_quiz4_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat2_unit5_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Odmocnina jako exponent", "a^(1/n)",
|
||||
"Tip: a^(1/2) je druhá odmocnina z a.",
|
||||
{
|
||||
"Pro a > 0 je a^(1/n) n-tá odmocnina z a.",
|
||||
"16^(1/2) = 4.",
|
||||
"a^(m/n) je n-tá odmocnina z aᵐ, nebo m-tá mocnina odmocniny.",
|
||||
"8^(2/3) = (8^(1/3))² = 2² = 4.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Stejný základ", "Exponenty se sčítají",
|
||||
"Tip: 3² · 3³ = 3⁵.",
|
||||
{
|
||||
"aᵐ · aⁿ = aᵐ⁺ⁿ.",
|
||||
"3² · 3³ = 3⁵ = 243.",
|
||||
"(aᵐ)ⁿ = aᵐ·ⁿ.",
|
||||
"Základ musí být při sčítání exponentů stejný.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Záporný exponent", "Převrácená hodnota",
|
||||
"Tip: a⁻ⁿ = 1/aⁿ pro a ≠ 0.",
|
||||
{
|
||||
"10⁻² = 1/100.",
|
||||
"10⁻¹ = 0,1 a 10⁰ = 1.",
|
||||
"Záporný exponent nepřevrací znaménko základu, ale celou mocninu.",
|
||||
"(−2)⁻² = 1/4, protože (−2)² = 4.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(2, &mat2_lessons[4], "mat2_unit5", "mat2_unit5_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat2_unit5_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Spočítejte 16^(1/2).", "num:4", "Druhá odmocnina z 16 je 4. Výsledek je 4."},
|
||||
{"Spočítejte 8^(2/3).", "num:4", "Třetí odmocnina z 8 je 2 a 2² = 4. Výsledek je 4."},
|
||||
{"Spočítejte 3² · 3³.", "num:243", "3⁵ = 243. Výsledek je 243."},
|
||||
{"Spočítejte 10⁻².", "num:1/100", "10⁻² = 1/10² = 1/100. Výsledek je 1/100."},
|
||||
};
|
||||
|
||||
return math_practice_page(2, 4, "mat2unit5",
|
||||
"mat2_ex5_title", "mat2_quiz5_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat2_unit6_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Poměry", "Odvěsny a přepona",
|
||||
"Tip: sinus je protilehlá odvěsna ku přeponě.",
|
||||
{
|
||||
"V pravoúhlém trojúhelníku je sin α = protilehlá / přepona.",
|
||||
"cos α = přilehlá / přepona.",
|
||||
"tan α = protilehlá / přilehlá.",
|
||||
"Přepona je strana proti pravému úhlu.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Tabulkové úhly", "30°, 45° a 60°",
|
||||
"Tip: sinus 30° je 1/2.",
|
||||
{
|
||||
"sin 30° = 1/2 a cos 60° = 1/2.",
|
||||
"sin 60° = cos 30° = √3/2.",
|
||||
"sin 45° = cos 45° = √2/2.",
|
||||
"tan 45° = 1.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Dopočet strany", "Přepona a úhel",
|
||||
"Tip: proti úhlu 30° je polovina přepony.",
|
||||
{
|
||||
"Známe-li přeponu a úhel, protilehlá odvěsna je přepona · sin α.",
|
||||
"Proti 30° při přeponě 10 je odvěsna 5.",
|
||||
"Přilehlá odvěsna by byla 10 · cos 30°.",
|
||||
"Tangens se hodí, když jsou známé dvě odvěsny a ne přepona.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(2, &mat2_lessons[5], "mat2_unit6", "mat2_unit6_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat2_unit6_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Kolik je sin 30°?", "num:1/2", "Sinus 30° je 1/2. Výsledek je 1/2."},
|
||||
{"Kolik je cos 60°?", "num:1/2", "Kosinus 60° je 1/2. Výsledek je 1/2."},
|
||||
{"Kolik je tan 45°?", "num:1", "Protilehlá a přilehlá odvěsna jsou u 45° stejně dlouhé. Výsledek je 1."},
|
||||
{"Přepona pravoúhlého trojúhelníku je 10 a jeden úhel je 30°. Jak dlouhá je odvěsna proti tomuto úhlu?", "num:5", "10 · sin 30° = 10 · 1/2 = 5. Výsledek je 5."},
|
||||
};
|
||||
|
||||
return math_practice_page(2, 5, "mat2unit6",
|
||||
"mat2_ex6_title", "mat2_quiz6_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat2_unit7_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Pythagoras", "a² + b² = c²",
|
||||
"Tip: 8² + 15² = 64 + 225 = 289 = 17².",
|
||||
{
|
||||
"V pravoúhlém trojúhelníku se součet čtverců nad odvěsnami rovná čtverci nad přeponou.",
|
||||
"Trojúhelník 8, 15, 17 je pravoúhlý a přepona je 17.",
|
||||
"Čtverec nad přeponou trojúhelníku 3, 4, 5 má obsah 25.",
|
||||
"Věta platí jen v pravoúhlém trojúhelníku.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Eukleides", "Úsek přepony",
|
||||
"Tip: a² = c · a_c, kde a_c je úsek přepony přilehlý k odvěsně a.",
|
||||
{
|
||||
"Odvěsna je geometrický průměr přepony a svého úseku.",
|
||||
"a² = c · a_c a b² = c · b_c.",
|
||||
"Úseky a_c a b_c dají dohromady přeponu c.",
|
||||
"U odvěsen 6 a 8 je přepona 10 a úsek u odvěsny 6 je 36/10 = 18/5.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Výška", "v² = a_c · b_c",
|
||||
"Tip: také platí a · b = c · v.",
|
||||
{
|
||||
"Výška k přeponě je geometrický průměr obou úseků.",
|
||||
"Z a · b = c · v plyne v = ab/c.",
|
||||
"Pro odvěsny 6 a 8 a přeponu 10 je v = 48/10 = 24/5.",
|
||||
"Obsah trojúhelníku je pak (c · v) / 2, stejně jako (a · b) / 2.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(2, &mat2_lessons[6], "mat2_unit7", "mat2_unit7_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat2_unit7_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Pravoúhlý trojúhelník má odvěsny 8 a 15. Jak dlouhá je přepona?", "num:17", "64 + 225 = 289 = 17². Výsledek je 17."},
|
||||
{"Jaký obsah má čtverec sestrojený nad přeponou pravoúhlého trojúhelníku se stranami 3, 4 a 5?", "num:25", "Přepona je 5 a 5² = 25. Výsledek je 25."},
|
||||
{"Odvěsny jsou 6 a 8, přepona je 10. Jak dlouhý je úsek přepony přilehlý k odvěsně 6?", "num:18/5", "a² = c · a_c, takže a_c = 36/10 = 18/5. Výsledek je 18/5."},
|
||||
{"Odvěsny jsou 6 a 8, přepona je 10. Jak dlouhá je výška k přeponě?", "num:24/5", "v = ab/c = 48/10 = 24/5. Výsledek je 24/5."},
|
||||
};
|
||||
|
||||
return math_practice_page(2, 6, "mat2unit7",
|
||||
"mat2_ex7_title", "mat2_quiz7_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat2_unit8_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Úhly", "Obvodový je polovina středového",
|
||||
"Tip: oba úhly musí být nad stejným obloukem.",
|
||||
{
|
||||
"Středový úhel má vrchol ve středu kružnice.",
|
||||
"Obvodový úhel má vrchol na kružnici.",
|
||||
"Nad stejným obloukem je obvodový úhel polovinou středového.",
|
||||
"Středový úhel 80° patří k obvodovému úhlu 40°.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Oblouk", "Část obvodu",
|
||||
"Tip: délka oblouku je (α/360°) · 2πr.",
|
||||
{
|
||||
"Celý obvod je 2πr.",
|
||||
"Oblouk 60° je šestina obvodu.",
|
||||
"Při r = 6 je jeho délka 2π.",
|
||||
"Výsledek nechte v násobcích π.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Thales", "Pravý úhel nad průměrem",
|
||||
"Tip: vrchol pravého úhlu leží na kružnici s průměrem AB.",
|
||||
{
|
||||
"Thaletova věta: úhel nad průměrem je pravý.",
|
||||
"Je-li AB průměr, je úhel ACB pravý pro každé C na kružnici různé od A a B.",
|
||||
"Na mřížce má průměr od [2; 4] do [8; 4] střed [5; 4] a poloměr 3.",
|
||||
"Vyhovují body [5; 7] a [5; 1].",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(2, &mat2_lessons[7], "mat2_unit8", "mat2_unit8_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat2_unit8_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Středový úhel je 80°. Jak velký je obvodový úhel nad stejným obloukem ve stupních?", "num:40", "Obvodový úhel je polovina středového. Výsledek je 40."},
|
||||
{"Kružnice má poloměr 6. Jak dlouhý je oblouk 60°? Nechte π ve výsledku.", "num:2*pi", "(60/360) · 2π · 6 = 2π. Výsledek je 2π."},
|
||||
{"Obvodový úhel je 35°. Jak velký je středový úhel nad stejným obloukem ve stupních?", "num:70", "Středový úhel je dvojnásobek obvodového. Výsledek je 70."},
|
||||
{"AB je průměr od [2; 4] do [8; 4]. Zakreslete bod C na kružnici, ve kterém je úhel ACB pravý.", "right:2,4;8,4", "Thaletova kružnice má střed [5; 4] a poloměr 3. Na mřížce vyhovuje [5; 7] nebo [5; 1]."},
|
||||
};
|
||||
|
||||
return math_practice_page(2, 7, "mat2unit8",
|
||||
"mat2_ex8_title", "mat2_quiz8_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
void add_mat2_pages(GtkStack *stack) {
|
||||
typedef GtkWidget *(*MathBuilder)(void);
|
||||
static const struct {
|
||||
const char *unit_name;
|
||||
const char *ex_name;
|
||||
MathBuilder build_unit;
|
||||
MathBuilder build_ex;
|
||||
} pages[] = {
|
||||
{"mat2unit1", "mat2ex1", build_mat2_unit1_page, build_mat2_unit1_exercise_page},
|
||||
{"mat2unit2", "mat2ex2", build_mat2_unit2_page, build_mat2_unit2_exercise_page},
|
||||
{"mat2unit3", "mat2ex3", build_mat2_unit3_page, build_mat2_unit3_exercise_page},
|
||||
{"mat2unit4", "mat2ex4", build_mat2_unit4_page, build_mat2_unit4_exercise_page},
|
||||
{"mat2unit5", "mat2ex5", build_mat2_unit5_page, build_mat2_unit5_exercise_page},
|
||||
{"mat2unit6", "mat2ex6", build_mat2_unit6_page, build_mat2_unit6_exercise_page},
|
||||
{"mat2unit7", "mat2ex7", build_mat2_unit7_page, build_mat2_unit7_exercise_page},
|
||||
{"mat2unit8", "mat2ex8", build_mat2_unit8_page, build_mat2_unit8_exercise_page},
|
||||
};
|
||||
|
||||
for (guint i = 0; i < G_N_ELEMENTS(pages); i++) {
|
||||
gtk_stack_add_named(stack, pages[i].build_unit(), pages[i].unit_name);
|
||||
gtk_stack_add_named(stack, pages[i].build_ex(), pages[i].ex_name);
|
||||
}
|
||||
}
|
||||
468
src/mat_year3.c
Normal file
468
src/mat_year3.c
Normal file
|
|
@ -0,0 +1,468 @@
|
|||
#include "graduately.h"
|
||||
|
||||
NetLesson mat3_lessons[MATH_N] = {
|
||||
{ .n_slides = 3, .unit_page = "mat3unit1", .ex_page = "mat3ex1" },
|
||||
{ .n_slides = 3, .unit_page = "mat3unit2", .ex_page = "mat3ex2" },
|
||||
{ .n_slides = 3, .unit_page = "mat3unit3", .ex_page = "mat3ex3" },
|
||||
{ .n_slides = 3, .unit_page = "mat3unit4", .ex_page = "mat3ex4" },
|
||||
{ .n_slides = 3, .unit_page = "mat3unit5", .ex_page = "mat3ex5" },
|
||||
{ .n_slides = 3, .unit_page = "mat3unit6", .ex_page = "mat3ex6" },
|
||||
{ .n_slides = 3, .unit_page = "mat3unit7", .ex_page = "mat3ex7" },
|
||||
{ .n_slides = 3, .unit_page = "mat3unit8", .ex_page = "mat3ex8" },
|
||||
};
|
||||
|
||||
static GtkWidget *build_mat3_unit1_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Parabola", "f(x) = ax² + bx + c",
|
||||
"Tip: pro a > 0 má parabola minimum, pro a < 0 maximum.",
|
||||
{
|
||||
"Grafem kvadratické funkce je parabola.",
|
||||
"Průsečík s osou y je bod [0; c].",
|
||||
"U f(x) = x² − 4x + 3 je f(0) = 3.",
|
||||
"Osa paraboly je svislá.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Vrchol", "x = −b/(2a)",
|
||||
"Tip: souřadnice vrcholu pište x;y.",
|
||||
{
|
||||
"x-ová souřadnice vrcholu je −b/(2a).",
|
||||
"U x² − 4x + 3 je to −(−4)/2 = 2.",
|
||||
"f(2) = 4 − 8 + 3 = −1.",
|
||||
"Vrchol je tedy [2; −1].",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Kořeny", "Průsečíky s osou x",
|
||||
"Tip: kořeny jsou řešení f(x) = 0.",
|
||||
{
|
||||
"x² − 4x + 3 = (x − 1)(x − 3).",
|
||||
"Kořeny jsou 1 a 3.",
|
||||
"Leží symetricky kolem osy paraboly.",
|
||||
"f(4) = 16 − 16 + 3 = 3, stejně jako f(0).",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(3, &mat3_lessons[0], "mat3_unit1", "mat3_unit1_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat3_unit1_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Najděte vrchol funkce f(x) = x² − 4x + 3. Napište x a y oddělené středníkem.", "pair:2;-1", "x = 4/2 = 2 a f(2) = −1. Napište 2;-1."},
|
||||
{"Najděte kořeny funkce f(x) = x² − 4x + 3. Oddělte je středníkem.", "set:1;3", "(x − 1)(x − 3) = 0. Napište 1;3."},
|
||||
{"Kolik je f(0) u funkce f(x) = x² − 4x + 3?", "num:3", "f(0) = 3, to je absolutní člen. Výsledek je 3."},
|
||||
{"Kolik je f(4) u funkce f(x) = x² − 4x + 3?", "num:3", "16 − 16 + 3 = 3. Výsledek je 3."},
|
||||
};
|
||||
|
||||
return math_practice_page(3, 0, "mat3unit1",
|
||||
"mat3_ex1_title", "mat3_quiz1_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat3_unit2_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Definice", "Logaritmus je exponent",
|
||||
"Tip: log_b(x) = y právě tehdy, když bʸ = x.",
|
||||
{
|
||||
"Základ logaritmu je kladný a různý od 1.",
|
||||
"Argument logaritmu je kladný.",
|
||||
"log₁₀(1000) = 3, protože 10³ = 1000.",
|
||||
"log₂(32) = 5, protože 2⁵ = 32.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Rovnice", "Stejný základ",
|
||||
"Tip: 2ˣ = 16 se přepíše jako 2ˣ = 2⁴.",
|
||||
{
|
||||
"Exponentialní rovnice se stejným základem porovná exponenty.",
|
||||
"2ˣ = 16 má řešení x = 4.",
|
||||
"log₃(81) = 4, protože 3⁴ = 81.",
|
||||
"log_b(b) = 1 a log_b(1) = 0.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Pravidla", "Součin a podíl",
|
||||
"Tip: logaritmus součinu je součet logaritmů.",
|
||||
{
|
||||
"log_b(xy) = log_b(x) + log_b(y).",
|
||||
"log_b(x/y) = log_b(x) − log_b(y).",
|
||||
"log_b(xⁿ) = n · log_b(x).",
|
||||
"Těmito pravidly se argument zjednodušuje, ne základ.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(3, &mat3_lessons[1], "mat3_unit2", "mat3_unit2_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat3_unit2_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Spočítejte logaritmus o základu 10 z 1000.", "num:3", "10³ = 1000, takže logaritmus je 3. Výsledek je 3."},
|
||||
{"Spočítejte logaritmus o základu 2 z 32.", "num:5", "2⁵ = 32. Výsledek je 5."},
|
||||
{"Vyřešte 2ˣ = 16. Napište x.", "num:4", "16 = 2⁴, takže x = 4. Výsledek je 4."},
|
||||
{"Spočítejte logaritmus o základu 3 z 81.", "num:4", "3⁴ = 81. Výsledek je 4."},
|
||||
};
|
||||
|
||||
return math_practice_page(3, 1, "mat3unit2",
|
||||
"mat3_ex2_title", "mat3_quiz2_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat3_unit3_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Jednotková kružnice", "Úhel od kladné poloosy x",
|
||||
"Tip: sinus je y-ová souřadnice, kosinus x-ová.",
|
||||
{
|
||||
"Bod na jednotkové kružnici pod úhlem α má souřadnice [cos α; sin α].",
|
||||
"cos 0° = 1 a sin 0° = 0.",
|
||||
"sin 90° = 1 a cos 90° = 0.",
|
||||
"cos 180° = −1 a sin 180° = 0.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • 45°", "Polovina pravého úhlu",
|
||||
"Tip: sin 45° = cos 45° = √2/2.",
|
||||
{
|
||||
"Úhel 45° půlí čtvrtkružnici.",
|
||||
"Souřadnice bodu splňují x = y a x² + y² = 1.",
|
||||
"Kladné řešení je √2/2.",
|
||||
"Odpověď lze psát jako sqrt(2)/2 nebo 1/sqrt(2).",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Znaménka", "Čtyři kvadranty",
|
||||
"Tip: v prvním kvadrantu jsou sinus i kosinus kladné.",
|
||||
{
|
||||
"Ve druhém kvadrantu je sinus kladný a kosinus záporný.",
|
||||
"Ve třetím jsou oba záporné.",
|
||||
"Ve čtvrtém je kosinus kladný a sinus záporný.",
|
||||
"Tangens je sinus dělený kosinem a není definován při cos α = 0.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(3, &mat3_lessons[2], "mat3_unit3", "mat3_unit3_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat3_unit3_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Kolik je sin 90°?", "num:1", "Bod [0; 1] má y-ovou souřadnici 1. Výsledek je 1."},
|
||||
{"Kolik je cos 180°?", "num:-1", "Bod [−1; 0] má x-ovou souřadnici −1. Výsledek je -1."},
|
||||
{"Kolik je sin 45°? Můžete psát sqrt(2)/2.", "num:sqrt(2)/2", "sin 45° = √2/2. Stejnou hodnotu má 1/√2."},
|
||||
{"Kolik je cos 0°?", "num:1", "Úhel 0° je bod [1; 0]. Výsledek je 1."},
|
||||
};
|
||||
|
||||
return math_practice_page(3, 2, "mat3unit3",
|
||||
"mat3_ex3_title", "mat3_quiz3_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat3_unit4_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Sinová věta", "a/sin α = 2R",
|
||||
"Tip: poměr strany a sinu protilehlého úhlu je pro všechny strany stejný.",
|
||||
{
|
||||
"a/sin α = b/sin β = c/sin γ = 2R.",
|
||||
"R je poloměr kružnice opsané.",
|
||||
"Když a = 10 a α = 30°, je 10 / (1/2) = 20 = 2R.",
|
||||
"Poloměr R je 10.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Kosinová věta", "c² = a² + b² − 2ab cos γ",
|
||||
"Tip: pro pravý úhel je cos 90° = 0 a věta přejde v Pythagora.",
|
||||
{
|
||||
"Kosinová věta dopočítá stranu proti známému úhlu.",
|
||||
"Jsou-li dvě strany 2 a sevřený úhel 60°, je třetí strana také 2.",
|
||||
"a² = 4 + 4 − 2 · 2 · 2 · cos 60° = 8 − 8 · 1/2 = 4.",
|
||||
"Rovnostranný trojúhelník má všechny úhly 60°.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Obsah", "Dvě strany a sevřený úhel",
|
||||
"Tip: S = (1/2) · b · c · sin α.",
|
||||
{
|
||||
"Obsah je polovina součinu dvou stran a sinu sevřeného úhlu.",
|
||||
"(1/2) · 4 · 6 · sin 30° = 12 · 1/2 = 6.",
|
||||
"Sinová věta také dopočítá další stranu: b = a · sin β / sin α.",
|
||||
"Pro a = 8, α = 30° a β = 45° je b = 8√2.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(3, &mat3_lessons[3], "mat3_unit4", "mat3_unit4_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat3_unit4_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Strana a = 10 leží proti úhlu 30°. Jaký je poloměr kružnice opsané?", "num:10", "a/sin 30° = 20 = 2R, takže R = 10. Výsledek je 10."},
|
||||
{"Dvě strany trojúhelníku mají délku 2 a svírají úhel 60°. Jak dlouhá je třetí strana?", "num:2", "a² = 4 + 4 − 8 · 1/2 = 4, takže a = 2. Výsledek je 2."},
|
||||
{"Dvě strany mají délky 4 a 6 a svírají úhel 30°. Jaký je obsah trojúhelníku?", "num:6", "(1/2) · 4 · 6 · 1/2 = 6. Výsledek je 6."},
|
||||
{"Strana a = 8 je proti 30° a úhel β je 45°. Jak dlouhá je strana b? Sin 45° je √2/2.", "num:8*sqrt(2)", "b = 8 · sin 45° / sin 30° = 8 · (√2/2) / (1/2) = 8√2."},
|
||||
};
|
||||
|
||||
return math_practice_page(3, 3, "mat3unit4",
|
||||
"mat3_ex4_title", "mat3_quiz4_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat3_unit5_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Aritmetická", "Stálý rozdíl",
|
||||
"Tip: aₙ = a₁ + (n − 1)d.",
|
||||
{
|
||||
"Aritmetická posloupnost přičítá stále stejný rozdíl d.",
|
||||
"Pro a₁ = 3 a d = 2 je a₅ = 3 + 4 · 2 = 11.",
|
||||
"Součet prvních n členů je sₙ = n/2 · (2a₁ + (n − 1)d).",
|
||||
"s₅ = 5/2 · (6 + 8) = 35.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Geometrická", "Stálý kvocient",
|
||||
"Tip: aₙ = a₁ · qⁿ⁻¹.",
|
||||
{
|
||||
"Geometrická posloupnost násobí stále stejným kvocientem q.",
|
||||
"Pro a₁ = 2 a q = 3 je a₄ = 2 · 3³ = 54.",
|
||||
"q = 1 znamená, že se členy nemění.",
|
||||
"q = 0 je po prvním členu samá nula, pokud je první člen definován.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Nekonečná řada", "Součet pro |q| < 1",
|
||||
"Tip: s = a₁ / (1 − q).",
|
||||
{
|
||||
"Nekonečná geometrická řada má součet jen pro |q| < 1.",
|
||||
"Pro a₁ = 6 a q = 1/2 je s = 6 / (1/2) = 12.",
|
||||
"Pro |q| ≥ 1 se součet členů neblíží ke konečnému číslu, kromě případu q = 1 a a₁ = 0.",
|
||||
"Řada 6 + 3 + 3/2 + … se tedy blíží ke 12.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(3, &mat3_lessons[4], "mat3_unit5", "mat3_unit5_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat3_unit5_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Aritmetická posloupnost má a₁ = 3 a d = 2. Kolik je a₅?", "num:11", "a₅ = 3 + 4 · 2 = 11. Výsledek je 11."},
|
||||
{"Aritmetická posloupnost má a₁ = 3 a d = 2. Kolik je součet prvních pěti členů?", "num:35", "s₅ = 5/2 · (6 + 8) = 35. Výsledek je 35."},
|
||||
{"Geometrická posloupnost má a₁ = 2 a q = 3. Kolik je a₄?", "num:54", "a₄ = 2 · 27 = 54. Výsledek je 54."},
|
||||
{"Nekonečná geometrická řada má a₁ = 6 a q = 1/2. Jaký je její součet?", "num:12", "s = 6 / (1/2) = 12. Výsledek je 12."},
|
||||
};
|
||||
|
||||
return math_practice_page(3, 4, "mat3unit5",
|
||||
"mat3_ex5_title", "mat3_quiz5_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat3_unit6_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Permutace", "Pořadí všech prvků",
|
||||
"Tip: n! = 1 · 2 · … · n a 0! = 1.",
|
||||
{
|
||||
"Permutace je uspořádání všech n různých prvků.",
|
||||
"Počet je n!.",
|
||||
"4! = 24 a 3! = 6.",
|
||||
"Na prvním místě je n možností, na dalším n − 1, a tak dál.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Variace", "Pořadí vybraných prvků",
|
||||
"Tip: variace bez opakování V(n, k) = n! / (n − k)!.",
|
||||
{
|
||||
"Variace vybírá k prvků z n a záleží na pořadí.",
|
||||
"V(5, 2) = 5 · 4 = 20.",
|
||||
"Opakování by dovolilo vybrat stejný prvek víckrát.",
|
||||
"Permutace je variace, která vybírá všech n prvků.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Kombinace", "Pořadí nehraje roli",
|
||||
"Tip: C(n, k) = n! / (k! · (n − k)!).",
|
||||
{
|
||||
"Kombinace vybírá k prvků a dvě vybrané skupiny se stejným obsahem jsou jedna.",
|
||||
"C(5, 2) = 10.",
|
||||
"C(n, k) = C(n, n − k).",
|
||||
"Počet variací je k! krát větší než počet kombinací.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(3, &mat3_lessons[5], "mat3_unit6", "mat3_unit6_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat3_unit6_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Kolik je permutací čtyř různých prvků?", "num:24", "4! = 24. Výsledek je 24."},
|
||||
{"Kolik je kombinací 2 prvků z 5?", "num:10", "C(5, 2) = (5 · 4) / 2 = 10. Výsledek je 10."},
|
||||
{"Kolik je variací 2 prvků z 5 bez opakování?", "num:20", "V(5, 2) = 5 · 4 = 20. Výsledek je 20."},
|
||||
{"Kolik je 3!?", "num:6", "3 · 2 · 1 = 6. Výsledek je 6."},
|
||||
};
|
||||
|
||||
return math_practice_page(3, 5, "mat3unit6",
|
||||
"mat3_ex6_title", "mat3_quiz6_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat3_unit7_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Definice", "Příznivé ku všem",
|
||||
"Tip: všechny výsledky musí být stejně možné.",
|
||||
{
|
||||
"Klasická pravděpodobnost je počet příznivých výsledků dělený počtem všech.",
|
||||
"Hodnota leží od 0 do 1.",
|
||||
"Jev jistý má pravděpodobnost 1, jev nemožný 0.",
|
||||
"Na kostce je sudé číslo ve třech případech ze šesti, tedy 1/2.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Nezávislost", "Součin pravděpodobností",
|
||||
"Tip: dvě mince mají čtyři stejně možné dvojice výsledků.",
|
||||
{
|
||||
"U dvou nezávislých mincí jsou možnosti PP, PO, OP a OO.",
|
||||
"Dva panny jsou jedna možnost ze čtyř, tedy 1/4.",
|
||||
"U dvou kostek je 36 stejně možných dvojic.",
|
||||
"Součet 7 nastane u šesti dvojic: 1+6, 2+5, 3+4, 4+3, 5+2 a 6+1.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Karty", "Čtyři esa v balíčku 32 karet",
|
||||
"Tip: mariášový balíček má 32 karet.",
|
||||
{
|
||||
"Pravděpodobnost esa je 4/32 = 1/8.",
|
||||
"Tažení jedné karty považuje každou kartu za stejně možnou.",
|
||||
"Doplňkový jev má pravděpodobnost 1 − p.",
|
||||
"Výsledek se krátí do základního tvaru.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(3, &mat3_lessons[6], "mat3_unit7", "mat3_unit7_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat3_unit7_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Jaká je pravděpodobnost sudého čísla při hodu hrací kostkou?", "num:1/2", "Sudá jsou 2, 4 a 6, tedy 3 ze 6, čili 1/2. Výsledek je 1/2."},
|
||||
{"Jaká je pravděpodobnost, že na dvou mincích padnou dvě panny?", "num:1/4", "Ze čtyř dvojic je příznivá jedna. Výsledek je 1/4."},
|
||||
{"V balíčku 32 karet jsou 4 esa. Jaká je pravděpodobnost, že tažená karta je eso?", "num:1/8", "4/32 = 1/8. Výsledek je 1/8."},
|
||||
{"Jaká je pravděpodobnost, že součet na dvou hracích kostkách je 7?", "num:1/6", "Příznivých dvojic je 6 z 36, tedy 1/6. Výsledek je 1/6."},
|
||||
};
|
||||
|
||||
return math_practice_page(3, 6, "mat3unit7",
|
||||
"mat3_ex7_title", "mat3_quiz7_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat3_unit8_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Krychle", "Objem a povrch",
|
||||
"Tip: krychle o hraně a má objem a³ a povrch 6a².",
|
||||
{
|
||||
"Objem je počet jednotkových krychlí, které se do tělesa vejdou.",
|
||||
"Krychle o hraně 3 má objem 27.",
|
||||
"Povrch je součet obsahů všech stěn: 6 · 9 = 54.",
|
||||
"Jednotky objemu jsou krychlové, jednotky povrchu čtvereční.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Válec a koule", "Vzorce s π",
|
||||
"Tip: výsledek nechte v násobcích π.",
|
||||
{
|
||||
"Objem válce je πr²v.",
|
||||
"Pro r = 2 a v = 5 je objem 20π.",
|
||||
"Objem koule je (4/3)πr³.",
|
||||
"Pro r = 3 je objem (4/3)π · 27 = 36π.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Jehlan", "Třetina hranolu",
|
||||
"Tip: objem jehlanu je (1/3) · obsah podstavy · výška.",
|
||||
{
|
||||
"Jehlan má stejný objem jako třetina hranolu se stejnou podstavou a výškou.",
|
||||
"Podstava o obsahu 12 a výška 4 dávají objem 16.",
|
||||
"Výška je kolmá vzdálenost vrcholu od roviny podstavy.",
|
||||
"Stejný vztah třetiny platí i pro kužel vůči válci.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(3, &mat3_lessons[7], "mat3_unit8", "mat3_unit8_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat3_unit8_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Krychle má hranu 3. Jaký je její objem?", "num:27", "3³ = 27. Výsledek je 27."},
|
||||
{"Krychle má hranu 3. Jaký je její povrch?", "num:54", "Šest stěn o obsahu 9 dává 54. Výsledek je 54."},
|
||||
{"Válec má poloměr 2 a výšku 5. Jaký je jeho objem? Nechte π ve výsledku.", "num:20*pi", "πr²v = π · 4 · 5 = 20π. Výsledek je 20π."},
|
||||
{"Koule má poloměr 3. Jaký je její objem? Nechte π ve výsledku.", "num:36*pi", "(4/3)π · 27 = 36π. Výsledek je 36π."},
|
||||
};
|
||||
|
||||
return math_practice_page(3, 7, "mat3unit8",
|
||||
"mat3_ex8_title", "mat3_quiz8_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
void add_mat3_pages(GtkStack *stack) {
|
||||
typedef GtkWidget *(*MathBuilder)(void);
|
||||
static const struct {
|
||||
const char *unit_name;
|
||||
const char *ex_name;
|
||||
MathBuilder build_unit;
|
||||
MathBuilder build_ex;
|
||||
} pages[] = {
|
||||
{"mat3unit1", "mat3ex1", build_mat3_unit1_page, build_mat3_unit1_exercise_page},
|
||||
{"mat3unit2", "mat3ex2", build_mat3_unit2_page, build_mat3_unit2_exercise_page},
|
||||
{"mat3unit3", "mat3ex3", build_mat3_unit3_page, build_mat3_unit3_exercise_page},
|
||||
{"mat3unit4", "mat3ex4", build_mat3_unit4_page, build_mat3_unit4_exercise_page},
|
||||
{"mat3unit5", "mat3ex5", build_mat3_unit5_page, build_mat3_unit5_exercise_page},
|
||||
{"mat3unit6", "mat3ex6", build_mat3_unit6_page, build_mat3_unit6_exercise_page},
|
||||
{"mat3unit7", "mat3ex7", build_mat3_unit7_page, build_mat3_unit7_exercise_page},
|
||||
{"mat3unit8", "mat3ex8", build_mat3_unit8_page, build_mat3_unit8_exercise_page},
|
||||
};
|
||||
|
||||
for (guint i = 0; i < G_N_ELEMENTS(pages); i++) {
|
||||
gtk_stack_add_named(stack, pages[i].build_unit(), pages[i].unit_name);
|
||||
gtk_stack_add_named(stack, pages[i].build_ex(), pages[i].ex_name);
|
||||
}
|
||||
}
|
||||
472
src/mat_year4.c
Normal file
472
src/mat_year4.c
Normal file
|
|
@ -0,0 +1,472 @@
|
|||
#include "graduately.h"
|
||||
|
||||
NetLesson mat4_lessons[MATH_N] = {
|
||||
{ .n_slides = 3, .unit_page = "mat4unit1", .ex_page = "mat4ex1" },
|
||||
{ .n_slides = 3, .unit_page = "mat4unit2", .ex_page = "mat4ex2" },
|
||||
{ .n_slides = 3, .unit_page = "mat4unit3", .ex_page = "mat4ex3" },
|
||||
{ .n_slides = 3, .unit_page = "mat4unit4", .ex_page = "mat4ex4" },
|
||||
{ .n_slides = 3, .unit_page = "mat4unit5", .ex_page = "mat4ex5" },
|
||||
{ .n_slides = 3, .unit_page = "mat4unit6", .ex_page = "mat4ex6" },
|
||||
{ .n_slides = 3, .unit_page = "mat4unit7", .ex_page = "mat4ex7" },
|
||||
{ .n_slides = 3, .unit_page = "mat4unit8", .ex_page = "mat4ex8" },
|
||||
};
|
||||
|
||||
static GtkWidget *build_mat4_unit1_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Souřadnice", "Velikost a součet",
|
||||
"Tip: velikost vektoru (a; b) je √(a² + b²).",
|
||||
{
|
||||
"Vektor má velikost a směr.",
|
||||
"|(3; 4)| = 5, protože 9 + 16 = 25.",
|
||||
"Vektory se sčítají po souřadnicích.",
|
||||
"(1; 2) + (3; −1) = (4; 1).",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Skalární součin", "Číslo, ne vektor",
|
||||
"Tip: (a; b) · (c; d) = ac + bd.",
|
||||
{
|
||||
"Skalární součin dvou vektorů je číslo.",
|
||||
"(1; 2) · (3; 4) = 3 + 8 = 11.",
|
||||
"Nulový skalární součin znamená kolmé vektory.",
|
||||
"Velikost lze psát jako odmocninu ze skalárního součinu vektoru se sebou.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Vzdálenost", "Velikost rozdílu",
|
||||
"Tip: na mřížce osa y míří nahoru.",
|
||||
{
|
||||
"Vzdálenost bodů je velikost vektoru mezi nimi.",
|
||||
"Od [1; 1] do [4; 5] je vektor (3; 4) a vzdálenost 5.",
|
||||
"Koncový bod vektoru (3; 2) umístěného do [1; 1] je [4; 3].",
|
||||
"Pořadí souřadnic je x a potom y.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(4, &mat4_lessons[0], "mat4_unit1", "mat4_unit1_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat4_unit1_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Jaká je velikost vektoru (3; 4)?", "num:5", "√(9 + 16) = √25 = 5. Výsledek je 5."},
|
||||
{"Sečtěte vektory (1; 2) a (3; −1). Napište souřadnice oddělené středníkem.", "pair:4;1", "(1 + 3; 2 + (−1)) = (4; 1). Napište 4;1."},
|
||||
{"Spočítejte skalární součin vektorů (1; 2) a (3; 4).", "num:11", "1 · 3 + 2 · 4 = 11. Výsledek je 11."},
|
||||
{"Jaká je vzdálenost bodů [1; 1] a [4; 5]?", "num:5", "Rozdíl je (3; 4) a jeho velikost je 5. Výsledek je 5."},
|
||||
{"Z bodu A = [1; 1] naneste vektor (3; 2). Zakreslete koncový bod.", "points:4,3;fix:1,1", "Ke každé souřadnici se přičte složka vektoru: [1 + 3; 1 + 2] = [4; 3]."},
|
||||
};
|
||||
|
||||
return math_practice_page(4, 0, "mat4unit1",
|
||||
"mat4_ex1_title", "mat4_quiz1_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat4_unit2_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Směrnice", "y = ax + b",
|
||||
"Tip: a = (y₂ − y₁)/(x₂ − x₁).",
|
||||
{
|
||||
"Body [1; 2] a [4; 8] určují směrnici (8 − 2)/(4 − 1) = 2.",
|
||||
"Přímka procházející bodem [0; 1] se směrnicí 2 má rovnici y = 2x + 1.",
|
||||
"Úsek na ose y je 1.",
|
||||
"Svislá přímka směrnici tohoto tvaru nemá.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Parametr", "Bod a směrový vektor",
|
||||
"Tip: X = A + t · u.",
|
||||
{
|
||||
"Parametrické vyjádření je x = x₀ + t · u₁, y = y₀ + t · u₂.",
|
||||
"Přímka (1; 3) + t(2; 0) má při t = 2 bod [5; 3].",
|
||||
"Každé t dá právě jeden bod přímky.",
|
||||
"Směrový vektor (2; 0) je vodorovný.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Vzdálenost", "Bod od přímky",
|
||||
"Tip: pro ax + by + c = 0 je vzdálenost |ax₀ + by₀ + c| / √(a² + b²).",
|
||||
{
|
||||
"Přímka 3x + 4y − 10 = 0 má a = 3, b = 4 a c = −10.",
|
||||
"Vzdálenost počátku je |−10| / √(9 + 16) = 10/5 = 2.",
|
||||
"Čitatel je absolutní hodnota, vzdálenost není záporná.",
|
||||
"Čtvrtý vrchol rovnoběžníku k bodům A, B a C je B + C − A.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(4, &mat4_lessons[1], "mat4_unit2", "mat4_unit2_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat4_unit2_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Jaká je směrnice přímky body [1; 2] a [4; 8]?", "num:2", "(8 − 2)/(4 − 1) = 2. Výsledek je 2."},
|
||||
{"Přímka má rovnici y = 2x + 1. Jaký je její úsek na ose y?", "num:1", "Úsek je absolutní člen, tedy 1. Výsledek je 1."},
|
||||
{"Bod přímky je (1; 3) + t(2; 0). Kam přímka dojde pro t = 2? Napište x a y středníkem.", "pair:5;3", "(1; 3) + 2 · (2; 0) = (5; 3). Napište 5;3."},
|
||||
{"Jaká je vzdálenost bodu [0; 0] od přímky 3x + 4y − 10 = 0?", "num:2", "|−10| / √(9 + 16) = 10/5 = 2. Výsledek je 2."},
|
||||
{"A = [1; 1], B = [4; 1] a C = [2; 3]. Zakreslete čtvrtý vrchol rovnoběžníku, ve kterém jsou AB a AC sousední strany.", "para:1,1;4,1;2,3", "D = B + C − A = [4 + 2 − 1; 1 + 3 − 1] = [5; 3]."},
|
||||
};
|
||||
|
||||
return math_practice_page(4, 1, "mat4unit2",
|
||||
"mat4_ex2_title", "mat4_quiz2_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat4_unit3_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Kružnice", "Střed a poloměr",
|
||||
"Tip: (x − m)² + (y − n)² = r² má střed [m; n].",
|
||||
{
|
||||
"Kružnice je množina bodů stejně vzdálených od středu.",
|
||||
"(x − 1)² + (y + 2)² = 9 má střed [1; −2].",
|
||||
"y + 2 je totéž co y − (−2).",
|
||||
"Poloměr je √9 = 3, ne 9.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Elipsa", "Dvě poloosy",
|
||||
"Tip: ve tvaru x²/a² + y²/b² = 1 je a poloosa na ose x.",
|
||||
{
|
||||
"Elipsa v osové poloze se středem v počátku má rovnici x²/a² + y²/b² = 1.",
|
||||
"U x²/25 + y²/9 = 1 je a = 5 a b = 3.",
|
||||
"Delší poloosa je hlavní, kratší vedlejší.",
|
||||
"Vrcholy na ose x jsou [±a; 0], na ose y [0; ±b].",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Excentricita", "Vzdálenost ohniska od středu",
|
||||
"Tip: pro a > b je e = √(a² − b²).",
|
||||
{
|
||||
"Lineární výstřednost e splňuje e² = a² − b², když je a hlavní poloosa.",
|
||||
"√(25 − 9) = √16 = 4.",
|
||||
"Ohniska elipsy jsou [±e; 0].",
|
||||
"Součet vzdáleností bodu elipsy od obou ohnisek je 2a.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(4, &mat4_lessons[2], "mat4_unit3", "mat4_unit3_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat4_unit3_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Kružnice má rovnici (x − 1)² + (y + 2)² = 9. Napište souřadnice středu jako x;y.", "pair:1;-2", "Střed je [1; −2]. Napište 1;-2."},
|
||||
{"Jaký je poloměr kružnice (x − 1)² + (y + 2)² = 9?", "num:3", "Pravá strana je r², takže r = 3. Výsledek je 3."},
|
||||
{"Elipsa má rovnici x²/25 + y²/9 = 1. Jak dlouhá je poloosa a na ose x?", "num:5", "a² = 25, takže a = 5. Výsledek je 5."},
|
||||
{"Elipsa má rovnici x²/25 + y²/9 = 1. Jak dlouhá je poloosa b na ose y?", "num:3", "b² = 9, takže b = 3. Výsledek je 3."},
|
||||
{"Elipsa má a = 5 a b = 3. Jaká je její lineární výstřednost e?", "num:4", "e = √(25 − 9) = √16 = 4. Výsledek je 4."},
|
||||
};
|
||||
|
||||
return math_practice_page(4, 2, "mat4unit3",
|
||||
"mat4_ex3_title", "mat4_quiz3_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat4_unit4_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Tvar", "a + bi",
|
||||
"Tip: i² = −1. Reálnou a imaginární část pište oddělené středníkem.",
|
||||
{
|
||||
"Komplexní číslo a + bi má reálnou část a a imaginární část b.",
|
||||
"i je imaginární jednotka a i² = −1.",
|
||||
"Čísla se sčítají po složkách.",
|
||||
"(2 + 3i) + (1 − i) = 3 + 2i.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Násobení", "Jako dvojčlen",
|
||||
"Tip: (1 + i)(1 − i) je rozdíl čtverců.",
|
||||
{
|
||||
"(a + bi)(c + di) = ac + adi + bci + bdi².",
|
||||
"bdi² = −bd, proto se imaginární jednotka ve výsledku nemusí objevit.",
|
||||
"(1 + i)(1 − i) = 1 − (i)² = 1 − (−1) = 2.",
|
||||
"Komplexní čísla lze násobit i v goniometrickém tvaru sčítáním argumentů.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Absolutní hodnota", "Vzdálenost od nuly",
|
||||
"Tip: |a + bi| = √(a² + b²).",
|
||||
{
|
||||
"Absolutní hodnota je vzdálenost obrazu čísla od počátku Gaussovy roviny.",
|
||||
"|3 + 4i| = 5.",
|
||||
"|z| je vždy reálné nezáporné číslo.",
|
||||
"i² = −1, i³ = −i a i⁴ = 1.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(4, &mat4_lessons[3], "mat4_unit4", "mat4_unit4_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat4_unit4_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Sečtěte 2 + 3i a 1 − i. Napište reálnou a imaginární část středníkem.", "pair:3;2", "(2 + 1) + (3 − 1)i = 3 + 2i. Napište 3;2."},
|
||||
{"Spočítejte (1 + i)(1 − i).", "num:2", "1 − i² = 1 − (−1) = 2. Výsledek je 2."},
|
||||
{"Jaká je absolutní hodnota čísla 3 + 4i?", "num:5", "√(9 + 16) = 5. Výsledek je 5."},
|
||||
{"Kolik je i²?", "num:-1", "Imaginární jednotka splňuje i² = −1. Výsledek je -1."},
|
||||
};
|
||||
|
||||
return math_practice_page(4, 3, "mat4unit4",
|
||||
"mat4_ex4_title", "mat4_quiz4_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat4_unit5_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Mocnina", "(xⁿ)' = n xⁿ⁻¹",
|
||||
"Tip: derivace je směrnice tečny.",
|
||||
{
|
||||
"Derivace funkce v bodě je směrnice tečny grafu v tom bodě.",
|
||||
"(x³)' = 3x². V bodě x = 2 je 3 · 4 = 12.",
|
||||
"(5x²)' = 10x. V bodě x = 1 je 10.",
|
||||
"Derivace konstanty je 0 a konstanta se vytýká.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Tečna", "Směrnice v bodě",
|
||||
"Tip: u f(x) = x² je f'(x) = 2x.",
|
||||
{
|
||||
"Tečna ke grafu y = x² v bodě x = 1 má směrnici 2.",
|
||||
"Rovnice tečny používá bod [1; 1] a tuto směrnici.",
|
||||
"Kladná derivace znamená, že funkce v bodě roste.",
|
||||
"Záporná derivace znamená, že funkce klesá.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Sinus", "Derivace sinu je kosinus",
|
||||
"Tip: (sin x)' = cos x a cos 0 = 1.",
|
||||
{
|
||||
"Derivace funkce sin x je cos x.",
|
||||
"Derivace funkce cos x je −sin x.",
|
||||
"V bodě x = 0 je derivace sinu rovna cos 0 = 1.",
|
||||
"Úhel v tomto vzorci je v radiánech, ale nula je nula v obou jednotkách.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(4, &mat4_lessons[4], "mat4_unit5", "mat4_unit5_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat4_unit5_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Jaká je derivace funkce x³ v bodě x = 2?", "num:12", "(x³)' = 3x² a 3 · 4 = 12. Výsledek je 12."},
|
||||
{"Jaká je derivace funkce 5x² v bodě x = 1?", "num:10", "(5x²)' = 10x a v jedničce je 10. Výsledek je 10."},
|
||||
{"Jakou směrnici má tečna ke grafu y = x² v bodě x = 1?", "num:2", "Derivace 2x je v bodě 1 rovna 2. Výsledek je 2."},
|
||||
{"Jaká je derivace funkce sin x v bodě x = 0? Derivace sinu je kosinus.", "num:1", "(sin x)' = cos x a cos 0 = 1. Výsledek je 1."},
|
||||
};
|
||||
|
||||
return math_practice_page(4, 4, "mat4unit5",
|
||||
"mat4_ex5_title", "mat4_quiz5_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat4_unit6_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Stacionární bod", "Derivace je nula",
|
||||
"Tip: f'(x) = 0 je vodorovná tečna.",
|
||||
{
|
||||
"V lokálním extrému diferencovatelné funkce je derivace nulová.",
|
||||
"U f(x) = x³ − 3x je f'(x) = 3x² − 3.",
|
||||
"3(x² − 1) = 0 dává x = −1 a x = 1.",
|
||||
"To jsou jediní kandidáti na lokální extrém.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Maximum a minimum", "Znaménko druhé derivace",
|
||||
"Tip: f''(x) = 6x. Záporná druhá derivace znamená lokální maximum.",
|
||||
{
|
||||
"V x = −1 je f''(−1) = −6 < 0, takže jde o lokální maximum.",
|
||||
"f(−1) = −1 + 3 = 2.",
|
||||
"V x = 1 je f''(1) = 6 > 0, takže jde o lokální minimum.",
|
||||
"f(1) = 1 − 3 = −2.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Jiný příklad", "Lineární derivace",
|
||||
"Tip: g'(x) = 2x − 4 je nula při x = 2.",
|
||||
{
|
||||
"Kde je derivace nula, má graf vodorovnou tečnu.",
|
||||
"Rovnice 2x − 4 = 0 má řešení x = 2.",
|
||||
"Pro x < 2 je derivace 2x − 4 záporná, funkce klesá.",
|
||||
"Pro x > 2 je derivace kladná, funkce roste.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(4, &mat4_lessons[5], "mat4_unit6", "mat4_unit6_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat4_unit6_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Funkce f(x) = x³ − 3x. Ve kterém x má lokální maximum?", "num:-1", "f'(x) = 3x² − 3 = 0 pro x = ±1 a v −1 je maximum. Výsledek je -1."},
|
||||
{"Funkce f(x) = x³ − 3x. Jaká je hodnota lokálního maxima?", "num:2", "f(−1) = −1 + 3 = 2. Výsledek je 2."},
|
||||
{"Funkce f(x) = x³ − 3x. Ve kterém x má lokální minimum?", "num:1", "Druhá derivace 6x je v x = 1 kladná. Výsledek je 1."},
|
||||
{"Derivace funkce je g'(x) = 2x − 4. Pro které x je derivace nulová?", "num:2", "2x − 4 = 0 dává x = 2. Výsledek je 2."},
|
||||
};
|
||||
|
||||
return math_practice_page(4, 5, "mat4unit6",
|
||||
"mat4_ex6_title", "mat4_quiz6_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat4_unit7_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Primitivní funkce", "Opačný postup k derivaci",
|
||||
"Tip: primitivní funkce k xⁿ je xⁿ⁺¹/(n + 1) pro n ≠ −1.",
|
||||
{
|
||||
"Neurčitý integrál je množina primitivních funkcí.",
|
||||
"Liší se o konstantu, protože derivace konstanty je nula.",
|
||||
"Primitivní funkce k 2x je x².",
|
||||
"Primitivní funkce k x² je x³/3.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Určitý integrál", "Newtonův vzorec",
|
||||
"Tip: ∫ od a do b z f je F(b) − F(a).",
|
||||
{
|
||||
"Určitý integrál nezáporné funkce je obsah plochy pod grafem.",
|
||||
"∫ od 0 do 3 z 2x dx = [x²] od 0 do 3 = 9.",
|
||||
"∫ od 0 do 2 z x² dx = [x³/3] od 0 do 2 = 8/3.",
|
||||
"Dolní mez 0 u těchto mocnin často vynuluje první člen.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Konstanta", "Násobek lze vytknout",
|
||||
"Tip: integrál z c · f je c krát integrál z f.",
|
||||
{
|
||||
"∫ od 0 do 1 z 3x² dx = [x³] od 0 do 1 = 1.",
|
||||
"∫ od 0 do 2 z 6x dx = [3x²] od 0 do 2 = 12.",
|
||||
"Záporná funkce dává záporný integrál, nejde vždy o obsah beze znaménka.",
|
||||
"Součet funkcí se integruje člen po členu.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(4, &mat4_lessons[6], "mat4_unit7", "mat4_unit7_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat4_unit7_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Spočítejte integrál od 0 do 3 z funkce 2x.", "num:9", "Primitivní funkce je x² a 9 − 0 = 9. Výsledek je 9."},
|
||||
{"Spočítejte integrál od 0 do 2 z funkce x².", "num:8/3", "Primitivní funkce je x³/3 a 8/3 − 0 = 8/3. Výsledek je 8/3."},
|
||||
{"Spočítejte integrál od 0 do 1 z funkce 3x².", "num:1", "Primitivní funkce je x³ a 1 − 0 = 1. Výsledek je 1."},
|
||||
{"Spočítejte integrál od 0 do 2 z funkce 6x.", "num:12", "Primitivní funkce je 3x² a 12 − 0 = 12. Výsledek je 12."},
|
||||
};
|
||||
|
||||
return math_practice_page(4, 6, "mat4unit7",
|
||||
"mat4_ex7_title", "mat4_quiz7_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat4_unit8_page(void) {
|
||||
static const NetSlide slides[] = {
|
||||
{
|
||||
"1 / 3 • Číslo", "Logaritmus a zlomek",
|
||||
"Tip: log₂(8) = 3, protože 2³ = 8.",
|
||||
{
|
||||
"Logaritmus vrací exponent.",
|
||||
"1/2 + 1/3 = 5/6.",
|
||||
"Společný jmenovatel se hledá i v maturitním příkladu stejně jako na základní škole.",
|
||||
"Výsledek se krátí.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 / 3 • Počet a vektor", "Kombinace a velikost",
|
||||
"Tip: C(6, 2) = 15 a |(3; 4)| = 5.",
|
||||
{
|
||||
"C(6, 2) = (6 · 5) / 2 = 15.",
|
||||
"Pořadí vybrané dvojice se nepočítá dvakrát.",
|
||||
"Velikost vektoru (3; 4) je přepona trojúhelníku 3-4-5.",
|
||||
"Skalární součin by u kolmých vektorů vyšel nula.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 / 3 • Rovnice", "x² − 1 = 0",
|
||||
"Tip: rozdíl čtverců (x − 1)(x + 1).",
|
||||
{
|
||||
"x² − 1 = 0 má kořeny −1 a 1.",
|
||||
"Oba se napíšou, oddělené středníkem.",
|
||||
"Diskriminant je 0 − 4 · 1 · (−1) = 4.",
|
||||
"Zkouška: 1 − 1 = 0 a 1 − 1 = 0.",
|
||||
NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return math_unit_page(4, &mat4_lessons[7], "mat4_unit8", "mat4_unit8_sub",
|
||||
slides, G_N_ELEMENTS(slides));
|
||||
}
|
||||
|
||||
static GtkWidget *build_mat4_unit8_exercise_page(void) {
|
||||
static const MathItem qs[] = {
|
||||
{"Spočítejte logaritmus o základu 2 z 8.", "num:3", "2³ = 8. Výsledek je 3."},
|
||||
{"Kolik je kombinací 2 prvků ze 6?", "num:15", "C(6, 2) = 15. Výsledek je 15."},
|
||||
{"Jaká je velikost vektoru (3; 4)?", "num:5", "√(9 + 16) = 5. Výsledek je 5."},
|
||||
{"Spočítejte 1/2 + 1/3.", "num:5/6", "3/6 + 2/6 = 5/6. Výsledek je 5/6."},
|
||||
{"Najděte kořeny rovnice x² − 1 = 0. Oddělte je středníkem.", "set:-1;1", "(x − 1)(x + 1) = 0. Napište -1;1."},
|
||||
};
|
||||
|
||||
return math_practice_page(4, 7, "mat4unit8",
|
||||
"mat4_ex8_title", "mat4_quiz8_head",
|
||||
qs, G_N_ELEMENTS(qs));
|
||||
}
|
||||
|
||||
void add_mat4_pages(GtkStack *stack) {
|
||||
typedef GtkWidget *(*MathBuilder)(void);
|
||||
static const struct {
|
||||
const char *unit_name;
|
||||
const char *ex_name;
|
||||
MathBuilder build_unit;
|
||||
MathBuilder build_ex;
|
||||
} pages[] = {
|
||||
{"mat4unit1", "mat4ex1", build_mat4_unit1_page, build_mat4_unit1_exercise_page},
|
||||
{"mat4unit2", "mat4ex2", build_mat4_unit2_page, build_mat4_unit2_exercise_page},
|
||||
{"mat4unit3", "mat4ex3", build_mat4_unit3_page, build_mat4_unit3_exercise_page},
|
||||
{"mat4unit4", "mat4ex4", build_mat4_unit4_page, build_mat4_unit4_exercise_page},
|
||||
{"mat4unit5", "mat4ex5", build_mat4_unit5_page, build_mat4_unit5_exercise_page},
|
||||
{"mat4unit6", "mat4ex6", build_mat4_unit6_page, build_mat4_unit6_exercise_page},
|
||||
{"mat4unit7", "mat4ex7", build_mat4_unit7_page, build_mat4_unit7_exercise_page},
|
||||
{"mat4unit8", "mat4ex8", build_mat4_unit8_page, build_mat4_unit8_exercise_page},
|
||||
};
|
||||
|
||||
for (guint i = 0; i < G_N_ELEMENTS(pages); i++) {
|
||||
gtk_stack_add_named(stack, pages[i].build_unit(), pages[i].unit_name);
|
||||
gtk_stack_add_named(stack, pages[i].build_ex(), pages[i].ex_name);
|
||||
}
|
||||
}
|
||||
955
src/matematika.c
Normal file
955
src/matematika.c
Normal file
|
|
@ -0,0 +1,955 @@
|
|||
#include "graduately.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* Mathematics: elementary review plus four gymnasium years.
|
||||
* Lessons are slides, then typed examples. A few lessons also
|
||||
* ask for a point snapped to the grid. */
|
||||
|
||||
#define MATH_CELL 28.0
|
||||
#define MATH_PAD 18.0
|
||||
#define MATH_GX 10
|
||||
#define MATH_GY 8
|
||||
#define MATH_PTS 16
|
||||
|
||||
typedef struct {
|
||||
const char *map_page;
|
||||
const char *back_page;
|
||||
const char *title_key;
|
||||
const char *sub_key;
|
||||
char **progress_path;
|
||||
NetLesson *lessons;
|
||||
const char *unit_keys[MATH_N];
|
||||
const char *sub_keys[MATH_N];
|
||||
GtkWidget *nodes[MATH_N];
|
||||
int note_last_w;
|
||||
} MathTrack;
|
||||
|
||||
typedef struct {
|
||||
const char *spec;
|
||||
int ux[MATH_PTS];
|
||||
int uy[MATH_PTS];
|
||||
int n;
|
||||
int fx[8];
|
||||
int fy[8];
|
||||
int nf;
|
||||
int need;
|
||||
int checked;
|
||||
int correct;
|
||||
GtkWidget *area;
|
||||
} MathBoard;
|
||||
|
||||
typedef struct {
|
||||
const MathItem *items;
|
||||
int n;
|
||||
MathTrack *track;
|
||||
int lesson_id;
|
||||
GtkWidget *feedback;
|
||||
GtkWidget **entries;
|
||||
MathBoard **boards;
|
||||
GtkWidget **hints;
|
||||
} MathExCtx;
|
||||
|
||||
static MathTrack math_tracks[5];
|
||||
static gboolean math_ready;
|
||||
static char math_key_store[5][MATH_N][32];
|
||||
static char math_sub_store[5][MATH_N][40];
|
||||
|
||||
static void math_ensure(void) {
|
||||
static const char *prefixes[] = {"mat0", "mat", "mat2", "mat3", "mat4"};
|
||||
static const char *maps[] = {
|
||||
"mat0map", "matmap", "mat2map", "mat3map", "mat4map",
|
||||
};
|
||||
static const char *titles[] = {
|
||||
"mat0_year", "mat_year1", "mat_year2", "mat_year3", "mat_year4",
|
||||
};
|
||||
static const char *subs[] = {
|
||||
"mat0_sub", "mat_sub", "mat2_sub", "mat3_sub", "mat4_sub",
|
||||
};
|
||||
NetLesson *lessons[5] = {
|
||||
mat0_lessons, mat_lessons, mat2_lessons, mat3_lessons, mat4_lessons,
|
||||
};
|
||||
char **paths[5] = {
|
||||
&app_progress_mat0, &app_progress_mat, &app_progress_mat2,
|
||||
&app_progress_mat3, &app_progress_mat4,
|
||||
};
|
||||
int y, i;
|
||||
|
||||
if (math_ready)
|
||||
return;
|
||||
for (y = 0; y < 5; y++) {
|
||||
MathTrack *T = &math_tracks[y];
|
||||
|
||||
T->map_page = maps[y];
|
||||
T->back_page = "matyears";
|
||||
T->title_key = titles[y];
|
||||
T->sub_key = subs[y];
|
||||
T->progress_path = paths[y];
|
||||
T->lessons = lessons[y];
|
||||
T->note_last_w = -1;
|
||||
for (i = 0; i < MATH_N; i++) {
|
||||
g_snprintf(math_key_store[y][i], sizeof math_key_store[y][i],
|
||||
"%s_unit%d", prefixes[y], i + 1);
|
||||
g_snprintf(math_sub_store[y][i], sizeof math_sub_store[y][i],
|
||||
"%s_unit%d_sub", prefixes[y], i + 1);
|
||||
T->unit_keys[i] = math_key_store[y][i];
|
||||
T->sub_keys[i] = math_sub_store[y][i];
|
||||
}
|
||||
}
|
||||
math_ready = TRUE;
|
||||
}
|
||||
|
||||
static void math_save(MathTrack *T) {
|
||||
GKeyFile *kf;
|
||||
gchar *data;
|
||||
|
||||
if (!T->progress_path || !*T->progress_path)
|
||||
return;
|
||||
kf = g_key_file_new();
|
||||
for (int i = 0; i < MATH_N; i++) {
|
||||
gchar key[8];
|
||||
|
||||
g_snprintf(key, sizeof key, "%d", i + 1);
|
||||
g_key_file_set_boolean(kf, "done", key, T->lessons[i].done);
|
||||
}
|
||||
if (g_mkdir_with_parents(PROGRESS_DIR, 0755) != 0) {
|
||||
g_key_file_free(kf);
|
||||
return;
|
||||
}
|
||||
data = g_key_file_to_data(kf, NULL, NULL);
|
||||
g_key_file_free(kf);
|
||||
if (data) {
|
||||
GError *err = NULL;
|
||||
|
||||
g_file_set_contents(*T->progress_path, data, -1, &err);
|
||||
if (err)
|
||||
g_error_free(err);
|
||||
g_free(data);
|
||||
}
|
||||
}
|
||||
|
||||
static void math_load_one(MathTrack *T) {
|
||||
GKeyFile *kf;
|
||||
GError *err = NULL;
|
||||
|
||||
if (!T->progress_path || !*T->progress_path)
|
||||
return;
|
||||
kf = g_key_file_new();
|
||||
if (!g_key_file_load_from_file(kf, *T->progress_path, G_KEY_FILE_NONE, &err)) {
|
||||
if (err)
|
||||
g_error_free(err);
|
||||
g_key_file_free(kf);
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < MATH_N; i++) {
|
||||
gchar key[8];
|
||||
|
||||
g_snprintf(key, sizeof key, "%d", i + 1);
|
||||
T->lessons[i].done = g_key_file_get_boolean(kf, "done", key, NULL);
|
||||
}
|
||||
g_key_file_free(kf);
|
||||
}
|
||||
|
||||
void math_load_progress(void) {
|
||||
math_ensure();
|
||||
for (int y = 0; y < 5; y++)
|
||||
math_load_one(&math_tracks[y]);
|
||||
}
|
||||
|
||||
void math_rail_theme_reset(void) {
|
||||
}
|
||||
|
||||
static void math_refresh(MathTrack *T) {
|
||||
for (int i = 0; i < MATH_N; i++) {
|
||||
GtkWidget *node = T->nodes[i];
|
||||
GtkWidget *icon = T->lessons[i].done_icon;
|
||||
|
||||
if (!node)
|
||||
continue;
|
||||
if (T->lessons[i].done) {
|
||||
gtk_widget_remove_css_class(node, "current");
|
||||
gtk_widget_add_css_class(node, "done");
|
||||
if (icon)
|
||||
gtk_widget_set_visible(icon, TRUE);
|
||||
} else {
|
||||
gtk_widget_remove_css_class(node, "done");
|
||||
gtk_widget_add_css_class(node, "current");
|
||||
if (icon)
|
||||
gtk_widget_set_visible(icon, FALSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void math_mark_done(MathTrack *T, int lesson_id) {
|
||||
if (lesson_id < 0 || lesson_id >= MATH_N)
|
||||
return;
|
||||
if (T->lessons[lesson_id].done)
|
||||
return;
|
||||
T->lessons[lesson_id].done = TRUE;
|
||||
math_save(T);
|
||||
math_refresh(T);
|
||||
refresh_stats_ui();
|
||||
}
|
||||
|
||||
void progress_for_math(ProgressSum *out) {
|
||||
if (!out)
|
||||
return;
|
||||
math_ensure();
|
||||
for (int y = 0; y < 5; y++) {
|
||||
MathTrack *T = &math_tracks[y];
|
||||
|
||||
out->total_ex += MATH_N;
|
||||
out->open_units += MATH_N;
|
||||
for (int i = 0; i < MATH_N; i++) {
|
||||
if (T->lessons[i].done) {
|
||||
out->done_ex++;
|
||||
out->done_units++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void math_open_unit(GtkButton *button, gpointer data) {
|
||||
NetLesson *L = data;
|
||||
|
||||
(void)button;
|
||||
if (!L)
|
||||
return;
|
||||
L->idx = 0;
|
||||
if (L->stack) {
|
||||
char name[16];
|
||||
|
||||
g_snprintf(name, sizeof name, "s%u", L->idx);
|
||||
gtk_stack_set_visible_child_name(GTK_STACK(L->stack), name);
|
||||
if (L->prev_btn)
|
||||
gtk_widget_set_sensitive(L->prev_btn, FALSE);
|
||||
if (L->next_btn)
|
||||
gtk_button_set_label(GTK_BUTTON(L->next_btn), tr("net_slide_next"));
|
||||
}
|
||||
gtk_stack_set_visible_child_name(main_stack, L->unit_page);
|
||||
}
|
||||
|
||||
static GtkWidget *math_lesson_button(MathTrack *T, int index) {
|
||||
GtkWidget *btn;
|
||||
GtkWidget *row;
|
||||
GtkWidget *num;
|
||||
GtkWidget *texts;
|
||||
GtkWidget *title;
|
||||
GtkWidget *sub;
|
||||
GtkWidget *icon;
|
||||
char *num_text;
|
||||
|
||||
btn = gtk_button_new();
|
||||
gtk_widget_add_css_class(btn, "unit-node");
|
||||
gtk_widget_add_css_class(btn, "current");
|
||||
gtk_widget_set_can_focus(btn, FALSE);
|
||||
gtk_widget_set_hexpand(btn, TRUE);
|
||||
gtk_widget_set_size_request(btn, -1, 84);
|
||||
|
||||
row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 14);
|
||||
gtk_widget_set_halign(row, GTK_ALIGN_START);
|
||||
gtk_widget_set_valign(row, GTK_ALIGN_CENTER);
|
||||
gtk_widget_set_margin_start(row, 18);
|
||||
gtk_widget_set_margin_end(row, 18);
|
||||
gtk_button_set_child(GTK_BUTTON(btn), row);
|
||||
|
||||
num_text = g_strdup_printf("%d", index + 1);
|
||||
num = gtk_label_new(num_text);
|
||||
g_free(num_text);
|
||||
gtk_widget_add_css_class(num, "unit-number");
|
||||
gtk_widget_set_valign(num, GTK_ALIGN_CENTER);
|
||||
gtk_box_append(GTK_BOX(row), num);
|
||||
|
||||
texts = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
|
||||
gtk_widget_set_halign(texts, GTK_ALIGN_START);
|
||||
gtk_widget_set_hexpand(texts, TRUE);
|
||||
gtk_box_append(GTK_BOX(row), texts);
|
||||
|
||||
title = gtk_label_new(NULL);
|
||||
i18n_bind(title, T->unit_keys[index], 0);
|
||||
gtk_widget_set_halign(title, GTK_ALIGN_START);
|
||||
gtk_widget_add_css_class(title, "unit-name");
|
||||
gtk_box_append(GTK_BOX(texts), title);
|
||||
|
||||
sub = gtk_label_new(NULL);
|
||||
i18n_bind(sub, T->sub_keys[index], 0);
|
||||
gtk_widget_set_halign(sub, GTK_ALIGN_START);
|
||||
gtk_label_set_wrap(GTK_LABEL(sub), TRUE);
|
||||
gtk_label_set_xalign(GTK_LABEL(sub), 0.0);
|
||||
gtk_widget_add_css_class(sub, "hint");
|
||||
gtk_box_append(GTK_BOX(texts), sub);
|
||||
|
||||
icon = icon_area_new(draw_check_icon, 0.1176, 0.1176, 0.1804, 18);
|
||||
gtk_widget_set_visible(icon, FALSE);
|
||||
gtk_widget_set_valign(icon, GTK_ALIGN_CENTER);
|
||||
gtk_box_append(GTK_BOX(row), icon);
|
||||
T->lessons[index].done_icon = icon;
|
||||
T->nodes[index] = btn;
|
||||
g_signal_connect(btn, "clicked", G_CALLBACK(math_open_unit),
|
||||
&T->lessons[index]);
|
||||
return btn;
|
||||
}
|
||||
|
||||
static GtkWidget *math_build_list(MathTrack *T) {
|
||||
GtkWidget *page;
|
||||
GtkWidget *scroll;
|
||||
GtkWidget *list;
|
||||
|
||||
math_ensure();
|
||||
page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
gtk_widget_set_hexpand(page, TRUE);
|
||||
gtk_widget_set_vexpand(page, TRUE);
|
||||
gtk_widget_set_margin_start(page, 32);
|
||||
gtk_widget_set_margin_end(page, 32);
|
||||
gtk_widget_set_margin_top(page, 24);
|
||||
gtk_widget_set_margin_bottom(page, 24);
|
||||
|
||||
gtk_box_append(GTK_BOX(page),
|
||||
top_bar(T->back_page, T->title_key, T->sub_key));
|
||||
|
||||
scroll = gtk_scrolled_window_new();
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
|
||||
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
|
||||
gtk_widget_set_vexpand(scroll, TRUE);
|
||||
gtk_widget_set_margin_top(scroll, 18);
|
||||
gtk_box_append(GTK_BOX(page), scroll);
|
||||
|
||||
list = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
|
||||
gtk_widget_set_halign(list, GTK_ALIGN_CENTER);
|
||||
gtk_widget_set_valign(list, GTK_ALIGN_START);
|
||||
gtk_widget_set_size_request(list, 520, -1);
|
||||
gtk_widget_set_margin_bottom(list, 12);
|
||||
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), list);
|
||||
|
||||
for (int i = 0; i < MATH_N; i++)
|
||||
gtk_box_append(GTK_BOX(list), math_lesson_button(T, i));
|
||||
math_refresh(T);
|
||||
return page;
|
||||
}
|
||||
|
||||
GtkWidget *build_mat0map_page(void) { return math_build_list(&math_tracks[0]); }
|
||||
GtkWidget *build_matmap_page(void) { return math_build_list(&math_tracks[1]); }
|
||||
GtkWidget *build_mat2map_page(void) { return math_build_list(&math_tracks[2]); }
|
||||
GtkWidget *build_mat3map_page(void) { return math_build_list(&math_tracks[3]); }
|
||||
GtkWidget *build_mat4map_page(void) { return math_build_list(&math_tracks[4]); }
|
||||
|
||||
static GtkWidget *math_year_button(const char *badge, const char *target,
|
||||
const char *title_key, const char *sub_key) {
|
||||
GtkWidget *btn;
|
||||
GtkWidget *row;
|
||||
GtkWidget *num;
|
||||
GtkWidget *texts;
|
||||
GtkWidget *title;
|
||||
GtkWidget *sub;
|
||||
|
||||
btn = gtk_button_new();
|
||||
gtk_widget_add_css_class(btn, "unit-node");
|
||||
gtk_widget_add_css_class(btn, "current");
|
||||
gtk_widget_set_can_focus(btn, FALSE);
|
||||
gtk_widget_set_hexpand(btn, TRUE);
|
||||
gtk_widget_set_size_request(btn, -1, 84);
|
||||
|
||||
row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 14);
|
||||
gtk_widget_set_halign(row, GTK_ALIGN_START);
|
||||
gtk_widget_set_valign(row, GTK_ALIGN_CENTER);
|
||||
gtk_widget_set_margin_start(row, 18);
|
||||
gtk_widget_set_margin_end(row, 18);
|
||||
gtk_button_set_child(GTK_BUTTON(btn), row);
|
||||
|
||||
num = gtk_label_new(badge);
|
||||
gtk_widget_add_css_class(num, "unit-number");
|
||||
gtk_widget_set_valign(num, GTK_ALIGN_CENTER);
|
||||
gtk_box_append(GTK_BOX(row), num);
|
||||
|
||||
texts = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
|
||||
gtk_widget_set_halign(texts, GTK_ALIGN_START);
|
||||
gtk_widget_set_hexpand(texts, TRUE);
|
||||
gtk_box_append(GTK_BOX(row), texts);
|
||||
|
||||
title = gtk_label_new(NULL);
|
||||
i18n_bind(title, title_key, 0);
|
||||
gtk_widget_set_halign(title, GTK_ALIGN_START);
|
||||
gtk_widget_add_css_class(title, "unit-name");
|
||||
gtk_box_append(GTK_BOX(texts), title);
|
||||
|
||||
sub = gtk_label_new(NULL);
|
||||
i18n_bind(sub, sub_key, 0);
|
||||
gtk_widget_set_halign(sub, GTK_ALIGN_START);
|
||||
gtk_label_set_wrap(GTK_LABEL(sub), TRUE);
|
||||
gtk_label_set_xalign(GTK_LABEL(sub), 0.0);
|
||||
gtk_widget_add_css_class(sub, "hint");
|
||||
gtk_box_append(GTK_BOX(texts), sub);
|
||||
|
||||
g_object_set_data_full(G_OBJECT(btn), "target", g_strdup(target), g_free);
|
||||
g_signal_connect(btn, "clicked", G_CALLBACK(on_nav_clicked), NULL);
|
||||
return btn;
|
||||
}
|
||||
|
||||
GtkWidget *build_matyears_page(void) {
|
||||
GtkWidget *page;
|
||||
GtkWidget *scroll;
|
||||
GtkWidget *list;
|
||||
static const char *badges[] = {"ZŠ", "1.", "2.", "3.", "4."};
|
||||
static const char *targets[] = {
|
||||
"mat0map", "matmap", "mat2map", "mat3map", "mat4map",
|
||||
};
|
||||
static const char *titles[] = {
|
||||
"mat0_year", "mat_year1", "mat_year2", "mat_year3", "mat_year4",
|
||||
};
|
||||
static const char *subs[] = {
|
||||
"mat0_sub", "mat_sub", "mat2_sub", "mat3_sub", "mat4_sub",
|
||||
};
|
||||
|
||||
page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
gtk_widget_set_hexpand(page, TRUE);
|
||||
gtk_widget_set_vexpand(page, TRUE);
|
||||
gtk_widget_set_margin_start(page, 32);
|
||||
gtk_widget_set_margin_end(page, 32);
|
||||
gtk_widget_set_margin_top(page, 24);
|
||||
gtk_widget_set_margin_bottom(page, 24);
|
||||
|
||||
gtk_box_append(GTK_BOX(page),
|
||||
top_bar("subjects", "Matematika", "mat_years_sub"));
|
||||
|
||||
scroll = gtk_scrolled_window_new();
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
|
||||
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
|
||||
gtk_widget_set_vexpand(scroll, TRUE);
|
||||
gtk_widget_set_margin_top(scroll, 18);
|
||||
gtk_box_append(GTK_BOX(page), scroll);
|
||||
|
||||
list = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
|
||||
gtk_widget_set_halign(list, GTK_ALIGN_CENTER);
|
||||
gtk_widget_set_valign(list, GTK_ALIGN_CENTER);
|
||||
gtk_widget_set_size_request(list, 480, -1);
|
||||
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), list);
|
||||
|
||||
for (int y = 0; y < 5; y++)
|
||||
gtk_box_append(GTK_BOX(list),
|
||||
math_year_button(badges[y], targets[y], titles[y], subs[y]));
|
||||
return page;
|
||||
}
|
||||
|
||||
static void math_slide_apply(NetLesson *L) {
|
||||
char name[16];
|
||||
|
||||
if (!L || !L->stack)
|
||||
return;
|
||||
g_snprintf(name, sizeof name, "s%u", L->idx);
|
||||
gtk_stack_set_visible_child_name(GTK_STACK(L->stack), name);
|
||||
if (L->prev_btn) {
|
||||
gtk_button_set_label(GTK_BUTTON(L->prev_btn), tr("net_slide_prev"));
|
||||
gtk_widget_set_sensitive(L->prev_btn, L->idx > 0);
|
||||
}
|
||||
if (L->next_btn) {
|
||||
gtk_button_set_label(
|
||||
GTK_BUTTON(L->next_btn),
|
||||
L->idx + 1 >= L->n_slides ? tr("net_slide_start")
|
||||
: tr("net_slide_next"));
|
||||
}
|
||||
}
|
||||
|
||||
static void math_slide_prev(GtkButton *button, gpointer data) {
|
||||
NetLesson *L = data;
|
||||
|
||||
(void)button;
|
||||
if (!L || L->idx == 0)
|
||||
return;
|
||||
L->idx--;
|
||||
math_slide_apply(L);
|
||||
}
|
||||
|
||||
static void math_slide_next(GtkButton *button, gpointer data) {
|
||||
NetLesson *L = data;
|
||||
|
||||
(void)button;
|
||||
if (!L)
|
||||
return;
|
||||
if (L->idx + 1 < L->n_slides) {
|
||||
L->idx++;
|
||||
math_slide_apply(L);
|
||||
} else {
|
||||
gtk_stack_set_visible_child_name(main_stack, L->ex_page);
|
||||
}
|
||||
}
|
||||
|
||||
static void math_rescale(MathTrack *T, int w) {
|
||||
int body, head, kick;
|
||||
|
||||
if (w < 160)
|
||||
w = 160;
|
||||
body = w / 46;
|
||||
if (body < 14)
|
||||
body = 14;
|
||||
if (body > 26)
|
||||
body = 26;
|
||||
head = body + 14;
|
||||
if (head > 46)
|
||||
head = 46;
|
||||
kick = body - 3;
|
||||
if (kick < 11)
|
||||
kick = 11;
|
||||
for (int i = 0; i < MATH_N; i++)
|
||||
net_rescale_lesson_notes(&T->lessons[i], body, head, kick);
|
||||
}
|
||||
|
||||
static gboolean math_note_tick(GtkWidget *w, GdkFrameClock *clock, gpointer data) {
|
||||
MathTrack *T = data;
|
||||
int width = gtk_widget_get_allocated_width(w);
|
||||
|
||||
(void)clock;
|
||||
if (width != T->note_last_w) {
|
||||
T->note_last_w = width;
|
||||
math_rescale(T, width);
|
||||
}
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
GtkWidget *math_unit_page(int year, NetLesson *L, const char *title_key,
|
||||
const char *sub_key, const NetSlide *slides,
|
||||
guint n_slides) {
|
||||
MathTrack *T;
|
||||
GtkWidget *page;
|
||||
GtkWidget *scroll;
|
||||
GtkWidget *nav;
|
||||
|
||||
math_ensure();
|
||||
if (year < 0 || year > 4)
|
||||
year = 0;
|
||||
T = &math_tracks[year];
|
||||
|
||||
net_lesson_notes_ensure(L);
|
||||
net_notes_target = L;
|
||||
L->n_slides = n_slides;
|
||||
|
||||
page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
gtk_widget_set_margin_start(page, 28);
|
||||
gtk_widget_set_margin_end(page, 28);
|
||||
gtk_widget_set_margin_top(page, 20);
|
||||
gtk_widget_set_margin_bottom(page, 20);
|
||||
|
||||
gtk_box_append(GTK_BOX(page), top_bar(T->map_page, title_key, sub_key));
|
||||
|
||||
scroll = gtk_scrolled_window_new();
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
|
||||
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
|
||||
gtk_widget_set_vexpand(scroll, TRUE);
|
||||
gtk_widget_set_margin_top(scroll, 10);
|
||||
gtk_box_append(GTK_BOX(page), scroll);
|
||||
|
||||
L->stack = gtk_stack_new();
|
||||
gtk_stack_set_transition_type(GTK_STACK(L->stack),
|
||||
GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT);
|
||||
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), L->stack);
|
||||
|
||||
for (guint s = 0; s < n_slides; s++) {
|
||||
GtkWidget *stage = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
GtkWidget *card;
|
||||
char name[16];
|
||||
|
||||
gtk_widget_set_vexpand(stage, TRUE);
|
||||
g_snprintf(name, sizeof name, "s%u", s);
|
||||
gtk_stack_add_named(GTK_STACK(L->stack), stage, name);
|
||||
|
||||
card = gtk_box_new(GTK_ORIENTATION_VERTICAL, 16);
|
||||
gtk_widget_add_css_class(card, "notes-card");
|
||||
gtk_widget_set_valign(card, GTK_ALIGN_CENTER);
|
||||
gtk_box_append(GTK_BOX(stage), card);
|
||||
|
||||
net_note_kicker(card, slides[s].kicker);
|
||||
net_note_title(card, slides[s].title);
|
||||
for (int i = 0; i < 8 && slides[s].line[i]; i++)
|
||||
net_note_line(card, slides[s].line[i], FALSE);
|
||||
if (slides[s].tip)
|
||||
net_note_line(card, slides[s].tip, TRUE);
|
||||
}
|
||||
|
||||
gtk_widget_add_tick_callback(L->stack, math_note_tick, T, NULL);
|
||||
T->note_last_w = -1;
|
||||
math_rescale(T, 0);
|
||||
net_notes_target = NULL;
|
||||
|
||||
nav = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10);
|
||||
gtk_box_set_homogeneous(GTK_BOX(nav), TRUE);
|
||||
gtk_widget_set_margin_top(nav, 10);
|
||||
gtk_box_append(GTK_BOX(page), nav);
|
||||
|
||||
L->prev_btn = gtk_button_new_with_label(tr("net_slide_prev"));
|
||||
gtk_widget_add_css_class(L->prev_btn, "btn-primary");
|
||||
g_signal_connect(L->prev_btn, "clicked", G_CALLBACK(math_slide_prev), L);
|
||||
gtk_box_append(GTK_BOX(nav), L->prev_btn);
|
||||
|
||||
L->next_btn = gtk_button_new_with_label(tr("net_slide_next"));
|
||||
gtk_widget_add_css_class(L->next_btn, "btn-primary");
|
||||
g_signal_connect(L->next_btn, "clicked", G_CALLBACK(math_slide_next), L);
|
||||
gtk_box_append(GTK_BOX(nav), L->next_btn);
|
||||
|
||||
L->idx = 0;
|
||||
math_slide_apply(L);
|
||||
return page;
|
||||
}
|
||||
|
||||
void math_lessons_apply_lang(void) {
|
||||
math_ensure();
|
||||
for (int y = 0; y < 5; y++) {
|
||||
for (int i = 0; i < MATH_N; i++)
|
||||
math_slide_apply(&math_tracks[y].lessons[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void paint_hex(cairo_t *cr, unsigned hex) {
|
||||
cairo_set_source_rgb(cr, ((hex >> 16) & 255) / 255.0,
|
||||
((hex >> 8) & 255) / 255.0, (hex & 255) / 255.0);
|
||||
}
|
||||
|
||||
static int board_px_w(void) {
|
||||
return (int)(MATH_PAD * 2 + MATH_GX * MATH_CELL);
|
||||
}
|
||||
|
||||
static int board_px_h(void) {
|
||||
return (int)(MATH_PAD * 2 + MATH_GY * MATH_CELL);
|
||||
}
|
||||
|
||||
static void board_to_screen(int gx, int gy, double *sx, double *sy) {
|
||||
*sx = MATH_PAD + gx * MATH_CELL;
|
||||
*sy = board_px_h() - MATH_PAD - gy * MATH_CELL;
|
||||
}
|
||||
|
||||
static gboolean board_snap(double x, double y, int *gx, int *gy) {
|
||||
double cx = (x - MATH_PAD) / MATH_CELL;
|
||||
double cy = (board_px_h() - MATH_PAD - y) / MATH_CELL;
|
||||
int ix = (int)lround(cx);
|
||||
int iy = (int)lround(cy);
|
||||
|
||||
if (ix < 0 || ix > MATH_GX || iy < 0 || iy > MATH_GY)
|
||||
return FALSE;
|
||||
if (fabs(cx - ix) > 0.45 || fabs(cy - iy) > 0.45)
|
||||
return FALSE;
|
||||
*gx = ix;
|
||||
*gy = iy;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void board_draw(GtkDrawingArea *area, cairo_t *cr, int width, int height,
|
||||
gpointer data) {
|
||||
MathBoard *b = data;
|
||||
int i;
|
||||
static const char *names[] = {"A", "B", "C", "D"};
|
||||
|
||||
(void)area;
|
||||
(void)width;
|
||||
(void)height;
|
||||
paint_hex(cr, app_theme.surface1);
|
||||
cairo_rectangle(cr, 0, 0, board_px_w(), board_px_h());
|
||||
cairo_fill(cr);
|
||||
|
||||
cairo_set_line_width(cr, 1);
|
||||
cairo_set_source_rgba(cr,
|
||||
((app_theme.subtext >> 16) & 255) / 255.0,
|
||||
((app_theme.subtext >> 8) & 255) / 255.0,
|
||||
(app_theme.subtext & 255) / 255.0, 0.55);
|
||||
for (i = 0; i <= MATH_GX; i++) {
|
||||
double x = MATH_PAD + i * MATH_CELL;
|
||||
|
||||
cairo_move_to(cr, x, MATH_PAD);
|
||||
cairo_line_to(cr, x, board_px_h() - MATH_PAD);
|
||||
}
|
||||
for (i = 0; i <= MATH_GY; i++) {
|
||||
double y = MATH_PAD + i * MATH_CELL;
|
||||
|
||||
cairo_move_to(cr, MATH_PAD, y);
|
||||
cairo_line_to(cr, board_px_w() - MATH_PAD, y);
|
||||
}
|
||||
cairo_stroke(cr);
|
||||
|
||||
paint_hex(cr, app_theme.text);
|
||||
cairo_set_line_width(cr, 1.6);
|
||||
cairo_move_to(cr, MATH_PAD, board_px_h() - MATH_PAD);
|
||||
cairo_line_to(cr, board_px_w() - MATH_PAD, board_px_h() - MATH_PAD);
|
||||
cairo_move_to(cr, MATH_PAD, board_px_h() - MATH_PAD);
|
||||
cairo_line_to(cr, MATH_PAD, MATH_PAD);
|
||||
cairo_stroke(cr);
|
||||
|
||||
if (b->nf >= 2) {
|
||||
double x0, y0, x1, y1;
|
||||
|
||||
paint_hex(cr, app_theme.accent);
|
||||
cairo_set_line_width(cr, 2);
|
||||
board_to_screen(b->fx[0], b->fy[0], &x0, &y0);
|
||||
board_to_screen(b->fx[1], b->fy[1], &x1, &y1);
|
||||
cairo_move_to(cr, x0, y0);
|
||||
cairo_line_to(cr, x1, y1);
|
||||
if (b->nf >= 3) {
|
||||
double x2, y2;
|
||||
|
||||
board_to_screen(b->fx[2], b->fy[2], &x2, &y2);
|
||||
cairo_move_to(cr, x0, y0);
|
||||
cairo_line_to(cr, x2, y2);
|
||||
}
|
||||
cairo_stroke(cr);
|
||||
}
|
||||
|
||||
for (i = 0; i < b->nf && i < 4; i++) {
|
||||
double x, y;
|
||||
|
||||
board_to_screen(b->fx[i], b->fy[i], &x, &y);
|
||||
paint_hex(cr, app_theme.accent);
|
||||
cairo_arc(cr, x, y, 5.5, 0, 2 * G_PI);
|
||||
cairo_fill(cr);
|
||||
paint_hex(cr, app_theme.text);
|
||||
cairo_set_font_size(cr, 13);
|
||||
cairo_move_to(cr, x + 7, y - 7);
|
||||
cairo_show_text(cr, names[i]);
|
||||
}
|
||||
for (i = 0; i < b->n; i++) {
|
||||
double x, y;
|
||||
char label[8];
|
||||
|
||||
board_to_screen(b->ux[i], b->uy[i], &x, &y);
|
||||
if (b->checked)
|
||||
paint_hex(cr, b->correct ? app_theme.success : app_theme.error);
|
||||
else
|
||||
paint_hex(cr, app_theme.success);
|
||||
cairo_arc(cr, x, y, 5.5, 0, 2 * G_PI);
|
||||
cairo_fill(cr);
|
||||
paint_hex(cr, app_theme.text);
|
||||
g_snprintf(label, sizeof label, "%d", i + 1);
|
||||
cairo_set_font_size(cr, 13);
|
||||
cairo_move_to(cr, x + 7, y + 14);
|
||||
cairo_show_text(cr, label);
|
||||
}
|
||||
}
|
||||
|
||||
static int board_has_fixed(MathBoard *b, int x, int y) {
|
||||
for (int i = 0; i < b->nf; i++)
|
||||
if (b->fx[i] == x && b->fy[i] == y)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void board_pressed(GtkGestureClick *gesture, int n_press, double x, double y,
|
||||
gpointer data) {
|
||||
MathBoard *b = data;
|
||||
int gx, gy, i;
|
||||
|
||||
(void)gesture;
|
||||
(void)n_press;
|
||||
if (!board_snap(x, y, &gx, &gy) || board_has_fixed(b, gx, gy))
|
||||
return;
|
||||
for (i = 0; i < b->n; i++) {
|
||||
if (b->ux[i] == gx && b->uy[i] == gy) {
|
||||
memmove(&b->ux[i], &b->ux[i + 1], (b->n - i - 1) * sizeof(int));
|
||||
memmove(&b->uy[i], &b->uy[i + 1], (b->n - i - 1) * sizeof(int));
|
||||
b->n--;
|
||||
b->checked = 0;
|
||||
gtk_widget_queue_draw(b->area);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (b->n >= b->need || b->n >= MATH_PTS)
|
||||
return;
|
||||
b->ux[b->n] = gx;
|
||||
b->uy[b->n] = gy;
|
||||
b->n++;
|
||||
b->checked = 0;
|
||||
gtk_widget_queue_draw(b->area);
|
||||
}
|
||||
|
||||
static void board_undo(GtkButton *button, gpointer data) {
|
||||
MathBoard *b = data;
|
||||
|
||||
(void)button;
|
||||
if (b->n <= 0)
|
||||
return;
|
||||
b->n--;
|
||||
b->checked = 0;
|
||||
gtk_widget_queue_draw(b->area);
|
||||
}
|
||||
|
||||
static void board_clear(GtkButton *button, gpointer data) {
|
||||
MathBoard *b = data;
|
||||
|
||||
(void)button;
|
||||
b->n = 0;
|
||||
b->checked = 0;
|
||||
gtk_widget_queue_draw(b->area);
|
||||
}
|
||||
|
||||
static void math_ex_check(GtkButton *button, gpointer data) {
|
||||
MathExCtx *ctx = data;
|
||||
int ok = 0;
|
||||
|
||||
(void)button;
|
||||
for (int i = 0; i < ctx->n; i++) {
|
||||
int good = 0;
|
||||
|
||||
if (ctx->boards[i]) {
|
||||
MathBoard *b = ctx->boards[i];
|
||||
|
||||
good = math_draw_ok(b->spec, b->ux, b->uy, b->n);
|
||||
b->checked = 1;
|
||||
b->correct = good;
|
||||
gtk_widget_queue_draw(b->area);
|
||||
} else if (ctx->entries[i]) {
|
||||
const char *text = gtk_editable_get_text(GTK_EDITABLE(ctx->entries[i]));
|
||||
|
||||
gtk_widget_remove_css_class(ctx->entries[i], "ok");
|
||||
gtk_widget_remove_css_class(ctx->entries[i], "wrong");
|
||||
good = math_answer_ok(ctx->items[i].answer, text);
|
||||
gtk_widget_add_css_class(ctx->entries[i], good ? "ok" : "wrong");
|
||||
}
|
||||
if (good)
|
||||
ok++;
|
||||
if (ctx->hints[i])
|
||||
gtk_widget_set_visible(ctx->hints[i], TRUE);
|
||||
}
|
||||
if (ok == ctx->n) {
|
||||
set_feedback(ctx->feedback, TRUE, tr("feedback_ok"));
|
||||
math_mark_done(ctx->track, ctx->lesson_id);
|
||||
} else {
|
||||
set_feedback(ctx->feedback, FALSE, tr("feedback_retry_short"));
|
||||
}
|
||||
}
|
||||
|
||||
GtkWidget *math_practice_page(int year, int lesson_id, const char *back_page,
|
||||
const char *title_key, const char *heading_key,
|
||||
const MathItem *items, int n) {
|
||||
GtkWidget *page;
|
||||
GtkWidget *scroll;
|
||||
GtkWidget *body;
|
||||
GtkWidget *check;
|
||||
GtkWidget *head;
|
||||
GtkWidget *intro;
|
||||
MathExCtx *ctx = g_new0(MathExCtx, 1);
|
||||
|
||||
math_ensure();
|
||||
if (year < 0 || year > 4)
|
||||
year = 0;
|
||||
|
||||
page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
gtk_widget_set_margin_start(page, 32);
|
||||
gtk_widget_set_margin_end(page, 32);
|
||||
gtk_widget_set_margin_top(page, 24);
|
||||
gtk_widget_set_margin_bottom(page, 24);
|
||||
|
||||
gtk_box_append(GTK_BOX(page), top_bar(back_page, title_key, NULL));
|
||||
|
||||
scroll = gtk_scrolled_window_new();
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
|
||||
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
|
||||
gtk_widget_set_vexpand(scroll, TRUE);
|
||||
gtk_widget_set_margin_top(scroll, 12);
|
||||
gtk_box_append(GTK_BOX(page), scroll);
|
||||
|
||||
body = gtk_box_new(GTK_ORIENTATION_VERTICAL, 16);
|
||||
gtk_widget_set_hexpand(body, TRUE);
|
||||
gtk_widget_set_margin_start(body, 4);
|
||||
gtk_widget_set_margin_end(body, 4);
|
||||
gtk_widget_set_margin_bottom(body, 12);
|
||||
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), body);
|
||||
|
||||
head = gtk_label_new(NULL);
|
||||
i18n_bind(head, heading_key, 0);
|
||||
gtk_widget_set_halign(head, GTK_ALIGN_START);
|
||||
gtk_label_set_wrap(GTK_LABEL(head), TRUE);
|
||||
gtk_widget_add_css_class(head, "quiz-heading");
|
||||
gtk_box_append(GTK_BOX(body), head);
|
||||
|
||||
intro = gtk_label_new(NULL);
|
||||
i18n_bind(intro, "math_quiz_intro", 0);
|
||||
gtk_widget_set_halign(intro, GTK_ALIGN_START);
|
||||
gtk_label_set_wrap(GTK_LABEL(intro), TRUE);
|
||||
gtk_label_set_xalign(GTK_LABEL(intro), 0);
|
||||
gtk_widget_add_css_class(intro, "quiz-intro");
|
||||
gtk_box_append(GTK_BOX(body), intro);
|
||||
|
||||
ctx->items = items;
|
||||
ctx->n = n;
|
||||
ctx->track = &math_tracks[year];
|
||||
ctx->lesson_id = lesson_id;
|
||||
ctx->entries = g_new0(GtkWidget *, n);
|
||||
ctx->boards = g_new0(MathBoard *, n);
|
||||
ctx->hints = g_new0(GtkWidget *, n);
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
GtkWidget *card = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
|
||||
GtkWidget *prompt = gtk_label_new(items[i].prompt);
|
||||
GtkWidget *hint = gtk_label_new(items[i].hint);
|
||||
|
||||
gtk_widget_set_halign(card, GTK_ALIGN_START);
|
||||
gtk_box_append(GTK_BOX(body), card);
|
||||
gtk_label_set_wrap(GTK_LABEL(prompt), TRUE);
|
||||
gtk_label_set_xalign(GTK_LABEL(prompt), 0);
|
||||
gtk_widget_set_halign(prompt, GTK_ALIGN_START);
|
||||
gtk_box_append(GTK_BOX(card), prompt);
|
||||
|
||||
if (math_is_draw(items[i].answer)) {
|
||||
MathBoard *b = g_new0(MathBoard, 1);
|
||||
GtkWidget *area = gtk_drawing_area_new();
|
||||
GtkGesture *click = gtk_gesture_click_new();
|
||||
GtkWidget *tools = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
|
||||
GtkWidget *undo = gtk_button_new();
|
||||
GtkWidget *clear = gtk_button_new();
|
||||
GtkWidget *note = gtk_label_new(NULL);
|
||||
|
||||
b->spec = items[i].answer;
|
||||
b->need = math_draw_need(items[i].answer);
|
||||
if (b->need < 1)
|
||||
b->need = 1;
|
||||
b->nf = math_draw_fixed(items[i].answer, b->fx, b->fy, 8);
|
||||
b->area = area;
|
||||
ctx->boards[i] = b;
|
||||
|
||||
gtk_widget_set_size_request(area, board_px_w(), board_px_h());
|
||||
gtk_widget_set_halign(area, GTK_ALIGN_START);
|
||||
gtk_drawing_area_set_content_width(GTK_DRAWING_AREA(area), board_px_w());
|
||||
gtk_drawing_area_set_content_height(GTK_DRAWING_AREA(area), board_px_h());
|
||||
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(area), board_draw, b, NULL);
|
||||
gtk_widget_add_controller(area, GTK_EVENT_CONTROLLER(click));
|
||||
g_signal_connect(click, "pressed", G_CALLBACK(board_pressed), b);
|
||||
gtk_box_append(GTK_BOX(card), area);
|
||||
|
||||
i18n_bind(note, "math_draw_hint", 0);
|
||||
gtk_label_set_wrap(GTK_LABEL(note), TRUE);
|
||||
gtk_widget_add_css_class(note, "hint");
|
||||
gtk_widget_set_halign(note, GTK_ALIGN_START);
|
||||
gtk_box_append(GTK_BOX(card), note);
|
||||
|
||||
i18n_bind(undo, "math_undo", 1);
|
||||
i18n_bind(clear, "math_clear", 1);
|
||||
g_signal_connect(undo, "clicked", G_CALLBACK(board_undo), b);
|
||||
g_signal_connect(clear, "clicked", G_CALLBACK(board_clear), b);
|
||||
gtk_box_append(GTK_BOX(tools), undo);
|
||||
gtk_box_append(GTK_BOX(tools), clear);
|
||||
gtk_box_append(GTK_BOX(card), tools);
|
||||
} else {
|
||||
GtkWidget *entry = gtk_entry_new();
|
||||
|
||||
gtk_entry_set_placeholder_text(GTK_ENTRY(entry), tr("math_answer_ph"));
|
||||
gtk_widget_set_hexpand(entry, TRUE);
|
||||
gtk_widget_set_size_request(entry, 280, -1);
|
||||
gtk_box_append(GTK_BOX(card), entry);
|
||||
ctx->entries[i] = entry;
|
||||
}
|
||||
|
||||
gtk_label_set_wrap(GTK_LABEL(hint), TRUE);
|
||||
gtk_label_set_xalign(GTK_LABEL(hint), 0);
|
||||
gtk_widget_set_halign(hint, GTK_ALIGN_START);
|
||||
gtk_widget_add_css_class(hint, "hint");
|
||||
gtk_widget_set_visible(hint, FALSE);
|
||||
gtk_box_append(GTK_BOX(card), hint);
|
||||
ctx->hints[i] = hint;
|
||||
}
|
||||
|
||||
check = gtk_button_new();
|
||||
i18n_bind(check, "check", 1);
|
||||
gtk_widget_add_css_class(check, "btn-primary");
|
||||
gtk_widget_set_halign(check, GTK_ALIGN_START);
|
||||
gtk_box_append(GTK_BOX(body), check);
|
||||
g_signal_connect(check, "clicked", G_CALLBACK(math_ex_check), ctx);
|
||||
|
||||
ctx->feedback = gtk_label_new("");
|
||||
gtk_widget_set_halign(ctx->feedback, GTK_ALIGN_START);
|
||||
gtk_box_append(GTK_BOX(body), ctx->feedback);
|
||||
return page;
|
||||
}
|
||||
1362
src/math_check.c
Normal file
1362
src/math_check.c
Normal file
File diff suppressed because it is too large
Load diff
176
src/math_i18n.inc
Normal file
176
src/math_i18n.inc
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
{"mat_years_sub", "Opakování základní školy a čtyři ročníky gymnázia.", "Elementary review and four years of gymnasium mathematics."},
|
||||
{"math_quiz_intro", "Napište výsledek. Zlomky jako 3/4, desetinnou čárku i tečku, π jako pi, odmocninu jako sqrt(2) nebo √2. Více čísel oddělte středníkem.", "Write the result. Fractions as 3/4, a decimal comma or point, π as pi, a root as sqrt(2) or √2. Separate several numbers with a semicolon."},
|
||||
{"math_answer_ph", "výsledek", "result"},
|
||||
{"math_undo", "Zpět", "Undo"},
|
||||
{"math_clear", "Smazat body", "Clear points"},
|
||||
{"math_draw_hint", "Klepněte na mřížku. Osa y směřuje nahoru.", "Tap the grid. The y-axis points up."},
|
||||
{"mat0_year", "Opakování základní školy", "Elementary-school review"},
|
||||
{"mat0_sub", "Pořadí operací, zlomky, procenta, rovnice a rýsování.", "Order of operations, fractions, percentages, equations and a construction."},
|
||||
{"mat0_unit1", "Pořadí operací", "Order of operations"},
|
||||
{"mat0_unit1_sub", "Závorky, násobení a dělení, sčítání a odčítání.", "Brackets, then multiplication and division, then addition and subtraction."},
|
||||
{"mat0_ex1_title", "Příklady", "Practice"},
|
||||
{"mat0_quiz1_head", "Pořadí operací", "Order of operations"},
|
||||
{"mat0_unit2", "Zlomky", "Fractions"},
|
||||
{"mat0_unit2_sub", "Sčítání, odčítání, násobení a dělení zlomků.", "Adding, subtracting, multiplying and dividing fractions."},
|
||||
{"mat0_ex2_title", "Příklady", "Practice"},
|
||||
{"mat0_quiz2_head", "Zlomky", "Fractions"},
|
||||
{"mat0_unit3", "Desetinná čísla", "Decimals"},
|
||||
{"mat0_unit3_sub", "Sčítání, násobení a dělení desetinných čísel.", "Adding, multiplying and dividing decimals."},
|
||||
{"mat0_ex3_title", "Příklady", "Practice"},
|
||||
{"mat0_quiz3_head", "Desetinná čísla", "Decimals"},
|
||||
{"mat0_unit4", "Procenta", "Percentages"},
|
||||
{"mat0_unit4_sub", "Procento ze základu, počet procent a cena po slevě.", "A percentage of a base, the rate, and a price after a discount."},
|
||||
{"mat0_ex4_title", "Příklady", "Practice"},
|
||||
{"mat0_quiz4_head", "Procenta", "Percentages"},
|
||||
{"mat0_unit5", "Poměr a měřítko", "Ratio and scale"},
|
||||
{"mat0_unit5_sub", "Dělení v poměru, úměra a měřítko mapy.", "Sharing in a ratio, proportion and the scale of a map."},
|
||||
{"mat0_ex5_title", "Příklady", "Practice"},
|
||||
{"mat0_quiz5_head", "Poměr a měřítko", "Ratio and scale"},
|
||||
{"mat0_unit6", "Záporná čísla", "Negative numbers"},
|
||||
{"mat0_unit6_sub", "Sčítání, násobení a absolutní hodnota.", "Addition, multiplication and absolute value."},
|
||||
{"mat0_ex6_title", "Příklady", "Practice"},
|
||||
{"mat0_quiz6_head", "Záporná čísla", "Negative numbers"},
|
||||
{"mat0_unit7", "Lineární rovnice", "Linear equations"},
|
||||
{"mat0_unit7_sub", "Ekvivalentní úpravy a jednoduché rovnice.", "Equivalent steps and simple equations."},
|
||||
{"mat0_ex7_title", "Příklady", "Practice"},
|
||||
{"mat0_quiz7_head", "Lineární rovnice", "Linear equations"},
|
||||
{"mat0_unit8", "Obvod, obsah a čtverec", "Perimeter, area and a square"},
|
||||
{"mat0_unit8_sub", "Obdélník, trojúhelník a doplnění čtverce na mřížce.", "A rectangle, a triangle, and completing a square on the grid."},
|
||||
{"mat0_ex8_title", "Příklady", "Practice"},
|
||||
{"mat0_quiz8_head", "Obvod, obsah a čtverec", "Perimeter, area and a square"},
|
||||
{"mat_year1", "1. ročník", "Year 1"},
|
||||
{"mat_sub", "Dělitelnost, výrazy, rovnice, planimetrie a konstrukce.", "Divisibility, expressions, equations, plane figures and a construction."},
|
||||
{"mat_unit1", "Dělitelnost a prvočísla", "Divisibility and primes"},
|
||||
{"mat_unit1_sub", "Největší společný dělitel, násobek a rozklad.", "Highest common factor, lowest common multiple and factorisation."},
|
||||
{"mat_ex1_title", "Příklady", "Practice"},
|
||||
{"mat_quiz1_head", "Dělitelnost a prvočísla", "Divisibility and primes"},
|
||||
{"mat_unit2", "Mocniny a odmocniny", "Powers and roots"},
|
||||
{"mat_unit2_sub", "Mocniny se stejným základem a druhá odmocnina.", "Powers with the same base and the square root."},
|
||||
{"mat_ex2_title", "Příklady", "Practice"},
|
||||
{"mat_quiz2_head", "Mocniny a odmocniny", "Powers and roots"},
|
||||
{"mat_unit3", "Algebraické výrazy", "Algebraic expressions"},
|
||||
{"mat_unit3_sub", "Dosazení, druhá mocnina součtu a rozklad rozdílu čtverců.", "Substitution, the square of a sum and a difference of squares."},
|
||||
{"mat_ex3_title", "Příklady", "Practice"},
|
||||
{"mat_quiz3_head", "Algebraické výrazy", "Algebraic expressions"},
|
||||
{"mat_unit4", "Lineární rovnice s úpravami", "Linear equations"},
|
||||
{"mat_unit4_sub", "Rovnice s koeficientem, závorkou a zlomkem.", "Equations with a coefficient, a bracket and a fraction."},
|
||||
{"mat_ex4_title", "Příklady", "Practice"},
|
||||
{"mat_quiz4_head", "Lineární rovnice s úpravami", "Linear equations"},
|
||||
{"mat_unit5", "Slovní úlohy", "Word problems"},
|
||||
{"mat_unit5_sub", "Dráha, společná práce, směs a sleva.", "Distance, joint work, a mixture and a discount."},
|
||||
{"mat_ex5_title", "Příklady", "Practice"},
|
||||
{"mat_quiz5_head", "Slovní úlohy", "Word problems"},
|
||||
{"mat_unit6", "Planimetrie", "Plane figures"},
|
||||
{"mat_unit6_sub", "Úhly trojúhelníku, vnější úhel a pravidelný mnohoúhelník.", "Angles of a triangle, the exterior angle and a regular polygon."},
|
||||
{"mat_ex6_title", "Příklady", "Practice"},
|
||||
{"mat_quiz6_head", "Planimetrie", "Plane figures"},
|
||||
{"mat_unit7", "Obvody a obsahy", "Perimeter and area"},
|
||||
{"mat_unit7_sub", "Kružnice, lichoběžník a Pythagorova věta.", "The circle, the trapezium and Pythagoras."},
|
||||
{"mat_ex7_title", "Příklady", "Practice"},
|
||||
{"mat_quiz7_head", "Obvody a obsahy", "Perimeter and area"},
|
||||
{"mat_unit8", "Konstrukce trojúhelníku", "Constructing a triangle"},
|
||||
{"mat_unit8_sub", "Vzdálenost bodů a trojúhelník daný třemi stranami.", "The distance between points and a triangle given by three sides."},
|
||||
{"mat_ex8_title", "Příklady", "Practice"},
|
||||
{"mat_quiz8_head", "Konstrukce trojúhelníku", "Constructing a triangle"},
|
||||
{"mat_year2", "2. ročník", "Year 2"},
|
||||
{"mat2_sub", "Funkce, soustavy, kvadratické rovnice, goniometrie a kružnice.", "Functions, systems, quadratic equations, trigonometry and circles."},
|
||||
{"mat2_unit1", "Lineární funkce", "Linear functions"},
|
||||
{"mat2_unit1_sub", "Předpis, průsečík s osou y, směrnice a nulový bod.", "The formula, the y-intercept, the slope and a root."},
|
||||
{"mat2_ex1_title", "Příklady", "Practice"},
|
||||
{"mat2_quiz1_head", "Lineární funkce", "Linear functions"},
|
||||
{"mat2_unit2", "Soustavy rovnic", "Systems of equations"},
|
||||
{"mat2_unit2_sub", "Dvě lineární rovnice o dvou neznámých.", "Two linear equations in two unknowns."},
|
||||
{"mat2_ex2_title", "Příklady", "Practice"},
|
||||
{"mat2_quiz2_head", "Soustavy rovnic", "Systems of equations"},
|
||||
{"mat2_unit3", "Kvadratické rovnice", "Quadratic equations"},
|
||||
{"mat2_unit3_sub", "Rozklad, diskriminant a dvojnásobný kořen.", "Factoring, the discriminant and a double root."},
|
||||
{"mat2_ex3_title", "Příklady", "Practice"},
|
||||
{"mat2_quiz3_head", "Kvadratické rovnice", "Quadratic equations"},
|
||||
{"mat2_unit4", "Lomené výrazy", "Rational expressions"},
|
||||
{"mat2_unit4_sub", "Krácení, definiční obor a násobení zlomků s proměnnou.", "Cancelling, the domain and multiplying algebraic fractions."},
|
||||
{"mat2_ex4_title", "Příklady", "Practice"},
|
||||
{"mat2_quiz4_head", "Lomené výrazy", "Rational expressions"},
|
||||
{"mat2_unit5", "Mocniny s racionálním exponentem", "Rational exponents"},
|
||||
{"mat2_unit5_sub", "Odmocniny, součin mocnin a záporný exponent.", "Roots, a product of powers and a negative exponent."},
|
||||
{"mat2_ex5_title", "Příklady", "Practice"},
|
||||
{"mat2_quiz5_head", "Mocniny s racionálním exponentem", "Rational exponents"},
|
||||
{"mat2_unit6", "Goniometrie pravoúhlého trojúhelníku", "Right-triangle trigonometry"},
|
||||
{"mat2_unit6_sub", "Sinus, kosinus a tangens ostrého úhlu.", "Sine, cosine and tangent of an acute angle."},
|
||||
{"mat2_ex6_title", "Příklady", "Practice"},
|
||||
{"mat2_quiz6_head", "Goniometrie pravoúhlého trojúhelníku", "Right-triangle trigonometry"},
|
||||
{"mat2_unit7", "Pythagorova a Eukleidovy věty", "Pythagoras and Euclid"},
|
||||
{"mat2_unit7_sub", "Přepona, obsah čtverce nad ní a úseky přepony.", "The hypotenuse, the square on it, and the projections on it."},
|
||||
{"mat2_ex7_title", "Příklady", "Practice"},
|
||||
{"mat2_quiz7_head", "Pythagorova a Eukleidovy věty", "Pythagoras and Euclid"},
|
||||
{"mat2_unit8", "Kružnice a Thaletova věta", "Circles and Thales"},
|
||||
{"mat2_unit8_sub", "Obvodový a středový úhel, délka oblouku a pravý úhel nad průměrem.", "An inscribed and a central angle, arc length, and the right angle in a semicircle."},
|
||||
{"mat2_ex8_title", "Příklady", "Practice"},
|
||||
{"mat2_quiz8_head", "Kružnice a Thaletova věta", "Circles and Thales"},
|
||||
{"mat_year3", "3. ročník", "Year 3"},
|
||||
{"mat3_sub", "Logaritmus, posloupnosti, pravděpodobnost a stereometrie.", "Logarithms, sequences, probability and solid geometry."},
|
||||
{"mat3_unit1", "Kvadratická funkce", "The quadratic function"},
|
||||
{"mat3_unit1_sub", "Vrchol, kořeny a hodnota paraboly.", "The vertex, the roots and a value of a parabola."},
|
||||
{"mat3_ex1_title", "Příklady", "Practice"},
|
||||
{"mat3_quiz1_head", "Kvadratická funkce", "The quadratic function"},
|
||||
{"mat3_unit2", "Exponenciální funkce a logaritmus", "Exponentials and logarithms"},
|
||||
{"mat3_unit2_sub", "Mocnina se základem a logaritmus jako její opak.", "A power and the logarithm as its inverse."},
|
||||
{"mat3_ex2_title", "Příklady", "Practice"},
|
||||
{"mat3_quiz2_head", "Exponenciální funkce a logaritmus", "Exponentials and logarithms"},
|
||||
{"mat3_unit3", "Goniometrické funkce", "Trigonometric functions"},
|
||||
{"mat3_unit3_sub", "Sinus a kosinus na jednotkové kružnici.", "Sine and cosine on the unit circle."},
|
||||
{"mat3_ex3_title", "Příklady", "Practice"},
|
||||
{"mat3_quiz3_head", "Goniometrické funkce", "Trigonometric functions"},
|
||||
{"mat3_unit4", "Sinová a kosinová věta", "The sine and cosine rules"},
|
||||
{"mat3_unit4_sub", "Obecný trojúhelník, poloměr kružnice a obsah.", "A general triangle, the circumradius and the area."},
|
||||
{"mat3_ex4_title", "Příklady", "Practice"},
|
||||
{"mat3_quiz4_head", "Sinová a kosinová věta", "The sine and cosine rules"},
|
||||
{"mat3_unit5", "Posloupnosti a řady", "Sequences and series"},
|
||||
{"mat3_unit5_sub", "Aritmetická a geometrická posloupnost a nekonečná řada.", "An arithmetic and a geometric sequence, and an infinite series."},
|
||||
{"mat3_ex5_title", "Příklady", "Practice"},
|
||||
{"mat3_quiz5_head", "Posloupnosti a řady", "Sequences and series"},
|
||||
{"mat3_unit6", "Kombinatorika", "Combinatorics"},
|
||||
{"mat3_unit6_sub", "Permutace, variace a kombinace.", "Permutations, variations and combinations."},
|
||||
{"mat3_ex6_title", "Příklady", "Practice"},
|
||||
{"mat3_quiz6_head", "Kombinatorika", "Combinatorics"},
|
||||
{"mat3_unit7", "Pravděpodobnost", "Probability"},
|
||||
{"mat3_unit7_sub", "Klasická pravděpodobnost, kostka, mince a karty.", "Classical probability with a die, a coin and cards."},
|
||||
{"mat3_ex7_title", "Příklady", "Practice"},
|
||||
{"mat3_quiz7_head", "Pravděpodobnost", "Probability"},
|
||||
{"mat3_unit8", "Stereometrie", "Solid geometry"},
|
||||
{"mat3_unit8_sub", "Krychle, válec, koule a jehlan.", "The cube, the cylinder, the sphere and a pyramid."},
|
||||
{"mat3_ex8_title", "Příklady", "Practice"},
|
||||
{"mat3_quiz8_head", "Stereometrie", "Solid geometry"},
|
||||
{"mat_year4", "4. ročník", "Year 4"},
|
||||
{"mat4_sub", "Analytická geometrie, derivace, integrál a maturitní opakování.", "Coordinate geometry, derivatives, integrals and a maturita review."},
|
||||
{"mat4_unit1", "Vektory", "Vectors"},
|
||||
{"mat4_unit1_sub", "Velikost, součet, skalární součin a koncový bod.", "Magnitude, a sum, the dot product and an endpoint."},
|
||||
{"mat4_ex1_title", "Příklady", "Practice"},
|
||||
{"mat4_quiz1_head", "Vektory", "Vectors"},
|
||||
{"mat4_unit2", "Analytická přímka", "The line in the plane"},
|
||||
{"mat4_unit2_sub", "Směrnice, úsek, parametrické vyjádření a vzdálenost od přímky.", "Slope, intercept, a parametric equation and the distance from a line."},
|
||||
{"mat4_ex2_title", "Příklady", "Practice"},
|
||||
{"mat4_quiz2_head", "Analytická přímka", "The line in the plane"},
|
||||
{"mat4_unit3", "Kuželosečky", "Conic sections"},
|
||||
{"mat4_unit3_sub", "Kružnice a elipsa v osové poloze.", "A circle and an ellipse aligned with the axes."},
|
||||
{"mat4_ex3_title", "Příklady", "Practice"},
|
||||
{"mat4_quiz3_head", "Kuželosečky", "Conic sections"},
|
||||
{"mat4_unit4", "Komplexní čísla", "Complex numbers"},
|
||||
{"mat4_unit4_sub", "Součet, součin, absolutní hodnota a mocnina i.", "A sum, a product, the modulus and a power of i."},
|
||||
{"mat4_ex4_title", "Příklady", "Practice"},
|
||||
{"mat4_quiz4_head", "Komplexní čísla", "Complex numbers"},
|
||||
{"mat4_unit5", "Derivace", "The derivative"},
|
||||
{"mat4_unit5_sub", "Derivace mocniny, tečna a derivace sinu.", "The derivative of a power, a tangent and the derivative of sine."},
|
||||
{"mat4_ex5_title", "Příklady", "Practice"},
|
||||
{"mat4_quiz5_head", "Derivace", "The derivative"},
|
||||
{"mat4_unit6", "Průběh funkce", "The shape of a function"},
|
||||
{"mat4_unit6_sub", "Stacionární body, lokální extrémy a nulová derivace.", "Stationary points, local extrema and a zero derivative."},
|
||||
{"mat4_ex6_title", "Příklady", "Practice"},
|
||||
{"mat4_quiz6_head", "Průběh funkce", "The shape of a function"},
|
||||
{"mat4_unit7", "Integrál", "The integral"},
|
||||
{"mat4_unit7_sub", "Určitý integrál mocniny jako obsah pod grafem.", "The definite integral of a power as the area under the graph."},
|
||||
{"mat4_ex7_title", "Příklady", "Practice"},
|
||||
{"mat4_quiz7_head", "Integrál", "The integral"},
|
||||
{"mat4_unit8", "Maturitní mix", "Maturita mix"},
|
||||
{"mat4_unit8_sub", "Logaritmus, kombinace, vektor, zlomek a kvadratická rovnice.", "A logarithm, a combination, a vector, a fraction and a quadratic equation."},
|
||||
{"mat4_ex8_title", "Příklady", "Practice"},
|
||||
{"mat4_quiz8_head", "Maturitní mix", "Maturita mix"},
|
||||
|
|
@ -61,6 +61,7 @@ void load_progress(void) {
|
|||
lit_load_progress();
|
||||
sci_load_progress();
|
||||
fyz_load_progress();
|
||||
math_load_progress();
|
||||
}
|
||||
|
||||
/* Redraw the exercise rails of every unit that already has a page. */
|
||||
|
|
|
|||
45
src/search.c
45
src/search.c
|
|
@ -23,7 +23,7 @@ static GPtrArray *search_items;
|
|||
|
||||
static gboolean subject_open(int i) {
|
||||
return i == 0 || i == NET_SUBJ || i == HW_SUBJ || i == CZ_SUBJ
|
||||
|| i == ON_SUBJ || i == PHY_SUBJ || i == SCI_SUBJ;
|
||||
|| i == ON_SUBJ || i == MATH_SUBJ || i == PHY_SUBJ || i == SCI_SUBJ;
|
||||
}
|
||||
|
||||
static const char *subject_page(int i) {
|
||||
|
|
@ -37,6 +37,8 @@ static const char *subject_page(int i) {
|
|||
return "czechmap";
|
||||
if (i == ON_SUBJ)
|
||||
return "onyears";
|
||||
if (i == MATH_SUBJ)
|
||||
return "matyears";
|
||||
if (i == PHY_SUBJ)
|
||||
return "fyzyears";
|
||||
if (i == SCI_SUBJ)
|
||||
|
|
@ -275,6 +277,47 @@ static void catalog_rebuild(void) {
|
|||
g_snprintf(page, sizeof(page), "lit4ex%d", i);
|
||||
catalog_add(tr(key), tr("search_exercise"), page, FALSE, 3, "kviz quiz");
|
||||
}
|
||||
catalog_add_key("Matematika", "search_lesson", "matyears", FALSE, 1, "matematika");
|
||||
catalog_add_key("mat0_year", "search_lesson", "mat0map", FALSE, 1, "matematika zakladka");
|
||||
catalog_add_key("mat_year1", "search_lesson", "matmap", FALSE, 1, "matematika rocnik");
|
||||
catalog_add_key("mat_year2", "search_lesson", "mat2map", FALSE, 1, "matematika rocnik");
|
||||
catalog_add_key("mat_year3", "search_lesson", "mat3map", FALSE, 1, "matematika rocnik");
|
||||
catalog_add_key("mat_year4", "search_lesson", "mat4map", FALSE, 1, "matematika rocnik");
|
||||
for (i = 1; i <= MATH_N; i++) {
|
||||
g_snprintf(key, sizeof(key), "mat0_unit%d", i);
|
||||
g_snprintf(page, sizeof(page), "mat0unit%d", i);
|
||||
catalog_add_key(key, "search_lesson", page, FALSE, 2, "matematika");
|
||||
g_snprintf(page, sizeof(page), "mat0ex%d", i);
|
||||
catalog_add(tr(key), tr("search_exercise"), page, FALSE, 3, "priklady");
|
||||
}
|
||||
for (i = 1; i <= MATH_N; i++) {
|
||||
g_snprintf(key, sizeof(key), "mat_unit%d", i);
|
||||
g_snprintf(page, sizeof(page), "matunit%d", i);
|
||||
catalog_add_key(key, "search_lesson", page, FALSE, 2, "matematika");
|
||||
g_snprintf(page, sizeof(page), "matex%d", i);
|
||||
catalog_add(tr(key), tr("search_exercise"), page, FALSE, 3, "priklady");
|
||||
}
|
||||
for (i = 1; i <= MATH_N; i++) {
|
||||
g_snprintf(key, sizeof(key), "mat2_unit%d", i);
|
||||
g_snprintf(page, sizeof(page), "mat2unit%d", i);
|
||||
catalog_add_key(key, "search_lesson", page, FALSE, 2, "matematika");
|
||||
g_snprintf(page, sizeof(page), "mat2ex%d", i);
|
||||
catalog_add(tr(key), tr("search_exercise"), page, FALSE, 3, "priklady");
|
||||
}
|
||||
for (i = 1; i <= MATH_N; i++) {
|
||||
g_snprintf(key, sizeof(key), "mat3_unit%d", i);
|
||||
g_snprintf(page, sizeof(page), "mat3unit%d", i);
|
||||
catalog_add_key(key, "search_lesson", page, FALSE, 2, "matematika");
|
||||
g_snprintf(page, sizeof(page), "mat3ex%d", i);
|
||||
catalog_add(tr(key), tr("search_exercise"), page, FALSE, 3, "priklady");
|
||||
}
|
||||
for (i = 1; i <= MATH_N; i++) {
|
||||
g_snprintf(key, sizeof(key), "mat4_unit%d", i);
|
||||
g_snprintf(page, sizeof(page), "mat4unit%d", i);
|
||||
catalog_add_key(key, "search_lesson", page, FALSE, 2, "matematika");
|
||||
g_snprintf(page, sizeof(page), "mat4ex%d", i);
|
||||
catalog_add(tr(key), tr("search_exercise"), page, FALSE, 3, "priklady");
|
||||
}
|
||||
catalog_add_key("fyz_year1", "search_lesson", "fyzmap", FALSE, 1, "fyzika rocnik");
|
||||
catalog_add_key("fyz_year2", "search_lesson", "fyz2map", FALSE, 1, "fyzika rocnik");
|
||||
catalog_add_key("fyz_year3", "search_lesson", "fyz3map", FALSE, 1, "fyzika rocnik");
|
||||
|
|
|
|||
|
|
@ -79,6 +79,8 @@ void refresh_stats_ui(void) {
|
|||
progress_for_sci(&all);
|
||||
else if (s == PHY_SUBJ)
|
||||
progress_for_fyz(&all);
|
||||
else if (s == MATH_SUBJ)
|
||||
progress_for_math(&all);
|
||||
else
|
||||
progress_for_units(sub_unit_start[s], sub_unit_count[s], &all);
|
||||
}
|
||||
|
|
@ -110,6 +112,7 @@ void refresh_stats_ui(void) {
|
|||
gboolean has_content = (s == NET_SUBJ) || (s == HW_SUBJ)
|
||||
|| (s == ON_SUBJ) || (s == CZ_SUBJ)
|
||||
|| (s == SCI_SUBJ) || (s == PHY_SUBJ)
|
||||
|| (s == MATH_SUBJ)
|
||||
|| sub_unit_count[s] > 0;
|
||||
|
||||
if (!count || !bar)
|
||||
|
|
@ -128,6 +131,8 @@ void refresh_stats_ui(void) {
|
|||
progress_for_sci(&sp);
|
||||
else if (s == PHY_SUBJ)
|
||||
progress_for_fyz(&sp);
|
||||
else if (s == MATH_SUBJ)
|
||||
progress_for_math(&sp);
|
||||
else
|
||||
progress_for_units(sub_unit_start[s], sub_unit_count[s], &sp);
|
||||
if (has_content) {
|
||||
|
|
@ -399,6 +404,7 @@ GtkWidget *build_stats_page(void) {
|
|||
gboolean has_units = (s == NET_SUBJ) || (s == HW_SUBJ)
|
||||
|| (s == CZ_SUBJ) || (s == ON_SUBJ)
|
||||
|| (s == SCI_SUBJ) || (s == PHY_SUBJ)
|
||||
|| (s == MATH_SUBJ)
|
||||
|| sub_unit_count[s] > 0;
|
||||
gboolean has_deutsch_units = sub_unit_count[s] > 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/* Index 0 (Deutsch), NET_SUBJ (networks), HW_SUBJ (hardware), CZ_SUBJ
|
||||
* (Czech), ON_SUBJ (civics), PHY_SUBJ (physics) and SCI_SUBJ
|
||||
* (Czech), ON_SUBJ (civics), MATH_SUBJ (mathematics), PHY_SUBJ
|
||||
* (physics) and SCI_SUBJ
|
||||
* (natural sciences) are unlocked. */
|
||||
const char *sub_keys[NUM_SUBJECTS] = {
|
||||
"Deutsch",
|
||||
|
|
@ -155,7 +156,7 @@ void draw_czech_flag(GtkDrawingArea *area, cairo_t *cr,
|
|||
|
||||
static gboolean subject_is_open(int i) {
|
||||
return i == 0 || i == NET_SUBJ || i == HW_SUBJ || i == CZ_SUBJ
|
||||
|| i == ON_SUBJ || i == PHY_SUBJ || i == SCI_SUBJ;
|
||||
|| i == ON_SUBJ || i == MATH_SUBJ || i == PHY_SUBJ || i == SCI_SUBJ;
|
||||
}
|
||||
|
||||
static GtkWidget *subject_icon(int i) {
|
||||
|
|
@ -178,6 +179,9 @@ static GtkWidget *subject_icon(int i) {
|
|||
else if (i == ON_SUBJ)
|
||||
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(area),
|
||||
draw_civics_icon, NULL, NULL);
|
||||
else if (i == MATH_SUBJ)
|
||||
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(area),
|
||||
draw_math_icon, NULL, NULL);
|
||||
else if (i == PHY_SUBJ)
|
||||
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(area),
|
||||
draw_physics_icon, NULL, NULL);
|
||||
|
|
@ -200,6 +204,8 @@ static const char *subject_target(int i) {
|
|||
return "czechmap";
|
||||
if (i == ON_SUBJ)
|
||||
return "onyears";
|
||||
if (i == MATH_SUBJ)
|
||||
return "matyears";
|
||||
if (i == PHY_SUBJ)
|
||||
return "fyzyears";
|
||||
if (i == SCI_SUBJ)
|
||||
|
|
|
|||
|
|
@ -434,5 +434,6 @@ void apply_theme(void) {
|
|||
lit_rail_theme_reset();
|
||||
sci_rail_theme_reset();
|
||||
fyz_rail_theme_reset();
|
||||
math_rail_theme_reset();
|
||||
book_rail_theme_reset();
|
||||
}
|
||||
|
|
|
|||
15
src/util.c
15
src/util.c
|
|
@ -36,6 +36,11 @@ char *app_progress_fyz;
|
|||
char *app_progress_fyz2;
|
||||
char *app_progress_fyz3;
|
||||
char *app_progress_fyz4;
|
||||
char *app_progress_mat0;
|
||||
char *app_progress_mat;
|
||||
char *app_progress_mat2;
|
||||
char *app_progress_mat3;
|
||||
char *app_progress_mat4;
|
||||
|
||||
static char *dup_dirname(const char *path) {
|
||||
char *tmp = g_strdup(path);
|
||||
|
|
@ -188,6 +193,11 @@ static void set_progress_paths(const char *write_root) {
|
|||
g_free(app_progress_fyz2);
|
||||
g_free(app_progress_fyz3);
|
||||
g_free(app_progress_fyz4);
|
||||
g_free(app_progress_mat0);
|
||||
g_free(app_progress_mat);
|
||||
g_free(app_progress_mat2);
|
||||
g_free(app_progress_mat3);
|
||||
g_free(app_progress_mat4);
|
||||
|
||||
app_progress_dir = g_build_filename(write_root, PROGRESS_DIR_NAME, NULL);
|
||||
app_progress_u1 = g_build_filename(app_progress_dir, "unit1.conf", NULL);
|
||||
|
|
@ -212,6 +222,11 @@ static void set_progress_paths(const char *write_root) {
|
|||
app_progress_fyz2 = g_build_filename(app_progress_dir, "fyz2.conf", NULL);
|
||||
app_progress_fyz3 = g_build_filename(app_progress_dir, "fyz3.conf", NULL);
|
||||
app_progress_fyz4 = g_build_filename(app_progress_dir, "fyz4.conf", NULL);
|
||||
app_progress_mat0 = g_build_filename(app_progress_dir, "mat0.conf", NULL);
|
||||
app_progress_mat = g_build_filename(app_progress_dir, "mat.conf", NULL);
|
||||
app_progress_mat2 = g_build_filename(app_progress_dir, "mat2.conf", NULL);
|
||||
app_progress_mat3 = g_build_filename(app_progress_dir, "mat3.conf", NULL);
|
||||
app_progress_mat4 = g_build_filename(app_progress_dir, "mat4.conf", NULL);
|
||||
}
|
||||
|
||||
/* Resolve read-only asset root, chdir there, and point progress at a writable
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue