/*
 * Spy vs "Bob"
 *
 * Based on the Apple ][ game "Spy vs Spy", and 'dodger' by Bert Nelson
 */

/*
 * Copyright 1992 David Lemke and Network Computing Devices
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Network Computing Devices not be
 * used in advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.  Network Computing
 * Devices makes no representations about the suitability of this software
 * for any purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 * NETWORK COMPUTING DEVICES DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS,
 * IN NO EVENT SHALL NETWORK COMPUTING DEVICES BE LIABLE FOR ANY SPECIAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE
 * OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Author:
 *		Dave Lemke
 *		lemke@ncd.com
 *
 *		Network Computing Devices, Inc
 *		350 North Bernardo Ave
 *		Mountain View, CA 94043
 *
 *	@(#)svs.c	1.9	92/02/10
 *
 */

#include	<stdio.h>
#include	<X11/Xos.h>
#include 	<sys/types.h>
#include 	<sys/time.h>
#include	<errno.h>
#include	<X11/Xlib.h>
#include	<X11/Xutil.h>
#include	<math.h>

extern char *getenv();

extern int  errno;
char       *progname;
char        user_name[40];
Bool        debug = False;

#define	rnd(x)	(random() % (x))

/* game stuff */
#define	NUM_LEDGES		10
#define	NUM_VATORS		5

#define	MIN_LEDGE		3	/* tightest it will get */

#define	NUM_LIVES		5
#define	START_LEDGE		(NUM_LEDGES - 1)
#define	START_TIME		100
#define	DELAY_TIME		20000
#define	ROUNDS_PER_UPDATE	8

#define	GIFT_SCORE		500
#define	GIFT_NOTYET		1
#define	GIFT_OUT		2
#define	GIFT_TAKEN		3


extern void update_scores();
extern void show_scores();

int         score = 0;
int         highscore = 0;
int         time_left;
int         time_interval = ROUNDS_PER_UPDATE;
int         lives = 0;
int         ledge_num;
int         ledge_side;
int         level;
int	    ledge;
int         vator_loc[NUM_VATORS];
int         vator_speed[NUM_VATORS];
int         vator_run;
int         man_x,
            man_y;
int         man_speed;
int         man_delta;

int         gift_x,
            gift_y;
int         gift_state;
Bool        refresh_gift;
int         gift_time;

/* increase with each level */
double      level_factor = 1.0;

/* speeds */
#define	MIN_VATOR_SPEED		5
#define	VATOR_SPEED_RANGE	10

#ifdef fast
#define	LEVEL_INCREASE		(0.2)
#else
#define	LEVEL_INCREASE		(0.0)
#endif

/* X stuff */

Display    *dpy;
Window      game_win;
Colormap    cmap;
GC          text_gc;
GC          vator_gc;
GC          draw_gc;
unsigned long man_color,
            vator_color,
            vator_bg_color,
            timer_color,
            gift_color,
            splat_color,
            ledge_color;
unsigned long text_color;
unsigned long bg_color;
Bool        paused = False;
Pixmap      man_left,
            man_right,
            man_stand,
            splat_map,
            slack_map,
            vator_map;

#define	MAN		0
#define	VATOR		1
#define	TIMER		2
#define	LEDGE		3
#define	TEXT		4
#define	BACKGROUND	5
#define	GIFT		6
#define	VATOR_BG	7
#define	SPLAT		8

static char *game_colors[] = {
    "pink",			/* MAN */
    "blue",			/* VATOR */
    "yellow",			/* TIMER */
    "brown",			/* LEDGE */
    "white",			/* TEXT */
    "black",			/* BACKGROUND */
    "green",			/* GIFT */
    "white",			/* VATOR_BG */
    "pink",			/* SPLAT */
};


