#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
+#include <stdint.h>
#include <sys/types.h>
#include <dirent.h>
#include <string.h>
#include <assert.h>
+#include <readline/readline.h>
+#include <readline/history.h>
+
+typedef int bool_t;
+
typedef struct _item {
int count;
char *challenge;
bin->items[bin->num_items++] = item;
}
+static void
+bin_remove_item (bin_t *bin,
+ int item_index)
+{
+ /* Replace the current item with the last item, (no need to shift
+ * any more than that since we don't care about the order of the
+ * items within a bin). */
+ bin->num_items--;
+ if (bin->num_items)
+ bin->items[item_index] = bin->items[bin->num_items];
+}
+
static void
mnemon_init (mnemon_t *mnemon)
{
}
static void
-mnemon_add_item (mnemon_t *mnemon,
- category_t *category,
- int count,
- const char *challenge,
- const char *response)
+mnemon_remove_bin (mnemon_t *mnemon,
+ bin_t *bin)
{
- item_t *item;
- bin_t *bin;
+ int i = bin - mnemon->bins;
- item = category_add_item (category, count, challenge, response);
-
- bin = mnemon_get_bin (mnemon, count);
-
- bin_add_item (bin, item);
+ memmove (bin, bin + 1, (mnemon->num_bins - i) * sizeof (bin_t));
+ mnemon->num_bins--;
}
static void
int line_count = 0;
char *path;
category_t *category;
+ int i;
path = xmalloc (strlen (mnemon->dir_name) + 1 + strlen (name) + 1);
sprintf (path, "%s/%s", mnemon->dir_name, name);
chomp (line);
response = line;
- mnemon_add_item (mnemon, category, count, challenge, response);
+ category_add_item (category, count, challenge, response);
free (challenge);
}
free (line);
fclose (file);
free (path);
+
+ /* Resize category items to fit exactly. */
+ category->items_size = category->num_items;
+ category->items = xrealloc (category->items, category->items_size * sizeof (item_t));
+
+ /* Now that the category is completely loaded, with stable
+ * pointers to every item, we can add each item to its appropriate
+ * bin. */
+ for (i = 0; i < category->num_items; i++) {
+ item_t *item = &category->items[i];
+ bin_t *bin = mnemon_get_bin (mnemon, item->count);
+
+ bin_add_item (bin, item);
+ }
}
static void
mnemon->dir_name, category->name);
xasprintf (&lock_filename, "%s/.#%s",
mnemon->dir_name, category->name);
+
file = fopen (lock_filename, "w");
if (file == NULL) {
fprintf (stderr, "Error: Failed to open %s for writing: %s\n",
category_print (category, file);
+ fclose (file);
+
err = rename (lock_filename, filename);
if (err < 0) {
- fprintf (stderr, "Error: Failes to rename %s to %s: %s\n",
+ fprintf (stderr, "Error: Failed to rename %s to %s: %s\n",
lock_filename, filename, strerror (errno));
continue;
}
+
+ free (filename);
+ free (lock_filename);
+ }
+}
+
+/* Return a uniformly-distributed pseudo-random integer within the
+ * range:
+ *
+ * 0 <= result < num_values
+ */
+static int
+rand_within (int num_values)
+{
+ return (int) (num_values * (rand() / (RAND_MAX + 1.0)));
+}
+
+/* Return an exponentially-distributed pseudo-random integer within
+ * the range:
+ *
+ * 0 <= result < num_values
+ *
+ * The distribution is such that each successively larger value will
+ * occur with a probability of half of the previous value.
+ */
+static int
+rand_within_exponential (int num_values)
+{
+ static int r;
+ static uint32_t mask = 0;
+ int ones;
+ int bit;
+
+ /* Optimize the constant case. */
+ if (num_values == 1)
+ return 0;
+
+ ones = 0;
+
+ do {
+ if (mask == 0) {
+ r = rand ();
+ mask = 1 << 31;
+ while (mask > RAND_MAX)
+ mask >>= 1;
+ }
+ bit = r & mask;
+ mask >>= 1;
+ if (bit) {
+ ones++;
+ if (ones == num_values)
+ ones = 0;
+ }
+ } while (bit);
+
+ return ones;
+}
+
+static void
+mnemon_select_item (mnemon_t *mnemon,
+ bin_t **bin_ret,
+ int *item_index_ret)
+{
+ int bin_index;
+ bin_t *bin;
+
+ bin_index = rand_within_exponential (mnemon->num_bins);
+
+ bin = &mnemon->bins[bin_index];
+
+ *bin_ret = bin;
+ *item_index_ret = rand_within (bin->num_items);
+}
+
+static void
+mnemon_do_challenges (mnemon_t *mnemon)
+{
+ bin_t *bin;
+ int item_index;
+ item_t *item;
+ char *response;
+ bool_t correct;
+
+ while (1) {
+ mnemon_select_item (mnemon, &bin, &item_index);
+ item = bin->items[item_index];
+
+ printf ("%s\n", item->challenge);
+
+ response = readline ("> ");
+ if (response == NULL) {
+ printf ("\n");
+ break;
+ }
+
+ correct = (strcmp (response, item->response) == 0);
+
+ bin_remove_item (bin, item_index);
+ if (bin->num_items == 0)
+ mnemon_remove_bin (mnemon, bin);
+
+ if (correct) {
+ printf ("Correct! (Moving from %d to ", item->count);
+ item->count++;
+ /* We reserve an item count of 0 for an item that has
+ * never been asked. */
+ if (item->count == 0)
+ item->count = 1;
+ printf ("%d)\n\n", item->count);
+ } else {
+ printf (" %s is the correct answer. (Moving from %d to ",
+ item->response, item->count);
+ item->count--;
+ /* Penalize an incorrect response by forcing the count
+ * negative. */
+ if (item->count >= 0)
+ item->count = -1;
+ printf ("%d)\n\n", item->count);
+ }
+
+ bin = mnemon_get_bin (mnemon, item->count);
+
+ bin_add_item (bin, item);
}
}
{
mnemon_t mnemon;
+ srand (1);
+
mnemon_init (&mnemon);
mnemon_load (&mnemon);
+ mnemon_do_challenges (&mnemon);
+
mnemon_save (&mnemon);
mnemon_fini (&mnemon);