X-Git-Url: https://git.cworth.org/git?p=mnemon;a=blobdiff_plain;f=mnemon.c;h=3dee0347e153c5e2a5c00f93d717f57605fd787d;hp=dfcc60e78fe3a931c60f0d8b9d1a8d82ca798e0d;hb=3c887fd06a0a6790db978d13df4cc73f6d5b75b0;hpb=776dfc7b017ad307113ed9634de3adedcf61c28f diff --git a/mnemon.c b/mnemon.c index dfcc60e..3dee034 100644 --- a/mnemon.c +++ b/mnemon.c @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -34,6 +35,12 @@ #include #include +#define ASSERT_NOT_REACHED \ +do { \ + static const int NOT_REACHED = 0; \ + assert (NOT_REACHED); \ +} while (0) + typedef int bool_t; typedef struct _item { @@ -54,6 +61,14 @@ typedef enum { CATEGORY_ORDER_SEQUENTIAL } category_order_t; +typedef enum { + CHALLENGE_TYPE_TEXT, + CHALLENGE_TYPE_IMAGE, + CHALLENGE_TYPE_AUDIO, + CHALLENGE_TYPE_MIDI, + CHALLENGE_TYPE_TEXT_TO_SPEECH +} challenge_type_t; + typedef struct _category { char *name; int items_size; @@ -65,6 +80,10 @@ typedef struct _category { /* Support categories where responses are timed (0.0 == disable). */ double time_limit; int bin_zero_head; + /* Support challenges of non-text types (image, audio, etc.) */ + challenge_type_t challenge_type; + /* Whether to repeat afterwards (for a little extra reinforcement) */ + bool_t repeat; } category_t; typedef struct _mnemon { @@ -192,6 +211,8 @@ category_init (category_t *category, category->order = CATEGORY_ORDER_RANDOM; category->time_limit = 0.0; category->bin_zero_head = 0; + category->challenge_type = CHALLENGE_TYPE_TEXT; + category->repeat = 0; } static void @@ -261,6 +282,28 @@ category_print (category_t *category, fprintf (file, "time = %f\n\n", category->time_limit); + fprintf (file, "challenge = "); + switch (category->challenge_type) { + case CHALLENGE_TYPE_TEXT: + fprintf (file, "text"); + break; + case CHALLENGE_TYPE_IMAGE: + fprintf (file, "image"); + break; + case CHALLENGE_TYPE_AUDIO: + fprintf (file, "audio"); + break; + case CHALLENGE_TYPE_MIDI: + fprintf (file, "midi"); + break; + case CHALLENGE_TYPE_TEXT_TO_SPEECH: + fprintf (file, "text-to-speech"); + break; + } + fprintf (file, "\n\n"); + + fprintf (file, "repeat = %d\n\n", category->repeat); + for (i = 0; i < category->num_items; i++) { item = &category->items[i]; if (i != 0) @@ -577,7 +620,7 @@ mnemon_load_category (mnemon_t *mnemon, /* An initial digit means we hit an item. Trigger the * spaghetti machine. */ - if (*line >= '0' && *line <= '9') + if ((*line >= '0' && *line <= '9') || *line == '-') goto PARSE_BIN; equal = strchr (line, '='); @@ -617,6 +660,27 @@ mnemon_load_category (mnemon_t *mnemon, value, path, line_count); exit (1); } + } else if (strcmp (name, "challenge") == 0) { + if (strcmp (value, "text") == 0) { + category->challenge_type = CHALLENGE_TYPE_TEXT; + } else if (strcmp (value, "image") == 0) { + category->challenge_type = CHALLENGE_TYPE_IMAGE; + } else if (strcmp (value, "audio") == 0) { + category->challenge_type = CHALLENGE_TYPE_AUDIO; + } else if (strcmp (value, "midi") == 0) { + category->challenge_type = CHALLENGE_TYPE_MIDI; + } else if (strcmp (value, "text-to-speech") == 0) { + category->challenge_type = CHALLENGE_TYPE_TEXT_TO_SPEECH; + } else { + fprintf (stderr, "Unknown value for \"challenge\" option \"%s\" at %s:%d\n", + value, path, line_count); + exit (1); + } + } else if (strcmp (name, "repeat") == 0) { + if (strcmp (value, "0") == 0) + category->repeat = 0; + else + category->repeat = 1; } else { fprintf (stderr, "Unknown option %s at %s:%d\n", name, path, line_count); @@ -730,6 +794,7 @@ mnemon_save (mnemon_t *mnemon) category_print (category, file); + fsync (fileno (file)); fclose (file); err = rename (lock_filename, filename); @@ -863,9 +928,20 @@ mnemon_select_item (mnemon_t *mnemon, category_t *category; bin_index = rand_within_exponential (mnemon->num_bins); - bin = &mnemon->bins[bin_index]; + /* The most intuitive understanding of the to_introduce counter is + * that it's tracking never-before-learned items as they are + * pulled from the bin with score 0. But that bin can become + * empty. So the refined rule is that we decrement to_introduce + * whenever we pull from the lowest-indexed bin with a + * non-negative score. */ + if (mnemon->to_introduce && bin->score >=0 && + (bin_index == 0 || mnemon->bins[bin_index-1].score < 0)) + { + mnemon->to_introduce--; + } + item_index = rand_within (bin->num_items); item = bin->items[item_index]; @@ -987,6 +1063,7 @@ mnemon_handle_command (mnemon_t *mnemon, const char *arg; int len; switch (command[0]) { + /* 'h' for histogram */ case 'h': { char *category = NULL; @@ -1005,6 +1082,12 @@ mnemon_handle_command (mnemon_t *mnemon, mnemon_print_histogram (mnemon, category, length); } break; + /* 'r' for repeat */ + case 'r': + { + /* Nothing necessary for repeating. */ + } + break; default: printf ("Unknown command: %s\n", command); break; @@ -1039,13 +1122,11 @@ mnemon_handle_response (mnemon_t *mnemon, (time_limit == 0.0 || response_time < time_limit)) { item->score++; - mnemon->to_master--; /* We reserve an item score of 0 for an item that has * never been asked. */ if (item->score == 0) { item->score = 1; mnemon->unlearned--; - mnemon->to_master--; printf ("You got it!"); } else if (item->score < 0) { printf ("Yes---just give me %d more.", @@ -1054,6 +1135,8 @@ mnemon_handle_response (mnemon_t *mnemon, printf ("On your first try, no less!"); } else { printf ("Masterful (%dx).", item->score); + if (mnemon->to_master) + mnemon->to_master--; } } else { if (! correct) @@ -1069,12 +1152,13 @@ mnemon_handle_response (mnemon_t *mnemon, printf (" Oops, you knew that, right? (%dx)\n ", item->score); mnemon->unlearned++; - /* We add three here, (rather than just 2 to track the - * change in the item's score below), as an extra - * penalty. If the user is forgetting stuff learned - * previously, then more time should be spent on mastering - * than learning new items. */ - mnemon->to_master += item->score + 3; + /* We increase to_master here as an extra penalty. If the + * user is forgetting stuff learned previously, then more + * time should be spent on mastering than learning new + * items. Note that we only do this during the initial + * phase while new items are still being introduced. */ + if (mnemon->to_introduce) + mnemon->to_master++; /* We go to -2 to force a little extra reinforcement * when re-learning an item, (otherwise, it will often * get asked again immediately where it is easy to get @@ -1082,7 +1166,6 @@ mnemon_handle_response (mnemon_t *mnemon, item->score = -2; } else { item->score--; - mnemon->to_master++; } } @@ -1100,6 +1183,66 @@ mnemon_handle_response (mnemon_t *mnemon, bin_add_item (bin, item); } +static void +mnemon_show_challenge (mnemon_t *mnemon, + challenge_type_t challenge_type, + const char *challenge) +{ + const char *program; + char *command; + + if (challenge_type == CHALLENGE_TYPE_TEXT) { + printf ("%s\n", challenge); + return; + } + + /* XXX: Yes, shelling out to system is total cheese. The planned + * fix here is to bring graphical display in process, (or at least + * have a custom external program that accepts image filenames on + * stdin. + */ + switch (challenge_type) { + case CHALLENGE_TYPE_TEXT: + ASSERT_NOT_REACHED; + break; + case CHALLENGE_TYPE_IMAGE: + program = "xli -gamma 2.2"; + break; + case CHALLENGE_TYPE_AUDIO: + program = "play"; + break; + case CHALLENGE_TYPE_MIDI: + program = "timidity -Os"; + break; + case CHALLENGE_TYPE_TEXT_TO_SPEECH: + program = "mnemon-tts"; + break; + } + + xasprintf (&command, "%s %s/%s >/dev/null 2>&1 &", + program, + mnemon->dir_name, + challenge); + system (command); + free (command); +} + +static void +mnemon_hide_challenge (mnemon_t *mnemon, challenge_type_t challenge_type) +{ + char * command; + + if (challenge_type != CHALLENGE_TYPE_IMAGE) + return; + + /* XXX: And this is just embarrassing (obviously wrong in several + * ways). Hopefully I'll amend away any commit that includes this. + */ + xasprintf (&command, "killall xli"); + system (command); + free (command); +} + static void mnemon_do_challenges (mnemon_t *mnemon) { @@ -1146,21 +1289,21 @@ mnemon_do_challenges (mnemon_t *mnemon) mnemon_select_item (mnemon, &bin, &item_index, &category); item = bin->items[item_index]; - if (bin->score == 0) - mnemon->to_introduce--; - while (1) { if (category->time_limit > 0.0) { response = readline ("The next one is timed. Press enter when ready:"); free (response); } - - printf ("%s\n", item->challenge); + + mnemon_show_challenge (mnemon, category->challenge_type, + item->challenge); gettimeofday (&start, NULL); response = readline ("> "); gettimeofday (&end, NULL); + mnemon_hide_challenge (mnemon, category->challenge_type); + /* Terminate on EOF */ if (response == NULL) { printf ("\n"); @@ -1181,17 +1324,25 @@ mnemon_do_challenges (mnemon_t *mnemon) (start.tv_sec + start.tv_usec / 1e6), category->time_limit); free (response); + + /* Replay audio challenges for reinforcement. */ + if (category->repeat) + { + mnemon_show_challenge (mnemon, category->challenge_type, + item->challenge); + printf ("%s\n", item->challenge); + sleep (2); + } } while (mnemon->to_introduce || mnemon->unlearned || mnemon->to_master > 0); - - printf ("Great job.\n"); } int main (int argc, char *argv[]) { mnemon_t mnemon; + char *response; srand (time (NULL)); @@ -1205,5 +1356,15 @@ main (int argc, char *argv[]) mnemon_fini (&mnemon); + mnemon_init (&mnemon); + mnemon_load (&mnemon); + + printf ("Great job.\nHere are your current results:\n"); + mnemon_print_histogram (&mnemon, NULL, 0); + response = readline ("Press enter to quit.\n"); + free (response); + + mnemon_fini (&mnemon); + return 0; }