static char bob_vator_bits[] = {
    0xff, 0x01, 0x50, 0x25, 0x00, 0xfe, 0x03, 0x07, 0x00, 0xfc, 0xff, 0x03,
    0x80, 0x03, 0x03, 0x00, 0xff, 0x5e, 0x06, 0x00, 0x03, 0x03, 0xc0, 0x97,
    0x44, 0x0a, 0x00, 0x03, 0x03, 0xc0, 0x17, 0xa9, 0x1d, 0x00, 0x03, 0x03,
    0xe0, 0x7f, 0xdd, 0x7f, 0x00, 0x03, 0x03, 0xf0, 0xff, 0xff, 0xf3, 0x00,
    0x03, 0x03, 0xf0, 0xff, 0xdf, 0xf0, 0x00, 0x03, 0x03, 0xf0, 0xa5, 0x17,
    0xf0, 0x01, 0x03, 0x03, 0xf0, 0x00, 0x00, 0xf0, 0x00, 0x03, 0x03, 0xf8,
    0x00, 0x00, 0xe8, 0x00, 0x03, 0x03, 0xf0, 0x01, 0x00, 0xe8, 0x00, 0x03,
    0x03, 0xf0, 0x00, 0x00, 0xe4, 0x00, 0x03, 0x03, 0xf8, 0x00, 0x00, 0xe8,
    0x00, 0x03, 0x03, 0xf0, 0x09, 0x80, 0xfb, 0x00, 0x03, 0x03, 0xe8, 0x3e,
    0xe0, 0xee, 0x00, 0x03, 0x03, 0xf8, 0x68, 0x70, 0xfd, 0x00, 0x03, 0x03,
    0x30, 0xee, 0x78, 0xe3, 0x00, 0x03, 0x03, 0x30, 0xf2, 0xa8, 0x60, 0x00,
    0x03, 0x03, 0x30, 0xb4, 0xc0, 0x63, 0x00, 0x03, 0x03, 0x30, 0x98, 0x00,
    0x40, 0x00, 0x03, 0x03, 0x20, 0x80, 0x00, 0xa0, 0x00, 0x03, 0x03, 0xb0,
    0x40, 0x00, 0x60, 0x00, 0x03, 0x03, 0x80, 0xd1, 0x70, 0x18, 0x00, 0x03,
    0x03, 0xc0, 0xe9, 0x8e, 0x0d, 0x00, 0x03, 0x03, 0x80, 0xf7, 0x87, 0x3f,
    0x00, 0x03, 0x03, 0x80, 0xfa, 0xea, 0x0c, 0x00, 0x03, 0x03, 0x80, 0x32,
    0x49, 0x0c, 0x00, 0x03, 0x03, 0x00, 0x35, 0x35, 0x0c, 0x00, 0x03, 0x03,
    0x00, 0xf1, 0x0f, 0x06, 0x00, 0x03, 0x03, 0x00, 0xf6, 0x15, 0x02, 0x00,
    0x03, 0x03, 0x10, 0xda, 0x0b, 0x03, 0x00, 0x03, 0x03, 0x48, 0x4e, 0x00,
    0x01, 0x00, 0x03, 0x03, 0x58, 0x77, 0x80, 0x00, 0x00, 0x03, 0x03, 0xe2,
    0xf3, 0xc0, 0x00, 0x00, 0x03, 0x03, 0xcc, 0x41, 0x3f, 0x00, 0x00, 0x03,
    0x03, 0xdc, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0xdc, 0x00, 0x00, 0x00,
    0x00, 0x03, 0x07, 0x70, 0x00, 0x00, 0x00, 0x80, 0x03, 0xff, 0x01, 0x00,
0x00, 0x00, 0xfe, 0x03};

static char man_left_bits[] = {
    0x18, 0x01, 0x00, 0xf8, 0x00, 0x00, 0x7e, 0x00, 0x00, 0xfe, 0x00, 0x00,
    0xfc, 0x01, 0x00, 0xfa, 0x03, 0x00, 0xf1, 0xff, 0x01, 0x20, 0xff, 0x01,
    0x84, 0x1f, 0x01, 0x86, 0x3f, 0x01, 0xce, 0xff, 0x01, 0x78, 0xff, 0x03,
    0x20, 0xff, 0x01, 0x00, 0xff, 0x00, 0x80, 0x7f, 0x04, 0xc0, 0xfe, 0x0e,
0xc0, 0x9c, 0x0b, 0x80, 0x09, 0x09, 0x00, 0x03, 0x00, 0xc0, 0x01, 0x00};

static char man_right_bits[] = {
    0x00, 0x88, 0x01, 0x00, 0xf0, 0x01, 0x00, 0xe0, 0x07, 0x00, 0xf0, 0x07,
    0x00, 0xf8, 0x03, 0x00, 0xfc, 0x05, 0xf8, 0xff, 0x08, 0xf8, 0x4f, 0x00,
    0x88, 0x1f, 0x02, 0xc8, 0x1f, 0x06, 0xf8, 0x3f, 0x07, 0xfc, 0xef, 0x01,
    0xf8, 0x4f, 0x00, 0xf0, 0x0f, 0x00, 0xe2, 0x1f, 0x00, 0xf7, 0x37, 0x00,
0x9d, 0x33, 0x00, 0x09, 0x19, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x38, 0x00};

