Ready to ship 1.10
[wumpus.git] / superhack.c
1 /*
2  * superhack.c --- modern version of a classic adventure game
3  *
4  * Author: Eric S. Raymond <esr@snark.thyrsus.com>
5  *
6  * My update of a classic adventure game.  This code is no relation to
7  * the elaborate dungeon game called `Hack'.
8  *
9  * Any resemblance to persons living or dead is strictly coincidental.  And
10  * if you believe *that*...
11  *
12  * SPDX-License-Identifier: BSD-2-Clause
13  * SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com>
14  */
15
16 #include <ctype.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <sys/socket.h>
21 #include <time.h>
22 #include <unistd.h>
23
24 static int path[5];
25 static int j, k, scratchloc, pies;
26 static char inp[BUFSIZ]; /* common input buffer */
27
28 #define NUMBERS "0123456789"
29 #define YOU 0
30 #define RMS 1
31 #define STARLET1 2
32 #define STARLET2 3
33 #define DROID1 4
34 #define DROID2 5
35 #define LUSER1 6
36 #define LUSER2 7
37 #define LOCS 8
38 static int loc[LOCS];
39
40 #define NOT 0
41 #define WIN 1
42 #define LOSE -1
43 static int finished;
44
45 #define IGNORE(r)                                                              \
46         do {                                                                   \
47                 if (r)                                                         \
48                         ;                                                      \
49         } while (0)
50
51 static int cave[20][3] = {
52     {1, 4, 7},    {0, 2, 9},   {1, 3, 11},   {2, 4, 13},   {0, 3, 5},
53     {4, 6, 14},   {5, 7, 16},  {0, 6, 8},    {7, 9, 17},   {1, 8, 10},
54     {9, 11, 18},  {2, 10, 12}, {11, 13, 19}, {3, 12, 14},  {5, 13, 15},
55     {14, 16, 19}, {6, 15, 17}, {8, 16, 18},  {10, 17, 19}, {12, 15, 18},
56 };
57
58 #define FNA() (rand() % 20)
59
60 #define FNB() (rand() % 3)
61
62 #define FNC() (rand() % 4)
63
64 int getlet(prompt)
65 char *prompt;
66 {
67         (void)printf("%s? ", prompt);
68         if (fgets(inp, sizeof(inp), stdin)) {
69                 return (tolower(inp[0]));
70         } else {
71                 fputs("\n", stdout);
72                 exit(1);
73         }
74 }
75
76 #define PM(x) puts(x);
77
78 void print_instructions() {
79         PM("Welcome to `Hunt the Superhack'\n")
80
81         PM("   The superhack lives on the 9th floor of 45 Technology Square "
82            "in");
83         PM("Cambridge, Massachusetts.  Your mission is to throw a pie in his "
84            "face.\n");
85
86         PM("   First, you'll have to find him.  A botched experiment by an "
87            "MIT");
88         PM("physics group has regularized the floor's topology, so that each");
89         PM("room has exits to three other rooms.  (Look at a dodecahedron to");
90         PM("see how this works --- if you don't know what a dodecahedron is,");
91         PM("ask someone.)\n");
92
93         PM("You:");
94         PM("   Each turn you may move to an adjacent room or throw a pie.  If");
95         PM("you run out of pies, you lose.  Each pie can pass through up to");
96         PM("five rooms (connected by a continuous path from where you are).  "
97            "You");
98         PM("aim by telling the computer which rooms you want to throw "
99            "through.");
100         PM("If the path is incorrect (presumes a nonexistent connection) the ");
101         PM("pie moves at random.");
102
103         PM("   If a pie hits the superhack, you win. If it hits you, you "
104            "lose!\n");
105
106         (void)fputs("<Press return to continue>", stdout);
107         IGNORE(fgets(inp, sizeof(inp), stdin));
108         (void)putchar('\n');
109
110         PM("Hazards:");
111         PM("   Starlets --- two rooms contain lonely, beautiful women.  If "
112            "you");
113         PM("enter these, you will become fascinated and forget your mission "
114            "as");
115         PM("you engage in futile efforts to pick one up.  You weenie.");
116         PM("   Droids --- two rooms are guarded by experimental AI security ");
117         PM("droids.  If you enter either, the droid will grab you and hustle");
118         PM("you off to somewhere else, at random.");
119         PM("   Lusers --- two rooms contain hungry lusers.  If you blunder "
120            "into");
121         PM("either, they will eat one of your pies.");
122         PM("   Superhack --- the superhack is not bothered by hazards (the");
123         PM("lusers are in awe of him, he's programmed the droids to ignore "
124            "him,");
125         PM("and he has no sex life).  Usually he is hacking.  Two things can");
126         PM("interrupt him; you throwing a pie or you entering his room.\n");
127         PM("   On an interrupt, the superhack moves (3/4 chance) or stays "
128            "where");
129         PM("he is (1/4 chance).  After that, if he is where you are, he "
130            "flames");
131         PM("you and you lose!\n");
132
133         (void)fputs("<Press return to continue>", stdout);
134         (void)fgets(inp, sizeof(inp), stdin);
135         (void)putchar('\n');
136
137         PM("Warnings:");
138         PM("   When you are one room away from the superhack or a hazard,");
139         PM("the computer says:");
140         PM("   superhack:       \"I smell a superhack!\"");
141         PM("   security droid:  \"Droids nearby!\"");
142         PM("   starlet:         \"I smell perfume!\"");
143         PM("   luser:           \"Lusers nearby!\"");
144
145         PM("If you take too long finding the superhack, hazards may move.  "
146            "You");
147         PM("will get a warning when this happens.\n");
148
149         PM("Commands:");
150         PM("   Available commands are:\n");
151         PM("  ?            --- print long instructions.");
152         PM("  m <number>   --- move to room with given number.");
153         PM("  t <numbers>  --- throw through given rooms.");
154 #ifdef DEBUG
155         PM("  d            --- dump hazard locations.");
156 #endif /* DEBUG */
157         PM("\nThe list of room numbers after t must be space-separated.  "
158            "Anything");
159         PM("other than one of these commands displays a short help message.");
160 }
161
162 void move_hazard(where) int where;
163 {
164         int newloc;
165
166 retry:
167         newloc = FNA();
168         for (j = 0; j < LOCS; j++) {
169                 if (loc[j] == newloc) {
170                         goto retry;
171                 }
172         }
173
174         loc[where] = newloc;
175 }
176
177 void check_hazards() {
178         /* basic status report */
179         (void)printf("You are in room %d.  Exits lead to %d, %d, %d.  You have "
180                      "%d pies left.\n",
181                      loc[YOU] + 1, cave[loc[YOU]][0] + 1, cave[loc[YOU]][1] + 1,
182                      cave[loc[YOU]][2] + 1, pies);
183
184         /* maybe it's migration time */
185         if (FNA() == 0)
186                 switch (2 + FNB()) {
187                 case STARLET1:
188                 case STARLET2:
189                         PM("Swish, swish, swish --- starlets are moving!");
190                         move_hazard(STARLET1);
191                         move_hazard(STARLET2);
192                         break;
193
194                 case DROID1:
195                 case DROID2:
196                         PM("Clank, clank, clank --- droids are moving!");
197                         move_hazard(DROID1);
198                         move_hazard(DROID2);
199                         break;
200
201                 case LUSER1:
202                 case LUSER2:
203                         PM("Grumble, grumble, grumble --- lusers are moving!");
204                         move_hazard(LUSER1);
205                         move_hazard(LUSER2);
206                         break;
207                 }
208
209         /* display hazard warnings */
210         for (k = 0; k < 3; k++) {
211                 int room = cave[loc[YOU]][k];
212
213                 if (room == loc[RMS]) {
214                         (void)puts("I smell a superhack!");
215                 } else if (room == loc[STARLET1] || room == loc[STARLET2]) {
216                         (void)puts("I smell perfume!");
217                 } else if (room == loc[DROID1] || room == loc[DROID2]) {
218                         (void)puts("Droids nearby!");
219                 } else if (room == loc[LUSER1] || room == loc[LUSER2]) {
220                         (void)puts("Lusers nearby!");
221                 }
222         }
223 }
224
225 void throw() {
226         extern void check_shot(), move_superhack();
227         int j9;
228
229         j9 = sscanf(inp + strcspn(inp, NUMBERS), "%d %d %d %d %d", &path[0],
230                     &path[1], &path[2], &path[3], &path[4]);
231
232         if (j9 < 1) {
233                 PM("Sorry, I didn't see any room numbers after your throw "
234                    "command.");
235                 return;
236         }
237
238         for (k = 0; k < j9; k++) {
239                 if (k >= 2 && path[k] == path[k - 2]) {
240                         (void)puts(
241                             "Pies can't fly that crookedly --- try again.");
242                         return;
243                 }
244         }
245
246         scratchloc = loc[YOU];
247
248         for (k = 0; k < j9; k++) {
249                 int k1;
250
251                 for (k1 = 0; k1 < 3; k1++) {
252                         if (cave[scratchloc][k1] == path[k]) {
253                                 scratchloc = path[k];
254                                 check_shot();
255                                 if (finished != NOT) {
256                                         return;
257                                 }
258                         }
259                 }
260
261                 scratchloc = cave[scratchloc][FNB()];
262
263                 check_shot();
264         }
265
266         if (finished == NOT) {
267                 (void)puts("You missed.");
268
269                 scratchloc = loc[YOU];
270
271                 move_superhack();
272
273                 if (--pies <= 0) {
274                         finished = LOSE;
275                 }
276         }
277 }
278
279 void check_shot() {
280         if (scratchloc == loc[RMS]) {
281                 (void)puts("Splat!  You got the superhack!  You win.");
282                 finished = WIN;
283         }
284
285         else if (scratchloc == loc[YOU]) {
286                 (void)puts("Ugh!  The pie hit you!  You lose.");
287                 finished = LOSE;
288         }
289 }
290
291 void move_superhack() {
292         k = FNC();
293
294         if (k < 3) {
295                 loc[RMS] = cave[loc[RMS]][k];
296         }
297
298         if (loc[RMS] != loc[YOU]) {
299                 return;
300         }
301
302         (void)puts("The superhack flames you to a crisp.  You lose!");
303
304         finished = LOSE;
305 }
306
307 void move() {
308         if (sscanf(inp + strcspn(inp, NUMBERS), "%d", &scratchloc) < 1) {
309                 PM("Sorry, I didn't see a room number after your `m' command.");
310                 return;
311         }
312
313         scratchloc--;
314
315         for (k = 0; k < 3; k++) {
316                 if (cave[loc[YOU]][k] == scratchloc) {
317                         goto goodmove;
318                 }
319         }
320
321         PM("You can't get there from here!");
322         return;
323
324 goodmove:
325         loc[YOU] = scratchloc;
326
327         if (scratchloc == loc[RMS]) {
328                 PM("Yow! You interrupted the superhack.");
329                 move_superhack();
330         } else if (scratchloc == loc[STARLET1] || scratchloc == loc[STARLET2]) {
331                 PM("You begin to babble at an unimpressed starlet.  You lose!");
332                 finished = LOSE;
333         } else if (scratchloc == loc[DROID1] || scratchloc == loc[DROID2]) {
334                 PM("Zap --- security droid snatch.  Elsewheresville for you!");
335                 scratchloc = loc[YOU] = FNA();
336                 goto goodmove;
337         } else if (scratchloc == loc[LUSER1] || scratchloc == loc[LUSER2]) {
338                 PM("Munch --- lusers ate one of your pies!");
339                 pies--;
340         }
341 }
342
343 int main(int argc, char *argv[]) {
344         if (argc >= 2 && strcmp(argv[1], "-s") == 0) {
345                 srand(atoi(argv[2]));
346         } else {
347                 srand((int)time((long *)0));
348         }
349
350         for (;;) {
351         badlocs:
352                 for (j = 0; j < LOCS; j++) {
353                         loc[j] = FNA();
354                 }
355
356                 for (j = 0; j < LOCS; j++) {
357                         for (k = 0; k < LOCS; k++) {
358                                 if (j == k) {
359                                         continue;
360                                 } else if (loc[j] == loc[k]) {
361                                         goto badlocs;
362                                 }
363                         }
364                 }
365
366                 (void)puts("Hunt the Superhack");
367
368                 pies = 5;
369                 scratchloc = loc[YOU];
370                 finished = NOT;
371
372                 while (finished == NOT) {
373                         int c;
374
375                         check_hazards();
376
377                         c = getlet("Throw, move or help [t,m,?]");
378
379                         if (c == 't') {
380                                 throw();
381                         } else if (c == 'm') {
382                                 move();
383                         } else if (c == '?') {
384                                 print_instructions();
385                         }
386 #ifdef DEBUG
387                         else if (c == 'd') {
388                                 (void)printf("RMS is at %d, starlets at %d/%d, "
389                                              "droids %d/%d, lusers %d/%d\n",
390                                              loc[RMS] + 1, loc[STARLET1] + 1,
391                                              loc[STARLET2] + 1, loc[DROID1] + 1,
392                                              loc[DROID2] + 1, loc[LUSER1] + 1,
393                                              loc[LUSER2] + 1);
394                         }
395 #endif /* DEBUG */
396                         else {
397                                 PM("Available commands are:\n");
398                                 PM("  ?            --- print long "
399                                    "instructions.");
400                                 PM("  m <number>   --- move to room with given "
401                                    "number.");
402                                 PM("  t <numbers>  --- throw through given "
403                                    "rooms.");
404 #ifdef DEBUG
405                                 PM("  d            --- dump hazard locations.");
406 #endif /* DEBUG */
407                                 PM("The list of room numbers after t must be "
408                                    "space-separated");
409                         }
410
411                         (void)putchar('\n');
412                 }
413
414                 if (getlet("Play again") != 'y') {
415                         PM("Happy hacking!");
416                         break;
417                 }
418         }
419         return 0;
420 }
421
422 /* superhack.c ends here */