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