/* -*- Mode: C; tab-width: 4 -*- */
/* maze --- a varying maze */

#if 0
static const char sccsid[] = "@(#)maze.c	5.00 2000/11/01 xlockmore";

#endif

/*-
 * Copyright (c) 1988 by Sun Microsystems
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation.
 *
 * This file is provided AS IS with no warranties of any kind.  The author
 * shall have no liability with respect to the infringement of copyrights,
 * trade secrets or any patents by this file or any part thereof.  In no
 * event will the author be liable for any lost revenue or profits or
 * other special, indirect and consequential damages.
 *
 * Revision History:
 * 27-Nov-2001: interactive maze mode Ephraim Yawitz fyawitz@actcom.co.il
 * 01-Nov-2000: Allocation checks
 * 27-Oct-1997: xpm and ras capability added
 * 10-May-1997: Compatible with xscreensaver
 * 27-Feb-1996: Add ModeInfo args to init and callback hooks, use new
 *              ModeInfo handle to specify long pauses (eliminate onepause).
 *		        Ron Hitchens <ron AT idiom.com>
 * 20-Jul-1995: minimum size fix Peter Schmitzberger <schmitz@coma.sbg.ac.at>
 * 17-Jun-1995: removed sleep statements
 * 22-Mar-1995: multidisplay fix Caleb Epstein <epstein_caleb@jpmorgan.com>
 * 09-Mar-1995: changed how batchcount is used
 * 27-Feb-1995: patch for VMS
 * 04-Feb-1995: patch to slow down maze Heath Kehoe <hakehoe AT icaen.uiowa.edu>
 * 17-Jun-1994: HP ANSI C compiler needs a type cast for gray_bits
 *              Richard Lloyd <R.K.Lloyd@csc.liv.ac.uk>
 * 02-Sep-1993: xlock version David Bagley <bagleyd AT verizon.net>
 * 07-Mar-1993: Good ideas from xscreensaver Jamie Zawinski <jwz AT jwz.org>
 * 06-Jun-1985: Martin Weiss Sun Microsystems
 */

/*-
 * original copyright
 * **************************************************************************
 * Copyright 1988 by Sun Microsystems, Inc. Mountain View, CA.
 *
 * All Rights Reserved
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, 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 names of Sun or MIT not be used in advertising
 * or publicity pertaining to distribution of the software without specific
 * prior written permission. Sun and M.I.T. make no representations about the
 * suitability of this software for any purpose. It is provided "as is"
 * without any express or implied warranty.
 *
 * SUN DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 * IN NO EVENT SHALL SUN 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.
 * ***************************************************************************
 */

#ifdef STANDALONE
#define MODE_maze
#define DEFAULTS "*delay: 1000 \n" \
	"*cycles: 3000 \n" \
	"*size: -10 \n" \
	"*ncolors: 64 \n" \
	"*bitmap: \n" \
	"*trackmouse: False \n" \

# define free_maze 0
# define reshape_maze 0
# define maze_handle_event 0
#include "xlockmore.h"		/* in xscreensaver distribution */
#else /* STANDALONE */
#include "xlock.h"		/* in xlockmore distribution */
#include "color.h"
#include "iostuff.h"
#endif /* STANDALONE */

#ifdef MODE_maze

#define DEF_TRACKMOUSE  "False"
#define DEF_THICK  "False"
#define DEF_THREED  "False"
static Bool trackmouse;
static Bool thick;
static Bool threed;

static XrmOptionDescRec opts[] =
{
#ifdef DISABLE_INTERACTIVE
  {(char *) "-trackmouse", (char *) ".maze.trackmouse", XrmoptionNoArg, (caddr_t) "on"},
  {(char *) "+trackmouse", (char *) ".maze.trackmouse", XrmoptionNoArg, (caddr_t) "off"},
#endif
  {(char *) "-thick", (char *) ".maze.thick", XrmoptionNoArg, (caddr_t) "on"},
  {(char *) "+thick", (char *) ".maze.thick", XrmoptionNoArg, (caddr_t) "off"},
  {(char *) "-threed", (char *) ".maze.threed", XrmoptionNoArg, (caddr_t) "on"},
  {(char *) "+threed", (char *) ".maze.threed", XrmoptionNoArg, (caddr_t) "off"},
};

static argtype vars[] =
{
#ifdef DISABLE_INTERACTIVE
  {(void *) & trackmouse, (char *) "trackmouse", (char *) "TrackMouse", (char *) DEF_TRACKMOUSE, t_Bool},
#endif
  {(void *) & thick, (char *) "thick", (char *) "Thick", (char *) DEF_THICK, t_Bool},
  {(void *) & threed, (char *) "threed", (char *) "ThreeD", (char *) DEF_THREED, t_Bool},
};

static OptionStruct desc[] =
{
#ifdef DISABLE_INTERACTIVE
  {(char *) "-/+trackmouse", (char *) "turn on/off the tracking of the mouse"},
#endif
  {(char *) "-/+thick", (char *) "turn on/off the thick tracing"},
  {(char *) "-/+threed", (char *) "turn on/off the Three D look"},
};

ENTRYPOINT ModeSpecOpt maze_opts =
{sizeof opts / sizeof opts[0], opts, sizeof vars / sizeof vars[0], vars, desc};

