root/games/hunt/huntd/driver.c
/*      $NetBSD: driver.c,v 1.36 2021/05/02 12:50:45 rillig Exp $       */
/*
 * Copyright (c) 1983-2003, Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * + Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * + Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * + Neither the name of the University of California, San Francisco nor
 *   the names of its contributors may be used to endorse or promote
 *   products derived from this software without specific prior written
 *   permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: driver.c,v 1.36 2021/05/02 12:50:45 rillig Exp $");
#endif /* not lint */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/un.h>

#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <net/if.h>

#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <err.h>

#include "hunt.h"
#include "pathnames.h"

/*
 * There are three listening sockets in this daemon:
 *    - a datagram socket that listens on a well known port
 *    - a stream socket for general player connections
 *    - a second stream socket that prints the stats/scores
 *
 * These are (now) named as follows:
 *    - contact (contactsock, contactaddr, etc.)
 *    - hunt (huntsock, huntaddr, etc.)
 *    - stat (statsock, stataddr, etc.)
 *
 * Until being cleaned up in 2014 the code used an assortment of
 * inconsistent and confusing names to refer to the pieces of these:
 *
 *    test_port -> contactaddr
 *    Test_port -> contactport
 *    Test_socket -> contactsock
 *
 *    sock_port -> huntport
 *    Socket -> huntsock
 *
 *    Daemon -> both stataddr and huntaddr
 *    stat_port -> statport
 *    status -> statsock
 *
 * It isn't clear to me what purpose contactsocket is supposed to
 * serve; maybe it was supposed to avoid asking inetd to support
 * tcp/wait sockets? Anyway, we can't really change the protocol at
 * this point. (To complicate matters, contactsocket doesn't exist if
 * using AF_UNIX sockets; you just connect to the game socket.)
 *
 * When using internet sockets:
 *    - the contact socket listens on INADDR_ANY using the game port
 *      (either specified or the default: CONTACT_PORT, currently
 *      spelled TEST_PORT)
 *    - the hunt socket listens on INADDR_ANY using whatever port
 *    - the stat socket listens on INADDR_ANY using whatever port
 *
 * When using AF_UNIX sockets:
 *    - the contact socket isn't used
 *    - the hunt socket listens on the game socket (either a specified path
 *      or /tmp/hunt)
 *    - the stat socket listens on its own socket (huntsocket's path +
 *      ".stats")
 */

static bool localmode;                  /* true -> AF_UNIX; false -> AF_INET */
static bool inetd_spawned;              /* invoked via inetd? */
static bool standard_port = true;       /* listening on standard port? */

static struct sockaddr_storage huntaddr;
static struct sockaddr_storage stataddr;
static socklen_t huntaddrlen;
static socklen_t stataddrlen;

static uint16_t contactport = TEST_PORT;
static uint16_t huntport;               /* port # of tcp listen socket */
static uint16_t statport;               /* port # of statistics tcp socket */

static const char *huntsockpath = PATH_HUNTSOCKET;
static char *statsockpath;

static int contactsock;                 /* socket to answer datagrams */
int huntsock;                           /* main socket */
static int statsock;                    /* stat socket */

#ifdef VOLCANO
static int volcano = 0;                 /* Explosion size */
#endif

static void clear_scores(void);
static bool havechar(PLAYER *, int);
static void init(void);
static void makeboots(void);
static void send_stats(void);
static void zap(PLAYER *, bool, int);

static int
getnum(const char *s, unsigned long *ret)
{
        char *t;

        errno = 0;
        *ret = strtoul(s, &t, 0);
        if (errno || *t != '\0') {
                return -1;
        }
        return 0;
}

static __dead void
usage(const char *av0)
{
        fprintf(stderr, "Usage: %s [-s] [-p portnumber|socketpath]\n", av0);
        exit(1);
}

static void
makeaddr(const char *path, uint16_t port,
         struct sockaddr_storage *ss, socklen_t *len)
{
        struct sockaddr_in *sin;
        struct sockaddr_un *sun;

