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