#ifdef USE_MODULES
ModStruct   maze_description =
{"maze", "init_maze", "draw_maze", "release_maze",
 "refresh_maze", "init_maze", (char *) NULL, &maze_opts,
 1000, 1, 3000, -40, 64, 1.0, "",
 "Shows a random maze and a depth first search solution", 0, NULL};

#endif

#include "bitmaps/gray1.xbm"

#ifndef STANDALONE
/* aliases for vars defined in the bitmap file */
#define ICON_WIDTH   image_width
#define ICON_HEIGHT    image_height
#define ICON_BITS    image_bits

#include "maze.xbm"

#ifdef HAVE_XPM
#define ICON_NAME  image_name
#include "maze.xpm"
#define DEFAULT_XPM 1
#endif
#endif

#define MINGRIDSIZE	2

#define LOGOWIDTH ((mp->logo == NULL)?64:mp->logo->width)
#define LOGOHEIGHT ((mp->logo == NULL)?64:mp->logo->height)

#define WALL_TOP	0x8000
#define WALL_RIGHT	0x4000
#define WALL_BOTTOM	0x2000
#define WALL_LEFT	0x1000

#define DOOR_IN_TOP	0x800
#define DOOR_IN_RIGHT	0x400
#define DOOR_IN_BOTTOM	0x200
#define DOOR_IN_LEFT	0x100
#define DOOR_IN_ANY	0xF00

#define DOOR_OUT_TOP	0x80
#define DOOR_OUT_RIGHT	0x40
#define DOOR_OUT_BOTTOM	0x20
#define DOOR_OUT_LEFT	0x10

#define DIR_UP          0
#define DIR_RIGHT       1
#define DIR_DOWN        2
#define DIR_LEFT        3

#define START_SQUARE	0x2
#define END_SQUARE	0x1

typedef struct {
	unsigned int x;
	unsigned int y;
	char        dir;
} paths;

typedef struct {
	int         ncols, nrows, maze_size, xb, yb;
	int         sqnum, cur_sq_x, cur_sq_y, path_length;
	int         start_x, start_y, start_dir, end_x, end_y, end_dir;
	int         logo_x, logo_y;
	int         width, height;
	int         xs, ys, logo_size_x, logo_size_y;
	int         solving, current_path, stage;
	int         cycles;
	unsigned int *maze;
	paths      *move_list;
	paths      *save_path, *path;
	GC          grayGC;
	Pixmap      graypix;
	XImage     *logo;
	Colormap    cmap;
	unsigned long black, color;
	int         graphics_format;
	GC          backGC;
	int         time, space, threed;
} mazestruct;

static mazestruct *mazes = (mazestruct *) NULL;

static void
draw_wall(ModeInfo * mi, int i, int j, int dir)
{				/* draw a single wall */
	Display    *display = MI_DISPLAY(mi);
	Window      window = MI_WINDOW(mi);
	mazestruct *mp = &mazes[MI_SCREEN(mi)];
	GC          gc = mp->backGC;

	switch (dir) {
		case 0:
			XDrawLine(display, window, gc,
				  mp->xb + mp->xs * i,
				  mp->yb + mp->ys * j,
				  mp->xb + mp->xs * (i + 1),
				  mp->yb + mp->ys * j);
			break;
		case 1:
			XDrawLine(display, window, gc,
				  mp->xb + mp->xs * (i + 1),
				  mp->yb + mp->ys * j,
				  mp->xb + mp->xs * (i + 1),
				  mp->yb + mp->ys * (j + 1));
			break;
		case 2:
			XDrawLine(display, window, gc,
				  mp->xb + mp->xs * i,
				  mp->yb + mp->ys * (j + 1),
				  mp->xb + mp->xs * (i + 1),
				  mp->yb + mp->ys * (j + 1));
			break;
		case 3:
			XDrawLine(display, window, gc,
				  mp->xb + mp->xs * i,
				  mp->yb + mp->ys * j,
				  mp->xb + mp->xs * i,
				  mp->yb + mp->ys * (j + 1));
			break;
	}
}				/* end of draw_wall */

static void
draw_solid_square(ModeInfo * mi, GC gc,
		  register int i, register int j, register int dir)
{				/* draw a solid square in a square */
	Display    *display = MI_DISPLAY(mi);
	Window      window = MI_WINDOW(mi);
	mazestruct *mp = &mazes[MI_SCREEN(mi)];

	switch (dir) {
		case 0:
			XFillRectangle(display, window, gc,
				       mp->xb + 3 * mp->space + mp->xs * i,
				       mp->yb - 3 * mp->space + mp->ys * j,
				       mp->xs + 1 - 6 * mp->space - mp->threed,
				       mp->ys + 1 - mp->threed);
			break;
		case 1:
			XFillRectangle(display, window, gc,
				       mp->xb + 3 * mp->space + mp->xs * i,
				       mp->yb + 3 * mp->space + mp->ys * j,
				       mp->xs + 1 - mp->threed,
				       mp->ys + 1 - 6 * mp->space - mp->threed);
			break;
		case 2:
			XFillRectangle(display, window, gc,
				       mp->xb + 3 * mp->space + mp->xs * i,
				       mp->yb + 3 * mp->space + mp->ys * j,
				       mp->xs + 1 - 6 * mp->space - mp->threed,
				       mp->ys + 1 - mp->threed);
			break;
		case 3:
			XFillRectangle(display, window, gc,
				       mp->xb - 3 * mp->space + mp->xs * i,
				       mp->yb + 3 * mp->space + mp->ys * j,
				       mp->xs + 1 - mp->threed,
				       mp->ys + 1 - 6 * mp->space - mp->threed);
			break;
	}

}				/* end of draw_solid_square() */