        if (path == NULL) {
                sin = (struct sockaddr_in *)ss;
                sin->sin_family = AF_INET;
                sin->sin_addr.s_addr = INADDR_ANY;
                sin->sin_port = htons(port);
                *len = sizeof(*sin);
        } else {
                sun = (struct sockaddr_un *)ss;
                sun->sun_family = AF_UNIX;
                strlcpy(sun->sun_path, path, sizeof(sun->sun_path));
                *len = SUN_LEN(sun);
        }
}

static uint16_t
getsockport(int sock)
{
        struct sockaddr_storage addr;
        socklen_t len;

        len = sizeof(addr);
        if (getsockname(sock, (struct sockaddr *)&addr, &len) < 0)  {
                complain(LOG_ERR, "getsockname");
                exit(1);
        }
        switch (addr.ss_family) {
            case AF_INET:
                return ntohs(((struct sockaddr_in *)&addr)->sin_port);
            case AF_INET6:
                return ntohs(((struct sockaddr_in6 *)&addr)->sin6_port);
            default:
                break;
        }
        return 0;
}

/*
 * main:
 *      The main program.
 */
int
main(int ac, char **av)
{
        PLAYER *pp;
        unsigned long optargnum;
        uint16_t msg, reply;
        struct sockaddr_storage msgaddr;
        socklen_t msgaddrlen;
        static bool first = true;
        static bool server = false;
        int c, i;
        const int linger = 90 * 1000;

        while ((c = getopt(ac, av, "sp:")) != -1) {
                switch (c) {
                  case 's':
                        server = true;
                        break;
                  case 'p':
                        standard_port = false;
                        if (getnum(optarg, &optargnum) < 0) {
                                localmode = true;
                                huntsockpath = optarg;
                        } else if (optargnum < 0xffff) {
                                localmode = false;
                                contactport = optargnum;
                        } else {
                                usage(av[0]);
                        }
                        break;
                  default:
                        usage(av[0]);
                }
        }
        if (optind < ac)
                usage(av[0]);

        asprintf(&statsockpath, "%s.stats", huntsockpath);
        init();


again:
        do {
                errno = 0;
                while (poll(fdset, 3+MAXPL+MAXMON, INFTIM) < 0)
                {
                        if (errno != EINTR)
                                complain(LOG_WARNING, "poll");
                        errno = 0;
                }
                if (!localmode && fdset[2].revents & POLLIN) {
                        msgaddrlen = sizeof(msgaddr);
                        (void) recvfrom(contactsock, &msg, sizeof msg,
                                0, (struct sockaddr *)&msgaddr, &msgaddrlen);
                        switch (ntohs(msg)) {
                          case C_MESSAGE:
                                if (Nplayer <= 0)
                                        break;
                                reply = htons((u_short) Nplayer);
                                (void) sendto(contactsock, &reply,
                                        sizeof reply, 0,
                                        (struct sockaddr *)&msgaddr,
                                        msgaddrlen);
                                break;
                          case C_SCORES:
                                reply = htons(statport);
                                (void) sendto(contactsock, &reply,
                                        sizeof reply, 0,
                                        (struct sockaddr *)&msgaddr,
                                        msgaddrlen);
                                break;
                          case C_PLAYER:
                          case C_MONITOR:
                                if (msg == C_MONITOR && Nplayer <= 0)
                                        break;
                                reply = htons(huntport);
                                (void) sendto(contactsock, &reply,
                                        sizeof reply, 0,
                                        (struct sockaddr *)&msgaddr,
                                        msgaddrlen);
                                break;
                        }
                }

                {
                        for (pp = Player, i = 0; pp < End_player; pp++, i++)
                                if (havechar(pp, i + 3)) {
                                        execute(pp);
                                        pp->p_nexec++;
                                }
#ifdef MONITOR
                        for (pp = Monitor, i = 0; pp < End_monitor; pp++, i++)
                                if (havechar(pp, i + MAXPL + 3)) {
                                        mon_execute(pp);
                                        pp->p_nexec++;
                                }
#endif
                        moveshots();
                        for (pp = Player, i = 0; pp < End_player; )
                                if (pp->p_death[0] != '\0')
                                        zap(pp, true, i + 3);
                                else
                                        pp++, i++;
#ifdef MONITOR
                        for (pp = Monitor, i = 0; pp < End_monitor; )
                                if (pp->p_death[0] != '\0')
                                        zap(pp, false, i + MAXPL + 3);
                                else
                                        pp++, i++;
#endif
                }
                if (fdset[0].revents & POLLIN)
                        if (answer()) {
                                if (first) {
                                        /* announce start of game? */
                                }
                                first = false;
                        }
                if (fdset[1].revents & POLLIN)
                        send_stats();
                for (pp = Player, i = 0; pp < End_player; pp++, i++) {
                        if (fdset[i + 3].revents & POLLIN)
                                sendcom(pp, READY, pp->p_nexec);
                        pp->p_nexec = 0;
                        (void) fflush(pp->p_output);
                }
#ifdef MONITOR
                for (pp = Monitor, i = 0; pp < End_monitor; pp++, i++) {
                        if (fdset[i + MAXPL + 3].revents & POLLIN)
                                sendcom(pp, READY, pp->p_nexec);
                        pp->p_nexec = 0;
                        (void) fflush(pp->p_output);
                }
#endif
        } while (Nplayer > 0);

        if (poll(fdset, 3+MAXPL+MAXMON, linger) > 0) {
                goto again;
        }
        if (server) {
                clear_scores();
                makemaze();
                clearwalls();
#ifdef BOOTS
                makeboots();
#endif
                first = true;
                goto again;
        }

#ifdef MONITOR
        for (pp = Monitor, i = 0; pp < End_monitor; i++)
                zap(pp, false, i + MAXPL + 3);
#endif
        cleanup(0);
        /* NOTREACHED */
        return(0);
}

