From be429016afc256ce6731867ffed6f2d6f2d876f2 Mon Sep 17 00:00:00 2001 From: "Eric S. Raymond" Date: Sat, 27 Jan 2024 06:17:02 -0500 Subject: [PATCH] 1TBS reflow with clang-format. --- Makefile | 3 + actions.c | 3020 +++++++++++++++++++++++++------------------------- advent.h | 393 ++++--- cheat.c | 172 ++- init.c | 131 +-- main.c | 2614 ++++++++++++++++++++++--------------------- misc.c | 1410 +++++++++++------------ saveresume.c | 428 +++---- score.c | 254 ++--- 9 files changed, 4313 insertions(+), 4112 deletions(-) diff --git a/Makefile b/Makefile index 49c909e..9c837d2 100644 --- a/Makefile +++ b/Makefile @@ -68,6 +68,9 @@ cheat: $(CHEAT_OBJS) dungeon.o check: advent cheat cd tests; $(MAKE) --quiet +reflow: + @clang-format --style="{IndentWidth: 8, UseTab: ForIndentation}" -i $$(find . -name "*.[ch]") + # Requires gcov, lcov, libasan6, and libubsan1 # The last two are Ubuntu names, might vary on other distributions. # After this, run your browser on coverage/open-adventure/index.html diff --git a/actions.c b/actions.c index fed70b9..97e5a48 100644 --- a/actions.c +++ b/actions.c @@ -5,1604 +5,1642 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include +#include #include +#include #include -#include #include "advent.h" static phase_codes_t fill(verb_t, obj_t); static phase_codes_t attack(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 - * enemies, or no enemies but 2 others. */ - verb_t verb = command.verb; - obj_t obj = command.obj; - - if (obj == INTRANSITIVE) { - int changes = 0; - if (atdwrf(game.loc) > 0) { - obj = DWARF; - ++changes; - } - if (HERE(SNAKE)) { - obj = SNAKE; - ++changes; - } - if (AT(DRAGON) && game.objects[DRAGON].prop == DRAGON_BARS) { - obj = DRAGON; - ++changes; - } - if (AT(TROLL)) { - obj = TROLL; - ++changes; - } - if (AT(OGRE)) { - obj = OGRE; - ++changes; - } - if (HERE(BEAR) && game.objects[BEAR].prop == UNTAMED_BEAR) { - obj = BEAR; - ++changes; - } - /* check for low-priority targets */ - if (obj == INTRANSITIVE) { - /* Can't attack bird or machine by throwing axe. */ - if (HERE(BIRD) && verb != THROW) { - obj = BIRD; - ++changes; - } - if (HERE(VEND) && verb != THROW) { - obj = VEND; - ++changes; - } - /* Clam and oyster both treated as clam for intransitive case; - * no harm done. */ - if (HERE(CLAM) || HERE(OYSTER)) { - obj = CLAM; - ++changes; - } - } - if (changes >= 2) - return GO_UNKNOWN; - } - - if (obj == BIRD) { - if (game.closed) { - rspeak(UNHAPPY_BIRD); - } else { - DESTROY(BIRD); - rspeak(BIRD_DEAD); - } - return GO_CLEAROBJ; - } - if (obj == VEND) { - state_change(VEND, - game.objects[VEND].prop == VEND_BLOCKS ? VEND_UNBLOCKS : VEND_BLOCKS); - - return GO_CLEAROBJ; - } - - if (obj == BEAR) { - switch (game.objects[BEAR].prop) { - case UNTAMED_BEAR: - rspeak(BEAR_HANDS); - break; - case SITTING_BEAR: - rspeak(BEAR_CONFUSED); - break; - case CONTENTED_BEAR: - rspeak(BEAR_CONFUSED); - break; - case BEAR_DEAD: - rspeak(ALREADY_DEAD); - break; - } - return GO_CLEAROBJ; - } - if (obj == DRAGON && game.objects[DRAGON].prop == DRAGON_BARS) { - /* Fun stuff for dragon. If he insists on attacking it, win! - * Set game.prop to dead, move dragon to central loc (still - * fixed), move rug there (not fixed), and move him there, - * too. Then do a null motion to get new description. */ - rspeak(BARE_HANDS_QUERY); - if (!silent_yes_or_no()) { - speak(arbitrary_messages[NASTY_DRAGON]); - return GO_MOVE; - } - state_change(DRAGON, DRAGON_DEAD); - game.objects[RUG].prop = RUG_FLOOR; - /* Hardcoding LOC_SECRET5 as the dragon's death location is ugly. - * The way it was computed before was worse; it depended on the - * two dragon locations being LOC_SECRET4 and LOC_SECRET6 and - * LOC_SECRET5 being right between them. - */ - move(DRAGON + NOBJECTS, IS_FIXED); - move(RUG + NOBJECTS, IS_FREE); - move(DRAGON, LOC_SECRET5); - move(RUG, LOC_SECRET5); - drop(BLOOD, LOC_SECRET5); - for (obj_t i = 1; i <= NOBJECTS; i++) { - if (game.objects[i].place == objects[DRAGON].plac || - game.objects[i].place == objects[DRAGON].fixd) - move(i, LOC_SECRET5); - } - game.loc = LOC_SECRET5; - return GO_MOVE; - } - - if (obj == OGRE) { - rspeak(OGRE_DODGE); - if (atdwrf(game.loc) == 0) { - return GO_CLEAROBJ; - } - rspeak(KNIFE_THROWN); - DESTROY(OGRE); - int dwarves = 0; - for (int i = 1; i < PIRATE; i++) { - if (game.dwarves[i].loc == game.loc) { - ++dwarves; - game.dwarves[i].loc = LOC_LONGWEST; - game.dwarves[i].seen = false; - } - } - rspeak((dwarves > 1) ? - OGRE_PANIC1 : - OGRE_PANIC2); - return GO_CLEAROBJ; - } - - switch (obj) { - case INTRANSITIVE: - rspeak(NO_TARGET); - break; - case CLAM: - case OYSTER: - rspeak(SHELL_IMPERVIOUS); - break; - case SNAKE: - rspeak(SNAKE_WARNING); - break; - case DWARF: - if (game.closed) { - return GO_DWARFWAKE; - } - rspeak(BARE_HANDS_QUERY); - break; - case DRAGON: - rspeak(ALREADY_DEAD); - break; - case TROLL: - rspeak(ROCKY_TROLL); - break; - default: - speak(actions[verb].message); - } - return GO_CLEAROBJ; + /* 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 + * enemies, or no enemies but 2 others. */ + verb_t verb = command.verb; + obj_t obj = command.obj; + + if (obj == INTRANSITIVE) { + int changes = 0; + if (atdwrf(game.loc) > 0) { + obj = DWARF; + ++changes; + } + if (HERE(SNAKE)) { + obj = SNAKE; + ++changes; + } + if (AT(DRAGON) && game.objects[DRAGON].prop == DRAGON_BARS) { + obj = DRAGON; + ++changes; + } + if (AT(TROLL)) { + obj = TROLL; + ++changes; + } + if (AT(OGRE)) { + obj = OGRE; + ++changes; + } + if (HERE(BEAR) && game.objects[BEAR].prop == UNTAMED_BEAR) { + obj = BEAR; + ++changes; + } + /* check for low-priority targets */ + if (obj == INTRANSITIVE) { + /* Can't attack bird or machine by throwing axe. */ + if (HERE(BIRD) && verb != THROW) { + obj = BIRD; + ++changes; + } + if (HERE(VEND) && verb != THROW) { + obj = VEND; + ++changes; + } + /* Clam and oyster both treated as clam for intransitive + * case; no harm done. */ + if (HERE(CLAM) || HERE(OYSTER)) { + obj = CLAM; + ++changes; + } + } + if (changes >= 2) + return GO_UNKNOWN; + } + + if (obj == BIRD) { + if (game.closed) { + rspeak(UNHAPPY_BIRD); + } else { + DESTROY(BIRD); + rspeak(BIRD_DEAD); + } + return GO_CLEAROBJ; + } + if (obj == VEND) { + state_change(VEND, game.objects[VEND].prop == VEND_BLOCKS + ? VEND_UNBLOCKS + : VEND_BLOCKS); + + return GO_CLEAROBJ; + } + + if (obj == BEAR) { + switch (game.objects[BEAR].prop) { + case UNTAMED_BEAR: + rspeak(BEAR_HANDS); + break; + case SITTING_BEAR: + rspeak(BEAR_CONFUSED); + break; + case CONTENTED_BEAR: + rspeak(BEAR_CONFUSED); + break; + case BEAR_DEAD: + rspeak(ALREADY_DEAD); + break; + } + return GO_CLEAROBJ; + } + if (obj == DRAGON && game.objects[DRAGON].prop == DRAGON_BARS) { + /* Fun stuff for dragon. If he insists on attacking it, win! + * Set game.prop to dead, move dragon to central loc (still + * fixed), move rug there (not fixed), and move him there, + * too. Then do a null motion to get new description. */ + rspeak(BARE_HANDS_QUERY); + if (!silent_yes_or_no()) { + speak(arbitrary_messages[NASTY_DRAGON]); + return GO_MOVE; + } + state_change(DRAGON, DRAGON_DEAD); + game.objects[RUG].prop = RUG_FLOOR; + /* Hardcoding LOC_SECRET5 as the dragon's death location is + * ugly. The way it was computed before was worse; it depended + * on the two dragon locations being LOC_SECRET4 and LOC_SECRET6 + * and LOC_SECRET5 being right between them. + */ + move(DRAGON + NOBJECTS, IS_FIXED); + move(RUG + NOBJECTS, IS_FREE); + move(DRAGON, LOC_SECRET5); + move(RUG, LOC_SECRET5); + drop(BLOOD, LOC_SECRET5); + for (obj_t i = 1; i <= NOBJECTS; i++) { + if (game.objects[i].place == objects[DRAGON].plac || + game.objects[i].place == objects[DRAGON].fixd) + move(i, LOC_SECRET5); + } + game.loc = LOC_SECRET5; + return GO_MOVE; + } + + if (obj == OGRE) { + rspeak(OGRE_DODGE); + if (atdwrf(game.loc) == 0) { + return GO_CLEAROBJ; + } + rspeak(KNIFE_THROWN); + DESTROY(OGRE); + int dwarves = 0; + for (int i = 1; i < PIRATE; i++) { + if (game.dwarves[i].loc == game.loc) { + ++dwarves; + game.dwarves[i].loc = LOC_LONGWEST; + game.dwarves[i].seen = false; + } + } + rspeak((dwarves > 1) ? OGRE_PANIC1 : OGRE_PANIC2); + return GO_CLEAROBJ; + } + + switch (obj) { + case INTRANSITIVE: + rspeak(NO_TARGET); + break; + case CLAM: + case OYSTER: + rspeak(SHELL_IMPERVIOUS); + break; + case SNAKE: + rspeak(SNAKE_WARNING); + break; + case DWARF: + if (game.closed) { + return GO_DWARFWAKE; + } + rspeak(BARE_HANDS_QUERY); + break; + case DRAGON: + rspeak(ALREADY_DEAD); + break; + case TROLL: + rspeak(ROCKY_TROLL); + break; + default: + speak(actions[verb].message); + } + return GO_CLEAROBJ; } static phase_codes_t bigwords(vocab_t id) { -/* Only called on FEE FIE FOE FOO (AND FUM). Advance to next state if given - * in proper order. Look up foo in special section of vocab to determine which - * word we've got. Last word zips the eggs back to the giant room (unless - * already there). */ - int foobar = abs(game.foobar); - - /* Only FEE can start a magic-word sequence. */ - if ((foobar == WORD_EMPTY) && (id == FIE || id == FOE || id == FOO || id == FUM)) { - rspeak(NOTHING_HAPPENS); - return GO_CLEAROBJ; - } - - if ((foobar == WORD_EMPTY && id == FEE) || - (foobar == FEE && id == FIE) || - (foobar == FIE && id == FOE) || - (foobar == FOE && id == FOO)) { - game.foobar = id; - if (id != FOO) { - rspeak(OK_MAN); - return GO_CLEAROBJ; - } - game.foobar = WORD_EMPTY; - if (game.objects[EGGS].place == objects[EGGS].plac || - (TOTING(EGGS) && game.loc == objects[EGGS].plac)) { - rspeak(NOTHING_HAPPENS); - return GO_CLEAROBJ; - } else { - /* Bring back troll if we steal the eggs back from him before - * crossing. */ - if (game.objects[EGGS].place == LOC_NOWHERE && game.objects[TROLL].place == LOC_NOWHERE - && game.objects[TROLL].prop == TROLL_UNPAID) - game.objects[TROLL].prop = TROLL_PAIDONCE; - if (HERE(EGGS)) { - pspeak(EGGS, look, true, EGGS_VANISHED); - } else if (game.loc == objects[EGGS].plac) { - pspeak(EGGS, look, true, EGGS_HERE); - } else { - pspeak(EGGS, look, true, EGGS_DONE); - } - move(EGGS, objects[EGGS].plac); - - return GO_CLEAROBJ; - } - } else { - /* Magic-word sequence was started but is incorrect */ - if (settings.oldstyle || game.seenbigwords) { - rspeak(START_OVER); + /* Only called on FEE FIE FOE FOO (AND FUM). Advance to next state if + * given in proper order. Look up foo in special section of vocab to + * determine which word we've got. Last word zips the eggs back to the + * giant room (unless already there). */ + int foobar = abs(game.foobar); + + /* Only FEE can start a magic-word sequence. */ + if ((foobar == WORD_EMPTY) && + (id == FIE || id == FOE || id == FOO || id == FUM)) { + rspeak(NOTHING_HAPPENS); + return GO_CLEAROBJ; + } + + if ((foobar == WORD_EMPTY && id == FEE) || + (foobar == FEE && id == FIE) || (foobar == FIE && id == FOE) || + (foobar == FOE && id == FOO)) { + game.foobar = id; + if (id != FOO) { + rspeak(OK_MAN); + return GO_CLEAROBJ; + } + game.foobar = WORD_EMPTY; + if (game.objects[EGGS].place == objects[EGGS].plac || + (TOTING(EGGS) && game.loc == objects[EGGS].plac)) { + rspeak(NOTHING_HAPPENS); + return GO_CLEAROBJ; + } else { + /* Bring back troll if we steal the eggs back from him + * before crossing. */ + if (game.objects[EGGS].place == LOC_NOWHERE && + game.objects[TROLL].place == LOC_NOWHERE && + game.objects[TROLL].prop == TROLL_UNPAID) + game.objects[TROLL].prop = TROLL_PAIDONCE; + if (HERE(EGGS)) { + pspeak(EGGS, look, true, EGGS_VANISHED); + } else if (game.loc == objects[EGGS].plac) { + pspeak(EGGS, look, true, EGGS_HERE); + } else { + pspeak(EGGS, look, true, EGGS_DONE); + } + move(EGGS, objects[EGGS].plac); + + return GO_CLEAROBJ; + } } else { - rspeak(WELL_POINTLESS); + /* Magic-word sequence was started but is incorrect */ + if (settings.oldstyle || game.seenbigwords) { + rspeak(START_OVER); + } else { + rspeak(WELL_POINTLESS); + } + game.foobar = WORD_EMPTY; + return GO_CLEAROBJ; } - game.foobar = WORD_EMPTY; - return GO_CLEAROBJ; - } } static void blast(void) { -/* Blast. No effect unless you've got dynamite, which is a neat trick! */ - if (PROP_IS_NOTFOUND(ROD2) || !game.closed) - rspeak(REQUIRES_DYNAMITE); - else { - if (HERE(ROD2)) { - game.bonus = splatter; - rspeak(SPLATTER_MESSAGE); - } else if (game.loc == LOC_NE) { - game.bonus = defeat; - rspeak(DEFEAT_MESSAGE); - } else { - game.bonus = victory; - rspeak(VICTORY_MESSAGE); - } - terminate(endgame); - } + /* Blast. No effect unless you've got dynamite, which is a neat trick! + */ + if (PROP_IS_NOTFOUND(ROD2) || !game.closed) + rspeak(REQUIRES_DYNAMITE); + else { + if (HERE(ROD2)) { + game.bonus = splatter; + rspeak(SPLATTER_MESSAGE); + } else if (game.loc == LOC_NE) { + game.bonus = defeat; + rspeak(DEFEAT_MESSAGE); + } else { + game.bonus = victory; + rspeak(VICTORY_MESSAGE); + } + terminate(endgame); + } } static phase_codes_t vbreak(verb_t verb, obj_t obj) { -/* Break. Only works for mirror in repository and, of course, the vase. */ - switch (obj) { - case MIRROR: - if (game.closed) { - state_change(MIRROR, MIRROR_BROKEN); - return GO_DWARFWAKE; - } else { - rspeak(TOO_FAR); - break; - } - case VASE: - if (game.objects[VASE].prop == VASE_WHOLE) { - if (TOTING(VASE)) - drop(VASE, game.loc); - state_change(VASE, VASE_BROKEN); - game.objects[VASE].fixed = IS_FIXED; - break; - } - /* FALLTHRU */ - default: - speak(actions[verb].message); - } - return (GO_CLEAROBJ); + /* Break. Only works for mirror in repository and, of course, the + * vase. */ + switch (obj) { + case MIRROR: + if (game.closed) { + state_change(MIRROR, MIRROR_BROKEN); + return GO_DWARFWAKE; + } else { + rspeak(TOO_FAR); + break; + } + case VASE: + if (game.objects[VASE].prop == VASE_WHOLE) { + if (TOTING(VASE)) + drop(VASE, game.loc); + state_change(VASE, VASE_BROKEN); + game.objects[VASE].fixed = IS_FIXED; + break; + } + /* FALLTHRU */ + default: + speak(actions[verb].message); + } + return (GO_CLEAROBJ); } static phase_codes_t brief(void) { -/* Brief. Intransitive only. Suppress full descriptions after first time. */ - game.abbnum = 10000; - game.detail = 3; - rspeak(BRIEF_CONFIRM); - return GO_CLEAROBJ; + /* Brief. Intransitive only. Suppress full descriptions after first + * time. */ + game.abbnum = 10000; + game.detail = 3; + rspeak(BRIEF_CONFIRM); + return GO_CLEAROBJ; } static phase_codes_t vcarry(verb_t verb, obj_t obj) { -/* Carry an object. Special cases for bird and cage (if bird in cage, can't - * take one without the other). Liquids also special, since they depend on - * status of bottle. Also various side effects, etc. */ - if (obj == INTRANSITIVE) { - /* Carry, no object given yet. OK if only one object present. */ - if (game.locs[game.loc].atloc == NO_OBJECT || - game.link[game.locs[game.loc].atloc] != 0 || - atdwrf(game.loc) > 0) { - return GO_UNKNOWN; - } - obj = game.locs[game.loc].atloc; - } - - if (TOTING(obj)) { - speak(actions[verb].message); - return GO_CLEAROBJ; - } - - if (obj == MESSAG) { - rspeak(REMOVE_MESSAGE); - DESTROY(MESSAG); - return GO_CLEAROBJ; - } - - 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 ? DEEP_ROOTS : YOU_JOKING); - break; - case BEAR: - rspeak( game.objects[BEAR].prop == SITTING_BEAR ? BEAR_CHAINED : YOU_JOKING); - break; - case CHAIN: - rspeak( game.objects[BEAR].prop != UNTAMED_BEAR ? STILL_LOCKED : YOU_JOKING); - break; - case RUG: - rspeak(game.objects[RUG].prop == RUG_HOVER ? RUG_HOVERS : YOU_JOKING); - break; - case URN: - rspeak(URN_NOBUDGE); - break; - case CAVITY: - rspeak(DOUGHNUT_HOLES); - break; - case BLOOD: - rspeak(FEW_DROPS); - break; - case SIGN: - rspeak(HAND_PASSTHROUGH); - break; - default: - rspeak(YOU_JOKING); - } - return GO_CLEAROBJ; - } - - if (obj == WATER || obj == OIL) { - if (!HERE(BOTTLE) || LIQUID() != obj) { - if (!TOTING(BOTTLE)) { - rspeak(NO_CONTAINER); - return GO_CLEAROBJ; - } - if (game.objects[BOTTLE].prop == EMPTY_BOTTLE) { - return (fill(verb, BOTTLE)); - } else { - rspeak(BOTTLE_FULL); - } - return GO_CLEAROBJ; - } - obj = BOTTLE; - } - - if (game.holdng >= INVLIMIT) { - rspeak(CARRY_LIMIT); - return GO_CLEAROBJ; - - } - - if (obj == BIRD && game.objects[BIRD].prop != BIRD_CAGED && !PROP_IS_STASHED(BIRD)) { - if (game.objects[BIRD].prop == BIRD_FOREST_UNCAGED) { - DESTROY(BIRD); - rspeak(BIRD_CRAP); - return GO_CLEAROBJ; - } - if (!TOTING(CAGE)) { - rspeak(CANNOT_CARRY); - return GO_CLEAROBJ; - } - if (TOTING(ROD)) { - rspeak(BIRD_EVADES); - return GO_CLEAROBJ; - } - game.objects[BIRD].prop = BIRD_CAGED; - } - if ((obj == BIRD || obj == CAGE) && - (game.objects[BIRD].prop == BIRD_CAGED || PROP_STASHED(BIRD) == BIRD_CAGED)) { - /* expression maps BIRD to CAGE and CAGE to BIRD */ - carry(BIRD + CAGE - obj, game.loc); - } - - carry(obj, game.loc); - - if (obj == BOTTLE && LIQUID() != NO_OBJECT) { - game.objects[LIQUID()].place = CARRIED; - } - - if (GSTONE(obj) && !PROP_IS_FOUND(obj)) { - PROP_SET_FOUND(obj); - game.objects[CAVITY].prop = CAVITY_EMPTY; - } - rspeak(OK_MAN); - return GO_CLEAROBJ; + /* Carry an object. Special cases for bird and cage (if bird in cage, + * can't take one without the other). Liquids also special, since they + * depend on status of bottle. Also various side effects, etc. */ + if (obj == INTRANSITIVE) { + /* Carry, no object given yet. OK if only one object present. + */ + if (game.locs[game.loc].atloc == NO_OBJECT || + game.link[game.locs[game.loc].atloc] != 0 || + atdwrf(game.loc) > 0) { + return GO_UNKNOWN; + } + obj = game.locs[game.loc].atloc; + } + + if (TOTING(obj)) { + speak(actions[verb].message); + return GO_CLEAROBJ; + } + + if (obj == MESSAG) { + rspeak(REMOVE_MESSAGE); + DESTROY(MESSAG); + return GO_CLEAROBJ; + } + + 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 + ? DEEP_ROOTS + : YOU_JOKING); + break; + case BEAR: + rspeak(game.objects[BEAR].prop == SITTING_BEAR + ? BEAR_CHAINED + : YOU_JOKING); + break; + case CHAIN: + rspeak(game.objects[BEAR].prop != UNTAMED_BEAR + ? STILL_LOCKED + : YOU_JOKING); + break; + case RUG: + rspeak(game.objects[RUG].prop == RUG_HOVER + ? RUG_HOVERS + : YOU_JOKING); + break; + case URN: + rspeak(URN_NOBUDGE); + break; + case CAVITY: + rspeak(DOUGHNUT_HOLES); + break; + case BLOOD: + rspeak(FEW_DROPS); + break; + case SIGN: + rspeak(HAND_PASSTHROUGH); + break; + default: + rspeak(YOU_JOKING); + } + return GO_CLEAROBJ; + } + + if (obj == WATER || obj == OIL) { + if (!HERE(BOTTLE) || LIQUID() != obj) { + if (!TOTING(BOTTLE)) { + rspeak(NO_CONTAINER); + return GO_CLEAROBJ; + } + if (game.objects[BOTTLE].prop == EMPTY_BOTTLE) { + return (fill(verb, BOTTLE)); + } else { + rspeak(BOTTLE_FULL); + } + return GO_CLEAROBJ; + } + obj = BOTTLE; + } + + if (game.holdng >= INVLIMIT) { + rspeak(CARRY_LIMIT); + return GO_CLEAROBJ; + } + + if (obj == BIRD && game.objects[BIRD].prop != BIRD_CAGED && + !PROP_IS_STASHED(BIRD)) { + if (game.objects[BIRD].prop == BIRD_FOREST_UNCAGED) { + DESTROY(BIRD); + rspeak(BIRD_CRAP); + return GO_CLEAROBJ; + } + if (!TOTING(CAGE)) { + rspeak(CANNOT_CARRY); + return GO_CLEAROBJ; + } + if (TOTING(ROD)) { + rspeak(BIRD_EVADES); + return GO_CLEAROBJ; + } + game.objects[BIRD].prop = BIRD_CAGED; + } + if ((obj == BIRD || obj == CAGE) && + (game.objects[BIRD].prop == BIRD_CAGED || + PROP_STASHED(BIRD) == BIRD_CAGED)) { + /* expression maps BIRD to CAGE and CAGE to BIRD */ + carry(BIRD + CAGE - obj, game.loc); + } + + carry(obj, game.loc); + + if (obj == BOTTLE && LIQUID() != NO_OBJECT) { + game.objects[LIQUID()].place = CARRIED; + } + + if (GSTONE(obj) && !PROP_IS_FOUND(obj)) { + PROP_SET_FOUND(obj); + game.objects[CAVITY].prop = CAVITY_EMPTY; + } + rspeak(OK_MAN); + return GO_CLEAROBJ; } static int chain(verb_t verb) { -/* Do something to the bear's chain */ - if (verb != LOCK) { - if (game.objects[BEAR].prop == UNTAMED_BEAR) { - rspeak(BEAR_BLOCKS); - return GO_CLEAROBJ; - } - if (game.objects[CHAIN].prop == CHAIN_HEAP) { - rspeak(ALREADY_UNLOCKED); - return GO_CLEAROBJ; - } - game.objects[CHAIN].prop = CHAIN_HEAP; - game.objects[CHAIN].fixed = IS_FREE; - if (game.objects[BEAR].prop != BEAR_DEAD) - game.objects[BEAR].prop = CONTENTED_BEAR; - - switch (game.objects[BEAR].prop) { - // LCOV_EXCL_START - case BEAR_DEAD: - /* Can't be reached until the bear can die in some way other - * than a bridge collapse. Leave in in case this changes, but - * exclude from coverage testing. */ - game.objects[BEAR].fixed = IS_FIXED; - break; - // LCOV_EXCL_STOP - default: - game.objects[BEAR].fixed = IS_FREE; - } - rspeak(CHAIN_UNLOCKED); - return GO_CLEAROBJ; - } - - if (game.objects[CHAIN].prop != CHAIN_HEAP) { - rspeak(ALREADY_LOCKED); - return GO_CLEAROBJ; - } - if (game.loc != objects[CHAIN].plac) { - rspeak(NO_LOCKSITE); - return GO_CLEAROBJ; - } - - game.objects[CHAIN].prop = CHAIN_FIXED; - - if (TOTING(CHAIN)) - drop(CHAIN, game.loc); - game.objects[CHAIN].fixed = IS_FIXED; - - rspeak(CHAIN_LOCKED); - return GO_CLEAROBJ; + /* Do something to the bear's chain */ + if (verb != LOCK) { + if (game.objects[BEAR].prop == UNTAMED_BEAR) { + rspeak(BEAR_BLOCKS); + return GO_CLEAROBJ; + } + if (game.objects[CHAIN].prop == CHAIN_HEAP) { + rspeak(ALREADY_UNLOCKED); + return GO_CLEAROBJ; + } + game.objects[CHAIN].prop = CHAIN_HEAP; + game.objects[CHAIN].fixed = IS_FREE; + if (game.objects[BEAR].prop != BEAR_DEAD) + game.objects[BEAR].prop = CONTENTED_BEAR; + + switch (game.objects[BEAR].prop) { + // LCOV_EXCL_START + case BEAR_DEAD: + /* Can't be reached until the bear can die in some way + * other than a bridge collapse. Leave in in case this + * changes, but exclude from coverage testing. */ + game.objects[BEAR].fixed = IS_FIXED; + break; + // LCOV_EXCL_STOP + default: + game.objects[BEAR].fixed = IS_FREE; + } + rspeak(CHAIN_UNLOCKED); + return GO_CLEAROBJ; + } + + if (game.objects[CHAIN].prop != CHAIN_HEAP) { + rspeak(ALREADY_LOCKED); + return GO_CLEAROBJ; + } + if (game.loc != objects[CHAIN].plac) { + rspeak(NO_LOCKSITE); + return GO_CLEAROBJ; + } + + game.objects[CHAIN].prop = CHAIN_FIXED; + + if (TOTING(CHAIN)) + drop(CHAIN, game.loc); + game.objects[CHAIN].fixed = IS_FIXED; + + rspeak(CHAIN_LOCKED); + return GO_CLEAROBJ; } static phase_codes_t discard(verb_t verb, obj_t obj) { -/* Discard object. "Throw" also comes here for most objects. Special cases for - * bird (might attack snake or dragon) and cage (might contain bird) and vase. - * Drop coins at vending machine for extra batteries. */ - if (obj == ROD && !TOTING(ROD) && TOTING(ROD2)) { - obj = ROD2; - } - - if (!TOTING(obj)) { - speak(actions[verb].message); - return GO_CLEAROBJ; - } - - if (GSTONE(obj) && AT(CAVITY) && game.objects[CAVITY].prop != CAVITY_FULL) { - rspeak(GEM_FITS); - game.objects[obj].prop = STATE_IN_CAVITY; - game.objects[CAVITY].prop = CAVITY_FULL; - if (HERE(RUG) && ((obj == EMERALD && game.objects[RUG].prop != RUG_HOVER) || - (obj == RUBY && game.objects[RUG].prop == RUG_HOVER))) { - if (obj == RUBY) { - rspeak(RUG_SETTLES); - } else if (TOTING(RUG)) { - rspeak(RUG_WIGGLES); - } else { - rspeak(RUG_RISES); - } - if (!TOTING(RUG) || obj == RUBY) { - int k = (game.objects[RUG].prop == RUG_HOVER) ? RUG_FLOOR : RUG_HOVER; - game.objects[RUG].prop = k; - if (k == RUG_HOVER) { - k = objects[SAPPH].plac; - } - move(RUG + NOBJECTS, k); - } - } - drop(obj, game.loc); - return GO_CLEAROBJ; - } - - if (obj == COINS && HERE(VEND)) { - DESTROY(COINS); - drop(BATTERY, game.loc); - pspeak(BATTERY, look, true, FRESH_BATTERIES); - return GO_CLEAROBJ; - } - - if (LIQUID() == obj) { - obj = BOTTLE; - } - if (obj == BOTTLE && LIQUID() != NO_OBJECT) { - game.objects[LIQUID()].place = LOC_NOWHERE; - } - - if (obj == BEAR && AT(TROLL)) { - state_change(TROLL, TROLL_GONE); - move(TROLL, LOC_NOWHERE); - move(TROLL + NOBJECTS, IS_FREE); - move(TROLL2, objects[TROLL].plac); - move(TROLL2 + NOBJECTS, objects[TROLL].fixd); - juggle(CHASM); - drop(obj, game.loc); - return GO_CLEAROBJ; - } - - if (obj == VASE) { - if (game.loc != objects[PILLOW].plac) { - state_change(VASE, AT(PILLOW) - ? VASE_WHOLE - : VASE_DROPPED); - if (game.objects[VASE].prop != VASE_WHOLE) { - game.objects[VASE].fixed = IS_FIXED; - } - drop(obj, game.loc); - return GO_CLEAROBJ; - } - } - - if (obj == CAGE && game.objects[BIRD].prop == BIRD_CAGED) { - drop(BIRD, game.loc); - } - - if (obj == BIRD) { - if (AT(DRAGON) && game.objects[DRAGON].prop == DRAGON_BARS) { - rspeak(BIRD_BURNT); - DESTROY(BIRD); - return GO_CLEAROBJ; - } - if (HERE(SNAKE)) { - rspeak(BIRD_ATTACKS); - if (game.closed) { - return GO_DWARFWAKE; - } - DESTROY(SNAKE); - /* Set game.prop for use by travel options */ - game.objects[SNAKE].prop = SNAKE_CHASED; - } else { - rspeak(OK_MAN); - } - - game.objects[BIRD].prop = FOREST(game.loc) ? BIRD_FOREST_UNCAGED : BIRD_UNCAGED; - drop(obj, game.loc); - return GO_CLEAROBJ; - } - - rspeak(OK_MAN); - drop(obj, game.loc); - return GO_CLEAROBJ; + /* Discard object. "Throw" also comes here for most objects. Special + * cases for bird (might attack snake or dragon) and cage (might contain + * bird) and vase. Drop coins at vending machine for extra batteries. */ + if (obj == ROD && !TOTING(ROD) && TOTING(ROD2)) { + obj = ROD2; + } + + if (!TOTING(obj)) { + speak(actions[verb].message); + return GO_CLEAROBJ; + } + + if (GSTONE(obj) && AT(CAVITY) && + game.objects[CAVITY].prop != CAVITY_FULL) { + rspeak(GEM_FITS); + game.objects[obj].prop = STATE_IN_CAVITY; + game.objects[CAVITY].prop = CAVITY_FULL; + if (HERE(RUG) && + ((obj == EMERALD && game.objects[RUG].prop != RUG_HOVER) || + (obj == RUBY && game.objects[RUG].prop == RUG_HOVER))) { + if (obj == RUBY) { + rspeak(RUG_SETTLES); + } else if (TOTING(RUG)) { + rspeak(RUG_WIGGLES); + } else { + rspeak(RUG_RISES); + } + if (!TOTING(RUG) || obj == RUBY) { + int k = (game.objects[RUG].prop == RUG_HOVER) + ? RUG_FLOOR + : RUG_HOVER; + game.objects[RUG].prop = k; + if (k == RUG_HOVER) { + k = objects[SAPPH].plac; + } + move(RUG + NOBJECTS, k); + } + } + drop(obj, game.loc); + return GO_CLEAROBJ; + } + + if (obj == COINS && HERE(VEND)) { + DESTROY(COINS); + drop(BATTERY, game.loc); + pspeak(BATTERY, look, true, FRESH_BATTERIES); + return GO_CLEAROBJ; + } + + if (LIQUID() == obj) { + obj = BOTTLE; + } + if (obj == BOTTLE && LIQUID() != NO_OBJECT) { + game.objects[LIQUID()].place = LOC_NOWHERE; + } + + if (obj == BEAR && AT(TROLL)) { + state_change(TROLL, TROLL_GONE); + move(TROLL, LOC_NOWHERE); + move(TROLL + NOBJECTS, IS_FREE); + move(TROLL2, objects[TROLL].plac); + move(TROLL2 + NOBJECTS, objects[TROLL].fixd); + juggle(CHASM); + drop(obj, game.loc); + return GO_CLEAROBJ; + } + + if (obj == VASE) { + if (game.loc != objects[PILLOW].plac) { + state_change(VASE, + AT(PILLOW) ? VASE_WHOLE : VASE_DROPPED); + if (game.objects[VASE].prop != VASE_WHOLE) { + game.objects[VASE].fixed = IS_FIXED; + } + drop(obj, game.loc); + return GO_CLEAROBJ; + } + } + + if (obj == CAGE && game.objects[BIRD].prop == BIRD_CAGED) { + drop(BIRD, game.loc); + } + + if (obj == BIRD) { + if (AT(DRAGON) && game.objects[DRAGON].prop == DRAGON_BARS) { + rspeak(BIRD_BURNT); + DESTROY(BIRD); + return GO_CLEAROBJ; + } + if (HERE(SNAKE)) { + rspeak(BIRD_ATTACKS); + if (game.closed) { + return GO_DWARFWAKE; + } + DESTROY(SNAKE); + /* Set game.prop for use by travel options */ + game.objects[SNAKE].prop = SNAKE_CHASED; + } else { + rspeak(OK_MAN); + } + + game.objects[BIRD].prop = + FOREST(game.loc) ? BIRD_FOREST_UNCAGED : BIRD_UNCAGED; + drop(obj, game.loc); + return GO_CLEAROBJ; + } + + rspeak(OK_MAN); + drop(obj, game.loc); + return GO_CLEAROBJ; } static phase_codes_t drink(verb_t verb, obj_t obj) { -/* Drink. If no object, assume water and look for it here. If water is in - * the bottle, drink that, else must be at a water loc, so drink stream. */ - if (obj == INTRANSITIVE && LIQLOC(game.loc) != WATER && - (LIQUID() != WATER || !HERE(BOTTLE))) { - return GO_UNKNOWN; - } - - if (obj == BLOOD) { - DESTROY(BLOOD); - state_change(DRAGON, DRAGON_BLOODLESS); - game.blooded = true; - return GO_CLEAROBJ; - } - - if (obj != INTRANSITIVE && obj != WATER) { - rspeak(RIDICULOUS_ATTEMPT); - return GO_CLEAROBJ; - } - if (LIQUID() == WATER && HERE(BOTTLE)) { - game.objects[WATER].place = LOC_NOWHERE; - state_change(BOTTLE, EMPTY_BOTTLE); - return GO_CLEAROBJ; - } - - speak(actions[verb].message); - return GO_CLEAROBJ; + /* Drink. If no object, assume water and look for it here. If water + * is in the bottle, drink that, else must be at a water loc, so drink + * stream. */ + if (obj == INTRANSITIVE && LIQLOC(game.loc) != WATER && + (LIQUID() != WATER || !HERE(BOTTLE))) { + return GO_UNKNOWN; + } + + if (obj == BLOOD) { + DESTROY(BLOOD); + state_change(DRAGON, DRAGON_BLOODLESS); + game.blooded = true; + return GO_CLEAROBJ; + } + + if (obj != INTRANSITIVE && obj != WATER) { + rspeak(RIDICULOUS_ATTEMPT); + return GO_CLEAROBJ; + } + if (LIQUID() == WATER && HERE(BOTTLE)) { + game.objects[WATER].place = LOC_NOWHERE; + state_change(BOTTLE, EMPTY_BOTTLE); + return GO_CLEAROBJ; + } + + speak(actions[verb].message); + return GO_CLEAROBJ; } static phase_codes_t eat(verb_t verb, obj_t obj) { -/* Eat. Intransitive: assume food if present, else ask what. Transitive: food - * ok, some things lose appetite, rest are ridiculous. */ - switch (obj) { - case INTRANSITIVE: - if (!HERE(FOOD)) - return GO_UNKNOWN; - /* FALLTHRU */ - case FOOD: - DESTROY(FOOD); - rspeak(THANKS_DELICIOUS); - break; - case BIRD: - case SNAKE: - case CLAM: - case OYSTER: - case DWARF: - case DRAGON: - case TROLL: - case BEAR: - case OGRE: - rspeak(LOST_APPETITE); - break; - default: - speak(actions[verb].message); - } - return GO_CLEAROBJ; + /* Eat. Intransitive: assume food if present, else ask what. + * Transitive: food ok, some things lose appetite, rest are ridiculous. + */ + switch (obj) { + case INTRANSITIVE: + if (!HERE(FOOD)) + return GO_UNKNOWN; + /* FALLTHRU */ + case FOOD: + DESTROY(FOOD); + rspeak(THANKS_DELICIOUS); + break; + case BIRD: + case SNAKE: + case CLAM: + case OYSTER: + case DWARF: + case DRAGON: + case TROLL: + case BEAR: + case OGRE: + rspeak(LOST_APPETITE); + break; + default: + speak(actions[verb].message); + } + return GO_CLEAROBJ; } static phase_codes_t extinguish(verb_t verb, obj_t obj) { -/* Extinguish. Lamp, urn, dragon/volcano (nice try). */ - if (obj == INTRANSITIVE) { - if (HERE(LAMP) && game.objects[LAMP].prop == LAMP_BRIGHT) { - obj = LAMP; + /* Extinguish. Lamp, urn, dragon/volcano (nice try). */ + if (obj == INTRANSITIVE) { + if (HERE(LAMP) && game.objects[LAMP].prop == LAMP_BRIGHT) { + obj = LAMP; + } + if (HERE(URN) && game.objects[URN].prop == URN_LIT) { + obj = URN; + } + if (obj == INTRANSITIVE) { + return GO_UNKNOWN; + } } - if (HERE(URN) && game.objects[URN].prop == URN_LIT) { - obj = URN; + + switch (obj) { + case URN: + if (game.objects[URN].prop != URN_EMPTY) { + state_change(URN, URN_DARK); + } else { + pspeak(URN, change, true, URN_DARK); + } + break; + case LAMP: + state_change(LAMP, LAMP_DARK); + rspeak(DARK(game.loc) ? PITCH_DARK : NO_MESSAGE); + break; + case DRAGON: + case VOLCANO: + rspeak(BEYOND_POWER); + break; + default: + speak(actions[verb].message); } - if (obj == INTRANSITIVE) { - return GO_UNKNOWN; - } - } - - switch (obj) { - case URN: - if (game.objects[URN].prop != URN_EMPTY) { - state_change(URN, URN_DARK); - } else { - pspeak(URN, change, true, URN_DARK); - } - break; - case LAMP: - state_change(LAMP, LAMP_DARK); - rspeak(DARK(game.loc) ? - PITCH_DARK : - NO_MESSAGE); - break; - case DRAGON: - case VOLCANO: - rspeak(BEYOND_POWER); - break; - default: - speak(actions[verb].message); - } - return GO_CLEAROBJ; + return GO_CLEAROBJ; } static phase_codes_t feed(verb_t verb, obj_t obj) { -/* Feed. If bird, no seed. Snake, dragon, troll: quip. If dwarf, make him - * mad. Bear, special. */ - switch (obj) { - case BIRD: - rspeak(BIRD_PINING); - break; - case DRAGON: - if (game.objects[DRAGON].prop != DRAGON_BARS) { - rspeak(RIDICULOUS_ATTEMPT); - } else { - rspeak(NOTHING_EDIBLE); - } - break; - case SNAKE: - if (!game.closed && HERE(BIRD)) { - DESTROY(BIRD); - rspeak(BIRD_DEVOURED); - } else { - rspeak(NOTHING_EDIBLE); - } - break; - case TROLL: - rspeak(TROLL_VICES); - break; - case DWARF: - if (HERE(FOOD)) { - game.dflag += 2; - rspeak(REALLY_MAD); - } else { - speak(actions[verb].message); - } - break; - case BEAR: - if (game.objects[BEAR].prop == BEAR_DEAD) { - rspeak(RIDICULOUS_ATTEMPT); - break; - } - if (game.objects[BEAR].prop == UNTAMED_BEAR) { - if (HERE(FOOD)) { - DESTROY(FOOD); - game.objects[AXE].fixed = IS_FREE; - game.objects[AXE].prop = AXE_HERE; - state_change(BEAR, SITTING_BEAR); - } else { - rspeak(NOTHING_EDIBLE); - } - break; - } - speak(actions[verb].message); - break; - case OGRE: - if (HERE(FOOD)) { - rspeak(OGRE_FULL); - } else { - speak(actions[verb].message); + /* Feed. If bird, no seed. Snake, dragon, troll: quip. If dwarf, + * make him mad. Bear, special. */ + switch (obj) { + case BIRD: + rspeak(BIRD_PINING); + break; + case DRAGON: + if (game.objects[DRAGON].prop != DRAGON_BARS) { + rspeak(RIDICULOUS_ATTEMPT); + } else { + rspeak(NOTHING_EDIBLE); + } + break; + case SNAKE: + if (!game.closed && HERE(BIRD)) { + DESTROY(BIRD); + rspeak(BIRD_DEVOURED); + } else { + rspeak(NOTHING_EDIBLE); + } + break; + case TROLL: + rspeak(TROLL_VICES); + break; + case DWARF: + if (HERE(FOOD)) { + game.dflag += 2; + rspeak(REALLY_MAD); + } else { + speak(actions[verb].message); + } + break; + case BEAR: + if (game.objects[BEAR].prop == BEAR_DEAD) { + rspeak(RIDICULOUS_ATTEMPT); + break; + } + if (game.objects[BEAR].prop == UNTAMED_BEAR) { + if (HERE(FOOD)) { + DESTROY(FOOD); + game.objects[AXE].fixed = IS_FREE; + game.objects[AXE].prop = AXE_HERE; + state_change(BEAR, SITTING_BEAR); + } else { + rspeak(NOTHING_EDIBLE); + } + break; + } + speak(actions[verb].message); + break; + case OGRE: + if (HERE(FOOD)) { + rspeak(OGRE_FULL); + } else { + speak(actions[verb].message); + } + break; + default: + rspeak(AM_GAME); } - break; - default: - rspeak(AM_GAME); - } - return GO_CLEAROBJ; + return GO_CLEAROBJ; } phase_codes_t fill(verb_t verb, obj_t obj) { -/* Fill. Bottle or urn must be empty, and liquid available. (Vase - * is nasty.) */ - if (obj == VASE) { - if (LIQLOC(game.loc) == NO_OBJECT) { - rspeak(FILL_INVALID); - return GO_CLEAROBJ; - } - if (!TOTING(VASE)) { - rspeak(ARENT_CARRYING); - return GO_CLEAROBJ; - } - rspeak(SHATTER_VASE); - game.objects[VASE].prop = VASE_BROKEN; - game.objects[VASE].fixed = IS_FIXED; - drop(VASE, game.loc); - return GO_CLEAROBJ; - } - - if (obj == URN) { - if (game.objects[URN].prop != URN_EMPTY) { - rspeak(FULL_URN); - return GO_CLEAROBJ; - } - if (!HERE(BOTTLE)) { - rspeak(FILL_INVALID); - return GO_CLEAROBJ; - } - int k = LIQUID(); - switch (k) { - case WATER: - game.objects[BOTTLE].prop = EMPTY_BOTTLE; - rspeak(WATER_URN); - break; - case OIL: - game.objects[URN].prop = URN_DARK; - game.objects[BOTTLE].prop = EMPTY_BOTTLE; - rspeak(OIL_URN); - break; - case NO_OBJECT: - default: - rspeak(FILL_INVALID); - return GO_CLEAROBJ; - } - game.objects[k].place = LOC_NOWHERE; - return GO_CLEAROBJ; - } - if (obj != INTRANSITIVE && obj != BOTTLE) { - speak(actions[verb].message); - return GO_CLEAROBJ; - } - if (obj == INTRANSITIVE && !HERE(BOTTLE)) - return GO_UNKNOWN; - - if (HERE(URN) && game.objects[URN].prop != URN_EMPTY) { - rspeak(URN_NOPOUR); - return GO_CLEAROBJ; - } - if (LIQUID() != NO_OBJECT) { - rspeak(BOTTLE_FULL); - return GO_CLEAROBJ; - } - if (LIQLOC(game.loc) == NO_OBJECT) { - rspeak(NO_LIQUID); - return GO_CLEAROBJ; - } - - state_change(BOTTLE, (LIQLOC(game.loc) == OIL) - ? OIL_BOTTLE - : WATER_BOTTLE); - if (TOTING(BOTTLE)) { - game.objects[LIQUID()].place = CARRIED; - } - return GO_CLEAROBJ; + /* Fill. Bottle or urn must be empty, and liquid available. (Vase + * is nasty.) */ + if (obj == VASE) { + if (LIQLOC(game.loc) == NO_OBJECT) { + rspeak(FILL_INVALID); + return GO_CLEAROBJ; + } + if (!TOTING(VASE)) { + rspeak(ARENT_CARRYING); + return GO_CLEAROBJ; + } + rspeak(SHATTER_VASE); + game.objects[VASE].prop = VASE_BROKEN; + game.objects[VASE].fixed = IS_FIXED; + drop(VASE, game.loc); + return GO_CLEAROBJ; + } + + if (obj == URN) { + if (game.objects[URN].prop != URN_EMPTY) { + rspeak(FULL_URN); + return GO_CLEAROBJ; + } + if (!HERE(BOTTLE)) { + rspeak(FILL_INVALID); + return GO_CLEAROBJ; + } + int k = LIQUID(); + switch (k) { + case WATER: + game.objects[BOTTLE].prop = EMPTY_BOTTLE; + rspeak(WATER_URN); + break; + case OIL: + game.objects[URN].prop = URN_DARK; + game.objects[BOTTLE].prop = EMPTY_BOTTLE; + rspeak(OIL_URN); + break; + case NO_OBJECT: + default: + rspeak(FILL_INVALID); + return GO_CLEAROBJ; + } + game.objects[k].place = LOC_NOWHERE; + return GO_CLEAROBJ; + } + if (obj != INTRANSITIVE && obj != BOTTLE) { + speak(actions[verb].message); + return GO_CLEAROBJ; + } + if (obj == INTRANSITIVE && !HERE(BOTTLE)) + return GO_UNKNOWN; + + if (HERE(URN) && game.objects[URN].prop != URN_EMPTY) { + rspeak(URN_NOPOUR); + return GO_CLEAROBJ; + } + if (LIQUID() != NO_OBJECT) { + rspeak(BOTTLE_FULL); + return GO_CLEAROBJ; + } + if (LIQLOC(game.loc) == NO_OBJECT) { + rspeak(NO_LIQUID); + return GO_CLEAROBJ; + } + + state_change(BOTTLE, + (LIQLOC(game.loc) == OIL) ? OIL_BOTTLE : WATER_BOTTLE); + if (TOTING(BOTTLE)) { + game.objects[LIQUID()].place = CARRIED; + } + return GO_CLEAROBJ; } static phase_codes_t find(verb_t verb, obj_t obj) { -/* Find. Might be carrying it, or it might be here. Else give caveat. */ - if (TOTING(obj)) { - rspeak(ALREADY_CARRYING); - return GO_CLEAROBJ; - } - - if (game.closed) { - rspeak(NEEDED_NEARBY); - return GO_CLEAROBJ; - } - - if (AT(obj) || (LIQUID() == obj && AT(BOTTLE)) || - obj == LIQLOC(game.loc) || (obj == DWARF && atdwrf(game.loc) > 0)) { - rspeak(YOU_HAVEIT); - return GO_CLEAROBJ; - } - - - speak(actions[verb].message); - return GO_CLEAROBJ; + /* Find. Might be carrying it, or it might be here. Else give caveat. + */ + if (TOTING(obj)) { + rspeak(ALREADY_CARRYING); + return GO_CLEAROBJ; + } + + if (game.closed) { + rspeak(NEEDED_NEARBY); + return GO_CLEAROBJ; + } + + if (AT(obj) || (LIQUID() == obj && AT(BOTTLE)) || + obj == LIQLOC(game.loc) || (obj == DWARF && atdwrf(game.loc) > 0)) { + rspeak(YOU_HAVEIT); + return GO_CLEAROBJ; + } + + speak(actions[verb].message); + return GO_CLEAROBJ; } static phase_codes_t fly(verb_t verb, obj_t obj) { -/* Fly. Snide remarks unless hovering rug is here. */ - if (obj == INTRANSITIVE) { - if (!HERE(RUG)) { - rspeak(FLAP_ARMS); - return GO_CLEAROBJ; - } - if (game.objects[RUG].prop != RUG_HOVER) { - rspeak(RUG_NOTHING2); - return GO_CLEAROBJ; - } - obj = RUG; - } - - if (obj != RUG) { - speak(actions[verb].message); - return GO_CLEAROBJ; - } - if (game.objects[RUG].prop != RUG_HOVER) { - rspeak(RUG_NOTHING1); - return GO_CLEAROBJ; - } - - if (game.loc == LOC_CLIFF) { - game.oldlc2 = game.oldloc; - game.oldloc = game.loc; - game.newloc = LOC_LEDGE; - rspeak(RUG_GOES); - } else if (game.loc == LOC_LEDGE) { - game.oldlc2 = game.oldloc; - game.oldloc = game.loc; - game.newloc = LOC_CLIFF; - rspeak(RUG_RETURNS); - } else { -// LCOV_EXCL_START - /* should never happen */ - rspeak(NOTHING_HAPPENS); -// LCOV_EXCL_STOP - } - return GO_TERMINATE; + /* Fly. Snide remarks unless hovering rug is here. */ + if (obj == INTRANSITIVE) { + if (!HERE(RUG)) { + rspeak(FLAP_ARMS); + return GO_CLEAROBJ; + } + if (game.objects[RUG].prop != RUG_HOVER) { + rspeak(RUG_NOTHING2); + return GO_CLEAROBJ; + } + obj = RUG; + } + + if (obj != RUG) { + speak(actions[verb].message); + return GO_CLEAROBJ; + } + if (game.objects[RUG].prop != RUG_HOVER) { + rspeak(RUG_NOTHING1); + return GO_CLEAROBJ; + } + + if (game.loc == LOC_CLIFF) { + game.oldlc2 = game.oldloc; + game.oldloc = game.loc; + game.newloc = LOC_LEDGE; + rspeak(RUG_GOES); + } else if (game.loc == LOC_LEDGE) { + game.oldlc2 = game.oldloc; + game.oldloc = game.loc; + game.newloc = LOC_CLIFF; + rspeak(RUG_RETURNS); + } else { + // LCOV_EXCL_START + /* should never happen */ + rspeak(NOTHING_HAPPENS); + // LCOV_EXCL_STOP + } + return GO_TERMINATE; } static phase_codes_t inven(void) { -/* Inventory. If object, treat same as find. Else report on current burden. */ - bool empty = true; - for (obj_t i = 1; i <= NOBJECTS; i++) { - if (i == BEAR || !TOTING(i)) - continue; - if (empty) { - rspeak(NOW_HOLDING); - empty = false; - } - pspeak(i, touch, false, -1); - } - if (TOTING(BEAR)) - rspeak(TAME_BEAR); - if (empty) - rspeak(NO_CARRY); - return GO_CLEAROBJ; + /* Inventory. If object, treat same as find. Else report on current + * burden. */ + bool empty = true; + for (obj_t i = 1; i <= NOBJECTS; i++) { + if (i == BEAR || !TOTING(i)) + continue; + if (empty) { + rspeak(NOW_HOLDING); + empty = false; + } + pspeak(i, touch, false, -1); + } + if (TOTING(BEAR)) + rspeak(TAME_BEAR); + if (empty) + rspeak(NO_CARRY); + return GO_CLEAROBJ; } static phase_codes_t light(verb_t verb, obj_t obj) { -/* Light. Applicable only to lamp and urn. */ - if (obj == INTRANSITIVE) { - int selects = 0; - if (HERE(LAMP) && game.objects[LAMP].prop == LAMP_DARK && game.limit >= 0) { - obj = LAMP; - selects++; - } - if (HERE(URN) && game.objects[URN].prop == URN_DARK) { - obj = URN; - selects++; - } - if (selects != 1) - return GO_UNKNOWN; - } - - switch (obj) { - case URN: - state_change(URN, game.objects[URN].prop == URN_EMPTY ? - URN_EMPTY : - URN_LIT); - break; - case LAMP: - if (game.limit < 0) { - rspeak(LAMP_OUT); - break; - } - state_change(LAMP, LAMP_BRIGHT); - if (game.wzdark) { - return GO_TOP; - } - break; - default: - speak(actions[verb].message); - } - return GO_CLEAROBJ; + /* Light. Applicable only to lamp and urn. */ + if (obj == INTRANSITIVE) { + int selects = 0; + if (HERE(LAMP) && game.objects[LAMP].prop == LAMP_DARK && + game.limit >= 0) { + obj = LAMP; + selects++; + } + if (HERE(URN) && game.objects[URN].prop == URN_DARK) { + obj = URN; + selects++; + } + if (selects != 1) + return GO_UNKNOWN; + } + + switch (obj) { + case URN: + state_change(URN, game.objects[URN].prop == URN_EMPTY + ? URN_EMPTY + : URN_LIT); + break; + case LAMP: + if (game.limit < 0) { + rspeak(LAMP_OUT); + break; + } + state_change(LAMP, LAMP_BRIGHT); + if (game.wzdark) { + return GO_TOP; + } + break; + default: + speak(actions[verb].message); + } + return GO_CLEAROBJ; } static phase_codes_t listen(void) { -/* Listen. Intransitive only. Print stuff based on object sound properties. */ - bool soundlatch = false; - vocab_t sound = locations[game.loc].sound; - if (sound != SILENT) { - rspeak(sound); - if (!locations[game.loc].loud) { - rspeak(NO_MESSAGE); - } - soundlatch = true; - } - for (obj_t i = 1; i <= NOBJECTS; i++) { - if (!HERE(i) || objects[i].sounds[0] == NULL || PROP_IS_STASHED_OR_UNSEEN(i)) { - continue; - } - int mi = game.objects[i].prop; - /* (ESR) Some unpleasant magic on object states here. Ideally - * we'd have liked the bird to be a normal object that we can - * use state_change() on; can't do it, because there are - * actually two different series of per-state birdsounds - * depending on whether player has drunk dragon's blood. */ - if (i == BIRD) { - mi += 3 * game.blooded; - } - pspeak(i, hear, true, mi, game.zzword); - rspeak(NO_MESSAGE); - if (i == BIRD && mi == BIRD_ENDSTATE) { - DESTROY(BIRD); - } - soundlatch = true; - } - if (!soundlatch) { - rspeak(ALL_SILENT); - } - return GO_CLEAROBJ; + /* Listen. Intransitive only. Print stuff based on object sound + * properties. */ + bool soundlatch = false; + vocab_t sound = locations[game.loc].sound; + if (sound != SILENT) { + rspeak(sound); + if (!locations[game.loc].loud) { + rspeak(NO_MESSAGE); + } + soundlatch = true; + } + for (obj_t i = 1; i <= NOBJECTS; i++) { + if (!HERE(i) || objects[i].sounds[0] == NULL || + PROP_IS_STASHED_OR_UNSEEN(i)) { + continue; + } + int mi = game.objects[i].prop; + /* (ESR) Some unpleasant magic on object states here. Ideally + * we'd have liked the bird to be a normal object that we can + * use state_change() on; can't do it, because there are + * actually two different series of per-state birdsounds + * depending on whether player has drunk dragon's blood. */ + if (i == BIRD) { + mi += 3 * game.blooded; + } + pspeak(i, hear, true, mi, game.zzword); + rspeak(NO_MESSAGE); + if (i == BIRD && mi == BIRD_ENDSTATE) { + DESTROY(BIRD); + } + soundlatch = true; + } + if (!soundlatch) { + rspeak(ALL_SILENT); + } + return GO_CLEAROBJ; } static phase_codes_t lock(verb_t verb, obj_t obj) { -/* Lock, unlock, no object given. Assume various things if present. */ - if (obj == INTRANSITIVE) { - if (HERE(CLAM)) { - obj = CLAM; - } - if (HERE(OYSTER)) { - obj = OYSTER; - } - if (AT(DOOR)) { - obj = DOOR; - } - if (AT(GRATE)) { - obj = GRATE; - } - if (HERE(CHAIN)) { - obj = CHAIN; - } - if (obj == INTRANSITIVE) { - rspeak(NOTHING_LOCKED); - return GO_CLEAROBJ; - } - } - - /* Lock, unlock object. Special stuff for opening clam/oyster - * and for chain. */ - - switch (obj) { - case CHAIN: - if (HERE(KEYS)) { - return chain(verb); - } else { - rspeak(NO_KEYS); - } - break; - case GRATE: - if (HERE(KEYS)) { - if (game.closng) { - rspeak(EXIT_CLOSED); - if (!game.panic) { - game.clock2 = PANICTIME; - } - game.panic = true; - } else { - state_change(GRATE, (verb == LOCK) ? - GRATE_CLOSED : - GRATE_OPEN); - } - } else { - rspeak(NO_KEYS); - } - break; - case CLAM: - if (verb == LOCK) { - rspeak(HUH_MAN); - } else if (TOTING(CLAM)) { - rspeak(DROP_CLAM); - } else if (!TOTING(TRIDENT)) { - rspeak(CLAM_OPENER); - } else { - DESTROY(CLAM); - drop(OYSTER, game.loc); - drop(PEARL, LOC_CULDESAC); - rspeak(PEARL_FALLS); - } - break; - case OYSTER: - if (verb == LOCK) { - rspeak(HUH_MAN); - } else if (TOTING(OYSTER)) { - rspeak(DROP_OYSTER); - } else if (!TOTING(TRIDENT)) { - rspeak(OYSTER_OPENER); - } else { - rspeak(OYSTER_OPENS); - } - break; - case DOOR: - rspeak((game.objects[DOOR].prop == DOOR_UNRUSTED) ? OK_MAN : RUSTY_DOOR); - break; - case CAGE: - rspeak( NO_LOCK); - break; - case KEYS: - rspeak(CANNOT_UNLOCK); - break; - default: - speak(actions[verb].message); - } - - return GO_CLEAROBJ; + /* Lock, unlock, no object given. Assume various things if present. */ + if (obj == INTRANSITIVE) { + if (HERE(CLAM)) { + obj = CLAM; + } + if (HERE(OYSTER)) { + obj = OYSTER; + } + if (AT(DOOR)) { + obj = DOOR; + } + if (AT(GRATE)) { + obj = GRATE; + } + if (HERE(CHAIN)) { + obj = CHAIN; + } + if (obj == INTRANSITIVE) { + rspeak(NOTHING_LOCKED); + return GO_CLEAROBJ; + } + } + + /* Lock, unlock object. Special stuff for opening clam/oyster + * and for chain. */ + + switch (obj) { + case CHAIN: + if (HERE(KEYS)) { + return chain(verb); + } else { + rspeak(NO_KEYS); + } + break; + case GRATE: + if (HERE(KEYS)) { + if (game.closng) { + rspeak(EXIT_CLOSED); + if (!game.panic) { + game.clock2 = PANICTIME; + } + game.panic = true; + } else { + state_change(GRATE, (verb == LOCK) + ? GRATE_CLOSED + : GRATE_OPEN); + } + } else { + rspeak(NO_KEYS); + } + break; + case CLAM: + if (verb == LOCK) { + rspeak(HUH_MAN); + } else if (TOTING(CLAM)) { + rspeak(DROP_CLAM); + } else if (!TOTING(TRIDENT)) { + rspeak(CLAM_OPENER); + } else { + DESTROY(CLAM); + drop(OYSTER, game.loc); + drop(PEARL, LOC_CULDESAC); + rspeak(PEARL_FALLS); + } + break; + case OYSTER: + if (verb == LOCK) { + rspeak(HUH_MAN); + } else if (TOTING(OYSTER)) { + rspeak(DROP_OYSTER); + } else if (!TOTING(TRIDENT)) { + rspeak(OYSTER_OPENER); + } else { + rspeak(OYSTER_OPENS); + } + break; + case DOOR: + rspeak((game.objects[DOOR].prop == DOOR_UNRUSTED) ? OK_MAN + : RUSTY_DOOR); + break; + case CAGE: + rspeak(NO_LOCK); + break; + case KEYS: + rspeak(CANNOT_UNLOCK); + break; + default: + speak(actions[verb].message); + } + + return GO_CLEAROBJ; } static phase_codes_t pour(verb_t verb, obj_t obj) { -/* Pour. If no object, or object is bottle, assume contents of bottle. - * special tests for pouring water or oil on plant or rusty door. */ - if (obj == BOTTLE || obj == INTRANSITIVE) { - obj = LIQUID(); - } - if (obj == NO_OBJECT) { - return GO_UNKNOWN; - } - if (!TOTING(obj)) { - speak(actions[verb].message); - return GO_CLEAROBJ; - } - - if (obj != OIL && obj != WATER) { - rspeak(CANT_POUR); - return GO_CLEAROBJ; - } - if (HERE(URN) && game.objects[URN].prop == URN_EMPTY) { - return fill(verb, URN); - } - game.objects[BOTTLE].prop = EMPTY_BOTTLE; - game.objects[obj].place = LOC_NOWHERE; - if (!(AT(PLANT) || AT(DOOR))) { - rspeak(GROUND_WET); - return GO_CLEAROBJ; - } - if (!AT(DOOR)) { - if (obj == WATER) { - /* cycle through the three plant states */ - state_change(PLANT, MOD(game.objects[PLANT].prop + 1, 3)); - game.objects[PLANT2].prop = game.objects[PLANT].prop; - return GO_MOVE; - } else { - rspeak(SHAKING_LEAVES); - return GO_CLEAROBJ; - } - } else { - state_change(DOOR, (obj == OIL) ? - DOOR_UNRUSTED : - DOOR_RUSTED); - return GO_CLEAROBJ; - } + /* Pour. If no object, or object is bottle, assume contents of bottle. + * special tests for pouring water or oil on plant or rusty door. */ + if (obj == BOTTLE || obj == INTRANSITIVE) { + obj = LIQUID(); + } + if (obj == NO_OBJECT) { + return GO_UNKNOWN; + } + if (!TOTING(obj)) { + speak(actions[verb].message); + return GO_CLEAROBJ; + } + + if (obj != OIL && obj != WATER) { + rspeak(CANT_POUR); + return GO_CLEAROBJ; + } + if (HERE(URN) && game.objects[URN].prop == URN_EMPTY) { + return fill(verb, URN); + } + game.objects[BOTTLE].prop = EMPTY_BOTTLE; + game.objects[obj].place = LOC_NOWHERE; + if (!(AT(PLANT) || AT(DOOR))) { + rspeak(GROUND_WET); + return GO_CLEAROBJ; + } + if (!AT(DOOR)) { + if (obj == WATER) { + /* cycle through the three plant states */ + state_change(PLANT, + MOD(game.objects[PLANT].prop + 1, 3)); + game.objects[PLANT2].prop = game.objects[PLANT].prop; + return GO_MOVE; + } else { + rspeak(SHAKING_LEAVES); + return GO_CLEAROBJ; + } + } else { + state_change(DOOR, (obj == OIL) ? DOOR_UNRUSTED : DOOR_RUSTED); + return GO_CLEAROBJ; + } } static phase_codes_t quit(void) { -/* Quit. Intransitive only. Verify intent and exit if that's what he wants. */ - if (yes_or_no(arbitrary_messages[REALLY_QUIT], arbitrary_messages[OK_MAN], arbitrary_messages[OK_MAN])) { - terminate(quitgame); - } - return GO_CLEAROBJ; + /* Quit. Intransitive only. Verify intent and exit if that's what he + * wants. */ + if (yes_or_no(arbitrary_messages[REALLY_QUIT], + arbitrary_messages[OK_MAN], arbitrary_messages[OK_MAN])) { + terminate(quitgame); + } + return GO_CLEAROBJ; } static phase_codes_t read(command_t command) /* Read. Print stuff based on objtxt. Oyster (?) is special case. */ { - if (command.obj == INTRANSITIVE) { - command.obj = NO_OBJECT; - for (int i = 1; i <= NOBJECTS; i++) { - if (HERE(i) && objects[i].texts[0] != NULL && !PROP_IS_STASHED(i)) { - command.obj = command.obj * NOBJECTS + i; - } - } - if (command.obj > NOBJECTS || command.obj == NO_OBJECT || DARK(game.loc)) { - return GO_UNKNOWN; - } - } - - if (DARK(game.loc)) { - sspeak(NO_SEE, command.word[0].raw); - } else if (command.obj == OYSTER) { - if (!TOTING(OYSTER) || !game.closed) { - rspeak(DONT_UNDERSTAND); - } else if (!game.clshnt) { - game.clshnt = yes_or_no(arbitrary_messages[CLUE_QUERY], arbitrary_messages[WAYOUT_CLUE], arbitrary_messages[OK_MAN]); - } else { - pspeak(OYSTER, hear, true, 1); // Not really a sound, but oh well. - } - } else if (objects[command.obj].texts[0] == NULL || PROP_IS_NOTFOUND(command.obj)) { - speak(actions[command.verb].message); - } else { - pspeak(command.obj, study, true, game.objects[command.obj].prop); - } - return GO_CLEAROBJ; + if (command.obj == INTRANSITIVE) { + command.obj = NO_OBJECT; + for (int i = 1; i <= NOBJECTS; i++) { + if (HERE(i) && objects[i].texts[0] != NULL && + !PROP_IS_STASHED(i)) { + command.obj = command.obj * NOBJECTS + i; + } + } + if (command.obj > NOBJECTS || command.obj == NO_OBJECT || + DARK(game.loc)) { + return GO_UNKNOWN; + } + } + + if (DARK(game.loc)) { + sspeak(NO_SEE, command.word[0].raw); + } else if (command.obj == OYSTER) { + if (!TOTING(OYSTER) || !game.closed) { + rspeak(DONT_UNDERSTAND); + } else if (!game.clshnt) { + game.clshnt = yes_or_no(arbitrary_messages[CLUE_QUERY], + arbitrary_messages[WAYOUT_CLUE], + arbitrary_messages[OK_MAN]); + } else { + pspeak(OYSTER, hear, true, + 1); // Not really a sound, but oh well. + } + } else if (objects[command.obj].texts[0] == NULL || + PROP_IS_NOTFOUND(command.obj)) { + speak(actions[command.verb].message); + } else { + pspeak(command.obj, study, true, + game.objects[command.obj].prop); + } + return GO_CLEAROBJ; } static phase_codes_t reservoir(void) { -/* Z'ZZZ (word gets recomputed at startup; different each game). */ - if (!AT(RESER) && game.loc != LOC_RESBOTTOM) { - rspeak(NOTHING_HAPPENS); - return GO_CLEAROBJ; - } else { - state_change(RESER, - game.objects[RESER].prop == WATERS_PARTED ? WATERS_UNPARTED : WATERS_PARTED); - if (AT(RESER)) - return GO_CLEAROBJ; - else { - game.oldlc2 = game.loc; - game.newloc = LOC_NOWHERE; - rspeak(NOT_BRIGHT); - return GO_TERMINATE; - } - } + /* Z'ZZZ (word gets recomputed at startup; different each game). */ + if (!AT(RESER) && game.loc != LOC_RESBOTTOM) { + rspeak(NOTHING_HAPPENS); + return GO_CLEAROBJ; + } else { + state_change(RESER, game.objects[RESER].prop == WATERS_PARTED + ? WATERS_UNPARTED + : WATERS_PARTED); + if (AT(RESER)) + return GO_CLEAROBJ; + else { + game.oldlc2 = game.loc; + game.newloc = LOC_NOWHERE; + rspeak(NOT_BRIGHT); + return GO_TERMINATE; + } + } } static phase_codes_t rub(verb_t verb, obj_t obj) { -/* Rub. Yields various snide remarks except for lit urn. */ - if (obj == URN && game.objects[URN].prop == URN_LIT) { - DESTROY(URN); - drop(AMBER, game.loc); - game.objects[AMBER].prop = AMBER_IN_ROCK; - --game.tally; - drop(CAVITY, game.loc); - rspeak(URN_GENIES); - } else if (obj != LAMP) { - rspeak(PECULIAR_NOTHING); - } else { - speak(actions[verb].message); - } - return GO_CLEAROBJ; + /* Rub. Yields various snide remarks except for lit urn. */ + if (obj == URN && game.objects[URN].prop == URN_LIT) { + DESTROY(URN); + drop(AMBER, game.loc); + game.objects[AMBER].prop = AMBER_IN_ROCK; + --game.tally; + drop(CAVITY, game.loc); + rspeak(URN_GENIES); + } else if (obj != LAMP) { + rspeak(PECULIAR_NOTHING); + } else { + speak(actions[verb].message); + } + return GO_CLEAROBJ; } static phase_codes_t say(command_t command) { -/* Say. Echo WD2. Magic words override. */ - if (command.word[1].type == MOTION && - (command.word[1].id == XYZZY || - command.word[1].id == PLUGH || - command.word[1].id == PLOVER)) { - return GO_WORD2; - } - if (command.word[1].type == ACTION && command.word[1].id == PART) { - return reservoir(); - } - - if (command.word[1].type == ACTION && - (command.word[1].id == FEE || - command.word[1].id == FIE || - command.word[1].id == FOE || - command.word[1].id == FOO || - command.word[1].id == FUM || - command.word[1].id == PART)) { - return bigwords(command.word[1].id); - } - sspeak(OKEY_DOKEY, command.word[1].raw); - return GO_CLEAROBJ; + /* Say. Echo WD2. Magic words override. */ + if (command.word[1].type == MOTION && + (command.word[1].id == XYZZY || command.word[1].id == PLUGH || + command.word[1].id == PLOVER)) { + return GO_WORD2; + } + if (command.word[1].type == ACTION && command.word[1].id == PART) { + return reservoir(); + } + + if (command.word[1].type == ACTION && + (command.word[1].id == FEE || command.word[1].id == FIE || + command.word[1].id == FOE || command.word[1].id == FOO || + command.word[1].id == FUM || command.word[1].id == PART)) { + return bigwords(command.word[1].id); + } + sspeak(OKEY_DOKEY, command.word[1].raw); + return GO_CLEAROBJ; } -static phase_codes_t throw_support(vocab_t spk) -{ - rspeak(spk); - drop(AXE, game.loc); - return GO_MOVE; +static phase_codes_t throw_support(vocab_t spk) { + rspeak(spk); + drop(AXE, game.loc); + return GO_MOVE; } static phase_codes_t throwit(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 - * troll. Treasures special for troll. */ - if (!TOTING(command.obj)) { - speak(actions[command.verb].message); - return GO_CLEAROBJ; - } - if (objects[command.obj].is_treasure && AT(TROLL)) { - /* Snarf a treasure for the troll. */ - drop(command.obj, LOC_NOWHERE); - move(TROLL, LOC_NOWHERE); - move(TROLL + NOBJECTS, IS_FREE); - drop(TROLL2, objects[TROLL].plac); - drop(TROLL2 + NOBJECTS, objects[TROLL].fixd); - juggle(CHASM); - rspeak(TROLL_SATISFIED); - return GO_CLEAROBJ; - } - if (command.obj == FOOD && HERE(BEAR)) { - /* But throwing food is another story. */ - command.obj = BEAR; - return (feed(command.verb, command.obj)); - } - if (command.obj != AXE) { - return (discard(command.verb, command.obj)); - } else { - if (atdwrf(game.loc) <= 0) { - if (AT(DRAGON) && game.objects[DRAGON].prop == DRAGON_BARS) - return throw_support(DRAGON_SCALES); - if (AT(TROLL)) { - return throw_support(TROLL_RETURNS); - } - if (AT(OGRE)) { - return throw_support(OGRE_DODGE); - } - if (HERE(BEAR) && game.objects[BEAR].prop == UNTAMED_BEAR) { - /* This'll teach him to throw the axe at the bear! */ - drop(AXE, game.loc); - game.objects[AXE].fixed = IS_FIXED; - juggle(BEAR); - state_change(AXE, AXE_LOST); - return GO_CLEAROBJ; - } - command.obj = INTRANSITIVE; - return (attack(command)); - } - - if (randrange(NDWARVES + 1) < game.dflag) { - return throw_support(DWARF_DODGES); - } else { - int i = atdwrf(game.loc); - game.dwarves[i].seen = false; - game.dwarves[i].loc = LOC_NOWHERE; - return throw_support((++game.dkill == 1) ? - DWARF_SMOKE : - KILLED_DWARF); - } - } + /* 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 + * troll. Treasures special for troll. */ + if (!TOTING(command.obj)) { + speak(actions[command.verb].message); + return GO_CLEAROBJ; + } + if (objects[command.obj].is_treasure && AT(TROLL)) { + /* Snarf a treasure for the troll. */ + drop(command.obj, LOC_NOWHERE); + move(TROLL, LOC_NOWHERE); + move(TROLL + NOBJECTS, IS_FREE); + drop(TROLL2, objects[TROLL].plac); + drop(TROLL2 + NOBJECTS, objects[TROLL].fixd); + juggle(CHASM); + rspeak(TROLL_SATISFIED); + return GO_CLEAROBJ; + } + if (command.obj == FOOD && HERE(BEAR)) { + /* But throwing food is another story. */ + command.obj = BEAR; + return (feed(command.verb, command.obj)); + } + if (command.obj != AXE) { + return (discard(command.verb, command.obj)); + } else { + if (atdwrf(game.loc) <= 0) { + if (AT(DRAGON) && + game.objects[DRAGON].prop == DRAGON_BARS) + return throw_support(DRAGON_SCALES); + if (AT(TROLL)) { + return throw_support(TROLL_RETURNS); + } + if (AT(OGRE)) { + return throw_support(OGRE_DODGE); + } + if (HERE(BEAR) && + game.objects[BEAR].prop == UNTAMED_BEAR) { + /* This'll teach him to throw the axe at the + * bear! */ + drop(AXE, game.loc); + game.objects[AXE].fixed = IS_FIXED; + juggle(BEAR); + state_change(AXE, AXE_LOST); + return GO_CLEAROBJ; + } + command.obj = INTRANSITIVE; + return (attack(command)); + } + + if (randrange(NDWARVES + 1) < game.dflag) { + return throw_support(DWARF_DODGES); + } else { + int i = atdwrf(game.loc); + game.dwarves[i].seen = false; + game.dwarves[i].loc = LOC_NOWHERE; + return throw_support( + (++game.dkill == 1) ? DWARF_SMOKE : KILLED_DWARF); + } + } } static phase_codes_t wake(verb_t verb, obj_t obj) { -/* Wake. Only use is to disturb the dwarves. */ - if (obj != DWARF || !game.closed) { - speak(actions[verb].message); - return GO_CLEAROBJ; - } else { - rspeak(PROD_DWARF); - return GO_DWARFWAKE; - } + /* Wake. Only use is to disturb the dwarves. */ + if (obj != DWARF || !game.closed) { + speak(actions[verb].message); + return GO_CLEAROBJ; + } else { + rspeak(PROD_DWARF); + return GO_DWARFWAKE; + } } static phase_codes_t seed(verb_t verb, const char *arg) { -/* Set seed */ - int32_t seed = strtol(arg, NULL, 10); - speak(actions[verb].message, seed); - set_seed(seed); - --game.turns; - return GO_TOP; + /* Set seed */ + int32_t seed = strtol(arg, NULL, 10); + speak(actions[verb].message, seed); + set_seed(seed); + --game.turns; + return GO_TOP; } static phase_codes_t waste(verb_t verb, turn_t turns) { -/* Burn turns */ - game.limit -= turns; - speak(actions[verb].message, (int)game.limit); - return GO_TOP; + /* Burn turns */ + game.limit -= turns; + speak(actions[verb].message, (int)game.limit); + return GO_TOP; } static phase_codes_t wave(verb_t verb, obj_t obj) { -/* Wave. No effect unless waving rod at fissure or at bird. */ - if (obj != ROD || !TOTING(obj) || (!HERE(BIRD) && (game.closng || !AT(FISSURE)))) { - speak(((!TOTING(obj)) && (obj != ROD || - !TOTING(ROD2))) ? - arbitrary_messages[ARENT_CARRYING] : - actions[verb].message); - return GO_CLEAROBJ; - } - - if (game.objects[BIRD].prop == BIRD_UNCAGED && game.loc == game.objects[STEPS].place - && PROP_IS_NOTFOUND(JADE)) { - drop(JADE, game.loc); - PROP_SET_FOUND(JADE); - --game.tally; - rspeak(NECKLACE_FLY); - return GO_CLEAROBJ; - } else { - if (game.closed) { - rspeak((game.objects[BIRD].prop == BIRD_CAGED) ? - CAGE_FLY : - FREE_FLY); - return GO_DWARFWAKE; - } - if (game.closng || !AT(FISSURE)) { - rspeak((game.objects[BIRD].prop == BIRD_CAGED) ? - CAGE_FLY : - FREE_FLY); - return GO_CLEAROBJ; - } - if (HERE(BIRD)) - rspeak((game.objects[BIRD].prop == BIRD_CAGED) ? - CAGE_FLY : - FREE_FLY); - - state_change(FISSURE, - game.objects[FISSURE].prop == BRIDGED ? UNBRIDGED : BRIDGED); - return GO_CLEAROBJ; - } + /* Wave. No effect unless waving rod at fissure or at bird. */ + if (obj != ROD || !TOTING(obj) || + (!HERE(BIRD) && (game.closng || !AT(FISSURE)))) { + speak(((!TOTING(obj)) && (obj != ROD || !TOTING(ROD2))) + ? arbitrary_messages[ARENT_CARRYING] + : actions[verb].message); + return GO_CLEAROBJ; + } + + if (game.objects[BIRD].prop == BIRD_UNCAGED && + game.loc == game.objects[STEPS].place && PROP_IS_NOTFOUND(JADE)) { + drop(JADE, game.loc); + PROP_SET_FOUND(JADE); + --game.tally; + rspeak(NECKLACE_FLY); + return GO_CLEAROBJ; + } else { + if (game.closed) { + rspeak((game.objects[BIRD].prop == BIRD_CAGED) + ? CAGE_FLY + : FREE_FLY); + return GO_DWARFWAKE; + } + if (game.closng || !AT(FISSURE)) { + rspeak((game.objects[BIRD].prop == BIRD_CAGED) + ? CAGE_FLY + : FREE_FLY); + return GO_CLEAROBJ; + } + if (HERE(BIRD)) + rspeak((game.objects[BIRD].prop == BIRD_CAGED) + ? CAGE_FLY + : FREE_FLY); + + state_change(FISSURE, game.objects[FISSURE].prop == BRIDGED + ? UNBRIDGED + : BRIDGED); + return GO_CLEAROBJ; + } } phase_codes_t action(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. - */ - /* Previously, actions that result in a message, but don't do anything - * further were called "specials". Now they're handled here as normal - * actions. If noaction is true, then we spit out the message and return */ - if (actions[command.verb].noaction) { - speak(actions[command.verb].message); - return GO_CLEAROBJ; - } - - if (command.part == unknown) { - /* Analyse an object word. See if the thing is here, whether - * we've got a verb yet, and so on. Object must be here - * unless verb is "find" or "invent(ory)" (and no new verb - * yet to be analysed). Water and oil are also funny, since - * they are never actually dropped at any location, but might - * be here inside the bottle or urn or as a feature of the - * location. */ - if (HERE(command.obj)) { - /* FALL THROUGH */; - } else if (command.obj == DWARF && atdwrf(game.loc) > 0) { - /* FALL THROUGH */; - } else if (!game.closed && ((LIQUID() == command.obj && HERE(BOTTLE)) || - command.obj == LIQLOC(game.loc))) { - /* FALL THROUGH */; - } else if (command.obj == OIL && HERE(URN) && game.objects[URN].prop != URN_EMPTY) { - command.obj = URN; - /* FALL THROUGH */; - } else if (command.obj == PLANT && AT(PLANT2) && game.objects[PLANT2].prop != PLANT_THIRSTY) { - command.obj = PLANT2; - /* FALL THROUGH */; - } else if (command.obj == KNIFE && game.knfloc == game.loc) { - game.knfloc = -1; - rspeak(KNIVES_VANISH); - return GO_CLEAROBJ; - } else if (command.obj == ROD && HERE(ROD2)) { - command.obj = ROD2; - /* FALL THROUGH */; - } else if ((command.verb == FIND || - command.verb == INVENTORY) && (command.word[1].id == WORD_EMPTY || command.word[1].id == WORD_NOT_FOUND)) { - /* FALL THROUGH */; - } else { - sspeak(NO_SEE, command.word[0].raw); - return GO_CLEAROBJ; - } - - if (command.verb != 0) { - command.part = transitive; - } - } - - switch (command.part) { - case intransitive: - if (command.word[1].raw[0] != '\0' && command.verb != SAY) { - return GO_WORD2; - } - if (command.verb == SAY) { - /* KEYS is not special, anything not NO_OBJECT or INTRANSITIVE - * will do here. We're preventing interpretation as an intransitive - * verb when the word is unknown. */ - command.obj = command.word[1].raw[0] != '\0' ? KEYS : NO_OBJECT; - } - if (command.obj == NO_OBJECT || command.obj == INTRANSITIVE) { - /* Analyse an intransitive verb (ie, no object given yet). */ - switch (command.verb) { - case CARRY: - return vcarry(command.verb, INTRANSITIVE); - case DROP: - return GO_UNKNOWN; - case SAY: - return GO_UNKNOWN; - case UNLOCK: - return lock(command.verb, INTRANSITIVE); - case NOTHING: { - rspeak(OK_MAN); - return (GO_CLEAROBJ); - } - case LOCK: - return lock(command.verb, INTRANSITIVE); - case LIGHT: - return light(command.verb, INTRANSITIVE); - case EXTINGUISH: - return extinguish(command.verb, INTRANSITIVE); - case WAVE: - return GO_UNKNOWN; - case TAME: - return GO_UNKNOWN; - case GO: { - speak(actions[command.verb].message); - return GO_CLEAROBJ; - } - case ATTACK: - command.obj = INTRANSITIVE; - return attack(command); - case POUR: - return pour(command.verb, INTRANSITIVE); - case EAT: - return eat(command.verb, INTRANSITIVE); - case DRINK: - return drink(command.verb, INTRANSITIVE); - case RUB: - return GO_UNKNOWN; - case THROW: - return GO_UNKNOWN; - case QUIT: - return quit(); - case FIND: - return GO_UNKNOWN; - case INVENTORY: - return inven(); - case FEED: - return GO_UNKNOWN; - case FILL: - return fill(command.verb, INTRANSITIVE); - case BLAST: - blast(); - return GO_CLEAROBJ; - case SCORE: - score(scoregame); - return GO_CLEAROBJ; - case FEE: - case FIE: - case FOE: - case FOO: - case FUM: - return bigwords(command.word[0].id); - case BRIEF: - return brief(); - case READ: - command.obj = INTRANSITIVE; - return read(command); - case BREAK: - return GO_UNKNOWN; - case WAKE: - return GO_UNKNOWN; - case SAVE: - return suspend(); - case RESUME: - return resume(); - case FLY: - return fly(command.verb, INTRANSITIVE); - case LISTEN: - return listen(); - case PART: - return reservoir(); - case SEED: - case WASTE: - rspeak(NUMERIC_REQUIRED); - return GO_TOP; - default: // LCOV_EXCL_LINE - BUG(INTRANSITIVE_ACTION_VERB_EXCEEDS_GOTO_LIST); // LCOV_EXCL_LINE - } - } - /* FALLTHRU */ - case transitive: - /* Analyse a transitive verb. */ - switch (command.verb) { - case CARRY: - return vcarry(command.verb, command.obj); - case DROP: - return discard(command.verb, command.obj); - case SAY: - return say(command); - case UNLOCK: - return lock(command.verb, command.obj); - case NOTHING: { - rspeak(OK_MAN); - return (GO_CLEAROBJ); - } - case LOCK: - return lock(command.verb, command.obj); - case LIGHT: - return light(command.verb, command.obj); - case EXTINGUISH: - return extinguish(command.verb, command.obj); - case WAVE: - return wave(command.verb, command.obj); - case TAME: { - speak(actions[command.verb].message); - return GO_CLEAROBJ; - } - case GO: { - speak(actions[command.verb].message); - return GO_CLEAROBJ; - } - case ATTACK: - return attack(command); - case POUR: - return pour(command.verb, command.obj); - case EAT: - return eat(command.verb, command.obj); - case DRINK: - return drink(command.verb, command.obj); - case RUB: - return rub(command.verb, command.obj); - case THROW: - return throwit(command); - case QUIT: - speak(actions[command.verb].message); - return GO_CLEAROBJ; - case FIND: - return find(command.verb, command.obj); - case INVENTORY: - return find(command.verb, command.obj); - case FEED: - return feed(command.verb, command.obj); - case FILL: - return fill(command.verb, command.obj); - case BLAST: - blast(); - return GO_CLEAROBJ; - case SCORE: - speak(actions[command.verb].message); - return GO_CLEAROBJ; - case FEE: - case FIE: - case FOE: - case FOO: - case FUM: - speak(actions[command.verb].message); - return GO_CLEAROBJ; - case BRIEF: - speak(actions[command.verb].message); - return GO_CLEAROBJ; - case READ: - return read(command); - case BREAK: - return vbreak(command.verb, command.obj); - case WAKE: - return wake(command.verb, command.obj); - case SAVE: - speak(actions[command.verb].message); - return GO_CLEAROBJ; - case RESUME: - speak(actions[command.verb].message); - return GO_CLEAROBJ; - case FLY: - return fly(command.verb, command.obj); - case LISTEN: - speak(actions[command.verb].message); - return GO_CLEAROBJ; - // LCOV_EXCL_START - // This case should never happen - here only as placeholder - case PART: - return reservoir(); - // LCOV_EXCL_STOP - case SEED: - return seed(command.verb, command.word[1].raw); - case WASTE: - return waste(command.verb, (turn_t)atol(command.word[1].raw)); - default: // LCOV_EXCL_LINE - BUG(TRANSITIVE_ACTION_VERB_EXCEEDS_GOTO_LIST); // LCOV_EXCL_LINE - } - case unknown: - /* Unknown verb, couldn't deduce object - might need hint */ - sspeak(WHAT_DO, command.word[0].raw); - return GO_CHECKHINT; - default: // LCOV_EXCL_LINE - BUG(SPEECHPART_NOT_TRANSITIVE_OR_INTRANSITIVE_OR_UNKNOWN); // LCOV_EXCL_LINE - } + /* Analyse a verb. Remember what it was, go back for object if second + * word unless verb is "say", which snarfs arbitrary second word. + */ + /* Previously, actions that result in a message, but don't do anything + * further were called "specials". Now they're handled here as normal + * actions. If noaction is true, then we spit out the message and return + */ + if (actions[command.verb].noaction) { + speak(actions[command.verb].message); + return GO_CLEAROBJ; + } + + if (command.part == unknown) { + /* Analyse an object word. See if the thing is here, whether + * we've got a verb yet, and so on. Object must be here + * unless verb is "find" or "invent(ory)" (and no new verb + * yet to be analysed). Water and oil are also funny, since + * they are never actually dropped at any location, but might + * be here inside the bottle or urn or as a feature of the + * location. */ + if (HERE(command.obj)) { + /* FALL THROUGH */; + } else if (command.obj == DWARF && atdwrf(game.loc) > 0) { + /* FALL THROUGH */; + } else if (!game.closed && + ((LIQUID() == command.obj && HERE(BOTTLE)) || + command.obj == LIQLOC(game.loc))) { + /* FALL THROUGH */; + } else if (command.obj == OIL && HERE(URN) && + game.objects[URN].prop != URN_EMPTY) { + command.obj = URN; + /* FALL THROUGH */; + } else if (command.obj == PLANT && AT(PLANT2) && + game.objects[PLANT2].prop != PLANT_THIRSTY) { + command.obj = PLANT2; + /* FALL THROUGH */; + } else if (command.obj == KNIFE && game.knfloc == game.loc) { + game.knfloc = -1; + rspeak(KNIVES_VANISH); + return GO_CLEAROBJ; + } else if (command.obj == ROD && HERE(ROD2)) { + command.obj = ROD2; + /* FALL THROUGH */; + } else if ((command.verb == FIND || + command.verb == INVENTORY) && + (command.word[1].id == WORD_EMPTY || + command.word[1].id == WORD_NOT_FOUND)) { + /* FALL THROUGH */; + } else { + sspeak(NO_SEE, command.word[0].raw); + return GO_CLEAROBJ; + } + + if (command.verb != 0) { + command.part = transitive; + } + } + + switch (command.part) { + case intransitive: + if (command.word[1].raw[0] != '\0' && command.verb != SAY) { + return GO_WORD2; + } + if (command.verb == SAY) { + /* KEYS is not special, anything not NO_OBJECT or + * INTRANSITIVE will do here. We're preventing + * interpretation as an intransitive verb when the word + * is unknown. */ + command.obj = + command.word[1].raw[0] != '\0' ? KEYS : NO_OBJECT; + } + if (command.obj == NO_OBJECT || command.obj == INTRANSITIVE) { + /* Analyse an intransitive verb (ie, no object given + * yet). */ + switch (command.verb) { + case CARRY: + return vcarry(command.verb, INTRANSITIVE); + case DROP: + return GO_UNKNOWN; + case SAY: + return GO_UNKNOWN; + case UNLOCK: + return lock(command.verb, INTRANSITIVE); + case NOTHING: { + rspeak(OK_MAN); + return (GO_CLEAROBJ); + } + case LOCK: + return lock(command.verb, INTRANSITIVE); + case LIGHT: + return light(command.verb, INTRANSITIVE); + case EXTINGUISH: + return extinguish(command.verb, INTRANSITIVE); + case WAVE: + return GO_UNKNOWN; + case TAME: + return GO_UNKNOWN; + case GO: { + speak(actions[command.verb].message); + return GO_CLEAROBJ; + } + case ATTACK: + command.obj = INTRANSITIVE; + return attack(command); + case POUR: + return pour(command.verb, INTRANSITIVE); + case EAT: + return eat(command.verb, INTRANSITIVE); + case DRINK: + return drink(command.verb, INTRANSITIVE); + case RUB: + return GO_UNKNOWN; + case THROW: + return GO_UNKNOWN; + case QUIT: + return quit(); + case FIND: + return GO_UNKNOWN; + case INVENTORY: + return inven(); + case FEED: + return GO_UNKNOWN; + case FILL: + return fill(command.verb, INTRANSITIVE); + case BLAST: + blast(); + return GO_CLEAROBJ; + case SCORE: + score(scoregame); + return GO_CLEAROBJ; + case FEE: + case FIE: + case FOE: + case FOO: + case FUM: + return bigwords(command.word[0].id); + case BRIEF: + return brief(); + case READ: + command.obj = INTRANSITIVE; + return read(command); + case BREAK: + return GO_UNKNOWN; + case WAKE: + return GO_UNKNOWN; + case SAVE: + return suspend(); + case RESUME: + return resume(); + case FLY: + return fly(command.verb, INTRANSITIVE); + case LISTEN: + return listen(); + case PART: + return reservoir(); + case SEED: + case WASTE: + rspeak(NUMERIC_REQUIRED); + return GO_TOP; + default: // LCOV_EXCL_LINE + BUG(INTRANSITIVE_ACTION_VERB_EXCEEDS_GOTO_LIST); // LCOV_EXCL_LINE + } + } + /* FALLTHRU */ + case transitive: + /* Analyse a transitive verb. */ + switch (command.verb) { + case CARRY: + return vcarry(command.verb, command.obj); + case DROP: + return discard(command.verb, command.obj); + case SAY: + return say(command); + case UNLOCK: + return lock(command.verb, command.obj); + case NOTHING: { + rspeak(OK_MAN); + return (GO_CLEAROBJ); + } + case LOCK: + return lock(command.verb, command.obj); + case LIGHT: + return light(command.verb, command.obj); + case EXTINGUISH: + return extinguish(command.verb, command.obj); + case WAVE: + return wave(command.verb, command.obj); + case TAME: { + speak(actions[command.verb].message); + return GO_CLEAROBJ; + } + case GO: { + speak(actions[command.verb].message); + return GO_CLEAROBJ; + } + case ATTACK: + return attack(command); + case POUR: + return pour(command.verb, command.obj); + case EAT: + return eat(command.verb, command.obj); + case DRINK: + return drink(command.verb, command.obj); + case RUB: + return rub(command.verb, command.obj); + case THROW: + return throwit(command); + case QUIT: + speak(actions[command.verb].message); + return GO_CLEAROBJ; + case FIND: + return find(command.verb, command.obj); + case INVENTORY: + return find(command.verb, command.obj); + case FEED: + return feed(command.verb, command.obj); + case FILL: + return fill(command.verb, command.obj); + case BLAST: + blast(); + return GO_CLEAROBJ; + case SCORE: + speak(actions[command.verb].message); + return GO_CLEAROBJ; + case FEE: + case FIE: + case FOE: + case FOO: + case FUM: + speak(actions[command.verb].message); + return GO_CLEAROBJ; + case BRIEF: + speak(actions[command.verb].message); + return GO_CLEAROBJ; + case READ: + return read(command); + case BREAK: + return vbreak(command.verb, command.obj); + case WAKE: + return wake(command.verb, command.obj); + case SAVE: + speak(actions[command.verb].message); + return GO_CLEAROBJ; + case RESUME: + speak(actions[command.verb].message); + return GO_CLEAROBJ; + case FLY: + return fly(command.verb, command.obj); + case LISTEN: + speak(actions[command.verb].message); + return GO_CLEAROBJ; + // LCOV_EXCL_START + // This case should never happen - here only as placeholder + case PART: + return reservoir(); + // LCOV_EXCL_STOP + case SEED: + return seed(command.verb, command.word[1].raw); + case WASTE: + return waste(command.verb, + (turn_t)atol(command.word[1].raw)); + default: // LCOV_EXCL_LINE + BUG(TRANSITIVE_ACTION_VERB_EXCEEDS_GOTO_LIST); // LCOV_EXCL_LINE + } + case unknown: + /* Unknown verb, couldn't deduce object - might need hint */ + sspeak(WHAT_DO, command.word[0].raw); + return GO_CHECKHINT; + default: // LCOV_EXCL_LINE + BUG(SPEECHPART_NOT_TRANSITIVE_OR_INTRANSITIVE_OR_UNKNOWN); // LCOV_EXCL_LINE + } } // end diff --git a/advent.h b/advent.h index 55518cb..b263645 100644 --- a/advent.h +++ b/advent.h @@ -4,11 +4,11 @@ * SPDX-FileCopyrightText: (C) 1977, 2005 by Will Crowther and Don Woods * SPDX-License-Identifier: BSD-2-Clause */ +#include +#include +#include #include #include -#include -#include -#include #include "dungeon.h" @@ -18,29 +18,30 @@ #define LCG_C 221587L #define LCG_M 1048576L -#define LINESIZE 1024 -#define TOKLEN 5 // # outputting characters in a token */ -#define PIRATE NDWARVES // must be NDWARVES-1 when zero-origin -#define DALTLC LOC_NUGGET // alternate dwarf location -#define INVLIMIT 7 // inventory limit (# of objects) -#define INTRANSITIVE -1 // illegal object number -#define GAMELIMIT 330 // base limit of turns -#define NOVICELIMIT 1000 // limit of turns for novice -#define WARNTIME 30 // late game starts at game.limit-this -#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. -#define WORD_EMPTY 0 // "Word empty" flag value for the vocab hash functions -#define PIT_KILL_PROB 35 // Percentage probability of dying from fall in pit. -#define CARRIED -1 // Player is toting it -#define READ_MODE "rb" // b is not needed for POSIX but harmless -#define WRITE_MODE "wb" // b is not needed for POSIX but harmless +#define LINESIZE 1024 +#define TOKLEN 5 // # outputting characters in a token */ +#define PIRATE NDWARVES // must be NDWARVES-1 when zero-origin +#define DALTLC LOC_NUGGET // alternate dwarf location +#define INVLIMIT 7 // inventory limit (# of objects) +#define INTRANSITIVE -1 // illegal object number +#define GAMELIMIT 330 // base limit of turns +#define NOVICELIMIT 1000 // limit of turns for novice +#define WARNTIME 30 // late game starts at game.limit-this +#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. +#define WORD_EMPTY 0 // "Word empty" flag value for the vocab hash functions +#define PIT_KILL_PROB 35 // Percentage probability of dying from fall in pit. +#define CARRIED -1 // Player is toting it +#define READ_MODE "rb" // b is not needed for POSIX but harmless +#define WRITE_MODE "wb" // b is not needed for POSIX but harmless /* Special object-state values - integers > 0 are object-specific */ -#define STATE_NOTFOUND -1 // 'Not found" state of treasures -#define STATE_FOUND 0 // After discovered, before messed with -#define STATE_IN_CAVITY 1 // State value common to all gemstones +#define STATE_NOTFOUND -1 // 'Not found" state of treasures +#define STATE_FOUND 0 // After discovered, before messed with +#define STATE_IN_CAVITY 1 // State value common to all gemstones /* Special fixed object-state values - integers > 0 are location */ #define IS_FIXED -1 @@ -67,39 +68,45 @@ * 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) +#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. + * telling whether or not the player has seen the object. * * 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. */ -#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 +#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 PROP_STASHED(obj) PROP_STASHIFY(game.objects[obj].prop) -#define PROMPT "> " +#define PROMPT "> " /* * DESTROY(N) = Get rid of an item by putting it in LOC_NOWHERE @@ -116,62 +123,72 @@ * 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 + * 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)) -#define TOTING(OBJ) (game.objects[OBJ].place == CARRIED) -#define AT(OBJ) (game.objects[OBJ].place == game.loc || game.objects[OBJ].fixed == game.loc) -#define HERE(OBJ) (AT(OBJ) || TOTING(OBJ)) -#define CNDBIT(L,N) (tstbit(conditions[L],N)) -#define LIQUID() (game.objects[BOTTLE].prop == WATER_BOTTLE? WATER : game.objects[BOTTLE].prop == OIL_BOTTLE ? OIL : NO_OBJECT ) -#define LIQLOC(LOC) (CNDBIT((LOC),COND_FLUID)? CNDBIT((LOC),COND_OILY) ? OIL : WATER : NO_OBJECT) -#define FORCED(LOC) CNDBIT(LOC, COND_FORCED) -#define DARK(DUMMY) (!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 INDEEP(LOC) CNDBIT((LOC),COND_DEEP) -#define BUG(x) bug(x, #x) +#define DESTROY(N) move(N, LOC_NOWHERE) +#define MOD(N, M) ((N) % (M)) +#define TOTING(OBJ) (game.objects[OBJ].place == CARRIED) +#define AT(OBJ) \ + (game.objects[OBJ].place == game.loc || \ + game.objects[OBJ].fixed == game.loc) +#define HERE(OBJ) (AT(OBJ) || TOTING(OBJ)) +#define CNDBIT(L, N) (tstbit(conditions[L], N)) +#define LIQUID() \ + (game.objects[BOTTLE].prop == WATER_BOTTLE ? WATER \ + : game.objects[BOTTLE].prop == OIL_BOTTLE ? OIL \ + : NO_OBJECT) +#define LIQLOC(LOC) \ + (CNDBIT((LOC), COND_FLUID) ? CNDBIT((LOC), COND_OILY) ? OIL : WATER \ + : NO_OBJECT) +#define FORCED(LOC) CNDBIT(LOC, COND_FORCED) +#define DARK(DUMMY) \ + (!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 INDEEP(LOC) CNDBIT((LOC), COND_DEEP) +#define BUG(x) bug(x, #x) enum bugtype { - SPECIAL_TRAVEL_500_GT_L_GT_300_EXCEEDS_GOTO_LIST, - VOCABULARY_TYPE_N_OVER_1000_NOT_BETWEEN_0_AND_3, - INTRANSITIVE_ACTION_VERB_EXCEEDS_GOTO_LIST, - TRANSITIVE_ACTION_VERB_EXCEEDS_GOTO_LIST, - CONDITIONAL_TRAVEL_ENTRY_WITH_NO_ALTERATION, - LOCATION_HAS_NO_TRAVEL_ENTRIES, - HINT_NUMBER_EXCEEDS_GOTO_LIST, - SPEECHPART_NOT_TRANSITIVE_OR_INTRANSITIVE_OR_UNKNOWN, - ACTION_RETURNED_PHASE_CODE_BEYOND_END_OF_SWITCH, + SPECIAL_TRAVEL_500_GT_L_GT_300_EXCEEDS_GOTO_LIST, + VOCABULARY_TYPE_N_OVER_1000_NOT_BETWEEN_0_AND_3, + INTRANSITIVE_ACTION_VERB_EXCEEDS_GOTO_LIST, + TRANSITIVE_ACTION_VERB_EXCEEDS_GOTO_LIST, + CONDITIONAL_TRAVEL_ENTRY_WITH_NO_ALTERATION, + LOCATION_HAS_NO_TRAVEL_ENTRIES, + HINT_NUMBER_EXCEEDS_GOTO_LIST, + SPEECHPART_NOT_TRANSITIVE_OR_INTRANSITIVE_OR_UNKNOWN, + ACTION_RETURNED_PHASE_CODE_BEYOND_END_OF_SWITCH, }; -enum speaktype {touch, look, hear, study, change}; +enum speaktype { touch, look, hear, study, change }; -enum termination {endgame, quitgame, scoregame}; +enum termination { endgame, quitgame, scoregame }; -enum speechpart {unknown, intransitive, transitive}; +enum speechpart { unknown, intransitive, transitive }; -typedef enum {NO_WORD_TYPE, MOTION, OBJECT, ACTION, NUMERIC} word_type_t; +typedef enum { NO_WORD_TYPE, MOTION, OBJECT, ACTION, NUMERIC } word_type_t; -typedef enum scorebonus {none, splatter, defeat, victory} score_t; +typedef enum scorebonus { none, splatter, defeat, victory } score_t; /* Phase codes for action returns. * These were at one time FORTRAN line numbers. */ typedef enum { - GO_TERMINATE, - GO_MOVE, - GO_TOP, - GO_CLEAROBJ, - GO_CHECKHINT, - GO_WORD2, - GO_UNKNOWN, - GO_DWARFWAKE, + GO_TERMINATE, + GO_MOVE, + GO_TOP, + GO_CLEAROBJ, + GO_CHECKHINT, + GO_WORD2, + GO_UNKNOWN, + GO_DWARFWAKE, } phase_codes_t; /* Use fixed-lwength types to make the save format moore portable */ @@ -183,75 +200,75 @@ typedef int32_t turn_t; // turn counter or threshold */ typedef int32_t bool32_t; // turn counter or threshold */ struct game_t { - int32_t lcg_x; - int32_t abbnum; // How often to print int descriptions - score_t bonus; // What kind of finishing bonus we are getting - loc_t chloc; // pirate chest location - loc_t chloc2; // pirate chest alternate location - turn_t clock1; // # turns from finding last treasure to close - turn_t clock2; // # turns from warning till blinding flash - bool32_t clshnt; // has player read the clue in the endgame? - bool32_t closed; // whether we're all the way closed - bool32_t closng; // whether it's closing time yet - bool32_t lmwarn; // has player been warned about lamp going dim? - bool32_t novice; // asked for instructions at start-up? - bool32_t panic; // has player found out he's trapped? - bool32_t wzdark; // whether the loc he's leaving was dark - bool32_t blooded; // has player drunk of dragon's blood? - int32_t conds; // min value for cond[loc] if loc has any hints - int32_t detail; // level of detail in descriptions + int32_t lcg_x; + int32_t abbnum; // How often to print int descriptions + score_t bonus; // What kind of finishing bonus we are getting + loc_t chloc; // pirate chest location + loc_t chloc2; // pirate chest alternate location + turn_t clock1; // # turns from finding last treasure to close + turn_t clock2; // # turns from warning till blinding flash + bool32_t clshnt; // has player read the clue in the endgame? + bool32_t closed; // whether we're all the way closed + bool32_t closng; // whether it's closing time yet + bool32_t lmwarn; // has player been warned about lamp going dim? + bool32_t novice; // asked for instructions at start-up? + bool32_t panic; // has player found out he's trapped? + bool32_t wzdark; // whether the loc he's leaving was dark + bool32_t blooded; // has player drunk of dragon's blood? + int32_t conds; // min value for cond[loc] if loc has any hints + int32_t detail; // level of detail in descriptions - /* dflag controls the level of activation of dwarves: - * 0 No dwarf stuff yet (wait until reaches Hall Of Mists) - * 1 Reached Hall Of Mists, but hasn't met first dwarf - * 2 Met first dwarf, others start moving, no knives thrown yet - * 3 A knife has been thrown (first set always misses) - * 3+ Dwarves are mad (increases their accuracy) */ - int32_t dflag; + /* dflag controls the level of activation of dwarves: + * 0 No dwarf stuff yet (wait until reaches Hall Of Mists) + * 1 Reached Hall Of Mists, but hasn't met first dwarf + * 2 Met first dwarf, others start moving, no knives thrown + *yet 3 A knife has been thrown (first set always misses) 3+ + *Dwarves are mad (increases their accuracy) */ + int32_t dflag; - int32_t dkill; // dwarves killed - int32_t dtotal; // total dwarves (including pirate) in loc - int32_t foobar; // progress in saying "FEE FIE FOE FOO". - int32_t holdng; // number of objects being carried - int32_t igo; // # uses of "go" instead of a direction - int32_t iwest; // # times he's said "west" instead of "w" - loc_t knfloc; // knife location; LOC_NOWERE if none, -1 after caveat - turn_t limit; // lifetime of lamp - loc_t loc; // where player is now - loc_t newloc; // where player is going - turn_t numdie; // number of times killed so far - loc_t oldloc; // where player was - loc_t oldlc2; // where player was two moves ago - obj_t oldobj; // last object player handled - int32_t saved; // point penalty for saves - int32_t tally; // count of treasures gained - int32_t thresh; // current threshold for endgame scoring tier - bool32_t seenbigwords; // have we red the graffiti in the Giant's Room? - turn_t trnluz; // # points lost so far due to turns used - turn_t turns; // counts commands given (ignores yes/no) - char zzword[TOKLEN + 1]; // randomly generated magic word from bird - struct { - int32_t abbrev; // has location been seen? - int32_t atloc; // head of object linked list per location - } locs[NLOCATIONS + 1]; - struct { - int32_t seen; // true if dwarf has seen him - loc_t loc; // location of dwarves, initially hard-wired in - loc_t oldloc; // prior loc of each dwarf, initially garbage - } dwarves[NDWARVES + 1]; - struct { + int32_t dkill; // dwarves killed + int32_t dtotal; // total dwarves (including pirate) in loc + int32_t foobar; // progress in saying "FEE FIE FOE FOO". + int32_t holdng; // number of objects being carried + int32_t igo; // # uses of "go" instead of a direction + int32_t iwest; // # times he's said "west" instead of "w" + loc_t knfloc; // knife location; LOC_NOWERE if none, -1 after caveat + turn_t limit; // lifetime of lamp + loc_t loc; // where player is now + loc_t newloc; // where player is going + turn_t numdie; // number of times killed so far + loc_t oldloc; // where player was + loc_t oldlc2; // where player was two moves ago + obj_t oldobj; // last object player handled + int32_t saved; // point penalty for saves + int32_t tally; // count of treasures gained + int32_t thresh; // current threshold for endgame scoring tier + bool32_t seenbigwords; // have we red the graffiti in the Giant's Room? + turn_t trnluz; // # points lost so far due to turns used + turn_t turns; // counts commands given (ignores yes/no) + char zzword[TOKLEN + 1]; // randomly generated magic word from bird + struct { + int32_t abbrev; // has location been seen? + int32_t atloc; // head of object linked list per location + } locs[NLOCATIONS + 1]; + struct { + int32_t seen; // true if dwarf has seen him + loc_t loc; // location of dwarves, initially hard-wired in + 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? + 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 */ - loc_t place; // location of object - } objects[NOBJECTS + 1]; - struct { - bool32_t used; // hints[i].used = true iff hint i has been used. - int32_t lc; // hints[i].lc = show int at LOC with cond bit i - } hints[NHINTS]; - obj_t link[NOBJECTS * 2 + 1];// object-list links + loc_t fixed; // fixed location of object (if not IS_FREE) + int32_t prop; // object state */ + loc_t place; // location of object + } objects[NOBJECTS + 1]; + struct { + bool32_t used; // hints[i].used = true iff hint i has been used. + int32_t lc; // hints[i].lc = show int at LOC with cond bit i + } hints[NHINTS]; + obj_t link[NOBJECTS * 2 + 1]; // object-list links }; /* @@ -259,56 +276,64 @@ struct game_t { * This data is not saved in a saved game. */ struct settings_t { - FILE *logfp; - bool oldstyle; - bool prompt; - char **argv; - int argc; - int optind; - FILE *scriptfp; - int debug; + FILE *logfp; + bool oldstyle; + bool prompt; + char **argv; + int argc; + int optind; + FILE *scriptfp; + int debug; }; typedef struct { - char raw[LINESIZE]; - vocab_t id; - word_type_t type; + char raw[LINESIZE]; + vocab_t id; + word_type_t type; } command_word_t; -typedef enum {EMPTY, RAW, TOKENIZED, GIVEN, PREPROCESSED, PROCESSING, EXECUTED} command_state_t; +typedef enum { + EMPTY, + RAW, + TOKENIZED, + GIVEN, + PREPROCESSED, + PROCESSING, + EXECUTED +} command_state_t; typedef struct { - enum speechpart part; - command_word_t word[2]; - verb_t verb; - obj_t obj; - command_state_t state; + enum speechpart part; + command_word_t word[2]; + verb_t verb; + obj_t obj; + command_state_t state; } command_t; /* * Bump on save format change. * - * Note: Verify that the tests run clean before bumping this, then rebuild the check - * files afterwards. Otherwise you will get a spurious failure due to the old version - * having been generated into a check file. + * Note: Verify that the tests run clean before bumping this, then rebuild the + * check files afterwards. Otherwise you will get a spurious failure due to the + * old version having been generated into a check file. */ -#define SAVE_VERSION 31 +#define SAVE_VERSION 31 /* * Goes at start of file so saves can be identified by file(1) and the like. */ -#define ADVENT_MAGIC "open-adventure\n" +#define ADVENT_MAGIC "open-adventure\n" /* * If you change the first three members, the resume function may not properly - * reject saves from older versions. Later members can change, but bump the version - * when you do that. + * reject saves from older versions. Later members can change, but bump the + * version when you do that. */ struct save_t { - char magic[sizeof(ADVENT_MAGIC)]; - int32_t version; - int32_t canary; - struct game_t game; + char magic[sizeof(ADVENT_MAGIC)]; + int32_t version; + int32_t canary; + struct game_t game; }; extern struct game_t game; @@ -318,13 +343,13 @@ extern struct settings_t settings; extern char *myreadline(const char *); extern bool get_command_input(command_t *); extern void clear_command(command_t *); -extern void speak(const char*, ...); +extern void speak(const char *, ...); extern void sspeak(int msg, ...); extern void pspeak(vocab_t, enum speaktype, bool, int, ...); extern void rspeak(vocab_t, ...); -extern void echo_input(FILE*, const char*, const char*); +extern void echo_input(FILE *, const char *, const char *); extern bool silent_yes_or_no(void); -extern bool yes_or_no(const char*, const char*, const char*); +extern bool yes_or_no(const char *, const char *, const char *); extern void juggle(obj_t); extern void move(obj_t, loc_t); extern void put(obj_t, loc_t, int); diff --git a/cheat.c b/cheat.c index 279bcd9..4e94025 100644 --- a/cheat.c +++ b/cheat.c @@ -7,93 +7,90 @@ * SPDX-FileCopyrightText: (C) 1977, 2005 by Will Crowther and Don Woods * SPDX-License-Identifier: BSD-2-Clause */ +#include "advent.h" +#include #include -#include -#include #include -#include -#include "advent.h" - -int main(int argc, char *argv[]) -{ - int ch; - char *savefilename = NULL; - FILE *fp = NULL; - - // Initialize game variables - initialise(); - - /* we're generating a saved game, so saved once by default, - * unless overridden with command-line options below. - */ - game.saved = 1; - - /* Options. */ - const char* opts = "d:l:s:t:v:o:"; - const char* usage = "Usage: %s [-d numdie] [-s numsaves] [-v version] -o savefilename \n" - " -d number of deaths. Signed integer.\n" - " -l lifetime of lamp in turns. Signed integer.\n" - " -s number of saves. Signed integer.\n" - " -t number of turns. Signed integer.\n" - " -v version number of save format.\n" - " -o required. File name of save game to write.\n"; - - while ((ch = getopt(argc, argv, opts)) != EOF) { - switch (ch) { - case 'd': - game.numdie = (turn_t)atoi(optarg); - printf("cheat: game.numdie = %d\n", game.numdie); - break; - case 'l': - game.limit = (turn_t)atoi(optarg); - printf("cheat: game.limit = %d\n", game.limit); - break; - case 's': - game.saved = (int)atoi(optarg); - printf("cheat: game.saved = %d\n", game.saved); - break; - case 't': - game.turns = (turn_t)atoi(optarg); - printf("cheat: game.turns = %d\n", game.turns); - break; - case 'v': - save.version = atoi(optarg); - printf("cheat: version = %d\n", save.version); - break; - case 'o': - savefilename = optarg; - break; - default: - fprintf(stderr, - usage, argv[0]); - exit(EXIT_FAILURE); - break; - } - } - - // Save filename required; the point of cheat is to generate save file - if (savefilename == NULL) { - fprintf(stderr, - usage, argv[0]); - fprintf(stderr, - "ERROR: filename required\n"); - exit(EXIT_FAILURE); - } - - fp = fopen(savefilename, WRITE_MODE); - if (fp == NULL) { - fprintf(stderr, - "Can't open file %s. Exiting.\n", savefilename); - exit(EXIT_FAILURE); - } - - savefile(fp); - - fclose(fp); - - printf("cheat: %s created.\n", savefilename); +#include +#include - return EXIT_SUCCESS; +int main(int argc, char *argv[]) { + int ch; + char *savefilename = NULL; + FILE *fp = NULL; + + // Initialize game variables + initialise(); + + /* we're generating a saved game, so saved once by default, + * unless overridden with command-line options below. + */ + game.saved = 1; + + /* Options. */ + const char *opts = "d:l:s:t:v:o:"; + const char *usage = + "Usage: %s [-d numdie] [-s numsaves] [-v version] -o savefilename " + "\n" + " -d number of deaths. Signed integer.\n" + " -l lifetime of lamp in turns. Signed integer.\n" + " -s number of saves. Signed integer.\n" + " -t number of turns. Signed integer.\n" + " -v version number of save format.\n" + " -o required. File name of save game to write.\n"; + + while ((ch = getopt(argc, argv, opts)) != EOF) { + switch (ch) { + case 'd': + game.numdie = (turn_t)atoi(optarg); + printf("cheat: game.numdie = %d\n", game.numdie); + break; + case 'l': + game.limit = (turn_t)atoi(optarg); + printf("cheat: game.limit = %d\n", game.limit); + break; + case 's': + game.saved = (int)atoi(optarg); + printf("cheat: game.saved = %d\n", game.saved); + break; + case 't': + game.turns = (turn_t)atoi(optarg); + printf("cheat: game.turns = %d\n", game.turns); + break; + case 'v': + save.version = atoi(optarg); + printf("cheat: version = %d\n", save.version); + break; + case 'o': + savefilename = optarg; + break; + default: + fprintf(stderr, usage, argv[0]); + exit(EXIT_FAILURE); + break; + } + } + + // Save filename required; the point of cheat is to generate save file + if (savefilename == NULL) { + fprintf(stderr, usage, argv[0]); + fprintf(stderr, "ERROR: filename required\n"); + exit(EXIT_FAILURE); + } + + fp = fopen(savefilename, WRITE_MODE); + if (fp == NULL) { + fprintf(stderr, "Can't open file %s. Exiting.\n", savefilename); + exit(EXIT_FAILURE); + } + + savefile(fp); + + fclose(fp); + + printf("cheat: %s created.\n", savefilename); + + return EXIT_SUCCESS; } // LCOV_EXCL_START @@ -102,12 +99,7 @@ int main(int argc, char *argv[]) * See the actually useful version of this in main.c */ -char *myreadline(const char *prompt) -{ - return readline(prompt); -} +char *myreadline(const char *prompt) { return readline(prompt); } // LCOV_EXCL_STOP /* end */ - - diff --git a/init.c b/init.c index 94c972d..d00255d 100644 --- a/init.c +++ b/init.c @@ -5,97 +5,86 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include -#include -#include +#include #include +#include +#include #include -#include +#include #include "advent.h" -struct settings_t settings = { - .logfp = NULL, - .oldstyle = false, - .prompt = true -}; +struct settings_t settings = {.logfp = NULL, .oldstyle = false, .prompt = true}; struct game_t game = { /* Last dwarf is special (the pirate). He always starts at his * chest's eventual location inside the maze. This loc is saved * in chloc for ref. The dead end in the other maze has its * loc stored in chloc2. */ - .chloc = LOC_MAZEEND12, - .chloc2 = LOC_DEADEND13, - .abbnum = 5, - .clock1 = WARNTIME, - .clock2 = FLASHTIME, - .newloc = LOC_START, - .loc = LOC_START, - .limit = GAMELIMIT, - .foobar = WORD_EMPTY, + .chloc = LOC_MAZEEND12, .chloc2 = LOC_DEADEND13, .abbnum = 5, + .clock1 = WARNTIME, .clock2 = FLASHTIME, .newloc = LOC_START, + .loc = LOC_START, .limit = GAMELIMIT, .foobar = WORD_EMPTY, }; -int initialise(void) -{ - if (settings.oldstyle) - printf("Initialising...\n"); +int initialise(void) { + if (settings.oldstyle) + printf("Initialising...\n"); - srand(time(NULL)); - int seedval = (int)rand(); - set_seed(seedval); + srand(time(NULL)); + int seedval = (int)rand(); + set_seed(seedval); - for (int i = 1; i <= NDWARVES; i++) { - game.dwarves[i].loc = dwarflocs[i-1]; - } + for (int i = 1; i <= NDWARVES; i++) { + game.dwarves[i].loc = dwarflocs[i - 1]; + } - for (int i = 1; i <= NOBJECTS; i++) { - game.objects[i].place = LOC_NOWHERE; - } + for (int i = 1; i <= NOBJECTS; i++) { + game.objects[i].place = LOC_NOWHERE; + } - for (int i = 1; i <= NLOCATIONS; i++) { - if (!(locations[i].description.big == 0 || tkey[i] == 0)) { - int k = tkey[i]; - if (travel[k].motion == HERE) { - conditions[i] |= (1 << COND_FORCED); - } - } - } + for (int i = 1; i <= NLOCATIONS; i++) { + if (!(locations[i].description.big == 0 || tkey[i] == 0)) { + int k = tkey[i]; + if (travel[k].motion == HERE) { + conditions[i] |= (1 << COND_FORCED); + } + } + } - /* Set up the game.locs atloc and game.link arrays. - * We'll use the DROP subroutine, which prefaces new objects on the - * lists. Since we want things in the other order, we'll run the - * loop backwards. If the object is in two locs, we drop it twice. - * Also, since two-placed objects are typically best described - * last, we'll drop them first. */ - for (int i = NOBJECTS; i >= 1; i--) { - if (objects[i].fixd > 0) { - drop(i + NOBJECTS, objects[i].fixd); - drop(i, objects[i].plac); - } - } + /* Set up the game.locs atloc and game.link arrays. + * We'll use the DROP subroutine, which prefaces new objects on the + * lists. Since we want things in the other order, we'll run the + * loop backwards. If the object is in two locs, we drop it twice. + * Also, since two-placed objects are typically best described + * last, we'll drop them first. */ + for (int i = NOBJECTS; i >= 1; i--) { + if (objects[i].fixd > 0) { + drop(i + NOBJECTS, objects[i].fixd); + drop(i, objects[i].plac); + } + } - for (int i = 1; i <= NOBJECTS; i++) { - int k = NOBJECTS + 1 - i; - game.objects[k].fixed = objects[k].fixd; - if (objects[k].plac != 0 && objects[k].fixd <= 0) { - drop(k, objects[k].plac); + for (int i = 1; i <= NOBJECTS; i++) { + int k = NOBJECTS + 1 - i; + game.objects[k].fixed = objects[k].fixd; + if (objects[k].plac != 0 && objects[k].fixd <= 0) { + drop(k, objects[k].plac); + } } - } - /* 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) { - ++game.tally; - if (objects[treasure].inventory != 0) { - PROP_SET_NOT_FOUND(treasure); - } - } - } - game.conds = setbit(COND_HBASE); + /* 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) { + ++game.tally; + if (objects[treasure].inventory != 0) { + PROP_SET_NOT_FOUND(treasure); + } + } + } + game.conds = setbit(COND_HBASE); - return seedval; + return seedval; } diff --git a/main.c b/main.c index fd0852f..cff0780 100644 --- a/main.c +++ b/main.c @@ -3,432 +3,460 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include -#include -#include +#include "advent.h" +#include +#include #include #include +#include +#include +#include #include -#include #include -#include -#include "advent.h" -#define DIM(a) (sizeof(a)/sizeof(a[0])) +#define DIM(a) (sizeof(a) / sizeof(a[0])) #if defined ADVENT_AUTOSAVE -static FILE* autosave_fp; -void autosave(void) -{ - if (autosave_fp != NULL) { - rewind(autosave_fp); - savefile(autosave_fp); - fflush(autosave_fp); - } +static FILE *autosave_fp; +void autosave(void) { + if (autosave_fp != NULL) { + rewind(autosave_fp); + savefile(autosave_fp); + fflush(autosave_fp); + } } #endif // LCOV_EXCL_START // exclude from coverage analysis because it requires interactivity to test -static void sig_handler(int signo) -{ - if (signo == SIGINT) { - if (settings.logfp != NULL) - fflush(settings.logfp); - } +static void sig_handler(int signo) { + if (signo == SIGINT) { + if (settings.logfp != NULL) + fflush(settings.logfp); + } #if defined ADVENT_AUTOSAVE - if (signo == SIGHUP || signo == SIGTERM) { - autosave(); - } + if (signo == SIGHUP || signo == SIGTERM) { + autosave(); + } #endif - exit(EXIT_FAILURE); + exit(EXIT_FAILURE); } // LCOV_EXCL_STOP -char *myreadline(const char *prompt) -{ - /* - * This function isn't required for gameplay, readline() straight - * up would suffice for that. It's where we interpret command-line - * logfiles for testing purposes. - */ - /* Normal case - no script arguments */ - if (settings.argc == 0) { - char *ln = readline(prompt); - if (ln == NULL) { - fputs(prompt, stdout); - } - return ln; - } - - char *buf = malloc(LINESIZE + 1); - for (;;) { - if (settings.scriptfp == NULL || feof(settings.scriptfp)) { - if (settings.optind >= settings.argc) { - free(buf); - return NULL; - } - - char *next = settings.argv[settings.optind++]; - - if (settings.scriptfp != NULL && feof(settings.scriptfp)) { - fclose(settings.scriptfp); - } - if (strcmp(next, "-") == 0) { - settings.scriptfp = stdin; // LCOV_EXCL_LINE - } else { - settings.scriptfp = fopen(next, "r"); - } - } - - if (isatty(fileno(settings.scriptfp))) { - free(buf); // LCOV_EXCL_LINE - return readline(prompt); // LCOV_EXCL_LINE - } else { - char *ln = fgets(buf, LINESIZE, settings.scriptfp); - if (ln != NULL) { - fputs(prompt, stdout); - fputs(ln, stdout); +char *myreadline(const char *prompt) { + /* + * This function isn't required for gameplay, readline() straight + * up would suffice for that. It's where we interpret command-line + * logfiles for testing purposes. + */ + /* Normal case - no script arguments */ + if (settings.argc == 0) { + char *ln = readline(prompt); + if (ln == NULL) { + fputs(prompt, stdout); + } return ln; - } - } - } + } + + char *buf = malloc(LINESIZE + 1); + for (;;) { + if (settings.scriptfp == NULL || feof(settings.scriptfp)) { + if (settings.optind >= settings.argc) { + free(buf); + return NULL; + } + + char *next = settings.argv[settings.optind++]; - return NULL; + if (settings.scriptfp != NULL && + feof(settings.scriptfp)) { + fclose(settings.scriptfp); + } + if (strcmp(next, "-") == 0) { + settings.scriptfp = stdin; // LCOV_EXCL_LINE + } else { + settings.scriptfp = fopen(next, "r"); + } + } + + if (isatty(fileno(settings.scriptfp))) { + free(buf); // LCOV_EXCL_LINE + return readline(prompt); // LCOV_EXCL_LINE + } else { + char *ln = fgets(buf, LINESIZE, settings.scriptfp); + if (ln != NULL) { + fputs(prompt, stdout); + fputs(ln, stdout); + return ln; + } + } + } + + return NULL; } /* Check if this loc is eligible for any hints. If been here int * enough, display. Ignore "HINTS" < 4 (special stuff, see database * notes). */ -static void checkhints(void) -{ - if (conditions[game.loc] >= game.conds) { - for (int hint = 0; hint < NHINTS; hint++) { - if (game.hints[hint].used) { - continue; - } - if (!CNDBIT(game.loc, hint + 1 + COND_HBASE)) { - game.hints[hint].lc = -1; - } - ++game.hints[hint].lc; - /* Come here if he's been int enough at required loc(s) for some - * unused hint. */ - if (game.hints[hint].lc >= hints[hint].turns) { - int i; - - switch (hint) { - case 0: - /* cave */ - if (game.objects[GRATE].prop == GRATE_CLOSED && !HERE(KEYS)) { - break; - } - game.hints[hint].lc = 0; - return; - case 1: /* bird */ - if (game.objects[BIRD].place == game.loc && TOTING(ROD) && game.oldobj == BIRD) { - break; - } - return; - case 2: /* snake */ - if (HERE(SNAKE) && !HERE(BIRD)) { - break; - } - game.hints[hint].lc = 0; - return; - case 3: /* maze */ - if (game.locs[game.loc].atloc == NO_OBJECT && - game.locs[game.oldloc].atloc == NO_OBJECT && - game.locs[game.oldlc2].atloc == NO_OBJECT && - game.holdng > 1) - break; - game.hints[hint].lc = 0; - return; - case 4: /* dark */ - if (!PROP_IS_NOTFOUND(EMERALD) && PROP_IS_NOTFOUND(PYRAMID)) { - break; - } - game.hints[hint].lc = 0; - return; - case 5: /* witt */ - break; - case 6: /* urn */ - if (game.dflag == 0) { - break; - } - game.hints[hint].lc = 0; - return; - case 7: /* woods */ - if (game.locs[game.loc].atloc == NO_OBJECT && - game.locs[game.oldloc].atloc == NO_OBJECT && - game.locs[game.oldlc2].atloc == NO_OBJECT) - break; - return; - case 8: /* ogre */ - i = atdwrf(game.loc); - if (i < 0) { - game.hints[hint].lc = 0; - return; - } - if (HERE(OGRE) && i == 0) { - break; - } - return; - case 9: /* jade */ - if (game.tally == 1 && PROP_IS_STASHED_OR_UNSEEN(JADE)) { - break; - } - game.hints[hint].lc = 0; - return; - default: // LCOV_EXCL_LINE - // Should never happen - BUG(HINT_NUMBER_EXCEEDS_GOTO_LIST); // LCOV_EXCL_LINE - } - - /* Fall through to hint display */ - game.hints[hint].lc = 0; - if (!yes_or_no(hints[hint].question, arbitrary_messages[NO_MESSAGE], arbitrary_messages[OK_MAN])) { - return; - } - rspeak(HINT_COST, hints[hint].penalty, hints[hint].penalty); - game.hints[hint].used = yes_or_no(arbitrary_messages[WANT_HINT], hints[hint].hint, arbitrary_messages[OK_MAN]); - if (game.hints[hint].used && game.limit > WARNTIME) { - game.limit += WARNTIME * hints[hint].penalty; +static void checkhints(void) { + if (conditions[game.loc] >= game.conds) { + for (int hint = 0; hint < NHINTS; hint++) { + if (game.hints[hint].used) { + continue; + } + if (!CNDBIT(game.loc, hint + 1 + COND_HBASE)) { + game.hints[hint].lc = -1; + } + ++game.hints[hint].lc; + /* Come here if he's been int enough at required loc(s) + * for some unused hint. */ + if (game.hints[hint].lc >= hints[hint].turns) { + int i; + + switch (hint) { + case 0: + /* cave */ + if (game.objects[GRATE].prop == + GRATE_CLOSED && + !HERE(KEYS)) { + break; + } + game.hints[hint].lc = 0; + return; + case 1: /* bird */ + if (game.objects[BIRD].place == + game.loc && + TOTING(ROD) && + game.oldobj == BIRD) { + break; + } + return; + case 2: /* snake */ + if (HERE(SNAKE) && !HERE(BIRD)) { + break; + } + game.hints[hint].lc = 0; + return; + case 3: /* maze */ + if (game.locs[game.loc].atloc == + NO_OBJECT && + game.locs[game.oldloc].atloc == + NO_OBJECT && + game.locs[game.oldlc2].atloc == + NO_OBJECT && + game.holdng > 1) + break; + game.hints[hint].lc = 0; + return; + case 4: /* dark */ + if (!PROP_IS_NOTFOUND(EMERALD) && + PROP_IS_NOTFOUND(PYRAMID)) { + break; + } + game.hints[hint].lc = 0; + return; + case 5: /* witt */ + break; + case 6: /* urn */ + if (game.dflag == 0) { + break; + } + game.hints[hint].lc = 0; + return; + case 7: /* woods */ + if (game.locs[game.loc].atloc == + NO_OBJECT && + game.locs[game.oldloc].atloc == + NO_OBJECT && + game.locs[game.oldlc2].atloc == + NO_OBJECT) + break; + return; + case 8: /* ogre */ + i = atdwrf(game.loc); + if (i < 0) { + game.hints[hint].lc = 0; + return; + } + if (HERE(OGRE) && i == 0) { + break; + } + return; + case 9: /* jade */ + if (game.tally == 1 && + PROP_IS_STASHED_OR_UNSEEN(JADE)) { + break; + } + game.hints[hint].lc = 0; + return; + default: // LCOV_EXCL_LINE + // Should never happen + BUG(HINT_NUMBER_EXCEEDS_GOTO_LIST); // LCOV_EXCL_LINE + } + + /* Fall through to hint display */ + game.hints[hint].lc = 0; + if (!yes_or_no(hints[hint].question, + arbitrary_messages[NO_MESSAGE], + arbitrary_messages[OK_MAN])) { + return; + } + rspeak(HINT_COST, hints[hint].penalty, + hints[hint].penalty); + game.hints[hint].used = + yes_or_no(arbitrary_messages[WANT_HINT], + hints[hint].hint, + arbitrary_messages[OK_MAN]); + if (game.hints[hint].used && + game.limit > WARNTIME) { + game.limit += + WARNTIME * hints[hint].penalty; + } + } } - } - } - } + } } -static bool spotted_by_pirate(int i) -{ - if (i != PIRATE) { - return false; - } - - /* The pirate's spotted him. Pirate leaves him alone once we've - * found chest. K counts if a treasure is here. If not, and - * 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)) { - return true; - } - int snarfed = 0; - bool movechest = false, robplayer = false; - for (int treasure = 1; treasure <= NOBJECTS; treasure++) { - if (!objects[treasure].is_treasure) { - continue; - } - /* Pirate won't take pyramid from plover room or dark - * room (too easy!). */ - if (treasure == PYRAMID && (game.loc == objects[PYRAMID].plac || - game.loc == objects[EMERALD].plac)) { - continue; - } - if (TOTING(treasure) || HERE(treasure)) { - ++snarfed; +static bool spotted_by_pirate(int i) { + if (i != PIRATE) { + return false; } - if (TOTING(treasure)) { - movechest = true; - robplayer = true; - } - } - /* Force chest placement before player finds last treasure */ - if (game.tally == 1 && snarfed == 0 && game.objects[CHEST].place == LOC_NOWHERE && HERE(LAMP) && game.objects[LAMP].prop == LAMP_BRIGHT) { - rspeak(PIRATE_SPOTTED); - movechest = true; - } - /* Do things in this order (chest move before robbery) so chest is listed - * last at the maze location. */ - if (movechest) { - move(CHEST, game.chloc); - move(MESSAG, game.chloc2); - game.dwarves[PIRATE].loc = game.chloc; - game.dwarves[PIRATE].oldloc = game.chloc; - game.dwarves[PIRATE].seen = false; - } else { - /* You might get a hint of the pirate's presence even if the - * chest doesn't move... */ - if (game.dwarves[PIRATE].oldloc != game.dwarves[PIRATE].loc && PCT(20)) { - rspeak(PIRATE_RUSTLES); + + /* The pirate's spotted him. Pirate leaves him alone once we've + * found chest. K counts if a treasure is here. If not, and + * 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)) { + return true; } - } - if (robplayer) { - rspeak(PIRATE_POUNCES); - for (int treasure = 1; treasure <= NOBJECTS; treasure++) { - if (!objects[treasure].is_treasure) { - continue; - } - if (!(treasure == PYRAMID && (game.loc == objects[PYRAMID].plac || - game.loc == objects[EMERALD].plac))) { - if (AT(treasure) && game.objects[treasure].fixed == IS_FREE) { - carry(treasure, game.loc); + int snarfed = 0; + bool movechest = false, robplayer = false; + for (int treasure = 1; treasure <= NOBJECTS; treasure++) { + if (!objects[treasure].is_treasure) { + continue; + } + /* Pirate won't take pyramid from plover room or dark + * room (too easy!). */ + if (treasure == PYRAMID && + (game.loc == objects[PYRAMID].plac || + game.loc == objects[EMERALD].plac)) { + continue; + } + if (TOTING(treasure) || HERE(treasure)) { + ++snarfed; } if (TOTING(treasure)) { - drop(treasure, game.chloc); + movechest = true; + robplayer = true; + } + } + /* Force chest placement before player finds last treasure */ + if (game.tally == 1 && snarfed == 0 && + game.objects[CHEST].place == LOC_NOWHERE && HERE(LAMP) && + game.objects[LAMP].prop == LAMP_BRIGHT) { + rspeak(PIRATE_SPOTTED); + movechest = true; + } + /* Do things in this order (chest move before robbery) so chest is + * listed last at the maze location. */ + if (movechest) { + move(CHEST, game.chloc); + move(MESSAG, game.chloc2); + game.dwarves[PIRATE].loc = game.chloc; + game.dwarves[PIRATE].oldloc = game.chloc; + game.dwarves[PIRATE].seen = false; + } else { + /* You might get a hint of the pirate's presence even if the + * chest doesn't move... */ + if (game.dwarves[PIRATE].oldloc != game.dwarves[PIRATE].loc && + PCT(20)) { + rspeak(PIRATE_RUSTLES); } - } - } - } + } + if (robplayer) { + rspeak(PIRATE_POUNCES); + for (int treasure = 1; treasure <= NOBJECTS; treasure++) { + if (!objects[treasure].is_treasure) { + continue; + } + if (!(treasure == PYRAMID && + (game.loc == objects[PYRAMID].plac || + game.loc == objects[EMERALD].plac))) { + if (AT(treasure) && + game.objects[treasure].fixed == IS_FREE) { + carry(treasure, game.loc); + } + if (TOTING(treasure)) { + drop(treasure, game.chloc); + } + } + } + } - return true; + return true; } static bool dwarfmove(void) { -/* Dwarves move. Return true if player survives, false if he dies. */ - int kk, stick, attack; - loc_t tk[21]; - - /* Dwarf stuff. See earlier comments for description of - * variables. Remember sixth dwarf is pirate and is thus - * very different except for motion rules. */ - - /* First off, don't let the dwarves follow him into a pit or a - * wall. Activate the whole mess the first time he gets as far - * as the Hall of Mists (what INDEEP() tests). If game.newloc - * is forbidden to pirate (in particular, if it's beyond the - * troll bridge), bypass dwarf stuff. That way pirate can't - * steal return toll, and dwarves can't meet the bear. Also - * means dwarves won't follow him into dead end in maze, but - * c'est la vie. They'll wait for him outside the dead end. */ - if (game.loc == LOC_NOWHERE || FORCED(game.loc) || CNDBIT(game.newloc, COND_NOARRR)) { - return true; - } - - /* Dwarf activity level ratchets up */ - if (game.dflag == 0) { - if (INDEEP(game.loc)) - game.dflag = 1; - return true; - } - - /* When we encounter the first dwarf, we kill 0, 1, or 2 of - * the 5 dwarves. If any of the survivors is at game.loc, - * replace him with the alternate. */ - if (game.dflag == 1) { - if (!INDEEP(game.loc) || - (PCT(95) && (!CNDBIT(game.loc, COND_NOBACK) || PCT(85)))) { - return true; + /* Dwarves move. Return true if player survives, false if he dies. */ + int kk, stick, attack; + loc_t tk[21]; + + /* Dwarf stuff. See earlier comments for description of + * variables. Remember sixth dwarf is pirate and is thus + * very different except for motion rules. */ + + /* First off, don't let the dwarves follow him into a pit or a + * wall. Activate the whole mess the first time he gets as far + * as the Hall of Mists (what INDEEP() tests). If game.newloc + * is forbidden to pirate (in particular, if it's beyond the + * troll bridge), bypass dwarf stuff. That way pirate can't + * steal return toll, and dwarves can't meet the bear. Also + * means dwarves won't follow him into dead end in maze, but + * c'est la vie. They'll wait for him outside the dead end. */ + if (game.loc == LOC_NOWHERE || FORCED(game.loc) || + CNDBIT(game.newloc, COND_NOARRR)) { + return true; + } + + /* Dwarf activity level ratchets up */ + if (game.dflag == 0) { + if (INDEEP(game.loc)) + game.dflag = 1; + return true; + } + + /* When we encounter the first dwarf, we kill 0, 1, or 2 of + * the 5 dwarves. If any of the survivors is at game.loc, + * replace him with the alternate. */ + if (game.dflag == 1) { + if (!INDEEP(game.loc) || + (PCT(95) && (!CNDBIT(game.loc, COND_NOBACK) || PCT(85)))) { + return true; + } + game.dflag = 2; + for (int i = 1; i <= 2; i++) { + int j = 1 + randrange(NDWARVES - 1); + if (PCT(50)) { + game.dwarves[j].loc = 0; + } + } + + /* Alternate initial loc for dwarf, in case one of them + * starts out on top of the adventurer. */ + for (int i = 1; i <= NDWARVES - 1; i++) { + if (game.dwarves[i].loc == game.loc) { + game.dwarves[i].loc = DALTLC; + } + game.dwarves[i].oldloc = game.dwarves[i].loc; + } + rspeak(DWARF_RAN); + drop(AXE, game.loc); + return true; + } + + /* Things are in full swing. Move each dwarf at random, + * except if he's seen us he sticks with us. Dwarves stay + * deep inside. If wandering at random, they don't back up + * unless there's no alternative. If they don't have to + * move, they attack. And, of course, dead dwarves don't do + * much of anything. */ + game.dtotal = 0; + attack = 0; + stick = 0; + for (int i = 1; i <= NDWARVES; i++) { + if (game.dwarves[i].loc == 0) { + continue; + } + /* Fill tk array with all the places this dwarf might go. */ + unsigned int j = 1; + kk = tkey[game.dwarves[i].loc]; + if (kk != 0) + do { + enum desttype_t desttype = travel[kk].desttype; + game.newloc = travel[kk].destval; + /* Have we avoided a dwarf encounter? */ + if (desttype != dest_goto) + continue; + else if (!INDEEP(game.newloc)) + continue; + else if (game.newloc == game.dwarves[i].oldloc) + continue; + else if (j > 1 && game.newloc == tk[j - 1]) + continue; + else if (j >= DIM(tk) - 1) + /* This can't actually happen. */ + continue; // LCOV_EXCL_LINE + else if (game.newloc == game.dwarves[i].loc) + continue; + else if (FORCED(game.newloc)) + continue; + else if (i == PIRATE && + CNDBIT(game.newloc, COND_NOARRR)) + continue; + else if (travel[kk].nodwarves) + continue; + tk[j++] = game.newloc; + } while (!travel[kk++].stop); + tk[j] = game.dwarves[i].oldloc; + if (j >= 2) { + --j; + } + j = 1 + randrange(j); + game.dwarves[i].oldloc = game.dwarves[i].loc; + game.dwarves[i].loc = tk[j]; + game.dwarves[i].seen = + (game.dwarves[i].seen && INDEEP(game.loc)) || + (game.dwarves[i].loc == game.loc || + game.dwarves[i].oldloc == game.loc); + if (!game.dwarves[i].seen) { + continue; + } + game.dwarves[i].loc = game.loc; + if (spotted_by_pirate(i)) { + continue; + } + /* This threatening little dwarf is in the room with him! */ + ++game.dtotal; + if (game.dwarves[i].oldloc == game.dwarves[i].loc) { + ++attack; + if (game.knfloc >= LOC_NOWHERE) { + game.knfloc = game.loc; + } + if (randrange(1000) < 95 * (game.dflag - 2)) { + ++stick; + } + } + } + + /* Now we know what's happening. Let's tell the poor sucker about it. + */ + if (game.dtotal == 0) { + return true; } - game.dflag = 2; - for (int i = 1; i <= 2; i++) { - int j = 1 + randrange(NDWARVES - 1); - if (PCT(50)) { - game.dwarves[j].loc = 0; - } - } - - /* Alternate initial loc for dwarf, in case one of them - * starts out on top of the adventurer. */ - for (int i = 1; i <= NDWARVES - 1; i++) { - if (game.dwarves[i].loc == game.loc) { - game.dwarves[i].loc = DALTLC; - } - game.dwarves[i].oldloc = game.dwarves[i].loc; - } - rspeak(DWARF_RAN); - drop(AXE, game.loc); - return true; - } - - /* Things are in full swing. Move each dwarf at random, - * except if he's seen us he sticks with us. Dwarves stay - * deep inside. If wandering at random, they don't back up - * unless there's no alternative. If they don't have to - * move, they attack. And, of course, dead dwarves don't do - * much of anything. */ - game.dtotal = 0; - attack = 0; - stick = 0; - for (int i = 1; i <= NDWARVES; i++) { - if (game.dwarves[i].loc == 0) { - continue; + rspeak(game.dtotal == 1 ? DWARF_SINGLE : DWARF_PACK, game.dtotal); + if (attack == 0) { + return true; } - /* Fill tk array with all the places this dwarf might go. */ - unsigned int j = 1; - kk = tkey[game.dwarves[i].loc]; - if (kk != 0) - do { - enum desttype_t desttype = travel[kk].desttype; - game.newloc = travel[kk].destval; - /* Have we avoided a dwarf encounter? */ - if (desttype != dest_goto) - continue; - else if (!INDEEP(game.newloc)) - continue; - else if (game.newloc == game.dwarves[i].oldloc) - continue; - else if (j > 1 && game.newloc == tk[j - 1]) - continue; - else if (j >= DIM(tk) - 1) - /* This can't actually happen. */ - continue; // LCOV_EXCL_LINE - else if (game.newloc == game.dwarves[i].loc) - continue; - else if (FORCED(game.newloc)) - continue; - else if (i == PIRATE && CNDBIT(game.newloc, COND_NOARRR)) - continue; - else if (travel[kk].nodwarves) - continue; - tk[j++] = game.newloc; - } while - (!travel[kk++].stop); - tk[j] = game.dwarves[i].oldloc; - if (j >= 2) { - --j; + if (game.dflag == 2) { + game.dflag = 3; } - j = 1 + randrange(j); - game.dwarves[i].oldloc = game.dwarves[i].loc; - game.dwarves[i].loc = tk[j]; - game.dwarves[i].seen = (game.dwarves[i].seen && INDEEP(game.loc)) || - (game.dwarves[i].loc == game.loc || - game.dwarves[i].oldloc == game.loc); - if (!game.dwarves[i].seen) { - continue; + if (attack > 1) { + rspeak(THROWN_KNIVES, attack); + rspeak(stick > 1 ? MULTIPLE_HITS + : (stick == 1 ? ONE_HIT : NONE_HIT), + stick); + } else { + rspeak(KNIFE_THROWN); + rspeak(stick ? GETS_YOU : MISSES_YOU); } - game.dwarves[i].loc = game.loc; - if (spotted_by_pirate(i)) { - continue; + if (stick == 0) { + return true; } - /* This threatening little dwarf is in the room with him! */ - ++game.dtotal; - if (game.dwarves[i].oldloc == game.dwarves[i].loc) { - ++attack; - if (game.knfloc >= LOC_NOWHERE) { - game.knfloc = game.loc; - } - if (randrange(1000) < 95 * (game.dflag - 2)) { - ++stick; - } - } - } - - /* Now we know what's happening. Let's tell the poor sucker about it. */ - if (game.dtotal == 0) { - return true; - } - rspeak(game.dtotal == 1 ? DWARF_SINGLE : DWARF_PACK, game.dtotal); - if (attack == 0) { - return true; - } - if (game.dflag == 2) { - game.dflag = 3; - } - if (attack > 1) { - rspeak(THROWN_KNIVES, attack); - rspeak(stick > 1 ? MULTIPLE_HITS : (stick == 1 ? ONE_HIT : NONE_HIT), stick); - } else { - rspeak(KNIFE_THROWN); - rspeak(stick ? GETS_YOU : MISSES_YOU); - } - if (stick == 0) { - return true; - } - game.oldlc2 = game.loc; - return false; + game.oldlc2 = game.loc; + return false; } /* "You're dead, Jim." @@ -451,69 +479,73 @@ static bool dwarfmove(void) { * cave without the lamp!). game.oldloc is zapped so he can't just * "retreat". */ static void croak(void) { -/* Okay, he's dead. Let's get on with it. */ - const char* query = obituaries[game.numdie].query; - const char* yes_response = obituaries[game.numdie].yes_response; - - ++game.numdie; - - if (game.closng) { - /* He died during closing time. No resurrection. Tally up a - * death and exit. */ - rspeak(DEATH_CLOSING); - terminate(endgame); - } else if (!yes_or_no(query, yes_response, arbitrary_messages[OK_MAN]) - || game.numdie == NDEATHS) { - /* Player is asked if he wants to try again. If not, or if - * he's already used all of his lives, we end the game */ - terminate(endgame); - } else { - /* If player wishes to continue, we empty the liquids in the - * user's inventory, turn off the lamp, and drop all items - * where he died. */ - game.objects[WATER].place = game.objects[OIL].place = LOC_NOWHERE; - if (TOTING(LAMP)) - game.objects[LAMP].prop = LAMP_DARK; - for (int j = 1; j <= NOBJECTS; j++) { - int i = NOBJECTS + 1 - j; - if (TOTING(i)) { - /* Always leave lamp where it's accessible aboveground */ - drop(i, (i == LAMP) ? LOC_START : game.oldlc2); - } - } - game.oldloc = game.loc = game.newloc = LOC_BUILDING; - } + /* Okay, he's dead. Let's get on with it. */ + const char *query = obituaries[game.numdie].query; + const char *yes_response = obituaries[game.numdie].yes_response; + + ++game.numdie; + + if (game.closng) { + /* He died during closing time. No resurrection. Tally up a + * death and exit. */ + rspeak(DEATH_CLOSING); + terminate(endgame); + } else if (!yes_or_no(query, yes_response, + arbitrary_messages[OK_MAN]) || + game.numdie == NDEATHS) { + /* Player is asked if he wants to try again. If not, or if + * he's already used all of his lives, we end the game */ + terminate(endgame); + } else { + /* If player wishes to continue, we empty the liquids in the + * user's inventory, turn off the lamp, and drop all items + * where he died. */ + game.objects[WATER].place = game.objects[OIL].place = + LOC_NOWHERE; + if (TOTING(LAMP)) + game.objects[LAMP].prop = LAMP_DARK; + for (int j = 1; j <= NOBJECTS; j++) { + int i = NOBJECTS + 1 - j; + if (TOTING(i)) { + /* Always leave lamp where it's accessible + * aboveground */ + drop(i, (i == LAMP) ? LOC_START : game.oldlc2); + } + } + game.oldloc = game.loc = game.newloc = LOC_BUILDING; + } } static void describe_location(void) { -/* Describe the location to the user */ - const char* msg = locations[game.loc].description.small; + /* Describe the location to the user */ + const char *msg = locations[game.loc].description.small; - if (MOD(game.locs[game.loc].abbrev, game.abbnum) == 0 || msg == NO_MESSAGE) - msg = locations[game.loc].description.big; + if (MOD(game.locs[game.loc].abbrev, game.abbnum) == 0 || + msg == NO_MESSAGE) + msg = locations[game.loc].description.big; - if (!FORCED(game.loc) && DARK(game.loc)) { - msg = arbitrary_messages[PITCH_DARK]; - } + if (!FORCED(game.loc) && DARK(game.loc)) { + msg = arbitrary_messages[PITCH_DARK]; + } - if (TOTING(BEAR)) { - rspeak(TAME_BEAR); - } + if (TOTING(BEAR)) { + rspeak(TAME_BEAR); + } - speak(msg); + speak(msg); - if (game.loc == LOC_Y2 && PCT(25) && !game.closng) - rspeak(SAYS_PLUGH); + if (game.loc == LOC_Y2 && PCT(25) && !game.closng) + rspeak(SAYS_PLUGH); } - static bool traveleq(int a, int b) { -/* Are two travel entries equal for purposes of skip after failed condition? */ - return (travel[a].condtype == travel[b].condtype) - && (travel[a].condarg1 == travel[b].condarg1) - && (travel[a].condarg2 == travel[b].condarg2) - && (travel[a].desttype == travel[b].desttype) - && (travel[a].destval == travel[b].destval); + /* Are two travel entries equal for purposes of skip after failed + * condition? */ + return (travel[a].condtype == travel[b].condtype) && + (travel[a].condarg1 == travel[b].condarg1) && + (travel[a].condarg2 == travel[b].condarg2) && + (travel[a].desttype == travel[b].desttype) && + (travel[a].destval == travel[b].destval); } /* Given the current location in "game.loc", and a motion verb number in @@ -523,297 +555,342 @@ static bool traveleq(int a, int b) { * does, game.newloc will be limbo, and game.oldloc will be what killed * him, so we need game.oldlc2, which is the last place he was * safe.) */ -static void playermove(int motion) -{ - int scratchloc, travel_entry = tkey[game.loc]; - game.newloc = game.loc; - if (travel_entry == 0) { - BUG(LOCATION_HAS_NO_TRAVEL_ENTRIES); // LCOV_EXCL_LINE - } - if (motion == NUL) { - return; - } else if (motion == BACK) { - /* Handle "go back". Look for verb which goes from game.loc to - * game.oldloc, or to game.oldlc2 If game.oldloc has forced-motion. - * te_tmp saves entry -> forced loc -> previous loc. */ - motion = game.oldloc; - if (FORCED(motion)) - motion = game.oldlc2; - game.oldlc2 = game.oldloc; - game.oldloc = game.loc; - if (CNDBIT(game.loc, COND_NOBACK)) { - rspeak(TWIST_TURN); - return; - } - if (motion == game.loc) { - rspeak(FORGOT_PATH); - return; - } - - int te_tmp = 0; - for (;;) { - enum desttype_t desttype = travel[travel_entry].desttype; - scratchloc = travel[travel_entry].destval; - if (desttype != dest_goto || scratchloc != motion) { - if (desttype == dest_goto) { - if (FORCED(scratchloc) && travel[tkey[scratchloc]].destval == motion) - te_tmp = travel_entry; - } - if (!travel[travel_entry].stop) { - ++travel_entry; /* go to next travel entry for this location */ - continue; - } - /* we've reached the end of travel entries for game.loc */ - travel_entry = te_tmp; - if (travel_entry == 0) { - rspeak(NOT_CONNECTED); - return; - } - } - - motion = travel[travel_entry].motion; - travel_entry = tkey[game.loc]; - break; /* fall through to ordinary travel */ - } - } else if (motion == LOOK) { - /* Look. Can't give more detail. Pretend it wasn't dark - * (though it may now be dark) so he won't fall into a - * pit while staring into the gloom. */ - if (game.detail < 3) { - rspeak(NO_MORE_DETAIL); +static void playermove(int motion) { + int scratchloc, travel_entry = tkey[game.loc]; + game.newloc = game.loc; + if (travel_entry == 0) { + BUG(LOCATION_HAS_NO_TRAVEL_ENTRIES); // LCOV_EXCL_LINE + } + if (motion == NUL) { + return; + } else if (motion == BACK) { + /* Handle "go back". Look for verb which goes from game.loc to + * game.oldloc, or to game.oldlc2 If game.oldloc has + * forced-motion. te_tmp saves entry -> forced loc -> previous + * loc. */ + motion = game.oldloc; + if (FORCED(motion)) + motion = game.oldlc2; + game.oldlc2 = game.oldloc; + game.oldloc = game.loc; + if (CNDBIT(game.loc, COND_NOBACK)) { + rspeak(TWIST_TURN); + return; + } + if (motion == game.loc) { + rspeak(FORGOT_PATH); + return; + } + + int te_tmp = 0; + for (;;) { + enum desttype_t desttype = + travel[travel_entry].desttype; + scratchloc = travel[travel_entry].destval; + if (desttype != dest_goto || scratchloc != motion) { + if (desttype == dest_goto) { + if (FORCED(scratchloc) && + travel[tkey[scratchloc]].destval == + motion) + te_tmp = travel_entry; + } + if (!travel[travel_entry].stop) { + ++travel_entry; /* go to next travel + entry for this + location */ + continue; + } + /* we've reached the end of travel entries for + * game.loc */ + travel_entry = te_tmp; + if (travel_entry == 0) { + rspeak(NOT_CONNECTED); + return; + } + } + + motion = travel[travel_entry].motion; + travel_entry = tkey[game.loc]; + break; /* fall through to ordinary travel */ + } + } else if (motion == LOOK) { + /* Look. Can't give more detail. Pretend it wasn't dark + * (though it may now be dark) so he won't fall into a + * pit while staring into the gloom. */ + if (game.detail < 3) { + rspeak(NO_MORE_DETAIL); + } + ++game.detail; + game.wzdark = false; + game.locs[game.loc].abbrev = 0; + return; + } else if (motion == CAVE) { + /* Cave. Different messages depending on whether above ground. + */ + rspeak((OUTSID(game.loc) && game.loc != LOC_GRATE) + ? FOLLOW_STREAM + : NEED_DETAIL); + return; + } else { + /* none of the specials */ + game.oldlc2 = game.oldloc; + game.oldloc = game.loc; } - ++game.detail; - game.wzdark = false; - game.locs[game.loc].abbrev = 0; - return; - } else if (motion == CAVE) { - /* Cave. Different messages depending on whether above ground. */ - rspeak((OUTSID(game.loc) && game.loc != LOC_GRATE) ? FOLLOW_STREAM : NEED_DETAIL); - return; - } else { - /* none of the specials */ - game.oldlc2 = game.oldloc; - game.oldloc = game.loc; - } - - /* Look for a way to fulfil the motion verb passed in - travel_entry indexes - * the beginning of the motion entries for here (game.loc). */ - for (;;) { - if ((travel[travel_entry].motion == HERE) || travel[travel_entry].motion == motion) - break; - if (travel[travel_entry].stop) { - /* Couldn't find an entry matching the motion word passed - * in. Various messages depending on word given. */ - switch (motion) { - case EAST: - case WEST: - case SOUTH: - case NORTH: - case NE: - case NW: - case SW: - case SE: - case UP: - case DOWN: - rspeak(BAD_DIRECTION); - break; - case FORWARD: - case LEFT: - case RIGHT: - rspeak(UNSURE_FACING); - break; - case OUTSIDE: - case INSIDE: - rspeak(NO_INOUT_HERE); - break; - case XYZZY: - case PLUGH: - rspeak(NOTHING_HAPPENS); - break; - case CRAWL: - rspeak(WHICH_WAY); - break; - default: - rspeak(CANT_APPLY); - } - return; - } - ++travel_entry; - } - - /* (ESR) We've found a destination that goes with the motion verb. - * Next we need to check any conditional(s) on this destination, and - * possibly on following entries. */ - do { - for (;;) { /* L12 loop */ - for (;;) { - enum condtype_t condtype = travel[travel_entry].condtype; - int condarg1 = travel[travel_entry].condarg1; - int condarg2 = travel[travel_entry].condarg2; - if (condtype < cond_not) { - /* YAML N and [pct N] conditionals */ - if (condtype == cond_goto || condtype == cond_pct) { - if (condarg1 == 0 || PCT(condarg1)) - break; - /* else fall through */ - } - /* YAML [with OBJ] clause */ - else if (TOTING(condarg1) || (condtype == cond_with && AT(condarg1))) - break; - /* else fall through to check [not OBJ STATE] */ - } else if (game.objects[condarg1].prop != condarg2) { - break; + + /* Look for a way to fulfil the motion verb passed in - travel_entry + * indexes the beginning of the motion entries for here (game.loc). */ + for (;;) { + if ((travel[travel_entry].motion == HERE) || + travel[travel_entry].motion == motion) + break; + if (travel[travel_entry].stop) { + /* Couldn't find an entry matching the motion word + * passed in. Various messages depending on word given. + */ + switch (motion) { + case EAST: + case WEST: + case SOUTH: + case NORTH: + case NE: + case NW: + case SW: + case SE: + case UP: + case DOWN: + rspeak(BAD_DIRECTION); + break; + case FORWARD: + case LEFT: + case RIGHT: + rspeak(UNSURE_FACING); + break; + case OUTSIDE: + case INSIDE: + rspeak(NO_INOUT_HERE); + break; + case XYZZY: + case PLUGH: + rspeak(NOTHING_HAPPENS); + break; + case CRAWL: + rspeak(WHICH_WAY); + break; + default: + rspeak(CANT_APPLY); + } + return; } + ++travel_entry; + } - /* We arrive here on conditional failure. - * Skip to next non-matching destination */ - int te_tmp = travel_entry; - do { - if (travel[te_tmp].stop) - BUG(CONDITIONAL_TRAVEL_ENTRY_WITH_NO_ALTERATION); // LCOV_EXCL_LINE - ++te_tmp; - } while - (traveleq(travel_entry, te_tmp)); - travel_entry = te_tmp; - } - - /* Found an eligible rule, now execute it */ - enum desttype_t desttype = travel[travel_entry].desttype; - game.newloc = travel[travel_entry].destval; - if (desttype == dest_goto) { - return; - } - - if (desttype == dest_speak) { - /* Execute a speak rule */ - rspeak(game.newloc); - game.newloc = game.loc; - return; - } else { - switch (game.newloc) { - case 1: - /* Special travel 1. Plover-alcove passage. Can carry only - * emerald. Note: travel table must include "useless" - * entries going through passage, which can never be used - * for actual motion, but can be spotted by "go back". */ - game.newloc = (game.loc == LOC_PLOVER) - ? LOC_ALCOVE - : LOC_PLOVER; - if (game.holdng > 1 || (game.holdng == 1 && !TOTING(EMERALD))) { - game.newloc = game.loc; - rspeak(MUST_DROP); - } - return; - case 2: - /* Special travel 2. Plover transport. Drop the - * emerald (only use special travel if toting - * it), so he's forced to use the plover-passage - * to get it out. Having dropped it, go back and - * pretend he wasn't carrying it after all. */ - drop(EMERALD, game.loc); - { - int te_tmp = travel_entry; - do { - if (travel[te_tmp].stop) - BUG(CONDITIONAL_TRAVEL_ENTRY_WITH_NO_ALTERATION); // LCOV_EXCL_LINE - ++te_tmp; - } while - (traveleq(travel_entry, te_tmp)); - travel_entry = te_tmp; - } - continue; /* goto L12 */ - case 3: - /* Special travel 3. Troll bridge. Must be done - * only as special motion so that dwarves won't - * wander across and encounter the bear. (They - * won't follow the player there because that - * region is forbidden to the pirate.) If - * game.prop[TROLL]=TROLL_PAIDONCE, he's crossed - * since paying, so step out and block him. - * (standard travel entries check for - * game.prop[TROLL]=TROLL_UNPAID.) Special stuff - * for bear. */ - if (game.objects[TROLL].prop == TROLL_PAIDONCE) { - pspeak(TROLL, look, true, TROLL_PAIDONCE); - game.objects[TROLL].prop = TROLL_UNPAID; - DESTROY(TROLL2); - move(TROLL2 + NOBJECTS, IS_FREE); - move(TROLL, objects[TROLL].plac); - move(TROLL + NOBJECTS, objects[TROLL].fixd); - juggle(CHASM); - game.newloc = game.loc; - return; - } else { - game.newloc = objects[TROLL].plac + objects[TROLL].fixd - game.loc; - if (game.objects[TROLL].prop == TROLL_UNPAID) - game.objects[TROLL].prop = TROLL_PAIDONCE; - if (!TOTING(BEAR)) { - return; + /* (ESR) We've found a destination that goes with the motion verb. + * Next we need to check any conditional(s) on this destination, and + * possibly on following entries. */ + do { + for (;;) { /* L12 loop */ + for (;;) { + enum condtype_t condtype = + travel[travel_entry].condtype; + int condarg1 = travel[travel_entry].condarg1; + int condarg2 = travel[travel_entry].condarg2; + if (condtype < cond_not) { + /* YAML N and [pct N] conditionals */ + if (condtype == cond_goto || + condtype == cond_pct) { + if (condarg1 == 0 || + PCT(condarg1)) + break; + /* else fall through */ + } + /* YAML [with OBJ] clause */ + else if (TOTING(condarg1) || + (condtype == cond_with && + AT(condarg1))) + break; + /* else fall through to check [not OBJ + * STATE] */ + } else if (game.objects[condarg1].prop != + condarg2) { + break; + } + + /* We arrive here on conditional failure. + * Skip to next non-matching destination */ + int te_tmp = travel_entry; + do { + if (travel[te_tmp].stop) + BUG(CONDITIONAL_TRAVEL_ENTRY_WITH_NO_ALTERATION); // LCOV_EXCL_LINE + ++te_tmp; + } while (traveleq(travel_entry, te_tmp)); + travel_entry = te_tmp; } - state_change(CHASM, BRIDGE_WRECKED); - game.objects[TROLL].prop = TROLL_GONE; - drop(BEAR, game.newloc); - game.objects[BEAR].fixed = IS_FIXED; - game.objects[BEAR].prop = BEAR_DEAD; - game.oldlc2 = game.newloc; - croak(); - return; - } - default: // LCOV_EXCL_LINE - BUG(SPECIAL_TRAVEL_500_GT_L_GT_300_EXCEEDS_GOTO_LIST); // LCOV_EXCL_LINE - } - } - break; /* Leave L12 loop */ - } - } while - (false); + + /* Found an eligible rule, now execute it */ + enum desttype_t desttype = + travel[travel_entry].desttype; + game.newloc = travel[travel_entry].destval; + if (desttype == dest_goto) { + return; + } + + if (desttype == dest_speak) { + /* Execute a speak rule */ + rspeak(game.newloc); + game.newloc = game.loc; + return; + } else { + switch (game.newloc) { + case 1: + /* Special travel 1. Plover-alcove + * passage. Can carry only emerald. + * Note: travel table must include + * "useless" entries going through + * passage, which can never be used for + * actual motion, but can be spotted by + * "go back". */ + game.newloc = (game.loc == LOC_PLOVER) + ? LOC_ALCOVE + : LOC_PLOVER; + if (game.holdng > 1 || + (game.holdng == 1 && + !TOTING(EMERALD))) { + game.newloc = game.loc; + rspeak(MUST_DROP); + } + return; + case 2: + /* Special travel 2. Plover transport. + * Drop the emerald (only use special + * travel if toting it), so he's forced + * to use the plover-passage to get it + * out. Having dropped it, go back and + * pretend he wasn't carrying it after + * all. */ + drop(EMERALD, game.loc); + { + int te_tmp = travel_entry; + do { + if (travel[te_tmp].stop) + BUG(CONDITIONAL_TRAVEL_ENTRY_WITH_NO_ALTERATION); // LCOV_EXCL_LINE + ++te_tmp; + } while (traveleq(travel_entry, + te_tmp)); + travel_entry = te_tmp; + } + continue; /* goto L12 */ + case 3: + /* Special travel 3. Troll bridge. Must + * be done only as special motion so + * that dwarves won't wander across and + * encounter the bear. (They won't + * follow the player there because that + * region is forbidden to the pirate.) + * If game.prop[TROLL]=TROLL_PAIDONCE, + * he's crossed since paying, so step + * out and block him. (standard travel + * entries check for + * game.prop[TROLL]=TROLL_UNPAID.) + * Special stuff for bear. */ + if (game.objects[TROLL].prop == + TROLL_PAIDONCE) { + pspeak(TROLL, look, true, + TROLL_PAIDONCE); + game.objects[TROLL].prop = + TROLL_UNPAID; + DESTROY(TROLL2); + move(TROLL2 + NOBJECTS, + IS_FREE); + move(TROLL, + objects[TROLL].plac); + move(TROLL + NOBJECTS, + objects[TROLL].fixd); + juggle(CHASM); + game.newloc = game.loc; + return; + } else { + game.newloc = + objects[TROLL].plac + + objects[TROLL].fixd - + game.loc; + if (game.objects[TROLL].prop == + TROLL_UNPAID) + game.objects[TROLL] + .prop = + TROLL_PAIDONCE; + if (!TOTING(BEAR)) { + return; + } + state_change(CHASM, + BRIDGE_WRECKED); + game.objects[TROLL].prop = + TROLL_GONE; + drop(BEAR, game.newloc); + game.objects[BEAR].fixed = + IS_FIXED; + game.objects[BEAR].prop = + BEAR_DEAD; + game.oldlc2 = game.newloc; + croak(); + return; + } + default: // LCOV_EXCL_LINE + BUG(SPECIAL_TRAVEL_500_GT_L_GT_300_EXCEEDS_GOTO_LIST); // LCOV_EXCL_LINE + } + } + break; /* Leave L12 loop */ + } + } while (false); } static void lampcheck(void) { -/* Check game limit and lamp timers */ - if (game.objects[LAMP].prop == LAMP_BRIGHT) { - --game.limit; - } - - /* Another way we can force an end to things is by having the - * lamp give out. When it gets close, we come here to warn him. - * First following arm checks if the lamp and fresh batteries are - * here, in which case we replace the batteries and continue. - * Second is for other cases of lamp dying. Even after it goes - * out, he can explore outside for a while if desired. */ - if (game.limit <= WARNTIME) { - if (HERE(BATTERY) && game.objects[BATTERY].prop == FRESH_BATTERIES && HERE(LAMP)) { - rspeak(REPLACE_BATTERIES); - game.objects[BATTERY].prop = DEAD_BATTERIES; + /* Check game limit and lamp timers */ + if (game.objects[LAMP].prop == LAMP_BRIGHT) { + --game.limit; + } + + /* Another way we can force an end to things is by having the + * lamp give out. When it gets close, we come here to warn him. + * First following arm checks if the lamp and fresh batteries are + * here, in which case we replace the batteries and continue. + * Second is for other cases of lamp dying. Even after it goes + * out, he can explore outside for a while if desired. */ + if (game.limit <= WARNTIME) { + if (HERE(BATTERY) && + game.objects[BATTERY].prop == FRESH_BATTERIES && + HERE(LAMP)) { + rspeak(REPLACE_BATTERIES); + game.objects[BATTERY].prop = DEAD_BATTERIES; #ifdef __unused__ - /* This code from the original game seems to have been faulty. - * No tests ever passed the guard, and with the guard removed - * the game hangs when the lamp limit is reached. - */ - if (TOTING(BATTERY)) { - drop(BATTERY, game.loc); - } + /* This code from the original game seems to have been + * faulty. No tests ever passed the guard, and with the + * guard removed the game hangs when the lamp limit is + * reached. + */ + if (TOTING(BATTERY)) { + drop(BATTERY, game.loc); + } #endif - game.limit += BATTERYLIFE; - game.lmwarn = false; - } else if (!game.lmwarn && HERE(LAMP)) { - game.lmwarn = true; - if (game.objects[BATTERY].prop == DEAD_BATTERIES) { - rspeak(MISSING_BATTERIES); - } else if (game.objects[BATTERY].place == LOC_NOWHERE) { - rspeak(LAMP_DIM); - } else { - rspeak(GET_BATTERIES); - } - } - } - if (game.limit == 0) { - game.limit = -1; - game.objects[LAMP].prop = LAMP_DARK; - if (HERE(LAMP)) { - rspeak(LAMP_OUT); + game.limit += BATTERYLIFE; + game.lmwarn = false; + } else if (!game.lmwarn && HERE(LAMP)) { + game.lmwarn = true; + if (game.objects[BATTERY].prop == DEAD_BATTERIES) { + rspeak(MISSING_BATTERIES); + } else if (game.objects[BATTERY].place == LOC_NOWHERE) { + rspeak(LAMP_DIM); + } else { + rspeak(GET_BATTERIES); + } + } + } + if (game.limit == 0) { + game.limit = -1; + game.objects[LAMP].prop = LAMP_DARK; + if (HERE(LAMP)) { + rspeak(LAMP_OUT); + } } - } } /* Handle the closing of the cave. The cave closes "clock1" turns @@ -835,464 +912,503 @@ static void lampcheck(void) { * arise from the use of negative prop numbers to suppress the object * descriptions until he's actually moved the objects. */ static bool closecheck(void) { - /* If a turn threshold has been met, apply penalties and tell - * the player about it. */ - for (int i = 0; i < NTHRESHOLDS; ++i) { - if (game.turns == turn_thresholds[i].threshold + 1) { - game.trnluz += turn_thresholds[i].point_loss; - speak(turn_thresholds[i].message); - } - } - - /* Don't tick game.clock1 unless well into cave (and not at Y2). */ - if (game.tally == 0 && INDEEP(game.loc) && game.loc != LOC_Y2) { - --game.clock1; - } - - /* When the first warning comes, we lock the grate, destroy - * the bridge, kill all the dwarves (and the pirate), remove - * the troll and bear (unless dead), and set "closng" to - * true. Leave the dragon; too much trouble to move it. - * from now until clock2 runs out, he cannot unlock the - * grate, move to any location outside the cave, or create - * the bridge. Nor can he be resurrected if he dies. Note - * that the snake is already gone, since he got to the - * treasure accessible only via the hall of the mountain - * king. Also, he's been in giant room (to get eggs), so we - * can refer to it. Also also, he's gotten the pearl, so we - * know the bivalve is an oyster. *And*, the dwarves must - * have been activated, since we've found chest. */ - if (game.clock1 == 0) { - game.objects[GRATE].prop = GRATE_CLOSED; - game.objects[FISSURE].prop = UNBRIDGED; - for (int i = 1; i <= NDWARVES; i++) { - game.dwarves[i].seen = false; - game.dwarves[i].loc = LOC_NOWHERE; - } - DESTROY(TROLL); - move(TROLL + NOBJECTS, IS_FREE); - move(TROLL2, objects[TROLL].plac); - move(TROLL2 + NOBJECTS, objects[TROLL].fixd); - juggle(CHASM); - if (game.objects[BEAR].prop != BEAR_DEAD) { - DESTROY(BEAR); + /* If a turn threshold has been met, apply penalties and tell + * the player about it. */ + for (int i = 0; i < NTHRESHOLDS; ++i) { + if (game.turns == turn_thresholds[i].threshold + 1) { + game.trnluz += turn_thresholds[i].point_loss; + speak(turn_thresholds[i].message); + } } - game.objects[CHAIN].prop = CHAIN_HEAP; - game.objects[CHAIN].fixed = IS_FREE; - game.objects[AXE].prop = AXE_HERE; - game.objects[AXE].fixed = IS_FREE; - rspeak(CAVE_CLOSING); - game.clock1 = -1; - game.closng = true; - return game.closed; - } else if (game.clock1 < 0) - --game.clock2; - if (game.clock2 == 0) { - /* Once he's panicked, and clock2 has run out, we come here - * to set up the storage room. The room has two locs, - * hardwired as LOC_NE and LOC_SW. At the ne end, we - * place empty bottles, a nursery of plants, a bed of - * oysters, a pile of lamps, rods with stars, sleeping - * dwarves, and him. At the sw end we place grate over - * treasures, snake pit, covey of caged birds, more rods, and - * pillows. A mirror stretches across one wall. Many of the - * objects come from known locations and/or states (e.g. the - * snake is known to have been destroyed and needn't be - * carried away from its old "place"), making the various - * objects be handled differently. We also drop all other - * objects he might be carrying (lest he has some which - * could cause trouble, such as the keys). We describe the - * flash of light and trundle back. */ - put(BOTTLE, LOC_NE, EMPTY_BOTTLE); - put(PLANT, LOC_NE, PLANT_THIRSTY); - put(OYSTER, LOC_NE, STATE_FOUND); - put(LAMP, LOC_NE, LAMP_DARK); - put(ROD, LOC_NE, STATE_FOUND); - put(DWARF, LOC_NE, STATE_FOUND); - game.loc = LOC_NE; - game.oldloc = LOC_NE; - game.newloc = LOC_NE; - /* Leave the grate with normal (non-negative) property. - * Reuse sign. */ - move(GRATE, LOC_SW); - move(SIGN, LOC_SW); - game.objects[SIGN].prop = ENDGAME_SIGN; - put(SNAKE, LOC_SW, SNAKE_CHASED); - put(BIRD, LOC_SW, BIRD_CAGED); - put(CAGE, LOC_SW, STATE_FOUND); - put(ROD2, LOC_SW, STATE_FOUND); - put(PILLOW, LOC_SW, STATE_FOUND); - - put(MIRROR, LOC_NE, STATE_FOUND); - game.objects[MIRROR].fixed = LOC_SW; - - for (int i = 1; i <= NOBJECTS; i++) { - if (TOTING(i)) { - DESTROY(i); - } - } - - rspeak(CAVE_CLOSED); - game.closed = true; - return game.closed; - } - - lampcheck(); - return false; -} -static void listobjects(void) { -/* Print out descriptions of objects at this location. If - * not closing and property value is negative, tally off - * another treasure. Rug is special case; once seen, its - * game.prop is RUG_DRAGON (dragon on it) till dragon is killed. - * 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)) { - ++game.locs[game.loc].abbrev; - for (int i = game.locs[game.loc].atloc; i != 0; i = game.link[i]) { - obj_t obj = i; - if (obj > NOBJECTS) { - obj = obj - NOBJECTS; - } - if (obj == STEPS && TOTING(NUGGET)) { - continue; - } - /* (ESR) Warning: it looks like you could get away with - * running this code only on objects with the treasure - * property set. Nope. There is mystery here. - */ - if (PROP_IS_STASHED_OR_UNSEEN(obj)) { - if (game.closed) { - continue; + /* Don't tick game.clock1 unless well into cave (and not at Y2). */ + if (game.tally == 0 && INDEEP(game.loc) && game.loc != LOC_Y2) { + --game.clock1; + } + + /* When the first warning comes, we lock the grate, destroy + * the bridge, kill all the dwarves (and the pirate), remove + * the troll and bear (unless dead), and set "closng" to + * true. Leave the dragon; too much trouble to move it. + * from now until clock2 runs out, he cannot unlock the + * grate, move to any location outside the cave, or create + * the bridge. Nor can he be resurrected if he dies. Note + * that the snake is already gone, since he got to the + * treasure accessible only via the hall of the mountain + * king. Also, he's been in giant room (to get eggs), so we + * can refer to it. Also also, he's gotten the pearl, so we + * know the bivalve is an oyster. *And*, the dwarves must + * have been activated, since we've found chest. */ + if (game.clock1 == 0) { + game.objects[GRATE].prop = GRATE_CLOSED; + game.objects[FISSURE].prop = UNBRIDGED; + for (int i = 1; i <= NDWARVES; i++) { + game.dwarves[i].seen = false; + game.dwarves[i].loc = LOC_NOWHERE; } - PROP_SET_FOUND(obj); - if (obj == RUG) { - game.objects[RUG].prop = RUG_DRAGON; + DESTROY(TROLL); + move(TROLL + NOBJECTS, IS_FREE); + move(TROLL2, objects[TROLL].plac); + move(TROLL2 + NOBJECTS, objects[TROLL].fixd); + juggle(CHASM); + if (game.objects[BEAR].prop != BEAR_DEAD) { + DESTROY(BEAR); } - if (obj == CHAIN) { - game.objects[CHAIN].prop = CHAINING_BEAR; + game.objects[CHAIN].prop = CHAIN_HEAP; + game.objects[CHAIN].fixed = IS_FREE; + game.objects[AXE].prop = AXE_HERE; + game.objects[AXE].fixed = IS_FREE; + rspeak(CAVE_CLOSING); + game.clock1 = -1; + game.closng = true; + return game.closed; + } else if (game.clock1 < 0) + --game.clock2; + if (game.clock2 == 0) { + /* Once he's panicked, and clock2 has run out, we come here + * to set up the storage room. The room has two locs, + * hardwired as LOC_NE and LOC_SW. At the ne end, we + * place empty bottles, a nursery of plants, a bed of + * oysters, a pile of lamps, rods with stars, sleeping + * dwarves, and him. At the sw end we place grate over + * treasures, snake pit, covey of caged birds, more rods, and + * pillows. A mirror stretches across one wall. Many of the + * objects come from known locations and/or states (e.g. the + * snake is known to have been destroyed and needn't be + * carried away from its old "place"), making the various + * objects be handled differently. We also drop all other + * objects he might be carrying (lest he has some which + * could cause trouble, such as the keys). We describe the + * flash of light and trundle back. */ + put(BOTTLE, LOC_NE, EMPTY_BOTTLE); + put(PLANT, LOC_NE, PLANT_THIRSTY); + put(OYSTER, LOC_NE, STATE_FOUND); + put(LAMP, LOC_NE, LAMP_DARK); + put(ROD, LOC_NE, STATE_FOUND); + put(DWARF, LOC_NE, STATE_FOUND); + game.loc = LOC_NE; + game.oldloc = LOC_NE; + game.newloc = LOC_NE; + /* Leave the grate with normal (non-negative) property. + * Reuse sign. */ + move(GRATE, LOC_SW); + move(SIGN, LOC_SW); + game.objects[SIGN].prop = ENDGAME_SIGN; + put(SNAKE, LOC_SW, SNAKE_CHASED); + put(BIRD, LOC_SW, BIRD_CAGED); + put(CAGE, LOC_SW, STATE_FOUND); + put(ROD2, LOC_SW, STATE_FOUND); + put(PILLOW, LOC_SW, STATE_FOUND); + + put(MIRROR, LOC_NE, STATE_FOUND); + game.objects[MIRROR].fixed = LOC_SW; + + for (int i = 1; i <= NOBJECTS; i++) { + if (TOTING(i)) { + DESTROY(i); + } } - if (obj == EGGS) { - game.seenbigwords = true; + + rspeak(CAVE_CLOSED); + game.closed = true; + return game.closed; + } + + lampcheck(); + return false; +} + +static void listobjects(void) { + /* Print out descriptions of objects at this location. If + * not closing and property value is negative, tally off + * another treasure. Rug is special case; once seen, its + * game.prop is RUG_DRAGON (dragon on it) till dragon is killed. + * 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)) { + ++game.locs[game.loc].abbrev; + for (int i = game.locs[game.loc].atloc; i != 0; + i = game.link[i]) { + obj_t obj = i; + if (obj > NOBJECTS) { + obj = obj - NOBJECTS; + } + if (obj == STEPS && TOTING(NUGGET)) { + continue; + } + /* (ESR) Warning: it looks like you could get away with + * running this code only on objects with the treasure + * property set. Nope. There is mystery here. + */ + if (PROP_IS_STASHED_OR_UNSEEN(obj)) { + if (game.closed) { + continue; + } + PROP_SET_FOUND(obj); + if (obj == RUG) { + game.objects[RUG].prop = RUG_DRAGON; + } + if (obj == CHAIN) { + game.objects[CHAIN].prop = + CHAINING_BEAR; + } + if (obj == EGGS) { + game.seenbigwords = true; + } + --game.tally; + /* Note: There used to be a test here to see + * whether the player had blown it so badly that + * he could never ever see the remaining + * treasures, and if so the lamp was zapped to + * 35 turns. But the tests were too + * simple-minded; things like killing the bird + * before the snake was gone (can never see + * jewelry), and doing it "right" was hopeless. + * E.G., could cross troll bridge several times, + * using up all available treasures, breaking + * vase, using coins to buy batteries, etc., and + * eventually never be able to get across again. + * If bottle were left on far side, could then + * never get eggs or trident, and the effects + * propagate. So the whole thing was flushed. + * anyone who makes such a gross blunder isn't + * likely to find everything else anyway (so + * goes the rationalisation). */ + } + int kk = game.objects[obj].prop; + if (obj == STEPS) { + kk = (game.loc == game.objects[STEPS].fixed) + ? STEPS_UP + : STEPS_DOWN; + } + pspeak(obj, look, true, kk); } - --game.tally; - /* Note: There used to be a test here to see whether the - * player had blown it so badly that he could never ever see - * the remaining treasures, and if so the lamp was zapped to - * 35 turns. But the tests were too simple-minded; things - * like killing the bird before the snake was gone (can never - * see jewelry), and doing it "right" was hopeless. E.G., - * could cross troll bridge several times, using up all - * available treasures, breaking vase, using coins to buy - * batteries, etc., and eventually never be able to get - * across again. If bottle were left on far side, could then - * never get eggs or trident, and the effects propagate. So - * the whole thing was flushed. anyone who makes such a - * gross blunder isn't likely to find everything else anyway - * (so goes the rationalisation). */ - } - int kk = game.objects[obj].prop; - if (obj == STEPS) { - kk = (game.loc == game.objects[STEPS].fixed) - ? STEPS_UP - : STEPS_DOWN; - } - pspeak(obj, look, true, kk); - } - } + } } -/* Pre-processes a command input to see if we need to tease out a few specific cases: +/* Pre-processes a command input to see if we need to tease out a few specific + * cases: * - "enter water" or "enter stream": - * weird specific case that gets the user wet, and then kicks us back to get another command + * weird specific case that gets the user wet, and then kicks us back to get + * another command * - : - * Irregular form of input, but should be allowed. We switch back to form for - * further processing. + * Irregular form of input, but should be allowed. We switch back to + * form for further processing. * - "grate": - * If in location with grate, we move to that grate. If we're in a number of other places, - * we move to the entrance. + * If in location with grate, we move to that grate. If we're in a number of + * other places, we move to the entrance. * - "water plant", "oil plant", "water door", "oil door": * Change to "pour water" or "pour oil" based on context * - "cage bird": * If bird is present, we change to "carry bird" * - * Returns true if pre-processing is complete, and we're ready to move to the primary command - * processing, false otherwise. */ + * Returns true if pre-processing is complete, and we're ready to move to the + * primary command processing, false otherwise. */ static bool preprocess_command(command_t *command) { - if (command->word[0].type == MOTION && command->word[0].id == ENTER - && (command->word[1].id == STREAM || command->word[1].id == WATER)) { - if (LIQLOC(game.loc) == WATER) { - rspeak(FEET_WET); + if (command->word[0].type == MOTION && command->word[0].id == ENTER && + (command->word[1].id == STREAM || command->word[1].id == WATER)) { + if (LIQLOC(game.loc) == WATER) { + rspeak(FEET_WET); + } else { + rspeak(WHERE_QUERY); + } } else { - rspeak(WHERE_QUERY); + if (command->word[0].type == OBJECT) { + /* From OV to VO form */ + if (command->word[1].type == ACTION) { + command_word_t stage = command->word[0]; + command->word[0] = command->word[1]; + command->word[1] = stage; + } + + if (command->word[0].id == GRATE) { + command->word[0].type = MOTION; + if (game.loc == LOC_START || + game.loc == LOC_VALLEY || + game.loc == LOC_SLIT) { + command->word[0].id = DEPRESSION; + } + if (game.loc == LOC_COBBLE || + game.loc == LOC_DEBRIS || + game.loc == LOC_AWKWARD || + game.loc == LOC_BIRDCHAMBER || + game.loc == LOC_PITTOP) { + command->word[0].id = ENTRANCE; + } + } + if ((command->word[0].id == WATER || + command->word[0].id == OIL) && + (command->word[1].id == PLANT || + command->word[1].id == DOOR)) { + if (AT(command->word[1].id)) { + command->word[1] = command->word[0]; + command->word[0].id = POUR; + command->word[0].type = ACTION; + strncpy(command->word[0].raw, "pour", + LINESIZE - 1); + } + } + if (command->word[0].id == CAGE && + command->word[1].id == BIRD && HERE(CAGE) && + HERE(BIRD)) { + command->word[0].id = CARRY; + command->word[0].type = ACTION; + } + } + + /* If no word type is given for the first word, we assume it's a + * motion. */ + if (command->word[0].type == NO_WORD_TYPE) + command->word[0].type = MOTION; + + command->state = PREPROCESSED; + return true; } - } else { - if (command->word[0].type == OBJECT) { - /* From OV to VO form */ - if (command->word[1].type == ACTION) { - command_word_t stage = command->word[0]; - command->word[0] = command->word[1]; - command->word[1] = stage; - } - - if (command->word[0].id == GRATE) { - command->word[0].type = MOTION; - if (game.loc == LOC_START || - game.loc == LOC_VALLEY || - game.loc == LOC_SLIT) { - command->word[0].id = DEPRESSION; - } - if (game.loc == LOC_COBBLE || - game.loc == LOC_DEBRIS || - game.loc == LOC_AWKWARD || - game.loc == LOC_BIRDCHAMBER || - game.loc == LOC_PITTOP) { - command->word[0].id = ENTRANCE; - } - } - if ((command->word[0].id == WATER || command->word[0].id == OIL) && - (command->word[1].id == PLANT || command->word[1].id == DOOR)) { - if (AT(command->word[1].id)) { - command->word[1] = command->word[0]; - command->word[0].id = POUR; - command->word[0].type = ACTION; - strncpy(command->word[0].raw, "pour", LINESIZE - 1); - } - } - if (command->word[0].id == CAGE && command->word[1].id == BIRD && HERE(CAGE) && HERE(BIRD)) { - command->word[0].id = CARRY; - command->word[0].type = ACTION; - } - } - - /* If no word type is given for the first word, we assume it's a motion. */ - if (command->word[0].type == NO_WORD_TYPE) - command->word[0].type = MOTION; - - command->state = PREPROCESSED; - return true; - } - return false; + return false; } 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) { - rspeak(EXIT_CLOSED); - game.newloc = game.loc; - if (!game.panic) { - game.clock2 = PANICTIME; + /* 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) { + rspeak(EXIT_CLOSED); + game.newloc = game.loc; + if (!game.panic) { + game.clock2 = PANICTIME; + } + game.panic = true; + } + + /* See if a dwarf has seen him and has come from where he + * wants to go. If so, the dwarf's blocking his way. If + * coming from place forbidden to pirate (dwarves rooted in + * place) let him get out (and attacked). */ + if (game.newloc != game.loc && !FORCED(game.loc) && + !CNDBIT(game.loc, COND_NOARRR)) { + for (size_t i = 1; i <= NDWARVES - 1; i++) { + if (game.dwarves[i].oldloc == game.newloc && + game.dwarves[i].seen) { + game.newloc = game.loc; + rspeak(DWARF_BLOCK); + break; + } + } + } + game.loc = game.newloc; + + if (!dwarfmove()) { + croak(); } - game.panic = true; - } - - /* See if a dwarf has seen him and has come from where he - * wants to go. If so, the dwarf's blocking his way. If - * coming from place forbidden to pirate (dwarves rooted in - * place) let him get out (and attacked). */ - if (game.newloc != game.loc && !FORCED(game.loc) && !CNDBIT(game.loc, COND_NOARRR)) { - for (size_t i = 1; i <= NDWARVES - 1; i++) { - if (game.dwarves[i].oldloc == game.newloc && game.dwarves[i].seen) { - game.newloc = game.loc; - rspeak(DWARF_BLOCK); - break; - } - } - } - game.loc = game.newloc; - - if (!dwarfmove()) { - croak(); - } - - if (game.loc == LOC_NOWHERE) { - croak(); - } - - /* The easiest way to get killed is to fall into a pit in - * pitch darkness. */ - if (!FORCED(game.loc) && DARK(game.loc) && game.wzdark && PCT(PIT_KILL_PROB)) { - rspeak(PIT_FALL); - game.oldlc2 = game.loc; - croak(); - return false; - } - - return true; + + if (game.loc == LOC_NOWHERE) { + croak(); + } + + /* The easiest way to get killed is to fall into a pit in + * pitch darkness. */ + if (!FORCED(game.loc) && DARK(game.loc) && game.wzdark && + PCT(PIT_KILL_PROB)) { + rspeak(PIT_FALL); + game.oldlc2 = game.loc; + croak(); + return false; + } + + return true; } static bool do_command(void) { -/* Get and execute a command */ - static command_t command; - clear_command(&command); - - /* Describe the current location and (maybe) get next command. */ - while (command.state != EXECUTED) { - describe_location(); - - if (FORCED(game.loc)) { - playermove(HERE); - return true; - } - - listobjects(); - - /* Command not yet given; keep getting commands from user - * until valid command is both given and executed. */ - clear_command(&command); - while (command.state <= GIVEN) { - - if (game.closed) { - /* If closing time, check for any stashed objects - * being toted and unstash them. This 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)) && 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); - } - } - - /* 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 && game.knfloc != game.loc) { - game.knfloc = LOC_NOWHERE; - } - - /* Check some for hints, get input from user, increment - * turn, and pre-process commands. Keep going until - * pre-processing is done. */ - while ( command.state < PREPROCESSED ) { - checkhints(); - - /* Get command input from user */ - if (!get_command_input(&command)) { - return false; + /* Get and execute a command */ + static command_t command; + clear_command(&command); + + /* Describe the current location and (maybe) get next command. */ + while (command.state != EXECUTED) { + describe_location(); + + if (FORCED(game.loc)) { + playermove(HERE); + return true; } - /* Every input, check "foobar" flag. If zero, nothing's going - * on. If pos, make neg. If neg, he skipped a word, so make it - * zero. - */ - game.foobar = (game.foobar > WORD_EMPTY) ? -game.foobar : WORD_EMPTY; - - ++game.turns; - preprocess_command(&command); - } - - /* check if game is closed, and exit if it is */ - if (closecheck()) { - return true; - } - - /* loop until all words in command are processed */ - while (command.state == PREPROCESSED ) { - command.state = PROCESSING; - - if (command.word[0].id == WORD_NOT_FOUND) { - /* Gee, I don't understand. */ - sspeak(DONT_KNOW, command.word[0].raw); - clear_command(&command); - continue; - } - - /* Give user hints of shortcuts */ - if (strncasecmp(command.word[0].raw, "west", sizeof("west")) == 0) { - if (++game.iwest == 10) { - rspeak(W_IS_WEST); - } - } - if (strncasecmp(command.word[0].raw, "go", sizeof("go")) == 0 && command.word[1].id != WORD_EMPTY) { - if (++game.igo == 10) { - rspeak(GO_UNNEEDED); - } - } - - switch (command.word[0].type) { - case MOTION: - playermove(command.word[0].id); - command.state = EXECUTED; - continue; - case OBJECT: - command.part = unknown; - command.obj = command.word[0].id; - break; - case ACTION: - if (command.word[1].type == NUMERIC) { - command.part = transitive; - } else { - command.part = intransitive; - } - command.verb = command.word[0].id; - break; - case NUMERIC: - if (!settings.oldstyle) { - sspeak(DONT_KNOW, command.word[0].raw); - clear_command(&command); - continue; - } - break;// LCOV_EXCL_LINE - default: // LCOV_EXCL_LINE - case NO_WORD_TYPE: // LCOV_EXCL_LINE - BUG(VOCABULARY_TYPE_N_OVER_1000_NOT_BETWEEN_0_AND_3); // LCOV_EXCL_LINE - } - - switch (action(command)) { - case GO_TERMINATE: - command.state = EXECUTED; - break; - case GO_MOVE: - playermove(NUL); - command.state = EXECUTED; - break; - case GO_WORD2: + listobjects(); + + /* Command not yet given; keep getting commands from user + * until valid command is both given and executed. */ + clear_command(&command); + while (command.state <= GIVEN) { + + if (game.closed) { + /* If closing time, check for any stashed + * objects being toted and unstash them. This + * 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)) && + 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); + } + } + + /* 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 && + game.knfloc != game.loc) { + game.knfloc = LOC_NOWHERE; + } + + /* Check some for hints, get input from user, increment + * turn, and pre-process commands. Keep going until + * pre-processing is done. */ + while (command.state < PREPROCESSED) { + checkhints(); + + /* Get command input from user */ + if (!get_command_input(&command)) { + return false; + } + + /* Every input, check "foobar" flag. If zero, + * nothing's going on. If pos, make neg. If neg, + * he skipped a word, so make it zero. + */ + game.foobar = (game.foobar > WORD_EMPTY) + ? -game.foobar + : WORD_EMPTY; + + ++game.turns; + preprocess_command(&command); + } + + /* check if game is closed, and exit if it is */ + if (closecheck()) { + return true; + } + + /* loop until all words in command are processed */ + while (command.state == PREPROCESSED) { + command.state = PROCESSING; + + if (command.word[0].id == WORD_NOT_FOUND) { + /* Gee, I don't understand. */ + sspeak(DONT_KNOW, command.word[0].raw); + clear_command(&command); + continue; + } + + /* Give user hints of shortcuts */ + if (strncasecmp(command.word[0].raw, "west", + sizeof("west")) == 0) { + if (++game.iwest == 10) { + rspeak(W_IS_WEST); + } + } + if (strncasecmp(command.word[0].raw, "go", + sizeof("go")) == 0 && + command.word[1].id != WORD_EMPTY) { + if (++game.igo == 10) { + rspeak(GO_UNNEEDED); + } + } + + switch (command.word[0].type) { + case MOTION: + playermove(command.word[0].id); + command.state = EXECUTED; + continue; + case OBJECT: + command.part = unknown; + command.obj = command.word[0].id; + break; + case ACTION: + if (command.word[1].type == NUMERIC) { + command.part = transitive; + } else { + command.part = intransitive; + } + command.verb = command.word[0].id; + break; + case NUMERIC: + if (!settings.oldstyle) { + sspeak(DONT_KNOW, + command.word[0].raw); + clear_command(&command); + continue; + } + break; // LCOV_EXCL_LINE + default: // LCOV_EXCL_LINE + case NO_WORD_TYPE: // LCOV_EXCL_LINE + BUG(VOCABULARY_TYPE_N_OVER_1000_NOT_BETWEEN_0_AND_3); // LCOV_EXCL_LINE + } + + switch (action(command)) { + case GO_TERMINATE: + command.state = EXECUTED; + break; + case GO_MOVE: + playermove(NUL); + command.state = EXECUTED; + break; + case GO_WORD2: #ifdef GDEBUG - printf("Word shift\n"); + printf("Word shift\n"); #endif /* GDEBUG */ - /* Get second word for analysis. */ - command.word[0] = command.word[1]; - command.word[1] = empty_command_word; - command.state = PREPROCESSED; - break; - case GO_UNKNOWN: - /* Random intransitive verbs come here. Clear obj just in case - * (see attack()). */ - command.word[0].raw[0] = toupper(command.word[0].raw[0]); - sspeak(DO_WHAT, command.word[0].raw); - command.obj = NO_OBJECT; - - /* object cleared; we need to go back to the preprocessing step */ - command.state = GIVEN; - break; - case GO_CHECKHINT: // FIXME: re-name to be more contextual; this was previously a label - command.state = GIVEN; - break; - case GO_DWARFWAKE: - /* Oh dear, he's disturbed the dwarves. */ - rspeak(DWARVES_AWAKEN); - terminate(endgame); - case GO_CLEAROBJ: // FIXME: re-name to be more contextual; this was previously a label - clear_command(&command); - break; - case GO_TOP: // FIXME: re-name to be more contextual; this was previously a label - break; - default: // LCOV_EXCL_LINE - BUG(ACTION_RETURNED_PHASE_CODE_BEYOND_END_OF_SWITCH); // LCOV_EXCL_LINE - } - } /* while command has not been fully processed */ - } /* while command is not yet given */ - } /* while command is not executed */ - - /* command completely executed; we return true. */ - return true; + /* Get second word for analysis. */ + command.word[0] = command.word[1]; + command.word[1] = empty_command_word; + command.state = PREPROCESSED; + break; + case GO_UNKNOWN: + /* Random intransitive verbs come here. + * Clear obj just in case (see + * attack()). */ + command.word[0].raw[0] = + toupper(command.word[0].raw[0]); + sspeak(DO_WHAT, command.word[0].raw); + command.obj = NO_OBJECT; + + /* object cleared; we need to go back to + * the preprocessing step */ + command.state = GIVEN; + break; + case GO_CHECKHINT: // FIXME: re-name to be more + // contextual; this was + // previously a label + command.state = GIVEN; + break; + case GO_DWARFWAKE: + /* Oh dear, he's disturbed the dwarves. + */ + rspeak(DWARVES_AWAKEN); + terminate(endgame); + case GO_CLEAROBJ: // FIXME: re-name to be more + // contextual; this was + // previously a label + clear_command(&command); + break; + case GO_TOP: // FIXME: re-name to be more + // contextual; this was previously + // a label + break; + default: // LCOV_EXCL_LINE + BUG(ACTION_RETURNED_PHASE_CODE_BEYOND_END_OF_SWITCH); // LCOV_EXCL_LINE + } + } /* while command has not been fully processed */ + } /* while command is not yet given */ + } /* while command is not executed */ + + /* command completely executed; we return true. */ + return true; } /* @@ -1306,132 +1422,140 @@ static bool do_command(void) { * Revived 2017 as Open Adventure. */ -int main(int argc, char *argv[]) -{ - int ch; +int main(int argc, char *argv[]) { + int ch; - /* Options. */ + /* Options. */ #if defined ADVENT_AUTOSAVE - const char* opts = "dl:oa:"; - const char* usage = "Usage: %s [-l logfilename] [-o] [-a filename] [script...]\n"; - FILE *rfp = NULL; - const char* autosave_filename = NULL; + const char *opts = "dl:oa:"; + const char *usage = + "Usage: %s [-l logfilename] [-o] [-a filename] [script...]\n"; + FILE *rfp = NULL; + const char *autosave_filename = NULL; #elif !defined ADVENT_NOSAVE - const char* opts = "dl:or:"; - const char* usage = "Usage: %s [-l logfilename] [-o] [-r restorefilename] [script...]\n"; - FILE *rfp = NULL; + const char *opts = "dl:or:"; + const char *usage = "Usage: %s [-l logfilename] [-o] [-r " + "restorefilename] [script...]\n"; + FILE *rfp = NULL; #else - const char* opts = "dl:o"; - const char* usage = "Usage: %s [-l logfilename] [-o] [script...]\n"; + const char *opts = "dl:o"; + const char *usage = "Usage: %s [-l logfilename] [-o] [script...]\n"; #endif - while ((ch = getopt(argc, argv, opts)) != EOF) { - switch (ch) { - case 'd': // LCOV_EXCL_LINE - settings.debug +=1; // LCOV_EXCL_LINE - break; // LCOV_EXCL_LINE - case 'l': - settings.logfp = fopen(optarg, "w"); - if (settings.logfp == NULL) { - fprintf(stderr, - "advent: can't open logfile %s for write\n", - optarg); - } - signal(SIGINT, sig_handler); - break; - case 'o': - settings.oldstyle = true; - settings.prompt = false; - break; + while ((ch = getopt(argc, argv, opts)) != EOF) { + switch (ch) { + case 'd': // LCOV_EXCL_LINE + settings.debug += 1; // LCOV_EXCL_LINE + break; // LCOV_EXCL_LINE + case 'l': + settings.logfp = fopen(optarg, "w"); + if (settings.logfp == NULL) { + fprintf( + stderr, + "advent: can't open logfile %s for write\n", + optarg); + } + signal(SIGINT, sig_handler); + break; + case 'o': + settings.oldstyle = true; + settings.prompt = false; + break; #ifdef ADVENT_AUTOSAVE - case 'a': - rfp = fopen(optarg, READ_MODE); - autosave_filename = optarg; - signal(SIGHUP, sig_handler); - signal(SIGTERM, sig_handler); - break; + case 'a': + rfp = fopen(optarg, READ_MODE); + autosave_filename = optarg; + signal(SIGHUP, sig_handler); + signal(SIGTERM, sig_handler); + break; #elif !defined ADVENT_NOSAVE - case 'r': - rfp = fopen(optarg, "r"); - if (rfp == NULL) { - fprintf(stderr, - "advent: can't open save file %s for read\n", - optarg); - } - break; + case 'r': + rfp = fopen(optarg, "r"); + if (rfp == NULL) { + fprintf(stderr, + "advent: can't open save file %s for " + "read\n", + optarg); + } + break; #endif - default: - fprintf(stderr, - usage, argv[0]); - fprintf(stderr, - " -l create a log file of your game named as specified'\n"); - fprintf(stderr, - " -o 'oldstyle' (no prompt, no command editing, displays 'Initialising...')\n"); + default: + fprintf(stderr, usage, argv[0]); + fprintf(stderr, " -l create a log file of your " + "game named as specified'\n"); + fprintf(stderr, + " -o 'oldstyle' (no prompt, no command " + "editing, displays 'Initialising...')\n"); #if defined ADVENT_AUTOSAVE - fprintf(stderr, - " -a automatic save/restore from specified saved game file\n"); + fprintf(stderr, " -a automatic save/restore " + "from specified saved game file\n"); #elif !defined ADVENT_NOSAVE - fprintf(stderr, - " -r restore from specified saved game file\n"); + fprintf(stderr, " -r restore from specified " + "saved game file\n"); #endif - exit(EXIT_FAILURE); - break; - } - } + exit(EXIT_FAILURE); + break; + } + } - /* copy invocation line part after switches */ - settings.argc = argc - optind; - settings.argv = argv + optind; - settings.optind = 0; + /* copy invocation line part after switches */ + settings.argc = argc - optind; + settings.argv = argv + optind; + settings.optind = 0; - /* Initialize game variables */ - int seedval = initialise(); + /* Initialize game variables */ + int seedval = initialise(); #if !defined ADVENT_NOSAVE - if (!rfp) { - game.novice = yes_or_no(arbitrary_messages[WELCOME_YOU], arbitrary_messages[CAVE_NEARBY], arbitrary_messages[NO_MESSAGE]); - if (game.novice) { - game.limit = NOVICELIMIT; - } - } else { - restore(rfp); + if (!rfp) { + game.novice = yes_or_no(arbitrary_messages[WELCOME_YOU], + arbitrary_messages[CAVE_NEARBY], + arbitrary_messages[NO_MESSAGE]); + if (game.novice) { + game.limit = NOVICELIMIT; + } + } else { + restore(rfp); #if defined ADVENT_AUTOSAVE - score(scoregame); + score(scoregame); #endif - } + } #if defined ADVENT_AUTOSAVE - if (autosave_filename != NULL) { - if ((autosave_fp = fopen(autosave_filename, WRITE_MODE)) == NULL) { - perror(autosave_filename); - return EXIT_FAILURE; - } - autosave(); - } + if (autosave_filename != NULL) { + if ((autosave_fp = fopen(autosave_filename, WRITE_MODE)) == + NULL) { + perror(autosave_filename); + return EXIT_FAILURE; + } + autosave(); + } #endif #else - game.novice = yes_or_no(arbitrary_messages[WELCOME_YOU], arbitrary_messages[CAVE_NEARBY], arbitrary_messages[NO_MESSAGE]); - if (game.novice) - game.limit = NOVICELIMIT; + game.novice = yes_or_no(arbitrary_messages[WELCOME_YOU], + arbitrary_messages[CAVE_NEARBY], + arbitrary_messages[NO_MESSAGE]); + if (game.novice) + game.limit = NOVICELIMIT; #endif - if (settings.logfp) { - fprintf(settings.logfp, "seed %d\n", seedval); - } - - /* interpret commands until EOF or interrupt */ - for (;;) { - // if we're supposed to move, move - if (!do_move()) { - continue; + if (settings.logfp) { + fprintf(settings.logfp, "seed %d\n", seedval); } - // get command - if (!do_command()) { - break; + /* interpret commands until EOF or interrupt */ + for (;;) { + // if we're supposed to move, move + if (!do_move()) { + continue; + } + + // get command + if (!do_command()) { + break; + } } - } - /* show score and exit */ - terminate(quitgame); + /* show score and exit */ + terminate(quitgame); } /* end */ diff --git a/misc.c b/misc.c index c853237..ad110d9 100644 --- a/misc.c +++ b/misc.c @@ -5,746 +5,754 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include -#include -#include -#include -#include -#include #include #include #include +#include +#include +#include +#include +#include +#include #include "advent.h" #include "dungeon.h" -static void* xcalloc(size_t size) -{ - void* ptr = calloc(size, 1); - 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); - // LCOV_EXCL_STOP - } - return (ptr); +static void *xcalloc(size_t size) { + void *ptr = calloc(size, 1); + 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); + // LCOV_EXCL_STOP + } + return (ptr); } /* I/O routines (speak, pspeak, rspeak, sspeak, get_input, yes) */ -static void vspeak(const char* msg, bool blank, va_list ap) -/* Engine for various speak functions */ -{ - // Do nothing if we got a null pointer. - if (msg == NULL) - return; - - // Do nothing if we got an empty string. - if (strlen(msg) == 0) - return; - - if (blank == true) - printf("\n"); - - int msglen = strlen(msg); - - // Rendered string - ssize_t size = 2000; /* msglen > 50 ? msglen*2 : 100; */ - char* rendered = xcalloc(size); - char* renderp = rendered; - - // Handle format specifiers (including the custom %S) by - // adjusting the parameter accordingly, and replacing the - // specifier with %s. - bool pluralize = false; - for (int i = 0; i < msglen; i++) { - if (msg[i] != '%') { - /* Ugh. Least obtrusive way to deal with artifacts "on the floor" - * being dropped outside of both cave and building. */ - if (strncmp(msg + i, "floor", 5) == 0 && strchr(" .", msg[i + 5]) && !INSIDE(game.loc)) { - strcpy(renderp, "ground"); - renderp += 6; - i += 4; - size -= 5; - } else { - *renderp++ = msg[i]; - size--; - } - } else { - i++; - // Integer specifier. - if (msg[i] == 'd') { - int32_t arg = va_arg(ap, int32_t); - int ret = snprintf(renderp, size, "%" PRId32, arg); - if (ret < size) { - renderp += ret; - size -= ret; - } - pluralize = (arg != 1); - } - - // Unmodified string specifier. - if (msg[i] == 's') { - char *arg = va_arg(ap, char *); - strncat(renderp, arg, size - 1); - size_t len = strlen(renderp); - renderp += len; - size -= len; - } - - // Singular/plural specifier. - if (msg[i] == 'S') { - // look at the *previous* numeric parameter - if (pluralize) { - *renderp++ = 's'; - size--; - } - } - - // LCOV_EXCL_START - doesn't occur in test suite. - /* Version specifier */ - if (msg[i] == 'V') { - strcpy(renderp, VERSION); - size_t len = strlen(VERSION); - renderp += len; - size -= len; - } - // LCOV_EXCL_STOP - } - } - *renderp = 0; - - // Print the message. - printf("%s\n", rendered); - - free(rendered); -} - -void speak(const char* msg, ...) -/* speak a specified string */ -{ - va_list ap; - va_start(ap, msg); - vspeak(msg, true, ap); - va_end(ap); -} - -void sspeak(const int msg, ...) -/* Speak a message from the arbitrary-messages list */ -{ - va_list ap; - va_start(ap, msg); - fputc('\n', stdout); - vprintf(arbitrary_messages[msg], ap); - fputc('\n', stdout); - va_end(ap); -} - -void pspeak(vocab_t msg, enum speaktype mode, bool blank, int skip, ...) -/* Find the skip+1st message from msg and print it. Modes are: - * feel = for inventory, what you can touch - * look = the full description for the state the object is in - * listen = the sound for the state the object is in - * study = text on the object. */ -{ - va_list ap; - va_start(ap, skip); - switch (mode) { - case touch: - vspeak(objects[msg].inventory, blank, ap); - break; - case look: - vspeak(objects[msg].descriptions[skip], blank, ap); - break; - case hear: - vspeak(objects[msg].sounds[skip], blank, ap); - break; - case study: - vspeak(objects[msg].texts[skip], blank, ap); - break; - case change: - vspeak(objects[msg].changes[skip], blank, ap); - break; - } - va_end(ap); -} - -void rspeak(vocab_t i, ...) -/* Print the i-th "random" message (section 6 of database). */ -{ - va_list ap; - va_start(ap, i); - vspeak(arbitrary_messages[i], true, ap); - va_end(ap); -} - -void echo_input(FILE* destination, const char* input_prompt, const char* input) -{ - size_t len = strlen(input_prompt) + strlen(input) + 1; - char* prompt_and_input = (char*) xcalloc(len); - strcpy(prompt_and_input, input_prompt); - strcat(prompt_and_input, input); - fprintf(destination, "%s\n", prompt_and_input); - free(prompt_and_input); -} - -static int word_count(char* str) -{ - char delims[] = " \t"; - int count = 0; - int inblanks = true; - - for (char *s = str; *s; s++) - if (inblanks) { - if (strchr(delims, *s) == 0) { - ++count; - inblanks = false; - } - } else { - if (strchr(delims, *s) != 0) { - inblanks = true; - } - } - - return (count); -} - -static char* get_input(void) -{ - // Set up the prompt - char input_prompt[] = PROMPT; - if (!settings.prompt) - input_prompt[0] = '\0'; - - // Print a blank line - printf("\n"); - - char* input; - for (;;) { - input = myreadline(input_prompt); - - if (input == NULL) // Got EOF; return with it. - return (input); - if (input[0] == '#') { // Ignore comments. - free(input); - continue; - } - // We have a 'normal' line; leave the loop. - break; - } - - // Strip trailing newlines from the input - input[strcspn(input, "\n")] = 0; - - add_history(input); - - if (!isatty(0)) - echo_input(stdout, input_prompt, input); - - if (settings.logfp) - echo_input(settings.logfp, "", input); - - return (input); -} - -bool silent_yes_or_no(void) -{ - bool outcome = false; - - for (;;) { - char* reply = get_input(); - if (reply == NULL) { - // LCOV_EXCL_START - // Should be unreachable. Reply should never be NULL - free(reply); - exit(EXIT_SUCCESS); - // LCOV_EXCL_STOP - } - if (strlen(reply) == 0) { - free(reply); - rspeak(PLEASE_ANSWER); - continue; - } - - char* firstword = (char*) xcalloc(strlen(reply) + 1); - sscanf(reply, "%s", firstword); - - free(reply); - - 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); - } - return (outcome); -} - - -bool yes_or_no(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. */ -{ - bool outcome = false; - - for (;;) { - speak(question); - - char* reply = get_input(); - if (reply == NULL) { - // LCOV_EXCL_START - // Should be unreachable. Reply should never be NULL - free(reply); - exit(EXIT_SUCCESS); - // LCOV_EXCL_STOP - } - - if (strlen(reply) == 0) { - free(reply); - rspeak(PLEASE_ANSWER); - continue; - } - - char* firstword = (char*) xcalloc(strlen(reply) + 1); - sscanf(reply, "%s", firstword); - - free(reply); - - 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) { - speak(yes_response); - outcome = true; - break; - } else if (no == 0 || n == 0) { - speak(no_response); - outcome = false; - break; - } else - rspeak(PLEASE_ANSWER); - - } - - return (outcome); -} - -/* Data structure routines */ - -static 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 (strncasecmp(word, motions[i].words.strs[j], TOKLEN) == 0 && (strlen(word) > 1 || - strchr(ignore, word[0]) == NULL || - !settings.oldstyle)) - return (i); - } - } - // If execution reaches here, we didn't find the word. - return (WORD_NOT_FOUND); -} - -static 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 (strncasecmp(word, objects[i].words.strs[j], TOKLEN) == 0) - return (i); - } - } - // If execution reaches here, we didn't find the word. - return (WORD_NOT_FOUND); -} - -static 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 (strncasecmp(word, actions[i].words.strs[j], TOKLEN) == 0 && (strlen(word) > 1 || - strchr(ignore, word[0]) == NULL || - !settings.oldstyle)) - return (i); - } - } - // If execution reaches here, we didn't find the word. - return (WORD_NOT_FOUND); -} - -static bool is_valid_int(const char *str) -/* Returns true if the string passed in is represents a valid integer, - * that could then be parsed by atoi() */ -{ - // Handle negative number - if (*str == '-') - ++str; - - // Handle empty string or just "-". Should never reach this - // point, because this is only used with transitive verbs. - if (!*str) - return false; // LCOV_EXCL_LINE - - // Check for non-digit chars in the rest of the string. - while (*str) { - if (!isdigit(*str)) - return false; - else - ++str; - } - - return true; -} - -static void get_vocab_metadata(const char* word, vocab_t* id, word_type_t* type) -{ - /* Check for an empty string */ - if (strncmp(word, "", sizeof("")) == 0) { - *id = WORD_EMPTY; - *type = NO_WORD_TYPE; - return; - } - - vocab_t ref_num; - - ref_num = get_motion_vocab_id(word); - // Second conjunct is because the magic-word placeholder is a bit special - if (ref_num != WORD_NOT_FOUND) { - *id = ref_num; - *type = MOTION; - return; - } - - ref_num = get_object_vocab_id(word); - if (ref_num != WORD_NOT_FOUND) { - *id = ref_num; - *type = OBJECT; - return; - } - - ref_num = get_action_vocab_id(word); - if (ref_num != WORD_NOT_FOUND && ref_num != PART) { - *id = ref_num; - *type = ACTION; - return; - } - - // Check for the reservoir magic word. - if (strcasecmp(word, game.zzword) == 0) { - *id = PART; - *type = ACTION; - return; - } - - // Check words that are actually numbers. - if (is_valid_int(word)) { - *id = WORD_EMPTY; - *type = NUMERIC; - return; - } - - *id = WORD_NOT_FOUND; - *type = NO_WORD_TYPE; - return; -} - -static void tokenize(char* raw, command_t *cmd) -{ - /* - * Be careful about modifying this. We do not want to nuke the - * the speech part or ID from the previous turn. - */ - memset(&cmd->word[0].raw, '\0', sizeof(cmd->word[0].raw)); - 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. */ - sscanf(raw, "%s%s", cmd->word[0].raw, cmd->word[1].raw); - - /* (ESR) In oldstyle mode, simulate the uppercasing and truncating - * effect on raw tokens of packing them into sixbit characters, 5 - * to a 32-bit word. This is something the FORTRAN version did - * because archaic FORTRAN had no string types. Don Wood's - * mechanical translation of 2.5 to C retained the packing and - * thus this misfeature. - * - * It's philosophically questionable whether this is the right - * thing to do even in oldstyle mode. On one hand, the text - * mangling was not authorial intent, but a result of limitations - * 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. - */ - if (settings.oldstyle) { - cmd->word[0].raw[TOKLEN + TOKLEN] = cmd->word[1].raw[TOKLEN + TOKLEN] = '\0'; - for (size_t i = 0; i < strlen(cmd->word[0].raw); i++) - cmd->word[0].raw[i] = toupper(cmd->word[0].raw[i]); - for (size_t i = 0; i < strlen(cmd->word[1].raw); i++) - cmd->word[1].raw[i] = toupper(cmd->word[1].raw[i]); - } - - /* populate command with parsed vocabulary metadata */ - get_vocab_metadata(cmd->word[0].raw, &(cmd->word[0].id), &(cmd->word[0].type)); - get_vocab_metadata(cmd->word[1].raw, &(cmd->word[1].id), &(cmd->word[1].type)); - cmd->state = TOKENIZED; -} - -bool get_command_input(command_t *command) -/* Get user input on stdin, parse and map to command */ -{ - char inputbuf[LINESIZE]; - char* input; - - for (;;) { - input = get_input(); - if (input == NULL) - return false; - if (word_count(input) > 2) { - rspeak(TWO_WORDS); - free(input); - continue; - } - if (strcmp(input, "") != 0) - break; - free(input); - } - - strncpy(inputbuf, input, LINESIZE - 1); - free(input); - - tokenize(inputbuf, command); +static void vspeak(const char *msg, bool blank, va_list ap) { + /* Engine for various speak functions */ + // Do nothing if we got a null pointer. + if (msg == NULL) { + return; + } + + // Do nothing if we got an empty string. + if (strlen(msg) == 0) { + return; + } + + if (blank == true) { + printf("\n"); + } + + int msglen = strlen(msg); + + // Rendered string + ssize_t size = 2000; /* msglen > 50 ? msglen*2 : 100; */ + char *rendered = xcalloc(size); + char *renderp = rendered; + + // Handle format specifiers (including the custom %S) by + // adjusting the parameter accordingly, and replacing the + // specifier with %s. + bool pluralize = false; + for (int i = 0; i < msglen; i++) { + if (msg[i] != '%') { + /* Ugh. Least obtrusive way to deal with artifacts "on + * the floor" being dropped outside of both cave and + * building. */ + if (strncmp(msg + i, "floor", 5) == 0 && + strchr(" .", msg[i + 5]) && !INSIDE(game.loc)) { + strcpy(renderp, "ground"); + renderp += 6; + i += 4; + size -= 5; + } else { + *renderp++ = msg[i]; + size--; + } + } else { + i++; + // Integer specifier. + if (msg[i] == 'd') { + int32_t arg = va_arg(ap, int32_t); + int ret = + snprintf(renderp, size, "%" PRId32, arg); + if (ret < size) { + renderp += ret; + size -= ret; + } + pluralize = (arg != 1); + } + + // Unmodified string specifier. + if (msg[i] == 's') { + char *arg = va_arg(ap, char *); + strncat(renderp, arg, size - 1); + size_t len = strlen(renderp); + renderp += len; + size -= len; + } + + // Singular/plural specifier. + if (msg[i] == 'S') { + // look at the *previous* numeric parameter + if (pluralize) { + *renderp++ = 's'; + size--; + } + } + + // LCOV_EXCL_START - doesn't occur in test suite. + /* Version specifier */ + if (msg[i] == 'V') { + strcpy(renderp, VERSION); + size_t len = strlen(VERSION); + renderp += len; + size -= len; + } + // LCOV_EXCL_STOP + } + } + *renderp = 0; + + // Print the message. + printf("%s\n", rendered); + + free(rendered); +} + +void speak(const char *msg, ...) { + /* speak a specified string */ + va_list ap; + va_start(ap, msg); + vspeak(msg, true, ap); + va_end(ap); +} + +void sspeak(const int msg, ...) { + /* Speak a message from the arbitrary-messages list */ + va_list ap; + va_start(ap, msg); + fputc('\n', stdout); + vprintf(arbitrary_messages[msg], ap); + fputc('\n', stdout); + va_end(ap); +} + +void pspeak(vocab_t msg, enum speaktype mode, bool blank, int skip, ...) { + /* Find the skip+1st message from msg and print it. Modes are: + * feel = for inventory, what you can touch + * look = the full description for the state the object is in + * listen = the sound for the state the object is in + * study = text on the object. */ + va_list ap; + va_start(ap, skip); + switch (mode) { + case touch: + vspeak(objects[msg].inventory, blank, ap); + break; + case look: + vspeak(objects[msg].descriptions[skip], blank, ap); + break; + case hear: + vspeak(objects[msg].sounds[skip], blank, ap); + break; + case study: + vspeak(objects[msg].texts[skip], blank, ap); + break; + case change: + vspeak(objects[msg].changes[skip], blank, ap); + break; + } + va_end(ap); +} + +void rspeak(vocab_t i, ...) { + /* Print the i-th "random" message (section 6 of database). */ + va_list ap; + va_start(ap, i); + vspeak(arbitrary_messages[i], true, ap); + va_end(ap); +} + +void echo_input(FILE *destination, const char *input_prompt, + const char *input) { + size_t len = strlen(input_prompt) + strlen(input) + 1; + char *prompt_and_input = (char *)xcalloc(len); + strcpy(prompt_and_input, input_prompt); + strcat(prompt_and_input, input); + fprintf(destination, "%s\n", prompt_and_input); + free(prompt_and_input); +} + +static int word_count(char *str) { + char delims[] = " \t"; + int count = 0; + int inblanks = true; + + for (char *s = str; *s; s++) + if (inblanks) { + if (strchr(delims, *s) == 0) { + ++count; + inblanks = false; + } + } else { + if (strchr(delims, *s) != 0) { + inblanks = true; + } + } + + return (count); +} + +static char *get_input(void) { + // Set up the prompt + char input_prompt[] = PROMPT; + if (!settings.prompt) + input_prompt[0] = '\0'; + + // Print a blank line + printf("\n"); + + char *input; + for (;;) { + input = myreadline(input_prompt); + + if (input == NULL) // Got EOF; return with it. + return (input); + if (input[0] == '#') { // Ignore comments. + free(input); + continue; + } + // We have a 'normal' line; leave the loop. + break; + } + + // Strip trailing newlines from the input + input[strcspn(input, "\n")] = 0; + + add_history(input); + + if (!isatty(0)) + echo_input(stdout, input_prompt, input); + + if (settings.logfp) + echo_input(settings.logfp, "", input); + + return (input); +} + +bool silent_yes_or_no(void) { + bool outcome = false; + + for (;;) { + char *reply = get_input(); + if (reply == NULL) { + // LCOV_EXCL_START + // Should be unreachable. Reply should never be NULL + free(reply); + exit(EXIT_SUCCESS); + // LCOV_EXCL_STOP + } + if (strlen(reply) == 0) { + free(reply); + rspeak(PLEASE_ANSWER); + continue; + } + + char *firstword = (char *)xcalloc(strlen(reply) + 1); + sscanf(reply, "%s", firstword); + + free(reply); + + 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); + } + return (outcome); +} + +bool yes_or_no(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. */ + bool outcome = false; + + for (;;) { + speak(question); + + char *reply = get_input(); + if (reply == NULL) { + // LCOV_EXCL_START + // Should be unreachable. Reply should never be NULL + free(reply); + exit(EXIT_SUCCESS); + // LCOV_EXCL_STOP + } + + if (strlen(reply) == 0) { + free(reply); + rspeak(PLEASE_ANSWER); + continue; + } + + char *firstword = (char *)xcalloc(strlen(reply) + 1); + sscanf(reply, "%s", firstword); + + free(reply); + + 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) { + speak(yes_response); + outcome = true; + break; + } else if (no == 0 || n == 0) { + speak(no_response); + outcome = false; + break; + } else + rspeak(PLEASE_ANSWER); + } + + return (outcome); +} + +/* Data structure routines */ + +static 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 (strncasecmp(word, motions[i].words.strs[j], + TOKLEN) == 0 && + (strlen(word) > 1 || + strchr(ignore, word[0]) == NULL || + !settings.oldstyle)) + return (i); + } + } + // If execution reaches here, we didn't find the word. + return (WORD_NOT_FOUND); +} + +static 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 (strncasecmp(word, objects[i].words.strs[j], + TOKLEN) == 0) + return (i); + } + } + // If execution reaches here, we didn't find the word. + return (WORD_NOT_FOUND); +} + +static 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 (strncasecmp(word, actions[i].words.strs[j], + TOKLEN) == 0 && + (strlen(word) > 1 || + strchr(ignore, word[0]) == NULL || + !settings.oldstyle)) { + return (i); + } + } + } + // If execution reaches here, we didn't find the word. + return (WORD_NOT_FOUND); +} + +static bool is_valid_int(const char *str) { + /* Returns true if the string passed in is represents a valid integer, + * that could then be parsed by atoi() */ + // Handle negative number + if (*str == '-') { + ++str; + } + + // Handle empty string or just "-". Should never reach this + // point, because this is only used with transitive verbs. + if (!*str) { + return false; // LCOV_EXCL_LINE + } + + // Check for non-digit chars in the rest of the string. + while (*str) { + if (!isdigit(*str)) { + return false; + } else { + ++str; + } + } + + return true; +} + +static void get_vocab_metadata(const char *word, vocab_t *id, + word_type_t *type) { + /* Check for an empty string */ + if (strncmp(word, "", sizeof("")) == 0) { + *id = WORD_EMPTY; + *type = NO_WORD_TYPE; + return; + } + + vocab_t ref_num; + + ref_num = get_motion_vocab_id(word); + // Second conjunct is because the magic-word placeholder is a bit + // special + if (ref_num != WORD_NOT_FOUND) { + *id = ref_num; + *type = MOTION; + return; + } + + ref_num = get_object_vocab_id(word); + if (ref_num != WORD_NOT_FOUND) { + *id = ref_num; + *type = OBJECT; + return; + } + + ref_num = get_action_vocab_id(word); + if (ref_num != WORD_NOT_FOUND && ref_num != PART) { + *id = ref_num; + *type = ACTION; + return; + } + + // Check for the reservoir magic word. + if (strcasecmp(word, game.zzword) == 0) { + *id = PART; + *type = ACTION; + return; + } + + // Check words that are actually numbers. + if (is_valid_int(word)) { + *id = WORD_EMPTY; + *type = NUMERIC; + return; + } + + *id = WORD_NOT_FOUND; + *type = NO_WORD_TYPE; + return; +} + +static void tokenize(char *raw, command_t *cmd) { + /* + * Be careful about modifying this. We do not want to nuke the + * the speech part or ID from the previous turn. + */ + memset(&cmd->word[0].raw, '\0', sizeof(cmd->word[0].raw)); + 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. */ + sscanf(raw, "%s%s", cmd->word[0].raw, cmd->word[1].raw); + + /* (ESR) In oldstyle mode, simulate the uppercasing and truncating + * effect on raw tokens of packing them into sixbit characters, 5 + * to a 32-bit word. This is something the FORTRAN version did + * because archaic FORTRAN had no string types. Don Wood's + * mechanical translation of 2.5 to C retained the packing and + * thus this misfeature. + * + * It's philosophically questionable whether this is the right + * thing to do even in oldstyle mode. On one hand, the text + * mangling was not authorial intent, but a result of limitations + * 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. + */ + if (settings.oldstyle) { + cmd->word[0].raw[TOKLEN + TOKLEN] = + cmd->word[1].raw[TOKLEN + TOKLEN] = '\0'; + for (size_t i = 0; i < strlen(cmd->word[0].raw); i++) { + cmd->word[0].raw[i] = toupper(cmd->word[0].raw[i]); + } + for (size_t i = 0; i < strlen(cmd->word[1].raw); i++) { + cmd->word[1].raw[i] = toupper(cmd->word[1].raw[i]); + } + } + + /* populate command with parsed vocabulary metadata */ + get_vocab_metadata(cmd->word[0].raw, &(cmd->word[0].id), + &(cmd->word[0].type)); + get_vocab_metadata(cmd->word[1].raw, &(cmd->word[1].id), + &(cmd->word[1].type)); + cmd->state = TOKENIZED; +} + +bool get_command_input(command_t *command) { + /* Get user input on stdin, parse and map to command */ + char inputbuf[LINESIZE]; + char *input; + + for (;;) { + input = get_input(); + if (input == NULL) + return false; + if (word_count(input) > 2) { + rspeak(TWO_WORDS); + free(input); + continue; + } + if (strcmp(input, "") != 0) { + break; + } + free(input); + } + + strncpy(inputbuf, input, LINESIZE - 1); + free(input); + + tokenize(inputbuf, command); #ifdef GDEBUG - /* Needs to stay synced with enum word_type_t */ - const char *types[] = {"NO_WORD_TYPE", "MOTION", "OBJECT", "ACTION", "NUMERIC"}; - /* needs to stay synced with enum speechpart */ - const char *roles[] = {"unknown", "intransitive", "transitive"}; - printf("Command: role = %s type1 = %s, id1 = %d, type2 = %s, id2 = %d\n", - roles[command->part], - types[command->word[0].type], - command->word[0].id, - types[command->word[1].type], - command->word[1].id); + /* Needs to stay synced with enum word_type_t */ + const char *types[] = {"NO_WORD_TYPE", "MOTION", "OBJECT", "ACTION", + "NUMERIC"}; + /* needs to stay synced with enum speechpart */ + const char *roles[] = {"unknown", "intransitive", "transitive"}; + printf( + "Command: role = %s type1 = %s, id1 = %d, type2 = %s, id2 = %d\n", + roles[command->part], types[command->word[0].type], + command->word[0].id, types[command->word[1].type], + command->word[1].id); #endif - command->state = GIVEN; - return true; -} - -void clear_command(command_t *cmd) -/* Resets the state of the command to empty */ -{ - cmd->verb = ACT_NULL; - cmd->part = unknown; - game.oldobj = cmd->obj; - cmd->obj = NO_OBJECT; - cmd->state = EMPTY; -} - -void juggle(obj_t 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. */ -{ - loc_t i, j; - - i = game.objects[object].place; - j = game.objects[object].fixed; - move(object, i); - move(object + NOBJECTS, j); -} - -void move(obj_t object, loc_t where) -/* Place any object anywhere by picking it up and dropping it. May - * already be toting, in which case the carry is a no-op. Mustn't - * pick up objects which are not at any loc, since carry wants to - * remove objects from game atloc chains. */ -{ - loc_t from; - - if (object > NOBJECTS) - from = game.objects[object - NOBJECTS].fixed; - else - from = game.objects[object].place; - /* (ESR) Used to check for !SPECIAL(from). I *think* that was wrong... */ - if (from != LOC_NOWHERE && from != CARRIED) - carry(object, from); - drop(object, 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. */ -{ - 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); -#endif + command->state = GIVEN; + return true; } -void carry(obj_t object, loc_t where) -/* Start toting an object, removing it from the list of things at its former - * location. Incr holdng unless it was already being toted. If object>NOBJECTS - * (moving "fixed" second loc), don't change game.place or game.holdng. */ -{ - int temp; +void clear_command(command_t *cmd) { + /* Resets the state of the command to empty */ + cmd->verb = ACT_NULL; + cmd->part = unknown; + game.oldobj = cmd->obj; + cmd->obj = NO_OBJECT; + cmd->state = EMPTY; +} - if (object <= NOBJECTS) { - if (game.objects[object].place == CARRIED) - return; - game.objects[object].place = CARRIED; +void juggle(obj_t 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. */ + loc_t i, j; - /* - * Without this conditional your inventory is overcounted - * when you pick up the bird while it's caged. This fixes - * a cosmetic bug in the original. - * - * Possibly this check should be skipped whwn oldstyle is on. + i = game.objects[object].place; + j = game.objects[object].fixed; + move(object, i); + move(object + NOBJECTS, j); +} + +void move(obj_t object, loc_t where) { + /* Place any object anywhere by picking it up and dropping it. May + * already be toting, in which case the carry is a no-op. Mustn't + * pick up objects which are not at any loc, since carry wants to + * remove objects from game atloc chains. */ + loc_t from; + + if (object > NOBJECTS) { + from = game.objects[object - NOBJECTS].fixed; + } else { + from = game.objects[object].place; + } + /* (ESR) Used to check for !SPECIAL(from). I *think* that was wrong... */ - if (object != BIRD) - ++game.holdng; - } - if (game.locs[where].atloc == object) { - game.locs[where].atloc = game.link[object]; - return; - } - temp = game.locs[where].atloc; - while (game.link[temp] != object) { - temp = game.link[temp]; - } - game.link[temp] = game.link[object]; -} - -void drop(obj_t object, loc_t where) -/* Place an object at a given loc, prefixing it onto the game atloc list. Decr - * game.holdng if the object was being toted. No state change on the object. */ -{ - if (object > NOBJECTS) - game.objects[object - NOBJECTS].fixed = where; - else { - if (game.objects[object].place == CARRIED) - if (object != BIRD) - /* The bird has to be weightless. This ugly hack (and the - * corresponding code in the carry function) brought to you - * by the fact that when the bird is caged, we need to be able - * to either 'take bird' or 'take cage' and have the right thing - * happen. - */ - --game.holdng; - game.objects[object].place = where; - } - if (where == LOC_NOWHERE || where == CARRIED) - return; - game.link[object] = game.locs[where].atloc; - game.locs[where].atloc = object; -} - -int atdwrf(loc_t where) -/* Return the index of first dwarf at the given location, zero if no dwarf is - * there (or if dwarves not active yet), -1 if all dwarves are dead. Ignore - * the pirate (6th dwarf). */ -{ - int at; - - at = 0; - if (game.dflag < 2) - return at; - at = -1; - for (int i = 1; i <= NDWARVES - 1; i++) { - if (game.dwarves[i].loc == where) - return i; - if (game.dwarves[i].loc != 0) - at = 0; - } - return at; + if (from != LOC_NOWHERE && from != CARRIED) { + carry(object, from); + } + drop(object, 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. */ + 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); +#endif +} + +void carry(obj_t object, loc_t where) { + /* Start toting an object, removing it from the list of things at its + * former location. Incr holdng unless it was already being toted. If + * object>NOBJECTS (moving "fixed" second loc), don't change game.place + * or game.holdng. */ + int temp; + + if (object <= NOBJECTS) { + if (game.objects[object].place == CARRIED) { + return; + } + game.objects[object].place = CARRIED; + + /* + * Without this conditional your inventory is overcounted + * when you pick up the bird while it's caged. This fixes + * a cosmetic bug in the original. + * + * Possibly this check should be skipped whwn oldstyle is on. + */ + if (object != BIRD) + ++game.holdng; + } + if (game.locs[where].atloc == object) { + game.locs[where].atloc = game.link[object]; + return; + } + temp = game.locs[where].atloc; + while (game.link[temp] != object) { + temp = game.link[temp]; + } + game.link[temp] = game.link[object]; +} + +void drop(obj_t object, loc_t where) { + /* Place an object at a given loc, prefixing it onto the game atloc + * list. Decr game.holdng if the object was being toted. No state + * change on the object. */ + if (object > NOBJECTS) { + game.objects[object - NOBJECTS].fixed = where; + } else { + if (game.objects[object].place == CARRIED) + if (object != BIRD) + /* The bird has to be weightless. This ugly + * hack (and the corresponding code in the carry + * function) brought to you by the fact that + * when the bird is caged, we need to be able to + * either 'take bird' or 'take cage' and have + * the right thing happen. + */ + --game.holdng; + game.objects[object].place = where; + } + if (where == LOC_NOWHERE || where == CARRIED) { + return; + } + game.link[object] = game.locs[where].atloc; + game.locs[where].atloc = object; +} + +int atdwrf(loc_t where) { + /* Return the index of first dwarf at the given location, zero if no + * dwarf is there (or if dwarves not active yet), -1 if all dwarves are + * dead. Ignore the pirate (6th dwarf). */ + int at; + + at = 0; + if (game.dflag < 2) { + return at; + } + at = -1; + for (int i = 1; i <= NDWARVES - 1; i++) { + if (game.dwarves[i].loc == where) { + return i; + } + if (game.dwarves[i].loc != 0) { + at = 0; + } + } + return at; } /* Utility routines (setbit, tstbit, set_seed, get_next_lcg_value, * randrange) */ -int setbit(int bit) -/* Returns 2**bit for use in constructing bit-masks. */ -{ - return (1L << bit); +int setbit(int bit) { + /* Returns 2**bit for use in constructing bit-masks. */ + return (1L << bit); } -bool tstbit(int mask, int bit) -/* Returns true if the specified bit is set in the mask. */ -{ - return (mask & (1 << bit)) != 0; +bool tstbit(int mask, int bit) { + /* Returns true if the specified bit is set in the mask. */ + return (mask & (1 << bit)) != 0; } -void set_seed(int32_t seedval) -/* Set the LCG1 seed */ -{ - game.lcg_x = seedval % LCG_M; - if (game.lcg_x < 0) { - game.lcg_x = LCG_M + game.lcg_x; - } - // once seed is set, we need to generate the Z`ZZZ word - for (int i = 0; i < 5; ++i) { - game.zzword[i] = 'A' + randrange(26); - } - game.zzword[1] = '\''; // force second char to apostrophe - game.zzword[5] = '\0'; +void set_seed(int32_t seedval) { + /* Set the LCG1 seed */ + game.lcg_x = seedval % LCG_M; + if (game.lcg_x < 0) { + game.lcg_x = LCG_M + game.lcg_x; + } + // once seed is set, we need to generate the Z`ZZZ word + for (int i = 0; i < 5; ++i) { + game.zzword[i] = 'A' + randrange(26); + } + game.zzword[1] = '\''; // force second char to apostrophe + game.zzword[5] = '\0'; } -static int32_t get_next_lcg_value(void) -/* Return the LCG's current value, and then iterate it. */ -{ - int32_t old_x = game.lcg_x; - game.lcg_x = (LCG_A * game.lcg_x + LCG_C) % LCG_M; - if (settings.debug) { - printf("# random %d\n", old_x); // LCOV_EXCL_LINE - } - return old_x; +static int32_t get_next_lcg_value(void) { + /* Return the LCG's current value, and then iterate it. */ + int32_t old_x = game.lcg_x; + game.lcg_x = (LCG_A * game.lcg_x + LCG_C) % LCG_M; + if (settings.debug) { + printf("# random %d\n", old_x); // LCOV_EXCL_LINE + } + return old_x; } -int32_t randrange(int32_t range) -/* Return a random integer from [0, range). */ -{ - return range * get_next_lcg_value() / LCG_M; +int32_t randrange(int32_t range) { + /* Return a random integer from [0, range). */ + return range * get_next_lcg_value() / LCG_M; } // LCOV_EXCL_START -void bug(enum bugtype num, const char *error_string) -{ - fprintf(stderr, "Fatal error %d, %s.\n", num, error_string); - exit(EXIT_FAILURE); +void bug(enum bugtype num, const char *error_string) { + fprintf(stderr, "Fatal error %d, %s.\n", num, error_string); + exit(EXIT_FAILURE); } // LCOV_EXCL_STOP -void state_change(obj_t obj, int state) -/* Object must have a change-message list for this to be useful; only some do */ -{ - game.objects[obj].prop = state; - pspeak(obj, change, true, state); +void state_change(obj_t obj, int state) { + /* Object must have a change-message list for this to be useful; only + * some do */ + game.objects[obj].prop = state; + pspeak(obj, change, true, state); } /* end */ diff --git a/saveresume.c b/saveresume.c index 34404c6..ea30e4d 100644 --- a/saveresume.c +++ b/saveresume.c @@ -8,243 +8,263 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include +#include #include #include -#include #include -#include #include "advent.h" /* * Use this to detect endianness mismatch. Can't be unchanged by byte-swapping. */ -#define ENDIAN_MAGIC 2317 +#define ENDIAN_MAGIC 2317 struct save_t save; -#define IGNORE(r) do{if (r){}}while(0) - -int savefile(FILE *fp) -/* Save game to file. No input or output from user. */ -{ - memcpy(&save.magic, ADVENT_MAGIC, sizeof(ADVENT_MAGIC)); - if (save.version == 0) - save.version = SAVE_VERSION; - if (save.canary == 0) - save.canary = ENDIAN_MAGIC; - - save.game = game; - IGNORE(fwrite(&save, sizeof(struct save_t), 1, fp)); - return (0); +#define IGNORE(r) \ + do { \ + if (r) { \ + } \ + } while (0) + +int savefile(FILE *fp) { + /* Save game to file. No input or output from user. */ + memcpy(&save.magic, ADVENT_MAGIC, sizeof(ADVENT_MAGIC)); + if (save.version == 0) { + save.version = SAVE_VERSION; + } + if (save.canary == 0) { + save.canary = ENDIAN_MAGIC; + } + save.game = game; + IGNORE(fwrite(&save, sizeof(struct save_t), 1, fp)); + return (0); } /* Suspend and resume */ -static char *strip(char *name) -{ - // Trim leading whitespace - while(isspace((unsigned char)*name)) - name++; // LCOV_EXCL_LINE - if(*name != '\0') { - // Trim trailing whitespace; - // might be left there by autocomplete - char *end = name + strlen(name) - 1; - while(end > name && isspace((unsigned char)*end)) - end--; - // Write new null terminator character - end[1] = '\0'; - } - - return name; +static char *strip(char *name) { + // Trim leading whitespace + while (isspace((unsigned char)*name)) { + name++; // LCOV_EXCL_LINE + } + if (*name != '\0') { + // Trim trailing whitespace; + // might be left there by autocomplete + char *end = name + strlen(name) - 1; + while (end > name && isspace((unsigned char)*end)) { + end--; + } + // Write new null terminator character + end[1] = '\0'; + } + + return name; } -int suspend(void) -{ - /* Suspend. Offer to save things in a file, but charging - * some points (so can't win by using saved games to retry - * battles or to start over after learning zzword). - * If ADVENT_NOSAVE is defined, gripe instead. */ +int suspend(void) { + /* Suspend. Offer to save things in a file, but charging + * some points (so can't win by using saved games to retry + * battles or to start over after learning zzword). + * If ADVENT_NOSAVE is defined, gripe instead. */ #if defined ADVENT_NOSAVE || defined ADVENT_AUTOSAVE - rspeak(SAVERESUME_DISABLED); - return GO_TOP; + rspeak(SAVERESUME_DISABLED); + return GO_TOP; #endif - FILE *fp = NULL; - - rspeak(SUSPEND_WARNING); - if (!yes_or_no(arbitrary_messages[THIS_ACCEPTABLE], arbitrary_messages[OK_MAN], arbitrary_messages[OK_MAN])) - return GO_CLEAROBJ; - game.saved = game.saved + 5; - - while (fp == NULL) { - char* name = myreadline("\nFile name: "); - if (name == NULL) - return GO_TOP; - name = strip(name); - if (strlen(name) == 0) - return GO_TOP; // LCOV_EXCL_LINE - fp = fopen(strip(name), WRITE_MODE); - if (fp == NULL) - printf("Can't open file %s, try again.\n", name); - free(name); - } - - savefile(fp); - fclose(fp); - rspeak(RESUME_HELP); - exit(EXIT_SUCCESS); + FILE *fp = NULL; + + rspeak(SUSPEND_WARNING); + if (!yes_or_no(arbitrary_messages[THIS_ACCEPTABLE], + arbitrary_messages[OK_MAN], + arbitrary_messages[OK_MAN])) { + return GO_CLEAROBJ; + } + game.saved = game.saved + 5; + + while (fp == NULL) { + char *name = myreadline("\nFile name: "); + if (name == NULL) { + return GO_TOP; + } + name = strip(name); + if (strlen(name) == 0) { + return GO_TOP; // LCOV_EXCL_LINE + } + fp = fopen(strip(name), WRITE_MODE); + if (fp == NULL) { + printf("Can't open file %s, try again.\n", name); + } + free(name); + } + + savefile(fp); + fclose(fp); + rspeak(RESUME_HELP); + exit(EXIT_SUCCESS); } -int resume(void) -{ - /* Resume. Read a suspended game back from a file. - * If ADVENT_NOSAVE is defined, gripe instead. */ +int resume(void) { + /* Resume. Read a suspended game back from a file. + * If ADVENT_NOSAVE is defined, gripe instead. */ #if defined ADVENT_NOSAVE || defined ADVENT_AUTOSAVE - rspeak(SAVERESUME_DISABLED); - return GO_TOP; + rspeak(SAVERESUME_DISABLED); + return GO_TOP; #endif - FILE *fp = NULL; - - if (game.loc != LOC_START || game.locs[LOC_START].abbrev != 1) { - rspeak(RESUME_ABANDON); - if (!yes_or_no(arbitrary_messages[THIS_ACCEPTABLE], arbitrary_messages[OK_MAN], arbitrary_messages[OK_MAN])) - return GO_CLEAROBJ; - } - - while (fp == NULL) { - char* name = myreadline("\nFile name: "); - if (name == NULL) - return GO_TOP; - name = strip(name); - if (strlen(name) == 0) - return GO_TOP; // LCOV_EXCL_LINE - fp = fopen(name, READ_MODE); - if (fp == NULL) - printf("Can't open file %s, try again.\n", name); - free(name); - } - - return restore(fp); + FILE *fp = NULL; + + if (game.loc != LOC_START || game.locs[LOC_START].abbrev != 1) { + rspeak(RESUME_ABANDON); + if (!yes_or_no(arbitrary_messages[THIS_ACCEPTABLE], + arbitrary_messages[OK_MAN], + arbitrary_messages[OK_MAN])) { + return GO_CLEAROBJ; + } + } + + while (fp == NULL) { + char *name = myreadline("\nFile name: "); + if (name == NULL) + return GO_TOP; + name = strip(name); + if (strlen(name) == 0) + return GO_TOP; // LCOV_EXCL_LINE + fp = fopen(name, READ_MODE); + if (fp == NULL) { + printf("Can't open file %s, try again.\n", name); + } + free(name); + } + + return restore(fp); } -int restore(FILE* fp) -{ - /* Read and restore game state from file, assuming - * sane initial state. - * If ADVENT_NOSAVE is defined, gripe instead. */ +int restore(FILE *fp) { + /* Read and restore game state from file, assuming + * sane initial state. + * If ADVENT_NOSAVE is defined, gripe instead. */ #ifdef ADVENT_NOSAVE - rspeak(SAVERESUME_DISABLED); - return GO_TOP; + rspeak(SAVERESUME_DISABLED); + return GO_TOP; #endif - IGNORE(fread(&save, sizeof(struct save_t), 1, fp)); - fclose(fp); - if (memcmp(save.magic, ADVENT_MAGIC, sizeof(ADVENT_MAGIC)) != 0 || save.canary != ENDIAN_MAGIC) - rspeak(BAD_SAVE); - else if (save.version != SAVE_VERSION) { - rspeak(VERSION_SKEW, save.version / 10, MOD(save.version, 10), SAVE_VERSION / 10, MOD(SAVE_VERSION, 10)); - } else if (!is_valid(save.game)) { - rspeak(SAVE_TAMPERING); - exit(EXIT_SUCCESS); - } else { - game = save.game; - } - return GO_TOP; + IGNORE(fread(&save, sizeof(struct save_t), 1, fp)); + fclose(fp); + if (memcmp(save.magic, ADVENT_MAGIC, sizeof(ADVENT_MAGIC)) != 0 || + save.canary != ENDIAN_MAGIC) { + rspeak(BAD_SAVE); + } else if (save.version != SAVE_VERSION) { + rspeak(VERSION_SKEW, save.version / 10, MOD(save.version, 10), + SAVE_VERSION / 10, MOD(SAVE_VERSION, 10)); + } else if (!is_valid(save.game)) { + rspeak(SAVE_TAMPERING); + exit(EXIT_SUCCESS); + } else { + game = save.game; + } + return GO_TOP; } -bool is_valid(struct game_t valgame) -{ - /* Save files can be roughly grouped into three groups: - * With valid, reachable state, with valid, but unreachable - * state and with invalid state. We check that state is - * valid: no states are outside minimal or maximal value - */ - - /* Prevent division by zero */ - if (valgame.abbnum == 0) { - return false; // LCOV_EXCL_LINE - } - - /* Check for RNG overflow. Truncate */ - if (valgame.lcg_x >= LCG_M) { - valgame.lcg_x %= LCG_M; // LCOV_EXCL_LINE - } - - /* Check for RNG underflow. Transpose */ - if (valgame.lcg_x < LCG_M) { - valgame.lcg_x = LCG_M + (valgame.lcg_x % LCG_M); - } - - /* Bounds check for locations */ - if ( valgame.chloc < -1 || valgame.chloc > NLOCATIONS || - valgame.chloc2 < -1 || valgame.chloc2 > NLOCATIONS || - valgame.loc < 0 || valgame.loc > NLOCATIONS || - valgame.newloc < 0 || valgame.newloc > NLOCATIONS || - valgame.oldloc < 0 || valgame.oldloc > NLOCATIONS || - valgame.oldlc2 < 0 || valgame.oldlc2 > NLOCATIONS) { - return false; // LCOV_EXCL_LINE - } - /* Bounds check for location arrays */ - for (int i = 0; i <= NDWARVES; i++) { - if (valgame.dwarves[i].loc < -1 || valgame.dwarves[i].loc > NLOCATIONS || - valgame.dwarves[i].oldloc < -1 || valgame.dwarves[i].oldloc > NLOCATIONS) { - return false; // LCOV_EXCL_LINE - } - } - - for (int i = 0; i <= NOBJECTS; i++) { - if (valgame.objects[i].place < -1 || valgame.objects[i].place > NLOCATIONS || - valgame.objects[i].fixed < -1 || valgame.objects[i].fixed > NLOCATIONS) { - return false; // LCOV_EXCL_LINE - } - } - - /* Bounds check for dwarves */ - if (valgame.dtotal < 0 || valgame.dtotal > NDWARVES || - valgame.dkill < 0 || valgame.dkill > NDWARVES) { - return false; // LCOV_EXCL_LINE - } - - /* Validate that we didn't die too many times in save */ - if (valgame.numdie >= NDEATHS) { - return false; // LCOV_EXCL_LINE - } - - /* Recalculate tally, throw the towel if in disagreement */ - int temp_tally = 0; - for (int treasure = 1; treasure <= NOBJECTS; treasure++) { - if (objects[treasure].is_treasure) { - if (PROP_IS_NOTFOUND2(valgame, treasure)) { - ++temp_tally; - } - } - } - if (temp_tally != valgame.tally) { - return false; // LCOV_EXCL_LINE - } - - /* Check that properties of objects aren't beyond expected */ - for (obj_t obj = 0; obj <= NOBJECTS; obj++) { - if (PROP_IS_INVALID(valgame.objects[obj].prop)) { - return false; // LCOV_EXCL_LINE - } - } - - /* Check that values in linked lists for objects in locations are inside bounds */ - for (loc_t loc = LOC_NOWHERE; loc <= NLOCATIONS; loc++) { - if (valgame.locs[loc].atloc < NO_OBJECT || valgame.locs[loc].atloc > NOBJECTS * 2) { - return false; // LCOV_EXCL_LINE - } - } - for (obj_t obj = 0; obj <= NOBJECTS * 2; obj++ ) { - if (valgame.link[obj] < NO_OBJECT || valgame.link[obj] > NOBJECTS * 2) { - return false; // LCOV_EXCL_LINE - } - } - - return true; +bool is_valid(struct game_t valgame) { + /* Save files can be roughly grouped into three groups: + * With valid, reachable state, with valid, but unreachable + * state and with invalid state. We check that state is + * valid: no states are outside minimal or maximal value + */ + + /* Prevent division by zero */ + if (valgame.abbnum == 0) { + return false; // LCOV_EXCL_LINE + } + + /* Check for RNG overflow. Truncate */ + if (valgame.lcg_x >= LCG_M) { + valgame.lcg_x %= LCG_M; // LCOV_EXCL_LINE + } + + /* Check for RNG underflow. Transpose */ + if (valgame.lcg_x < LCG_M) { + valgame.lcg_x = LCG_M + (valgame.lcg_x % LCG_M); + } + + /* Bounds check for locations */ + if (valgame.chloc < -1 || valgame.chloc > NLOCATIONS || + valgame.chloc2 < -1 || valgame.chloc2 > NLOCATIONS || + valgame.loc < 0 || valgame.loc > NLOCATIONS || valgame.newloc < 0 || + valgame.newloc > NLOCATIONS || valgame.oldloc < 0 || + valgame.oldloc > NLOCATIONS || valgame.oldlc2 < 0 || + valgame.oldlc2 > NLOCATIONS) { + return false; // LCOV_EXCL_LINE + } + /* Bounds check for location arrays */ + for (int i = 0; i <= NDWARVES; i++) { + if (valgame.dwarves[i].loc < -1 || + valgame.dwarves[i].loc > NLOCATIONS || + valgame.dwarves[i].oldloc < -1 || + valgame.dwarves[i].oldloc > NLOCATIONS) { + return false; // LCOV_EXCL_LINE + } + } + + for (int i = 0; i <= NOBJECTS; i++) { + if (valgame.objects[i].place < -1 || + valgame.objects[i].place > NLOCATIONS || + valgame.objects[i].fixed < -1 || + valgame.objects[i].fixed > NLOCATIONS) { + return false; // LCOV_EXCL_LINE + } + } + + /* Bounds check for dwarves */ + if (valgame.dtotal < 0 || valgame.dtotal > NDWARVES || + valgame.dkill < 0 || valgame.dkill > NDWARVES) { + return false; // LCOV_EXCL_LINE + } + + /* Validate that we didn't die too many times in save */ + if (valgame.numdie >= NDEATHS) { + return false; // LCOV_EXCL_LINE + } + + /* Recalculate tally, throw the towel if in disagreement */ + int temp_tally = 0; + for (int treasure = 1; treasure <= NOBJECTS; treasure++) { + if (objects[treasure].is_treasure) { + if (PROP_IS_NOTFOUND2(valgame, treasure)) { + ++temp_tally; + } + } + } + if (temp_tally != valgame.tally) { + return false; // LCOV_EXCL_LINE + } + + /* Check that properties of objects aren't beyond expected */ + for (obj_t obj = 0; obj <= NOBJECTS; obj++) { + if (PROP_IS_INVALID(valgame.objects[obj].prop)) { + return false; // LCOV_EXCL_LINE + } + } + + /* Check that values in linked lists for objects in locations are inside + * bounds */ + for (loc_t loc = LOC_NOWHERE; loc <= NLOCATIONS; loc++) { + if (valgame.locs[loc].atloc < NO_OBJECT || + valgame.locs[loc].atloc > NOBJECTS * 2) { + return false; // LCOV_EXCL_LINE + } + } + for (obj_t obj = 0; obj <= NOBJECTS * 2; obj++) { + if (valgame.link[obj] < NO_OBJECT || + valgame.link[obj] > NOBJECTS * 2) { + return false; // LCOV_EXCL_LINE + } + } + + return true; } /* end */ diff --git a/score.c b/score.c index 473ec6a..1d84c79 100644 --- a/score.c +++ b/score.c @@ -4,157 +4,159 @@ * SPDX-FileCopyrightText: (C) 1977, 2005 by Will Crowther and Don Woods * SPDX-License-Identifier: BSD-2-Clause */ -#include #include "advent.h" #include "dungeon.h" +#include -static int mxscor; /* ugh..the price for having score() not exit. */ +static int mxscor; /* ugh..the price for having score() not exit. */ int score(enum termination mode) { -/* mode is 'scoregame' if scoring, 'quitgame' if quitting, 'endgame' if died - * or won */ - int score = 0; + /* mode is 'scoregame' if scoring, 'quitgame' if quitting, 'endgame' if + * died or won */ + int score = 0; - /* The present scoring algorithm is as follows: - * Objective: Points: Present total possible: - * Getting well into cave 25 25 - * Each treasure < chest 12 60 - * Treasure chest itself 14 14 - * Each treasure > chest 16 224 - * Surviving (MAX-NUM)*10 30 - * Not quitting 4 4 - * Reaching "game.closng" 25 25 - * "Closed": Quit/Killed 10 - * Klutzed 25 - * Wrong way 30 - * Success 45 45 - * Came to Witt's End 1 1 - * Round out the total 2 2 - * TOTAL: 430 - * Points can also be deducted for using hints or too many turns, or for - * saving intermediate positions. */ + /* The present scoring algorithm is as follows: + * Objective: Points: Present total possible: + * Getting well into cave 25 25 + * Each treasure < chest 12 60 + * Treasure chest itself 14 14 + * Each treasure > chest 16 224 + * Surviving (MAX-NUM)*10 30 + * Not quitting 4 4 + * Reaching "game.closng" 25 25 + * "Closed": Quit/Killed 10 + * Klutzed 25 + * Wrong way 30 + * Success 45 45 + * Came to Witt's End 1 1 + * Round out the total 2 2 + * TOTAL: 430 + * Points can also be deducted for using hints or too many turns, or + * for saving intermediate positions. */ - /* First tally up the treasures. Must be in building and not broken. - * Give the poor guy 2 points just for finding each treasure. */ - mxscor = 0; - for (int i = 1; i <= NOBJECTS; i++) { - if (!objects[i].is_treasure) { - continue; + /* First tally up the treasures. Must be in building and not broken. + * Give the poor guy 2 points just for finding each treasure. */ + mxscor = 0; + for (int i = 1; i <= NOBJECTS; i++) { + if (!objects[i].is_treasure) { + continue; + } + if (objects[i].inventory != 0) { + int k = 12; + if (i == CHEST) { + k = 14; + } + if (i > CHEST) { + k = 16; + } + if (!PROP_IS_STASHED(i) && !PROP_IS_NOTFOUND(i)) { + score += 2; + } + if (game.objects[i].place == LOC_BUILDING && + PROP_IS_FOUND(i)) { + score += k - 2; + } + mxscor += k; + } } - if (objects[i].inventory != 0) { - int k = 12; - if (i == CHEST) { - k = 14; - } - if (i > CHEST) { - k = 16; - } - if (!PROP_IS_STASHED(i) && !PROP_IS_NOTFOUND(i)) { - score += 2; - } - if (game.objects[i].place == LOC_BUILDING && PROP_IS_FOUND(i)) { - score += k - 2; - } - mxscor += k; - } - } - /* Now look at how he finished and how far he got. NDEATHS and - * game.numdie tell us how well he survived. game.dflag will tell us - * if he ever got suitably deep into the cave. game.closng still - * indicates whether he reached the endgame. And if he got as far as - * "cave closed" (indicated by "game.closed"), then bonus is zero for - * mundane exits or 133, 134, 135 if he blew it (so to speak). */ - score += (NDEATHS - game.numdie) * 10; - mxscor += NDEATHS * 10; - if (mode == endgame) { - score += 4; - } - mxscor += 4; - if (game.dflag != 0) { - score += 25; - } - mxscor += 25; - if (game.closng) { - score += 25; - } - mxscor += 25; - if (game.closed) { - if (game.bonus == none) { - score += 10; + /* Now look at how he finished and how far he got. NDEATHS and + * game.numdie tell us how well he survived. game.dflag will tell us + * if he ever got suitably deep into the cave. game.closng still + * indicates whether he reached the endgame. And if he got as far as + * "cave closed" (indicated by "game.closed"), then bonus is zero for + * mundane exits or 133, 134, 135 if he blew it (so to speak). */ + score += (NDEATHS - game.numdie) * 10; + mxscor += NDEATHS * 10; + if (mode == endgame) { + score += 4; } - if (game.bonus == splatter) { - score += 25; + mxscor += 4; + if (game.dflag != 0) { + score += 25; } - if (game.bonus == defeat) { - score += 30; + mxscor += 25; + if (game.closng) { + score += 25; } - if (game.bonus == victory) { - score += 45; + mxscor += 25; + if (game.closed) { + if (game.bonus == none) { + score += 10; + } + if (game.bonus == splatter) { + score += 25; + } + if (game.bonus == defeat) { + score += 30; + } + if (game.bonus == victory) { + score += 45; + } } - } - mxscor += 45; + mxscor += 45; - /* Did he come to Witt's End as he should? */ - if (game.objects[MAGAZINE].place == LOC_WITTSEND) { - score += 1; - } - mxscor += 1; + /* Did he come to Witt's End as he should? */ + if (game.objects[MAGAZINE].place == LOC_WITTSEND) { + score += 1; + } + mxscor += 1; - /* Round it off. */ - score += 2; - mxscor += 2; + /* Round it off. */ + score += 2; + mxscor += 2; - /* Deduct for hints/turns/saves. Hints < 4 are special; see database desc. */ - for (int i = 0; i < NHINTS; i++) { - if (game.hints[i].used) { - score = score - hints[i].penalty; + /* Deduct for hints/turns/saves. Hints < 4 are special; see database + * desc. */ + for (int i = 0; i < NHINTS; i++) { + if (game.hints[i].used) { + score = score - hints[i].penalty; + } + } + if (game.novice) { + score -= 5; + } + if (game.clshnt) { + score -= 10; } - } - if (game.novice) { - score -= 5; - } - if (game.clshnt) { - score -= 10; - } - score = score - game.trnluz - game.saved; + score = score - game.trnluz - game.saved; - /* Return to score command if that's where we came from. */ - if (mode == scoregame) { - rspeak(GARNERED_POINTS, score, mxscor, game.turns, game.turns); - } + /* Return to score command if that's where we came from. */ + if (mode == scoregame) { + rspeak(GARNERED_POINTS, score, mxscor, game.turns, game.turns); + } - return score; + return score; } void terminate(enum termination mode) { -/* End of game. Let's tell him all about it. */ - int points = score(mode); + /* End of game. Let's tell him all about it. */ + int points = score(mode); #if defined ADVENT_AUTOSAVE - autosave(); + autosave(); #endif - if (points + game.trnluz + 1 >= mxscor && game.trnluz != 0) { - rspeak(TOOK_LONG); - } - if (points + game.saved + 1 >= mxscor && game.saved != 0) { - rspeak(WITHOUT_SUSPENDS); - } - rspeak(TOTAL_SCORE, points, mxscor, game.turns, game.turns); - for (int i = 1; i <= (int)NCLASSES; i++) { - if (classes[i].threshold >= points) { - speak(classes[i].message); - if (i < (int)NCLASSES) { - int nxt = classes[i].threshold + 1 - points; - rspeak(NEXT_HIGHER, nxt, nxt); - } else { - rspeak(NO_HIGHER); - } - exit(EXIT_SUCCESS); - } - } - rspeak(OFF_SCALE); - exit(EXIT_SUCCESS); + if (points + game.trnluz + 1 >= mxscor && game.trnluz != 0) { + rspeak(TOOK_LONG); + } + if (points + game.saved + 1 >= mxscor && game.saved != 0) { + rspeak(WITHOUT_SUSPENDS); + } + rspeak(TOTAL_SCORE, points, mxscor, game.turns, game.turns); + for (int i = 1; i <= (int)NCLASSES; i++) { + if (classes[i].threshold >= points) { + speak(classes[i].message); + if (i < (int)NCLASSES) { + int nxt = classes[i].threshold + 1 - points; + rspeak(NEXT_HIGHER, nxt, nxt); + } else { + rspeak(NO_HIGHER); + } + exit(EXIT_SUCCESS); + } + } + rspeak(OFF_SCALE); + exit(EXIT_SUCCESS); } /* end */ -- 2.31.1