Add networks unit 6: path, channel and circuit with quiz.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
pepaskopec-blip 2026-09-11 18:04:36 +02:00
commit 454cb9d40c
4 changed files with 202 additions and 2 deletions

18
i18n.c
View file

@ -223,6 +223,12 @@ const TrEntry tr_ui[] = {
"4 slides about signals and modulation, then a quiz."},
{"net_ex5_title", "Kvíz: signály a modulace",
"Quiz: signals and modulation"},
{"net_unit6", "Cesta, kanál a okruh",
"Path, channel and circuit"},
{"net_unit6_sub", "4 snímky o cestě, kanálu a okruhu a pak kvíz.",
"4 slides about path, channel and circuit, then a quiz."},
{"net_ex6_title", "Kvíz: cesta, kanál a okruh",
"Quiz: path, channel and circuit"},
{"net_slide_next", "Další", "Next"},
{"net_slide_prev", "Zpět", "Back"},
{"net_slide_start", "Přejít na cvičení", "Go to the exercise"},
@ -321,6 +327,18 @@ const TrEntry tr_content[] = {
"Bit rate = amount of information per second [bit/s]"},
{"Šířka pásma určuje maximální možnou přenosovou rychlost", NULL,
"Bandwidth determines the maximum possible bit rate"},
{"Datový spoj umožňuje výměnu informací mezi uzly", NULL,
"A data link enables information exchange between nodes"},
{"Fyzické médium: kabely, mikrovlny nebo družice", NULL,
"Physical medium: cables, microwaves or satellites"},
{"Kanál je jednosměrný souhrn prostředků pro spojení", NULL,
"A channel is a one-way set of means for a connection"},
{"Okruh tvoří dva kanály pro obousměrný přenos", NULL,
"A circuit is made of two channels for two-way transfer"},
{"Trunking spojuje více cest do jednoho okruhu", NULL,
"Trunking joins multiple paths into one circuit"},
{"Trunking zvyšuje šířku pásma spojením cest", NULL,
"Trunking increases bandwidth by combining paths"},
{"čtrnáct", NULL, "fourteen"},
{"sedmnáct", NULL, "seventeen"},
{"dvacet", NULL, "twenty"},

2
main.c
View file

