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