static char man_stand_bits[] = {
    0x80, 0x03, 0x00, 0xc0, 0x07, 0x00, 0xf0, 0x1f, 0x00, 0xc0, 0x07, 0x00,
    0x80, 0x03, 0x00, 0x00, 0x01, 0x00, 0x80, 0x03, 0x00, 0xf0, 0x0f, 0x00,
    0xfc, 0x1f, 0x00, 0xfe, 0xbf, 0x0f, 0xf3, 0x7f, 0x01, 0xf3, 0xff, 0x01,
    0xfe, 0x1f, 0x01, 0xfc, 0x1f, 0x00, 0xf8, 0x3f, 0x00, 0xf0, 0x3f, 0x00,
0xf0, 0x7f, 0x00, 0xc0, 0x0c, 0x00, 0xc0, 0x0c, 0x00, 0xf0, 0x3c, 0x00};


static char slack_bits[] = {
0xeb, 0x56, 0xa9, 0x32, 0xeb, 0x32, 0xaa, 0x52, 0xbb, 0x56};

static char splatter_bits[] = {
    0x68, 0x20, 0x02, 0x00, 0x2b, 0x49, 0x31, 0x1e, 0x3e, 0x92, 0x88, 0x11,
    0x26, 0x22, 0x45, 0x1e, 0x6e, 0xec, 0xe3, 0x07, 0xf8, 0xff, 0xff, 0x00,
    0x00, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00};

#define	MAN_WIDTH	20
#define	MAN_HEIGHT	20
#define	MAN_DELTA	6	/* motion per step */
#define	MIN_MAN_DELTA	4

#define SPLAT_WIDTH	30
#define SPLAT_HEIGHT	10

#define	VATOR_WIDTH	50
#define	VATOR_HEIGHT	40
#define	VATOR_GAP	40	/* space between them */

#define	GIFT_WIDTH	15
#define	GIFT_HEIGHT	5

#define	LEDGE_WIDTH	30
#define	LEDGE_HEIGHT	4

#define	LEDGE_GAP	((int)(VATOR_HEIGHT * 1.2 + LEDGE_HEIGHT))

#define	TIMER_HEIGHT	25
#define	TIMER_SLICE	1	/* visual size of each tick */

#define	INFO_HEIGHT	100	/* size of timer, score, etc */

#define	DELIM_HEIGHT	10	/* bar between game and info */

#define	WIN_WIDTH	(NUM_VATORS * VATOR_WIDTH + (NUM_VATORS - 1) * VATOR_GAP + 2 * LEDGE_WIDTH)
#define	WIN_HEIGHT	(NUM_LEDGES * LEDGE_GAP + INFO_HEIGHT + DELIM_HEIGHT)

/* range of y locs for elevators */
#define	VATOR_RUN	(WIN_HEIGHT - (INFO_HEIGHT + DELIM_HEIGHT) - VATOR_HEIGHT)

/* range of man */
#define	MAN_MAX_X	(WIN_WIDTH - MAN_DELTA - MAN_WIDTH)

#define	TIMER_Y		(WIN_HEIGHT - ((INFO_HEIGHT * 2) / 3))
#define	EXTRA_MAN_X	(WIN_WIDTH - 2 * MAN_WIDTH)
#define	STATUS_Y	(TIMER_Y + 15)
#define	STATUS_X	(2 * TIMER_SLICE)

#define	FONTNAME	"fixed"

#define	COLLISION_FUDGE	4

#define	vator_pos(i) (LEDGE_WIDTH + (i) * (VATOR_GAP + VATOR_WIDTH))
#define	man_start_pos(i)	(MAN_DELTA + (i) * (WIN_WIDTH - MAN_WIDTH - (2 * MAN_DELTA)))
#define	man_y_pos(l)	((LEDGE_GAP * ((l) + 1)) - (LEDGE_HEIGHT + MAN_HEIGHT))

#define	min(a, b)	(((a) < (b)) ? (a) : (b))
#define	max(a, b)	(((a) > (b)) ? (a) : (b))

static void wait_for_start();
static void run();
static void finish_up();

