#include #include #include #include #include "strarr.h" /* eow: guess the last three letters of a set of words. e.g. ti___, man___, cas___: the player must guess `tle`. How to do - Eliminate all words less than NUMBER_OF_CHARS + 1 chars. - Then somehow search for common endings...? for (word in words) ending = get ending for (word in remaining words) 10 => 10 + 9 + 8 + 7 ... For 370k words, that is 68 bil. That will take seconds. I think, since this is a simple game, I will just pick out words at random. In other words, randomly index into the array (possibly costly in this structure)... I think add a function to generate interstitial indexes and perform random (not: linear from start-to-end) indexes. Then, iterate over the entire list (subsecond) and find ones which share the same ending. Use those for the game. TODO + Finish logic to accept a guess. + Add flags to change settings. - Make number of guesses configurable. + Add option to get a hint, which gives away one of the letters. (When it does this, it reveals the letter in the actual word displayed.) - Figure out bug that when you run /hint, it for some reason goes prints a line up, swallowing the topmost line? - Add in /set commands. */ int MIN_LENGTH = 6; int NUMBER_OF_CHARS = 3; int NUMBER_OF_WORDS = 3; int NUMBER_OF_TRIES = 100; /* Get the first entry from the words as a random index. */ char * random_index(strarr *words_arr, int min_length) { for (;;) { int idx = rand() % words_arr->len; char *w = strarr_random_index(words_arr, idx); if (strlen(w) < min_length) { continue; } return w; } } /* Get strings matching the ending of the first word. Caller must free the array but not the members. Returns NULL if the allocation fails, or there are insufficient matching strings for the entry provided. */ const char ** get_matching_strs(strarr *words_arr, const char *first) { // The `-1` is due to the first word already being chosen. const char **arr = calloc(sizeof(char*), NUMBER_OF_WORDS - 1); if (!arr) { return NULL; } /* Get ending. */ const char *ending = first + (strlen(first) - NUMBER_OF_CHARS); /* Iterate over words and find NUMBER_OF_WORDS entries with this ending. */ char *p; int i; for (i = 0, p = strarr_index(words_arr, 0); p != NULL; p = strarr_next(words_arr, p)) { size_t sz = strlen(p); if (sz < MIN_LENGTH || strcmp((p + (sz - NUMBER_OF_CHARS)), ending) != 0 || strcmp(p, first) == 0) { continue; } /* If it's passed the check, we know it is the right length, and has the correct ending: add it to the list, check if we've got all the words, quit if so. */ arr[i++] = p; if (i == NUMBER_OF_WORDS - 1) { return arr; } } /* If we managed to break out of the loop, it means we've iterated over the entire array and not found enough words. So return NULL to signify error. */ free(arr); return NULL; } void print_n_uscore(int i) { for (int rep = 0; rep < i; ++rep) { printf("_"); } } void display_prompt(int totalpoints, int points, const char *first, const char **others, const char *hinted_ending, int guesses) { printf("Points: %d Points available: %d\n", totalpoints, points); printf("Your words are: \n"); printf(" %.*s", (int)strlen(first) - NUMBER_OF_CHARS, first); printf("%s", hinted_ending); printf("\n"); for (int i = 0; i < NUMBER_OF_WORDS - 1; ++i) { printf(" %.*s", (int)strlen(others[i]) - NUMBER_OF_CHARS, others[i]); printf("%s", hinted_ending); printf("\n"); } printf("What do you think the word is (incorrect guesses: %d): ", guesses); } void clear_prompt() { static int is_first_call = 1; if (is_first_call) { /* do nothing */ is_first_call = 0; return; } int lines_displayed = 3 + (NUMBER_OF_WORDS - 1) + 1 + 1 + 1; for (int c = 0; c < lines_displayed - 1; ++c) { printf(" "); printf("\033[F"); } } int ask(const char *word, const char **others, int totalpoints) { const char *ending = word + strlen(word) - NUMBER_OF_CHARS; char *hinted_ending = malloc(strlen(ending)); for (int i = 0; i < strlen(ending); ++i) { hinted_ending[i] = '_'; } hinted_ending[strlen(ending)] = '\0'; printf("Type /help for help, or /hint, or /giveup, or /quit to quit.\n"); int hinted_to = 0; int guesses = 0; int points = 5; for (;;) { /* Display the words. */ clear_prompt(); display_prompt(totalpoints, points, word, others, hinted_ending, guesses); /* Get input. */ char ui[256]; scanf("%s", ui); /* TODO: add safety to check len(input) < 256 */ if (!strcmp(ui, ending)) { break; } else if (!strcmp(ui, "/help")) { /* TODO add help */ } else if (!strcmp(ui, "/hint")) { if (hinted_to == NUMBER_OF_CHARS - 1) { continue; /* do not give hint as hints are exhausted*/ } hinted_ending[hinted_to] = ending[hinted_to]; hinted_to++; if (--points == 0) { points = 1; } } else if (!strcmp(ui, "/giveup")) { points = -1; break; } else if (!strcmp(ui, "/quit")) { exit(0); } else { guesses++; if (--points == 0) { points = 1; } continue; } } return points; } void run(strarr *words) { int points = 0; for (;;) { /* Generate the first word, guaranteed to be at least the min length. */ const char *w = random_index(words, MIN_LENGTH); /* Find two words with matching endings - for now, just grab the first NUMBER_OF_WORDS entries we find, but... TODO: add logic to get more than just the first two entries. */ const char **others = NULL; /* Continually retries until a valid set found. If no valid words found after NUMBER_OF_TRIES attempts, just panic and die. */ int i = 0; while (i++ < NUMBER_OF_TRIES && !others) { others = get_matching_strs(words, w); } if (!others) { // printf("No words matching the ending of %s - retrying!\n", w); continue; } /* Ask the user what the suffix is. */ points += ask(w, others, points); free(others); } } int main(int argc, char *argv[]) { /* Setup. */ srand(time(NULL)); /* Parse arguments. */ for (int i = 0; i < argc; ++i) { if (argv[i][0] == '-') { char f = argv[i][1]; switch (f) { case 'l': MIN_LENGTH = strtol(argv[++i], NULL, 10); if (MIN_LENGTH < 2) { fprintf(stderr, "-l flag invalid\n"); exit(1); } break; case 'c': NUMBER_OF_CHARS = strtol(argv[++i], NULL, 10); if (NUMBER_OF_CHARS < 1) { fprintf(stderr, "-c flag invalid\n"); exit(2); } break; case 'n': NUMBER_OF_WORDS = strtol(argv[++i], NULL, 10); if (NUMBER_OF_WORDS < 2) { fprintf(stderr, "-n flag invalid\n"); exit(3); } break; case 't': NUMBER_OF_TRIES = strtol(argv[++i], NULL, 10); if (NUMBER_OF_TRIES < 2) { fprintf(stderr, "-t flag invalid\n"); exit(4); } break; } } } if (MIN_LENGTH <= NUMBER_OF_CHARS) { fprintf(stderr, "min length of word (-l) cannot be less than number of characters missing (-c)\n"); exit(5); } /* Read contents of file. */ FILE *fp = fopen("./words.txt", "r"); fseek(fp, 0, SEEK_END); long fsize = ftell(fp); fseek(fp, 0, SEEK_SET); char *words = malloc(fsize + 1); fread(words, fsize, 1, fp); fclose(fp); words[fsize] = 0; /* Convert into an array. */ // size_t words_sz = 0; strarr words_arr; strarr_init_from_str_split(&words_arr, words, '\n'); /* Run the game. */ run(&words_arr); strarr_free(&words_arr); return 0; }