/*
 * init:
 *      Initialize the global parameters.
 */
static void
init(void)
{
        int i;
        struct sockaddr_storage stdinaddr;
        struct sockaddr_storage contactaddr;
        socklen_t contactaddrlen;
        socklen_t len;

#ifndef DEBUG
#ifdef TIOCNOTTY
        (void) ioctl(fileno(stdout), TIOCNOTTY, NULL);
#endif
        (void) setpgrp(getpid(), getpid());
        (void) signal(SIGHUP, SIG_IGN);
        (void) signal(SIGINT, SIG_IGN);
        (void) signal(SIGQUIT, SIG_IGN);
        (void) signal(SIGTERM, cleanup);
#endif

        (void) chdir("/var/tmp");       /* just in case it core dumps */
        (void) umask(0);                /* No privacy at all! */
        (void) signal(SIGPIPE, SIG_IGN);

#ifdef LOG
        openlog("huntd", LOG_PID, LOG_DAEMON);
#endif

        /*
         * check for inetd
         */
        len = sizeof(stdinaddr);
        if (getsockname(STDIN_FILENO, (struct sockaddr *)&stdinaddr,
                        &len) >= 0) {
                inetd_spawned = true;
                /* force localmode, assimilate stdin as appropriate */
                if (stdinaddr.ss_family == AF_UNIX) {
                        localmode = true;
                        contactsock = -1;
                        huntsock = STDIN_FILENO;
                }
                else {
                        localmode = false;
                        contactsock = STDIN_FILENO;
                        huntsock = -1;
                }
        } else {
                /* keep value of localmode; no sockets yet */
                contactsock = -1;
                huntsock = -1;
        }

        /*
         * initialize contact socket
         */
        if (!localmode && contactsock < 0) {
                makeaddr(NULL, contactport, &contactaddr, &contactaddrlen);
                contactsock = socket(AF_INET, SOCK_DGRAM, 0);
                if (bind(contactsock, (struct sockaddr *) &contactaddr,
                         contactaddrlen) < 0) {
                        complain(LOG_ERR, "bind");
                        exit(1);
                }
                (void) listen(contactsock, 5);
        }

        /*
         * Initialize main socket
         */
        if (huntsock < 0) {
                makeaddr(localmode ? huntsockpath : NULL, 0, &huntaddr,
                         &huntaddrlen);
                huntsock = socket(huntaddr.ss_family, SOCK_STREAM, 0);
                if (bind(huntsock, (struct sockaddr *)&huntaddr,
                         huntaddrlen) < 0) {
                        if (errno == EADDRINUSE)
                                exit(0);
                        else {
                                complain(LOG_ERR, "bind");
                                cleanup(1);
                        }
                }
                (void) listen(huntsock, 5);
        }

        /*
         * Initialize statistics socket
         */
        makeaddr(localmode ? statsockpath : NULL, 0, &stataddr, &stataddrlen);
        statsock = socket(stataddr.ss_family, SOCK_STREAM, 0);
        if (bind(statsock, (struct sockaddr *)&stataddr, stataddrlen) < 0) {
                if (errno == EADDRINUSE)
                        exit(0);
                else {
                        complain(LOG_ERR, "bind");
                        cleanup(1);
                }
        }
        (void) listen(statsock, 5);

        if (!localmode) {
                contactport = getsockport(contactsock);
                statport = getsockport(statsock);
                huntport = getsockport(huntsock);
                if (contactport != TEST_PORT) {
                        standard_port = false;
                }
        }

        /*
         * Initialize minimal poll mask
         */
        fdset[0].fd = huntsock;
        fdset[0].events = POLLIN;
        fdset[1].fd = statsock;
        fdset[1].events = POLLIN;
        if (localmode) {
                fdset[2].fd = -1;
                fdset[2].events = 0;
        } else {
                fdset[2].fd = contactsock;
                fdset[2].events = POLLIN;
        }

        srandom(time(NULL));
        makemaze();
#ifdef BOOTS
        makeboots();
#endif

        for (i = 0; i < NASCII; i++)
                See_over[i] = true;
        See_over[DOOR] = false;
        See_over[WALL1] = false;
        See_over[WALL2] = false;
        See_over[WALL3] = false;
#ifdef REFLECT
        See_over[WALL4] = false;
        See_over[WALL5] = false;
#endif

}