static void
enter_square(ModeInfo * mi, int n)
{				/* move into a neighboring square */
	mazestruct *mp = &mazes[MI_SCREEN(mi)];

	draw_solid_square(mi, mp->backGC, (int) mp->path[n].x, (int) mp->path[n].y,
			  (int) mp->path[n].dir);

	mp->path[n + 1].dir = -1;
	switch (mp->path[n].dir) {
		case 0:
			mp->path[n + 1].x = mp->path[n].x;
			mp->path[n + 1].y = mp->path[n].y - 1;
			break;
		case 1:
			mp->path[n + 1].x = mp->path[n].x + 1;
			mp->path[n + 1].y = mp->path[n].y;
			break;
		case 2:
			mp->path[n + 1].x = mp->path[n].x;
			mp->path[n + 1].y = mp->path[n].y + 1;
			break;
		case 3:
			mp->path[n + 1].x = mp->path[n].x - 1;
			mp->path[n + 1].y = mp->path[n].y;
			break;
	}

}				/* end of enter_square() */

static void
free_path(mazestruct * mp)
{
	if (mp->maze) {
		free(mp->maze);
		mp->maze = (unsigned int *) NULL;
	}
	if (mp->move_list) {
		free(mp->move_list);
		mp->move_list = (paths *) NULL;
	}
	if (mp->save_path) {
		free(mp->save_path);
		mp->save_path = (paths *) NULL;
	}
	if (mp->path) {
		free(mp->path);
		mp->path = (paths *) NULL;
	}
}

static void
free_stuff(Display * display, mazestruct * mp)
{
	if (mp->cmap != None) {
		XFreeColormap(display, mp->cmap);
		if (mp->backGC != None) {
			XFreeGC(display, mp->backGC);
			mp->backGC = None;
		}
		mp->cmap = None;
	} else
		mp->backGC = None;
}

static void
free_maze_screen(Display * display, mazestruct * mp)
{
	if (mp == NULL) {
		return;
	}
	free_path(mp);
	if (mp->grayGC != None) {
		XFreeGC(display, mp->grayGC);
		mp->grayGC = None;
	}
	if (mp->graypix != None) {
		XFreePixmap(display, mp->graypix);
		mp->graypix = None;
	}
	free_stuff(display, mp);
#ifndef STANDALONE
/* this used to work there I think */

	if (mp->logo != None) {
		destroyImage(&mp->logo, &mp->graphics_format);
		mp->logo = None;
	}
#endif
	mp = NULL;
}

static Bool
set_maze_sizes(ModeInfo * mi)
{
	mazestruct *mp = &mazes[MI_SCREEN(mi)];
	Display *display = MI_DISPLAY(mi);
	int         size = MI_SIZE(mi);
	int         minsize;

	if (MI_IS_FULLRANDOM(mi)) {
		int randNum = NRAND(4);

		mp->threed = (randNum == 1) ? 1 : 0;
		mp->space = (randNum == 0) ? 0 : 1;
	} else {
		mp->threed = (threed) ? 1 : 0;
		mp->space = (!threed && thick) ? 0 : 1;
	}
	minsize = mp->space * 4 + 3 + mp->threed;
	if (size < -minsize) {
		free_path(mp);
		mp->ys = NRAND(MIN(-size, MAX(minsize, (MIN(mp->width, mp->height) - 1) /
				      MINGRIDSIZE)) - minsize + 1) + minsize;
	} else if (size < minsize) {
		if (!size)
			mp->ys = MAX(minsize, (MIN(mp->width, mp->height) - 1) / MINGRIDSIZE);
		else
			mp->ys = minsize;
	} else
		mp->ys = MIN(size, MAX(minsize, (MIN(mp->width, mp->height) - 1) /
				       MINGRIDSIZE));
	mp->xs = mp->ys;
	if (mp->logo == NULL) {
		mp->logo_size_x = 64 / mp->xs + 1;
		mp->logo_size_y = 64 / mp->ys + 1;
	} else {
		mp->logo_size_x = LOGOWIDTH / mp->xs + 1;
		mp->logo_size_y = LOGOHEIGHT / mp->ys + 1;
	}
	mp->ncols = MAX((mp->width - 1) / mp->xs, MINGRIDSIZE);
	mp->nrows = MAX((mp->height - 1) / mp->ys, MINGRIDSIZE);

	mp->xb = (mp->width - mp->ncols * mp->xs) / 2;
	mp->yb = (mp->height - mp->nrows * mp->ys) / 2;
	mp->maze_size = mp->ncols * mp->nrows;
	if (!mp->maze)
		if ((mp->maze = (unsigned int *) calloc(mp->maze_size,
				 sizeof (unsigned int))) == NULL) {
			free_maze_screen(display, mp);
			return False;
		}
	if (!mp->move_list)
		if ((mp->move_list = (paths *) calloc(mp->maze_size,
				sizeof (paths))) == NULL) {
			free_maze_screen(display, mp);
			return False;
		}
	if (!mp->save_path)
		if ((mp->save_path = (paths *) calloc(mp->maze_size,
				sizeof (paths))) == NULL) {
			free_maze_screen(display, mp);
			return False;
		}
	if (!mp->path)
		if (( mp->path = (paths *) calloc(mp->maze_size,
				sizeof (paths))) == NULL) {
			free_maze_screen(display, mp);
			return False;
		}
	return True;
}				/* end of set_maze_sizes */


