#include <sys/wait.h>
#include <err.h>
#include <fcntl.h>
#include <paths.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "pathnames.h"
#define MAX_LINKS_IN_ROOM 25
#define MAX_ROOMS_IN_CAVE 250
#define ROOMS_IN_CAVE 20
#define MIN_ROOMS_IN_CAVE 10
#define LINKS_IN_ROOM 3
#define NUMBER_OF_ARROWS 5
#define PIT_COUNT 3
#define BAT_COUNT 3
#define EASY 1
#define HARD 2
#define plural(n) (n == 1 ? "" : "s")
struct room_record {
int tunnel[MAX_LINKS_IN_ROOM];
int has_a_pit, has_a_bat;
} cave[MAX_ROOMS_IN_CAVE+1];
int player_loc = -1;
int wumpus_loc = -1;
int level = EASY;
int arrows_left;
int oldstyle = 0;
#ifdef DEBUG
int debug = 0;
#endif
int pit_num = -1;
int bat_num = -1;
int room_num = ROOMS_IN_CAVE;
int link_num = LINKS_IN_ROOM;
int arrow_num = NUMBER_OF_ARROWS;
char answer[20];
int bats_nearby(void);
void cave_init(void);
void clear_things_in_cave(void);
void display_room_stats(void);
void dodecahedral_cave_init(void);
int gcd(int, int);
int getans(const char *);
void initialize_things_in_cave(void);
void instructions(void);
int int_compare(const void *, const void *);
void kill_wump(void);
int main(int, char **);
int move_to(const char *);
void move_wump(void);
void no_arrows(void);
void pit_kill(void);
void pit_kill_bat(void);
int pit_nearby(void);
void pit_survive(void);
int shoot(char *);
void shoot_self(void);
int take_action(void);
__dead void usage(void);
void wump_kill(void);
void wump_bat_kill(void);
void wump_walk_kill(void);
int wump_nearby(void);
int
main(int argc, char *argv[])
{
int c;
if (pledge("stdio rpath proc exec", NULL) == -1)
err(1, "pledge");
#ifdef DEBUG
while ((c = getopt(argc, argv, "a:b:hop:r:t:d")) != -1)
#else
while ((c = getopt(argc, argv, "a:b:hop:r:t:")) != -1)
#endif
switch (c) {
case 'a':
arrow_num = atoi(optarg);
break;
case 'b':
bat_num = atoi(optarg);
break;
#ifdef DEBUG
case 'd':
debug = 1;
break;
#endif
case 'h':
level = HARD;
break;
case 'o':
oldstyle = 1;
break;
case 'p':
pit_num = atoi(optarg);
break;
case 'r':
room_num = atoi(optarg);
if (room_num < MIN_ROOMS_IN_CAVE)
errx(1,
"no self-respecting wumpus would live in such a small cave!");
if (room_num > MAX_ROOMS_IN_CAVE)
errx(1,
"even wumpii can't furnish caves that large!");
break;
case 't':
link_num = atoi(optarg);
if (link_num < 2)
errx(1,
"wumpii like extra doors in their caves!");
break;
default:
usage();
}
if (oldstyle) {
room_num = 20;
link_num = 3;
if (bat_num < 0)
bat_num = 2;
if (pit_num < 0)
pit_num = 2;
} else {
if (bat_num < 0)
bat_num = BAT_COUNT;
if (pit_num < 0)
pit_num = PIT_COUNT;
}
if (link_num > MAX_LINKS_IN_ROOM ||
link_num > room_num - (room_num / 4))
errx(1,
"too many tunnels! The cave collapsed!\n(Fortunately, the wumpus escaped!)");
if (level == HARD) {
if (room_num / 2 - bat_num)
bat_num += arc4random_uniform(room_num / 2 - bat_num);
if (room_num / 2 - pit_num)
pit_num += arc4random_uniform(room_num / 2 - pit_num);
}
if (bat_num > room_num / 2 - 1)
errx(1,
"the wumpus refused to enter the cave, claiming it was too crowded!");
if (pit_num > room_num / 2 - 1)
errx(1,
"the wumpus refused to enter the cave, claiming it was too dangerous!");
instructions();
if (pledge("stdio", NULL) == -1)
err(1, "pledge");
if (oldstyle)
dodecahedral_cave_init();
else
cave_init();
(void)printf(
"\nYou're in a cave with %d rooms and %d tunnels leading from each room.\n\
There are %d bat%s and %d pit%s scattered throughout the cave, and your\n\
quiver holds %d custom super anti-evil Wumpus arrows. Good luck.\n",
room_num, link_num, bat_num, plural(bat_num), pit_num,
plural(pit_num), arrow_num);
for (;;) {
initialize_things_in_cave();
arrows_left = arrow_num;
do {
display_room_stats();
(void)printf("Move or shoot? (m-s) ");
(void)fflush(stdout);
(void)fpurge(stdin);
if (!fgets(answer, sizeof(answer), stdin))
break;
} while (!take_action());
(void)fpurge(stdin);
if (!getans("\nCare to play another game? (y-n) ")) {
(void)printf("\n");
return 0;
}
clear_things_in_cave();
if (!getans("In the same cave? (y-n) ")) {
if (oldstyle)
dodecahedral_cave_init();
else
cave_init();
}
}
}
void
display_room_stats(void)
{
int i;
(void)printf(
"\nYou are in room %d of the cave, and have %d arrow%s left.\n",
player_loc, arrows_left, plural(arrows_left));
if (bats_nearby())
(void)printf("*rustle* *rustle* (must be bats nearby)\n");
if (pit_nearby())
(void)printf("*whoosh* (I feel a draft from some pits).\n");
if (wump_nearby())
(void)printf("*sniff* (I can smell the evil Wumpus nearby!)\n");
(void)printf("There are tunnels to rooms %d, ",
cave[player_loc].tunnel[0]);
for (i = 1; i < link_num - 1; i++)
(void)printf("%d, ", cave[player_loc].tunnel[i]);
(void)printf("and %d.\n", cave[player_loc].tunnel[link_num - 1]);
}
int
take_action(void)
{
switch (*answer) {
case 'M':
case 'm':
return(move_to(answer + 1));
case 'S':
case 's':
return(shoot(answer + 1));
case 'Q':
case 'q':
case 'x':
exit(0);
case '\n':
return(0);
}
if (arc4random_uniform(15) == 1)
(void)printf("Que pasa?\n");
else
(void)printf("I don't understand!\n");
return(0);
}
int
move_to(const char *room_number)
{
int i, just_moved_by_bats, next_room, tunnel_available;
tunnel_available = just_moved_by_bats = 0;
next_room = atoi(room_number);
while (next_room < 1 || next_room > room_num ) {
if (next_room < 0 && next_room != -1)
(void)printf("Sorry, but we're constrained to a semi-Euclidean cave!\n");
if (next_room > room_num )
(void)printf("What? The cave surely isn't quite that big!\n");
(void)printf("To which room do you wish to move? ");
(void)fflush(stdout);
if (!fgets(answer, sizeof(answer), stdin))
return(1);
next_room = atoi(answer);
}
tunnel_available = 0;
for (i = 0; i < link_num; i++)
if (cave[player_loc].tunnel[i] == next_room)
tunnel_available = 1;
if (!tunnel_available) {
(void)printf("*Oof!* (You hit the wall)\n");
if (arc4random_uniform(6) == 1) {
(void)printf("Your colorful comments awaken the wumpus!\n");
move_wump();
if (wumpus_loc == player_loc) {
wump_walk_kill();
return(1);
}
}
return(0);
}
player_loc = next_room;
for (;;) {
if (next_room == wumpus_loc) {
if (just_moved_by_bats)
wump_bat_kill();
else
wump_kill();
return(1);
}
if (cave[next_room].has_a_pit) {
if (arc4random_uniform(12) < 2) {
pit_survive();
return(0);
} else {
if (just_moved_by_bats)
pit_kill_bat();
else
pit_kill();
return(1);
}
}
if (cave[next_room].has_a_bat) {
(void)printf(
"*flap* *flap* *flap* (humongous bats pick you up and move you%s!)\n",
just_moved_by_bats ? " again": "");
next_room = player_loc =
arc4random_uniform(room_num) + 1;
just_moved_by_bats = 1;
}
else
break;
}
return(0);
}
int
shoot(char *room_list)
{
int chance, next, roomcnt;
int j, arrow_location, link, ok;
char *p;
arrow_location = player_loc;
for (roomcnt = 1;; ++roomcnt, room_list = NULL) {
if (!(p = strtok(room_list, " \t\n"))) {
if (roomcnt == 1) {
(void)printf("Enter a list of rooms to shoot into:\n");
(void)fflush(stdout);
if (!(p = strtok(fgets(answer, sizeof(answer), stdin),
" \t\n"))) {
(void)printf(
"The arrow falls to the ground at your feet.\n");
return(0);
}
} else
break;
}
if (roomcnt > 5) {
(void)printf(
"The arrow wavers in its flight and can go no further than room %d!\n",
arrow_location);
break;
}
next = atoi(p);
if (next == 0)
break;
chance = arc4random_uniform(10);
if (roomcnt == 4 && chance < 2) {
(void)printf(
"Your finger slips on the bowstring! *twaaaaaang*\n\
The arrow is weakly shot and can go no further than room %d!\n",arrow_location);
break;
} else if (roomcnt == 5 && chance < 6) {
(void)printf(
"The arrow wavers in its flight and can go no further than room %d!\n",
arrow_location);
break;
}
for (j = 0, ok = 0; j < link_num; j++)
if (cave[arrow_location].tunnel[j] == next)
ok = 1;
if (ok) {
arrow_location = next;
} else {
link = (arc4random_uniform(link_num));
if (cave[arrow_location].tunnel[link] == player_loc)
(void)printf(
"*thunk* The arrow can't find a way from %d to %d and flies back into\n\
your room!\n",
arrow_location, next);
else
(void)printf(
"*thunk* The arrow can't find a way from %d to %d and flies randomly\n\
into room %d!\n", arrow_location, next, cave[arrow_location].tunnel[link]);
arrow_location = cave[arrow_location].tunnel[link];
}
if (arrow_location == wumpus_loc) {
kill_wump();
return(1);
}
if (arrow_location == player_loc) {
shoot_self();
return(1);
}
}
if (!--arrows_left) {
no_arrows();
return(1);
}
{
static int lastchance = 2;
lastchance += 2;
if (arc4random_uniform(level == EASY ? 12 : 9) < lastchance) {
move_wump();
if (wumpus_loc == player_loc) {
wump_walk_kill();
lastchance = arc4random_uniform(3);
return(1);
}
}
}
(void)printf("The arrow hit nothing.\n");
return(0);
}
int
gcd(int a, int b)
{
int r;
if (!(r = (a % b)))
return(b);
return(gcd(b, r));
}
void
cave_init(void)
{
int i, j, k, link;
int delta;
for (i = 1; i <= room_num; ++i)
for (j = 0; j < link_num ; ++j)
cave[i].tunnel[j] = -1;
do {
delta = arc4random_uniform(room_num - 1) + 1;
} while (gcd(room_num, delta + 1) != 1);
for (i = 1; i <= room_num; ++i) {
link = ((i + delta) % room_num) + 1;
cave[i].tunnel[0] = link;
cave[link].tunnel[1] = i;
}
for (i = 1; i <= room_num; i++)
for (j = 2; j < link_num ; j++) {
if (cave[i].tunnel[j] != -1)
continue;
try_again: link = arc4random_uniform(room_num) + 1;
for (k = 0; k < j; k++)
if (cave[i].tunnel[k] == link)
goto try_again;
if (link == i)
goto try_again;
cave[i].tunnel[j] = link;
if (arc4random() % 2 == 1)
continue;
for (k = 0; k < link_num; ++k) {
if (cave[link].tunnel[k] == i)
k = link_num;
else {
if (cave[link].tunnel[k] == -1) {
cave[link].tunnel[k] = i;
k = link_num;
}
}
}
}
for (i = 1; i <= room_num; ++i)
qsort(cave[i].tunnel, (u_int)link_num,
sizeof(cave[i].tunnel[0]), int_compare);
#ifdef DEBUG
if (debug)
for (i = 1; i <= room_num; ++i) {
(void)printf("<room %d has tunnels to ", i);
for (j = 0; j < link_num; ++j)
(void)printf("%d ", cave[i].tunnel[j]);
(void)printf(">\n");
}
#endif
}
void
dodecahedral_cave_init(void)
{
int vert[20][3] = {
{1, 4, 7},
{0, 2, 9},
{1, 3, 11},
{2, 4, 13},
{0, 3, 5},
{4, 6, 14},
{5, 7, 16},
{0, 6, 8},
{7, 9, 17},
{1, 8, 10},
{9, 11, 18},
{2, 10, 12},
{11, 13, 19},
{3, 12, 14},
{5, 13, 15},
{14, 16, 19},
{6, 15, 17},
{8, 16, 18},
{10, 17, 19},
{12, 15, 18},
};
int loc[20];
int i, j, temp;
if (room_num != 20 || link_num != 3)
errx(1, "wrong parameters for dodecahedron");
for (i = 0; i < 20; i++)
loc[i] = i;
for (i = 0; i < 20; i++) {
j = arc4random_uniform(20 - i);
if (j) {
temp = loc[i];
loc[i] = loc[i + j];
loc[i + j] = temp;
}
}
for (i = 0; i < 20; i++) {
for (j = 0; j < 3; j++)
cave[loc[i] + 1].tunnel[j] = loc[vert[i][j]] + 1;
}
for (i = 1; i <= room_num; ++i)
qsort(cave[i].tunnel, (u_int)link_num,
sizeof(cave[i].tunnel[0]), int_compare);
#ifdef DEBUG
if (debug)
for (i = 1; i <= room_num; ++i) {
(void)printf("<room %d has tunnels to ", i);
for (j = 0; j < link_num; ++j)
(void)printf("%d ", cave[i].tunnel[j]);
(void)printf(">\n");
}
#endif
}
void
clear_things_in_cave(void)
{
int i;
for (i = 1; i <= room_num; ++i)
cave[i].has_a_bat = cave[i].has_a_pit = 0;
}
void
initialize_things_in_cave(void)
{
int i, loc;
for (i = 0; i < bat_num; ++i) {
do {
loc = arc4random_uniform(room_num) + 1;
} while (cave[loc].has_a_bat);
cave[loc].has_a_bat = 1;
#ifdef DEBUG
if (debug)
(void)printf("<bat in room %d>\n", loc);
#endif
}
for (i = 0; i < pit_num; ++i) {
do {
loc = arc4random_uniform(room_num) + 1;
} while (cave[loc].has_a_pit || cave[loc].has_a_bat);
cave[loc].has_a_pit = 1;
#ifdef DEBUG
if (debug)
(void)printf("<pit in room %d>\n", loc);
#endif
}
wumpus_loc = arc4random_uniform(room_num) + 1;
#ifdef DEBUG
if (debug)
(void)printf("<wumpus in room %d>\n", wumpus_loc);
#endif
do {
player_loc = arc4random_uniform(room_num) + 1;
} while (player_loc == wumpus_loc || cave[player_loc].has_a_pit ||
cave[player_loc].has_a_bat);
}
int
getans(const char *prompt)
{
char buf[20];
for (;;) {
(void)printf("%s", prompt);
(void)fflush(stdout);
if (!fgets(buf, sizeof(buf), stdin))
return(0);
if (*buf == 'N' || *buf == 'n')
return(0);
if (*buf == 'Y' || *buf == 'y')
return(1);
(void)printf(
"I don't understand your answer; please enter 'y' or 'n'!\n");
}
}
int
bats_nearby(void)
{
int i;
for (i = 0; i < link_num; ++i)
if (cave[cave[player_loc].tunnel[i]].has_a_bat)
return(1);
return(0);
}
int
pit_nearby(void)
{
int i;
for (i = 0; i < link_num; ++i)
if (cave[cave[player_loc].tunnel[i]].has_a_pit)
return(1);
return(0);
}
int
wump_nearby(void)
{
int i, j;
for (i = 0; i < link_num; ++i) {
if (cave[player_loc].tunnel[i] == wumpus_loc)
return(1);
for (j = 0; j < link_num; ++j)
if (cave[cave[player_loc].tunnel[i]].tunnel[j] ==
wumpus_loc)
return(1);
}
return(0);
}
void
move_wump(void)
{
wumpus_loc = cave[wumpus_loc].tunnel[arc4random_uniform(link_num)];
#ifdef DEBUG
if (debug)
(void)printf("Wumpus moved to room %d\n",wumpus_loc);
#endif
}
int
int_compare(const void *a, const void *b)
{
return(*(const int *)a < *(const int *)b ? -1 : 1);
}
void
instructions(void)
{
const char *pager;
pid_t pid;
int status;
int fd;
if (!getans("Instructions? (y-n) "))
return;
if ((fd = open(_PATH_WUMPINFO, O_RDONLY)) == -1) {
(void)printf(
"Sorry, but the instruction file seems to have disappeared in a\n\
puff of greasy black smoke! (poof)\n");
return;
}
if (!isatty(1))
pager = "/bin/cat";
else {
if (!(pager = getenv("PAGER")) || (*pager == 0))
pager = _PATH_PAGER;
}
switch (pid = fork()) {
case 0:
if (dup2(fd, 0) == -1)
err(1, "dup2");
(void)execl(_PATH_BSHELL, "sh", "-c", pager, (char *)NULL);
err(1, "exec sh -c %s", pager);
case -1:
err(1, "fork");
default:
(void)waitpid(pid, &status, 0);
close(fd);
break;
}
}
void
usage(void)
{
(void)fprintf(stderr,
"usage: %s [-ho] [-a arrows] [-b bats] [-p pits] "
"[-r rooms] [-t tunnels]\n", getprogname());
exit(1);
}
void
wump_kill(void)
{
(void)printf(
"*ROAR* *chomp* *snurfle* *chomp*!\n\
Much to the delight of the Wumpus, you walk right into his mouth,\n\
making you one of the easiest dinners he's ever had! For you, however,\n\
it's a rather unpleasant death. The only good thing is that it's been\n\
so long since the evil Wumpus cleaned his teeth that you immediately\n\
pass out from the stench!\n");
}
void
wump_walk_kill(void)
{
(void)printf(
"Oh dear. All the commotion has managed to awaken the evil Wumpus, who\n\
has chosen to walk into this very room! Your eyes open wide as they behold\n\
the great sucker-footed bulk that is the Wumpus; the mouth of the Wumpus\n\
also opens wide as the evil beast beholds dinner.\n\
*ROAR* *chomp* *snurfle* *chomp*!\n");
}
void
wump_bat_kill(void)
{
(void)printf(
"Flap, flap. The bats fly you right into the room with the evil Wumpus!\n\
The Wumpus, seeing a fine dinner flying overhead, takes a swipe at you,\n\
and the bats, not wanting to serve as hors d'oeuvres, drop their\n\
soon-to-be-dead weight and take off in the way that only bats flying out\n\
of a very bad place can. As you fall towards the large, sharp, and very\n\
foul-smelling teeth of the Wumpus, you think, \"Man, this is going to hurt.\"\n\
It does.\n");
}
void
kill_wump(void)
{
(void)printf(
"*thwock!* *groan* *crash*\n\n\
A horrible roar fills the cave, and you realize, with a smile, that you\n\
have slain the evil Wumpus and won the game! You don't want to tarry for\n\
long, however, because not only is the Wumpus famous, but the stench of\n\
dead Wumpus is also quite well known--a stench powerful enough to slay the\n\
mightiest adventurer at a single whiff!!\n");
}
void
no_arrows(void)
{
(void)printf(
"\nYou turn and look at your quiver, and realize with a sinking feeling\n\
that you've just shot your last arrow (figuratively, too). Sensing this\n\
with its psychic powers, the evil Wumpus rampages through the cave, finds\n\
you, and with a mighty *ROAR* eats you alive!\n");
}
void
shoot_self(void)
{
(void)printf(
"\n*Thwack!* A sudden piercing feeling informs you that your wild arrow\n\
has ricocheted back and wedged in your side, causing extreme agony. The\n\
evil Wumpus, with its psychic powers, realizes this and immediately rushes\n\
to your side, not to help, alas, but to EAT YOU!\n\
(*CHOMP*)\n");
}
void
pit_kill(void)
{
(void)printf(
"*AAAUUUUGGGGGHHHHHhhhhhhhhhh...*\n\
The whistling sound and updraft as you walked into this room of the\n\
cave apparently weren't enough to clue you in to the presence of the\n\
bottomless pit. You have a lot of time to reflect on this error as\n\
you fall many miles to the core of the earth. Look on the bright side;\n\
you can at least find out if Jules Verne was right...\n");
}
void
pit_kill_bat(void)
{
(void)printf(
"*AAAUUUUGGGGGHHHHHhhhhhhhhhh...*\n\
It appears the bats have decided to drop you into a bottomless pit. At\n\
least, that's what the whistling sound and updraft would suggest. Look on\n\
the bright side; you can at least find out if Jules Verne was right...\n");
}
void
pit_survive(void)
{
(void)printf(
"Without conscious thought you grab for the side of the cave and manage\n\
to grasp onto a rocky outcrop. Beneath your feet stretches the limitless\n\
depths of a bottomless pit! Rock crumbles beneath your feet!\n");
}