#ifdef BOOTS
/*
 * makeboots:
 *      Put the boots in the maze
 */
static void
makeboots(void)
{
        int x, y;
        PLAYER *pp;

        do {
                x = rand_num(WIDTH - 1) + 1;
                y = rand_num(HEIGHT - 1) + 1;
        } while (Maze[y][x] != SPACE);
        Maze[y][x] = BOOT_PAIR;
        for (pp = Boot; pp < &Boot[NBOOTS]; pp++)
                pp->p_flying = -1;
}
#endif


/*
 * checkdam:
 *      Check the damage to the given player, and see if s/he is killed
 */
void
checkdam(PLAYER *ouch, PLAYER *gotcha, IDENT *credit, int amt,
         char this_shot_type)
{
        const char *cp;

        if (ouch->p_death[0] != '\0')
                return;
#ifdef BOOTS
        if (this_shot_type == SLIME)
                switch (ouch->p_nboots) {
                  default:
                        break;
                  case 1:
                        amt = (amt + 1) / 2;
                        break;
                  case 2:
                        if (gotcha != NULL)
                                message(gotcha, "He has boots on!");
                        return;
                }
#endif
        ouch->p_damage += amt;
        if (ouch->p_damage <= ouch->p_damcap) {
                (void) snprintf(Buf, sizeof(Buf), "%2d", ouch->p_damage);
                cgoto(ouch, STAT_DAM_ROW, STAT_VALUE_COL);
                outstr(ouch, Buf, 2);
                return;
        }

        /* Someone DIED */
        switch (this_shot_type) {
          default:
                cp = "Killed";
                break;
#ifdef FLY
          case FALL:
                cp = "Killed on impact";
                break;
#endif
          case KNIFE:
                cp = "Stabbed to death";
                ouch->p_ammo = 0;               /* No exploding */
                break;
          case SHOT:
                cp = "Shot to death";
                break;
          case GRENADE:
          case SATCHEL:
          case BOMB:
                cp = "Bombed";
                break;
          case MINE:
          case GMINE:
                cp = "Blown apart";
                break;
#ifdef  OOZE
          case SLIME:
                cp = "Slimed";
                if (credit != NULL)
                        credit->i_slime++;
                break;
#endif
#ifdef  VOLCANO
          case LAVA:
                cp = "Baked";
                break;
#endif
#ifdef DRONE
          case DSHOT:
                cp = "Eliminated";
                break;
#endif
        }
        if (credit == NULL) {
                (void) snprintf(ouch->p_death, sizeof(ouch->p_death),
                        "| %s by %s |", cp,
                        (this_shot_type == MINE || this_shot_type == GMINE) ?
                        "a mine" : "act of God");
                return;
        }

        (void) snprintf(ouch->p_death, sizeof(ouch->p_death),
                "| %s by %s |", cp, credit->i_name);

        if (ouch == gotcha) {           /* No use killing yourself */
                credit->i_kills--;
                credit->i_bkills++;
        }
        else if (ouch->p_ident->i_team == ' '
        || ouch->p_ident->i_team != credit->i_team) {
                credit->i_kills++;
                credit->i_gkills++;
        }
        else {
                credit->i_kills--;
                credit->i_bkills++;
        }
        credit->i_score = credit->i_kills / (double) credit->i_entries;
        ouch->p_ident->i_deaths++;
        if (ouch->p_nchar == 0)
                ouch->p_ident->i_stillb++;
        if (gotcha == NULL)
                return;
        gotcha->p_damcap += STABDAM;
        gotcha->p_damage -= STABDAM;
        if (gotcha->p_damage < 0)
                gotcha->p_damage = 0;
        (void) snprintf(Buf, sizeof(Buf), "%2d/%2d", gotcha->p_damage,
                        gotcha->p_damcap);
        cgoto(gotcha, STAT_DAM_ROW, STAT_VALUE_COL);
        outstr(gotcha, Buf, 5);
        (void) snprintf(Buf, sizeof(Buf), "%3d",
                        (gotcha->p_damcap - MAXDAM) / 2);
        cgoto(gotcha, STAT_KILL_ROW, STAT_VALUE_COL);
        outstr(gotcha, Buf, 3);
        (void) snprintf(Buf, sizeof(Buf), "%5.2f", gotcha->p_ident->i_score);
        for (ouch = Player; ouch < End_player; ouch++) {
                cgoto(ouch, STAT_PLAY_ROW + 1 + (gotcha - Player),
                        STAT_NAME_COL);
                outstr(ouch, Buf, 5);
        }
#ifdef MONITOR
        for (ouch = Monitor; ouch < End_monitor; ouch++) {
                cgoto(ouch, STAT_PLAY_ROW + 1 + (gotcha - Player),
                        STAT_NAME_COL);
                outstr(ouch, Buf, 5);
        }
#endif
}