static void draw_ledges();
static void draw_init_screen();
static void draw_scores();
static void draw_obj();
static void draw_timer();
static void draw_status();
static void draw_message();
static void draw_message2();
static void erase_obj();
static void new_man();
static void new_gift();
static void flush_keyboard();

static void
usage()
{
    fprintf(stderr, "Usage: %s [-display displayname]\n", progname);
    exit(0);
}

static unsigned long
get_color(cname)
    char       *cname;
{
    XColor      col;

    XParseColor(dpy, cmap, cname, &col);
    XAllocColor(dpy, cmap, &col);
    return col.pixel;
}

static void
init_display(ac, av, dname)
    int         ac;
    char      **av;
    char       *dname;
{
    Bool        is_color = False;
    int         screen;
    unsigned long wmask,
                gcmask;
    XSetWindowAttributes wattr;
    XGCValues   gcv;
    XFontStruct *pfont;
    int         x,
                y;
    XSizeHints  xsh;
    XWMHints    wmhints;

    dpy = XOpenDisplay(dname);
    if (!dpy) {
	fprintf(stderr, "%s: Couldn't open display \"%s\"\n", progname, dname);
	exit(-1);
    }
    screen = DefaultScreen(dpy);

    if (debug)
	XSynchronize(dpy, 1);

    if (DisplayCells(dpy, screen) > 2)
	is_color = True;
    cmap = DefaultColormap(dpy, screen);

    /* set up colors */
    if (is_color) {
	man_color = get_color(game_colors[MAN]);
	splat_color = get_color(game_colors[SPLAT]);
	vator_color = get_color(game_colors[VATOR]);
	vator_bg_color = get_color(game_colors[VATOR_BG]);
	ledge_color = get_color(game_colors[LEDGE]);
	timer_color = get_color(game_colors[TIMER]);
	text_color = get_color(game_colors[TEXT]);
	gift_color = get_color(game_colors[GIFT]);
	bg_color = get_color(game_colors[BACKGROUND]);
    } else {
	man_color = WhitePixel(dpy, screen);
	splat_color = WhitePixel(dpy, screen);
	vator_color = BlackPixel(dpy, screen);
	vator_bg_color = WhitePixel(dpy, screen);
	ledge_color = WhitePixel(dpy, screen);
	timer_color = WhitePixel(dpy, screen);
	text_color = WhitePixel(dpy, screen);
	gift_color = WhitePixel(dpy, screen);
	bg_color = BlackPixel(dpy, screen);
    }

    x = 100;
    y = 100;

    wattr.background_pixel = bg_color;
    wattr.event_mask = ExposureMask | KeyPressMask |
	ButtonPressMask | ButtonReleaseMask;

    wmask = CWEventMask | CWBackPixel;

    game_win = XCreateWindow(dpy, RootWindow(dpy, screen), x, y, WIN_WIDTH,
	       WIN_HEIGHT, 0, CopyFromParent, CopyFromParent, CopyFromParent,
			     wmask, &wattr);

    xsh.flags = PSize | PMinSize | PMaxSize;
    xsh.width = xsh.min_width = xsh.max_width = WIN_WIDTH;
    xsh.height = xsh.min_height = xsh.max_height = WIN_HEIGHT;
    XSetStandardProperties(dpy, game_win, "Spy vs \"Bob\"", "SvB", None,
			   av, ac, &xsh);

    wmhints.input = True;
    wmhints.flags = InputHint;
    XSetWMHints(dpy, game_win, &wmhints);

    pfont = XLoadQueryFont(dpy, FONTNAME);

    gcmask = GCForeground | GCBackground;
    gcv.foreground = text_color;
    gcv.background = bg_color;
    if (pfont) {
	gcv.font = pfont->fid;
	gcmask |= GCFont;
    }
    text_gc = XCreateGC(dpy, game_win, gcmask, &gcv);

    gcmask = GCForeground | GCBackground | GCGraphicsExposures;
    gcv.foreground = man_color;
    gcv.background = bg_color;
    gcv.graphics_exposures = False;
    draw_gc = XCreateGC(dpy, game_win, gcmask, &gcv);

    gcmask = GCForeground | GCBackground | GCGraphicsExposures;
    gcv.foreground = vator_color;
    gcv.background = vator_bg_color;
    gcv.graphics_exposures = False;
    vator_gc = XCreateGC(dpy, game_win, gcmask, &gcv);

    /* make bitmaps */
    vator_map = XCreateBitmapFromData(dpy, game_win, bob_vator_bits,
				      VATOR_WIDTH, VATOR_HEIGHT);
    man_left = XCreateBitmapFromData(dpy, game_win, man_left_bits,
				     MAN_WIDTH, MAN_HEIGHT);
    man_right = XCreateBitmapFromData(dpy, game_win, man_right_bits,
				      MAN_WIDTH, MAN_HEIGHT);
    man_stand = XCreateBitmapFromData(dpy, game_win, man_stand_bits,
				      MAN_WIDTH, MAN_HEIGHT);
    slack_map = XCreateBitmapFromData(dpy, game_win, slack_bits,
				      GIFT_WIDTH, GIFT_HEIGHT);
    splat_map = XCreateBitmapFromData(dpy, game_win, splatter_bits,
				      SPLAT_WIDTH, SPLAT_HEIGHT);

    XMapWindow(dpy, game_win);
}

