From 50435465a62d630e4b61fbec38270321211c4907 Mon Sep 17 00:00:00 2001 From: "Jason S. Ninneman" Date: Wed, 28 Jun 2017 09:35:55 -0700 Subject: [PATCH] Completely wire vocab words in YAML to the code. This massive patch: * Finishes working all the vocab words into YAML structures. * Adds vocab ID generator functions. * Redoes the input-getting system, removing the need for GETIN(), GETTXT(), etc. * Changes advent<->ascii mapping to avoid the special 'shift' character. * Works around some bad behavior in the dragon attack logic. * Handles the reservoir magic word without changing the database contents. --- actions.c | 41 ++++++--- advent.h | 17 +++- adventure.yaml | 67 +++++++++++++- cheat.c | 2 +- common.c | 32 +++++++ common.h | 2 + main.c | 58 ++++++++---- misc.c | 227 ++++++++++++++++++++++++++++++++++++++++++++++- newdungeon.py | 57 +++++++++--- saveresume.c | 3 +- tests/logopt.chk | 6 +- 11 files changed, 460 insertions(+), 52 deletions(-) diff --git a/actions.c b/actions.c index 910d46c..311a71d 100644 --- a/actions.c +++ b/actions.c @@ -12,7 +12,7 @@ static void state_change(long obj, long state) pspeak(obj, change, state); } -static int attack(FILE *input, struct command_t *command) +static int attack(struct command_t *command) /* Attack. Assume target if unambiguous. "Throw" also links here. * Attackable objects fall into two categories: enemies (snake, * dwarf, etc.) and others (bird, clam, machine). Ambiguous if 2 @@ -86,9 +86,17 @@ static int attack(FILE *input, struct command_t *command) * fixed), move rug there (not fixed), and move him there, * too. Then do a null motion to get new description. */ rspeak(BARE_HANDS_QUERY); - GETIN(input, &command->wd1, &command->wd1x, &command->wd2, &command->wd2x); - if (command->wd1 != MAKEWD(WORD_YINIT) && command->wd1 != MAKEWD(WORD_YES)) - return GO_CHECKFOO; + if(silent_yes()) + { + // FIXME: setting wd1 is a workaround for broken logic + command->wd1 = token_to_packed("Y"); + } + else + { + // FIXME: setting wd1 is a workaround for broken logic + command->wd1 = token_to_packed("N"); + return GO_CHECKFOO; + } pspeak(DRAGON, look, 3); game.prop[DRAGON] = 1; game.prop[RUG] = 0; @@ -115,7 +123,10 @@ static int bigwords(token_t foo) * Look up foo in section 3 of vocab to determine which word we've got. Last * word zips the eggs back to the giant room (unless already there). */ { - int k = vocab(foo, 3); + //int k = vocab(foo, 3); + char word[6]; + packed_to_token(foo, word); + int k = (int) get_special_vocab_id(word); int spk = NOTHING_HAPPENS; if (game.foobar != 1 - k) { if (game.foobar != 0 && game.loc == LOC_GIANTROOM)spk = START_OVER; @@ -706,7 +717,8 @@ static int listen(void) int mi = game.prop[i]; if (i == BIRD) mi += 3 * game.blooded; - pspeak(i, hear, mi, game.zzword); + long packed_zzword = token_to_packed(game.zzword); + pspeak(i, hear, mi, packed_zzword); spk = NO_MESSAGE; /* FIXME: Magic number, sensitive to bird state logic */ if (i == BIRD && game.prop[i] == 5) @@ -884,7 +896,10 @@ static int say(struct command_t *command) b = command->wd2x; command->wd1 = command->wd2; } - int wd = vocab(command->wd1, -1); + //int wd = vocab(command->wd1, -1); + char word1[6]; + packed_to_token(command->wd1, word1); + int wd = (int) get_vocab_id(word1); /* FIXME: Magic numbers */ if (wd == 62 || wd == 65 || wd == 71 || wd == 2025 || wd == 2034) { /* FIXME: scribbles on the interpreter's command block */ @@ -902,7 +917,7 @@ static int throw_support(long spk) return GO_MOVE; } -static int throw (FILE *cmdin, struct command_t *command) +static int throw (struct command_t *command) /* Throw. Same as discard unless axe. Then same as attack except * ignore bird, and if dwarf is present then one might be killed. * (Only way to do so!) Axe also special for dragon, bear, and @@ -952,7 +967,7 @@ static int throw (FILE *cmdin, struct command_t *command) return GO_CLEAROBJ; } command->obj = 0; - return (attack(cmdin, command)); + return (attack(command)); } if (randrange(NDWARVES + 1) < game.dflag) { @@ -1015,7 +1030,7 @@ static int wave(token_t verb, token_t obj) } } -int action(FILE *input, struct command_t *command) +int action(struct command_t *command) /* Analyse a verb. Remember what it was, go back for object if second word * unless verb is "say", which snarfs arbitrary second word. */ @@ -1104,7 +1119,7 @@ int action(FILE *input, struct command_t *command) return GO_CLEAROBJ; } case ATTACK: - return attack(input, command); + return attack(command); case POUR: return pour(command->verb, command->obj); case EAT: @@ -1189,7 +1204,7 @@ int action(FILE *input, struct command_t *command) return GO_CLEAROBJ; } case ATTACK: - return attack(input, command); + return attack(command); case POUR: return pour(command->verb, command->obj); case EAT: @@ -1199,7 +1214,7 @@ int action(FILE *input, struct command_t *command) case RUB: return rub(command->verb, command->obj); case THROW: - return throw(input, command); + return throw(command); case QUIT: { rspeak(spk); return GO_CLEAROBJ; diff --git a/advent.h b/advent.h index 722b3fb..d161d04 100644 --- a/advent.h +++ b/advent.h @@ -20,6 +20,7 @@ #define FLASHTIME 50 /*turns from first warning till blinding flash */ #define PANICTIME 15 /* time left after closing */ #define BATTERYLIFE 2500 /* turn limit increment from batteries */ +#define WORD_NOT_FOUND -1 /* "Word not found" flag value for the vocab hash functions. */ typedef long token_t; /* word token - someday this will be char[TOKLEN+1] */ typedef long vocab_t; /* index into a vocabulary array */ @@ -62,7 +63,7 @@ struct game_t { long trnluz; /* # points lost so far due to number of turns used */ long turns; /* how many commands he's given (ignores yes/no) */ bool wzdark; /* whether the loc he's leaving was dark */ - long zzword; + char zzword[6]; bool blooded; /* has player drunk of dragon's blood? */ long abbrev[NLOCATIONS + 1]; long atloc[NLOCATIONS + 1]; @@ -91,9 +92,11 @@ enum speaktype {touch, look, hear, study, change}; /* b is not needed for POSIX but harmless */ #define READ_MODE "rb" #define WRITE_MODE "wb" +extern char* xstrdup(const char* s); extern void* xmalloc(size_t size); extern void packed_to_token(long, char token[]); -extern void token_to_packed(char token[], long*); +extern long token_to_packed(const char token[6]); +extern void tokenize(char*, long tokens[4]); extern void vspeak(const char*, va_list); extern bool wordeq(token_t, token_t); extern bool wordempty(token_t); @@ -103,11 +106,18 @@ extern void pspeak(vocab_t, enum speaktype, int, ...); extern void rspeak(vocab_t, ...); extern bool GETIN(FILE *, token_t*, token_t*, token_t*, token_t*); extern void echo_input(FILE*, char*, char*); +extern int word_count(char*); extern char* get_input(void); +extern bool silent_yes(void); extern bool yes(const char*, const char*, const char*); extern long GETTXT(bool, bool, bool); extern token_t MAKEWD(long); extern long vocab(long, long); +extern int get_motion_vocab_id(const char*); +extern int get_object_vocab_id(const char*); +extern int get_action_vocab_id(const char*); +extern int get_special_vocab_id(const char*); +extern long get_vocab_id(const char*); extern void juggle(long); extern void move(long, long); extern long put(long, long, long); @@ -117,6 +127,7 @@ extern long atdwrf(long); extern long setbit(long); extern bool tstbit(long, int); extern long rndvoc(long, long); +extern void make_zzword(char*); extern bool MAPLIN(FILE *); extern void datime(long*, long*); @@ -176,7 +187,7 @@ struct command_t { }; void initialise(void); -int action(FILE *input, struct command_t *command); +int action(struct command_t *command); /* Phase codes for action returns. * These were at one time FORTRAN line numbers. diff --git a/adventure.yaml b/adventure.yaml index 2228016..87ef4b0 100644 --- a/adventure.yaml +++ b/adventure.yaml @@ -618,6 +618,71 @@ actions: !!omap message: HUH_MAN words: !!null +specials: !!omap +- SPC_0: + message: !!null + words: !!null +- SPC_1: + message: !!null + words: ['fee'] +- SPC_2: + message: !!null + words: ['fie'] +- SPC_3: + message: !!null + words: ['foe'] +- SPC_4: + message: !!null + words: ['foo'] +- SPC_5: + message: !!null + words: ['fum'] +- SPC_13: + message: YOUR_WELCOME + words: ['thank'] +- SPC_50: + message: WORN_OUT + words: ['sesam', 'opens', 'abra', 'abrac', 'shaza', 'hocus', 'pocus'] +- SPC_51: + message: VOCAB_DESCRIPTION + words: ['help', '?'] +- SPC_54: + message: OK_MAN + words: ['no'] +- SPC_64: + message: FOREST_LOOK + words: ['tree', 'trees'] +- SPC_66: + message: DIGGING_FUTILE + words: ['dig', 'excav'] +- SPC_68: + message: IM_CONFUSED + words: ['lost'] +- SPC_69: + message: EXPLAIN_MIST + words: ['mist'] +- SPC_79: + message: WATCH_IT + words: ['fuck'] +- SPC_139: + message: STOP_UNKNOWN + words: ['stop'] +- SPC_142: + message: QUICK_START + words: ['info', 'infor'] +- SPC_147: + message: NOT_KNOWHOW + words: ['swim'] +- SPC_246: + message: WIZARDS_NODISTURB + words: ['wizar'] +- SPC_271: + message: GUESS_AGAIN + words: ['yes'] +- SPC_275: + message: ADVENTURE_NEWS + words: ['news'] + hints: - hint: &grate name: CAVE @@ -3141,7 +3206,7 @@ objects: !!omap - 'Even though it''s an oyster, the critter''s as tight-mouthed as a clam.' - 'It says the same thing it did before. Hm, maybe it''s a pun?' - MAGAZINE: - words: ['issue', 'spelu', '"spel'] + words: ['magaz', 'issue', 'spelu', '"spel'] inventory: '"Spelunker Today"' locations: LOC_ANTEROOM descriptions: diff --git a/cheat.c b/cheat.c index fecfdc2..f305dba 100644 --- a/cheat.c +++ b/cheat.c @@ -81,7 +81,7 @@ int main(int argc, char *argv[]) /* Initialize game variables */ initialise(); - game.zzword = rndvoc(3, 0); + make_zzword(game.zzword); game.newloc = LOC_START; game.loc = LOC_START; game.limit = GAMELIMIT; diff --git a/common.c b/common.c index 8418a09..51f7197 100644 --- a/common.c +++ b/common.c @@ -34,3 +34,35 @@ const char ascii_to_advent[] = { 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 123, 124, 125, 126, 83, }; + +const char new_advent_to_ascii[] = { + ' ', '!', '"', '#', '$', '%', '&', '\'', + '(', ')', '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', ':', ';', '<', '=', '>', '?', + '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', '\0', '\0', '\0', '\0', '\0', +}; + +const char new_ascii_to_advent[] = { + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, + + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, + + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, +}; diff --git a/common.h b/common.h index 2b08527..2aab83d 100644 --- a/common.h +++ b/common.h @@ -4,6 +4,8 @@ extern const char advent_to_ascii[128]; extern const char ascii_to_advent[128]; +extern const char new_advent_to_ascii[64]; +extern const char new_ascii_to_advent[128]; enum bugtype { TOO_MANY_VOCABULARY_WORDS, // 4 diff --git a/main.c b/main.c index 7ccffbb..2b264b9 100644 --- a/main.c +++ b/main.c @@ -20,6 +20,7 @@ #include #include #include +#include #include "advent.h" #include "database.h" #include "linenoise/linenoise.h" @@ -61,7 +62,7 @@ static void sig_handler(int signo) * Revived 2017 as Open Adventure. */ -static bool do_command(FILE *); +static bool do_command(void); int main(int argc, char *argv[]) { @@ -136,7 +137,7 @@ int main(int argc, char *argv[]) initialise(); /* Start-up, dwarf stuff */ - game.zzword = rndvoc(3, 0); + make_zzword(game.zzword); game.newloc = LOC_START; game.loc = LOC_START; game.limit = GAMELIMIT; @@ -153,7 +154,7 @@ int main(int argc, char *argv[]) /* interpret commands until EOF or interrupt */ for (;;) { - if (!do_command(stdin)) + if (!do_command()) break; } /* show score and exit */ @@ -170,7 +171,7 @@ static bool fallback_handler(char *buf) // autogenerated, so don't charge user time for it. --game.turns; // here we reconfigure any global game state that uses random numbers - game.zzword = rndvoc(3, 0); + make_zzword(game.zzword); return true; } return false; @@ -931,7 +932,7 @@ static void listobjects(void) } } -static bool do_command(FILE *cmdin) +static bool do_command() /* Get and execute a command */ { long V1, V2; @@ -1024,8 +1025,25 @@ L2600: game.knfloc = 0; /* This is where we get a new command from the user */ - if (!GETIN(cmdin, &command.wd1, &command.wd1x, &command.wd2, &command.wd2x)) - return false; + char* input; + for (;;) { + input = get_input(); + if (input == NULL) + return(false); + if (word_count(input) > 2) + { + rspeak(TWO_WORDS); + continue; + } + if (strcmp(input, "") != 0) + break; + } + long tokens[4]; + tokenize(input, tokens); + command.wd1 = tokens[0]; + command.wd1x = tokens[1]; + command.wd2 = tokens[2]; + command.wd2x = tokens[3]; /* Every input, check "game.foobar" flag. If zero, nothing's * going on. If pos, make neg. If neg, he skipped a word, @@ -1057,8 +1075,12 @@ L2607: } else lampcheck(); - V1 = vocab(command.wd1, -1); - V2 = vocab(command.wd2, -1); + char word1[6]; + char word2[6]; + packed_to_token(command.wd1, word1); + packed_to_token(command.wd2, word2); + V1 = get_vocab_id(word1); + V2 = get_vocab_id(word2); if (V1 == ENTER && (V2 == STREAM || V2 == 1000 + WATER)) { if (LIQLOC(game.loc) == WATER) { rspeak(FEET_WET); @@ -1076,26 +1098,27 @@ L2607: if (!((V1 != 1000 + WATER && V1 != 1000 + OIL) || (V2 != 1000 + PLANT && V2 != 1000 + DOOR))) { if (AT(V2 - 1000)) - command.wd2 = MAKEWD(WORD_POUR); + command.wd2 = token_to_packed("POUR"); } if (V1 == 1000 + CAGE && V2 == 1000 + BIRD && HERE(CAGE) && HERE(BIRD)) - command.wd1 = MAKEWD(WORD_CATCH); + command.wd1 = token_to_packed("CATCH"); } L2620: - if (wordeq(command.wd1, MAKEWD(WORD_WEST))) { + if (wordeq(command.wd1, token_to_packed("WEST"))) { ++game.iwest; if (game.iwest == 10) rspeak(W_IS_WEST); } - if (wordeq(command.wd1, MAKEWD(WORD_GO)) && !wordempty(command.wd2)) { + if (wordeq(command.wd1, token_to_packed("GO")) && !wordempty(command.wd2)) { if (++igo == 10) rspeak(GO_UNNEEDED); } Lookup: - defn = vocab(command.wd1, -1); + packed_to_token(command.wd1, word1); + defn = get_vocab_id(word1); if (defn == -1) { /* Gee, I don't understand. */ - if (fallback_handler(rawbuf)) + if (fallback_handler(input)) continue; rspeak(DONT_KNOW, command.wd1, command.wd1x); goto L2600; @@ -1116,14 +1139,14 @@ Lookup: command.verb = kmod; break; case 3: - rspeak(kmod); + rspeak(specials[kmod].message); goto L2012; default: BUG(VOCABULARY_TYPE_N_OVER_1000_NOT_BETWEEN_0_AND_3); // LCOV_EXCL_LINE } Laction: - switch (action(cmdin, &command)) { + switch (action(&command)) { case GO_TERMINATE: return true; case GO_MOVE: @@ -1158,6 +1181,7 @@ Laction: default: BUG(ACTION_RETURNED_PHASE_CODE_BEYOND_END_OF_SWITCH); // LCOV_EXCL_LINE } + linenoiseFree(input); } } diff --git a/misc.c b/misc.c index be6ec81..61d80d8 100644 --- a/misc.c +++ b/misc.c @@ -11,6 +11,18 @@ #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 +40,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 +56,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) @@ -236,6 +297,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 @@ -259,7 +337,9 @@ char* get_input() // Should be unreachable in tests, as they will use a non-interactive shell. printf("%s", input_prompt); // LCOV_EXCL_STOP - IGNORE(getline(&input, &n, stdin)); + 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. @@ -284,6 +364,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. */ @@ -317,7 +439,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) { @@ -434,6 +556,94 @@ long vocab(long id, long init) BUG(RAN_OFF_END_OF_VOCABULARY_TABLE); // LCOV_EXCL_LINE } +int get_motion_vocab_id(const char* word) +// Return the first motion number that has 'word' as one of its words. +{ + 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); +} + +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); + } + } + // If execution reaches here, we didn't find the word. + return(WORD_NOT_FOUND); +} + +int get_action_vocab_id(const char* word) +// Return the first motion number that has 'word' as one of its words. +{ + 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); +} + +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); + } + } + // If execution reaches here, we didn't find the word. + return(WORD_NOT_FOUND); +} + +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 + + 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) /* Juggle an object by picking it up and putting it down again, the purpose * being to get the object to the front of the chain of things at its loc. */ @@ -595,6 +805,15 @@ long rndvoc(long second, long force) return rnd; } +void make_zzword(char zzword[6]) +{ + for (int i = 0; i < 5; ++i) + { + zzword[i] = 'A' + randrange(26); + } + zzword[1] = '\''; // force second char to apostrophe + zzword[5] = '\0'; +} /* Machine dependent routines (MAPLIN, SAVEIO) */ diff --git a/newdungeon.py b/newdungeon.py index 022580c..d1ea21c 100755 --- a/newdungeon.py +++ b/newdungeon.py @@ -85,6 +85,12 @@ h_template = """/* Generated from adventure.yaml - do not hand-hack! */ #define COND_HJADE 20 /* Found all treasures except jade */ typedef struct {{ + const char** strs; + const int n; +}} string_group_t; + +typedef struct {{ + const string_group_t words; const char* inventory; int plac, fixd; bool is_treasure; @@ -130,11 +136,11 @@ typedef struct {{ }} hint_t; typedef struct {{ - const char** words; + const string_group_t words; }} motion_t; typedef struct {{ - const char** words; + const string_group_t words; const long message; }} action_t; @@ -166,6 +172,7 @@ extern const hint_t hints[]; extern long conditions[]; extern const motion_t motions[]; extern const action_t actions[]; +extern const action_t specials[]; extern const travelop_t travel[]; extern const long tkey[]; @@ -175,7 +182,9 @@ extern const long tkey[]; #define NCLASSES {} #define NDEATHS {} #define NTHRESHOLDS {} +#define NMOTIONS {} #define NACTIONS {} +#define NSPECIALS {} #define NTRAVEL {} #define NKEYS {} @@ -199,6 +208,10 @@ enum action_refs {{ {} }}; +enum special_refs {{ +{} +}}; + /* State definitions */ {} @@ -250,6 +263,10 @@ const action_t actions[] = {{ {} }}; +const action_t specials[] = {{ +{} +}}; + {} const travelop_t travel[] = {{ @@ -278,6 +295,19 @@ def get_refs(l): ref_str = ref_str[:-1] # trim trailing newline return ref_str +def get_string_group(strings): + template = """{{ + .strs = {}, + .n = {}, + }}""" + if strings == []: + strs = "NULL" + else: + strs = "(const char* []) {" + ", ".join([make_c_string(s) for s in strings]) + "}" + n = len(strings) + sg_str = template.format(strs, n) + return sg_str + def get_arbitrary_messages(arb): template = """ {}, """ @@ -339,6 +369,7 @@ def get_locations(loc): def get_objects(obj): template = """ {{ // {} + .words = {}, .inventory = {}, .plac = {}, .fixd = {}, @@ -360,6 +391,10 @@ def get_objects(obj): obj_str = "" for (i, item) in enumerate(obj): attr = item[1] + try: + words_str = get_string_group(attr["words"]) + except KeyError: + words_str = get_string_group([]) i_msg = make_c_string(attr["inventory"]) descriptions_str = "" if attr["descriptions"] == None: @@ -412,7 +447,7 @@ def get_objects(obj): sys.stderr.write("dungeon: unknown object location in %s\n" % locs) sys.exit(1) treasure = "true" if attr.get("treasure") else "false" - obj_str += template.format(i, i_msg, locs[0], locs[1], treasure, descriptions_str, sounds_str, texts_str, changes_str) + obj_str += template.format(i, words_str, i_msg, locs[0], locs[1], treasure, descriptions_str, sounds_str, texts_str, changes_str) obj_str = obj_str[:-1] # trim trailing newline return obj_str @@ -493,10 +528,9 @@ def get_motions(motions): for motion in motions: contents = motion[1] if contents["words"] == None: - mot_str += template.format("NULL") - continue - c_words = [make_c_string(s) for s in contents["words"]] - words_str = "(const char* []) {" + ", ".join(c_words) + "}" + words_str = get_string_group([]) + else: + words_str = get_string_group(contents["words"]) mot_str += template.format(words_str) return mot_str @@ -511,10 +545,9 @@ def get_actions(actions): contents = action[1] if contents["words"] == None: - words_str = "NULL" + words_str = get_string_group([]) else: - c_words = [make_c_string(s) for s in contents["words"]] - words_str = "(const char* []) {" + ", ".join(c_words) + "}" + words_str = get_string_group(contents["words"]) if contents["message"] == None: message = "NO_MESSAGE" @@ -688,6 +721,7 @@ if __name__ == "__main__": get_condbits(db["locations"]), get_motions(db["motions"]), get_actions(db["actions"]), + get_actions(db["specials"]), "const long tkey[] = {%s};" % bigdump(tkey), get_travel(travel), ) @@ -699,7 +733,9 @@ if __name__ == "__main__": len(db["classes"])-1, len(db["obituaries"]), len(db["turn_thresholds"]), + len(db["motions"]), len(db["actions"]), + len(db["specials"]), len(travel), len(tkey), get_refs(db["arbitrary_messages"]), @@ -707,6 +743,7 @@ if __name__ == "__main__": get_refs(db["objects"]), get_refs(db["motions"]), get_refs(db["actions"]), + get_refs(db["specials"]), statedefines, ) diff --git a/saveresume.c b/saveresume.c index f4c694f..d291361 100644 --- a/saveresume.c +++ b/saveresume.c @@ -119,7 +119,8 @@ int restore(FILE* fp) rspeak(VERSION_SKEW, save.version / 10, MOD(save.version, 10), VRSION / 10, MOD(VRSION, 10)); } else { memcpy(&game, &save.game, sizeof(struct game_t)); - game.zzword = rndvoc(3, game.zzword); + //game.zzword = rndvoc(3, game.zzword); + make_zzword(game.zzword); } return GO_TOP; } diff --git a/tests/logopt.chk b/tests/logopt.chk index 06c8fba..4defeae 100644 --- a/tests/logopt.chk +++ b/tests/logopt.chk @@ -7,7 +7,8 @@ You are standing at the end of a road before a small brick building. Around you is a forest. A small stream flows out of the building and down a gully. -> +> in + You are inside a building, a well house for a large spring. There are some keys on the ground here. @@ -19,7 +20,8 @@ There is food here. There is a bottle of water here. > -> + + You scored 32 out of a possible 430, using 1 turn. You are obviously a rank amateur. Better luck next time. -- 2.31.1