@ -181,6 +181,8 @@ void activate(GtkApplication *app, gpointer user_data) {
gtk_stack_add_named(main_stack, build_net_unit4_exercise_page(), "netex4");
gtk_stack_add_named(main_stack, build_net_unit5_page(), "netunit5");
gtk_stack_add_named(main_stack, build_net_unit5_exercise_page(), "netex5");
gtk_stack_add_named(main_stack, build_net_unit6_page(), "netunit6");
gtk_stack_add_named(main_stack, build_net_unit6_exercise_page(), "netex6");
for (int i = 0; i < NUM_UNLOCKED; i++) {
UnitCtx *u = &units[i];

View file

@ -50,7 +50,8 @@
#define NET3_SLIDES 4
#define NET4_SLIDES 4
#define NET5_SLIDES 4
#define NET_LESSONS 5
#define NET6_SLIDES 4
#define NET_LESSONS 6
typedef enum {
THEME_CATPPUCCIN = 0,
@ -1013,6 +1014,7 @@ GtkWidget *build_net_unit2_page(void);
GtkWidget *build_net_unit3_page(void);
GtkWidget *build_net_unit4_page(void);
GtkWidget *build_net_unit5_page(void);
GtkWidget *build_net_unit6_page(void);
GtkWidget *net_qz_combo(void);
void net_qz_set_prefix(GtkComboBoxText *c, int pfx);
int net_qz_prefix(GtkComboBoxText *c);
@ -1028,6 +1030,7 @@ GtkWidget *build_net_unit2_exercise_page(void);
GtkWidget *build_net_unit3_exercise_page(void);
GtkWidget *build_net_unit4_exercise_page(void);
GtkWidget *build_net_unit5_exercise_page(void);
GtkWidget *build_net_unit6_exercise_page(void);
void activate(GtkApplication *app, gpointer user_data);
int main(int argc, char **argv);

179
net.c
View file

@ -9,6 +9,7 @@ NetLesson net_lessons[NET_LESSONS] = {
{ .n_slides = NET3_SLIDES, .unit_page = "netunit3", .ex_page = "netex3" },
{ .n_slides = NET4_SLIDES, .unit_page = "netunit4", .ex_page = "netex4" },
{ .n_slides = NET5_SLIDES, .unit_page = "netunit5", .ex_page = "netex5" },
{ .n_slides = NET6_SLIDES, .unit_page = "netunit6", .ex_page = "netex6" },
};
void net_paragraph(GtkWidget *box, const char *text) {
@ -435,7 +436,8 @@ void net_add_node(GtkFixed *fixed, int index) {
GtkWidget *name;
char *text;
static const char *unit_keys[NET_LESSONS] = {
"net_unit1", "net_unit2", "net_unit3", "net_unit4", "net_unit5"
"net_unit1", "net_unit2", "net_unit3", "net_unit4", "net_unit5",
"net_unit6"
};
card = gtk_button_new();
@ -912,6 +914,50 @@ GtkWidget *build_net_unit5_page(void) {
return build_net_unit_page(&net_lessons[4], "net_unit5", slides, NET5_SLIDES);
}
GtkWidget *build_net_unit6_page(void) {
static const NetSlide slides[NET6_SLIDES] = {
{
"1 / 4 • Spoj", "Datový spoj",
NULL,
{
"Datový spoj umožňuje výměnu informací.",
"Může být dvoubodový nebo vícebodový.",
NULL,
},
},
{
"2 / 4 • Cesta", "Přenosová cesta",
NULL,
{
"Přenosová cesta je fyzické médium.",
"Příklady: metalické nebo optické kabely,",
"mikrovlny, družice.",
NULL,
},
},
{
"3 / 4 • Kanál a okruh", "Přenosový kanál a okruh",
"Tip: okruh = dva kanály → obousměrný přenos",
{
"Přenosový kanál: jednosměrný souhrn prostředků pro spojení.",
"Okruh: tvořen dvěma kanály – umožňuje obousměrný přenos.",
NULL,
},
},
{
"4 / 4 • Trunking", "Spojování cest",
NULL,
{
"Trunking spojuje více cest do jednoho okruhu.",
"Cíl: zvýšení šířky pásma.",
NULL,
},
},
};
return build_net_unit_page(&net_lessons[5], "net_unit6", slides, NET6_SLIDES);
}
/* Solutions below are the canonical "largest subnet first, in order A–D"
* allocation. Scenario 3 does not fit into a /24 as written, which is why
* its "solution" explains that instead. */
@ -1832,3 +1878,134 @@ GtkWidget *build_net_unit5_exercise_page(void) {
return page;
}
/* ---- Unit 6: path, channel and circuit quiz ------------------------- */
const ChoiceQ net6_qs[] = {
{"Datový spoj slouží k:",
{"Jen měření amplitudy", "Výměně informací", "Jen FM modulaci",
"Jen tisku dokumentů"}, 4, 1},
{"Přenosová cesta je:",
{"Jen softwarová služba", "Fyzické médium přenosu",
"Jen CRC kód", "Jen peer‑to‑peer účet"}, 4, 1},
{"Mezi přenosová média patří:",
{"Jen parita", "Kabely, mikrovlny a družice", "Jen Baud",
"Jen start-bit"}, 4, 1},
{"Přenosový kanál je:",
{"Obousměrný vždy", "Jednosměrný souhrn prostředků pro spojení",
"Jen název serveru", "Jen typ QPSK"}, 4, 1},
{"Okruh vzniká:",
{"Z jednoho kanálu", "Ze dvou kanálů pro obousměrný přenos",
"Jen z parity", "Jen z Wi‑Fi hesla"}, 4, 1},
{"Trunking znamená:",
{"Odpojení všech kabelů", "Spojení více cest do jednoho okruhu",
"Jen asynchronní stop-bit", "Jen digitální 0 a 1"}, 4, 1},
{"Hlavní cíl trunkingu je:",
{"Snížit šířku pásma", "Zvýšit šířku pásma", "Zrušit kanály",
"Nahradit mainframe"}, 4, 1},
};
const char *net6_hints[] = {
"Datový spoj umožňuje výměnu informací mezi uzly",
"Fyzické médium: kabely, mikrovlny nebo družice",
"Fyzické médium: kabely, mikrovlny nebo družice",
"Kanál je jednosměrný souhrn prostředků pro spojení",
"Okruh tvoří dva kanály pro obousměrný přenos",
"Trunking spojuje více cest do jednoho okruhu",
"Trunking zvyšuje šířku pásma spojením cest",
};
GtkWidget *build_net_unit6_exercise_page(void) {
GtkWidget *page;
GtkWidget *scroll;
GtkWidget *body;
GtkWidget *check;
GtkWidget *btns;
NetMcqCtx *ctx = g_new0(NetMcqCtx, 1);
int n = (int)G_N_ELEMENTS(net6_qs);
int n_opts = net6_qs[0].n_options;
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("netunit6", "net_ex6_title", 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, 10);
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), body);
net_heading(body, "Kvíz k cestě, kanálu a okruhu");
net_paragraph(body,
"Vyberte u každé otázky jednu správnou odpověď "
"a stiskněte Zkontrolovat.");
ctx->qs = net6_qs;
ctx->n = n;
ctx->n_opts = n_opts;
ctx->toggles = g_new0(GtkToggleButton *, n * n_opts);
ctx->hints = g_new0(GtkWidget *, n);
for (int i = 0; i < n; i++) {
GtkWidget *prompt;
GtkWidget *row;
GtkToggleButton *first = NULL;
char *qtext;
qtext = g_strdup_printf("%d.) %s", i + 1, net6_qs[i].prompt);
prompt = gtk_label_new(qtext);
g_free(qtext);
gtk_widget_set_halign(prompt, GTK_ALIGN_START);
gtk_label_set_wrap(GTK_LABEL(prompt), TRUE);
gtk_widget_add_css_class(prompt, "ex-prompt");
gtk_widget_set_margin_top(prompt, 8);
gtk_box_append(GTK_BOX(body), prompt);
row = gtk_flow_box_new();
gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(row), GTK_SELECTION_NONE);
gtk_flow_box_set_min_children_per_line(GTK_FLOW_BOX(row), n_opts);
gtk_flow_box_set_max_children_per_line(GTK_FLOW_BOX(row), n_opts);
gtk_widget_set_halign(row, GTK_ALIGN_START);
gtk_box_append(GTK_BOX(body), row);
for (int o = 0; o < n_opts; o++) {
GtkWidget *tb =
gtk_toggle_button_new_with_label(net6_qs[i].options[o]);
gtk_widget_add_css_class(tb, "pill");
gtk_toggle_button_set_group(GTK_TOGGLE_BUTTON(tb), first);
if (!first)
first = GTK_TOGGLE_BUTTON(tb);
gtk_flow_box_append(GTK_FLOW_BOX(row), tb);
ctx->toggles[i * n_opts + o] = GTK_TOGGLE_BUTTON(tb);
}
ctx->hints[i] = meaning_add(body, net6_hints[i]);
}
btns = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
gtk_widget_set_margin_top(btns, 12);
gtk_box_append(GTK_BOX(body), btns);
check = gtk_button_new();
i18n_bind(check, "check", 1);
gtk_widget_add_css_class(check, "pill");
gtk_widget_set_halign(check, GTK_ALIGN_START);
gtk_box_append(GTK_BOX(btns), check);
g_signal_connect(check, "clicked", G_CALLBACK(net_mcq_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;
}