main(argc, argv)
    int         argc;
    char      **argv;
{
    int         i;
    char       *dname = NULL;

    progname = argv[0];
    for (i = 1; i < argc; i++) {
	if (!strncmp(argv[i], "-d", 2)) {
	    if (argv[++i])
		dname = argv[i];
	    else
		usage();
	}
    }
    strcpy(user_name, "Unknown");
    if (getenv("USER"))
	strcpy(user_name, getenv("USER"));
    srandom(getpid());
    init_display(argc, argv, dname);
    update_scores();
    draw_init_screen();
    while (1) {
	wait_for_start();
	run();
	finish_up();
    }
}

static void
refresh_screen()
{
    int         i;

    XClearWindow(dpy, game_win);

    draw_ledges();

    /* delimiter */
    XFillRectangle(dpy, game_win, text_gc, 0, WIN_HEIGHT - INFO_HEIGHT,
		   WIN_WIDTH, DELIM_HEIGHT);

    for (i = 0; i < NUM_VATORS; i++) {
	draw_obj(vator_pos(i), vator_loc[i], VATOR);
    }
    draw_obj(man_x, man_y, MAN);
    if (gift_state == GIFT_OUT)
	draw_obj(gift_x, gift_y, GIFT);
    draw_scores();
    draw_message(NULL);
    draw_message2(NULL);
}

static void
draw_init_screen()
{
    int         i;

    XClearWindow(dpy, game_win);

    /* place some random elevators */
    for (i = 0; i < NUM_VATORS; i++) {
	vator_loc[i] = rnd(VATOR_RUN);
    }

    /* and the man */
    man_x = man_start_pos(rnd(2));
    man_y = man_y_pos(rnd(NUM_LEDGES));

    gift_state = GIFT_NOTYET;
    new_gift();

    draw_message("     Spy Vs \"Bob\"     Hit 's' to Start, 'q' to Quit, 'h' for Hi Scores");
    draw_message2("    Use 'j', 'k', SPACE or Mouse buttons to move, 'p' to Pause");

    refresh_screen();
}

char
get_key(kev)
    XKeyPressedEvent *kev;
{
    char        buffer[10];
    int         len;

    len = XLookupString(kev, buffer, 10, NULL, NULL);
    if (len)
	return buffer[0];
    else
	return (char) -1;
}

/*
 * draws instructions, waits for 'q' or 's'
 */
static void
wait_for_start()
{
    XEvent      ev;
    Bool        done = False;
    char        c;

    while (!done) {
	XNextEvent(dpy, &ev);
	switch (ev.type) {
	case Expose:
	    if (ev.xexpose.count == 0)
		refresh_screen();
	    break;
	case KeyPress:
	    c = get_key((XKeyPressedEvent *) & ev);
	    switch (c) {
	    case 'q':
		exit(0);
		break;
	    case 's':
		done = True;
		break;
	    case 'h':
		show_scores();
                refresh_screen();
		break;
	    default:
		break;
	    }
	}
    }
    score = 0;
    draw_message("");
    draw_message2("");
}

static void
init_vators()
{
    int         i;

    for (i = 0; i < NUM_VATORS; i++) {
	vator_loc[i] = rnd(vator_run);
	vator_speed[i] = rnd(VATOR_SPEED_RANGE) + MIN_VATOR_SPEED;
    }
}

