diff --git a/INSTALL.adoc b/INSTALL.adoc index 5184d6b..fb93b7d 100644 --- a/INSTALL.adoc +++ b/INSTALL.adoc @@ -23,3 +23,5 @@ You can also use pip to install PyYAML: `pip3 install PyYAML`. 5. Run `./advent` to play. 6. If you want to buld the documentation you will need asciidoctor. + +7. Running the regression tests requires batchspell diff --git a/Makefile b/Makefile index 0da7fb8..e4e31cb 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ VERS=$(shell sed -n // SPDX-License-Identifier: CC-BY-4.0 +1.20: 2024-09-23:: + Make oldstyle correctly suppress line editing. + +1.19: 2024-06-27:: + Ensore that the KNIVES_VANISH message can't issue twice. + 1.18: 2024-02-15:: Bring the manual page fully up to date. diff --git a/actions.c b/actions.c index 31a09c8..469e92f 100644 --- a/actions.c +++ b/actions.c @@ -246,7 +246,7 @@ static phase_codes_t bigwords(vocab_t id) { static void blast(void) { /* Blast. No effect unless you've got dynamite, which is a neat trick! */ - if (PROP_IS_NOTFOUND(ROD2) || !game.closed) { + if (OBJECT_IS_NOTFOUND(ROD2) || !game.closed) { rspeak(REQUIRES_DYNAMITE); } else { if (HERE(ROD2)) { @@ -329,8 +329,8 @@ static phase_codes_t vcarry(verb_t verb, obj_t obj) { if (game.objects[obj].fixed != IS_FREE) { switch (obj) { case PLANT: - /* Next guard tests whether plant is tiny or stashed */ - rspeak(game.objects[PLANT].prop <= PLANT_THIRSTY + rspeak((game.objects[PLANT].prop == PLANT_THIRSTY || + OBJECT_IS_STASHED(PLANT)) ? DEEP_ROOTS : YOU_JOKING); break; @@ -389,7 +389,7 @@ static phase_codes_t vcarry(verb_t verb, obj_t obj) { } if (obj == BIRD && game.objects[BIRD].prop != BIRD_CAGED && - !PROP_IS_STASHED(BIRD)) { + !OBJECT_IS_STASHED(BIRD)) { if (game.objects[BIRD].prop == BIRD_FOREST_UNCAGED) { DESTROY(BIRD); rspeak(BIRD_CRAP); @@ -406,8 +406,7 @@ static phase_codes_t vcarry(verb_t verb, obj_t obj) { game.objects[BIRD].prop = BIRD_CAGED; } if ((obj == BIRD || obj == CAGE) && - (game.objects[BIRD].prop == BIRD_CAGED || - PROP_STASHED(BIRD) == BIRD_CAGED)) { + OBJECT_STATE_EQUALS(BIRD, BIRD_CAGED)) { /* expression maps BIRD to CAGE and CAGE to BIRD */ carry(BIRD + CAGE - obj, game.loc); } @@ -418,8 +417,8 @@ static phase_codes_t vcarry(verb_t verb, obj_t obj) { game.objects[LIQUID()].place = CARRIED; } - if (GSTONE(obj) && !PROP_IS_FOUND(obj)) { - PROP_SET_FOUND(obj); + if (GSTONE(obj) && !OBJECT_IS_FOUND(obj)) { + OBJECT_SET_FOUND(obj); game.objects[CAVITY].prop = CAVITY_EMPTY; } rspeak(OK_MAN); @@ -677,7 +676,7 @@ static phase_codes_t extinguish(verb_t verb, obj_t obj) { break; case LAMP: state_change(LAMP, LAMP_DARK); - rspeak(DARK(game.loc) ? PITCH_DARK : NO_MESSAGE); + rspeak(IS_DARK_HERE() ? PITCH_DARK : NO_MESSAGE); break; case DRAGON: case VOLCANO: @@ -971,7 +970,7 @@ static phase_codes_t listen(void) { } for (obj_t i = 1; i <= NOBJECTS; i++) { if (!HERE(i) || objects[i].sounds[0] == NULL || - PROP_IS_STASHED_OR_UNSEEN(i)) { + OBJECT_IS_STASHED(i) || OBJECT_IS_NOTFOUND(i)) { continue; } int mi = game.objects[i].prop; @@ -1151,17 +1150,17 @@ static phase_codes_t read(command_t command) command.obj = NO_OBJECT; for (int i = 1; i <= NOBJECTS; i++) { if (HERE(i) && objects[i].texts[0] != NULL && - !PROP_IS_STASHED(i)) { + !OBJECT_IS_STASHED(i)) { command.obj = command.obj * NOBJECTS + i; } } if (command.obj > NOBJECTS || command.obj == NO_OBJECT || - DARK(game.loc)) { + IS_DARK_HERE()) { return GO_UNKNOWN; } } - if (DARK(game.loc)) { + if (IS_DARK_HERE()) { sspeak(NO_SEE, command.word[0].raw); } else if (command.obj == OYSTER) { if (!TOTING(OYSTER) || !game.closed) { @@ -1175,7 +1174,7 @@ static phase_codes_t read(command_t command) 1); // Not really a sound, but oh well. } } else if (objects[command.obj].texts[0] == NULL || - PROP_IS_NOTFOUND(command.obj)) { + OBJECT_IS_NOTFOUND(command.obj)) { speak(actions[command.verb].message); } else { pspeak(command.obj, study, true, @@ -1351,9 +1350,9 @@ static phase_codes_t wave(verb_t verb, obj_t obj) { } if (game.objects[BIRD].prop == BIRD_UNCAGED && - game.loc == game.objects[STEPS].place && PROP_IS_NOTFOUND(JADE)) { + game.loc == game.objects[STEPS].place && OBJECT_IS_NOTFOUND(JADE)) { drop(JADE, game.loc); - PROP_SET_FOUND(JADE); + OBJECT_SET_FOUND(JADE); --game.tally; rspeak(NECKLACE_FLY); return GO_CLEAROBJ; diff --git a/advent.adoc b/advent.adoc index d7231f6..a064d1a 100644 --- a/advent.adoc +++ b/advent.adoc @@ -3,6 +3,9 @@ // SPDX-FileCopyrightText: (C) Eric S. Raymond // SPDX-License-Identifier: CC-BY-4.0 +// batchspell: add advent logfile savefile roleplaying Gillogly PDP Ctrl-D +// batchspell: add EOF autosave endianness wumpus zork nethack + == NAME == advent - Colossal Cave Adventure @@ -62,7 +65,7 @@ argument of '-' is taken as a directive to read from standard input. The binary save file format is fragile, dependent on your machine's endianness, and unlikely to survive through version bumps. There are -version and emdianness checks when attempting to restore from a save. +version and endianness checks when attempting to restore from a save. The input parser was the first attempt *ever* at natural-language parsing in a game and has some known deficiencies. While later text diff --git a/advent.h b/advent.h index 2a23eb9..fa0767b 100644 --- a/advent.h +++ b/advent.h @@ -47,7 +47,6 @@ #define IS_FIXED -1 #define IS_FREE 0 -#ifndef FOUNDBOOL /* (ESR) It is fitting that translation of the original ADVENT should * have left us a maze of twisty little conditionals that resists all * understanding. Setting and use of what is now the per-object state @@ -61,71 +60,49 @@ * STATE_NOTFOUND is only set on treasures. Non-treasures start the * game in STATE_FOUND. * - * PROP_STASHED is supposed to map a state property value to a + * PROP_STASHIFY is supposed to map a state property value to a * negative range, where the object cannot be picked up but the value * can be recovered later. Various objects get this property when * the cave starts to close. Only seems to be significant for the bird * and readable objects, notably the clam/oyster - but the code around - * those test is difficult to read. - */ -#define PROP_STASHIFY(n) (-1 - (n)) -#define PROP_IS_STASHED(obj) (game.objects[obj].prop < STATE_NOTFOUND) -#define PROP_IS_NOTFOUND(obj) (game.objects[obj].prop == STATE_NOTFOUND) -#define PROP_IS_FOUND(obj) (game.objects[obj].prop == STATE_FOUND) -#define PROP_IS_STASHED_OR_UNSEEN(obj) (game.objects[obj].prop < 0) -#define PROP_SET_FOUND(obj) (game.objects[obj].prop = STATE_FOUND) -#define PROP_SET_NOT_FOUND(obj) (game.objects[obj].prop = STATE_NOTFOUND) -#define PROP_IS_NOTFOUND2(g, o) (g.objects[o].prop == STATE_NOTFOUND) -#define PROP_IS_INVALID(val) (val < -MAX_STATE - 1 || val > MAX_STATE) -#else -/* (ESR) Only the boldest of adventurers will explore here. This - * alternate set of definitions for the macros above was an attempt to - * break from out of the state encoding a per-object "found" member - * telling whether or not the player has seen the object. + * those tests is difficult to read. * - * What's broken when you try to use thus is - * PROP_IS_STASHED_OR_UNSEEN. The symptom is game.tally getting - * decremented on non-treasures. + * All tests of the prop member are done with either these macros or ==. */ -#define PROP_STASHIFY(n) (-(n)) -#define PROP_IS_STASHED(obj) (game.objects[obj].prop < 0) -#define PROP_IS_NOTFOUND(obj) (!game.objects[obj].found) -#define PROP_IS_FOUND(obj) \ - (game.objects[obj].found && game.objects[obj].prop == 0) -#define PROP_IS_STASHED_OR_UNSEEN(obj) \ - (!game.objects[obj].found || game.objects[obj].prop < 0) -#define PROP_SET_FOUND(obj) \ - do { \ - game.objects[obj].found = true; \ - game.objects[obj].prop = STATE_FOUND; \ - } while (0) -#define PROP_SET_NOT_FOUND(obj) game.objects[obj].found = false -#define PROP_IS_NOTFOUND2(g, o) (!g.objects[o].found) -#define PROP_IS_INVALID(val) (val < -MAX_STATE || val > MAX_STATE) -#define PROP_SET_SEEN(obj) game.objects[object].found = true -#endif -#define PROP_STASHED(obj) PROP_STASHIFY(game.objects[obj].prop) +#define OBJECT_IS_NOTFOUND(obj) (game.objects[obj].prop == STATE_NOTFOUND) +#define OBJECT_IS_FOUND(obj) (game.objects[obj].prop == STATE_FOUND) +#define OBJECT_SET_FOUND(obj) (game.objects[obj].prop = STATE_FOUND) +#define OBJECT_SET_NOT_FOUND(obj) (game.objects[obj].prop = STATE_NOTFOUND) +#define OBJECT_IS_NOTFOUND2(g, o) (g.objects[o].prop == STATE_NOTFOUND) +#define PROP_IS_INVALID(val) (val < -MAX_STATE - 1 || val > MAX_STATE) +#define PROP_STASHIFY(n) (-1 - (n)) +#define OBJECT_STASHIFY(obj, pval) game.objects[obj].prop = PROP_STASHIFY(pval) +#define OBJECT_IS_STASHED(obj) (game.objects[obj].prop < STATE_NOTFOUND) +#define OBJECT_STATE_EQUALS(obj, pval) \ + ((game.objects[obj].prop == pval) || \ + (game.objects[obj].prop == PROP_STASHIFY(pval))) #define PROMPT "> " /* - * DESTROY(N) = Get rid of an item by putting it in LOC_NOWHERE - * MOD(N,M) = Arithmetic modulus - * TOTING(OBJ) = true if the OBJ is being carried - * AT(OBJ) = true if on either side of two-placed object - * HERE(OBJ) = true if the OBJ is at "LOC" (or is being carried) - * CNDBIT(L,N) = true if COND(L) has bit n set (bit 0 is units bit) - * LIQUID() = object number of liquid in bottle - * LIQLOC(LOC) = object number of liquid (if any) at LOC - * FORCED(LOC) = true if LOC moves without asking for input (COND=2) - * DARK(LOC) = true if location "LOC" is dark - * PCT(N) = true N% of the time (N integer from 0 to 100) - * GSTONE(OBJ) = true if OBJ is a gemstone - * FOREST(LOC) = true if LOC is part of the forest - * OUTSID(LOC) = true if location not in the cave - * INSIDE(LOC) = true if location is in the cave or the building at the - * beginning of the game INDEEP(LOC) = true if location is in the Hall of Mists - * or deeper BUG(X) = report bug and exit + * DESTROY(N) = Get rid of an item by putting it in LOC_NOWHERE + * MOD(N,M) = Arithmetic modulus + * TOTING(OBJ) = true if the OBJ is being carried + * AT(OBJ) = true if on either side of two-placed object + * HERE(OBJ) = true if the OBJ is at "LOC" (or is being carried) + * CNDBIT(L,N) = true if COND(L) has bit n set (bit 0 is units bit) + * LIQUID() = object number of liquid in bottle + * LIQLOC(LOC) = object number of liquid (if any) at LOC + * FORCED(LOC) = true if LOC moves without asking for input (COND=2) + * IS_DARK_HERE() = true if location "LOC" is dark + * PCT(N) = true N% of the time (N integer from 0 to 100) + * GSTONE(OBJ) = true if OBJ is a gemstone + * FOREST(LOC) = true if LOC is part of the forest + * OUTSIDE(LOC) = true if location not in the cave + * INSIDE(LOC) = true if location is in the cave or the building at the + * beginning of the game + * INDEEP(LOC) = true if location is in the Hall of Mists or deeper + * BUG(X) = report bug and exit */ #define DESTROY(N) move(N, LOC_NOWHERE) #define MOD(N, M) ((N) % (M)) @@ -143,15 +120,15 @@ (CNDBIT((LOC), COND_FLUID) ? CNDBIT((LOC), COND_OILY) ? OIL : WATER \ : NO_OBJECT) #define FORCED(LOC) CNDBIT(LOC, COND_FORCED) -#define DARK(DUMMY) \ +#define IS_DARK_HERE() \ (!CNDBIT(game.loc, COND_LIT) && \ (game.objects[LAMP].prop == LAMP_DARK || !HERE(LAMP))) #define PCT(N) (randrange(100) < (N)) #define GSTONE(OBJ) \ ((OBJ) == EMERALD || (OBJ) == RUBY || (OBJ) == AMBER || (OBJ) == SAPPH) #define FOREST(LOC) CNDBIT(LOC, COND_FOREST) -#define OUTSID(LOC) (CNDBIT(LOC, COND_ABOVE) || FOREST(LOC)) -#define INSIDE(LOC) (!OUTSID(LOC) || LOC == LOC_BUILDING) +#define OUTSIDE(LOC) (CNDBIT(LOC, COND_ABOVE) || FOREST(LOC)) +#define INSIDE(LOC) (!OUTSIDE(LOC) || LOC == LOC_BUILDING) #define INDEEP(LOC) CNDBIT((LOC), COND_DEEP) #define BUG(x) bug(x, #x) @@ -257,11 +234,8 @@ struct game_t { loc_t oldloc; // prior loc of each dwarf, initially garbage } dwarves[NDWARVES + 1]; struct { -#ifdef FOUNDBOOL - bool32_t found; // has the location of this object been found? -#endif loc_t fixed; // fixed location of object (if not IS_FREE) - int32_t prop; // object state */ + int32_t prop; // object state loc_t place; // location of object } objects[NOBJECTS + 1]; struct { diff --git a/adventure.yaml b/adventure.yaml index 31988de..141ffb7 100644 --- a/adventure.yaml +++ b/adventure.yaml @@ -1,6 +1,10 @@ # SPDX-FileCopyrightText: (C) Eric S. Raymond # SPDX-License-Identifier: BSD-2-Clause # +# batchspell: add XYZZY Twopit Bedquilt ne se sw nw dwarves dwarvish gouts +# batchspell: add Crowther add axe pinin Har har meself Hmmm Adventuredom +# batchspell: add Tsk gameplay bugfixes +# # This YAML file gets processed into a collection of data structures and # variable initializers describing Colossal Cave. It replaces an ad-hoc # text database shipped with Adventure versions up to 2.5. The format @@ -10,7 +14,7 @@ # We define a bunch of YAML structures: # # motions: Motion words, grouped into synonyms. The 'oldstyle' -# attribute, if false, means that single-letter synonyms should be +# attribute, if false, means that single-letter synonyms should not be # accepted in oldstyle mode; it defaults to true. # # actions: Action words, grouped into synonyms, and their corresponding @@ -3432,7 +3436,7 @@ objects: !!omap - 'There are a few recent issues of "Spelunker Today" magazine here.' texts: - |- - I'm afraid the magazine is written in dwarvish. But pencilled on one + I'm afraid the magazine is written in dwarvish. But penciled on one cover you see, "Please leave the magazines at the construction site." - DWARF: words: ['dwarf', 'dwarv'] @@ -3940,11 +3944,13 @@ obituaries: Oh dear, you seem to have gotten yourself killed. I might be able to help you out, but I've never really done this before. Do you want me to try to reincarnate you? + # batchspell: add wr yes_response: |- All right. But don't blame me if something goes wr...... --- POOF!! --- You are engulfed in a cloud of orange smoke. Coughing and gasping, you emerge from the smoke and find.... + # batchspell: remove wr - query: |- You clumsy oaf, you've done it again! I don't know how long I can keep this up. Do you want me to try reincarnating you again? @@ -4226,7 +4232,7 @@ actions: !!omap message: |- There is a puff of orange smoke; within it, fiery runes spell out: - \tOpen Adventure %V - http://www.catb.org/esr/open-adventure/ + Open Adventure %V - http://www.catb.org/esr/open-adventure/ words: ['versi'] noaction: true diff --git a/init.c b/init.c index e413fff..372e494 100644 --- a/init.c +++ b/init.c @@ -1,7 +1,7 @@ /* * Initialisation * - * SPDX-FileCopyrightText: (C) 1977, 2005 by Will Crowther and Don Woodsm + * SPDX-FileCopyrightText: (C) 1977, 2005 by Will Crowther and Don Woods * SPDX-License-Identifier: BSD-2-Clause */ @@ -76,13 +76,18 @@ int initialise(void) { /* Treasure props are initially STATE_NOTFOUND, and are set to * STATE_FOUND the first time they are described. game.tally * keeps track of how many are not yet found, so we know when to - * close the cave. */ - for (int treasure = 1; treasure <= NOBJECTS; treasure++) { - if (objects[treasure].is_treasure) { + * close the cave. + * (ESR) Non-treasures are set to STATE_FOUND explicitly so we + * don't rely on the value of uninitialized storage. This is to + * make translation to future languages easier. */ + for (int object = 1; object <= NOBJECTS; object++) { + if (objects[object].is_treasure) { ++game.tally; - if (objects[treasure].inventory != 0) { - PROP_SET_NOT_FOUND(treasure); + if (objects[object].inventory != NULL) { + OBJECT_SET_NOT_FOUND(object); } + } else { + OBJECT_SET_FOUND(object); } } game.conds = setbit(COND_HBASE); diff --git a/main.c b/main.c index 7f5d8a1..835fc1b 100644 --- a/main.c +++ b/main.c @@ -81,7 +81,7 @@ char *myreadline(const char *prompt) { } } - if (isatty(fileno(settings.scriptfp))) { + if (isatty(fileno(settings.scriptfp)) && !settings.oldstyle) { free(buf); // LCOV_EXCL_LINE return readline(prompt); // LCOV_EXCL_LINE } else { @@ -152,8 +152,8 @@ static void checkhints(void) { game.hints[hint].lc = 0; return; case 4: /* dark */ - if (!PROP_IS_NOTFOUND(EMERALD) && - PROP_IS_NOTFOUND(PYRAMID)) { + if (!OBJECT_IS_NOTFOUND(EMERALD) && + OBJECT_IS_NOTFOUND(PYRAMID)) { break; } game.hints[hint].lc = 0; @@ -188,7 +188,8 @@ static void checkhints(void) { return; case 9: /* jade */ if (game.tally == 1 && - PROP_IS_STASHED_OR_UNSEEN(JADE)) { + (OBJECT_IS_STASHED(JADE) || + OBJECT_IS_NOTFOUND(JADE))) { break; } game.hints[hint].lc = 0; @@ -231,8 +232,8 @@ static bool spotted_by_pirate(int i) { * tally=1 for an unseen chest, let the pirate be spotted. Note * that game.objexts,place[CHEST] = LOC_NOWHERE might mean that he's * thrown it to the troll, but in that case he's seen the chest - * PROP_IS_FOUND(CHEST) == true. */ - if (game.loc == game.chloc || !PROP_IS_NOTFOUND(CHEST)) { + * OBJECT_IS_FOUND(CHEST) == true. */ + if (game.loc == game.chloc || !OBJECT_IS_NOTFOUND(CHEST)) { return true; } int snarfed = 0; @@ -533,7 +534,7 @@ static void describe_location(void) { msg = locations[game.loc].description.big; } - if (!FORCED(game.loc) && DARK(game.loc)) { + if (!FORCED(game.loc) && IS_DARK_HERE()) { msg = arbitrary_messages[PITCH_DARK]; } @@ -639,7 +640,7 @@ static void playermove(int motion) { } else if (motion == CAVE) { /* Cave. Different messages depending on whether above ground. */ - rspeak((OUTSID(game.loc) && game.loc != LOC_GRATE) + rspeak((OUTSIDE(game.loc) && game.loc != LOC_GRATE) ? FOLLOW_STREAM : NEED_DETAIL); return; @@ -1046,7 +1047,7 @@ static void listobjects(void) { * Similarly for chain; game.prop is initially CHAINING_BEAR (locked to * bear). These hacks are because game.prop=0 is needed to * get full score. */ - if (!DARK(game.loc)) { + if (!IS_DARK_HERE()) { ++game.locs[game.loc].abbrev; for (int i = game.locs[game.loc].atloc; i != 0; i = game.link[i]) { @@ -1061,11 +1062,11 @@ static void listobjects(void) { * running this code only on objects with the treasure * property set. Nope. There is mystery here. */ - if (PROP_IS_STASHED_OR_UNSEEN(obj)) { + if (OBJECT_IS_STASHED(i) || OBJECT_IS_NOTFOUND(obj)) { if (game.closed) { continue; } - PROP_SET_FOUND(obj); + OBJECT_SET_FOUND(obj); if (obj == RUG) { game.objects[RUG].prop = RUG_DRAGON; } @@ -1192,7 +1193,7 @@ static bool preprocess_command(command_t *command) { static bool do_move(void) { /* Actually execute the move to the new location and dwarf movement */ /* Can't leave cave once it's closing (except by main office). */ - if (OUTSID(game.newloc) && game.newloc != 0 && game.closng) { + if (OUTSIDE(game.newloc) && game.newloc != 0 && game.closng) { rspeak(EXIT_CLOSED); game.newloc = game.loc; if (!game.panic) { @@ -1228,7 +1229,7 @@ static bool do_move(void) { /* The easiest way to get killed is to fall into a pit in * pitch darkness. */ - if (!FORCED(game.loc) && DARK(game.loc) && game.wzdark && + if (!FORCED(game.loc) && IS_DARK_HERE() && game.wzdark && PCT(PIT_KILL_PROB)) { rspeak(PIT_FALL); game.oldlc2 = game.loc; @@ -1266,25 +1267,28 @@ static bool do_command(void) { * way objects won't be described until they've * been picked up and put down separate from * their respective piles. */ - if ((PROP_IS_NOTFOUND(OYSTER) || - PROP_IS_STASHED(OYSTER)) && + if ((OBJECT_IS_NOTFOUND(OYSTER) || + OBJECT_IS_STASHED(OYSTER)) && TOTING(OYSTER)) { pspeak(OYSTER, look, true, 1); } for (size_t i = 1; i <= NOBJECTS; i++) { - if (TOTING(i) && (PROP_IS_NOTFOUND(i) || - PROP_IS_STASHED(i))) { - game.objects[i].prop = - PROP_STASHED(i); + if (TOTING(i) && + (OBJECT_IS_NOTFOUND(i) || + OBJECT_IS_STASHED(i))) { + OBJECT_STASHIFY( + i, game.objects[i].prop); } } } - /* Check to see if the room is dark. If the knife is - * here, and it's dark, the knife permanently disappears - */ - game.wzdark = DARK(game.loc); - if (game.knfloc != LOC_NOWHERE && + /* Check to see if the room is dark. */ + game.wzdark = IS_DARK_HERE(); + + /* If the knife is not here it permanently disappears. + * Possibly this should fire if the knife is here but + * the room is dark? */ + if (game.knfloc > LOC_NOWHERE && game.knfloc != game.loc) { game.knfloc = LOC_NOWHERE; } diff --git a/misc.c b/misc.c index 9e90d05..b28949e 100644 --- a/misc.c +++ b/misc.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -495,8 +496,8 @@ static void tokenize(char *raw, command_t *cmd) { memset(&cmd->word[1].raw, '\0', sizeof(cmd->word[1].raw)); /* Bound prefix on the %s would be needed to prevent buffer - * overflow. but we shortstop this more simply by making each - * raw-input buffer as int as the entire input buffer. */ + * overflow. We shortstop this more simply by making each + * raw-input buffer as long as the entire input buffer. */ sscanf(raw, "%s%s", cmd->word[0].raw, cmd->word[1].raw); /* (ESR) In oldstyle mode, simulate the uppercasing and truncating @@ -512,10 +513,14 @@ static void tokenize(char *raw, command_t *cmd) { * in their tools. On the other, not simulating this misbehavior * goes against the goal of making oldstyle as accurate as * possible an emulation of the original UI. + * + * The definition of TRUNCLEN is dubious. It accurately reflects the + * FORTRAN, but it's possible that was a bug and the proper definition + * is (TOKLEN). */ +#define TRUNCLEN (TOKLEN + TOKLEN) if (settings.oldstyle) { - cmd->word[0].raw[TOKLEN + TOKLEN] = - cmd->word[1].raw[TOKLEN + TOKLEN] = '\0'; + cmd->word[0].raw[TRUNCLEN] = cmd->word[1].raw[TRUNCLEN] = '\0'; for (size_t i = 0; i < strlen(cmd->word[0].raw); i++) { cmd->word[0].raw[i] = toupper(cmd->word[0].raw[i]); } @@ -617,14 +622,12 @@ void move(obj_t object, loc_t where) { } void put(obj_t object, loc_t where, int pval) { - /* put() is the same as move(), except it returns a value used to set - * up the negated game.prop values for the repository objects. */ + /* put() is the same as move(), except the object is stashed and + * can no longer be picked up. */ move(object, where); - /* (ESR) Read this in combination with the macro defintions in advebt.h. - */ - game.objects[object].prop = PROP_STASHIFY(pval); -#ifdef PROP_SET_SEEN - PROP_SET_SEEN(object); + OBJECT_STASHIFY(object, pval); +#ifdef OBJECT_SET_SEEN + OBJECT_SET_SEEN(object); #endif } diff --git a/notes.adoc b/notes.adoc index b970e7c..a247e49 100644 --- a/notes.adoc +++ b/notes.adoc @@ -16,8 +16,8 @@ of Peje Nilsson in restructuring some particularly grotty gotos is gratefully acknowledged. Petr Voropaev contributed fuzz testing and code cleanups. Aaron Traas did a lot of painstaking work to improve test coverage, and factored out the last handful of gotos. Ryan -Sarson nudged us into fixing a longstannding minor bug in the -handling of incorrect magic-word sequebcesm, +Sarson nudged us into fixing a longstanding minor bug in the +handling of incorrect magic-word sequences, == Nomenclature == @@ -75,10 +75,15 @@ Bug fixes: * A few minor typos have been corrected: absence of capitalization on "Swiss" and "Persian", inconsistent spelling of "imbedded" vs. "embedded", - "eying" for "eyeing", "thresholds" for "threshholds". + "eying" for "eyeing", "thresholds" for "threshholds", "pencilled" + for "penciled". * Under odd circumstances (dropping rug or vase outdoors) the game could - formerly say "floor" when it should say "ground" (or "dirt", or something). + formerly say "floor" when it should say "ground" (or "dirt", or + something). + +* The "knives vanish" message could formerly be emitted when "I see no + knife here." would be appropriate. Enhancements: diff --git a/saveresume.c b/saveresume.c index 100c925..1c778ef 100644 --- a/saveresume.c +++ b/saveresume.c @@ -230,7 +230,7 @@ bool is_valid(struct game_t valgame) { int temp_tally = 0; for (int treasure = 1; treasure <= NOBJECTS; treasure++) { if (objects[treasure].is_treasure) { - if (PROP_IS_NOTFOUND2(valgame, treasure)) { + if (OBJECT_IS_NOTFOUND2(valgame, treasure)) { ++temp_tally; } } diff --git a/score.c b/score.c index 1d84c79..ad1c00a 100644 --- a/score.c +++ b/score.c @@ -49,11 +49,11 @@ int score(enum termination mode) { if (i > CHEST) { k = 16; } - if (!PROP_IS_STASHED(i) && !PROP_IS_NOTFOUND(i)) { + if (!OBJECT_IS_STASHED(i) && !OBJECT_IS_NOTFOUND(i)) { score += 2; } if (game.objects[i].place == LOC_BUILDING && - PROP_IS_FOUND(i)) { + OBJECT_IS_FOUND(i)) { score += k - 2; } mxscor += k; diff --git a/tests/Makefile b/tests/Makefile index 2ed7431..997096a 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -170,7 +170,7 @@ oldcompare: echo 1..$(words $(shell ls *.log))) | $(TAPFILTER) @rm *.ochk *-new advent430 adventure.data -# List all NOMPARE tests. +# List all NOCOMPARE tests. residuals: @grep -n NOCOMPARE *.log diff --git a/tests/knife.chk b/tests/knife.chk new file mode 100644 index 0000000..76a3b6b --- /dev/null +++ b/tests/knife.chk @@ -0,0 +1,386 @@ + +Welcome to Adventure!! Would you like instructions? + +> no + +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. + +> seed 1640849217 + +Seed set to 1640849217 + +You're in front of building. + +> e + +You are inside a building, a well house for a large spring. + +There are some keys on the ground here. + +There is a shiny brass lamp nearby. + +There is food here. + +There is a bottle of water here. + +> get lamp + +OK + +> xyzzy + +>>Foof!<< + +It is now pitch dark. If you proceed you will likely fall into a pit. + +> get rod + +OK + +> on + +Your lamp is now on. + +You are in a debris room filled with stuff washed in from the surface. +A low wide passage with cobbles becomes plugged with mud and debris +here, but an awkward canyon leads upward and west. In the mud someone +has scrawled, "MAGIC WORD XYZZY". + +> w + +You are in an awkward sloping east/west canyon. + +> w + +You are in a splendid chamber thirty feet high. The walls are frozen +rivers of orange stone. An awkward canyon and a good passage exit +from east and west sides of the chamber. + +A cheerful little bird is sitting here singing. + +> w + +At your feet is a small pit breathing traces of white mist. An east +passage ends here except for a small crack leading on. + +Rough stone steps lead down the pit. + +> d + +You are at one end of a vast hall stretching forward out of sight to +the west. There are openings to either side. Nearby, a wide stone +staircase leads downward. The hall is filled with wisps of white mist +swaying to and fro almost as if alive. A cold wind blows up the +staircase. There is a passage at the top of a dome behind you. + +Rough stone steps lead up the dome. + +> w + +You are on the east bank of a fissure slicing clear across the hall. +The mist is quite thick here, and the fissure is too wide to jump. + +> wave rod + +A crystal bridge now spans the fissure. + +> w + +You are on the west side of the fissure in the Hall of Mists. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You're on east bank of fissure. + +A crystal bridge spans the fissure. + +> w + +You're on west bank of fissure. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You're on east bank of fissure. + +A crystal bridge spans the fissure. + +> w + +You're on west bank of fissure. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You're on east bank of fissure. + +A crystal bridge spans the fissure. + +> w + +You're on west bank of fissure. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You're on east bank of fissure. + +A crystal bridge spans the fissure. + +> w + +You're on west bank of fissure. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You are on the east bank of a fissure slicing clear across the hall. +The mist is quite thick here, and the fissure is too wide to jump. + +A crystal bridge spans the fissure. + +> w + +You are on the west side of the fissure in the Hall of Mists. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You're on east bank of fissure. + +A crystal bridge spans the fissure. + +> w + +You're on west bank of fissure. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You're on east bank of fissure. + +A crystal bridge spans the fissure. + +> w + +You're on west bank of fissure. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You're on east bank of fissure. + +A crystal bridge spans the fissure. + +> w + +You're on west bank of fissure. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You're on east bank of fissure. + +A crystal bridge spans the fissure. + +> w + +You're on west bank of fissure. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You are on the east bank of a fissure slicing clear across the hall. +The mist is quite thick here, and the fissure is too wide to jump. + +A crystal bridge spans the fissure. + +> w + +You are on the west side of the fissure in the Hall of Mists. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You're on east bank of fissure. + +A crystal bridge spans the fissure. + +> w + +You're on west bank of fissure. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You're on east bank of fissure. + +A crystal bridge spans the fissure. + +> w + +You're on west bank of fissure. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You're on east bank of fissure. + +A crystal bridge spans the fissure. + +> w + +You're on west bank of fissure. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You're on east bank of fissure. + +A crystal bridge spans the fissure. + +> w + +You're on west bank of fissure. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You are on the east bank of a fissure slicing clear across the hall. +The mist is quite thick here, and the fissure is too wide to jump. + +A crystal bridge spans the fissure. + +> w + +You are on the west side of the fissure in the Hall of Mists. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You're on east bank of fissure. + +A crystal bridge spans the fissure. + +> w + +You're on west bank of fissure. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +A little dwarf just walked around a corner, saw you, threw a little +axe at you which missed, cursed, and ran away. + +You're on east bank of fissure. + +There is a little axe here. + +A crystal bridge spans the fissure. + +> w + +There is a threatening little dwarf in the room with you! + +One sharp nasty knife is thrown at you! + +It misses! + +You're on west bank of fissure. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> get knife + +The dwarves' knives vanish as they strike the walls of the cave. + +> look + +Sorry, but I am not allowed to give more detail. I will repeat the +long description of your location. + +There is a threatening little dwarf in the room with you! + +One sharp nasty knife is thrown at you! + +It misses! + +You are on the west side of the fissure in the Hall of Mists. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> get knife + +I see no knife here. + +> quit + +Do you really want to quit now? + +> yes + +OK + +You scored 59 out of a possible 430, using 50 turns. + +Your score qualifies you as a novice class adventurer. + +To achieve the next higher rating, you need 62 more points. diff --git a/tests/knife.log b/tests/knife.log new file mode 100644 index 0000000..1c811ea --- /dev/null +++ b/tests/knife.log @@ -0,0 +1,57 @@ +## Test whether KNIVES_VANISH can be issued twice +# SPDX-FileCopyrightText: Copyright Eric S. Raymond +# SPDX-License-Identifier: BSD-2-Clause +#NOCOMPARE avoide spuriuous failure on second "get knife" +no +seed 1640849217 +e +get lamp +xyzzy +get rod +on +w +w +w +d +w +wave rod +w +e +w +e +w +e +w +e +w +e +w +e +w +e +w +e +w +e +w +e +w +e +w +e +w +e +w +e +w +e +w +e +w +e +w +get knife +look +get knife +quit +yes