X-Git-Url: https://jxself.org/git/?p=open-adventure.git;a=blobdiff_plain;f=misc.c;h=dbf4abb8f0dee6f63be8cec42cdc78e4d06a2fcd;hp=81f8e2b2bd4f0212d258495fb0bed42403a7501b;hb=83fb64b5a817de3f1023c79ab2319d01d9dce9ae;hpb=6521d49c0752da0bf65769a1d38b583d3a2a1854 diff --git a/misc.c b/misc.c index 81f8e2b..dbf4abb 100644 --- a/misc.c +++ b/misc.c @@ -7,10 +7,21 @@ #include #include "advent.h" -#include "database.h" #include "linenoise/linenoise.h" #include "newdb.h" +char* xstrdup(const char* s) +{ + char* ptr = strdup(s); + if (ptr == NULL) { + // LCOV_EXCL_START + // exclude from coverage analysis because we can't simulate an out of memory error in testing + fprintf(stderr, "Out of memory!\n"); + exit(EXIT_FAILURE); + } + return(ptr); +} + void* xmalloc(size_t size) { void* ptr = malloc(size); @@ -28,8 +39,8 @@ void packed_to_token(long packed, char token[6]) { // Unpack and map back to ASCII. for (int i = 0; i < 5; ++i) { - char advent = (packed >> i * 6) & 63; - token[4 - i] = advent_to_ascii[(int) advent]; + char advent = (packed >> i * 6) & 63; + token[i] = new_advent_to_ascii[(int) advent]; } // Ensure the last character is \0. @@ -44,6 +55,55 @@ void packed_to_token(long packed, char token[6]) } } +long token_to_packed(const char token[6]) +{ + size_t t_len = strlen(token); + long packed = 0; + for (size_t i = 0; i < t_len; ++i) + { + char mapped = new_ascii_to_advent[(int) token[i]]; + packed |= (mapped << (6 * i)); + } + return(packed); +} + +void tokenize(char* raw, long tokens[4]) +{ + // set each token to 0 + for (int i = 0; i < 4; ++i) + tokens[i] = 0; + + // grab the first two words + char* words[2]; + words[0] = (char*) xmalloc(strlen(raw)); + words[1] = (char*) xmalloc(strlen(raw)); + int word_count = sscanf(raw, "%s%s", words[0], words[1]); + + // make space for substrings and zero it out + char chunk_data[][6] = { + {"\0\0\0\0\0"}, + {"\0\0\0\0\0"}, + {"\0\0\0\0\0"}, + {"\0\0\0\0\0"}, + }; + + // break the words into up to 4 5-char substrings + sscanf(words[0], "%5s%5s", chunk_data[0], chunk_data[1]); + if (word_count == 2) + sscanf(words[1], "%5s%5s", chunk_data[2], chunk_data[3]); + free(words[0]); + free(words[1]); + + // uppercase all the substrings + for (int i = 0; i < 4; ++i) + for (unsigned int j = 0; j < strlen(chunk_data[i]); ++j) + chunk_data[i][j] = (char) toupper(chunk_data[i][j]); + + // pack the substrings + for (int i = 0; i < 4; ++i) + tokens[i] = token_to_packed(chunk_data[i]); +} + /* Hide the fact that wods are corrently packed longs */ bool wordeq(token_t a, token_t b) @@ -166,7 +226,7 @@ void pspeak(vocab_t msg, enum speaktype mode, int skip, ...) vspeak(objects[msg].inventory, ap); break; case look: - vspeak(objects[msg].longs[skip], ap); + vspeak(objects[msg].descriptions[skip], ap); break; case hear: vspeak(objects[msg].sounds[skip], ap); @@ -174,6 +234,9 @@ void pspeak(vocab_t msg, enum speaktype mode, int skip, ...) case study: vspeak(objects[msg].texts[skip], ap); break; + case change: + vspeak(objects[msg].changes[skip], ap); + break; } va_end(ap); } @@ -187,42 +250,6 @@ void rspeak(vocab_t i, ...) va_end(ap); } -bool GETIN(FILE *input, - long *pword1, long *pword1x, - long *pword2, long *pword2x) -/* Get a command from the adventurer. Snarf out the first word, pad it with - * blanks, and return it in WORD1. Chars 6 thru 10 are returned in WORD1X, in - * case we need to print out the whole word in an error message. Any number of - * blanks may follow the word. If a second word appears, it is returned in - * WORD2 (chars 6 thru 10 in WORD2X), else WORD2 is -1. */ -{ - long junk; - - for (;;) { - if (game.blklin) - fputc('\n', stdout);; - if (!MAPLIN(input)) - return false; - *pword1 = GETTXT(true, true, true); - if (game.blklin && *pword1 < 0) - continue; - *pword1x = GETTXT(false, true, true); - do { - junk = GETTXT(false, true, true); - } while - (junk > 0); - *pword2 = GETTXT(true, true, true); - *pword2x = GETTXT(false, true, true); - do { - junk = GETTXT(false, true, true); - } while - (junk > 0); - if (GETTXT(true, true, true) <= 0) - return true; - rspeak(TWO_WORDS); - } -} - void echo_input(FILE* destination, char* input_prompt, char* input) { size_t len = strlen(input_prompt) + strlen(input) + 1; @@ -233,6 +260,23 @@ void echo_input(FILE* destination, char* input_prompt, char* input) free(prompt_and_input); } +int word_count(char* s) +{ + char* copy = xstrdup(s); + char delims[] = " \t"; + int count = 0; + char* word; + + word = strtok(copy, delims); + while(word != NULL) + { + word = strtok(NULL, delims); + ++count; + } + free(copy); + return(count); +} + char* get_input() { // Set up the prompt @@ -252,8 +296,13 @@ char* get_input() input = NULL; size_t n = 0; if (isatty(0)) + // LCOV_EXCL_START + // Should be unreachable in tests, as they will use a non-interactive shell. printf("%s", input_prompt); - IGNORE(getline(&input, &n, stdin)); + // LCOV_EXCL_STOP + ssize_t numread = getline(&input, &n, stdin); + if (numread == -1) // Got EOF; return with it. + return(NULL); } if (input == NULL) // Got EOF; return with it. @@ -278,6 +327,48 @@ char* get_input() return (input); } +bool silent_yes() +{ + char* reply; + bool outcome; + + for (;;) { + reply = get_input(); + if (reply == NULL) { + // LCOV_EXCL_START + // Should be unreachable. Reply should never be NULL + linenoiseFree(reply); + exit(EXIT_SUCCESS); + // LCOV_EXCL_STOP + } + + char* firstword = (char*) xmalloc(strlen(reply)+1); + sscanf(reply, "%s", firstword); + + for (int i = 0; i < (int)strlen(firstword); ++i) + firstword[i] = tolower(firstword[i]); + + int yes = strncmp("yes", firstword, sizeof("yes") - 1); + int y = strncmp("y", firstword, sizeof("y") - 1); + int no = strncmp("no", firstword, sizeof("no") - 1); + int n = strncmp("n", firstword, sizeof("n") - 1); + + free(firstword); + + if (yes == 0 || y == 0) { + outcome = true; + break; + } else if (no == 0 || n == 0) { + outcome = false; + break; + } else + rspeak(PLEASE_ANSWER); + } + linenoiseFree(reply); + return (outcome); +} + + bool yes(const char* question, const char* yes_response, const char* no_response) /* Print message X, wait for yes/no answer. If yes, print Y and return true; * if no, print Z and return false. */ @@ -289,10 +380,13 @@ bool yes(const char* question, const char* yes_response, const char* no_response speak(question); reply = get_input(); - if (reply == NULL) { - linenoiseFree(reply); - exit(EXIT_SUCCESS); - } + if (reply == NULL) { + // LCOV_EXCL_START + // Should be unreachable. Reply should never be NULL + linenoiseFree(reply); + exit(EXIT_SUCCESS); + // LCOV_EXCL_STOP + } char* firstword = (char*) xmalloc(strlen(reply)+1); sscanf(reply, "%s", firstword); @@ -308,7 +402,7 @@ bool yes(const char* question, const char* yes_response, const char* no_response free(firstword); if (yes == 0 || y == 0) { - speak(yes_response); + speak(yes_response); outcome = true; break; } else if (no == 0 || n == 0) { @@ -322,107 +416,94 @@ bool yes(const char* question, const char* yes_response, const char* no_response return (outcome); } -/* Line-parsing routines (GETTXT, MAKEWD, PUTTXT, SHFTXT) */ +/* Data structure routines */ -long GETTXT(bool skip, bool onewrd, bool upper) -/* Take characters from an input line and pack them into 30-bit words. - * Skip says to skip leading blanks. ONEWRD says stop if we come to a - * blank. UPPER says to map all letters to uppercase. If we reach the - * end of the line, the word is filled up with blanks (which encode as 0's). - * If we're already at end of line when TEXT is called, we return -1. */ +int get_motion_vocab_id(const char* word) +// Return the first motion number that has 'word' as one of its words. { - long text; - static long splitting = -1; - - if (LNPOSN != splitting) - splitting = -1; - text = -1; - while (true) { - if (LNPOSN > LNLENG) - return (text); - if ((!skip) || INLINE[LNPOSN] != 0) - break; - ++LNPOSN; + for (int i = 0; i < NMOTIONS; ++i) + { + for (int j = 0; j < motions[i].words.n; ++j) + { + if (strcasecmp(word, motions[i].words.strs[j]) == 0) + return(i); + } } + // If execution reaches here, we didn't find the word. + return(WORD_NOT_FOUND); +} - text = 0; - for (int I = 1; I <= TOKLEN; I++) { - text = text * 64; - if (LNPOSN > LNLENG || (onewrd && INLINE[LNPOSN] == 0)) - continue; - char current = INLINE[LNPOSN]; - if (current < ascii_to_advent['%']) { - splitting = -1; - if (upper && current >= ascii_to_advent['a']) - current = current - 26; - text = text + current; - ++LNPOSN; - continue; - } - if (splitting != LNPOSN) { - text = text + ascii_to_advent['%']; - splitting = LNPOSN; - continue; - } - - text = text + current - ascii_to_advent['%']; - splitting = -1; - ++LNPOSN; +int get_object_vocab_id(const char* word) +// Return the first object number that has 'word' as one of its words. +{ + for (int i = 0; i < NOBJECTS + 1; ++i) // FIXME: the + 1 should go when 1-indexing for objects is removed + { + for (int j = 0; j < objects[i].words.n; ++j) + { + if (strcasecmp(word, objects[i].words.strs[j]) == 0) + return(i); + } } - - return text; + // If execution reaches here, we didn't find the word. + return(WORD_NOT_FOUND); } -token_t MAKEWD(long letters) -/* Combine TOKLEN (currently 5) uppercase letters (represented by - * pairs of decimal digits in lettrs) to form a 30-bit value matching - * the one that GETTXT would return given those characters plus - * trailing blanks. Caution: lettrs will overflow 31 bits if - * 5-letter word starts with V-Z. As a kludgey workaround, you can - * increment a letter by 5 by adding 50 to the next pair of - * digits. */ +int get_action_vocab_id(const char* word) +// Return the first motion number that has 'word' as one of its words. { - long i = 1, word = 0; + for (int i = 0; i < NACTIONS; ++i) + { + for (int j = 0; j < actions[i].words.n; ++j) + { + if (strcasecmp(word, actions[i].words.strs[j]) == 0) + return(i); + } + } + // If execution reaches here, we didn't find the word. + return(WORD_NOT_FOUND); +} - for (long k = letters; k != 0; k = k / 100) { - word = word + i * (MOD(k, 50) + 10); - i = i * 64; - if (MOD(k, 100) > 50)word = word + i * 5; +int get_special_vocab_id(const char* word) +// Return the first special number that has 'word' as one of its words. +{ + for (int i = 0; i < NSPECIALS; ++i) + { + for (int j = 0; j < specials[i].words.n; ++j) + { + if (strcasecmp(word, specials[i].words.strs[j]) == 0) + return(i); + } } - i = 64L * 64L * 64L * 64L * 64L / i; - word = word * i; - return word; + // If execution reaches here, we didn't find the word. + return(WORD_NOT_FOUND); } -/* Data structure routines */ +long get_vocab_id(const char* word) +// Search the vocab categories in order for the supplied word. +{ + long ref_num; + + ref_num = get_motion_vocab_id(word); + if (ref_num != WORD_NOT_FOUND) + return(ref_num + 0); // FIXME: replace with a proper hash -long vocab(long id, long init) -/* Look up ID in the vocabulary (ATAB) and return its "definition" (KTAB), or - * -1 if not found. If INIT is positive, this is an initialisation call setting - * up a keyword variable, and not finding it constitutes a bug. It also means - * that only KTAB values which taken over 1000 equal INIT may be considered. - * (Thus "STEPS", which is a motion verb as well as an object, may be located - * as an object.) And it also means the KTAB value is taken modulo 1000. */ -{ - long lexeme; - - for (long i = 1; i <= TABSIZ; i++) { - if (KTAB[i] == -1) { - lexeme = -1; - if (init < 0) - return (lexeme); - BUG(REQUIRED_VOCABULARY_WORD_NOT_FOUND); // LCOV_EXCL_LINE - } - if (init >= 0 && KTAB[i] / 1000 != init) - continue; - if (ATAB[i] == id) { - lexeme = KTAB[i]; - if (init >= 0) - lexeme = MOD(lexeme, 1000); - return (lexeme); - } - } - BUG(RAN_OFF_END_OF_VOCABULARY_TABLE); // LCOV_EXCL_LINE + ref_num = get_object_vocab_id(word); + if (ref_num != WORD_NOT_FOUND) + return(ref_num + 1000); // FIXME: replace with a proper hash + + ref_num = get_action_vocab_id(word); + if (ref_num != WORD_NOT_FOUND) + return(ref_num + 2000); // FIXME: replace with a proper hash + + ref_num = get_special_vocab_id(word); + if (ref_num != WORD_NOT_FOUND) + return(ref_num + 3000); // FIXME: replace with a proper hash + + // Check for the reservoir magic word. + if (strcasecmp(word, game.zzword) == 0) + return(PART + 2000); // FIXME: replace with a proper hash + + return(WORD_NOT_FOUND); } void juggle(long object) @@ -529,7 +610,7 @@ long atdwrf(long where) long setbit(long bit) /* Returns 2**bit for use in constructing bit-masks. */ { - return (1 << bit); + return (1L << bit); } bool tstbit(long mask, int bit) @@ -558,35 +639,16 @@ long randrange(long range) return range * get_next_lcg_value() / game.lcg_m; } -long rndvoc(long second, long force) -/* Searches the vocabulary ATAB for a word whose second character is - * char, and changes that word such that each of the other four - * characters is a random letter. If force is non-zero, it is used - * as the new word. Returns the new word. */ +void make_zzword(char zzword[6]) { - long rnd = force; - - if (rnd == 0) { - for (int i = 1; i <= 5; i++) { - long j = 11 + randrange(26); - if (i == 2) - j = second; - rnd = rnd * 64 + j; - } - } - - long div = 64L * 64L * 64L; - for (int i = 1; i <= TABSIZ; i++) { - if (MOD(ATAB[i] / div, 64L) == second) { - ATAB[i] = rnd; - break; - } + for (int i = 0; i < 5; ++i) + { + zzword[i] = 'A' + randrange(26); } - - return rnd; + zzword[1] = '\''; // force second char to apostrophe + zzword[5] = '\0'; } - /* Machine dependent routines (MAPLIN, SAVEIO) */ bool MAPLIN(FILE *fp)