static void
got_across()
{
    if (gift_state == GIFT_OUT)
	erase_obj(gift_x, gift_y, GIFT);
    man_speed = 0;
    ledge_num--;
    score += 200 + (time_left * 5);
    ledge++;
    if (ledge_num < 0) {	/* finished level */
	/* shrink the game board */
	if (level < (START_LEDGE - MIN_LEDGE)) {
	    /* don't drop it to 0, in case anyone's that good */
	    ledge_num = START_LEDGE - level;
	    vator_run -= LEDGE_GAP;
	} else {
	    ledge_num = MIN_LEDGE;
	}
	lives++;
	ledge_side = 0;
	new_man();
	level_factor += LEVEL_INCREASE;
	level++;
	time_interval--;	/* speed it up too */
	if (time_interval < 1)
	    time_interval = 1;
	man_delta--;
	if (man_delta <= MIN_MAN_DELTA)
	    man_delta = MIN_MAN_DELTA;
    }
    man_y = man_y_pos(ledge_num);
    time_left = START_TIME;
    gift_time = START_TIME - rnd(START_TIME / 2);
    gift_state = GIFT_NOTYET;
    flush_keyboard();
    draw_scores();
}

static void
move_man()
{
    erase_obj(man_x, man_y, MAN);
    man_x += man_speed;
    if (man_x < man_start_pos(0)) {
	man_x = man_start_pos(0);
	if (ledge_side == 1) {	/* made it across */
	    ledge_side = 0;
	    got_across();
	}
    }
    if (man_x > man_start_pos(1)) {
	man_x = man_start_pos(1);
	if (ledge_side == 0) {
	    ledge_side = 1;
	    got_across();
	}
    }
    draw_obj(man_x, man_y, MAN);
}

static void
move_vators()
{
    int         i;

    for (i = 0; i < NUM_VATORS; i++) {
	erase_obj(vator_pos(i), vator_loc[i], VATOR);
	vator_loc[i] += vator_speed[i];
	if (vator_loc[i] <= 0) {
	    vator_loc[i] = 0;
	    vator_speed[i] = level_factor *
		(rnd(VATOR_SPEED_RANGE) + MIN_VATOR_SPEED);
	} else if (vator_loc[i] >= vator_run) {
	    vator_loc[i] = vator_run;
	    vator_speed[i] = -level_factor *
		(rnd(VATOR_SPEED_RANGE) + MIN_VATOR_SPEED);
	}
	draw_obj(vator_pos(i), vator_loc[i], VATOR);
    }
}

static void
new_man()
{
    int         i;

    erase_obj(man_x, man_y, MAN);
    if (gift_state == GIFT_OUT)
	erase_obj(gift_x, gift_y, GIFT);
    for (i = 0; i < NUM_VATORS; i++)
	erase_obj(vator_pos(i), vator_loc[i], VATOR);
    time_left = START_TIME;
    gift_time = START_TIME - rnd(START_TIME / 2);
    init_vators();
    man_speed = 0;
    man_x = man_start_pos(ledge_side);
    man_y = man_y_pos(ledge_num);
    draw_obj(man_x, man_y, MAN);
    draw_status();
    flush_keyboard();
}

static void
check_collision()
{
    int         i;

    for (i = 0; i < NUM_VATORS; i++) {
	if (((man_x + MAN_WIDTH - COLLISION_FUDGE) > vator_pos(i)) &&
		(man_x < (vator_pos(i) + VATOR_WIDTH - COLLISION_FUDGE))) {
	    if ((man_y > vator_loc[i]) &&
		    (man_y < vator_loc[i] + VATOR_HEIGHT)) {
		lives--;
		draw_message("SPLAT!!!");
		erase_obj(man_x, man_y, MAN);
		draw_obj(vator_pos(i), vator_loc[i], VATOR);
		draw_obj(man_x - 5, man_y + 10, SPLAT);
		XSync(dpy, 0);
		sleep(1);
		refresh_screen();
		new_man();
		return;
	    }
	}
    }

    if (gift_state == GIFT_OUT) {
	if (((man_x + MAN_WIDTH) > gift_x) &&
		(man_x < (gift_x + GIFT_WIDTH))) {
	    score += GIFT_SCORE;
	    gift_state = GIFT_TAKEN;
	    draw_scores();
	    return;
	}
    }
}

static void
flush_keyboard()
{
    XEvent      ev;

    while (XCheckTypedEvent(dpy, KeyPress, &ev));
    while (XCheckTypedEvent(dpy, ButtonPress, &ev));
    while (XCheckTypedEvent(dpy, ButtonRelease, &ev));
}

