cards

a command-line tool for reviewing flashcards
git clone https://github.com/tanguyandreani/cards
Log | Files | Refs | README

cards.c (6578B)


      1 #include <stdio.h>
      2 #include <stdlib.h>		/* srand(), rand() */
      3 #include <string.h>
      4 
      5 /* sleep(), getopt() */
      6 #include <unistd.h>
      7 
      8 /* time() */
      9 #include <time.h>
     10 
     11 /* unicode support */
     12 #include <locale.h>
     13 #include <wchar.h>
     14 
     15 #include "csv.h"
     16 
     17 #define MAX_CSV_SIZE 1024
     18 #define MAX_QA_SIZE 512
     19 
     20 #define CARDS "cards.csv"
     21 
     22 char	       *filename = CARDS;
     23 
     24 struct Card {			/* a single card (a bucket's node) */
     25 
     26 	wchar_t	       *front;	/* key */
     27 	wchar_t	       *back;
     28 	unsigned int	priority;
     29 	unsigned int	last_appearance;
     30 
     31 	struct Card    *next;
     32 };
     33 
     34 struct Bucket {			/* buckets for the hash table */
     35 	unsigned int	size;
     36 	struct Card    *head;
     37 };
     38 
     39 struct Cards {			/* a hash table */
     40 	unsigned int	size;
     41 	unsigned int	population;
     42 	struct Bucket  *buckets;
     43 };
     44 
     45 unsigned int
     46 hash_key(const struct Cards *ht, const wchar_t * key)
     47 {
     48 	/* sdbm */
     49 	unsigned int	hashAddress = 0;
     50 	for (int i = 0; key[i] != L'\0'; i++) {
     51 		hashAddress = key[i] + (hashAddress << 6) + (hashAddress << 16) - hashAddress;
     52 	}
     53 	return hashAddress % ht->size;
     54 }
     55 
     56 int
     57 ht_init(struct Cards *ht, unsigned int size)
     58 {
     59 	ht->size = size;
     60 	ht->population = 0;
     61 	ht->buckets = malloc(sizeof(struct Bucket) * ht->size);
     62 	if (ht->buckets == NULL)
     63 		return (1);
     64 	for (unsigned int i = 0; i < ht->size; i++) {
     65 		ht->buckets[i].size = 0;
     66 		ht->buckets[i].head = NULL;
     67 	}
     68 	return (0);
     69 }
     70 
     71 void
     72 ht_destroy(struct Cards *ht)
     73 {
     74 	for (unsigned int i = 0; i < ht->size; i++) {
     75 
     76 		struct Card    *kv;
     77 		while ((kv = ht->buckets[i].head) != NULL) {
     78 			free((void *)kv->front);
     79 			free((void *)kv->back);
     80 			ht->buckets[i].head = kv->next;
     81 			ht->buckets[i].size--;
     82 			free(kv);
     83 		}
     84 
     85 	}
     86 
     87 	free(ht->buckets);
     88 
     89 	return;
     90 }
     91 
     92 int
     93 ht_insert(struct Cards *ht, const wchar_t * front,
     94 	  const wchar_t * back, unsigned int priority,
     95 	  unsigned int last_appearance)
     96 {
     97 	int		hashed_key = hash_key(ht, front);
     98 
     99 	struct Card    *kv = malloc(sizeof(struct Card));
    100 	if (kv == NULL)
    101 		return (1);
    102 
    103 	kv->front = wcsdup(front);
    104 	if (kv->front == NULL)
    105 		return (1);
    106 
    107 	kv->back = wcsdup(back);
    108 	if (kv->back == NULL)
    109 		return (1);
    110 
    111 	kv->priority = priority;
    112 	kv->last_appearance = last_appearance;
    113 	kv->next = ht->buckets[hashed_key].head;
    114 
    115 	ht->population++;
    116 	ht->buckets[hashed_key].head = kv;
    117 	ht->buckets[hashed_key].size++;
    118 
    119 	return (0);
    120 }
    121 
    122 int		stop_iterating;
    123 
    124 /* hardcore */
    125 void
    126 ht_iterate(struct Cards *ht, void (*f) (struct Card *))
    127 {
    128 	stop_iterating = 0;
    129 	for (int i = 0; i < ht->size; i++) {
    130 		struct Card    *kv = ht->buckets[i].head;
    131 		while (kv != NULL) {
    132 			f(kv);
    133 			if (stop_iterating == 1)
    134 				goto fail;
    135 			kv = kv->next;
    136 		}
    137 	}
    138 
    139 fail:
    140 	return;
    141 }
    142 
    143 /* accessed in main() and select_next_card() */
    144 static struct Card *next_card;
    145 
    146 void
    147 select_next_card(struct Card *card)
    148 {
    149 
    150 	if (next_card == NULL
    151 	    || (card->priority > next_card->priority
    152 		&& card->last_appearance <= next_card->last_appearance
    153 		&& (rand() >= RAND_MAX / 3))) {	/* 2/3 chances of being
    154 						 * selected */
    155 		next_card = card;
    156 	}
    157 
    158 	return;
    159 }
    160 
    161 void
    162 dump_card(struct Card *card)
    163 {
    164 	printf("%ls\t%ls", card->front, card->back);
    165 
    166 	return;
    167 }
    168 
    169 void
    170 dump_csv(struct Card *card)
    171 {
    172 	FILE	       *fp = fopen(filename, "a");
    173 	if (fp == NULL) {
    174 		fprintf(stderr, "Could not open %s in append mode!\n", filename);
    175 		usleep(500000);
    176 		stop_iterating = 1;
    177 		return;
    178 	}
    179 
    180 	fprintf(fp, "%ls,", card->front);
    181 
    182 	wchar_t	       *dup = card->back;
    183 	while (*dup != '\n') {	/* newline terminated string */
    184 		fputwc(*dup, fp);
    185 		dup++;
    186 	}
    187 
    188 	fprintf(fp, ",%u\n", card->priority);
    189 
    190 	fclose(fp);
    191 
    192 	return;
    193 }
    194 
    195 int
    196 main(int argc, char *argv[])
    197 {
    198 	(void)setlocale(LC_ALL, "");
    199 
    200 	srand(time(NULL));
    201 
    202 	struct Cards   *cards = malloc(sizeof(struct Cards));
    203 	if (cards == NULL
    204 	    || ht_init(cards, 100) != 0) {
    205 		fprintf(stderr, "Failed to allocate memory!\n");
    206 		exit(1);
    207 	}
    208 
    209 	wchar_t	       *line = malloc(sizeof(wchar_t) * MAX_QA_SIZE);
    210 	if (line == NULL) {
    211 		ht_destroy(cards);
    212 		free(cards);
    213 		fprintf(stderr, "Failed to allocate memory!\n");
    214 		exit(1);
    215 	}
    216 
    217 
    218 	struct {
    219 		unsigned int	show_answer;
    220 		unsigned int	dump_cards;
    221 	}		flags;
    222 	flags.show_answer = 0;	/* default */
    223 	flags.dump_cards = 0;
    224 
    225 	char		c;
    226 	while ((c = getopt(argc, argv, "f:vLh?")) != -1) {
    227 		switch (c) {
    228 		case 'f':
    229 			filename = optarg;
    230 			break;
    231 		case 'v':	/* enable verbose mode: answer are shown on
    232 				 * failure */
    233 			flags.show_answer = 1;
    234 			break;
    235 		case 'L':	/* print cards and exit */
    236 			flags.dump_cards = 1;
    237 			break;
    238 		case 'h':
    239 		case '?':
    240 			printf("usage: %s [-Lvh]\n", argv[0]);
    241 			goto end;
    242 			break;
    243 		}
    244 	}
    245 
    246 
    247 	FILE	       *fp = fopen(filename, "r");
    248 	if (fp == NULL) {
    249 		fprintf(stderr, "Could not open %s in read mode!\n", filename);
    250 		goto end;
    251 	}
    252 
    253 
    254 	char		csv_line[MAX_CSV_SIZE];
    255 	int		l = 0;
    256 
    257 	while (fgets(csv_line, MAX_CSV_SIZE, fp) != NULL) {
    258 
    259 		l++;
    260 
    261 		char	      **items = parse_csv(csv_line);
    262 		if (items == NULL || !items[0] || !items[1] || !items[2]) {
    263 			fprintf(stderr, "Couldn't parse line %d!\n", l);
    264 			fclose(fp);
    265 			goto end;
    266 		}
    267 
    268 		wchar_t		q[MAX_QA_SIZE], a[MAX_QA_SIZE];
    269 		swprintf(q, MAX_QA_SIZE, L"%hs", items[0]);
    270 		swprintf(a, MAX_QA_SIZE, L"%hs\n", items[1]);
    271 
    272 		unsigned int	p = strtol(items[2], NULL, 10);
    273 		ht_insert(cards, q, a, p == 0 ? 1000 : p, 0);
    274 
    275 		free_csv_line(items);
    276 
    277 	}
    278 
    279 	fclose(fp);
    280 
    281 	if (cards->population == 0) {
    282 		printf("No cards!\n");
    283 		goto end;
    284 	}
    285 
    286 	if (flags.dump_cards == 1) {
    287 		ht_iterate(cards, dump_card);
    288 		goto end;
    289 	}
    290 
    291 
    292 	int		success = 0;
    293 	int		i = 1;
    294 
    295 	ht_iterate(cards, select_next_card);
    296 	printf("\e[1;1H\e[2J");
    297 	printf("[%d/%d]", success, 0);
    298 	printf("\t%ls\n", next_card->front);
    299 	printf("? ");
    300 
    301 	while (fgetws(line, MAX_QA_SIZE, stdin) != NULL) {
    302 
    303 		printf("\e[1;1H\e[2J");	/* clear the screen */
    304 		if (wcscmp(L"!save\n", line) == 0) {
    305 			fp = fopen(filename, "w");
    306 			if (fp == NULL) {
    307 				fprintf(stderr, "Could not open %s in write mode!\n", filename);
    308 				usleep(500000);
    309 				i--;
    310 				goto prompt;
    311 			}
    312 			fputs("", fp);
    313 			fclose(fp);
    314 			ht_iterate(cards, dump_csv);
    315 			i--;	/* cancels i++ */
    316 			goto prompt;	/* show same card again */
    317 		} else if (wcscmp(next_card->back, line) == 0) {
    318 			printf("Nice!\n");
    319 			next_card->priority--;
    320 			success++;
    321 		} else {
    322 			printf("Wrong!\n");
    323 			next_card->priority++;
    324 
    325 			if (flags.show_answer == 1)
    326 				printf("right answer: %ls\n", next_card->back);
    327 		}
    328 		next_card->last_appearance = i;
    329 
    330 		ht_iterate(cards, select_next_card);
    331 		usleep(500000);
    332 
    333 prompt:
    334 		printf("\e[1;1H\e[2J");	/* clear the screen */
    335 		printf("[%d/%d]", success, i);
    336 		printf("\t%ls\n", next_card->front);
    337 		printf("? ");
    338 
    339 		i++;
    340 	}
    341 
    342 	putchar('\n');
    343 
    344 end:
    345 	ht_destroy(cards);
    346 	free(cards);
    347 	free(line);
    348 
    349 	return (0);
    350 }