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