static void
new_gift()
{
    if (gift_state == GIFT_NOTYET) {
	gift_y = man_y + GIFT_HEIGHT;
	gift_x = rnd(WIN_WIDTH - 2 * LEDGE_WIDTH) + LEDGE_WIDTH;
	draw_obj(gift_x, gift_y, GIFT);
	gift_state = GIFT_OUT;
    }
}

static Bool
handle_event()
{
    char        c = '\0';
    XEvent      ev;
    int         button;

    XNextEvent(dpy, &ev);
    switch (ev.type) {
    case KeyPress:
	c = get_key((XKeyPressedEvent *) & ev);
	switch (c) {
	case 'q':
	    return False;
	case 'j':
	    man_speed = -man_delta;
	    break;
	case 'k':
	    man_speed = man_delta;
	    break;
	case ' ':
	    man_speed = 0;
	    break;
	case 'p':
	    paused = !paused;
	    draw_status();
	    break;
	default:
	    break;
	}
	break;
    case Expose:
	refresh_screen();
	break;
    case ButtonPress:
	button = ev.xbutton.button;
	if (button == 1) {
	    man_speed = -man_delta;
	} else {
	    man_speed = man_delta;
	}
	break;
    case ButtonRelease:
	button = ev.xbutton.button;
	man_speed = 0;
	break;
    }
    return True;
}

static void
init_parms()
{
    ledge_num = START_LEDGE;
    ledge_side = 0;
    level = 1;
    ledge = 0;
    lives = NUM_LIVES;
    man_delta = MAN_DELTA;
    vator_run = VATOR_RUN;
    erase_obj(gift_x, gift_y, GIFT);
    gift_state = GIFT_NOTYET;
    level_factor = 1.0;
    time_interval = ROUNDS_PER_UPDATE;
}

/*
 * plays the game
 */
static void
run()
{
    int         time = 0;
    int         ret;
    int         serverfd = XConnectionNumber(dpy);
    fd_set      read_fds,
                base;
    struct timeval tval,
               *tm;

    FD_ZERO(&base);
    FD_SET(serverfd, &base);
    init_parms();
    new_man();

    tval.tv_sec = 0;
    tval.tv_usec = DELAY_TIME;
    while (1) {
	if (XPending(dpy)) {
	    if (!handle_event())
		return;
	    continue;
	}
	if (!paused)
	    tm = &tval;
	else
	    tm = (struct timeval *) 0;
	read_fds = base;
	ret = select(serverfd + 1, &read_fds, (fd_set *) 0, (fd_set *) 0, tm);
	if (ret == -1)
	    if (errno == EINTR)
		continue;
	if (ret == 1) {
	    if (!handle_event())
		return;
	    continue;
	}
	if (paused)
	    continue;
	if (time_left == 0) {
	    lives--;
	    draw_message("Time's Up");
	    XSync(dpy, 0);
	    sleep(1);
	    new_man();
	    time = 0;
	}
	if (lives == 0)
	    return;

	time++;
	if (time >= time_interval) {
	    time = 0;
	    time_left--;
	    if (gift_time <= time_left) {
		new_gift();
		gift_time = -1;
	    }
	    draw_timer();
            /* XXX this shouldn't really be necessray, but on a slow display,
             * output can get queueed enough that the action gets delayed
             */
	    XSync(dpy, 0);
	}
	move_vators();
	move_man();
	check_collision();
	if (gift_state == GIFT_OUT)
	    draw_obj(gift_x, gift_y, GIFT);
    }
}

/*
 * cleans up screen, shows score
 */
static void
finish_up()
{
    draw_message("     Spy Vs \"Bob\"     Hit 's' to Start, 'q' to Quit, 'h' for Hi Scores");
    draw_message2("    Use 'j', 'k', SPACE or Mouse buttons to move, 'p' to Pause");
    add_score(score, ledge);
    draw_scores();
    refresh_screen();
}

static void
erase_obj(x, y, type)
    int         x,
                y,
                type;
{
    switch (type) {
    case VATOR:
	XClearArea(dpy, game_win, x, y, VATOR_WIDTH, VATOR_HEIGHT, False);
	break;
    case GIFT:
	XClearArea(dpy, game_win, x, y, GIFT_WIDTH, GIFT_HEIGHT, False);
	break;
    case MAN:
	XClearArea(dpy, game_win, x, y, MAN_WIDTH, MAN_HEIGHT, False);
	break;
    }
}