static void
initialize_maze(ModeInfo * mi)
{				/* draw the surrounding wall and start/end squares */
	Display    *display = MI_DISPLAY(mi);
	mazestruct *mp = &mazes[MI_SCREEN(mi)];
	register int i, j, wall;

	if (MI_NPIXELS(mi) <= 2) {
		mp->color = MI_WHITE_PIXEL(mi);
	} else {
		mp->color = MI_PIXEL(mi, NRAND(MI_NPIXELS(mi)));
	}
	XSetForeground(display, mp->backGC, mp->color);
	XSetForeground(display, mp->grayGC, mp->color);
	/* initialize all squares */
	for (i = 0; i < mp->ncols; i++) {
		for (j = 0; j < mp->nrows; j++) {
			mp->maze[i * mp->nrows + j] = 0;
		}
	}

	/* top wall */
	for (i = 0; i < mp->ncols; i++) {
		mp->maze[i * mp->nrows] |= WALL_TOP;
	}

	/* right wall */
	for (j = 0; j < mp->nrows; j++) {
		mp->maze[(mp->ncols - 1) * mp->nrows + j] |= WALL_RIGHT;
	}

	/* bottom wall */
	for (i = 0; i < mp->ncols; i++) {
		mp->maze[i * mp->nrows + mp->nrows - 1] |= WALL_BOTTOM;
	}

	/* left wall */
	for (j = 0; j < mp->nrows; j++) {
		mp->maze[j] |= WALL_LEFT;
	}

	/* set start square */
	wall = NRAND(4);
	switch (wall) {
		case 0:
			i = NRAND(mp->ncols);
			j = 0;
			break;
		case 1:
			i = mp->ncols - 1;
			j = NRAND(mp->nrows);
			break;
		case 2:
			i = NRAND(mp->ncols);
			j = mp->nrows - 1;
			break;
		case 3:
			i = 0;
			j = NRAND(mp->nrows);
			break;
	}
	mp->maze[i * mp->nrows + j] |= START_SQUARE;
	mp->maze[i * mp->nrows + j] |= (DOOR_IN_TOP >> wall);
	mp->maze[i * mp->nrows + j] &= ~(WALL_TOP >> wall);
	mp->cur_sq_x = i;
	mp->cur_sq_y = j;
	mp->start_x = i;
	mp->start_y = j;
	mp->start_dir = wall;
	mp->sqnum = 0;

	/* set end square */
	wall = (wall + 2) % 4;
	switch (wall) {
		case 0:
			i = NRAND(mp->ncols);
			j = 0;
			break;
		case 1:
			i = mp->ncols - 1;
			j = NRAND(mp->nrows);
			break;
		case 2:
			i = NRAND(mp->ncols);
			j = mp->nrows - 1;
			break;
		case 3:
			i = 0;
			j = NRAND(mp->nrows);
			break;
	}
	mp->maze[i * mp->nrows + j] |= END_SQUARE;
	mp->maze[i * mp->nrows + j] |= (DOOR_OUT_TOP >> wall);
	mp->maze[i * mp->nrows + j] &= ~(WALL_TOP >> wall);
	mp->end_x = i;
	mp->end_y = j;
	mp->end_dir = wall;

	/* set logo */
	if ((mp->ncols > mp->logo_size_x + 2 + 4 * mp->space) &&
	    (mp->nrows > mp->logo_size_y + 2 + 4 * mp->space)) {
		mp->logo_x = NRAND(mp->ncols - mp->logo_size_x - 2 -
			4 * mp->space) + 1 + 2 * mp->space;
		mp->logo_y = NRAND(mp->nrows - mp->logo_size_y - 2 -
			4 * mp->space) + 1 + 2 * mp->space;

		for (i = 0; i < mp->logo_size_x; i++)
			for (j = 0; j < mp->logo_size_y; j++)
				mp->maze[(mp->logo_x + i) * mp->nrows + mp->logo_y + j] |=
					DOOR_IN_TOP;
	} else
		mp->logo_y = mp->logo_x = -1;
}

