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