/*
 * zap:
 *      Kill off a player and take him out of the game.
 */
static void
zap(PLAYER *pp, bool was_player, int i)
{
        int n, len;
        BULLET *bp;
        PLAYER *np;
        int x, y;

        if (was_player) {
                if (pp->p_undershot)
                        fixshots(pp->p_y, pp->p_x, pp->p_over);
                drawplayer(pp, false);
                Nplayer--;
        }

        len = strlen(pp->p_death);      /* Display the cause of death */
        x = (WIDTH - len) / 2;
        cgoto(pp, HEIGHT / 2, x);
        outstr(pp, pp->p_death, len);
        for (n = 1; n < len; n++)
                pp->p_death[n] = '-';
        pp->p_death[0] = '+';
        pp->p_death[len - 1] = '+';
        cgoto(pp, HEIGHT / 2 - 1, x);
        outstr(pp, pp->p_death, len);
        cgoto(pp, HEIGHT / 2 + 1, x);
        outstr(pp, pp->p_death, len);
        cgoto(pp, HEIGHT, 0);

#ifdef MONITOR
        if (was_player) {
#endif
                for (bp = Bullets; bp != NULL; bp = bp->b_next) {
                        if (bp->b_owner == pp)
                                bp->b_owner = NULL;
                        if (bp->b_x == pp->p_x && bp->b_y == pp->p_y)
                                bp->b_over = SPACE;
                }

                n = rand_num(pp->p_ammo);
                x = rand_num(pp->p_ammo);
                if (x > n)
                        n = x;
                if (pp->p_ammo == 0)
                        x = 0;
                else if (n == pp->p_ammo - 1) {
                        x = pp->p_ammo;
                        len = SLIME;
                }
                else {
                        for (x = MAXBOMB - 1; x > 0; x--)
                                if (n >= shot_req[x])
                                        break;
                        for (y = MAXSLIME - 1; y > 0; y--)
                                if (n >= slime_req[y])
                                        break;
                        if (y >= 0 && slime_req[y] > shot_req[x]) {
                                x = slime_req[y];
                                len = SLIME;
                        }
                        else if (x != 0) {
                                len = shot_type[x];
                                x = shot_req[x];
                        }
                }
                if (x > 0) {
                        (void) add_shot(len, pp->p_y, pp->p_x, pp->p_face, x,
                                NULL, true, SPACE);
                        (void) snprintf(Buf, sizeof(Buf), "%s detonated.",
                                pp->p_ident->i_name);
                        for (np = Player; np < End_player; np++)
                                message(np, Buf);
#ifdef MONITOR
                        for (np = Monitor; np < End_monitor; np++)
                                message(np, Buf);
#endif
#ifdef BOOTS
                        while (pp->p_nboots-- > 0) {
                                for (np = Boot; np < &Boot[NBOOTS]; np++)
                                        if (np->p_flying < 0)
                                                break;
                                if (np >= &Boot[NBOOTS])
                                        err(1, "Too many boots");
                                np->p_undershot = false;
                                np->p_x = pp->p_x;
                                np->p_y = pp->p_y;
                                np->p_flying = rand_num(20);
                                np->p_flyx = 2 * rand_num(6) - 5;
                                np->p_flyy = 2 * rand_num(6) - 5;
                                np->p_over = SPACE;
                                np->p_face = BOOT;
                                showexpl(np->p_y, np->p_x, BOOT);
                        }
#endif
                }
#ifdef BOOTS
                else if (pp->p_nboots > 0) {
                        if (pp->p_nboots == 2)
                                Maze[pp->p_y][pp->p_x] = BOOT_PAIR;
                        else
                                Maze[pp->p_y][pp->p_x] = BOOT;
                        if (pp->p_undershot)
                                fixshots(pp->p_y, pp->p_x,
                                        Maze[pp->p_y][pp->p_x]);
                }
#endif

#ifdef VOLCANO
                volcano += pp->p_ammo - x;
                if (rand_num(100) < volcano / 50) {
                        do {
                                x = rand_num(WIDTH / 2) + WIDTH / 4;
                                y = rand_num(HEIGHT / 2) + HEIGHT / 4;
                        } while (Maze[y][x] != SPACE);
                        (void) add_shot(LAVA, y, x, LEFTS, volcano,
                                NULL, true, SPACE);
                        for (np = Player; np < End_player; np++)
                                message(np, "Volcano eruption.");
                        volcano = 0;
                }
#endif

#ifdef DRONE
                if (rand_num(100) < 2) {
                        do {
                                x = rand_num(WIDTH / 2) + WIDTH / 4;
                                y = rand_num(HEIGHT / 2) + HEIGHT / 4;
                        } while (Maze[y][x] != SPACE);
                        add_shot(DSHOT, y, x, rand_dir(),
                                shot_req[MINDSHOT +
                                rand_num(MAXBOMB - MINDSHOT)],
                                NULL, false, SPACE);
                }
#endif

                sendcom(pp, ENDWIN);
                (void) putc(' ', pp->p_output);
                (void) fclose(pp->p_output);

                End_player--;
                if (pp != End_player) {
                        memcpy(pp, End_player, sizeof (PLAYER));
                        fdset[i] = fdset[End_player - Player + 3];
                        fdset[End_player - Player + 3].fd = -1;
                        (void) snprintf(Buf, sizeof(Buf), "%5.2f%c%-10.10s %c",
                                pp->p_ident->i_score, stat_char(pp),
                                pp->p_ident->i_name, pp->p_ident->i_team);
                        n = STAT_PLAY_ROW + 1 + (pp - Player);
                        for (np = Player; np < End_player; np++) {
                                cgoto(np, n, STAT_NAME_COL);
                                outstr(np, Buf, STAT_NAME_LEN);
                        }
#ifdef MONITOR
                        for (np = Monitor; np < End_monitor; np++) {
                                cgoto(np, n, STAT_NAME_COL);
                                outstr(np, Buf, STAT_NAME_LEN);
                        }
#endif
                } else
                        fdset[i].fd = -1;

                /* Erase the last player */
                n = STAT_PLAY_ROW + 1 + Nplayer;
                for (np = Player; np < End_player; np++) {
                        cgoto(np, n, STAT_NAME_COL);
                        ce(np);
                }
#ifdef MONITOR
                for (np = Monitor; np < End_monitor; np++) {
                        cgoto(np, n, STAT_NAME_COL);
                        ce(np);
                }
        }
        else {
                sendcom(pp, ENDWIN);
                (void) putc(LAST_PLAYER, pp->p_output);
                (void) fclose(pp->p_output);

                End_monitor--;
                if (pp != End_monitor) {
                        memcpy(pp, End_monitor, sizeof (PLAYER));
                        fdset[i] = fdset[End_monitor - Monitor + MAXPL + 3];
                        fdset[End_monitor - Monitor + MAXPL + 3].fd = -1;
                        (void) snprintf(Buf, sizeof(Buf), "%5.5s %-10.10s %c",
                                " ",
                                pp->p_ident->i_name, pp->p_ident->i_team);
                        n = STAT_MON_ROW + 1 + (pp - Player);
                        for (np = Player; np < End_player; np++) {
                                cgoto(np, n, STAT_NAME_COL);
                                outstr(np, Buf, STAT_NAME_LEN);
                        }
                        for (np = Monitor; np < End_monitor; np++) {
                                cgoto(np, n, STAT_NAME_COL);
                                outstr(np, Buf, STAT_NAME_LEN);
                        }
                } else
                        fdset[i].fd = -1;

                /* Erase the last monitor */
                n = STAT_MON_ROW + 1 + (End_monitor - Monitor);
                for (np = Player; np < End_player; np++) {
                        cgoto(np, n, STAT_NAME_COL);
                        ce(np);
                }
                for (np = Monitor; np < End_monitor; np++) {
                        cgoto(np, n, STAT_NAME_COL);
                        ce(np);
                }
        }
#endif
}