static int
choose_door(ModeInfo * mi)
{				/* pick a new path */
	mazestruct *mp = &mazes[MI_SCREEN(mi)];
	int         candidates[3];
	register int num_candidates;

	num_candidates = 0;

	/* top wall */
	if ((!(mp->maze[mp->cur_sq_x * mp->nrows + mp->cur_sq_y] &
	       DOOR_IN_TOP)) &&
	    (!(mp->maze[mp->cur_sq_x * mp->nrows + mp->cur_sq_y] &
	       DOOR_OUT_TOP)) &&
	    (!(mp->maze[mp->cur_sq_x * mp->nrows + mp->cur_sq_y] &
	       WALL_TOP))) {
		if (mp->maze[mp->cur_sq_x * mp->nrows + mp->cur_sq_y - 1] &
		    DOOR_IN_ANY) {
			mp->maze[mp->cur_sq_x * mp->nrows + mp->cur_sq_y] |= WALL_TOP;
			mp->maze[mp->cur_sq_x * mp->nrows + mp->cur_sq_y - 1] |=
				WALL_BOTTOM;
		} else
			candidates[num_candidates++] = 0;
	}
	/* right wall */
	if ((!(mp->maze[mp->cur_sq_x * mp->nrows + mp->cur_sq_y] &
	       DOOR_IN_RIGHT)) &&
	    (!(mp->maze[mp->cur_sq_x * mp->nrows + mp->cur_sq_y] &
	       DOOR_OUT_RIGHT)) &&
	    (!(mp->maze[mp->cur_sq_x * mp->nrows + mp->cur_sq_y] &
	       WALL_RIGHT))) {
		if (mp->maze[(mp->cur_sq_x + 1) * mp->nrows + mp->cur_sq_y] &
		    DOOR_IN_ANY) {
			mp->maze[mp->cur_sq_x * mp->nrows + mp->cur_sq_y] |= WALL_RIGHT;
			mp->maze[(mp->cur_sq_x + 1) * mp->nrows + mp->cur_sq_y] |=
				WALL_LEFT;
		} else
			candidates[num_candidates++] = 1;
	}
	/* bottom wall */
	if ((!(mp->maze[mp->cur_sq_x * mp->nrows + mp->cur_sq_y] &
	       DOOR_IN_BOTTOM)) &&
	    (!(mp->maze[mp->cur_sq_x * mp->nrows + mp->cur_sq_y] &
	       DOOR_OUT_BOTTOM)) &&
	    (!(mp->maze[mp->cur_sq_x * mp->nrows + mp->cur_sq_y] &
	       WALL_BOTTOM))) {
		if (mp->maze[mp->cur_sq_x * mp->nrows + mp->cur_sq_y + 1] &
		    DOOR_IN_ANY) {
			mp->maze[mp->cur_sq_x * mp->nrows + mp->cur_sq_y] |= WALL_BOTTOM;
			mp->maze[mp->cur_sq_x * mp->nrows + mp->cur_sq_y + 1] |= WALL_TOP;
		} else
			candidates[num_candidates++] = 2;
	}
	/* left wall */
	if ((!(mp->maze[mp->cur_sq_x * mp->nrows + mp->cur_sq_y] &
	       DOOR_IN_LEFT)) &&
	    (!(mp->maze[mp->cur_sq_x * mp->nrows + mp->cur_sq_y] &
	       DOOR_OUT_LEFT)) &&
	    (!(mp->maze[mp->cur_sq_x * mp->nrows + mp->cur_sq_y] &
	       WALL_LEFT))) {
		if (mp->maze[(mp->cur_sq_x - 1) * mp->nrows + mp->cur_sq_y] &
		    DOOR_IN_ANY) {
			mp->maze[mp->cur_sq_x * mp->nrows + mp->cur_sq_y] |= WALL_LEFT;
			mp->maze[(mp->cur_sq_x - 1) * mp->nrows + mp->cur_sq_y] |=
				WALL_RIGHT;
		} else
			candidates[num_candidates++] = 3;
	}
	/* done wall */
	if (num_candidates == 0)
		return (-1);
	if (num_candidates == 1)
		return (candidates[0]);
	return (candidates[NRAND(num_candidates)]);

}				/* end of choose_door() */

static void
draw_maze_walls(ModeInfo * mi)
{				/* pick a new path */
	mazestruct *mp = &mazes[MI_SCREEN(mi)];
	int         i, j, isize;

	MI_IS_DRAWN(mi) = True;

	for (i = 0; i < mp->ncols; i++) {
		isize = i * mp->nrows;
		for (j = 0; j < mp->nrows; j++) {
			/* Only need to draw half the walls... since they are shared */
			/* top wall */
			if (	/*(!(mp->maze[isize + j] & DOOR_IN_TOP)) &&
				   (!(mp->maze[isize + j] & DOOR_OUT_TOP)) && */
				   (mp->maze[isize + j] & WALL_TOP))
				draw_wall(mi, i, j, 0);
			/* left wall */
			if (	/*(!(mp->maze[isize + j] & DOOR_IN_RIGHT)) &&
				   (!(mp->maze[isize + j] & DOOR_OUT_RIGHT)) && */
				   (mp->maze[isize + j] & WALL_RIGHT))
				draw_wall(mi, i, j, 1);
		}
	}
}				/* end of draw_maze_walls() */

static int
backup(mazestruct * mp)
{				/* back up a move */
	mp->sqnum--;
	if (mp->sqnum >= 0) {
		mp->cur_sq_x = mp->move_list[mp->sqnum].x;
		mp->cur_sq_y = mp->move_list[mp->sqnum].y;
	}
	return (mp->sqnum);
}				/* end of backup() */

