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