/*
 * rand_num:
 *      Return a random number in a given range.
 */
int
rand_num(int range)
{
        return (range == 0 ? 0 : random() % range);
}

/*
 * havechar:
 *      Check to see if we have any characters in the input queue; if
 *      we do, read them, stash them away, and return true; else return
 *      false.
 */
static bool
havechar(PLAYER *pp, int i)
{

        if (pp->p_ncount < pp->p_nchar)
                return true;
        if (!(fdset[i].revents & POLLIN))
                return false;
check_again:
        pp->p_nchar = read(pp->p_fd, pp->p_cbuf, sizeof pp->p_cbuf);
        if (pp->p_nchar < 0 && errno == EINTR) {
                goto check_again;
        } else if (pp->p_nchar <= 0) {
                if (errno == EINTR)
                pp->p_cbuf[0] = 'q';
        }
        pp->p_ncount = 0;
        return true;
}

/*
 * cleanup:
 *      Exit with the given value, cleaning up any droppings lying around
 */
void
cleanup(int exitval)
{
        PLAYER *pp;

        for (pp = Player; pp < End_player; pp++) {
                cgoto(pp, HEIGHT, 0);
                sendcom(pp, ENDWIN);
                (void) putc(LAST_PLAYER, pp->p_output);
                (void) fclose(pp->p_output);
        }
#ifdef MONITOR
        for (pp = Monitor; pp < End_monitor; pp++) {
                cgoto(pp, HEIGHT, 0);
                sendcom(pp, ENDWIN);
                (void) putc(LAST_PLAYER, pp->p_output);
                (void) fclose(pp->p_output);
        }
#endif
        (void) close(huntsock);
#ifdef AF_UNIX_HACK
        (void) unlink(huntsockpath);
#endif

        exit(exitval);
}

