root/games/phantasia/io.c
/*      $NetBSD: io.c,v 1.14 2009/08/31 08:27:16 dholland Exp $ */

/*
 * io.c - input/output routines for Phantasia
 */

#include <string.h>
#include "include.h"

static void catchalarm(int) __dead2;

/*
 * FUNCTION: read a string from operator
 *
 * ARGUMENTS:
 *      char *cp - pointer to buffer area to fill
 *      int mx - maximum number of characters to put in buffer
 *
 * GLOBAL INPUTS: Echo, _iob[], Wizard, *stdscr
 *
 * GLOBAL OUTPUTS: _iob[]
 *
 * DESCRIPTION:
 *      Read a string from the keyboard.
 *      This routine is specially designed to:
 *
 *          - strip non-printing characters (unless Wizard)
 *          - echo, if desired
 *          - redraw the screen if CH_REDRAW is entered
 *          - read in only 'mx - 1' characters or less characters
 *          - nul-terminate string, and throw away newline
 *
 *      'mx' is assumed to be at least 2.
 */

void
getstring(char *cp, int mx)
{
        char *inptr;            /* pointer into string for next string */
        int x, y;               /* original x, y coordinates on screen */
        int ch;                 /* input */

        getyx(stdscr, y, x);    /* get coordinates on screen */
        inptr = cp;
        *inptr = '\0';          /* clear string to start */
        --mx;                   /* reserve room in string for nul terminator */

        do {
                /* get characters and process */
                if (Echo)
                        mvaddstr(y, x, cp);     /* print string on screen */
                clrtoeol();     /* clear any data after string */
                refresh();      /* update screen */

                ch = getchar(); /* get character */

                switch (ch) {
                case CH_ERASE:  /* back up one character */
                        if (inptr > cp)
                                --inptr;
                        break;

                case CH_KILL:   /* back up to original location */
                        inptr = cp;
                        break;

                case CH_NEWLINE:        /* terminate string */
                        break;

                case CH_REDRAW: /* redraw screen */
                        clearok(stdscr, TRUE);
                        continue;

                default:        /* put data in string */
                        if (ch >= ' ' || Wizard)
                                /* printing char; put in string */
                                *inptr++ = ch;
                }

                *inptr = '\0';  /* terminate string */
        } while (ch != CH_NEWLINE && inptr < cp + mx);
}

/*
 * FUNCTION: pause and prompt player
 *
 * ARGUMENTS:
 *      int where - line on screen on which to pause
 *
 * GLOBAL INPUTS: *stdscr
 *
 * DESCRIPTION:
 *      Print a message, and wait for a space character.
 */

void
more(int where)
{
        mvaddstr(where, 0, "-- more --");
        getanswer(" ", FALSE);
}

/*
 * FUNCTION: input a floating point number from operator
 *
 * RETURN VALUE: floating point number from operator
 *
 * GLOBAL INPUTS: Databuf[]
 *
 * DESCRIPTION:
 *      Read a string from player, and scan for a floating point
 *      number.
 *      If no valid number is found, return 0.0.
 */

double
infloat(void)
{
        double result;          /* return value */

        getstring(Databuf, SZ_DATABUF);
        if (sscanf(Databuf, "%lf", &result) < 1)
                /* no valid number entered */
                result = 0.0;

        return (result);
}

/*
 * FUNCTION: input an option value from player
 *
 * GLOBAL INPUTS: Player
 *
 * GLOBAL OUTPUTS: Player
 *
 * DESCRIPTION:
 *      Age increases with every move.
 *      Refresh screen, and get a single character option from player.
 *      Return a random value if player's ring has gone bad.
 */

int
inputoption(void)
{
        ++Player.p_age;         /* increase age */

        if (Player.p_ring.ring_type != R_SPOILED)
                /* ring ok */
                return (getanswer("T ", TRUE));
        else {
                /* bad ring */
                getanswer(" ", TRUE);
                return ((int)ROLL(0.0, 5.0) + '0');
        }
}

/*
 * FUNCTION: handle interrupt from operator
 *
 * GLOBAL INPUTS: Player, *stdscr
 *
 * DESCRIPTION:
 *      Allow player to quit upon hitting the interrupt key.
 *      If the player wants to quit while in battle, he/she automatically
 *      dies.
 */

