+/* 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,
+ int to_introduce)
+{
+ bin_t *bin;
+ int item_index;
+ item_t *item;
+ char *response;
+ bool_t correct;
+ int unlearned;
+ int i;
+
+ /* Count the number of items with negative counts. */
+ unlearned = 0;
+ for (i = 0; i < mnemon->num_bins; i++) {
+ bin = &mnemon->bins[i];
+ if (bin->count >= 0)
+ break;
+ unlearned += bin->num_items;
+ }
+
+ to_introduce -= unlearned;
+ if (to_introduce < 0)
+ to_introduce = 0;
+
+ if (unlearned) {
+ printf ("You've got %d items to learn already. ", unlearned);
+ if (to_introduce)
+ printf ("I'll introduce %d more as we go.", to_introduce);
+ printf ("\n");
+ } else {
+ printf ("Introducing %d new items.\n", to_introduce);
+ }
+ printf ("\n");
+
+ do {
+ do {
+ mnemon_select_item (mnemon, &bin, &item_index);
+ } while (bin->count == 0 && to_introduce == 0);
+
+ if (bin->count == 0)
+ to_introduce--;
+
+ 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) {
+ item->count++;
+ /* We reserve an item count of 0 for an item that has
+ * never been asked. */
+ if (item->count == 0) {
+ item->count = 1;
+ unlearned--;
+ printf ("You got it!");
+ } else if (item->count < 0) {
+ printf ("Yes---just give me %d more.",
+ - item->count);
+ } else {
+ printf ("Masterful.");
+ }
+ } else {
+ printf (" %s is the correct answer.",
+ item->response);
+ item->count--;
+ /* Penalize an incorrect response by forcing the count
+ * negative. */
+ if (item->count >= 0) {
+ item->count = -1;
+ unlearned++;
+ printf ( " Oops, you knew that, right?\n ");
+ }
+ }
+
+ printf (" (");
+ if (to_introduce)
+ printf ("%d to come. ", to_introduce);
+ if (unlearned)
+ printf ("%d still unlearned.", unlearned);
+ if (to_introduce == 0 && unlearned == 0)
+ printf ("Great job!");
+ printf (")\n\n");
+
+ bin = mnemon_get_bin (mnemon, item->count);
+
+ bin_add_item (bin, item);
+ } while (unlearned || to_introduce);
+}
+