/*
 * send_stats:
 *      Print stats to requestor
 */
static void
send_stats(void)
{
        IDENT *ip;
        FILE *fp;
        int s;
        struct sockaddr_storage newaddr;
        socklen_t socklen;

        /*
         * Get the output stream ready
         */
        socklen = sizeof(newaddr);
        s = accept(statsock, (struct sockaddr *)&newaddr, &socklen);
        if (s < 0) {
                if (errno == EINTR)
                        return;
                complain(LOG_WARNING, "accept");
                return;
        }
        fp = fdopen(s, "w");
        if (fp == NULL) {
                complain(LOG_WARNING, "fdopen");
                (void) close(s);
                return;
        }

        /*
         * Send output to requestor
         */
        fputs("Name\t\tScore\tDucked\tAbsorb\tFaced\tShot\tRobbed\tMissed\tSlimeK\n", fp);
        for (ip = Scores; ip != NULL; ip = ip->i_next) {
                fprintf(fp, "%s\t", ip->i_name);
                if (strlen(ip->i_name) < 8)
                        putc('\t', fp);
                fprintf(fp, "%.2f\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n",
                        ip->i_score, ip->i_ducked, ip->i_absorbed,
                        ip->i_faced, ip->i_shot, ip->i_robbed,
                        ip->i_missed, ip->i_slime);
        }
        fputs("\n\nName\t\tEnemy\tFriend\tDeaths\tStill\tSaved\n", fp);
        for (ip = Scores; ip != NULL; ip = ip->i_next) {
                if (ip->i_team == ' ') {
                        fprintf(fp, "%s\t", ip->i_name);
                        if (strlen(ip->i_name) < 8)
                                putc('\t', fp);
                }
                else {
                        fprintf(fp, "%s[%c]\t", ip->i_name, ip->i_team);
                        if (strlen(ip->i_name) + 3 < 8)
                                putc('\t', fp);
                }
                fprintf(fp, "%d\t%d\t%d\t%d\t%d\n",
                        ip->i_gkills, ip->i_bkills, ip->i_deaths,
                        ip->i_stillb, ip->i_saved);
        }

        (void) fclose(fp);
}

/*
 * clear_scores:
 *      Clear out the scores so the next session start clean
 */
static void
clear_scores(void)
{
        IDENT *ip, *nextip;

        for (ip = Scores; ip != NULL; ip = nextip) {
                nextip = ip->i_next;
                (void) free(ip);
        }
        Scores = NULL;
}