diff --git a/.gitignore b/.gitignore index 7aa1928..fcfc9f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Eric S. Raymond +# SPDX-FileCopyrightText: (C) Eric S. Raymond # SPDX-License-Identifier: BSD-2-Clause advent *.gcda diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6b6bd6d..b8f4b91 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Eric S. Raymond +# SPDX-FileCopyrightText: (C) Eric S. Raymond # SPDX-License-Identifier: BSD-2-Clause stages: - ci-build diff --git a/.shipper b/.shipper index d3ed58d..5e5904c 100644 --- a/.shipper +++ b/.shipper @@ -1,4 +1,4 @@ -#SPDX-FileCopyrightText: Eric S. Raymond +#SPDX-FileCopyrightText: (C) Eric S. Raymond #SPDX-License-Identifier: BSD-2-Clause extralines="""

There is a code coverage analysis and a symbol coverage analysis

diff --git a/Dockerfile.ci b/Dockerfile.ci index c8f29a1..b8eeef5 100644 --- a/Dockerfile.ci +++ b/Dockerfile.ci @@ -1,6 +1,6 @@ # This image is built by the Gitlab CI pipeline to be used in subsequent # pipeline steps. -# SPDX-FileCopyrightText: Copyright Eric S. Raymond +# SPDX-FileCopyrightText: (C) Eric S. Raymond # SPDX-License-Identifier: BSD-2-Clause FROM ubuntu @@ -9,4 +9,4 @@ FROM ubuntu ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update -RUN apt-get install --yes --no-install-recommends make gcc-10 libedit-dev libasan6 libubsan1 python3 python3-yaml lcov asciidoc libxslt1.1 pkg-config docbook-xml xsltproc +RUN apt-get install --yes --no-install-recommends make gcc libedit-dev libasan6 libubsan1 python3 python3-yaml lcov asciidoctor libxslt1.1 pkg-config docbook-xml xsltproc diff --git a/INSTALL.adoc b/INSTALL.adoc index b6f5b4b..fb93b7d 100644 --- a/INSTALL.adoc +++ b/INSTALL.adoc @@ -1,5 +1,5 @@ = Installing Open Adventure = -// SPDX-FileCopyrightText: Copyright Eric S. Raymond +// SPDX-FileCopyrightText: (C) Eric S. Raymond // SPDX-License-Identifier: CC-BY-4.0 Installation now requires Python3 due to a security issue @@ -21,3 +21,7 @@ You can also use pip to install PyYAML: `pip3 install PyYAML`. 4. Optionally run a regression test on the code with `make check`. 5. Run `./advent` to play. + +6. If you want to buld the documentation you will need asciidoctor. + +7. Running the regression tests requires batchspell diff --git a/Makefile b/Makefile index dfd3ff2..e4e31cb 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # Makefile for the open-source release of adventure 2.5 -# SPDX-FileCopyrightText: Copyright Eric S. Raymond +# SPDX-FileCopyrightText: (C) Eric S. Raymond # SPDX-License-Identifier: BSD-2-Clause # To build with save/resume disabled, pass CFLAGS="-DADVENT_NOSAVE" @@ -12,7 +12,7 @@ VERS=$(shell sed -n +// SPDX-FileCopyrightText: (C) Eric S. Raymond // SPDX-License-Identifier: CC-BY-4.0 +1.20: 2024-09-23:: + Make oldstyle correctly suppress line editing. + +1.19: 2024-06-27:: + Ensore that the KNIVES_VANISH message can't issue twice. + +1.18: 2024-02-15:: + Bring the manual page fully up to date. + 1.17: 2024-01-02:: Saying Z'ZZZ at reservoir no longer causes the waters to part and crash. diff --git a/README.adoc b/README.adoc index ba67510..6b25f7f 100644 --- a/README.adoc +++ b/README.adoc @@ -1,5 +1,5 @@ = README for Open Adventure = -// SPDX-FileCopyrightText: Copyright Eric S. Raymond +// SPDX-FileCopyrightText: (C) Eric S. Raymond // SPDX-License-Identifier: CC-BY-4.0 If you are reading this anywhere but at http://www.catb.org/~esr/open-adventure diff --git a/actions.c b/actions.c index 4f4ced1..469e92f 100644 --- a/actions.c +++ b/actions.c @@ -1,1596 +1,1659 @@ /* * Actions for the dungeon-running code. * - * SPDX-FileCopyrightText: 1977, 2005 by Will Crowther and Don Woods - * SPDX-FileCopyrightText 2017 by Eric S. Raymond + * SPDX-FileCopyrightText: (C) 1977, 2005 Will Crowther and Don Woods * SPDX-License-Identifier: BSD-2-Clause */ -#include -#include -#include -#include "advent.h" -#include "dungeon.h" #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; +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 == 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); + 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; - } + 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 == 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; + 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; + } - 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); + 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; - } - - 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); - else - rspeak(WELL_POINTLESS); - 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); - } +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); + } else { + rspeak(WELL_POINTLESS); + } + game.foobar = WORD_EMPTY; + return GO_CLEAROBJ; + } } -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); +static void blast(void) { + /* Blast. No effect unless you've got dynamite, which is a neat trick! + */ + if (OBJECT_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 brief(void) -/* 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 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); } -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; +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; } -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; +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; + } - 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 (TOTING(obj)) { + speak(actions[verb].message); + 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; - } + if (obj == MESSAG) { + rspeak(REMOVE_MESSAGE); + DESTROY(MESSAG); + return GO_CLEAROBJ; + } - game.objects[CHAIN].prop = CHAIN_FIXED; + if (game.objects[obj].fixed != IS_FREE) { + switch (obj) { + case PLANT: + rspeak((game.objects[PLANT].prop == PLANT_THIRSTY || + OBJECT_IS_STASHED(PLANT)) + ? 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 (TOTING(CHAIN)) - drop(CHAIN, game.loc); - game.objects[CHAIN].fixed = IS_FIXED; + 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; + } - rspeak(CHAIN_LOCKED); - return GO_CLEAROBJ; + if (game.holdng >= INVLIMIT) { + rspeak(CARRY_LIMIT); + return GO_CLEAROBJ; + } + + if (obj == BIRD && game.objects[BIRD].prop != BIRD_CAGED && + !OBJECT_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) && + OBJECT_STATE_EQUALS(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) && !OBJECT_IS_FOUND(obj)) { + OBJECT_SET_FOUND(obj); + game.objects[CAVITY].prop = CAVITY_EMPTY; + } + rspeak(OK_MAN); + 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; - } +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; + } - if (!TOTING(obj)) { - speak(actions[verb].message); - return GO_CLEAROBJ; - } + 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 (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 (game.objects[CHAIN].prop != CHAIN_HEAP) { + rspeak(ALREADY_LOCKED); + return GO_CLEAROBJ; + } + if (game.loc != objects[CHAIN].plac) { + rspeak(NO_LOCKSITE); + return GO_CLEAROBJ; + } - if (obj == COINS && HERE(VEND)) { - DESTROY(COINS); - drop(BATTERY, game.loc); - pspeak(BATTERY, look, true, FRESH_BATTERIES); - return GO_CLEAROBJ; - } + game.objects[CHAIN].prop = CHAIN_FIXED; - if (LIQUID() == obj) - obj = BOTTLE; - if (obj == BOTTLE && LIQUID() != NO_OBJECT) { - game.objects[LIQUID()].place = LOC_NOWHERE; - } + if (TOTING(CHAIN)) { + drop(CHAIN, game.loc); + } + game.objects[CHAIN].fixed = IS_FIXED; - 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; + rspeak(CHAIN_LOCKED); + 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; - } +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 (obj == BLOOD) { - DESTROY(BLOOD); - state_change(DRAGON, DRAGON_BLOODLESS); - game.blooded = true; - return GO_CLEAROBJ; - } + if (!TOTING(obj)) { + speak(actions[verb].message); + 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; - } + 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; + } - speak(actions[verb].message); - 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 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; +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; } -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; - if (HERE(URN) && game.objects[URN].prop == URN_LIT) - obj = URN; - 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; +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; } -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); - break; - default: - rspeak(AM_GAME); - } - 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; + } + if (HERE(URN) && game.objects[URN].prop == URN_LIT) { + obj = URN; + } + 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(IS_DARK_HERE() ? PITCH_DARK : NO_MESSAGE); + break; + case DRAGON: + case VOLCANO: + rspeak(BEYOND_POWER); + break; + default: + speak(actions[verb].message); + } + 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; +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); + } + break; + default: + rspeak(AM_GAME); + } + 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; - } +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 (game.closed) { - rspeak(NEEDED_NEARBY); - 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 (AT(obj) || (LIQUID() == obj && AT(BOTTLE)) || - obj == LIQLOC(game.loc) || (obj == DWARF && atdwrf(game.loc) > 0)) { - rspeak(YOU_HAVEIT); - return GO_CLEAROBJ; - } + 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; + } - - speak(actions[verb].message); - 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 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; - } +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 (obj != RUG) { - speak(actions[verb].message); - return GO_CLEAROBJ; - } - if (game.objects[RUG].prop != RUG_HOVER) { - rspeak(RUG_NOTHING1); - return GO_CLEAROBJ; - } + if (game.closed) { + rspeak(NEEDED_NEARBY); + 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; + 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 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; +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; } -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; +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; } -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; +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; } -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; +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 || + OBJECT_IS_STASHED(i) || OBJECT_IS_NOTFOUND(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 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; - } +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; + } + } - 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; - } + /* 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 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; +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; + } +} + +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; } 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 (command.obj == INTRANSITIVE) { + command.obj = NO_OBJECT; + for (int i = 1; i <= NOBJECTS; i++) { + if (HERE(i) && objects[i].texts[0] != NULL && + !OBJECT_IS_STASHED(i)) { + command.obj = command.obj * NOBJECTS + i; + } + } + if (command.obj > NOBJECTS || command.obj == NO_OBJECT || + IS_DARK_HERE()) { + 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 (IS_DARK_HERE()) { + 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 || + OBJECT_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; - } - } +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; + } + } } -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; +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; } -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(); +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; + 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)); - } +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); - } - } + 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; - } +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; + } } -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; +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; } -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; +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; } -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; - } +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); + if (game.objects[BIRD].prop == BIRD_UNCAGED && + game.loc == game.objects[STEPS].place && OBJECT_IS_NOTFOUND(JADE)) { + drop(JADE, game.loc); + OBJECT_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; - } + 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; - } +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.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; - } + 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 - } + 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.adoc b/advent.adoc index 5ce5b3f..a064d1a 100644 --- a/advent.adoc +++ b/advent.adoc @@ -1,8 +1,11 @@ = advent(6) = :doctype: manpage -// SPDX-FileCopyrightText: Copyright Eric S. Raymond +// SPDX-FileCopyrightText: (C) Eric S. Raymond // SPDX-License-Identifier: CC-BY-4.0 +// batchspell: add advent logfile savefile roleplaying Gillogly PDP Ctrl-D +// batchspell: add EOF autosave endianness wumpus zork nethack + == NAME == advent - Colossal Cave Adventure @@ -20,9 +23,9 @@ adventure". To learn more about the changes since the 350-point original, type 'news' at the command prompt. There is an 'adventure' in the BSD games package that is a C port by -Jim Gillogly of the Don Woods's 1977 version of this game. To avoid a name -collision, this game builds as 'advent', reflecting the fact that the -PDP-10 on which the game originally ran limited filenames to 6 characters. +Jim Gillogly of the 1977 version. To avoid a name collision, this game +builds as 'advent', reflecting the fact that the PDP-10 on which the +game originally ran limited filenames to 6 characters. This version is released as open source with the permission and encouragement of the original authors. @@ -34,7 +37,8 @@ command history. Some minor bugs and message typos have been fixed. Otherwise, the "version" command is almost the only way to tell you're not running -Don's 1977 version. +Don's 1977 version until you get to the new cave sections added for +2.5. To exit the game, type Ctrl-D (EOF). @@ -44,9 +48,9 @@ There have been no gameplay changes. -l:: Log commands to specified file. --r:: Restore game from specified file +-r:: Restore game from specified save file --a:: Load from specified file and autosave to it on exit or signal. +-a:: Load from specified save file and autosave to it on exit or signal. -o:: Old-style. Reverts some minor cosmetic fixes in game messages. Restores original interface, no prompt or line editing. @@ -59,9 +63,9 @@ argument of '-' is taken as a directive to read from standard input. == BUGS == -The binary save file format is fragile, dependent on your machine word -size and endianness, and unlikely to survive through version bumps. There -is a version check. +The binary save file format is fragile, dependent on your machine's +endianness, and unlikely to survive through version bumps. There are +version and endianness checks when attempting to restore from a save. The input parser was the first attempt *ever* at natural-language parsing in a game and has some known deficiencies. While later text diff --git a/advent.desktop b/advent.desktop index 3fe5420..1cc38f8 100644 --- a/advent.desktop +++ b/advent.desktop @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright Eric S. Raymond +# SPDX-FileCopyrightText: (C) Eric S. Raymond # SPDX-License-Identifier: BSD-2-Clause [Desktop Entry] Type=Application diff --git a/advent.h b/advent.h index 68890da..fa0767b 100644 --- a/advent.h +++ b/advent.h @@ -1,15 +1,14 @@ /* * Dungeon types and macros. * - * SPDX-FileCopyrightText: 1977, 2005 by Will Crowther and Don Woods - * SPDX-FileCopyrightText: 2017 by Eric S. Raymond + * 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" @@ -19,36 +18,35 @@ #define LCG_C 221587L #define LCG_M 1048576L -#define LINESIZE 1024 -#define TOKLEN 5 // # outputting characters in a token */ -#define NDWARVES 6 // number of dwarves -#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 #define IS_FREE 0 -#ifndef FOUNDBOOL /* (ESR) It is fitting that translation of the original ADVENT should * have left us a maze of twisty little conditionals that resists all * understanding. Setting and use of what is now the per-object state @@ -62,118 +60,112 @@ * STATE_NOTFOUND is only set on treasures. Non-treasures start the * game in STATE_FOUND. * - * PROP_STASHED is supposed to map a state property value to a + * PROP_STASHIFY is supposed to map a state property value to a * negative range, where the object cannot be picked up but the value * can be recovered later. Various objects get this property when * the cave starts to close. Only seems to be significant for the bird * and readable objects, notably the clam/oyster - but the code around - * those test is difficult to read. - */ -#define PROP_STASHIFY(n) (-1 - (n)) -#define PROP_IS_STASHED(obj) (game.objects[obj].prop < STATE_NOTFOUND) -#define PROP_IS_NOTFOUND(obj) (game.objects[obj].prop == STATE_NOTFOUND) -#define PROP_IS_FOUND(obj) (game.objects[obj].prop == STATE_FOUND) -#define PROP_IS_STASHED_OR_UNSEEN(obj) (game.objects[obj].prop < 0) -#define PROP_SET_FOUND(obj) (game.objects[obj].prop = STATE_FOUND) -#define PROP_SET_NOT_FOUND(obj) (game.objects[obj].prop = STATE_NOTFOUND) -#define PROP_IS_NOTFOUND2(g, o) (g.objects[o].prop == STATE_NOTFOUND) -#define PROP_IS_INVALID(val) (val < -MAX_STATE - 1 || val > MAX_STATE) -#else -/* (ESR) Only the boldest of adventurers will explore here. This - * alternate set of definitions for the macros above was an attempt to - * break from out of the state encoding a per-object "found" member - * telling whether or not the player has seen the object. + * those tests is difficult to read. * - * What's broken when you try to use thus is - * PROP_IS_STASHED_OR_UNSEEN. The symptom is game.tally getting - * decremented on non-treasures. + * All tests of the prop member are done with either these macros or ==. */ -#define PROP_STASHIFY(n) (-(n)) -#define PROP_IS_STASHED(obj) (game.objects[obj].prop < 0) -#define PROP_IS_NOTFOUND(obj) (!game.objects[obj].found) -#define PROP_IS_FOUND(obj) (game.objects[obj].found && game.objects[obj].prop == 0) -#define PROP_IS_STASHED_OR_UNSEEN(obj) (!game.objects[obj].found || game.objects[obj].prop < 0) -#define PROP_SET_FOUND(obj) do {game.objects[obj].found = true; game.objects[obj].prop = STATE_FOUND;} while(0) -#define PROP_SET_NOT_FOUND(obj) game.objects[obj].found = false -#define PROP_IS_NOTFOUND2(g, o) (!g.objects[o].found) -#define PROP_IS_INVALID(val) (val < -MAX_STATE || val > MAX_STATE) -#define PROP_SET_SEEN(obj) game.objects[object].found = true -#endif -#define PROP_STASHED(obj) PROP_STASHIFY(game.objects[obj].prop) +#define OBJECT_IS_NOTFOUND(obj) (game.objects[obj].prop == STATE_NOTFOUND) +#define OBJECT_IS_FOUND(obj) (game.objects[obj].prop == STATE_FOUND) +#define OBJECT_SET_FOUND(obj) (game.objects[obj].prop = STATE_FOUND) +#define OBJECT_SET_NOT_FOUND(obj) (game.objects[obj].prop = STATE_NOTFOUND) +#define OBJECT_IS_NOTFOUND2(g, o) (g.objects[o].prop == STATE_NOTFOUND) +#define PROP_IS_INVALID(val) (val < -MAX_STATE - 1 || val > MAX_STATE) +#define PROP_STASHIFY(n) (-1 - (n)) +#define OBJECT_STASHIFY(obj, pval) game.objects[obj].prop = PROP_STASHIFY(pval) +#define OBJECT_IS_STASHED(obj) (game.objects[obj].prop < STATE_NOTFOUND) +#define OBJECT_STATE_EQUALS(obj, pval) \ + ((game.objects[obj].prop == pval) || \ + (game.objects[obj].prop == PROP_STASHIFY(pval))) -#define PROMPT "> " +#define PROMPT "> " /* - * DESTROY(N) = Get rid of an item by putting it in LOC_NOWHERE - * MOD(N,M) = Arithmetic modulus - * TOTING(OBJ) = true if the OBJ is being carried - * AT(OBJ) = true if on either side of two-placed object - * HERE(OBJ) = true if the OBJ is at "LOC" (or is being carried) - * CNDBIT(L,N) = true if COND(L) has bit n set (bit 0 is units bit) - * LIQUID() = object number of liquid in bottle - * LIQLOC(LOC) = object number of liquid (if any) at LOC - * FORCED(LOC) = true if LOC moves without asking for input (COND=2) - * DARK(LOC) = true if location "LOC" is dark - * PCT(N) = true N% of the time (N integer from 0 to 100) - * GSTONE(OBJ) = true if OBJ is a gemstone - * FOREST(LOC) = true if LOC is part of the forest - * OUTSID(LOC) = true if location not in the cave - * INSIDE(LOC) = true if location is in the cave or the building at the beginning of the game - * INDEEP(LOC) = true if location is in the Hall of Mists or deeper - * BUG(X) = report bug and exit + * DESTROY(N) = Get rid of an item by putting it in LOC_NOWHERE + * MOD(N,M) = Arithmetic modulus + * TOTING(OBJ) = true if the OBJ is being carried + * AT(OBJ) = true if on either side of two-placed object + * HERE(OBJ) = true if the OBJ is at "LOC" (or is being carried) + * CNDBIT(L,N) = true if COND(L) has bit n set (bit 0 is units bit) + * LIQUID() = object number of liquid in bottle + * LIQLOC(LOC) = object number of liquid (if any) at LOC + * FORCED(LOC) = true if LOC moves without asking for input (COND=2) + * IS_DARK_HERE() = true if location "LOC" is dark + * PCT(N) = true N% of the time (N integer from 0 to 100) + * GSTONE(OBJ) = true if OBJ is a gemstone + * FOREST(LOC) = true if LOC is part of the forest + * OUTSIDE(LOC) = true if location not in the cave + * INSIDE(LOC) = true if location is in the cave or the building at the + * beginning of the game + * INDEEP(LOC) = true if location is in the Hall of Mists or deeper + * BUG(X) = report bug and exit */ -#define DESTROY(N) move(N, LOC_NOWHERE) -#define MOD(N,M) ((N) % (M)) -#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 IS_DARK_HERE() \ + (!CNDBIT(game.loc, COND_LIT) && \ + (game.objects[LAMP].prop == LAMP_DARK || !HERE(LAMP))) +#define PCT(N) (randrange(100) < (N)) +#define GSTONE(OBJ) \ + ((OBJ) == EMERALD || (OBJ) == RUBY || (OBJ) == AMBER || (OBJ) == SAPPH) +#define FOREST(LOC) CNDBIT(LOC, COND_FOREST) +#define OUTSIDE(LOC) (CNDBIT(LOC, COND_ABOVE) || FOREST(LOC)) +#define INSIDE(LOC) (!OUTSIDE(LOC) || LOC == LOC_BUILDING) +#define INDEEP(LOC) CNDBIT((LOC), COND_DEEP) +#define BUG(x) bug(x, #x) 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 */ @@ -185,75 +177,72 @@ 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 1t 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 { -#ifdef FOUNDBOOL - bool32_t found; // has the location of this object been found? -#endif - loc_t fixed; // fixed location of object (if not IS_FREE) - int32_t prop; // object state */ - 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 + 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 { + 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 }; /* @@ -261,56 +250,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; @@ -320,13 +317,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/adventure.yaml b/adventure.yaml index 2069759..141ffb7 100644 --- a/adventure.yaml +++ b/adventure.yaml @@ -1,6 +1,10 @@ -# SPDX-FileCopyrightText: Copyright Eric S. Raymond +# SPDX-FileCopyrightText: (C) Eric S. Raymond # SPDX-License-Identifier: BSD-2-Clause # +# batchspell: add XYZZY Twopit Bedquilt ne se sw nw dwarves dwarvish gouts +# batchspell: add Crowther add axe pinin Har har meself Hmmm Adventuredom +# batchspell: add Tsk gameplay bugfixes +# # This YAML file gets processed into a collection of data structures and # variable initializers describing Colossal Cave. It replaces an ad-hoc # text database shipped with Adventure versions up to 2.5. The format @@ -10,7 +14,7 @@ # We define a bunch of YAML structures: # # motions: Motion words, grouped into synonyms. The 'oldstyle' -# attribute, if false, means that single-letter synonyms should be +# attribute, if false, means that single-letter synonyms should not be # accepted in oldstyle mode; it defaults to true. # # actions: Action words, grouped into synonyms, and their corresponding @@ -3432,7 +3436,7 @@ objects: !!omap - 'There are a few recent issues of "Spelunker Today" magazine here.' texts: - |- - I'm afraid the magazine is written in dwarvish. But pencilled on one + I'm afraid the magazine is written in dwarvish. But penciled on one cover you see, "Please leave the magazines at the construction site." - DWARF: words: ['dwarf', 'dwarv'] @@ -3940,11 +3944,13 @@ obituaries: Oh dear, you seem to have gotten yourself killed. I might be able to help you out, but I've never really done this before. Do you want me to try to reincarnate you? + # batchspell: add wr yes_response: |- All right. But don't blame me if something goes wr...... --- POOF!! --- You are engulfed in a cloud of orange smoke. Coughing and gasping, you emerge from the smoke and find.... + # batchspell: remove wr - query: |- You clumsy oaf, you've done it again! I don't know how long I can keep this up. Do you want me to try reincarnating you again? @@ -4226,7 +4232,7 @@ actions: !!omap message: |- There is a puff of orange smoke; within it, fiery runes spell out: - \tOpen Adventure %V - http://www.catb.org/esr/open-adventure/ + Open Adventure %V - http://www.catb.org/esr/open-adventure/ words: ['versi'] noaction: true diff --git a/cheat.c b/cheat.c index 1a415b7..4e94025 100644 --- a/cheat.c +++ b/cheat.c @@ -4,97 +4,93 @@ * savefile(), so we know we're always outputting save files that advent * can import. * - * SPDX-FileCopyrightText: 1977, 2005 by Will Crowther and Don Woods - * SPDX-FileCopyrightText: 2017 by Eric S. Raymond + * SPDX-FileCopyrightText: (C) 1977, 2005 by Will Crowther and Don Woods * SPDX-License-Identifier: BSD-2-Clause */ -#include -#include -#include -#include -#include #include "advent.h" +#include +#include +#include +#include +#include -int main(int argc, char *argv[]) -{ - int ch; - char *savefilename = NULL; - FILE *fp = NULL; +int main(int argc, char *argv[]) { + int ch; + char *savefilename = NULL; + FILE *fp = NULL; - // Initialize game variables - initialise(); + // 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; + /* 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"; + /* 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; - } - } + 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); - } + // 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); - } + fp = fopen(savefilename, WRITE_MODE); + if (fp == NULL) { + fprintf(stderr, "Can't open file %s. Exiting.\n", savefilename); + exit(EXIT_FAILURE); + } - savefile(fp); + savefile(fp); - fclose(fp); + fclose(fp); - printf("cheat: %s created.\n", savefilename); + printf("cheat: %s created.\n", savefilename); - return EXIT_SUCCESS; + return EXIT_SUCCESS; } // LCOV_EXCL_START @@ -103,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/control b/control index 841e7ab..e47484d 100644 --- a/control +++ b/control @@ -5,11 +5,10 @@ Package: open-adventure Description: Colossal Cave Adventure, the 1995 430-point version. This is the last descendant of the original 1976 Colossal Cave Adventure - worked on by the original authors - Crowther & Woods. It has sometimes - been known as Adventure 2.5. The original PDP-10 name 'advent' is used - for the built program to avoid collision with the BSD Games version. - -XBS-Destinations: mailto:ubuntu-devel-discuss@lists.ubuntu.com + worked on by the original authors - Crowther & Woods; it is shipped with + their permission and encouragement. It has sometimes been known as + Adventure 2.5. The original PDP-10 name 'advent' is used for the + built program to avoid collision with the BSD Games version. Homepage: http://www.catb.org/~esr/open-adventure diff --git a/hints.adoc b/hints.adoc index c22c11b..f7e25e1 100644 --- a/hints.adoc +++ b/hints.adoc @@ -1,5 +1,5 @@ = Non-spoiler hints = -// SPDX-FileCopyrightText: Copyright Eric S. Raymond +// SPDX-FileCopyrightText: (C) Eric S. Raymond // SPDX-License-Identifier: CC-BY-4.0 Say the words you see. They can have interesting effects. diff --git a/history.adoc b/history.adoc index 5384c2c..8ca5da7 100644 --- a/history.adoc +++ b/history.adoc @@ -1,6 +1,6 @@ = A brief history of Colossal Cave Adventure = by Eric S. Raymond -// SPDX-FileCopyrightText: Copyright Eric S. Raymond +// SPDX-FileCopyrightText: (C) Eric S. Raymond // SPDX-License-Identifier: CC-BY-4.0 Adventure is the fons et origo of all later dungeon-crawling computer diff --git a/init.c b/init.c index aabd1cb..372e494 100644 --- a/init.c +++ b/init.c @@ -1,100 +1,96 @@ /* * Initialisation * - * SPDX-FileCopyrightText: 1977, 2005 by Will Crowther and Don Woods - * SPDX-FileCopyrightText: 2017 by Eric S. Raymond + * 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 +#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); - assert(NDWARVES == NDWARFLOCS); - for (int i = 1; i <= NDWARFLOCS; 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. + * (ESR) Non-treasures are set to STATE_FOUND explicitly so we + * don't rely on the value of uninitialized storage. This is to + * make translation to future languages easier. */ + for (int object = 1; object <= NOBJECTS; object++) { + if (objects[object].is_treasure) { + ++game.tally; + if (objects[object].inventory != NULL) { + OBJECT_SET_NOT_FOUND(object); + } + } else { + OBJECT_SET_FOUND(object); + } + } + game.conds = setbit(COND_HBASE); - return seedval; + return seedval; } diff --git a/main.c b/main.c index 16cf42d..835fc1b 100644 --- a/main.c +++ b/main.c @@ -1,401 +1,470 @@ /* - * SPDX-FileCopyrightText: 1977, 2005 by Will Crowther and Don Woods - * SPDX-FileCopyrightText: 2017 by Eric S. Raymond + * SPDX-FileCopyrightText: (C) 1977, 2005 by Will Crowther and Don Woods * 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" -#include "dungeon.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; - } - } - } + } - return NULL; + 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)) && !settings.oldstyle) { + 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; +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 - } + 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 (!OBJECT_IS_NOTFOUND(EMERALD) && + OBJECT_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 && + (OBJECT_IS_STASHED(JADE) || + OBJECT_IS_NOTFOUND(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; - } - } - } + /* 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; +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; - 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); - } - 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); - } - } - } + /* 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 + * OBJECT_IS_FOUND(CHEST) == true. */ + if (game.loc == game.chloc || !OBJECT_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; + } + 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); + } + } + 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]; +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. */ + /* 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; + /* 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; - } + /* 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; - } + /* 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; - } + /* 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; - } - } + /* 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; - 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; + /* 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; } /* "You're dead, Jim." @@ -417,72 +486,77 @@ static bool dwarfmove(void) * building (and heaven help him if he tries to xyzzy back into the * 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; +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; + ++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; - } + 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; +static void describe_location(void) { + /* 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) && IS_DARK_HERE()) { + 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); +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); } /* Given the current location in "game.loc", and a motion verb number in @@ -492,292 +566,353 @@ 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; - } +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; - } - } + 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; - } + 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((OUTSIDE(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; - } + /* 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; + /* (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; - } + /* 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; + /* 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); + 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; +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; + /* 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); + } + } } -static bool closecheck(void) /* Handle the closing of the cave. The cave closes "clock1" turns * after the last treasure has been located (including the pirate's * chest, which may of course never show up). Note that the @@ -796,448 +931,510 @@ static bool closecheck(void) * separating him from all the treasures. Most of these problems * arise from the use of negative prop numbers to suppress the object * descriptions until he's actually moved the objects. */ -{ - /* 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); - } - } +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; + /* 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); - 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); + /* 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); + } + 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; + put(MIRROR, LOC_NE, STATE_FOUND); + game.objects[MIRROR].fixed = LOC_SW; - for (int i = 1; i <= NOBJECTS; i++) { - if (TOTING(i)) - DESTROY(i); - } + for (int i = 1; i <= NOBJECTS; i++) { + if (TOTING(i)) { + DESTROY(i); + } + } - rspeak(CAVE_CLOSED); - game.closed = true; - return game.closed; - } + rspeak(CAVE_CLOSED); + game.closed = true; + return game.closed; + } - lampcheck(); - return false; + 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); - } - } +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 (!IS_DARK_HERE()) { + ++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 (OBJECT_IS_STASHED(i) || OBJECT_IS_NOTFOUND(obj)) { + if (game.closed) { + continue; + } + OBJECT_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); + } + } } -static bool preprocess_command(command_t *command) -/* 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. */ -{ - 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 { - 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; - } + * 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); + } else { + rspeak(WHERE_QUERY); + } + } 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 (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; + /* 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; + command->state = PREPROCESSED; + return true; + } + 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; - game.panic = true; - } +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 (OUTSIDE(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; + /* 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 (!dwarfmove()) { + croak(); + } - if (game.loc == LOC_NOWHERE) - 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; - } + /* The easiest way to get killed is to fall into a pit in + * pitch darkness. */ + if (!FORCED(game.loc) && IS_DARK_HERE() && game.wzdark && + PCT(PIT_KILL_PROB)) { + rspeak(PIT_FALL); + game.oldlc2 = game.loc; + croak(); + return false; + } - return true; + return true; } -static bool do_command(void) -/* Get and execute a command */ -{ - static command_t command; - clear_command(&command); +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(); + /* Describe the current location and (maybe) get next command. */ + while (command.state != EXECUTED) { + describe_location(); - if (FORCED(game.loc)) { - playermove(HERE); - return true; - } + if (FORCED(game.loc)) { + playermove(HERE); + return true; + } - listobjects(); + 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) { + /* 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); - } - } + 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 ((OBJECT_IS_NOTFOUND(OYSTER) || + OBJECT_IS_STASHED(OYSTER)) && + TOTING(OYSTER)) { + pspeak(OYSTER, look, true, 1); + } + for (size_t i = 1; i <= NOBJECTS; i++) { + if (TOTING(i) && + (OBJECT_IS_NOTFOUND(i) || + OBJECT_IS_STASHED(i))) { + OBJECT_STASHIFY( + i, game.objects[i].prop); + } + } + } - /* Check to see if the room is dark. If the knife is here, - * and it's dark, the knife permanently disappears */ - game.wzdark = DARK(game.loc); - if (game.knfloc != LOC_NOWHERE && game.knfloc != game.loc) - game.knfloc = LOC_NOWHERE; + /* Check to see if the room is dark. */ + game.wzdark = IS_DARK_HERE(); - /* 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(); + /* If the knife is not here it permanently disappears. + * Possibly this should fire if the knife is here but + * the room is dark? */ + if (game.knfloc > LOC_NOWHERE && + game.knfloc != game.loc) { + game.knfloc = LOC_NOWHERE; + } - /* Get command input from user */ - if (!get_command_input(&command)) - return false; + /* 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(); - /* 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; + /* Get command input from user */ + if (!get_command_input(&command)) { + return false; + } - ++game.turns; - preprocess_command(&command); - } + /* 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; - /* check if game is closed, and exit if it is */ - if (closecheck() ) - return true; + ++game.turns; + preprocess_command(&command); + } - /* loop until all words in command are processed */ - while (command.state == PREPROCESSED ) { - command.state = PROCESSING; + /* check if game is closed, and exit if it is */ + if (closecheck()) { + return true; + } - if (command.word[0].id == WORD_NOT_FOUND) { - /* Gee, I don't understand. */ - sspeak(DONT_KNOW, command.word[0].raw); - clear_command(&command); - continue; - } + /* loop until all words in command are processed */ + while (command.state == PREPROCESSED) { + command.state = PROCESSING; - /* 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); - } + if (command.word[0].id == WORD_NOT_FOUND) { + /* Gee, I don't understand. */ + sspeak(DONT_KNOW, command.word[0].raw); + clear_command(&command); + continue; + } - 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 - } + /* 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 (action(command)) { - case GO_TERMINATE: - command.state = EXECUTED; - break; - case GO_MOVE: - playermove(NUL); - command.state = EXECUTED; - break; - case GO_WORD2: + 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; + /* 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 */ + /* 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; + /* command completely executed; we return true. */ + return true; } /* @@ -1251,126 +1448,141 @@ 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); + 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; + /* 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); + // get command + if (!do_command()) { + break; + } + } + /* show score and exit */ + terminate(quitgame); } /* end */ diff --git a/make_dungeon.py b/make_dungeon.py index 0b8bc53..8d7f719 100755 --- a/make_dungeon.py +++ b/make_dungeon.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# SPDX-FileCopyrightText: Copyright Eric S. Raymond +# SPDX-FileCopyrightText: (C) Eric S. Raymond # SPDX-License-Identifier: BSD-2-Clause """ This is the open-adventure dungeon generator. It consumes a YAML description of diff --git a/make_graph.py b/make_graph.py index 35dd92f..5a58895 100755 --- a/make_graph.py +++ b/make_graph.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# SPDX-FileCopyrightText: Copyright Eric S. Raymond +# SPDX-FileCopyrightText: (C) Eric S. Raymond # SPDX-License-Identifier: BSD-2-Clause """\ usage: make_graph.py [-a] [-d] [-m] [-s] [-v] diff --git a/misc.c b/misc.c index d2d0b8f..b28949e 100644 --- a/misc.c +++ b/misc.c @@ -1,751 +1,775 @@ /* * I/O and support routines. * - * SPDX-FileCopyrightText: 1977, 2005 by Will Crowther and Don Woods - * SPDX-FileCopyrightText: 2017 by Eric S. Raymond + * 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 +#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; +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; + // Do nothing if we got an empty string. + if (strlen(msg) == 0) { + return; + } - if (blank == true) - printf("\n"); + if (blank == true) { + printf("\n"); + } - int msglen = strlen(msg); + int msglen = strlen(msg); - // Rendered string - ssize_t size = 2000; /* msglen > 50 ? msglen*2 : 100; */ - char* rendered = xcalloc(size); - char* renderp = rendered; + // 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); - } + // 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; - } + // 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--; - } - } + // 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; + // 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); + // Print the message. + printf("%s\n", rendered); - free(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 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 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 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 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); +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; +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; - } - } + 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); + return (count); } -static char* get_input(void) -{ - // Set up the prompt - char input_prompt[] = PROMPT; - if (!settings.prompt) - input_prompt[0] = '\0'; +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"); + // Print a blank line + printf("\n"); - char* input; - for (;;) { - input = myreadline(input_prompt); + 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; - } + 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; + // Strip trailing newlines from the input + input[strcspn(input, "\n")] = 0; - add_history(input); + add_history(input); - if (!isatty(0)) - echo_input(stdout, input_prompt, input); + if (!isatty(0)) { + echo_input(stdout, input_prompt, input); + } - if (settings.logfp) - echo_input(settings.logfp, "", input); + if (settings.logfp) { + echo_input(settings.logfp, "", input); + } - return (input); + return (input); } -bool silent_yes_or_no(void) -{ - bool outcome = false; +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; - } + 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); + char *firstword = (char *)xcalloc(strlen(reply) + 1); + sscanf(reply, "%s", firstword); - free(reply); + free(reply); - for (int i = 0; i < (int)strlen(firstword); ++i) - firstword[i] = tolower(firstword[i]); + 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); + 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); + 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); + 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; -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); - 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 + } - 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; + } - if (strlen(reply) == 0) { - free(reply); - rspeak(PLEASE_ANSWER); - continue; - } + char *firstword = (char *)xcalloc(strlen(reply) + 1); + sscanf(reply, "%s", firstword); - char* firstword = (char*) xcalloc(strlen(reply) + 1); - sscanf(reply, "%s", firstword); + free(reply); - free(reply); + for (int i = 0; i < (int)strlen(firstword); ++i) { + firstword[i] = tolower(firstword[i]); + } - 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); - 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); - 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); + } + } - 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); + return (outcome); } -/* Data structure routines */ +/* 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_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_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 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; +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 + // 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; - } + // Check for non-digit chars in the rest of the string. + while (*str) { + if (!isdigit(*str)) { + return false; + } else { + ++str; + } + } - return true; + 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; - } +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; + 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_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_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; - } + 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 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; - } + // 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; + *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)); +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); + /* Bound prefix on the %s would be needed to prevent buffer + * overflow. We shortstop this more simply by making each + * raw-input buffer as long as the entire input buffer. */ + sscanf(raw, "%s%s", cmd->word[0].raw, cmd->word[1].raw); - /* (ESR) In oldstyle mode, simulate the uppercasing and truncating - * 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]); - } + /* (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. + * + * The definition of TRUNCLEN is dubious. It accurately reflects the + * FORTRAN, but it's possible that was a bug and the proper definition + * is (TOKLEN). + */ +#define TRUNCLEN (TOKLEN + TOKLEN) + if (settings.oldstyle) { + cmd->word[0].raw[TRUNCLEN] = cmd->word[1].raw[TRUNCLEN] = '\0'; + for (size_t i = 0; i < strlen(cmd->word[0].raw); i++) { + cmd->word[0].raw[i] = toupper(cmd->word[0].raw[i]); + } + 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; + /* 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; +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); - } + 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); + strncpy(inputbuf, input, LINESIZE - 1); + free(input); - tokenize(inputbuf, command); + 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; + 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 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; +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); + 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; +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 -} - -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 > 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]; + if (from != LOC_NOWHERE && from != CARRIED) { + carry(object, from); + } + drop(object, where); } -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; +void put(obj_t object, loc_t where, int pval) { + /* put() is the same as move(), except the object is stashed and + * can no longer be picked up. */ + move(object, where); + OBJECT_STASHIFY(object, pval); +#ifdef OBJECT_SET_SEEN + OBJECT_SET_SEEN(object); +#endif } -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; +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; - 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 (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/notes.adoc b/notes.adoc index c369177..a247e49 100644 --- a/notes.adoc +++ b/notes.adoc @@ -1,6 +1,6 @@ = Open Adventure Maintainer's Notes = by Eric S. Raymond -// SPDX-FileCopyrightText: Copyright Eric S. Raymond +// SPDX-FileCopyrightText: (C) Eric S. Raymond // SPDX-License-Identifier: CC-BY-4.0 In which we explain what has been done to this code since Don Woods @@ -16,8 +16,8 @@ of Peje Nilsson in restructuring some particularly grotty gotos is gratefully acknowledged. Petr Voropaev contributed fuzz testing and code cleanups. Aaron Traas did a lot of painstaking work to improve test coverage, and factored out the last handful of gotos. Ryan -Sarson nudged us into fixing a longstannding minor bug in the -handling of incorrect magic-word sequebcesm, +Sarson nudged us into fixing a longstanding minor bug in the +handling of incorrect magic-word sequences, == Nomenclature == @@ -75,10 +75,15 @@ Bug fixes: * A few minor typos have been corrected: absence of capitalization on "Swiss" and "Persian", inconsistent spelling of "imbedded" vs. "embedded", - "eying" for "eyeing", "thresholds" for "threshholds". + "eying" for "eyeing", "thresholds" for "threshholds", "pencilled" + for "penciled". * Under odd circumstances (dropping rug or vase outdoors) the game could - formerly say "floor" when it should say "ground" (or "dirt", or something). + formerly say "floor" when it should say "ground" (or "dirt", or + something). + +* The "knives vanish" message could formerly be emitted when "I see no + knife here." would be appropriate. Enhancements: @@ -139,7 +144,7 @@ whitespace before processing. A -r command-line option has been added. When it is given (with a file path argument) it is functionally equivalent to a RESTORE command. -An -a command-line option has been added (comditionally on +An -a command-line option has been added (conditionally on ADVENT_AUTOSAVE) for use in BBS door systems. When this option is given, the game roads from the specified filename argument on startup and saves to it on quit or a received signal. There is a new nmessage diff --git a/saveresume.c b/saveresume.c index a2c10a0..1c778ef 100644 --- a/saveresume.c +++ b/saveresume.c @@ -4,249 +4,264 @@ * (ESR) This replaces a bunch of particularly nasty FORTRAN-derived code; * see the history.adoc file in the source distribution for discussion. * - * SPDX-FileCopyrightText: 1977, 2005 by Will Crowther and Don Woods - * SPDX-FileCopyrightText: 2017 by Eric S. Raymond + * 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 "advent.h" -#include "dungeon.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) +#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); +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'; - } +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; + 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; + 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; + 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); - } + 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. */ - -#if defined ADVENT_NOSAVE || defined ADVENT_AUTOSAVE - 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); -} - -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; -#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); + savefile(fp); + fclose(fp); + rspeak(RESUME_HELP); 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 - */ +int resume(void) { + /* Resume. Read a suspended game back from a file. + * If ADVENT_NOSAVE is defined, gripe instead. */ - /* Prevent division by zero */ - if (valgame.abbnum == 0) { - return false; // LCOV_EXCL_LINE - } +#if defined ADVENT_NOSAVE || defined ADVENT_AUTOSAVE + rspeak(SAVERESUME_DISABLED); + return GO_TOP; +#endif + FILE *fp = NULL; - /* Check for RNG overflow. Truncate */ - if (valgame.lcg_x >= LCG_M) { - valgame.lcg_x %= LCG_M; // LCOV_EXCL_LINE - } + 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; + } + } - /* Check for RNG underflow. Transpose */ - if (valgame.lcg_x < LCG_M) { - valgame.lcg_x = LCG_M + (valgame.lcg_x % LCG_M); - } + 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); + } - /* 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 - } - } + return restore(fp); +} - 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 - } - } +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; +#endif - /* Bounds check for dwarves */ - if (valgame.dtotal < 0 || valgame.dtotal > NDWARVES || - valgame.dkill < 0 || valgame.dkill > NDWARVES) { - return false; // LCOV_EXCL_LINE - } + 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; +} - /* Validate that we didn't die too many times in save */ - if (valgame.numdie >= NDEATHS) { - return false; // LCOV_EXCL_LINE - } +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 + */ - /* 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 - } + /* Prevent division by zero */ + if (valgame.abbnum == 0) { + 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 for RNG overflow. Truncate */ + if (valgame.lcg_x >= LCG_M) { + return false; + } - /* 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 - } - } + /* 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 + } + } - return true; + 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 (OBJECT_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 c0a0277..ad1c00a 100644 --- a/score.c +++ b/score.c @@ -1,145 +1,162 @@ /* * Scoring and wrap-up. * - * SPDX-FileCopyrightText: 1977, 2005 by Will Crowther and Don Woods - * SPDX-FileCopyrightText: 2017 by Eric S. Raymond + * 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; +int score(enum termination mode) { + /* 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; - 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; - } - } + /* 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 (!OBJECT_IS_STASHED(i) && !OBJECT_IS_NOTFOUND(i)) { + score += 2; + } + if (game.objects[i].place == LOC_BUILDING && + OBJECT_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; - if (game.bonus == splatter) - score += 25; - if (game.bonus == defeat) - score += 30; - if (game.bonus == victory) - score += 45; - } - mxscor += 45; + /* 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; + } + if (game.bonus == splatter) { + score += 25; + } + if (game.bonus == defeat) { + score += 30; + } + if (game.bonus == victory) { + score += 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; - } - if (game.novice) - score -= 5; - if (game.clshnt) - score -= 10; - score = score - game.trnluz - game.saved; + /* 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; + } + 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); +void terminate(enum termination 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 */ diff --git a/templates/dungeon.c.tpl b/templates/dungeon.c.tpl index a192adb..03640d5 100644 --- a/templates/dungeon.c.tpl +++ b/templates/dungeon.c.tpl @@ -54,6 +54,6 @@ const travelop_t travel[] = {{ const char *ignore = "{ignore}"; /* Dwarf starting locations */ -const int dwarflocs[NDWARFLOCS] = {{{dwarflocs}}}; +const int dwarflocs[NDWARVES] = {{{dwarflocs}}}; /* end */ diff --git a/templates/dungeon.h.tpl b/templates/dungeon.h.tpl index 2a26917..cf637c8 100644 --- a/templates/dungeon.h.tpl +++ b/templates/dungeon.h.tpl @@ -35,9 +35,8 @@ SPDX-License-Identifier: BSD-2-Clause #define COND_HOGRE 20 /* Trying to deal with ogre */ #define COND_HJADE 21 /* Found all treasures except jade */ -/* Count of dwarf starting locations */ -#define NDWARFLOCS {ndwarflocs} -extern const int dwarflocs[NDWARFLOCS]; +#define NDWARVES {ndwarflocs} // number of dwarves +extern const int dwarflocs[NDWARVES]; typedef struct {{ const char** strs; diff --git a/tests/Makefile b/tests/Makefile index c39bcbe..997096a 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -124,10 +124,6 @@ tap: count $(SGAMES) $(TEST_TARGETS) count: @echo 1..$(words $(TEST_TARGETS)) -foobar: - exit 1 - - # The following machinery tests the game against a binary made from # the advent430 branch To use it, switch to that branch, build the # binary, run it once to generate adventure.data, then switch back to @@ -174,7 +170,7 @@ oldcompare: echo 1..$(words $(shell ls *.log))) | $(TAPFILTER) @rm *.ochk *-new advent430 adventure.data -# List all NOMPARE tests. +# List all NOCOMPARE tests. residuals: @grep -n NOCOMPARE *.log diff --git a/tests/knife.chk b/tests/knife.chk new file mode 100644 index 0000000..76a3b6b --- /dev/null +++ b/tests/knife.chk @@ -0,0 +1,386 @@ + +Welcome to Adventure!! Would you like instructions? + +> no + +You are standing at the end of a road before a small brick building. +Around you is a forest. A small stream flows out of the building and +down a gully. + +> seed 1640849217 + +Seed set to 1640849217 + +You're in front of building. + +> e + +You are inside a building, a well house for a large spring. + +There are some keys on the ground here. + +There is a shiny brass lamp nearby. + +There is food here. + +There is a bottle of water here. + +> get lamp + +OK + +> xyzzy + +>>Foof!<< + +It is now pitch dark. If you proceed you will likely fall into a pit. + +> get rod + +OK + +> on + +Your lamp is now on. + +You are in a debris room filled with stuff washed in from the surface. +A low wide passage with cobbles becomes plugged with mud and debris +here, but an awkward canyon leads upward and west. In the mud someone +has scrawled, "MAGIC WORD XYZZY". + +> w + +You are in an awkward sloping east/west canyon. + +> w + +You are in a splendid chamber thirty feet high. The walls are frozen +rivers of orange stone. An awkward canyon and a good passage exit +from east and west sides of the chamber. + +A cheerful little bird is sitting here singing. + +> w + +At your feet is a small pit breathing traces of white mist. An east +passage ends here except for a small crack leading on. + +Rough stone steps lead down the pit. + +> d + +You are at one end of a vast hall stretching forward out of sight to +the west. There are openings to either side. Nearby, a wide stone +staircase leads downward. The hall is filled with wisps of white mist +swaying to and fro almost as if alive. A cold wind blows up the +staircase. There is a passage at the top of a dome behind you. + +Rough stone steps lead up the dome. + +> w + +You are on the east bank of a fissure slicing clear across the hall. +The mist is quite thick here, and the fissure is too wide to jump. + +> wave rod + +A crystal bridge now spans the fissure. + +> w + +You are on the west side of the fissure in the Hall of Mists. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You're on east bank of fissure. + +A crystal bridge spans the fissure. + +> w + +You're on west bank of fissure. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You're on east bank of fissure. + +A crystal bridge spans the fissure. + +> w + +You're on west bank of fissure. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You're on east bank of fissure. + +A crystal bridge spans the fissure. + +> w + +You're on west bank of fissure. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You're on east bank of fissure. + +A crystal bridge spans the fissure. + +> w + +You're on west bank of fissure. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You are on the east bank of a fissure slicing clear across the hall. +The mist is quite thick here, and the fissure is too wide to jump. + +A crystal bridge spans the fissure. + +> w + +You are on the west side of the fissure in the Hall of Mists. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You're on east bank of fissure. + +A crystal bridge spans the fissure. + +> w + +You're on west bank of fissure. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You're on east bank of fissure. + +A crystal bridge spans the fissure. + +> w + +You're on west bank of fissure. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You're on east bank of fissure. + +A crystal bridge spans the fissure. + +> w + +You're on west bank of fissure. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You're on east bank of fissure. + +A crystal bridge spans the fissure. + +> w + +You're on west bank of fissure. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You are on the east bank of a fissure slicing clear across the hall. +The mist is quite thick here, and the fissure is too wide to jump. + +A crystal bridge spans the fissure. + +> w + +You are on the west side of the fissure in the Hall of Mists. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You're on east bank of fissure. + +A crystal bridge spans the fissure. + +> w + +You're on west bank of fissure. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You're on east bank of fissure. + +A crystal bridge spans the fissure. + +> w + +You're on west bank of fissure. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You're on east bank of fissure. + +A crystal bridge spans the fissure. + +> w + +You're on west bank of fissure. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You're on east bank of fissure. + +A crystal bridge spans the fissure. + +> w + +You're on west bank of fissure. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You are on the east bank of a fissure slicing clear across the hall. +The mist is quite thick here, and the fissure is too wide to jump. + +A crystal bridge spans the fissure. + +> w + +You are on the west side of the fissure in the Hall of Mists. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +You're on east bank of fissure. + +A crystal bridge spans the fissure. + +> w + +You're on west bank of fissure. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> e + +A little dwarf just walked around a corner, saw you, threw a little +axe at you which missed, cursed, and ran away. + +You're on east bank of fissure. + +There is a little axe here. + +A crystal bridge spans the fissure. + +> w + +There is a threatening little dwarf in the room with you! + +One sharp nasty knife is thrown at you! + +It misses! + +You're on west bank of fissure. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> get knife + +The dwarves' knives vanish as they strike the walls of the cave. + +> look + +Sorry, but I am not allowed to give more detail. I will repeat the +long description of your location. + +There is a threatening little dwarf in the room with you! + +One sharp nasty knife is thrown at you! + +It misses! + +You are on the west side of the fissure in the Hall of Mists. + +There are diamonds here! + +A crystal bridge spans the fissure. + +> get knife + +I see no knife here. + +> quit + +Do you really want to quit now? + +> yes + +OK + +You scored 59 out of a possible 430, using 50 turns. + +Your score qualifies you as a novice class adventurer. + +To achieve the next higher rating, you need 62 more points. diff --git a/tests/knife.log b/tests/knife.log new file mode 100644 index 0000000..1c811ea --- /dev/null +++ b/tests/knife.log @@ -0,0 +1,57 @@ +## Test whether KNIVES_VANISH can be issued twice +# SPDX-FileCopyrightText: Copyright Eric S. Raymond +# SPDX-License-Identifier: BSD-2-Clause +#NOCOMPARE avoide spuriuous failure on second "get knife" +no +seed 1640849217 +e +get lamp +xyzzy +get rod +on +w +w +w +d +w +wave rod +w +e +w +e +w +e +w +e +w +e +w +e +w +e +w +e +w +e +w +e +w +e +w +e +w +e +w +e +w +e +w +e +w +e +w +get knife +look +get knife +quit +yes diff --git a/tests/tapdiffer b/tests/tapdiffer index 679867e..ea1cfa7 100755 --- a/tests/tapdiffer +++ b/tests/tapdiffer @@ -1,6 +1,4 @@ #! /bin/sh -# SPDX-FileCopyrightText: Copyright Eric S. Raymond -# SPDX-License-Identifier: MIT-0 # # tapdiffer - Render diff between input and checkfile as a TAP report # @@ -16,17 +14,23 @@ # OSD-compliant; otherwise the following SPDX tag incorporates the # MIT No Attribution license by reference. # +# SPDX-FileCopyrightText: (C) Eric S. Raymond +# SPDX-License-Identifier: MIT-0 +# # A newer version may be available at https://gitlab.com/esr/tapview # Check your last commit dqte for this file against the commit list # there to see if it might be a good idea to update. # -if [ "$1" = "-b" ] -then - diffopts=-ub - shift -else - diffopts=-u -fi +diffopts=-u +while getopts bn opt +do + case $opt in + b) diffopts=-ub;; + *) echo "tapdiffer: unknown option ${opt}."; exit 1;; + esac +done +# shellcheck disable=SC2004 +shift $(($OPTIND - 1)) legend=$1 checkfile=$2 @@ -37,7 +41,7 @@ if diff --text "${diffopts}" "${checkfile}" - >/tmp/tapdiff$$ then echo "ok - ${legend}" else - echo "not ok - ${checkfile}: ${legend}" + echo "not ok - ${legend}" if [ ! "${QUIET}" = 1 ] then echo " --- |" diff --git a/tests/tapview b/tests/tapview index b96b436..0a36f88 100755 --- a/tests/tapview +++ b/tests/tapview @@ -4,14 +4,15 @@ # This code is intended to be embedded in your project. The author # grants permission for it to be distributed under the prevailing # license of your project if you choose, provided that license is -# OSD-compliant; otherwise the following SPDX tag incorporates a -# license by reference. +# OSD-compliant; otherwise the following SPDX tag incorporates the +# MIT No Attribution license by reference. # -# SPDX-FileCopyrightText: Copyright Eric S. Raymond -# SPDX-License-Identifier: BSD-2-Clause +# SPDX-FileCopyrightText: (C) Eric S. Raymond +# SPDX-License-Identifier: MIT-0 # -# This is version 1.6 # A newer version may be available at https://gitlab.com/esr/tapview +# Check your last commit date for this file against the commit list +# there to see if it might be a good idea to update. # OK="." FAIL="F" @@ -19,13 +20,16 @@ SKIP="s" TODO_NOT_OK="x" TODO_OK="u" +LF=' +' + ship_char() { # shellcheck disable=SC2039 printf '%s' "$1" # https://www.etalabs.net/sh_tricks.html } ship_line() { - report="${report}${1}\n" + report="${report}${1}$LF" } ship_error() { @@ -34,7 +38,7 @@ ship_error() { then echo "" fi - report="${report}${1}\n" + report="${report}${1}$LF" echo "${report}" exit 1 } @@ -76,6 +80,10 @@ context_pop () { then ship_line "Expected $(context_get expect) tests but only ${testcount} ran." status=1 + elif [ "$(context_get plan)" != "" ] && [ "$(context_get expect)" -lt "$(context_get count)" ] + then + ship_line "${testcount} ran but $(context_get expect) expected." + status=1 fi }