void
interrupt(void)
{
        char line[81];          /* a place to store data already on screen */
        int loop;               /* counter */
        int x, y;               /* coordinates on screen */
        int ch;                 /* input */
        unsigned savealarm;     /* to save alarm value */

#ifdef SYS3
        signal(SIGINT, SIG_IGN);
#endif
#ifdef SYS5
        signal(SIGINT, SIG_IGN);
#endif

        savealarm = alarm(0);   /* turn off any alarms */

        getyx(stdscr, y, x);    /* save cursor location */

        for (loop = 0; loop < 80; ++loop) {     /* save line on screen */
                move(4, loop);
                line[loop] = inch();
        }
        line[80] = '\0';        /* nul terminate */

        if (Player.p_status == S_INBATTLE || Player.p_status == S_MONSTER) {
                /* in midst of fighting */
                mvaddstr(4, 0, "Quitting now will automatically kill your character.  Still want to ? ");
                ch = getanswer("NY", FALSE);
                if (ch == 'Y')
                        death("Bailing out");
                /* NOTREACHED */
        } else {
                mvaddstr(4, 0, "Do you really want to quit ? ");
                ch = getanswer("NY", FALSE);
                if (ch == 'Y')
                        leavegame();
                /* NOTREACHED */
        }

        mvaddstr(4, 0, line);   /* restore data on screen */
        move(y, x);             /* restore cursor */
        refresh();

#ifdef SYS3
        signal(SIGINT, interrupt);
#endif
#ifdef SYS5
        signal(SIGINT, interrupt);
#endif

        alarm(savealarm);       /* restore alarm */
}

/*
 * FUNCTION: get an answer from operator
 *
 * ARGUMENTS:
 *      char *choices - string of (upper case) valid choices
 *      bool def - set if default answer
 *
 * GLOBAL INPUTS: catchalarm(), Echo, _iob[], _ctype[], *stdscr, Timeout,
 *      Timeoenv[]
 *
 * GLOBAL OUTPUTS: _iob[]
 *
 * DESCRIPTION:
 *      Get a single character answer from operator.
 *      Timeout waiting for response.  If we timeout, or the
 *      answer in not in the list of valid choices, print choices,
 *      and wait again, otherwise return the first character in ths
 *      list of choices.
 *      Give up after 3 tries.
 */

int
getanswer(const char *choices, bool def)
{
        int ch;                         /* input */
        volatile int loop;              /* counter */
        volatile int oldx, oldy;        /* original coordinates on screen */

        getyx(stdscr, oldy, oldx);
        alarm(0);                       /* make sure alarm is off */

        for (loop = 3; loop; --loop) {
                /* try for 3 times */
                if (setjmp(Timeoenv) != 0) {
                        /* timed out waiting for response */
                        if (def || loop <= 1)
                                /* return default answer */
                                break;
                        else
                                /* prompt, and try again */
                                goto YELL;
                } else {
                        /* wait for response */
                        clrtoeol();
                        refresh();
#ifdef BSD41
                        sigset(SIGALRM, catchalarm);
#else
                        signal(SIGALRM, catchalarm);
#endif
                        /* set timeout */
                        if (Timeout)
                                alarm(7);       /* short */
                        else
                                alarm(600);     /* long */

                        ch = getchar();

                        alarm(0);       /* turn off timeout */

                        if (ch < 0) {
                                /* caught some signal */
                                ++loop;
                                continue;
                        } else if (ch == CH_REDRAW) {
                                /* redraw screen */
                                clearok(stdscr, TRUE);  /* force clear screen */
                                ++loop; /* don't count this input */
                                continue;
                        } else if (Echo) {
                                addch(ch);      /* echo character */
                                refresh();
                        }

                        if (islower(ch))
                                /* convert to upper case */
                                ch = toupper(ch);

                        if (def || strchr(choices, ch) != NULL)
                                /* valid choice */
                                return (ch);
                        else if (!def && loop > 1) {
                                /* bad choice; prompt, and try again */
YELL:                           mvprintw(oldy + 1, 0,
                                    "Please choose one of : [%s]\n", choices);
                                move(oldy, oldx);
                                clrtoeol();
                                continue;
                        } else
                                /* return default answer */
                                break;
                }
        }

        return (*choices);
}

/*
 * FUNCTION: catch timer when waiting for input
 *
 * GLOBAL INPUTS: Timeoenv[]
 *
 * DESCRIPTION:
 *      Come here when the alarm expires while waiting for input.
 *      Simply longjmp() into getanswer().
 */

static void
catchalarm(__unused int sig)
{
        longjmp(Timeoenv, 1);
}