static void
draw_obj(x, y, type)
    int         x,
                y,
                type;
{
    Pixmap      mmap;

    switch (type) {
    case VATOR:
	XCopyPlane(dpy, vator_map, game_win, vator_gc,
		   0, 0, VATOR_WIDTH, VATOR_HEIGHT, x, y, 1);
	break;
    case GIFT:
	XSetForeground(dpy, draw_gc, gift_color);
	XCopyPlane(dpy, slack_map, game_win, draw_gc,
		   0, 0, GIFT_WIDTH, GIFT_HEIGHT, x, y, 1);
	break;
    case MAN:
	XSetForeground(dpy, draw_gc, man_color);
	if (man_speed == 0)
	    mmap = man_stand;
	else if (man_speed < 0)
	    mmap = man_left;
	else
	    mmap = man_right;
	XCopyPlane(dpy, mmap, game_win, draw_gc,
		   0, 0, MAN_WIDTH, MAN_HEIGHT, x, y, 1);
	break;
    case SPLAT:
	XSetForeground(dpy, draw_gc, splat_color);
	XCopyPlane(dpy, splat_map, game_win, draw_gc,
		   0, 0, SPLAT_WIDTH, SPLAT_HEIGHT, x, y, 1);
	break;
    case LEDGE:
	XSetForeground(dpy, draw_gc, ledge_color);
	XFillRectangle(dpy, game_win, draw_gc, x, y, LEDGE_WIDTH, LEDGE_HEIGHT);
	break;
    }
}

static void
draw_ledges()
{
    int         i;
    int         y;

    for (i = 0; i < NUM_LEDGES; i++) {
	y = (i + 1) * LEDGE_GAP;
	draw_obj(0, y, LEDGE);
	draw_obj(WIN_WIDTH - LEDGE_WIDTH, y, LEDGE);
    }
}

static void
draw_scores()
{
    char        score_buffer[256];

    sprintf(score_buffer, "Time Left      Level %2d Best %7d Score %7d Lives %2d",
	    level, highscore, score, lives);
    XDrawImageString(dpy, game_win, text_gc, STATUS_X, WIN_HEIGHT - 5,
		     score_buffer, strlen(score_buffer));
}

static void
draw_timer()
{
    if (paused) {
	draw_message("PAUSED");
    } else {
	XClearArea(dpy, game_win, TIMER_SLICE,
		   TIMER_Y, TIMER_SLICE * START_TIME, TIMER_HEIGHT, False);
	XSetForeground(dpy, draw_gc, timer_color);
	XFillRectangle(dpy, game_win, draw_gc, TIMER_SLICE,
		       TIMER_Y, TIMER_SLICE * time_left, TIMER_HEIGHT);
    }
}

static void
draw_status()
{
    int         i,
                x,
                y;
    int         old_sp;

    XClearArea(dpy, game_win, TIMER_SLICE * (START_TIME + 1), TIMER_Y,
	       10000, MAN_HEIGHT, False);
    y = TIMER_Y;
    x = EXTRA_MAN_X;
    old_sp = man_speed;
    man_speed = 0;		/* make sure they're standing */
    for (i = 0; i < min(lives - 1, 7); i++) {
	draw_obj(x, y, MAN);
	x -= (int) (MAN_WIDTH * 1.5);
    }
    man_speed = old_sp;
    draw_timer();
    draw_scores();
}

static void
draw_message(str)
    char       *str;
{
    static char msgbuf[256];

    if (str)
	strcpy(msgbuf, str);
    XClearArea(dpy, game_win, TIMER_SLICE,
	       TIMER_Y, TIMER_SLICE * START_TIME, TIMER_HEIGHT, False);
    XDrawImageString(dpy, game_win, text_gc, STATUS_X, STATUS_Y,
		     msgbuf, strlen(msgbuf));
}

static void
draw_message2(str)
    char       *str;
{
    static char msgbuf2[256];

    if (str)
	strcpy(msgbuf2, str);
    XClearArea(dpy, game_win, STATUS_X,
	       STATUS_Y + 10, 1000, 15, False);
    XDrawImageString(dpy, game_win, text_gc, STATUS_X, STATUS_Y + 20,
		     msgbuf2, strlen(msgbuf2));
}
