games: Massive style(9) cleanup commit. Reduces differences to NetBSD.
[dragonfly.git] / games / hunt / hunt / hunt.c
blobc9955118f66e383b017e1b53ca6a2c61a561627e
1 /*-
2 * Copyright (c) 1983-2003, Regents of the University of California.
3 * All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of the University of California, San Francisco nor
15 * the names of its contributors may be used to endorse or promote
16 * products derived from this software without specific prior written
17 * permission.
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 * $OpenBSD: hunt.c,v 1.13 2008/03/17 09:17:56 sobrado Exp $
32 * $NetBSD: hunt.c,v 1.8 1998/09/13 15:27:28 hubertf Exp $
33 * $DragonFly: src/games/hunt/hunt/hunt.c,v 1.2 2008/09/04 16:12:51 swildner Exp $
36 #include <ctype.h>
37 #include <err.h>
38 #include <errno.h>
39 #include <curses.h>
40 #include <signal.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <unistd.h>
44 #include <netdb.h>
46 #include <sys/stat.h>
47 #include <sys/time.h>
48 #include <sys/types.h>
49 #include <sys/socket.h>
50 #include <sys/ioctl.h>
51 #include <sys/sockio.h>
53 #include <netinet/in.h>
54 #include <net/if.h>
56 #include <arpa/inet.h>
58 #include "hunt.h"
59 #include "display.h"
60 #include "client.h"
61 #include "list.h"
63 #ifndef __GNUC__
64 #define __attribute__(x)
65 #endif
67 FLAG Am_monitor = FALSE;
68 int Socket;
69 char map_key[256]; /* what to map keys to */
70 FLAG no_beep = FALSE;
71 char *Send_message = NULL;
73 static char *Sock_host;
74 static char *use_port;
75 static FLAG Query_driver = FALSE;
76 static FLAG Show_scores = FALSE;
77 static struct sockaddr Daemon;
80 static char name[NAMELEN];
81 static char team = '-';
83 static int in_visual;
85 static void dump_scores(void);
86 static long env_init(long);
87 static void fill_in_blanks(void);
88 static void leave(int, const char *) __attribute__((__noreturn__));
89 static void sigterm(int);
90 static int find_driver(void);
93 * main:
94 * Main program for local process
96 int
97 main(int ac, char **av)
99 int c;
100 long enter_status;
101 int option;
102 struct servent *se;
104 enter_status = env_init((long) Q_CLOAK);
105 while ((c = getopt(ac, av, "Sbcfh:l:mn:op:qst:w:")) != -1) {
106 switch (c) {
107 case 'l': /* rsh compatibility */
108 case 'n':
109 (void) strlcpy(name, optarg, sizeof name);
110 break;
111 case 't':
112 team = *optarg;
113 if (!isdigit(team) && team != ' ') {
114 warnx("Team names must be numeric or space");
115 team = '-';
117 break;
118 case 'o':
119 Otto_mode = TRUE;
120 break;
121 case 'm':
122 Am_monitor = TRUE;
123 break;
124 case 'S':
125 Show_scores = TRUE;
126 break;
127 case 'q': /* query whether hunt is running */
128 Query_driver = TRUE;
129 break;
130 case 'w':
131 Send_message = optarg;
132 break;
133 case 'h':
134 Sock_host = optarg;
135 break;
136 case 'p':
137 use_port = optarg;
138 Server_port = atoi(use_port);
139 break;
140 case 'c':
141 enter_status = Q_CLOAK;
142 break;
143 case 'f':
144 enter_status = Q_FLY;
145 break;
146 case 's':
147 enter_status = Q_SCAN;
148 break;
149 case 'b':
150 no_beep = !no_beep;
151 break;
152 default:
153 usage:
154 fputs("usage: hunt [-bcfmqSs] [-n name] [-p port] "
155 "[-t team] [-w message] [[-h] host]\n",
156 stderr);
157 exit(1);
160 if (optind + 1 < ac)
161 goto usage;
162 else if (optind + 1 == ac)
163 Sock_host = av[ac - 1];
165 if (Server_port == 0) {
166 se = getservbyname("hunt", "udp");
167 if (se != NULL)
168 Server_port = ntohs(se->s_port);
169 else
170 Server_port = HUNT_PORT;
173 if (Show_scores) {
174 dump_scores();
175 exit(0);
178 if (Query_driver) {
179 struct driver *driver;
181 probe_drivers(C_MESSAGE, Sock_host);
182 while ((driver = next_driver()) != NULL) {
183 printf("%d player%s hunting on %s!\n",
184 driver->response,
185 (driver->response == 1) ? "" : "s",
186 driver_name(driver));
187 if (Sock_host)
188 break;
190 exit(0);
192 if (Otto_mode) {
193 if (Am_monitor)
194 errx(1, "otto mode incompatible with monitor mode");
195 (void) strlcpy(name, "otto", sizeof name);
196 team = ' ';
197 } else
198 fill_in_blanks();
200 (void) fflush(stdout);
201 display_open();
202 in_visual = TRUE;
203 if (LINES < SCREEN_HEIGHT || COLS < SCREEN_WIDTH) {
204 errno = 0;
205 leave(1, "Need a larger window");
207 display_clear_the_screen();
208 (void) signal(SIGINT, intr);
209 (void) signal(SIGTERM, sigterm);
210 /* (void) signal(SIGPIPE, SIG_IGN); */
212 Daemon.sa_len = 0;
213 ask_driver:
214 while (!find_driver()) {
215 if (Am_monitor) {
216 errno = 0;
217 leave(1, "No one playing");
220 if (Sock_host == NULL) {
221 errno = 0;
222 leave(1, "huntd not running");
225 sleep(3);
227 Socket = -1;
229 for (;;) {
230 if (Socket != -1)
231 close(Socket);
233 Socket = socket(Daemon.sa_family, SOCK_STREAM, 0);
234 if (Socket < 0)
235 leave(1, "socket");
237 option = 1;
238 if (setsockopt(Socket, SOL_SOCKET, SO_USELOOPBACK,
239 &option, sizeof option) < 0)
240 warn("setsockopt loopback");
242 errno = 0;
243 if (connect(Socket, &Daemon, Daemon.sa_len) == -1) {
244 if (errno == ECONNREFUSED)
245 goto ask_driver;
246 leave(1, "connect");
249 do_connect(name, team, enter_status);
250 if (Send_message != NULL) {
251 do_message();
252 if (enter_status == Q_MESSAGE)
253 break;
254 Send_message = NULL;
255 continue;
257 playit();
258 if ((enter_status = quit(enter_status)) == Q_QUIT)
259 break;
261 leave(0, NULL);
262 /* NOTREACHED */
263 return(0);
267 * Set Daemon to be the address of a hunt driver, or return 0 on failure.
269 * We start quietly probing for drivers. As soon as one driver is found
270 * we show it in the list. If we run out of drivers and we only have one
271 * then we choose it. Otherwise we present a list of the found drivers.
273 static int
274 find_driver(void)
276 int last_driver, numdrivers, waiting, is_current;
277 struct driver *driver;
278 int c;
279 char buf[80];
280 const char *xname;
282 probe_drivers(Am_monitor ? C_MONITOR : C_PLAYER, Sock_host);
284 last_driver = -1;
285 numdrivers = 0;
286 waiting = 1;
287 for (;;) {
288 if (numdrivers == 0) {
289 /* Silently wait for at least one driver */
290 driver = next_driver();
291 } else if (!waiting || (driver =
292 next_driver_fd(STDIN_FILENO)) == (struct driver *)-1) {
293 /* We have a key waiting, or no drivers left */
294 c = getchar();
295 if (c == '\r' || c == '\n' || c == ' ') {
296 if (numdrivers == 1)
297 c = 'a';
298 else if (last_driver != -1)
299 c = 'a' + last_driver;
301 if (c < 'a' || c >= numdrivers + 'a') {
302 display_beep();
303 continue;
305 driver = &drivers[c - 'a'];
306 break;
309 if (driver == NULL) {
310 waiting = 0;
311 if (numdrivers == 0) {
312 probe_cleanup();
313 return 0; /* Failure */
315 if (numdrivers == 1) {
316 driver = &drivers[0];
317 break;
319 continue;
322 /* Use the preferred host straight away. */
323 if (Sock_host)
324 break;
326 if (numdrivers == 0) {
327 display_clear_the_screen();
328 display_move(1, 0);
329 display_put_str("Pick one:");
332 /* Mark the last driver we used with an asterisk */
333 is_current = (last_driver == -1 && Daemon.sa_len != 0 &&
334 memcmp(&Daemon, &driver->addr, Daemon.sa_len) == 0);
335 if (is_current)
336 last_driver = numdrivers;
338 /* Display it in the list if there is room */
339 if (numdrivers < HEIGHT - 3) {
340 xname = driver_name(driver);
341 display_move(3 + numdrivers, 0);
342 snprintf(buf, sizeof buf, "%6c %c %s",
343 is_current ? '*' : ' ', 'a' + numdrivers, xname);
344 display_put_str(buf);
347 /* Clear the last 'Enter letter' line if any */
348 display_move(4 + numdrivers, 0);
349 display_clear_eol();
351 if (last_driver != -1)
352 snprintf(buf, sizeof buf, "Enter letter [%c]: ",
353 'a' + last_driver);
354 else
355 snprintf(buf, sizeof buf, "Enter letter: ");
357 display_move(5 + numdrivers, 0);
358 display_put_str(buf);
359 display_refresh();
361 numdrivers++;
364 display_clear_the_screen();
365 Daemon = driver->addr;
367 probe_cleanup();
368 return 1; /* Success */
371 static void
372 dump_scores(void)
374 struct driver *driver;
375 int s, cnt, i;
376 char buf[1024];
378 probe_drivers(C_SCORES, Sock_host);
379 while ((driver = next_driver()) != NULL) {
380 printf("\n%s:\n", driver_name(driver));
381 fflush(stdout);
383 if ((s = socket(driver->addr.sa_family, SOCK_STREAM, 0)) < 0) {
384 warn("socket");
385 continue;
387 if (connect(s, &driver->addr, driver->addr.sa_len) < 0) {
388 warn("connect");
389 close(s);
390 continue;
392 while ((cnt = read(s, buf, sizeof buf)) > 0) {
393 /* Whittle out bad characters */
394 for (i = 0; i < cnt; i++)
395 if ((buf[i] < ' ' || buf[i] > '~') &&
396 buf[i] != '\n' && buf[i] != '\t')
397 buf[i] = '?';
398 fwrite(buf, cnt, 1, stdout);
400 if (cnt < 0)
401 warn("read");
402 (void)close(s);
403 if (Sock_host)
404 break;
406 probe_cleanup();
411 * bad_con:
412 * We had a bad connection. For the moment we assume that this
413 * means the game is full.
415 void
416 bad_con(void)
418 leave(1, "lost connection to huntd");
422 * bad_ver:
423 * version number mismatch.
425 void
426 bad_ver(void)
428 errno = 0;
429 leave(1, "Version number mismatch. No go.");
433 * sigterm:
434 * Handle a terminate signal
436 static void
437 sigterm(int signo __unused)
439 leave(0, NULL);
443 * rmnl:
444 * Remove a '\n' at the end of a string if there is one
446 static void
447 rmnl(char *s)
449 char *cp;
451 cp = strrchr(s, '\n');
452 if (cp != NULL)
453 *cp = '\0';
457 * intr:
458 * Handle a interrupt signal
460 void
461 intr(int dummy __unused)
463 int ch;
464 int explained;
465 int y, x;
467 (void) signal(SIGINT, SIG_IGN);
468 display_getyx(&y, &x);
469 display_move(HEIGHT, 0);
470 display_put_str("Really quit? ");
471 display_clear_eol();
472 display_refresh();
473 explained = FALSE;
474 for (;;) {
475 ch = getchar();
476 if (isupper(ch))
477 ch = tolower(ch);
478 if (ch == 'y') {
479 if (Socket != 0) {
480 (void) write(Socket, "q", 1);
481 (void) close(Socket);
483 leave(0, NULL);
485 else if (ch == 'n') {
486 (void) signal(SIGINT, intr);
487 display_move(y, x);
488 display_refresh();
489 return;
491 if (!explained) {
492 display_put_str("(Yes or No) ");
493 display_refresh();
494 explained = TRUE;
496 display_beep();
497 display_refresh();
502 * leave:
503 * Leave the game somewhat gracefully, restoring all current
504 * tty stats.
506 static void
507 leave(int eval, const char *mesg)
509 int saved_errno;
511 saved_errno = errno;
512 if (in_visual) {
513 display_move(HEIGHT, 0);
514 display_refresh();
515 display_end();
517 errno = saved_errno;
519 if (errno == 0 && mesg != NULL)
520 errx(eval, mesg);
521 else if (mesg != NULL)
522 err(eval, mesg);
523 exit(eval);
527 * env_init:
528 * initialise game parameters from the HUNT envvar
530 static long
531 env_init(long enter_status)
533 int i;
534 char *envp, *envname, *s;
536 /* Map all keys to themselves: */
537 for (i = 0; i < 256; i++)
538 map_key[i] = (char) i;
540 envname = NULL;
541 if ((envp = getenv("HUNT")) != NULL) {
542 while ((s = strpbrk(envp, "=,")) != NULL) {
543 if (strncmp(envp, "cloak,", s - envp + 1) == 0) {
544 enter_status = Q_CLOAK;
545 envp = s + 1;
547 else if (strncmp(envp, "scan,", s - envp + 1) == 0) {
548 enter_status = Q_SCAN;
549 envp = s + 1;
551 else if (strncmp(envp, "fly,", s - envp + 1) == 0) {
552 enter_status = Q_FLY;
553 envp = s + 1;
555 else if (strncmp(envp, "nobeep,", s - envp + 1) == 0) {
556 no_beep = TRUE;
557 envp = s + 1;
559 else if (strncmp(envp, "name=", s - envp + 1) == 0) {
560 envname = s + 1;
561 if ((s = strchr(envp, ',')) == NULL) {
562 *envp = '\0';
563 strlcpy(name, envname, sizeof name);
564 break;
566 *s = '\0';
567 strlcpy(name, envname, sizeof name);
568 envp = s + 1;
570 else if (strncmp(envp, "port=", s - envp + 1) == 0) {
571 use_port = s + 1;
572 Server_port = atoi(use_port);
573 if ((s = strchr(envp, ',')) == NULL) {
574 *envp = '\0';
575 break;
577 *s = '\0';
578 envp = s + 1;
580 else if (strncmp(envp, "host=", s - envp + 1) == 0) {
581 Sock_host = s + 1;
582 if ((s = strchr(envp, ',')) == NULL) {
583 *envp = '\0';
584 break;
586 *s = '\0';
587 envp = s + 1;
589 else if (strncmp(envp, "message=", s - envp + 1) == 0) {
590 Send_message = s + 1;
591 if ((s = strchr(envp, ',')) == NULL) {
592 *envp = '\0';
593 break;
595 *s = '\0';
596 envp = s + 1;
598 else if (strncmp(envp, "team=", s - envp + 1) == 0) {
599 team = *(s + 1);
600 if (!isdigit(team))
601 team = ' ';
602 if ((s = strchr(envp, ',')) == NULL) {
603 *envp = '\0';
604 break;
606 *s = '\0';
607 envp = s + 1;
608 } /* must be last option */
609 else if (strncmp(envp, "mapkey=", s - envp + 1) == 0) {
610 for (s = s + 1; *s != '\0'; s += 2) {
611 map_key[(unsigned int) *s] = *(s + 1);
612 if (*(s + 1) == '\0') {
613 break;
616 *envp = '\0';
617 break;
618 } else {
619 *s = '\0';
620 printf("unknown option %s\n", envp);
621 if ((s = strchr(envp, ',')) == NULL) {
622 *envp = '\0';
623 break;
625 envp = s + 1;
628 if (*envp != '\0') {
629 if (envname == NULL)
630 strlcpy(name, envp, sizeof name);
631 else
632 printf("unknown option %s\n", envp);
635 return enter_status;
639 * fill_in_blanks:
640 * quiz the user for the information they didn't provide earlier
642 static void
643 fill_in_blanks(void)
645 int i;
646 char *cp;
648 again:
649 if (name[0] != '\0') {
650 printf("Entering as '%s'", name);
651 if (team != ' ' && team != '-')
652 printf(" on team %c.\n", team);
653 else
654 putchar('\n');
655 } else {
656 printf("Enter your code name: ");
657 if (fgets(name, sizeof name, stdin) == NULL)
658 exit(1);
660 rmnl(name);
661 if (name[0] == '\0') {
662 printf("You have to have a code name!\n");
663 goto again;
665 for (cp = name; *cp != '\0'; cp++)
666 if (!isprint(*cp)) {
667 name[0] = '\0';
668 printf("Illegal character in your code name.\n");
669 goto again;
671 if (team == '-') {
672 printf("Enter your team (0-9 or nothing): ");
673 i = getchar();
674 if (isdigit(i))
675 team = i;
676 else if (i == '\n' || i == EOF || i == ' ')
677 team = ' ';
678 /* ignore trailing chars */
679 while (i != '\n' && i != EOF)
680 i = getchar();
681 if (team == '-') {
682 printf("Teams must be numeric.\n");
683 goto again;