static void
create_maze_walls(ModeInfo * mi)
{				/* create a maze layout given the intiialized maze */
	mazestruct *mp = &mazes[MI_SCREEN(mi)];
	register int i, newdoor;

	for (;;) {
		mp->move_list[mp->sqnum].x = mp->cur_sq_x;
		mp->move_list[mp->sqnum].y = mp->cur_sq_y;
		mp->move_list[mp->sqnum].dir = -1;
		while ((newdoor = choose_door(mi)) == -1)	/* pick a door */
			if (backup(mp) == -1)	/* no more doors ... backup */
				return;		/* done ... return */

		/* mark the out door */
		mp->maze[mp->cur_sq_x * mp->nrows + mp->cur_sq_y] |=
			(DOOR_OUT_TOP >> newdoor);

		switch (newdoor) {
			case 0:
				mp->cur_sq_y--;
				break;
			case 1:
				mp->cur_sq_x++;
				break;
			case 2:
				mp->cur_sq_y++;
				break;
			case 3:
				mp->cur_sq_x--;
				break;
		}
		mp->sqnum++;

		/* mark the in door */
		mp->maze[mp->cur_sq_x * mp->nrows + mp->cur_sq_y] |=
			(DOOR_IN_TOP >> ((newdoor + 2) % 4));

		/* if end square set path length and save path */
		if (mp->maze[mp->cur_sq_x * mp->nrows + mp->cur_sq_y] &
				END_SQUARE) {
			mp->path_length = mp->sqnum;
			for (i = 0; i < mp->path_length; i++) {
				mp->save_path[i].x = mp->move_list[i].x;
				mp->save_path[i].y = mp->move_list[i].y;
				mp->save_path[i].dir = mp->move_list[i].dir;
			}
		}
	}

}				/* end of create_maze_walls() */

static void
draw_maze_border(ModeInfo * mi)
{				/* draw the maze outline */
	Display    *display = MI_DISPLAY(mi);
	Window      window = MI_WINDOW(mi);
	mazestruct *mp = &mazes[MI_SCREEN(mi)];
	GC          gc = mp->backGC;
	register int i, j;

#ifndef STANDALONE
	if (mp->logo_x != -1) {
		(void) XPutImage(display, window, gc, mp->logo,
				 0, 0,
				 mp->xb + mp->xs * mp->logo_x +
			(mp->xs * mp->logo_size_x - LOGOWIDTH + 1) / 2,
				 mp->yb + mp->ys * mp->logo_y +
		       (mp->ys * mp->logo_size_y - LOGOHEIGHT + 1) / 2,
				 LOGOWIDTH, LOGOHEIGHT);
	}
#endif
	for (i = 0; i < mp->ncols; i++) {
		if (mp->maze[i * mp->nrows] & WALL_TOP) {
			XDrawLine(display, window, gc,
				  mp->xb + mp->xs * i, mp->yb,
				  mp->xb + mp->xs * (i + 1), mp->yb);
		}
		if ((mp->maze[i * mp->nrows + mp->nrows - 1] & WALL_BOTTOM)) {
			XDrawLine(display, window, gc,
				  mp->xb + mp->xs * i,
				  mp->yb + mp->ys * (mp->nrows),
				  mp->xb + mp->xs * (i + 1),
				  mp->yb + mp->ys * (mp->nrows));
		}
	}
	for (j = 0; j < mp->nrows; j++) {
		if (mp->maze[(mp->ncols - 1) * mp->nrows + j] & WALL_RIGHT) {
			XDrawLine(display, window, gc,
				  mp->xb + mp->xs * mp->ncols,
				  mp->yb + mp->ys * j,
				  mp->xb + mp->xs * mp->ncols,
				  mp->yb + mp->ys * (j + 1));
		}
		if (mp->maze[j] & WALL_LEFT) {
			XDrawLine(display, window, gc,
				  mp->xb, mp->yb + mp->ys * j,
				  mp->xb, mp->yb + mp->ys * (j + 1));
		}
	}

	draw_solid_square(mi, gc, mp->start_x, mp->start_y, mp->start_dir);
	draw_solid_square(mi, gc, mp->end_x, mp->end_y, mp->end_dir);
}				/* end of draw_maze() */

static void
try_to_move(ModeInfo *mi, int dir)
{                               /* based on solve_maze */
	mazestruct *mp = &mazes[MI_SCREEN(mi)];
	int colliding = mp->maze[mp->path[mp->current_path].x * mp->nrows +
		mp->path[mp->current_path].y] &
		(WALL_TOP >> dir); /*  Are we trying to go through a wall? */

	if (!colliding && ((mp->current_path == 0) ||
	/* We're either on the first path or we're not going backwards */
		((dir != (unsigned char) (mp->path[mp->current_path - 1].dir +
			2) % 4))) ) {
		mp->path[mp->current_path].dir = dir;
		enter_square(mi, mp->current_path);
		mp->current_path++;
#if 0
		(void) fprintf(stderr,
	"Calling draw_solid_square with params: x=%d, y= %d, dir=%d\n",
			(int) (mp->path[mp->current_path].x),
			(int) (mp->path[mp->current_path].y),
			(int) (dir));
#endif
		/* The following call is supposed to make things look more
		   intuitive, i.e., that the square we think we are on is
		   filled in.
                   The direction has to be reversed to prevent it from
		   drawing squares on top of lines.
		*/
		draw_solid_square(mi, mp->backGC,
			(int) (mp->path[mp->current_path].x),
			(int) (mp->path[mp->current_path].y),
			(int) ((dir + 2) % 4));
		if (mp->maze[mp->path[mp->current_path].x * mp->nrows +
			  mp->path[mp->current_path].y] & START_SQUARE) {
			mp->solving = 0;
			return;
		}
	} else {
		if (!colliding) {
			draw_solid_square(mi, mp->grayGC,
				(int) (mp->path[mp->current_path].x),
				(int) (mp->path[mp->current_path].y),
				(int) (mp->path[mp->current_path - 1].dir +
				2) % 4);
			mp->path[mp->current_path].dir = 4;
			mp->current_path--;
			draw_solid_square(mi, mp->grayGC,
				(int) (mp->path[mp->current_path].x),
				(int) (mp->path[mp->current_path].y),
				(int) (mp->path[mp->current_path].dir));
		}
#if 0
		else {
			/* Beep if we can't go in that direction */
			(void) fprintf(stdout, "\a");
			(void) fflush(stdout);
		}
#endif
	}
}         /* end of try_to_move() */

