2 * There used to be a note that said this:
4 * The author - Don Woods - apologises for the style of the code; it
5 * is a result of running the original Fortran IV source through a
6 * home-brew Fortran-to-C converter.
8 * Now that the code has been restructured into something much closer
9 * to idiomatic C, the following is more appropriate:
11 * ESR apologizes for the remaing gotos (now confined to one function
12 * in this file - there used to be over 350 of them, *everywhere*),
13 * and for the offensive globals. Applying the Structured Program
14 * Theorem can be hard.
16 #define DEFINE_GLOBALS_FROM_INCLUDES
25 #include "linenoise/linenoise.h"
31 char rawbuf[LINESIZE], INLINE[LINESIZE + 1];
33 long AMBER, AXE, BACK, BATTERY, BEAR, BIRD, BLOOD,
34 BOTTLE, CAGE, CAVE, CAVITY, CHAIN, CHASM, CHEST,
35 CLAM, COINS, DOOR, DPRSSN, DRAGON, DWARF, EGGS,
36 EMERALD, ENTER, ENTRNC, FIND, FISSURE, FOOD,
37 GRATE, HINT, INVENT, JADE, KEYS,
38 KNIFE, LAMP, LOCK, LOOK, MAGAZINE,
39 MESSAG, MIRROR, NUGGET, NUL, OGRE, OIL, OYSTER,
40 PEARL, PILLOW, PLANT, PLANT2, PYRAMID, RESER, ROD, ROD2,
41 RUBY, RUG, SAPPH, SAY, SIGN, SNAKE,
42 STEPS, STREAM, THROW, TRIDENT, TROLL, TROLL2,
43 URN, VASE, VEND, VOLCANO, WATER;
45 FILE *logfp = NULL, *rfp = NULL;
46 bool oldstyle = false;
50 static void sig_handler(int signo)
52 if (signo == SIGINT) {
62 * Adventure (rev 2: 20 treasures)
64 * History: Original idea & 5-treasure version (adventures) by Willie Crowther
65 * 15-treasure version (adventure) by Don Woods, April-June 1977
66 * 20-treasure version (rev 2) by Don Woods, August 1978
67 * Errata fixed: 78/12/25
68 * Revived 2017 as Open Adventure.
71 static bool do_command(FILE *);
73 int main(int argc, char *argv[])
80 const char* opts = "l:or:s";
81 const char* usage = "Usage: %s [-l logfilename] [-o] [-r restorefilename] [-s] \n";
83 const char* opts = "l:os";
84 const char* usage = "Usage: %s [-l logfilename] [-o] [-s] \n";
86 while ((ch = getopt(argc, argv, opts)) != EOF) {
89 logfp = fopen(optarg, "w");
92 "advent: can't open logfile %s for write\n",
94 signal(SIGINT, sig_handler);
98 editline = prompt = false;
100 #ifndef ADVENT_NOSAVE
102 rfp = fopen(optarg, "r");
105 "advent: can't open save file %s for read\n",
107 signal(SIGINT, sig_handler);
117 " -l create a log file of your game named as specified'\n");
119 " -o 'oldstyle' (no prompt, no command editing, displays 'Initialising...')\n");
120 #ifndef ADVENT_NOSAVE
122 " -r restore from specified saved game file\n");
125 " -s suppress command editing\n");
131 linenoiseHistorySetMaxLen(350);
133 /* Initialize our LCG PRNG with parameters tested against
134 * Knuth vol. 2. by the original authors */
137 game.lcg_m = 1048576;
139 long seedval = (long)rand();
142 /* Initialize game variables */
145 /* Start-up, dwarf stuff */
146 game.zzword = RNDVOC(3, 0);
147 game.newloc = LOC_START;
148 game.loc = LOC_START;
149 game.limit = GAMELIMIT;
151 game.novice = YES(arbitrary_messages[WELCOME_YOU], arbitrary_messages[CAVE_NEARBY], arbitrary_messages[NO_MESSAGE]);
153 game.limit = NOVICELIMIT;
159 fprintf(logfp, "seed %ld\n", seedval);
161 /* interpret commands until EOF or interrupt */
163 if (!do_command(stdin))
166 /* show score and exit */
170 static bool fallback_handler(char *buf)
171 /* fallback handler for commands not handled by FORTRANish parser */
174 if (sscanf(buf, "seed %ld", &sv) == 1) {
176 printf("Seed set to %ld\n", sv);
177 // autogenerated, so don't charge user time for it.
179 // here we reconfigure any global game state that uses random numbers
180 game.zzword = RNDVOC(3, 0);
186 /* Check if this loc is eligible for any hints. If been here long
187 * enough, display. Ignore "HINTS" < 4 (special stuff, see database
189 static void checkhints(void)
191 if (conditions[game.loc] >= game.conds) {
192 for (int hint = 0; hint < NHINTS; hint++) {
193 if (game.hinted[hint])
195 if (!CNDBIT(game.loc, hint + 1 + COND_HBASE))
196 game.hintlc[hint] = -1;
198 /* Come here if he's been long enough at required loc(s) for some
200 if (game.hintlc[hint] >= hints[hint].turns) {
206 if (game.prop[GRATE] == GRATE_CLOSED && !HERE(KEYS))
208 game.hintlc[hint] = 0;
211 if (game.place[BIRD] == game.loc && TOTING(ROD) && game.oldobj == BIRD)
215 if (HERE(SNAKE) && !HERE(BIRD))
217 game.hintlc[hint] = 0;
220 if (game.atloc[game.loc] == 0 &&
221 game.atloc[game.oldloc] == 0 &&
222 game.atloc[game.oldlc2] == 0 &&
225 game.hintlc[hint] = 0;
228 if (game.prop[EMERALD] != -1 && game.prop[PYRAMID] == -1)
230 game.hintlc[hint] = 0;
237 game.hintlc[hint] = 0;
240 if (game.atloc[game.loc] == 0 &&
241 game.atloc[game.oldloc] == 0 &&
242 game.atloc[game.oldlc2] == 0)
246 i = ATDWRF(game.loc);
248 game.hintlc[hint] = 0;
251 if (HERE(OGRE) && i == 0)
255 if (game.tally == 1 && game.prop[JADE] < 0)
257 game.hintlc[hint] = 0;
260 BUG(HINT_NUMBER_EXCEEDS_GOTO_LIST);
264 /* Fall through to hint display */
265 game.hintlc[hint] = 0;
266 if (!YES(hints[hint].question, arbitrary_messages[NO_MESSAGE], arbitrary_messages[OK_MAN]))
268 rspeak(HINT_COST, hints[hint].penalty, hints[hint].penalty);
269 game.hinted[hint] = YES(arbitrary_messages[WANT_HINT], hints[hint].hint, arbitrary_messages[OK_MAN]);
270 if (game.hinted[hint] && game.limit > WARNTIME)
271 game.limit += WARNTIME * hints[hint].penalty;
277 static bool spotted_by_pirate(int i)
282 /* The pirate's spotted him. He leaves him alone once we've
283 * found chest. K counts if a treasure is here. If not, and
284 * tally=1 for an unseen chest, let the pirate be spotted. Note
285 * that game.place[CHEST] = LOC_NOWHERE might mean that he's thrown
286 * it to the troll, but in that case he's seen the chest
288 if (game.loc == game.chloc || game.prop[CHEST] >= 0)
291 bool movechest = false, robplayer = false;
292 for (int treasure = MINTRS; treasure <= MAXTRS; treasure++) {
293 /* Pirate won't take pyramid from plover room or dark
294 * room (too easy!). */
295 if (treasure == PYRAMID && (game.loc == PLAC[PYRAMID] || game.loc == PLAC[EMERALD])) {
298 if (TOTING(treasure) || HERE(treasure))
300 if (TOTING(treasure)) {
305 /* Force chest placement before player finds last treasure */
306 if (game.tally == 1 && snarfed == 0 && game.place[CHEST] == LOC_NOWHERE && HERE(LAMP) && game.prop[LAMP] == LAMP_BRIGHT) {
307 rspeak(PIRATE_SPOTTED);
310 /* Do things in this order (chest move before robbery) so chest is listed
311 * last at the maze location. */
313 MOVE(CHEST, game.chloc);
314 MOVE(MESSAG, game.chloc2);
315 game.dloc[PIRATE] = game.chloc;
316 game.odloc[PIRATE] = game.chloc;
317 game.dseen[PIRATE] = false;
319 /* You might get a hint of the pirate's presence even if the
320 * chest doesn't move... */
321 if (game.odloc[PIRATE] != game.dloc[PIRATE] && PCT(20))
322 rspeak(PIRATE_RUSTLES);
325 rspeak(PIRATE_POUNCES);
326 for (int treasure = MINTRS; treasure <= MAXTRS; treasure++) {
327 if (!(treasure == PYRAMID && (game.loc == PLAC[PYRAMID] || game.loc == PLAC[EMERALD]))) {
328 if (AT(treasure) && game.fixed[treasure] == 0)
329 CARRY(treasure, game.loc);
330 if (TOTING(treasure))
331 DROP(treasure, game.chloc);
339 static bool dwarfmove(void)
340 /* Dwarves move. Return true if player survives, false if he dies. */
342 int kk, stick, attack;
345 /* Dwarf stuff. See earlier comments for description of
346 * variables. Remember sixth dwarf is pirate and is thus
347 * very different except for motion rules. */
349 /* First off, don't let the dwarves follow him into a pit or
350 * a wall. Activate the whole mess the first time he gets as
351 * far as the hall of mists (loc 15). If game.newloc is
352 * forbidden to pirate (in particular, if it's beyond the
353 * troll bridge), bypass dwarf stuff. That way pirate can't
354 * steal return toll, and dwarves can't meet the bear. Also
355 * means dwarves won't follow him into dead end in maze, but
356 * c'est la vie. They'll wait for him outside the dead
358 if (game.loc == 0 || FORCED(game.loc) || CNDBIT(game.newloc, COND_NOARRR))
361 /* Dwarf activity level ratchets up */
362 if (game.dflag == 0) {
363 if (INDEEP(game.loc))
368 /* When we encounter the first dwarf, we kill 0, 1, or 2 of
369 * the 5 dwarves. If any of the survivors is at loc,
370 * replace him with the alternate. */
371 if (game.dflag == 1) {
372 if (!INDEEP(game.loc) || (PCT(95) && (!CNDBIT(game.loc, COND_NOBACK) || PCT(85))))
375 for (int i = 1; i <= 2; i++) {
376 int j = 1 + randrange(NDWARVES - 1);
380 for (int i = 1; i <= NDWARVES - 1; i++) {
381 if (game.dloc[i] == game.loc)
382 game.dloc[i] = DALTLC;
383 game.odloc[i] = game.dloc[i];
390 /* Things are in full swing. Move each dwarf at random,
391 * except if he's seen us he sticks with us. Dwarves stay
392 * deep inside. If wandering at random, they don't back up
393 * unless there's no alternative. If they don't have to
394 * move, they attack. And, of course, dead dwarves don't do
395 * much of anything. */
399 for (int i = 1; i <= NDWARVES; i++) {
400 if (game.dloc[i] == 0)
402 /* Fill tk array with all the places this dwarf might go. */
404 kk = KEY[game.dloc[i]];
407 game.newloc = MOD(labs(TRAVEL[kk]) / 1000, 1000);
408 /* Have we avoided a dwarf encounter? */
409 bool avoided = (SPECIAL(game.newloc) ||
410 !INDEEP(game.newloc) ||
411 game.newloc == game.odloc[i] ||
412 (j > 1 && game.newloc == tk[j - 1]) ||
414 game.newloc == game.dloc[i] ||
415 FORCED(game.newloc) ||
416 (i == PIRATE && CNDBIT(game.newloc, COND_NOARRR)) ||
417 labs(TRAVEL[kk]) / 1000000 == 100);
419 tk[j++] = game.newloc;
423 (TRAVEL[kk - 1] >= 0);
424 tk[j] = game.odloc[i];
427 j = 1 + randrange(j);
428 game.odloc[i] = game.dloc[i];
429 game.dloc[i] = tk[j];
430 game.dseen[i] = (game.dseen[i] && INDEEP(game.loc)) || (game.dloc[i] == game.loc || game.odloc[i] == game.loc);
431 if (!game.dseen[i]) continue;
432 game.dloc[i] = game.loc;
433 if (spotted_by_pirate(i))
435 /* This threatening little dwarf is in the room with him! */
437 if (game.odloc[i] == game.dloc[i]) {
439 if (game.knfloc >= 0)
440 game.knfloc = game.loc;
441 if (randrange(1000) < 95 * (game.dflag - 2))
446 /* Now we know what's happening. Let's tell the poor sucker about it.
447 * Note that various of the "knife" messages must have specific relative
448 * positions in the rspeak database. */
449 if (game.dtotal == 0)
451 rspeak(game.dtotal == 1 ? DWARF_SINGLE : DWARF_PACK, game.dtotal);
454 if (game.dflag == 2)game.dflag = 3;
456 rspeak(THROWN_KNIVES, attack);
457 rspeak(stick > 1 ? MULTIPLE_HITS : (stick == 1 ? ONE_HIT : NONE_HIT), stick);
459 rspeak(KNIFE_THROWN);
464 game.oldlc2 = game.loc;
468 /* "You're dead, Jim."
470 * If the current loc is zero, it means the clown got himself killed.
471 * We'll allow this maxdie times. NDEATHS is automatically set based
472 * on the number of snide messages available. Each death results in
473 * a message (81, 83, etc.) which offers reincarnation; if accepted,
474 * this results in message 82, 84, etc. The last time, if he wants
475 * another chance, he gets a snide remark as we exit. When
476 * reincarnated, all objects being carried get dropped at game.oldlc2
477 * (presumably the last place prior to being killed) without change
478 * of props. the loop runs backwards to assure that the bird is
479 * dropped before the cage. (this kluge could be changed once we're
480 * sure all references to bird and cage are done by keywords.) The
481 * lamp is a special case (it wouldn't do to leave it in the cave).
482 * It is turned off and left outside the building (only if he was
483 * carrying it, of course). He himself is left inside the building
484 * (and heaven help him if he tries to xyzzy back into the cave
485 * without the lamp!). game.oldloc is zapped so he can't just
488 static void croak(void)
489 /* Okay, he's dead. Let's get on with it. */
491 const char* query = obituaries[game.numdie].query;
492 const char* yes_response = obituaries[game.numdie].yes_response;
495 /* He died during closing time. No resurrection. Tally up a
497 rspeak(DEATH_CLOSING);
499 } else if (game.numdie == NDEATHS || !YES(query, yes_response, arbitrary_messages[OK_MAN]))
502 game.place[WATER] = game.place[OIL] = LOC_NOWHERE;
504 game.prop[LAMP] = LAMP_DARK;
505 for (int j = 1; j <= NOBJECTS; j++) {
506 int i = NOBJECTS + 1 - j;
508 /* Always leave lamp where it's accessible aboveground */
509 DROP(i, (i == LAMP) ? LOC_START : game.oldlc2);
512 game.loc = LOC_BUILDING;
513 game.oldloc = game.loc;
517 /* Given the current location in "game.loc", and a motion verb number in
518 * "motion", put the new location in "game.newloc". The current loc is saved
519 * in "game.oldloc" in case he wants to retreat. The current
520 * game.oldloc is saved in game.oldlc2, in case he dies. (if he
521 * does, game.newloc will be limbo, and game.oldloc will be what killed
522 * him, so we need game.oldlc2, which is the last place he was
525 static bool playermove(token_t verb, int motion)
527 int scratchloc, k2, kk = KEY[game.loc];
528 game.newloc = game.loc;
530 BUG(LOCATION_HAS_NO_TRAVEL_ENTRIES);
533 else if (motion == BACK) {
534 /* Handle "go back". Look for verb which goes from game.loc to
535 * game.oldloc, or to game.oldlc2 If game.oldloc has forced-motion.
536 * k2 saves entry -> forced loc -> previous loc. */
537 motion = game.oldloc;
539 motion = game.oldlc2;
540 game.oldlc2 = game.oldloc;
541 game.oldloc = game.loc;
543 if (motion == game.loc)k2 = FORGOT_PATH;
544 if (CNDBIT(game.loc, COND_NOBACK))k2 = TWIST_TURN;
547 scratchloc = MOD((labs(TRAVEL[kk]) / 1000), 1000);
548 if (scratchloc != motion) {
549 if (!SPECIAL(scratchloc)) {
550 if (FORCED(scratchloc) && MOD((labs(TRAVEL[KEY[scratchloc]]) / 1000), 1000) == motion)
553 if (TRAVEL[kk] >= 0) {
559 rspeak(NOT_CONNECTED);
564 motion = MOD(labs(TRAVEL[kk]), 1000);
566 break; /* fall through to ordinary travel */
572 } else if (motion == LOOK) {
573 /* Look. Can't give more detail. Pretend it wasn't dark
574 * (though it may now be dark) so he won't fall into a
575 * pit while staring into the gloom. */
577 rspeak(NO_MORE_DETAIL);
580 game.abbrev[game.loc] = 0;
582 } else if (motion == CAVE) {
583 /* Cave. Different messages depending on whether above ground. */
584 rspeak((OUTSID(game.loc) && game.loc != LOC_GRATE) ? FOLLOW_STREAM : NEED_DETAIL);
587 /* none of the specials */
588 game.oldlc2 = game.oldloc;
589 game.oldloc = game.loc;
592 /* ordinary travel */
594 scratchloc = labs(TRAVEL[kk]);
595 if (MOD(scratchloc, 1000) == 1 || MOD(scratchloc, 1000) == motion)
597 if (TRAVEL[kk] < 0) {
598 /* FIXME: Magic numbers! */
599 /* Non-applicable motion. Various messages depending on
601 int spk = CANT_APPLY;
602 if (motion >= 43 && motion <= 50)spk = BAD_DIRECTION;
603 if (motion == 29 || motion == 30)spk = BAD_DIRECTION;
604 if (motion == 7 || motion == 36 || motion == 37)spk = UNSURE_FACING;
605 if (motion == 11 || motion == 19)spk = NO_INOUT_HERE;
606 if (verb == FIND || verb == INVENT)spk = NEARBY;
607 if (motion == 62 || motion == 65)spk = NOTHING_HAPPENS;
608 if (motion == 17)spk = WHICH_WAY;
614 scratchloc = scratchloc / 1000;
618 * (ESR) This special-travel loop may have to be repeated if it includes
619 * the plover passage. Same deal for any future cases where we need to
620 * block travel and then redo it once the blocking condition has been
623 for (;;) { /* L12 loop */
625 game.newloc = scratchloc / 1000;
626 motion = MOD(game.newloc, 100);
627 if (!SPECIAL(game.newloc)) {
628 if (game.newloc <= 100) {
629 if (game.newloc == 0 || PCT(game.newloc))
631 /* else fall through */
633 if (TOTING(motion) || (game.newloc > 200 && AT(motion)))
635 /* else fall through */
636 } else if (game.prop[motion] != game.newloc / 100 - 3)
640 BUG(CONDITIONAL_TRAVEL_ENTRY_WITH_NO_ALTERATION);
642 game.newloc = labs(TRAVEL[kk]) / 1000;
644 (game.newloc == scratchloc);
645 scratchloc = game.newloc;
648 game.newloc = MOD(scratchloc, 1000);
649 if (!SPECIAL(game.newloc))
651 if (game.newloc <= 500) {
652 game.newloc -= SPECIALBASE;
653 switch (game.newloc) {
655 /* Travel 301. Plover-alcove passage. Can carry only
656 * emerald. Note: travel table must include "useless"
657 * entries going through passage, which can never be used for
658 * actual motion, but can be spotted by "go back". */
659 /* FIXME: Arithmetic on location numbers */
660 game.newloc = 99 + 100 - game.loc;
661 if (game.holdng > 1 || (game.holdng == 1 && !TOTING(EMERALD))) {
662 game.newloc = game.loc;
667 /* Travel 302. Plover transport. Drop the emerald (only use
668 * special travel if toting it), so he's forced to use the
669 * plover-passage to get it out. Having dropped it, go back and
670 * pretend he wasn't carrying it after all. */
671 DROP(EMERALD, game.loc);
674 BUG(CONDITIONAL_TRAVEL_ENTRY_WITH_NO_ALTERATION);
676 game.newloc = labs(TRAVEL[kk]) / 1000;
678 (game.newloc == scratchloc);
679 scratchloc = game.newloc;
680 continue; /* goto L12 */
682 /* Travel 303. Troll bridge. Must be done only as special
683 * motion so that dwarves won't wander across and encounter
684 * the bear. (They won't follow the player there because
685 * that region is forbidden to the pirate.) If
686 * game.prop(TROLL)=1, he's crossed since paying, so step out
687 * and block him. (standard travel entries check for
688 * game.prop(TROLL)=0.) Special stuff for bear. */
689 if (game.prop[TROLL] == 1) {
690 pspeak(TROLL,look, 1);
691 game.prop[TROLL] = 0;
693 MOVE(TROLL2 + NOBJECTS, 0);
694 MOVE(TROLL, PLAC[TROLL]);
695 MOVE(TROLL + NOBJECTS, FIXD[TROLL]);
697 game.newloc = game.loc;
700 game.newloc = PLAC[TROLL] + FIXD[TROLL] - game.loc;
701 if (game.prop[TROLL] == 0)game.prop[TROLL] = 1;
702 if (!TOTING(BEAR)) return true;
703 rspeak(BRIDGE_COLLAPSE);
704 game.prop[CHASM] = 1;
705 game.prop[TROLL] = 2;
706 DROP(BEAR, game.newloc);
707 game.fixed[BEAR] = -1;
709 game.oldlc2 = game.newloc;
714 BUG(SPECIAL_TRAVEL_500_GT_L_GT_300_EXCEEDS_GOTO_LIST);
716 break; /* Leave L12 loop */
720 /* FIXME: Arithmetic on location number, becoming a message number */
721 rspeak(game.newloc - 500);
722 game.newloc = game.loc;
726 static bool closecheck(void)
727 /* Handle the closing of the cave. The cave closes "clock1" turns
728 * after the last treasure has been located (including the pirate's
729 * chest, which may of course never show up). Note that the
730 * treasures need not have been taken yet, just located. Hence
731 * clock1 must be large enough to get out of the cave (it only ticks
732 * while inside the cave). When it hits zero, we branch to 10000 to
733 * start closing the cave, and then sit back and wait for him to try
734 * to get out. If he doesn't within clock2 turns, we close the cave;
735 * if he does try, we assume he panics, and give him a few additional
736 * turns to get frantic before we close. When clock2 hits zero, we
737 * branch to 11000 to transport him into the final puzzle. Note that
738 * the puzzle depends upon all sorts of random things. For instance,
739 * there must be no water or oil, since there are beanstalks which we
740 * don't want to be able to water, since the code can't handle it.
741 * Also, we can have no keys, since there is a grate (having moved
742 * the fixed object!) there separating him from all the treasures.
743 * Most of these problems arise from the use of negative prop numbers
744 * to suppress the object descriptions until he's actually moved the
747 if (game.tally == 0 && INDEEP(game.loc) && game.loc != 33)
750 /* When the first warning comes, we lock the grate, destroy
751 * the bridge, kill all the dwarves (and the pirate), remove
752 * the troll and bear (unless dead), and set "closng" to
753 * true. Leave the dragon; too much trouble to move it.
754 * from now until clock2 runs out, he cannot unlock the
755 * grate, move to any location outside the cave, or create
756 * the bridge. Nor can he be resurrected if he dies. Note
757 * that the snake is already gone, since he got to the
758 * treasure accessible only via the hall of the mountain
759 * king. Also, he's been in giant room (to get eggs), so we
760 * can refer to it. Also also, he's gotten the pearl, so we
761 * know the bivalve is an oyster. *And*, the dwarves must
762 * have been activated, since we've found chest. */
763 if (game.clock1 == 0) {
764 game.prop[GRATE] = GRATE_CLOSED;
765 game.prop[FISSURE] = 0;
766 for (int i = 1; i <= NDWARVES; i++) {
767 game.dseen[i] = false;
771 MOVE(TROLL + NOBJECTS, 0);
772 MOVE(TROLL2, PLAC[TROLL]);
773 MOVE(TROLL2 + NOBJECTS, FIXD[TROLL]);
775 if (game.prop[BEAR] != 3)DESTROY(BEAR);
776 game.prop[CHAIN] = 0;
777 game.fixed[CHAIN] = 0;
780 rspeak(CAVE_CLOSING);
784 } else if (game.clock1 < 0)
786 if (game.clock2 == 0) {
787 /* Once he's panicked, and clock2 has run out, we come here
788 * to set up the storage room. The room has two locs,
789 * hardwired as 115 (ne) and 116 (sw). At the ne end, we
790 * place empty bottles, a nursery of plants, a bed of
791 * oysters, a pile of lamps, rods with stars, sleeping
792 * dwarves, and him. At the sw end we place grate over
793 * treasures, snake pit, covey of caged birds, more rods, and
794 * pillows. A mirror stretches across one wall. Many of the
795 * objects come from known locations and/or states (e.g. the
796 * snake is known to have been destroyed and needn't be
797 * carried away from its old "place"), making the various
798 * objects be handled differently. We also drop all other
799 * objects he might be carrying (lest he have some which
800 * could cause trouble, such as the keys). We describe the
801 * flash of light and trundle back. */
802 game.prop[BOTTLE] = PUT(BOTTLE, LOC_NE, EMPTY_BOTTLE);
803 game.prop[PLANT] = PUT(PLANT, LOC_NE, 0);
804 game.prop[OYSTER] = PUT(OYSTER, LOC_NE, 0);
805 game.prop[LAMP] = PUT(LAMP, LOC_NE, 0);
806 game.prop[ROD] = PUT(ROD, LOC_NE, 0);
807 game.prop[DWARF] = PUT(DWARF, LOC_NE, 0);
809 game.oldloc = LOC_NE;
810 game.newloc = LOC_NE;
811 /* Leave the grate with normal (non-negative) property.
813 PUT(GRATE, LOC_SW, 0);
814 PUT(SIGN, LOC_SW, 0);
815 game.prop[SIGN] = ENDGAME_SIGN;
816 game.prop[SNAKE] = PUT(SNAKE, LOC_SW, 1);
817 game.prop[BIRD] = PUT(BIRD, LOC_SW, 1);
818 game.prop[CAGE] = PUT(CAGE, LOC_SW, 0);
819 game.prop[ROD2] = PUT(ROD2, LOC_SW, 0);
820 game.prop[PILLOW] = PUT(PILLOW, LOC_SW, 0);
822 game.prop[MIRROR] = PUT(MIRROR, LOC_NE, 0);
823 game.fixed[MIRROR] = LOC_SW;
825 for (int i = 1; i <= NOBJECTS; i++) {
838 static void lampcheck(void)
839 /* Check game limit and lamp timers */
841 if (game.prop[LAMP] == LAMP_BRIGHT)
844 /* Another way we can force an end to things is by having the
845 * lamp give out. When it gets close, we come here to warn him.
846 * First following arm checks if the lamp and fresh batteries are
847 * here, in which case we replace the batteries and continue.
848 * Second is for other cases of lamp dying. Eve after it goes
849 * out, he can explore outside for a while if desired. */
850 if (game.limit <= WARNTIME && HERE(BATTERY) && game.prop[BATTERY] == FRESH_BATTERIES && HERE(LAMP)) {
851 rspeak(REPLACE_BATTERIES);
852 game.prop[BATTERY] = DEAD_BATTERIES;
854 DROP(BATTERY, game.loc);
855 game.limit += BATTERYLIFE;
857 } else if (game.limit == 0) {
859 game.prop[LAMP] = LAMP_DARK;
862 } else if (game.limit <= WARNTIME) {
863 if (!game.lmwarn && HERE(LAMP)) {
865 int spk = GET_BATTERIES;
866 if (game.place[BATTERY] == LOC_NOWHERE)spk = LAMP_DIM;
867 if (game.prop[BATTERY] == DEAD_BATTERIES)
868 spk = MISSING_BATTERIES;
874 static void listobjects(void)
875 /* Print out descriptions of objects at this location. If
876 * not closing and property value is negative, tally off
877 * another treasure. Rug is special case; once seen, its
878 * game.prop is 1 (dragon on it) till dragon is killed.
879 * Similarly for chain; game.prop is initially 1 (locked to
880 * bear). These hacks are because game.prop=0 is needed to
883 if (!DARK(game.loc)) {
884 ++game.abbrev[game.loc];
885 for (int i = game.atloc[game.loc]; i != 0; i = game.link[i]) {
887 if (obj > NOBJECTS)obj = obj - NOBJECTS;
888 if (obj == STEPS && TOTING(NUGGET))
890 if (game.prop[obj] < 0) {
894 if (obj == RUG || obj == CHAIN)
897 /* Note: There used to be a test here to see whether the
898 * player had blown it so badly that he could never ever see
899 * the remaining treasures, and if so the lamp was zapped to
900 * 35 turns. But the tests were too simple-minded; things
901 * like killing the bird before the snake was gone (can never
902 * see jewelry), and doing it "right" was hopeless. E.G.,
903 * could cross troll bridge several times, using up all
904 * available treasures, breaking vase, using coins to buy
905 * batteries, etc., and eventually never be able to get
906 * across again. If bottle were left on far side, could then
907 * never get eggs or trident, and the effects propagate. So
908 * the whole thing was flushed. anyone who makes such a
909 * gross blunder isn't likely to find everything else anyway
910 * (so goes the rationalisation). */
912 int kk = game.prop[obj];
913 if (obj == STEPS && game.loc == game.fixed[STEPS])
915 pspeak(obj, look, kk);
920 static bool do_command(FILE *cmdin)
921 /* Get and execute a command */
926 static struct command_t command;
929 /* Can't leave cave once it's closing (except by main office). */
930 if (OUTSID(game.newloc) && game.newloc != 0 && game.closng) {
932 game.newloc = game.loc;
933 if (!game.panic)game.clock2 = PANICTIME;
937 /* See if a dwarf has seen him and has come from where he
938 * wants to go. If so, the dwarf's blocking his way. If
939 * coming from place forbidden to pirate (dwarves rooted in
940 * place) let him get out (and attacked). */
941 if (game.newloc != game.loc && !FORCED(game.loc) && !CNDBIT(game.loc, COND_NOARRR)) {
942 for (size_t i = 1; i <= NDWARVES - 1; i++) {
943 if (game.odloc[i] == game.newloc && game.dseen[i]) {
944 game.newloc = game.loc;
950 game.loc = game.newloc;
955 /* Describe the current location and (maybe) get next command. */
960 const char* msg = locations[game.loc].description.small;
961 if (MOD(game.abbrev[game.loc], game.abbnum) == 0 || msg == 0)
962 msg = locations[game.loc].description.big;
963 if (!FORCED(game.loc) && DARK(game.loc)) {
964 /* The easiest way to get killed is to fall into a pit in
966 if (game.wzdark && PCT(35)) {
968 game.oldlc2 = game.loc;
970 continue; /* back to top of main interpreter loop */
972 msg = arbitrary_messages[PITCH_DARK];
974 if (TOTING(BEAR))rspeak(TAME_BEAR);
976 if (FORCED(game.loc)) {
977 if (playermove(command.verb, 1))
980 continue; /* back to top of main interpreter loop */
982 if (game.loc == LOC_Y2 && PCT(25) && !game.closng)
989 game.oldobj = command.obj;
995 /* If closing time, check for any objects being toted with
996 * game.prop < 0 and set the prop to -1-game.prop. This way
997 * objects won't be described until they've been picked up
998 * and put down separate from their respective piles. Don't
999 * tick game.clock1 unless well into cave (and not at Y2). */
1001 if (game.prop[OYSTER] < 0 && TOTING(OYSTER))
1002 pspeak(OYSTER, look, 1);
1003 for (size_t i = 1; i <= NOBJECTS; i++) {
1004 if (TOTING(i) && game.prop[i] < 0)
1005 game.prop[i] = -1 - game.prop[i];
1008 game.wzdark = DARK(game.loc);
1009 if (game.knfloc > 0 && game.knfloc != game.loc)
1012 /* This is where we get a new command from the user */
1013 if (!GETIN(cmdin, &command.wd1, &command.wd1x, &command.wd2, &command.wd2x))
1016 /* Every input, check "game.foobar" flag. If zero, nothing's
1017 * going on. If pos, make neg. If neg, he skipped a word,
1018 * so make it zero. */
1020 game.foobar = (game.foobar > 0 ? -game.foobar : 0);
1023 /* If a turn threshold has been met, apply penalties and tell
1024 * the player about it. */
1025 for (int i = 0; i < NTHRESHOLDS; ++i)
1027 if (game.turns == turn_thresholds[i].threshold + 1)
1029 game.trnluz += turn_thresholds[i].point_loss;
1030 speak(turn_thresholds[i].message);
1034 if (command.verb == SAY && command.wd2 > 0)
1036 if (command.verb == SAY) {
1037 command.part = transitive;
1046 V1 = VOCAB(command.wd1, -1);
1047 V2 = VOCAB(command.wd2, -1);
1048 if (V1 == ENTER && (V2 == STREAM || V2 == 1000 + WATER)) {
1049 if (LIQLOC(game.loc) == WATER) {
1052 rspeak(WHERE_QUERY);
1056 if (V1 == ENTER && command.wd2 > 0) {
1057 command.wd1 = command.wd2;
1058 command.wd1x = command.wd2x;
1059 wordclear(&command.wd2);
1061 /* FIXME: Magic numbers */
1062 if (!((V1 != 1000 + WATER && V1 != 1000 + OIL) ||
1063 (V2 != 1000 + PLANT && V2 != 1000 + DOOR))) {
1065 command.wd2 = MAKEWD(WORD_POUR);
1067 if (V1 == 1000 + CAGE && V2 == 1000 + BIRD && HERE(CAGE) && HERE(BIRD))
1068 command.wd1 = MAKEWD(WORD_CATCH);
1071 if (wordeq(command.wd1, MAKEWD(WORD_WEST))) {
1073 if (game.iwest == 10)
1076 if (wordeq(command.wd1, MAKEWD(WORD_GO)) && !wordempty(command.wd2)) {
1078 rspeak(GO_UNNEEDED);
1081 defn = VOCAB(command.wd1, -1);
1083 /* Gee, I don't understand. */
1084 if (fallback_handler(rawbuf))
1086 rspeak(DONT_KNOW, command.wd1, command.wd1x);
1089 kmod = MOD(defn, 1000);
1090 switch (defn / 1000) {
1092 if (playermove(command.verb, kmod))
1095 continue; /* back to top of main interpreter loop */
1097 command.part = unknown;
1101 command.part = intransitive;
1102 command.verb = kmod;
1108 BUG(VOCABULARY_TYPE_N_OVER_1000_NOT_BETWEEN_0_AND_3);
1112 switch (action(cmdin, &command)) {
1116 playermove(command.verb, NUL);
1119 continue; /* back to top of main interpreter loop */
1129 /* Get second word for analysis. */
1130 command.wd1 = command.wd2;
1131 command.wd1x = command.wd2x;
1132 wordclear(&command.wd2);
1135 /* Random intransitive verbs come here. Clear obj just in case
1136 * (see attack()). */
1137 rspeak(DO_WHAT, command.wd1, command.wd1x);
1141 /* Oh dear, he's disturbed the dwarves. */
1142 rspeak(DWARVES_AWAKEN);
1145 BUG(ACTION_RETURNED_PHASE_CODE_BEYOND_END_OF_SWITCH);