From a57c4923fe1c5b1bf419053fb24cc07b3943f802 Mon Sep 17 00:00:00 2001 From: dvorakmortonxd Date: Fri, 11 Sep 2026 22:08:45 +0200 Subject: [PATCH] made vocabulary exercise just a bit more tolerant --- exercise_common.c | 33 +++++++++++++++++++++++++++++++++ exercise_u1.c | 35 ++++++++++++++++++++++++----------- i18n.c | 1 + maturita.h | 2 ++ style.css | 5 +++++ util.c | 10 ++++++++++ 6 files changed, 75 insertions(+), 11 deletions(-) diff --git a/exercise_common.c b/exercise_common.c index d3ddc7e..5a2bfc5 100644 --- a/exercise_common.c +++ b/exercise_common.c @@ -155,6 +155,39 @@ gboolean answer_accepts(const char *sel_norm, const char *ans) { return ok; } +/* True when the typed answer matches the beginning of an accepted answer but + * is missing one or more trailing words (e.g. "jmenovat" for "jmenovat se"). + * Used to warn about an incomplete answer before counting it wrong. */ +gboolean answer_is_incomplete(const char *sel_norm, const char *ans) { + gchar **parts = g_strsplit(ans, "|", -1); + gboolean incomplete = FALSE; + + for (int i = 0; parts[i] != NULL && !incomplete; i++) { + gchar *a = normalize_answer(parts[i]); + gchar **sel_w = g_strsplit(sel_norm, " ", -1); + gchar **ans_w = g_strsplit(a, " ", -1); + int nsel = g_strv_length(sel_w); + int nans = g_strv_length(ans_w); + int k; + + if (sel_norm[0] && nsel > 0 && nsel < nans) { + incomplete = TRUE; + for (k = 0; k < nsel; k++) { + if (g_strcmp0(sel_w[k], ans_w[k]) != 0) { + incomplete = FALSE; + break; + } + } + } + g_strfreev(sel_w); + g_strfreev(ans_w); + g_free(a); + } + + g_strfreev(parts); + return incomplete; +} + void combo_list_check(GtkButton *button, gpointer data) { ComboListCtx *ctx = data; guint total = ctx->combos->len; diff --git a/exercise_u1.c b/exercise_u1.c index c093f79..c3e5858 100644 --- a/exercise_u1.c +++ b/exercise_u1.c @@ -1062,7 +1062,7 @@ const TypedQ trans_p13[] = { {"das Alter", "věk|age", "věk"}, {"auch", "také|taky|also|too", "také"}, {"aus", "z|ze|from", "z"}, - {"die Band, -s", "skupina|kapela|band", "(hudební) skupina"}, + {"die Band, -s", "skupina|hudební skupina|kapela|hudební kapela|band", "(hudební) skupina"}, {"chatten (Infinitiv)", "chatovat|chatuje|to chat", "chatovat"}, {"das Computerspiel, -e", "počítačová hra|computer game", "počítačová hra"}, {"die/das E-Mail, -s", "e-mail|email", "e-mail"}, @@ -1079,7 +1079,7 @@ const TypedQ trans_p13[] = { "Je fotbalový fanatik."}, {"gehen (Infinitiv)", "jít|jde|chodit|to go", "jít"}, {"Ich gehe gern auf Konzerte.", - "rád chodím na koncerty|ráda chodím na koncerty|i like going to concerts", + "rád chodím na koncerty|ráda chodím na koncerty|chodím rád na koncerty|chodím ráda na koncerty|i like going to concerts", "Rád(a) chodím na koncerty."}, {"gern", "rád|ráda|gladly", "rád/a"}, {"die Gitarre, -n", "kytara|guitar", "kytara"}, @@ -1096,7 +1096,7 @@ const TypedQ trans_p13[] = { {"kein/keine/kein", "žádný|žádná|žádné|no|none", "žádný/žádná/žádné"}, {"das Klavier, -e", "klavír|piano", "klavír"}, {"kommen (Infinitiv, aus)", - "přicházet|přijít|přichází|pochází|to come|to come from", + "přicházet|přijít|přichází|pocházet|pochází|to come|to come from", "přicházet, pocházet (z)"}, {"Er kommt aus Wien.", "pochází z vídně|he comes from vienna|he is from vienna", @@ -1119,30 +1119,30 @@ const TypedQ trans_p13[] = { }; const TypedQ trans_p14[] = { - {"falsch", "nesprávně|chybně|špatně|wrong|incorrect", "nesprávně, chybně"}, + {"falsch", "nesprávně|chybně|špatně|nepravda|wrong|incorrect", "nesprávně, chybně"}, {"die Flöte, -n", "flétna|flute", "flétna"}, {"haben (Infinitiv)", "mít|má|to have", "mít"}, - {"das Instrument, -e", "nástroj|instrument", "(hudební) nástroj"}, + {"das Instrument, -e", "nástroj|hudební nástroj|instrument", "(hudební) nástroj"}, {"jetzt", "teď|nyní|now", "teď"}, {"machen (Infinitiv)", "dělat|dělá|to do|to make", "dělat"}, {"richtig", "správně|správný|correct|right", "správně"}, {"der Sport", "sport", "sport"}, {"Sport treiben (Infinitiv)", - "sportovat|sportuje|provozovat sport|to do sport", "sportovat"}, + "sportovat|sportuje|provozovat sport|dělat sport|to do sport", "sportovat"}, {"das Squash", "squash", "squash"}, {"das/der Yoga", "jóga|yoga", "jóga"}, }; const TypedQ trans_p15[] = { - {"bald", "brzy|soon", "brzy"}, - {"Bis bald!", "brzy na shledanou|brzy se uvidíme|see you soon", + {"bald", "brzy|brzo|soon", "brzy"}, + {"Bis bald!", "brzy na shledanou|brzo na shledanou|brzy se uvidíme|brzo se uvidíme|see you soon", "Brzy na shledanou!"}, {"bis", "do|až|until", "do"}, {"Bis zum nächsten Mal!", "do příštího setkání|do příště|until next time", "Do příštího setkání!"}, - {"die Frau, -en", "paní|žena|woman|mrs", "paní"}, + {"die Frau, -en", "paní|žena|manželka|woman|mrs|wife", "paní"}, {"der Herr, -en", "pan|muž|man|mr", "pan"}, - {"Mach's gut!", "měj se hezky|měj se|take care", "Měj se hezky!"}, + {"Mach's gut!", "měj se hezky|měj se|mějte se hezky|mějte se|take care", "Měj se hezky!"}, {"das Museum, Museen", "muzeum|museum", "muzeum"}, {"Sie gehen ins Museum.", "jdou do muzea|they are going to the museum|they go to the museum", @@ -1186,6 +1186,7 @@ typedef struct { int done; int *order; /* queue of word indices, order[0] = current */ int qn; /* words still in the queue */ + gboolean warned; /* current word already got an "incomplete" warning */ } TransCtx; /* Canonical translation of a word, in the current UI language. */ @@ -1203,11 +1204,13 @@ static void trans_update_progress(TransCtx *ctx) { } static void trans_show(TransCtx *ctx) { + ctx->warned = FALSE; + if (ctx->qn <= 0) { gtk_label_set_text(GTK_LABEL(ctx->header), ""); gtk_label_set_text(GTK_LABEL(ctx->prompt), tr("trans_done")); gtk_widget_set_visible(ctx->entry, FALSE); - gtk_widget_set_sensitive(ctx->check, FALSE); + gtk_widget_set_visible(ctx->check, FALSE); set_feedback(ctx->feedback, TRUE, tr("feedback_ok")); trans_update_progress(ctx); mark_done(ctx->unit, ctx->ex_num); @@ -1255,6 +1258,16 @@ static void translate_check(GtkButton *button, gpointer data) { txt = gtk_editable_get_text(GTK_EDITABLE(ctx->entry)); norm = normalize_answer(txt ? txt : ""); good = txt && txt[0] && answer_accepts(norm, q->answers); + + /* Nearly there (e.g. "jmenovat" for "jmenovat se"): warn in yellow and let + * the student finish the answer. A second Check on the same word counts. */ + if (!good && !ctx->warned && txt && txt[0] && + answer_is_incomplete(norm, q->answers)) { + g_free(norm); + ctx->warned = TRUE; + set_feedback_warn(ctx->feedback, tr("trans_incomplete")); + return; + } g_free(norm); /* Pop the current word off the front of the queue. */ diff --git a/i18n.c b/i18n.c index 705a910..4772d20 100644 --- a/i18n.c +++ b/i18n.c @@ -80,6 +80,7 @@ const TrEntry tr_ui[] = { "Přeložte slovo. Špatná odpověď se vrátí na konec.", "Translate the word. Wrong answers come back at the end."}, {"trans_wrong", "Špatně. Správně: %s", "Wrong. Correct: %s"}, + {"trans_incomplete", "Nepřesné – něco chybí.", "Almost – something is missing."}, {"trans_progress", "Hotovo: %d / %d", "Done: %d / %d"}, {"trans_done", "Hotovo! Zvládl(a) jsi všechna slova.", "Done! You mastered all the words."}, diff --git a/maturita.h b/maturita.h index 505bfdf..0f3ef71 100644 --- a/maturita.h +++ b/maturita.h @@ -743,6 +743,7 @@ void shuffle_indices(int *arr, int n); void flow_clear(GtkFlowBox *fb); void answer_mark(GtkWidget *widget, gboolean ok); void set_feedback(GtkWidget *label, gboolean ok, const char *text); +void set_feedback_warn(GtkWidget *label, const char *text); gboolean text_has_german_umlaut(const char *s); void add_umlaut_note(GtkWidget *body); void unit_save_progress(UnitCtx *u); @@ -852,6 +853,7 @@ void combo_list_add(ComboListCtx *ctx, GtkWidget *combo, const char *ans); void combo_list_add_trans(ComboListCtx *ctx, GtkWidget *label); GtkWidget *make_word_combo(const char **words, int n); gboolean answer_accepts(const char *sel_norm, const char *ans); +gboolean answer_is_incomplete(const char *sel_norm, const char *ans); void combo_list_check(GtkButton *button, gpointer data); void sent_rebuild(SentBuilder *sb); GdkContentProvider *asm_drag_prepare(GtkDragSource *source, diff --git a/style.css b/style.css index 155a10e..805ff97 100644 --- a/style.css +++ b/style.css @@ -610,6 +610,11 @@ entry.answer-wrong { font-size: 15px; font-weight: 700; } +.feedback-warn { + color: @warning; + font-size: 15px; + font-weight: 700; +} .stats-page { padding: 8px 28px 24px 28px; } diff --git a/util.c b/util.c index 0c457d1..cb64ddc 100644 --- a/util.c +++ b/util.c @@ -92,10 +92,20 @@ void answer_mark(GtkWidget *widget, gboolean ok) { void set_feedback(GtkWidget *label, gboolean ok, const char *text) { gtk_widget_remove_css_class(label, "feedback-ok"); gtk_widget_remove_css_class(label, "feedback-err"); + gtk_widget_remove_css_class(label, "feedback-warn"); gtk_widget_add_css_class(label, ok ? "feedback-ok" : "feedback-err"); gtk_label_set_text(GTK_LABEL(label), text); } +/* Yellow "almost there" note: close to a correct answer but incomplete. */ +void set_feedback_warn(GtkWidget *label, const char *text) { + gtk_widget_remove_css_class(label, "feedback-ok"); + gtk_widget_remove_css_class(label, "feedback-err"); + gtk_widget_remove_css_class(label, "feedback-warn"); + gtk_widget_add_css_class(label, "feedback-warn"); + gtk_label_set_text(GTK_LABEL(label), text); +} + /* True if `s` contains any German special letter (ä, ö, ü or ß). */ gboolean text_has_german_umlaut(const char *s) { const char *p = s ? s : "";