static void
solve_maze(ModeInfo * mi)
{				/* solve it with graphical feedback */
	mazestruct *mp = &mazes[MI_SCREEN(mi)];

	if (!mp->solving) {
		/* plug up the surrounding wall */
		mp->maze[mp->start_x * mp->nrows + mp->start_y] |=
			(WALL_TOP >> mp->start_dir);
		mp->maze[mp->end_x * mp->nrows + mp->end_y] |=
			(WALL_TOP >> mp->end_dir);

		/* initialize search path */
		mp->current_path = 0;
		mp->path[mp->current_path].x = mp->end_x;
		mp->path[mp->current_path].y = mp->end_y;
		mp->path[mp->current_path].dir = -1;

		mp->solving = 1;
	}
	if (++mp->path[mp->current_path].dir >= 4) {
		if (mp->current_path == 0) {
			/* this can happen if a person backs up from going
			   the right way and then autosolves */
			mp->path[mp->current_path].dir = -1;
			return;
		}
		/* This draw is to fill in the dead ends,
		   it ends up drawing more gray boxes then it needs to. */
		draw_solid_square(mi, mp->grayGC,
				  (int) (mp->path[mp->current_path].x),
				  (int) (mp->path[mp->current_path].y),
			 (int) (mp->path[mp->current_path - 1].dir + 2) % 4);

		mp->current_path--;
		draw_solid_square(mi, mp->grayGC,
				  (int) (mp->path[mp->current_path].x),
				  (int) (mp->path[mp->current_path].y),
				  (int) (mp->path[mp->current_path].dir));
	} else if (!(mp->maze[mp->path[mp->current_path].x * mp->nrows +
			      mp->path[mp->current_path].y] &
		     (WALL_TOP >> mp->path[mp->current_path].dir)) &&
		   ((mp->current_path == 0) ||
		    ((mp->path[mp->current_path].dir !=
		      (unsigned char) (mp->path[mp->current_path - 1].dir +
				       2) % 4)))) {
		enter_square(mi, mp->current_path);
		mp->current_path++;
		/* This next call is to make the appearance more 'intuitive' */
		draw_solid_square(mi, mp->backGC,
                                  (int) (mp->path[mp->current_path].x),
                                  (int) (mp->path[mp->current_path].y),
                         (int) (mp->path[mp->current_path - 1].dir + 2) % 4);
		if (mp->maze[mp->path[mp->current_path].x * mp->nrows +
			     mp->path[mp->current_path].y] & START_SQUARE) {
			mp->solving = 0;
			return;
		}
	}
}				/* end of solve_maze() */

static void
mouse_maze(ModeInfo * mi)
{	/* solve it with graphical feedback */
	mazestruct *mp = &mazes[MI_SCREEN(mi)];
	Window      r, c;
	int         dx, dy, cx, cy;
	unsigned int m;

	(void) XQueryPointer(MI_DISPLAY(mi), MI_WINDOW(mi),
	  &r, &c, &dx, &dy, &cx, &cy, &m);
	dx = cx - (mp->path[mp->current_path].x * mp->xs + mp->xb) - mp->xs / 2;
	dy = cy - (mp->path[mp->current_path].y * mp->ys + mp->yb) - mp->ys / 2;
	if (cx <= mp->xb || cy <= mp->yb || cx > mp->ncols * mp->xs + mp->xb ||
			cy > mp->nrows * mp->ys + mp->yb) {
		solve_maze(mi);
		return;
	}
	if (2 * abs(dx) / (mp->xs + 1) > 2 * abs(dy) / mp->ys) {
		if (dx > 0) {
			try_to_move(mi, DIR_RIGHT);
		} else {
			try_to_move(mi, DIR_LEFT);
		}
	} else if (2 * abs(dx) / mp->xs < 2 * abs(dy) / (mp->ys + 1)) {
		if (dy > 0) {
			try_to_move(mi, DIR_DOWN);
		} else {
			try_to_move(mi, DIR_UP);
		}
	}
}

static Bool
init_stuff(ModeInfo * mi)
{
	mazestruct *mp = &mazes[MI_SCREEN(mi)];
#ifndef STANDALONE
	Display    *display = MI_DISPLAY(mi);

	if (mp->logo == None) {
		getImage(mi, &mp->logo, ICON_WIDTH, ICON_HEIGHT, ICON_BITS,
#ifdef HAVE_XPM
			 DEFAULT_XPM, ICON_NAME,
#endif
			 &mp->graphics_format, &mp->cmap, &mp->black);
		if (mp->logo == None) {
			free_maze_screen(display, mp);
			return False;
		}
	}
	if (mp->cmap != None) {
		Window      window = MI_WINDOW(mi);
		setColormap(display, window, mp->cmap, MI_IS_INWINDOW(mi));
		if (mp->backGC == None) {
			XGCValues   xgcv;

			xgcv.background = mp->black;
			if ((mp->backGC = XCreateGC(display, window, GCBackground,
					 &xgcv)) == None) {
				free_maze_screen(display, mp);
				return False;
			}
		}
	} else
#endif /* STANDALONE */
	{
		mp->black = MI_BLACK_PIXEL(mi);
		mp->backGC = MI_GC(mi);
	}
	return True;
}

ENTRYPOINT void
init_maze(ModeInfo * mi)
{
	Display    *display = MI_DISPLAY(mi);
	mazestruct *mp;

	MI_INIT(mi, mazes);
	mp = &mazes[MI_SCREEN(mi)];

	if (!init_stuff(mi))
		return;

	mp->width = MI_WIDTH(mi);
	mp->height = MI_HEIGHT(mi);
	mp->width = (mp->width >= 32) ? mp->width : 32;
	mp->height = (mp->height >= 32) ? mp->height : 32;

	if (mp->graypix == None)
		if ((mp->graypix = XCreateBitmapFromData(display, MI_WINDOW(mi),
			     (char *) gray1_bits, gray1_width, gray1_height)) == None) {
			free_maze_screen(display, mp);
			return;
		}
	if (!mp->grayGC) {
		if ((mp->grayGC = XCreateGC(display, MI_WINDOW(mi),
				 0L, (XGCValues *) 0)) == None) {
			free_maze_screen(display, mp);
			return;
		}
		XSetBackground(display, mp->grayGC, mp->black);
		XSetFillStyle(display, mp->grayGC, FillOpaqueStippled);
		XSetStipple(display, mp->grayGC, mp->graypix);
	}
	mp->solving = 0;
	mp->stage = 0;
	mp->time = 0;
	mp->cycles = MI_CYCLES(mi);
	if (mp->cycles < 4)
		mp->cycles = 4;
}

ENTRYPOINT void
draw_maze(ModeInfo * mi)
{
	mazestruct *mp;

	if (mazes == NULL)
		return;
	mp = &mazes[MI_SCREEN(mi)];
	if (mp->graypix == None)
		return;
	if (mp->solving) {
		if (trackmouse)
			mouse_maze(mi);
		else
			solve_maze(mi);
		return;
	}
	switch (mp->stage) {
		case 0:
			MI_CLEARWINDOWCOLORMAP(mi, mp->backGC, mp->black);

			if (!set_maze_sizes(mi))
				return;
			initialize_maze(mi);
			create_maze_walls(mi);
			mp->stage++;
			break;
		case 1:
			draw_maze_border(mi);
			mp->stage++;
			break;
		case 2:
			draw_maze_walls(mi);
			mp->stage++;
			break;
		case 3:
			if (++mp->time > mp->cycles / (4 * (1 + trackmouse)))
				mp->stage++;
			break;
		case 4:
			if (trackmouse) {
				/* Initialize paths and draw a square or two */
				int i;
				for (i = 0; i < 4; i++)
					solve_maze(mi);
			} else {
				solve_maze(mi);
			}
			mp->stage++;
			break;
		case 5:
			if (++mp->time > 3 * mp->cycles / 4)
				init_maze(mi);
			break;
	}
}				/*  end of draw_maze() */

ENTRYPOINT void
release_maze(ModeInfo * mi)
{
	if (mazes != NULL) {
		int         screen;

		for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
			free_maze_screen(MI_DISPLAY(mi), &mazes[screen]);
		free(mazes);
		mazes = (mazestruct *) NULL;
	}
}

#ifndef STANDALONE
ENTRYPOINT void
refresh_maze(ModeInfo * mi)
{
	mazestruct *mp;

	if (mazes == NULL)
		return;
	mp = &mazes[MI_SCREEN(mi)];
	if (mp->graypix == None)
		return;

#ifdef HAVE_XPM
	if (mp->graphics_format >= IS_XPM) {
		/* This is needed when another program changes the colormap. */
		free_maze_screen(MI_DISPLAY(mi), mp);
		init_maze(mi);
		return;
	}
#endif
	MI_CLEARWINDOWCOLORMAP(mi, mp->backGC, mp->black);
	XSetForeground(MI_DISPLAY(mi), mp->backGC, mp->color);
	if (mp->stage >= 1) {
		mp->stage = 3;
		mp->sqnum = 0;
		mp->cur_sq_x = mp->start_x;
		mp->cur_sq_y = mp->start_y;
		mp->maze[mp->start_x * mp->nrows + mp->start_y] |= START_SQUARE;
		mp->maze[mp->start_x * mp->nrows + mp->start_y] |=
			(DOOR_IN_TOP >> mp->start_dir);
		mp->maze[mp->start_x * mp->nrows + mp->start_y] &=
			~(WALL_TOP >> mp->start_dir);
		mp->maze[mp->end_x * mp->nrows + mp->end_y] |= END_SQUARE;
		mp->maze[mp->end_x * mp->nrows + mp->end_y] |=
			(DOOR_OUT_TOP >> mp->end_dir);
		mp->maze[mp->end_x * mp->nrows + mp->end_y] &=
			~(WALL_TOP >> mp->end_dir);
		draw_maze_border(mi);
		draw_maze_walls(mi);
	}
	mp->solving = 0;
}
#endif

XSCREENSAVER_MODULE ("Maze", maze)

#endif /* MODE_maze */
