/* glsnake.c - OpenGL imitation of Rubik's Snake
 * 
 * (c) 2001-2005 Jamie Wilkinson <jaq@spacepants.org>
 * (c) 2001-2003 Andrew Bennetts <andrew@puzzling.org> 
 * (c) 2001-2003 Peter Aylett <peter@ylett.com>
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#ifdef STANDALONE
# include "xlockmoreI.h"
#endif

/* HAVE_GLUT defined if we're building a standalone glsnake,
 * and not defined if we're building as an xscreensaver hack */
#ifdef HAVE_GLUT
# include <GL/glut.h>
#else
# ifdef HAVE_COCOA
#  include <OpenGL/gl.h>
#  include <OpenGL/glu.h>
#  define HAVE_GETTIMEOFDAY
# else
#  include <GL/gl.h>
#  include <GL/glu.h>
# endif
#endif

#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>

/* angles */
#define ZERO	  0.0
#define LEFT	 90.0
#define PIN    	180.0
#define RIGHT   270.0

#ifdef HAVE_GETTIMEOFDAY
# ifdef GETTIMEOFDAY_TWO_ARGS

#  include <sys/time.h>
#  include <time.h>
   typedef struct timeval snaketime;
#  define GETSECS(t) ((t).tv_sec)
#  define GETMSECS(t) ((t).tv_usec/1000)

# else /* !GETTIMEOFDAY_TWO_ARGS */

#  include <sys/time.h>
#  include <time.h>
   typedef struct timeval snaketime;
#  define GETSECS(t) ((t).tv_sec)
#  define GETMSECS(t) ((t).tv_usec/1000)

# endif /* GETTIMEOFDAY_TWO_ARGS */

#else /* !HAVE_GETTIMEOFDAY */
# ifdef HAVE_FTIME

#  include <sys/timeb.h>
   typedef struct timeb snaketime;
#  define GETSECS(t) ((long)(t).time)
#  define GETMSECS(t) ((t).millitm/1000)

# endif /* HAVE_FTIME */
#endif /* HAVE_GETTIMEOFDAY */

#include <math.h>

#ifndef M_SQRT1_2	/* Win32 doesn't have this constant  */
#define M_SQRT1_2 0.70710678118654752440084436210485
#endif

#define NODE_COUNT 24

#ifdef HAVE_GLUT
#define DEF_YANGVEL     0.10
#define DEF_ZANGVEL     0.14
#define DEF_EXPLODE     0.03
#define DEF_ANGVEL      1.0
#define DEF_STATICTIME  5000
#define DEF_ALTCOLOUR   0
#define DEF_TITLES      1
#define DEF_INTERACTIVE 0
#define DEF_ZOOM        25.0
#define DEF_WIREFRAME   0
#define DEF_TRANSPARENT 1
#else
/* xscreensaver options doobies prefer strings */
#define DEF_YANGVEL     "0.10"
#define DEF_ZANGVEL     "0.14"
#define DEF_EXPLODE     "0.03"
#define DEF_ANGVEL      "1.0"
#define DEF_STATICTIME  "5000"
#define DEF_ALTCOLOUR   "False"
#define DEF_TITLES      "True"
#define DEF_INTERACTIVE "False"
#define DEF_ZOOM        "25.0"
#define DEF_WIREFRAME   "False"
#define DEF_TRANSPARENT "True"
#endif

/* static variables */

static GLfloat explode;
static long statictime;
static GLfloat yspin = 60.0;
static GLfloat zspin = -45.0;
static GLfloat yangvel;
static GLfloat zangvel;
static Bool altcolour;
static Bool titles;
static Bool interactive;
static Bool wireframe;
static Bool transparent;
static GLfloat zoom;
static GLfloat angvel;

#ifndef HAVE_GLUT

#define glsnake_init    init_glsnake
#define glsnake_display draw_glsnake
#define glsnake_reshape reshape_glsnake
#define refresh_glsnake 0
#define release_glsnake 0
#define glsnake_handle_event 0

/* xscreensaver defaults */
#define DEFAULTS "*delay:          30000                      \n" \
                 "*count:          30                         \n" \
                 "*showFPS:        False                      \n" \
                 "*labelfont:   -*-times-bold-r-normal-*-180-*\n" \



#undef countof
#define countof(x) (sizeof((x))/sizeof((*x)))

#include "xlockmore.h"
#include "glxfonts.h"

static XrmOptionDescRec opts[] = {
    { "-explode", ".explode", XrmoptionSepArg, DEF_EXPLODE },
    { "-angvel", ".angvel", XrmoptionSepArg, DEF_ANGVEL },
    { "-statictime", ".statictime", XrmoptionSepArg, DEF_STATICTIME },
    { "-yangvel", ".yangvel", XrmoptionSepArg, DEF_YANGVEL },
    { "-zangvel", ".zangvel", XrmoptionSepArg, DEF_ZANGVEL },
    { "-altcolour", ".altcolour", XrmoptionNoArg, "True" },
    { "-no-altcolour", ".altcolour", XrmoptionNoArg, "False" },
    { "-titles", ".titles", XrmoptionNoArg, "True" },
    { "-no-titles", ".titles", XrmoptionNoArg, "False" },
    { "-zoom", ".zoom", XrmoptionSepArg, DEF_ZOOM },
    { "-wireframe", ".wireframe", XrmoptionNoArg, "true" },
    { "-no-wireframe", ".wireframe", XrmoptionNoArg, "false" },
    { "-transparent", ".transparent", XrmoptionNoArg, "true" },
    { "-no-transparent", ".transparent", XrmoptionNoArg, "false" },
};

static argtype vars[] = {
    {&explode, "explode", "Explode", DEF_EXPLODE, t_Float},
    {&angvel, "angvel", "Angular Velocity", DEF_ANGVEL, t_Float},
    {&statictime, "statictime", "Static Time", DEF_STATICTIME, t_Int},
    {&yangvel, "yangvel", "Angular Velocity about Y axis", DEF_YANGVEL, t_Float},
    {&zangvel, "zangvel", "Angular Velocity about X axis", DEF_ZANGVEL, t_Float},
    {&interactive, "interactive", "Interactive", DEF_INTERACTIVE, t_Bool},
    {&altcolour, "altcolour", "Alternate Colour Scheme", DEF_ALTCOLOUR, t_Bool},
    {&titles, "titles", "Titles", DEF_TITLES, t_Bool},
    {&zoom, "zoom", "Zoom", DEF_ZOOM, t_Float},
    {&wireframe, "wireframe", "Wireframe", DEF_WIREFRAME, t_Bool},
    {&transparent, "transparent", "Transparent!", DEF_TRANSPARENT, t_Bool},
};

ENTRYPOINT ModeSpecOpt glsnake_opts = {countof(opts), opts, countof(vars), vars, NULL};
#endif

struct model_s {
    const char * name;
    float node[NODE_COUNT];
};

struct glsnake_cfg {
#ifndef HAVE_GLUT
    GLXContext * glx_context;
    XFontStruct * font;
    GLuint font_list;
#else
    /* font list number */
    int font;
#endif

    /* window id */
    int window;

    /* is a morph in progress? */
    int morphing;

    /* has the model been paused? */
    int paused;

    /* snake metrics */
    int is_cyclic;
    int is_legal;
    float last_turn;
    int debug;

    /* the shape of the model */
    float node[NODE_COUNT];

    /* currently selected node for interactive mode */
    int selected;

    /* models */
    unsigned int prev_model;
    unsigned int next_model;

    /* model morphing */
    int new_morph;

    /* colours */
    float colour[2][4];
    int next_colour;
    int prev_colour;

    /* timing variables */
    snaketime last_iteration;
    snaketime last_morph;

    /* window size */
    int width, height;
    int old_width, old_height;

    /* the id of the display lists for drawing a node */
    GLuint node_solid, node_wire;

    /* is the window fullscreen? */
    int fullscreen;
};

#define COLOUR_CYCLIC 0
#define COLOUR_ACYCLIC 1
#define COLOUR_INVALID 2
#define COLOUR_AUTHENTIC 3
#define COLOUR_ORIGLOGO 4

static const float colour[][2][4] = {
    /* cyclic - green */
    { { 0.4, 0.8, 0.2, 0.6 },
      { 1.0, 1.0, 1.0, 0.6 } },
    /* acyclic - blue */
    { { 0.3, 0.1, 0.9, 0.6 },
      { 1.0, 1.0, 1.0, 0.6 } },
    /* invalid - grey */
    { { 0.3, 0.1, 0.9, 0.6 },
      { 1.0, 1.0, 1.0, 0.6 } },
    /* authentic - purple and green */
    { { 0.38, 0.0, 0.55, 0.7 },
      { 0.0,  0.5, 0.34, 0.7 } },
    /* old "authentic" colours from the logo */
    { { 171/255.0, 0, 1.0, 1.0 },
      { 46/255.0, 205/255.0, 227/255.0, 1.0 } }
};

static const struct model_s model[] = {
#define STRAIGHT_MODEL 0
    { "straight",
      { ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO,
	ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO,
	ZERO, ZERO, ZERO }
    },
    /* the models in the Rubik's snake manual */
    { "ball",
      { RIGHT, RIGHT, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, LEFT,
	RIGHT, LEFT, LEFT, RIGHT, RIGHT, LEFT, LEFT, RIGHT, LEFT, RIGHT,
	RIGHT, LEFT, RIGHT, LEFT, ZERO }
    },
#define START_MODEL 2
    { "snow",
      { RIGHT, RIGHT, RIGHT, RIGHT, LEFT, LEFT, LEFT, LEFT, RIGHT,
	RIGHT, RIGHT, RIGHT, LEFT, LEFT, LEFT, LEFT, RIGHT, RIGHT, RIGHT,
	RIGHT, LEFT, LEFT, LEFT, ZERO }
    },
    { "propellor",
      { ZERO, ZERO, ZERO, RIGHT, LEFT, RIGHT, ZERO, LEFT, ZERO, ZERO,
	ZERO, RIGHT, LEFT, RIGHT, ZERO, LEFT, ZERO, ZERO, ZERO, RIGHT,
	LEFT, RIGHT, ZERO, LEFT }
    },
    { "flamingo",
      { ZERO, PIN, ZERO, ZERO, ZERO, ZERO, ZERO, PIN, RIGHT, RIGHT, PIN,
	RIGHT, LEFT, PIN, LEFT, RIGHT, PIN, RIGHT, RIGHT, ZERO, ZERO,
	ZERO, PIN, ZERO }
    },
    { "cat",
      { ZERO, PIN, PIN, ZERO, PIN, PIN, ZERO, RIGHT, ZERO, PIN, PIN,
	ZERO, PIN, PIN, ZERO, PIN, PIN, ZERO, ZERO, ZERO, ZERO, ZERO,
	ZERO, ZERO }
    },
    { "rooster",
      { ZERO, ZERO, PIN, PIN, ZERO, LEFT, ZERO, LEFT, RIGHT, PIN, RIGHT,
	ZERO, PIN, PIN, ZERO, RIGHT, PIN, RIGHT, LEFT, ZERO, LEFT, ZERO,
	PIN, ZERO }
    },
    /* These models were taken from Andrew and Peter's original snake.c
     * as well as some newer ones made up by Jamie, Andrew and Peter. */
    { "half balls",
      { LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, LEFT, RIGHT, LEFT,
	LEFT, LEFT, LEFT, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, LEFT,
	RIGHT, LEFT, LEFT, LEFT, ZERO }
    },
    { "zigzag1",
      { RIGHT, RIGHT, RIGHT, LEFT, LEFT, LEFT, RIGHT, RIGHT, RIGHT,
	LEFT, LEFT, LEFT, RIGHT, RIGHT, RIGHT, LEFT, LEFT, LEFT, RIGHT,
	RIGHT, RIGHT, LEFT, LEFT, ZERO }
    },
    { "zigzag2",
      { PIN, ZERO, PIN, ZERO, PIN, ZERO, PIN, ZERO, PIN, ZERO, PIN,
	ZERO, PIN, ZERO, PIN, ZERO, PIN, ZERO, PIN, ZERO, PIN, ZERO, PIN,
	ZERO }
    },
    { "zigzag3",
      { PIN, LEFT, PIN, LEFT, PIN, LEFT, PIN, LEFT, PIN, LEFT, PIN,
	LEFT, PIN, LEFT, PIN, LEFT, PIN, LEFT, PIN, LEFT, PIN, LEFT, PIN,
	ZERO }
    },
    { "caterpillar",
      { RIGHT, RIGHT, PIN, LEFT, LEFT, PIN, RIGHT, RIGHT, PIN, LEFT,
	LEFT, PIN, RIGHT, RIGHT, PIN, LEFT, LEFT, PIN, RIGHT, RIGHT, PIN,
	LEFT, LEFT, ZERO }
    },
    { "bow",
      { RIGHT, LEFT, RIGHT, RIGHT, RIGHT, LEFT, LEFT, LEFT, RIGHT,
	LEFT, RIGHT, RIGHT, RIGHT, LEFT, LEFT, LEFT, RIGHT, LEFT, RIGHT,
	RIGHT, RIGHT, LEFT, LEFT, ZERO }
    },
    { "turtle",
      { ZERO, RIGHT, LEFT, ZERO, ZERO, RIGHT, LEFT, PIN, RIGHT, RIGHT,
	LEFT, RIGHT, LEFT, LEFT, PIN, LEFT, LEFT, LEFT, RIGHT, LEFT,
	RIGHT, RIGHT, RIGHT, ZERO }
    },
    { "basket",
      { RIGHT, PIN, ZERO, ZERO, PIN, LEFT, ZERO, LEFT, LEFT, ZERO,
	LEFT, PIN, ZERO, ZERO, PIN, RIGHT, PIN, LEFT, PIN, ZERO, ZERO,
	PIN, LEFT, ZERO }
    },
    { "thing",
      { PIN, RIGHT, LEFT, RIGHT, RIGHT, LEFT, PIN, LEFT, RIGHT, LEFT,
	LEFT, RIGHT, PIN, RIGHT, LEFT, RIGHT, RIGHT, LEFT, PIN, LEFT,
	RIGHT, LEFT, LEFT, ZERO }
    },
    { "hexagon",
      { ZERO, ZERO, ZERO, ZERO, LEFT, ZERO, ZERO, RIGHT, ZERO, ZERO,
	ZERO, ZERO, LEFT, ZERO, ZERO, RIGHT, ZERO, ZERO, ZERO, ZERO,
	LEFT, ZERO, ZERO, RIGHT }
    },
    { "tri1",
      { ZERO, ZERO, LEFT, RIGHT, ZERO, LEFT, ZERO, RIGHT, ZERO, ZERO,
	LEFT, RIGHT, ZERO, LEFT, ZERO, RIGHT, ZERO, ZERO, LEFT, RIGHT,
	ZERO, LEFT, ZERO, RIGHT }
    },
    { "triangle",
      { ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, LEFT, RIGHT, ZERO, ZERO,
	ZERO, ZERO, ZERO, ZERO, LEFT, RIGHT, ZERO, ZERO, ZERO, ZERO,
	ZERO, ZERO, LEFT, RIGHT }
    },
    { "flower",
      { ZERO, LEFT, PIN, RIGHT, RIGHT, PIN, ZERO, LEFT, PIN, RIGHT,
	RIGHT, PIN, ZERO, LEFT, PIN, RIGHT, RIGHT, PIN, ZERO, LEFT, PIN,
	RIGHT, RIGHT, PIN }
    },
    { "crucifix",
      { ZERO, PIN, PIN, ZERO, PIN, ZERO, PIN, PIN, ZERO, PIN, ZERO, PIN,
	PIN, ZERO, PIN, ZERO, ZERO, ZERO, PIN, PIN, ZERO, ZERO, ZERO, PIN }
    },
    { "kayak",
      { PIN, RIGHT, LEFT, PIN, LEFT, PIN, ZERO, ZERO, RIGHT, PIN, LEFT,
	ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, RIGHT, PIN, LEFT, ZERO, ZERO,
	PIN, RIGHT }
    },
    { "bird",
      { ZERO, ZERO, ZERO, ZERO, RIGHT, RIGHT, ZERO, LEFT, PIN, RIGHT,
	ZERO, RIGHT, ZERO, RIGHT, ZERO, RIGHT, PIN, LEFT, ZERO, RIGHT,
	LEFT, ZERO, PIN, ZERO }
    },
    { "seal",
      { RIGHT, LEFT, LEFT, PIN, RIGHT, LEFT, ZERO, PIN, PIN, ZERO,
	LEFT, ZERO, LEFT, PIN, RIGHT, ZERO, LEFT, LEFT, LEFT, PIN, RIGHT,
	RIGHT, LEFT, ZERO }
    },
    { "dog",
      { ZERO, ZERO, ZERO, ZERO, PIN, PIN, ZERO, PIN, ZERO, ZERO, PIN,
	ZERO, PIN, PIN, ZERO, ZERO, ZERO, PIN, ZERO, PIN, PIN, ZERO, PIN, ZERO }
    },
    { "frog",
      { RIGHT, RIGHT, LEFT, LEFT, RIGHT, PIN, RIGHT, PIN, LEFT, PIN,
	RIGHT, ZERO, LEFT, ZERO, LEFT, PIN, RIGHT, ZERO, LEFT, LEFT,
	RIGHT, LEFT, LEFT, ZERO }
    },
    { "quavers",
      { LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, ZERO, ZERO, ZERO,
	RIGHT, ZERO, ZERO, LEFT, RIGHT, ZERO, ZERO, ZERO, LEFT, LEFT,
	RIGHT, LEFT, RIGHT, RIGHT, ZERO }
    },
    { "fly",
      { LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, ZERO, PIN, ZERO, ZERO,
	LEFT, PIN, RIGHT, ZERO, ZERO, PIN, ZERO, LEFT, LEFT, RIGHT, LEFT,
	RIGHT, RIGHT, ZERO }
    },
    { "puppy",
      { ZERO, PIN, ZERO, PIN, PIN, ZERO, PIN, PIN, ZERO, ZERO, ZERO,
	RIGHT, RIGHT, PIN, RIGHT, LEFT, PIN, LEFT, RIGHT, PIN, RIGHT,
	LEFT, ZERO, ZERO }
    },
    { "stars",
      { LEFT, RIGHT, PIN, RIGHT, LEFT, PIN, LEFT, RIGHT, PIN, RIGHT,
	ZERO, ZERO, ZERO, RIGHT, PIN, RIGHT, LEFT, PIN, LEFT, RIGHT, PIN,
	RIGHT, LEFT, ZERO }
    },
    { "mountains",
      { RIGHT, PIN, RIGHT, PIN, RIGHT, PIN, LEFT, PIN, LEFT, PIN,
	LEFT, PIN, RIGHT, PIN, RIGHT, PIN, RIGHT, PIN, LEFT, PIN, LEFT,
	PIN, LEFT, PIN }
    },
    { "quad1",
      { RIGHT, PIN, RIGHT, RIGHT, RIGHT, PIN, LEFT, LEFT, LEFT, PIN,
	LEFT, PIN, RIGHT, PIN, RIGHT, RIGHT, RIGHT, PIN, LEFT, LEFT,
	LEFT, PIN, LEFT, PIN }
    },
    { "quad2",
      { ZERO, PIN, RIGHT, RIGHT, RIGHT, PIN, LEFT, LEFT, LEFT, PIN,
	ZERO, PIN, ZERO, PIN, RIGHT, RIGHT, RIGHT, PIN, LEFT, LEFT, LEFT,
	PIN, ZERO, PIN }
    },
    { "glasses",
      { ZERO, PIN, ZERO, RIGHT, RIGHT, PIN, LEFT, LEFT, ZERO, PIN,
	ZERO, PIN, ZERO, PIN, ZERO, RIGHT, RIGHT, PIN, LEFT, LEFT, ZERO,
	PIN, ZERO, PIN }
    },
    { "em",
      { ZERO, PIN, ZERO, ZERO, RIGHT, PIN, LEFT, ZERO, ZERO, PIN,
	ZERO, PIN, ZERO, PIN, ZERO, ZERO, RIGHT, PIN, LEFT, ZERO, ZERO,
	PIN, ZERO, PIN }
    },
    { "quad3",
      { ZERO, RIGHT, ZERO, ZERO, RIGHT, PIN, LEFT, ZERO, ZERO, LEFT,
	ZERO, PIN, ZERO, RIGHT, ZERO, ZERO, RIGHT, PIN, LEFT, ZERO, ZERO,
	LEFT, ZERO, PIN }
    },
    { "vee",
      { ZERO, ZERO, ZERO, ZERO, RIGHT, PIN, LEFT, ZERO, ZERO, ZERO,
	ZERO, PIN, ZERO, ZERO, ZERO, ZERO, RIGHT, PIN, LEFT, ZERO, ZERO,
	ZERO, ZERO, PIN }
    },
    { "square",
      { ZERO, ZERO, ZERO, RIGHT, RIGHT, PIN, LEFT, LEFT, ZERO, ZERO,
	ZERO, PIN, ZERO, ZERO, ZERO, RIGHT, RIGHT, PIN, LEFT, LEFT, ZERO,
	ZERO, ZERO, PIN }
    },
    { "eagle",
      { RIGHT, ZERO, ZERO, RIGHT, RIGHT, PIN, LEFT, LEFT, ZERO, ZERO,
	LEFT, PIN, RIGHT, ZERO, ZERO, RIGHT, RIGHT, PIN, LEFT, LEFT,
	ZERO, ZERO, LEFT, PIN }
    },
    { "volcano",
      { RIGHT, ZERO, LEFT, RIGHT, RIGHT, PIN, LEFT, LEFT, RIGHT,
	ZERO, LEFT, PIN, RIGHT, ZERO, LEFT, RIGHT, RIGHT, PIN, LEFT,
	LEFT, RIGHT, ZERO, LEFT, PIN }
    },
    { "saddle",
      { RIGHT, ZERO, LEFT, ZERO, RIGHT, PIN, LEFT, ZERO, RIGHT, ZERO,
	LEFT, PIN, RIGHT, ZERO, LEFT, ZERO, RIGHT, PIN, LEFT, ZERO, RIGHT,
	ZERO, LEFT, PIN }
    },
    { "c3d",
      { ZERO, ZERO, RIGHT, ZERO, ZERO, PIN, ZERO, ZERO, LEFT, ZERO,
	ZERO, PIN, ZERO, ZERO, RIGHT, ZERO, ZERO, PIN, ZERO, ZERO, LEFT,
	ZERO, ZERO, PIN }
    },
    { "block",
      { ZERO, ZERO, PIN, PIN, ZERO, RIGHT, PIN, LEFT, PIN, RIGHT, PIN,
	RIGHT, PIN, LEFT, PIN, RIGHT, ZERO, ZERO, PIN, ZERO, ZERO, LEFT,
	PIN, RIGHT }
    },
    { "duck",
      { LEFT, PIN, LEFT, PIN, ZERO, PIN, PIN, ZERO, PIN, ZERO, LEFT,
	PIN, RIGHT, ZERO, PIN, ZERO, PIN, PIN, ZERO, ZERO, LEFT, PIN,
	LEFT, ZERO }
    },
    { "prayer",
      { RIGHT, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, ZERO, ZERO,
	ZERO, RIGHT, PIN, LEFT, ZERO, ZERO, ZERO, RIGHT, RIGHT, LEFT,
	RIGHT, LEFT, LEFT, LEFT, PIN }
    },
    { "giraffe",
      { ZERO, ZERO, ZERO, RIGHT, PIN, LEFT, ZERO, ZERO, ZERO, RIGHT,
	RIGHT, RIGHT, PIN, LEFT, RIGHT, ZERO, PIN, ZERO, LEFT, RIGHT,
	PIN, LEFT, LEFT, LEFT }
    },
    { "tie fighter",
      { PIN, LEFT, RIGHT, LEFT, LEFT, PIN, RIGHT, ZERO, RIGHT, LEFT,
	ZERO, PIN, LEFT, LEFT, RIGHT, RIGHT, RIGHT, PIN, LEFT, ZERO,
	LEFT, RIGHT, ZERO, ZERO }
    },
    { "Strong Arms",
      { PIN, PIN, ZERO, ZERO, PIN, ZERO, ZERO, RIGHT, ZERO, RIGHT,
	RIGHT, PIN, RIGHT, RIGHT, ZERO, RIGHT, ZERO, ZERO, PIN, ZERO,
	ZERO, PIN, PIN, ZERO }
    },

    /* the following modesl were created during the slug/compsoc codefest
     * febrray 2003 */
    { "cool looking gegl",
      { PIN, PIN, ZERO, ZERO, RIGHT, ZERO, ZERO, PIN, PIN, ZERO, LEFT,
	ZERO, ZERO, PIN, ZERO, PIN, PIN, ZERO, LEFT, RIGHT, PIN, ZERO,
	ZERO, ZERO }
    },
    { "knuckledusters",
      { ZERO, ZERO, ZERO, ZERO, PIN, RIGHT, ZERO, PIN, PIN, ZERO, PIN,
	PIN, ZERO, RIGHT, RIGHT, ZERO, PIN, PIN, ZERO, PIN, PIN, ZERO,
	RIGHT, ZERO }
    },
    { "k's turd",
      { RIGHT, RIGHT, PIN, RIGHT, LEFT, RIGHT, PIN, RIGHT, LEFT,
	RIGHT, PIN, RIGHT, LEFT, RIGHT, PIN, RIGHT, LEFT, RIGHT, PIN,
	RIGHT, LEFT, RIGHT, PIN, ZERO }
    },
    { "lightsabre",
      { ZERO, ZERO, ZERO, ZERO, ZERO, PIN, PIN, ZERO, ZERO, ZERO, ZERO,
	ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO,
	ZERO, ZERO }
    },
    { "not a stairway",
      { LEFT, ZERO, RIGHT, LEFT, RIGHT, ZERO, LEFT, RIGHT, LEFT, ZERO,
	RIGHT, LEFT, RIGHT, ZERO, LEFT, RIGHT, LEFT, ZERO, RIGHT, LEFT,
	RIGHT, ZERO, LEFT, ZERO }
    },
    { "not very good (but accurate) gegl",
      { ZERO, PIN, PIN, ZERO, ZERO, ZERO, PIN, PIN, ZERO, LEFT, ZERO,
	PIN, PIN, ZERO, RIGHT, LEFT, ZERO, PIN, ZERO, PIN, PIN, ZERO,
	PIN, ZERO }
    },
    { "box",
      { ZERO, ZERO, ZERO, ZERO, PIN, ZERO, ZERO, ZERO, ZERO, ZERO,
	ZERO, PIN, ZERO, ZERO, ZERO, ZERO, PIN, ZERO, ZERO, ZERO, ZERO,
	ZERO, ZERO, ZERO }
    },
    { "kissy box",
      { PIN, ZERO, ZERO, ZERO, PIN, ZERO, ZERO, ZERO, ZERO, ZERO,
	ZERO, PIN, ZERO, ZERO, ZERO, ZERO, PIN, ZERO, ZERO, ZERO, ZERO,
	ZERO, PIN, ZERO }
    },
    { "erect penis",     /* thanks benno */
      { PIN, ZERO, PIN, PIN, ZERO, ZERO, PIN, ZERO, ZERO, ZERO, PIN,
	PIN, ZERO, ZERO, ZERO, RIGHT, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO,
	ZERO, ZERO }
    },
    { "flaccid penis",
      { PIN, ZERO, PIN, PIN, ZERO, ZERO, PIN, ZERO, ZERO, ZERO, PIN,
	PIN, ZERO, ZERO, ZERO, RIGHT, PIN, ZERO, ZERO, ZERO, ZERO, ZERO,
	ZERO, ZERO }
    },
    { "vagina",
      { RIGHT, ZERO, ZERO, ZERO, RIGHT, ZERO, ZERO, PIN, ZERO, ZERO,
	LEFT, ZERO, ZERO, ZERO, LEFT, ZERO, LEFT, PIN, LEFT, PIN, RIGHT,
	PIN, RIGHT, ZERO }
    },
    { "mask",
      { ZERO, RIGHT, LEFT, PIN, RIGHT, RIGHT, PIN, ZERO, ZERO, PIN,
	ZERO, ZERO, PIN, ZERO, PIN, ZERO, PIN, ZERO, ZERO, PIN, ZERO,
	ZERO, ZERO, ZERO }
    },
    { "poles or columns or something",
      { LEFT, RIGHT, LEFT, ZERO, ZERO, ZERO, PIN, PIN, ZERO, ZERO,
	ZERO, LEFT, RIGHT, LEFT, ZERO, ZERO, ZERO, PIN, PIN, ZERO, ZERO,
	ZERO, LEFT, ZERO }
    },
    { "crooked v",
      { ZERO, LEFT, ZERO, ZERO, ZERO, ZERO, PIN, PIN, ZERO, ZERO,
	ZERO, LEFT, ZERO, LEFT, ZERO, ZERO, ZERO, PIN, PIN, ZERO, ZERO,
	ZERO, ZERO, ZERO }
    },
    { "dog leg",
      { ZERO, LEFT, ZERO, ZERO, ZERO, ZERO, PIN, PIN, ZERO, ZERO, ZERO,
	LEFT, ZERO, RIGHT, ZERO, ZERO, ZERO, ZERO, PIN, PIN, ZERO, ZERO,
	ZERO, ZERO }
    },
    { "scrubby",
      { ZERO, ZERO, ZERO, ZERO, ZERO, LEFT, ZERO, ZERO, ZERO, ZERO,
	LEFT, RIGHT, ZERO, ZERO, ZERO, ZERO, LEFT, RIGHT, ZERO, ZERO,
	LEFT, PIN, ZERO, ZERO }
    },
    { "voltron's eyes",
      { ZERO, ZERO, PIN, RIGHT, ZERO, LEFT, ZERO, ZERO, RIGHT, ZERO,
	LEFT, PIN, ZERO, ZERO, PIN, ZERO, LEFT, ZERO, RIGHT, LEFT, ZERO,
	RIGHT, ZERO, ZERO }
    },
    { "flying toaster",
      { PIN, ZERO, ZERO, PIN, PIN, ZERO, RIGHT, ZERO, PIN, PIN, ZERO,
	RIGHT, ZERO, PIN, PIN, ZERO, RIGHT, ZERO, PIN, PIN, ZERO, ZERO,
	PIN, ZERO }
    },
    { "dubbya",
      { PIN, ZERO, ZERO, PIN, PIN, ZERO, RIGHT, ZERO, PIN, PIN, ZERO,
	ZERO, ZERO, PIN, PIN, ZERO, RIGHT, ZERO, PIN, PIN, ZERO, ZERO,
	PIN, ZERO }
    },
    { "tap handle",
      { PIN, ZERO, ZERO, PIN, PIN, ZERO, RIGHT, ZERO, PIN, PIN, ZERO,
	LEFT, ZERO, PIN, PIN, ZERO, RIGHT, ZERO, PIN, PIN, ZERO, ZERO,
	PIN, ZERO }
    },
    { "wingnut",
      { PIN, ZERO, ZERO, PIN, PIN, ZERO, RIGHT, ZERO, PIN, PIN, ZERO,
	PIN, ZERO, PIN, PIN, ZERO, RIGHT, ZERO, PIN, PIN, ZERO, ZERO,
	PIN, ZERO }
    },
    { "tight twist",
      { RIGHT, ZERO, ZERO, LEFT, ZERO, LEFT, RIGHT, ZERO, RIGHT, LEFT,
	RIGHT, PIN, RIGHT, LEFT, RIGHT, ZERO, RIGHT, LEFT, ZERO, LEFT,
	ZERO, ZERO, RIGHT, ZERO }
    },
    { "double helix",
      { RIGHT, ZERO, RIGHT, ZERO, RIGHT, ZERO, RIGHT, ZERO, RIGHT,
	ZERO, RIGHT, ZERO, RIGHT, LEFT, RIGHT, PIN, ZERO, RIGHT, ZERO,
	RIGHT, ZERO, RIGHT, ZERO, ZERO }
    },

    /* These models come from the website at 
     * http://www.geocities.com/stigeide/snake */
#if 0
    { "Abstract",
      { RIGHT, LEFT, RIGHT, ZERO, PIN, ZERO, LEFT, RIGHT, LEFT, PIN, ZERO, ZERO, PIN, LEFT, RIGHT, LEFT, ZERO, PIN, ZERO, RIGHT, LEFT, RIGHT, ZERO, ZERO }
    },
#endif
    { "toadstool",
      { LEFT, RIGHT, ZERO, RIGHT, LEFT, ZERO, ZERO, RIGHT, LEFT, PIN, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, RIGHT, RIGHT, RIGHT, PIN, RIGHT, LEFT, PIN, ZERO }
    },
    { "AlanH2",
      { LEFT, RIGHT, ZERO, RIGHT, LEFT, ZERO, ZERO, RIGHT, LEFT, PIN, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, RIGHT, RIGHT, LEFT, LEFT, RIGHT, LEFT, RIGHT, ZERO }
    },
    { "AlanH3",
      { LEFT, RIGHT, ZERO, RIGHT, LEFT, ZERO, ZERO, RIGHT, LEFT, PIN, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, RIGHT, RIGHT, LEFT, PIN, LEFT, RIGHT, PIN, ZERO }
    },
    { "AlanH4",
      { ZERO, ZERO, PIN, LEFT, RIGHT, LEFT, ZERO, RIGHT, LEFT, RIGHT, ZERO, PIN, ZERO, LEFT, RIGHT, LEFT, ZERO, RIGHT, LEFT, RIGHT, PIN, ZERO, ZERO, ZERO }
    },
    { "Alien",
      { RIGHT, LEFT, RIGHT, PIN, ZERO, ZERO, PIN, RIGHT, LEFT, RIGHT, ZERO, PIN, PIN, ZERO, LEFT, LEFT, RIGHT, RIGHT, LEFT, LEFT, ZERO, PIN, PIN, ZERO }
    },
    { "Angel",
      { ZERO, RIGHT, LEFT, PIN, RIGHT, RIGHT, RIGHT, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, LEFT, LEFT, LEFT, PIN, RIGHT, LEFT, ZERO, ZERO, RIGHT, LEFT, ZERO }
    },
    { "AnotherFigure",
      { LEFT, PIN, RIGHT, ZERO, ZERO, PIN, RIGHT, LEFT, LEFT, PIN, RIGHT, LEFT, ZERO, PIN, ZERO, RIGHT, LEFT, PIN, RIGHT, RIGHT, LEFT, PIN, ZERO, ZERO }
    },
    { "Ball",
        { LEFT, RIGHT, LEFT, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, LEFT, RIGHT , ZERO }
    },
    { "Basket",
        { ZERO, RIGHT, RIGHT, ZERO, RIGHT, RIGHT, ZERO, RIGHT, LEFT, ZERO, LEFT, LEFT, PIN, RIGHT, LEFT, ZERO, LEFT, RIGHT, PIN, LEFT, LEFT, ZERO, LEFT , ZERO }
    },
    { "Beetle",
        { PIN, LEFT, RIGHT, ZERO, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, ZERO, LEFT, RIGHT, PIN, RIGHT , ZERO }
    },
    { "bone",
        { PIN, PIN, LEFT, ZERO, PIN, PIN, ZERO, LEFT, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, RIGHT, ZERO, PIN, PIN, ZERO, RIGHT, PIN, PIN , ZERO }
    },
    { "Bow",
        { LEFT, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, RIGHT, LEFT, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, RIGHT, LEFT, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT , ZERO }
    },
    { "bra",
        { RIGHT, RIGHT, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, LEFT, LEFT, LEFT, RIGHT, RIGHT, RIGHT, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, LEFT, LEFT, LEFT , ZERO }
    },
    { "bronchosaurus",
        { ZERO, PIN, ZERO, PIN, PIN, ZERO, PIN, ZERO, ZERO, PIN, ZERO, PIN, PIN, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, PIN , ZERO }
    },
    { "Cactus",
        { PIN, LEFT, ZERO, PIN, PIN, ZERO, RIGHT, PIN, LEFT, ZERO, ZERO, PIN, RIGHT, PIN, LEFT, ZERO, ZERO, RIGHT, PIN, LEFT, PIN, ZERO, ZERO , ZERO }
    },
    { "Camel",
        { RIGHT, ZERO, PIN, RIGHT, PIN, RIGHT, ZERO, RIGHT, PIN, RIGHT, LEFT, PIN, LEFT, RIGHT, PIN, RIGHT, ZERO, RIGHT, PIN, RIGHT, ZERO, ZERO, LEFT , ZERO }
    },
    { "Candlestick",
        { LEFT, PIN, LEFT, ZERO, RIGHT, PIN, LEFT, ZERO, RIGHT, PIN, RIGHT, PIN, LEFT, PIN, LEFT, ZERO, RIGHT, PIN, LEFT, ZERO, RIGHT, PIN, RIGHT , ZERO }
    },
    { "Cat",
        { ZERO, PIN, PIN, ZERO, PIN, PIN, ZERO, RIGHT, ZERO, PIN, PIN, ZERO, PIN, PIN, ZERO, PIN, PIN, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO , ZERO }
    },
    { "Cave",
        { RIGHT, ZERO, ZERO, PIN, LEFT, ZERO, PIN, PIN, ZERO, RIGHT, LEFT, PIN, RIGHT, RIGHT, LEFT, LEFT, PIN, RIGHT, RIGHT, LEFT, PIN, ZERO, ZERO , ZERO }
    },
    { "Chains",
        { PIN, ZERO, ZERO, PIN, LEFT, LEFT, PIN, RIGHT, RIGHT, PIN, ZERO, ZERO, PIN, ZERO, ZERO, PIN, LEFT, LEFT, PIN, RIGHT, RIGHT, PIN, ZERO , ZERO }
    },
    { "Chair",
        { RIGHT, LEFT, RIGHT, RIGHT, RIGHT, LEFT, RIGHT, ZERO, ZERO, PIN, PIN, ZERO, PIN, PIN, ZERO, PIN, PIN, ZERO, ZERO, LEFT, RIGHT, LEFT, LEFT , ZERO }
    },
    { "Chick",
        { RIGHT, RIGHT, RIGHT, PIN, LEFT, PIN, LEFT, PIN, RIGHT, RIGHT, RIGHT, PIN, LEFT, LEFT, LEFT, PIN, RIGHT, PIN, RIGHT, PIN, LEFT, LEFT, LEFT , ZERO }
    },
    { "Clockwise",
        { RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT , ZERO }
    },
    { "cobra",
        { ZERO, RIGHT, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, LEFT, LEFT, ZERO, LEFT, RIGHT, ZERO, ZERO, PIN, ZERO, ZERO, RIGHT , ZERO }
    },
#if 0
    { "Cobra2",
        { LEFT, ZERO, PIN, ZERO, PIN, LEFT, ZERO, PIN, ZERO, LEFT, LEFT, PIN, RIGHT, RIGHT, ZERO, PIN, ZERO, RIGHT, PIN, ZERO, PIN, ZERO, RIGHT , ZERO }
    },
#endif
    { "Cobra3",
        { ZERO, LEFT, ZERO, PIN, PIN, ZERO, PIN, PIN, ZERO, RIGHT, ZERO, PIN, ZERO, ZERO, LEFT, ZERO, ZERO, ZERO, PIN, ZERO, ZERO, ZERO, LEFT , ZERO }
    },
    { "Compact1",
        { ZERO, ZERO, PIN, ZERO, ZERO, LEFT, PIN, RIGHT, PIN, LEFT, PIN, LEFT, PIN, RIGHT, PIN, LEFT, ZERO, PIN, PIN, ZERO, ZERO, LEFT, PIN , ZERO }
    },
    { "Compact2",
        { LEFT, PIN, RIGHT, ZERO, ZERO, PIN, PIN, ZERO, RIGHT, PIN, LEFT, ZERO, ZERO, RIGHT, PIN, RIGHT, PIN, LEFT, PIN, RIGHT, ZERO, ZERO, ZERO , ZERO }
    },
    { "Compact3",
        { ZERO, PIN, ZERO, PIN, PIN, ZERO, LEFT, PIN, RIGHT, ZERO, PIN, PIN, ZERO, PIN, ZERO, PIN, PIN, ZERO, LEFT, PIN, RIGHT, ZERO, PIN , ZERO }
    },
    { "Compact4",
        { PIN, RIGHT, ZERO, ZERO, PIN, ZERO, ZERO, PIN, PIN, ZERO, PIN, RIGHT, PIN, LEFT, PIN, ZERO, PIN, PIN, ZERO, ZERO, PIN, ZERO, ZERO , ZERO }
    },
    { "Compact5",
        { LEFT, ZERO, LEFT, PIN, RIGHT, PIN, LEFT, PIN, LEFT, PIN, RIGHT, PIN, RIGHT, PIN, LEFT, PIN, RIGHT, ZERO, RIGHT, PIN, RIGHT, PIN, LEFT , ZERO }
    },
    { "Contact",
        { PIN, ZERO, ZERO, PIN, LEFT, LEFT, PIN, LEFT, RIGHT, RIGHT, PIN, LEFT, LEFT, RIGHT, PIN, RIGHT, RIGHT, PIN, ZERO, ZERO, PIN, RIGHT, PIN , ZERO }
    },
    { "Contact2",
        { RIGHT, PIN, ZERO, LEFT, LEFT, PIN, RIGHT, RIGHT, ZERO, PIN, LEFT, PIN, RIGHT, PIN, ZERO, LEFT, LEFT, PIN, RIGHT, RIGHT, ZERO, PIN, LEFT , ZERO }
    },
    { "Cook",
        { ZERO, ZERO, PIN, PIN, ZERO, RIGHT, ZERO, RIGHT, LEFT, PIN, LEFT, ZERO, PIN, PIN, ZERO, LEFT, PIN, LEFT, RIGHT, ZERO, RIGHT, ZERO, PIN , ZERO }
    },
    { "Counterclockwise",
        { LEFT, LEFT, LEFT, LEFT, LEFT, LEFT, LEFT, LEFT, LEFT, LEFT, LEFT, LEFT, LEFT, LEFT, LEFT, LEFT, LEFT, LEFT, LEFT, LEFT, LEFT, LEFT, LEFT , ZERO }
    },
    { "Cradle",
        { LEFT, LEFT, ZERO, PIN, LEFT, RIGHT, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, LEFT, RIGHT, PIN, ZERO, RIGHT, RIGHT, LEFT, LEFT, ZERO, ZERO, RIGHT , ZERO }
    },
    { "Crankshaft",
        { ZERO, PIN, PIN, ZERO, PIN, PIN, ZERO, PIN, LEFT, LEFT, PIN, RIGHT, LEFT, ZERO, PIN, LEFT, PIN, RIGHT, ZERO, ZERO, ZERO, PIN, RIGHT , ZERO }
    },
    { "Cross",
        { ZERO, PIN, ZERO, PIN, PIN, ZERO, PIN, ZERO, ZERO, ZERO, PIN, PIN, ZERO, ZERO, ZERO, PIN, ZERO, PIN, PIN, ZERO, PIN, ZERO, PIN , ZERO }
    },
    { "Cross2",
        { ZERO, ZERO, PIN, PIN, ZERO, LEFT, ZERO, ZERO, PIN, PIN, ZERO, RIGHT, ZERO, ZERO, PIN, PIN, ZERO, LEFT, ZERO, ZERO, PIN, PIN, ZERO , ZERO }
    },
    { "Cross3",
        { ZERO, ZERO, PIN, PIN, ZERO, LEFT, ZERO, ZERO, PIN, PIN, ZERO, RIGHT, ZERO, ZERO, PIN, PIN, ZERO, LEFT, ZERO, ZERO, PIN, PIN, ZERO, ZERO }
    },
    { "CrossVersion1",
        { PIN, ZERO, RIGHT, PIN, LEFT, PIN, RIGHT, PIN, RIGHT, PIN, LEFT, PIN, RIGHT, ZERO, PIN, RIGHT, PIN, RIGHT, LEFT, PIN, LEFT, RIGHT, PIN, ZERO }
    },
    { "CrossVersion2",
        { RIGHT, LEFT, PIN, LEFT, LEFT, ZERO, RIGHT, LEFT, PIN, RIGHT, RIGHT, PIN, LEFT, LEFT, PIN, RIGHT, LEFT, ZERO, LEFT, LEFT, PIN, LEFT, RIGHT, ZERO }
    },
    { "Crown",
        { LEFT, ZERO, PIN, ZERO, RIGHT, ZERO, ZERO, LEFT, ZERO, PIN, ZERO, RIGHT, LEFT, ZERO, PIN, ZERO, RIGHT, ZERO, ZERO, LEFT, ZERO, PIN, ZERO, ZERO }
    },
    { "DNAStrand",
        { RIGHT, PIN, RIGHT, PIN, RIGHT, PIN, RIGHT, PIN, RIGHT, PIN, RIGHT, PIN, RIGHT, PIN, RIGHT, PIN, RIGHT, PIN, RIGHT, PIN, RIGHT, PIN, RIGHT, ZERO }
    },
    { "Diamond",
        { ZERO, RIGHT, ZERO, ZERO, LEFT, ZERO, ZERO, RIGHT, PIN, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, PIN, LEFT, ZERO, ZERO, RIGHT, ZERO, ZERO, LEFT, ZERO }
    },
    { "Dog",
        { RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, LEFT, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, RIGHT, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, ZERO, LEFT, RIGHT, ZERO }
    },
    { "DogFace",
        { ZERO, ZERO, PIN, PIN, ZERO, LEFT, LEFT, RIGHT, PIN, ZERO, PIN, PIN, ZERO, PIN, LEFT, RIGHT, RIGHT, ZERO, PIN, PIN, ZERO, ZERO, PIN, ZERO }
    },
    { "DoublePeak",
        { ZERO, ZERO, PIN, ZERO, ZERO, RIGHT, LEFT, PIN, LEFT, RIGHT, PIN, RIGHT, LEFT, LEFT, ZERO, PIN, ZERO, RIGHT, RIGHT, LEFT, PIN, LEFT, RIGHT, ZERO }
    },
    { "DoubleRoof",
        { ZERO, LEFT, LEFT, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, LEFT, LEFT, ZERO, LEFT, RIGHT, PIN, LEFT, LEFT, PIN, RIGHT, ZERO }
    },
    { "txoboggan",
        { ZERO, ZERO, ZERO, RIGHT, PIN, LEFT, ZERO, ZERO, ZERO, PIN, PIN, ZERO, ZERO, ZERO, ZERO, LEFT, PIN, RIGHT, ZERO, ZERO, ZERO, ZERO, PIN, ZERO }
    },
    { "Doubled",
        { LEFT, PIN, LEFT, RIGHT, PIN, RIGHT, LEFT, PIN, LEFT, RIGHT, LEFT, ZERO, LEFT, PIN, LEFT, PIN, LEFT, RIGHT, PIN, RIGHT, LEFT, PIN, LEFT, ZERO }
    },
    { "Doubled1",
        { LEFT, PIN, LEFT, RIGHT, PIN, RIGHT, LEFT, PIN, LEFT, ZERO, RIGHT, ZERO, RIGHT, ZERO, LEFT, PIN, LEFT, RIGHT, PIN, RIGHT, LEFT, PIN, LEFT, ZERO }
    },
    { "Doubled2",
        { LEFT, PIN, LEFT, RIGHT, PIN, RIGHT, LEFT, PIN, LEFT, LEFT, RIGHT, ZERO, RIGHT, LEFT, LEFT, PIN, LEFT, RIGHT, PIN, RIGHT, LEFT, PIN, LEFT, ZERO }
    },
    { "DumblingSpoon",
        { PIN, PIN, ZERO, ZERO, ZERO, ZERO, ZERO, LEFT, ZERO, ZERO, LEFT, RIGHT, ZERO, ZERO, LEFT, RIGHT, ZERO, ZERO, RIGHT, ZERO, ZERO, ZERO, ZERO, ZERO }
    },
    { "Embrace",
        { PIN, ZERO, ZERO, PIN, RIGHT, PIN, LEFT, PIN, ZERO, RIGHT, PIN, RIGHT, PIN, LEFT, PIN, LEFT, ZERO, PIN, RIGHT, PIN, LEFT, PIN, ZERO, ZERO }
    },
    { "EndlessBelt",
        { ZERO, RIGHT, LEFT, ZERO, ZERO, ZERO, LEFT, RIGHT, ZERO, PIN, RIGHT, LEFT, ZERO, LEFT, RIGHT, LEFT, PIN, LEFT, RIGHT, LEFT, ZERO, LEFT, RIGHT, ZERO }
    },
    { "Entrance",
        { LEFT, LEFT, RIGHT, RIGHT, RIGHT, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, RIGHT, LEFT, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, LEFT, LEFT, LEFT, RIGHT, ZERO }
    },
    { "Esthetic",
        { LEFT, LEFT, PIN, RIGHT, RIGHT, ZERO, LEFT, PIN, RIGHT, PIN, LEFT, PIN, LEFT, PIN, RIGHT, PIN, LEFT, ZERO, RIGHT, LEFT, PIN, RIGHT, RIGHT, ZERO }
    },
    { "Explosion",
        { RIGHT, RIGHT, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, RIGHT, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, LEFT, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, LEFT, ZERO }
    },
    { "F-ZeroXCar",
        { RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, PIN, RIGHT, LEFT, ZERO, ZERO, RIGHT, LEFT, ZERO, ZERO, LEFT, RIGHT, PIN, RIGHT, LEFT, PIN, LEFT, RIGHT, ZERO }
    },
    { "Face",
        { ZERO, RIGHT, PIN, RIGHT, LEFT, PIN, LEFT, LEFT, PIN, RIGHT, RIGHT, PIN, RIGHT, LEFT, PIN, LEFT, PIN, LEFT, PIN, LEFT, RIGHT, PIN, RIGHT, ZERO }
    },
    { "Fantasy",
        { LEFT, LEFT, RIGHT, PIN, ZERO, RIGHT, ZERO, LEFT, PIN, LEFT, PIN, RIGHT, PIN, RIGHT, ZERO, LEFT, ZERO, PIN, LEFT, RIGHT, RIGHT, RIGHT, PIN, ZERO }
    },
    { "Fantasy1",
        { PIN, ZERO, ZERO, PIN, PIN, ZERO, PIN, RIGHT, LEFT, RIGHT, RIGHT, PIN, LEFT, LEFT, RIGHT, LEFT, PIN, ZERO, PIN, PIN, ZERO, ZERO, PIN, ZERO }
    },
    { "FaserGun",
        { ZERO, ZERO, LEFT, RIGHT, PIN, RIGHT, ZERO, RIGHT, PIN, RIGHT, LEFT, PIN, LEFT, RIGHT, PIN, RIGHT, ZERO, RIGHT, PIN, RIGHT, RIGHT, ZERO, PIN, ZERO }
    },
    { "FelixW",
        { ZERO, RIGHT, ZERO, PIN, LEFT, ZERO, LEFT, RIGHT, ZERO, ZERO, RIGHT, PIN, LEFT, ZERO, ZERO, LEFT, RIGHT, ZERO, RIGHT, PIN, ZERO, LEFT, ZERO, ZERO }
    },
    { "Flamingo",
        { ZERO, PIN, ZERO, ZERO, ZERO, ZERO, ZERO, PIN, LEFT, LEFT, PIN, LEFT, RIGHT, PIN, RIGHT, LEFT, PIN, LEFT, LEFT, ZERO, ZERO, ZERO, PIN, ZERO }
    },
    { "FlatOnTheTop",
        { ZERO, PIN, PIN, ZERO, PIN, RIGHT, ZERO, RIGHT, LEFT, PIN, RIGHT, RIGHT, PIN, LEFT, RIGHT, ZERO, RIGHT, ZERO, ZERO, PIN, ZERO, ZERO, PIN, ZERO }
    },
    { "Fly",
        { ZERO, LEFT, PIN, RIGHT, ZERO, PIN, LEFT, PIN, LEFT, RIGHT, PIN, RIGHT, PIN, RIGHT, PIN, LEFT, PIN, LEFT, PIN, LEFT, RIGHT, PIN, RIGHT, ZERO }
    },
    { "Fountain",
        { LEFT, RIGHT, LEFT, RIGHT, RIGHT, PIN, LEFT, PIN, LEFT, RIGHT, RIGHT, PIN, LEFT, LEFT, RIGHT, RIGHT, PIN, LEFT, LEFT, RIGHT, PIN, RIGHT, PIN, ZERO }
    },
    { "Frog",
        { LEFT, LEFT, RIGHT, RIGHT, LEFT, PIN, LEFT, PIN, RIGHT, PIN, LEFT, ZERO, RIGHT, ZERO, RIGHT, PIN, LEFT, ZERO, RIGHT, RIGHT, LEFT, RIGHT, RIGHT, ZERO }
    },
    { "Frog2",
        { LEFT, ZERO, LEFT, RIGHT, RIGHT, PIN, LEFT, RIGHT, ZERO, ZERO, RIGHT, PIN, LEFT, ZERO, ZERO, LEFT, RIGHT, PIN, LEFT, LEFT, RIGHT, ZERO, RIGHT, ZERO }
    },
    { "Furby",
        { PIN, ZERO, LEFT, PIN, RIGHT, ZERO, PIN, PIN, ZERO, PIN, PIN, ZERO, ZERO, PIN, ZERO, RIGHT, PIN, LEFT, ZERO, PIN, ZERO, ZERO, PIN, ZERO }
    },
    { "Gate",
        { ZERO, ZERO, PIN, ZERO, ZERO, RIGHT, ZERO, PIN, PIN, ZERO, LEFT, PIN, LEFT, LEFT, PIN, RIGHT, RIGHT, PIN, RIGHT, ZERO, PIN, PIN, ZERO, ZERO }
    },
    { "Ghost",
        { LEFT, LEFT, LEFT, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, RIGHT, RIGHT, RIGHT, PIN, LEFT, RIGHT, ZERO, ZERO, LEFT, RIGHT, ZERO, ZERO, LEFT, RIGHT, ZERO }
    },
    { "Globus",
        { RIGHT, LEFT, ZERO, PIN, LEFT, LEFT, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, LEFT, RIGHT, PIN, ZERO, RIGHT, LEFT, ZERO, ZERO }
    },
    { "Grotto",
        { PIN, PIN, ZERO, LEFT, RIGHT, LEFT, ZERO, PIN, RIGHT, PIN, LEFT, ZERO, ZERO, ZERO, ZERO, RIGHT, PIN, LEFT, PIN, ZERO, RIGHT, LEFT, RIGHT, ZERO }
    },
    { "H",
        { PIN, ZERO, PIN, PIN, ZERO, ZERO, ZERO, ZERO, PIN, PIN, ZERO, PIN, LEFT, ZERO, PIN, PIN, ZERO, ZERO, ZERO, ZERO, PIN, PIN, ZERO, ZERO }
    },
    { "HeadOfDevil",
        { PIN, ZERO, RIGHT, ZERO, RIGHT, PIN, LEFT, ZERO, RIGHT, PIN, RIGHT, LEFT, PIN, LEFT, LEFT, PIN, RIGHT, RIGHT, PIN, RIGHT, LEFT, ZERO, ZERO, ZERO }
    },
    { "Heart",
        { RIGHT, ZERO, ZERO, ZERO, PIN, LEFT, PIN, LEFT, RIGHT, RIGHT, ZERO, PIN, ZERO, LEFT, LEFT, RIGHT, PIN, RIGHT, PIN, ZERO, ZERO, ZERO, LEFT, ZERO }
    },
    { "Heart2",
        { ZERO, PIN, ZERO, ZERO, LEFT, ZERO, LEFT, ZERO, ZERO, ZERO, ZERO, PIN, ZERO, ZERO, ZERO, ZERO, RIGHT, ZERO, RIGHT, ZERO, ZERO, PIN, ZERO, ZERO }
    },
    { "Hexagon",
        { ZERO, ZERO, ZERO, ZERO, LEFT, ZERO, ZERO, RIGHT, ZERO, ZERO, ZERO, ZERO, LEFT, ZERO, ZERO, RIGHT, ZERO, ZERO, ZERO, ZERO, LEFT, ZERO, ZERO, ZERO }
    },
    { "HoleInTheMiddle1",
        { ZERO, LEFT, RIGHT, PIN, LEFT, LEFT, PIN, RIGHT, LEFT, ZERO, LEFT, RIGHT, ZERO, RIGHT, LEFT, PIN, RIGHT, RIGHT, PIN, LEFT, RIGHT, ZERO, RIGHT, ZERO }
    },
    { "HoleInTheMiddle2",
        { ZERO, LEFT, RIGHT, ZERO, RIGHT, RIGHT, PIN, LEFT, RIGHT, ZERO, RIGHT, LEFT, ZERO, LEFT, RIGHT, ZERO, RIGHT, RIGHT, PIN, LEFT, RIGHT, ZERO, RIGHT, ZERO }
    },
    { "HouseBoat",
        { RIGHT, RIGHT, PIN, LEFT, LEFT, LEFT, PIN, RIGHT, RIGHT, RIGHT, PIN, LEFT, RIGHT, ZERO, LEFT, PIN, RIGHT, PIN, LEFT, PIN, LEFT, RIGHT, PIN, ZERO }
    },
    { "HouseByHouse",
        { LEFT, PIN, LEFT, PIN, LEFT, PIN, RIGHT, PIN, RIGHT, PIN, RIGHT, PIN, LEFT, PIN, LEFT, PIN, LEFT, PIN, RIGHT, PIN, RIGHT, PIN, RIGHT, ZERO }
    },
    { "Infinity",
        { LEFT, LEFT, LEFT, RIGHT, RIGHT, LEFT, LEFT, RIGHT, RIGHT, LEFT, LEFT, LEFT, LEFT, LEFT, LEFT, RIGHT, RIGHT, LEFT, LEFT, RIGHT, RIGHT, LEFT, LEFT, ZERO }
    },
    { "Integral",
        { RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, LEFT, LEFT, LEFT, LEFT, LEFT, LEFT, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, ZERO }
    },
    { "Iron",
        { ZERO, ZERO, ZERO, ZERO, PIN, RIGHT, ZERO, RIGHT, ZERO, ZERO, LEFT, PIN, RIGHT, ZERO, ZERO, RIGHT, PIN, LEFT, ZERO, ZERO, RIGHT, ZERO, RIGHT, ZERO }
    },
    { "just squares",
        { RIGHT, RIGHT, LEFT, PIN, LEFT, PIN, RIGHT, PIN, RIGHT, LEFT, LEFT, PIN, RIGHT, RIGHT, LEFT, PIN, LEFT, PIN, RIGHT, PIN, RIGHT, LEFT, LEFT, ZERO }
    },
    { "Kink",
        { ZERO, PIN, PIN, ZERO, PIN, ZERO, PIN, PIN, ZERO, ZERO, RIGHT, PIN, LEFT, ZERO, ZERO, PIN, PIN, ZERO, PIN, ZERO, PIN, PIN, ZERO, ZERO }
    },
    { "Knot",
        { LEFT, LEFT, PIN, LEFT, ZERO, LEFT, RIGHT, LEFT, PIN, LEFT, LEFT, RIGHT, RIGHT, PIN, RIGHT, LEFT, RIGHT, ZERO, RIGHT, PIN, RIGHT, RIGHT, LEFT, ZERO }
    },
    { "Leaf",
        { ZERO, PIN, PIN, ZERO, ZERO, LEFT, ZERO, LEFT, ZERO, ZERO, PIN, ZERO, ZERO, RIGHT, ZERO, RIGHT, PIN, LEFT, ZERO, RIGHT, PIN, LEFT, ZERO, ZERO }
    },
    { "LeftAsRight",
        { RIGHT, PIN, LEFT, RIGHT, LEFT, ZERO, RIGHT, LEFT, PIN, RIGHT, RIGHT, PIN, LEFT, LEFT, PIN, RIGHT, LEFT, ZERO, RIGHT, LEFT, RIGHT, PIN, LEFT, ZERO }
    },
    { "Long-necked",
        { PIN, ZERO, LEFT, PIN, LEFT, PIN, RIGHT, PIN, RIGHT, ZERO, PIN, ZERO, LEFT, PIN, LEFT, PIN, RIGHT, PIN, LEFT, ZERO, PIN, PIN, ZERO, ZERO }
    },
    { "lunar module",
        { PIN, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, PIN, LEFT, RIGHT, ZERO, RIGHT, LEFT, ZERO, LEFT, ZERO }
    },
    { "magnifying glass",
        { ZERO, ZERO, PIN, ZERO, LEFT, ZERO, PIN, PIN, ZERO, ZERO, RIGHT, PIN, LEFT, ZERO, ZERO, PIN, PIN, ZERO, RIGHT, ZERO, PIN, ZERO, ZERO, ZERO }
    },
    { "Mask",
        { ZERO, ZERO, ZERO, RIGHT, ZERO, RIGHT, LEFT, ZERO, LEFT, PIN, ZERO, PIN, ZERO, ZERO, PIN, ZERO, PIN, RIGHT, ZERO, RIGHT, LEFT, ZERO, LEFT, ZERO }
    },
    { "Microscope",
        { PIN, PIN, ZERO, ZERO, PIN, ZERO, RIGHT, PIN, ZERO, ZERO, RIGHT, PIN, LEFT, ZERO, ZERO, PIN, LEFT, ZERO, PIN, PIN, ZERO, PIN, PIN, ZERO }
    },
    { "Mirror",
        { PIN, RIGHT, LEFT, ZERO, PIN, PIN, ZERO, ZERO, LEFT, RIGHT, ZERO, ZERO, PIN, ZERO, ZERO, LEFT, RIGHT, PIN, RIGHT, ZERO, PIN, PIN, ZERO, ZERO }
    },
    { "MissPiggy",
        { ZERO, LEFT, LEFT, PIN, RIGHT, ZERO, RIGHT, RIGHT, PIN, LEFT, LEFT, RIGHT, RIGHT, PIN, LEFT, LEFT, ZERO, LEFT, PIN, RIGHT, RIGHT, ZERO, RIGHT, ZERO }
    },
    { "Mole",
        { ZERO, RIGHT, ZERO, RIGHT, LEFT, RIGHT, PIN, ZERO, LEFT, PIN, RIGHT, ZERO, PIN, LEFT, RIGHT, LEFT, ZERO, LEFT, ZERO, RIGHT, RIGHT, PIN, LEFT, ZERO }
    },
    { "Monk",
        { LEFT, ZERO, PIN, PIN, ZERO, LEFT, ZERO, PIN, PIN, ZERO, RIGHT, ZERO, PIN, PIN, ZERO, RIGHT, LEFT, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, ZERO }
    },
    { "Mountain",
        { ZERO, RIGHT, LEFT, PIN, RIGHT, RIGHT, PIN, LEFT, RIGHT, ZERO, LEFT, PIN, LEFT, ZERO, RIGHT, LEFT, PIN, RIGHT, RIGHT, PIN, LEFT, RIGHT, ZERO, ZERO }
    },
    { "mountains",
        { ZERO, PIN, ZERO, LEFT, PIN, LEFT, RIGHT, PIN, RIGHT, PIN, RIGHT, PIN, LEFT, PIN, LEFT, PIN, LEFT, RIGHT, PIN, RIGHT, ZERO, PIN, ZERO, ZERO }
    },
    { "MouseWithoutTail",
        { ZERO, PIN, PIN, ZERO, LEFT, ZERO, PIN, PIN, ZERO, ZERO, RIGHT, PIN, LEFT, ZERO, ZERO, PIN, PIN, ZERO, RIGHT, ZERO, PIN, PIN, ZERO, ZERO }
    },
    { "mushroom",
        { PIN, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, PIN, LEFT, RIGHT, ZERO, ZERO, LEFT, PIN, ZERO, RIGHT, ZERO, PIN, PIN, ZERO, LEFT, ZERO, PIN, ZERO }
    },
    { "necklace",
        { ZERO, ZERO, LEFT, ZERO, ZERO, ZERO, LEFT, ZERO, ZERO, ZERO, ZERO, PIN, ZERO, ZERO, ZERO, ZERO, RIGHT, ZERO, ZERO, ZERO, RIGHT, ZERO, ZERO, ZERO }
    },
    { "NestledAgainst",
        { LEFT, ZERO, PIN, LEFT, LEFT, RIGHT, RIGHT, PIN, ZERO, RIGHT, PIN, LEFT, ZERO, RIGHT, LEFT, PIN, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, LEFT, ZERO }
    },
    { "NoClue",
        { ZERO, RIGHT, PIN, LEFT, LEFT, LEFT, ZERO, LEFT, PIN, RIGHT, RIGHT, PIN, LEFT, LEFT, PIN, RIGHT, ZERO, RIGHT, RIGHT, RIGHT, PIN, LEFT, ZERO, ZERO }
    },
    { "Noname",
        { LEFT, PIN, RIGHT, PIN, RIGHT, ZERO, PIN, ZERO, ZERO, PIN, PIN, ZERO, PIN, PIN, ZERO, RIGHT, PIN, LEFT, PIN, RIGHT, PIN, RIGHT, LEFT, ZERO }
    },
    { "Obelisk",
        { PIN, ZERO, ZERO, ZERO, PIN, RIGHT, PIN, LEFT, PIN, LEFT, PIN, LEFT, RIGHT, PIN, RIGHT, PIN, RIGHT, PIN, LEFT, PIN, ZERO, ZERO, ZERO, ZERO }
    },
    { "Ostrich",
        { ZERO, ZERO, PIN, PIN, ZERO, LEFT, ZERO, PIN, PIN, ZERO, PIN, PIN, ZERO, RIGHT, ZERO, PIN, PIN, ZERO, ZERO, ZERO, ZERO, ZERO, PIN, ZERO }
    },
    { "Ostrich2",
        { PIN, PIN, ZERO, PIN, LEFT, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, RIGHT, PIN, ZERO, PIN, ZERO, ZERO, PIN, ZERO, ZERO }
    },
    { "pair of glasses",
        { ZERO, PIN, ZERO, ZERO, PIN, ZERO, ZERO, PIN, ZERO, LEFT, ZERO, PIN, ZERO, RIGHT, ZERO, PIN, ZERO, ZERO, PIN, ZERO, ZERO, PIN, ZERO, ZERO }
    },
    { "Parrot",
        { ZERO, ZERO, ZERO, ZERO, RIGHT, RIGHT, ZERO, LEFT, PIN, RIGHT, ZERO, RIGHT, ZERO, RIGHT, ZERO, RIGHT, PIN, LEFT, ZERO, RIGHT, LEFT, ZERO, PIN, ZERO }
    },
    { "Penis",
        { PIN, PIN, RIGHT, ZERO, PIN, PIN, ZERO, PIN, ZERO, ZERO, RIGHT, PIN, LEFT, ZERO, ZERO, PIN, ZERO, PIN, PIN, ZERO, LEFT, PIN, PIN, ZERO }
    },
    { "PictureComingSoon",
        { LEFT, LEFT, ZERO, RIGHT, LEFT, PIN, RIGHT, RIGHT, PIN, RIGHT, LEFT, PIN, LEFT, RIGHT, PIN, RIGHT, RIGHT, PIN, RIGHT, LEFT, ZERO, RIGHT, RIGHT, ZERO }
    },
    { "Pitti",
        { LEFT, PIN, ZERO, ZERO, PIN, ZERO, ZERO, PIN, ZERO, ZERO, RIGHT, PIN, LEFT, ZERO, ZERO, PIN, ZERO, ZERO, PIN, ZERO, ZERO, PIN, RIGHT, ZERO }
    },
    { "Plait",
        { LEFT, LEFT, LEFT, LEFT, LEFT, LEFT, LEFT, LEFT, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, LEFT, ZERO }
    },
    { "Platform",
        { RIGHT, PIN, ZERO, ZERO, ZERO, ZERO, PIN, ZERO, ZERO, PIN, PIN, ZERO, PIN, LEFT, ZERO, RIGHT, LEFT, PIN, RIGHT, RIGHT, PIN, LEFT, RIGHT, ZERO }
    },
    { "PodRacer",
        { ZERO, PIN, ZERO, PIN, RIGHT, PIN, ZERO, RIGHT, PIN, LEFT, LEFT, PIN, RIGHT, LEFT, ZERO, PIN, PIN, ZERO, ZERO, LEFT, ZERO, PIN, LEFT, ZERO }
    },
#if 0
    { "Pokemon",
        { LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, LEFT, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, LEFT, LEFT, RIGHT, RIGHT, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, ZERO }
    },
#endif
    { "Prawn",
        { RIGHT, PIN, ZERO, PIN, RIGHT, ZERO, PIN, PIN, ZERO, ZERO, LEFT, PIN, RIGHT, ZERO, ZERO, PIN, PIN, ZERO, LEFT, PIN, ZERO, PIN, LEFT, ZERO }
    },
    { "Propeller",
        { ZERO, ZERO, ZERO, RIGHT, ZERO, LEFT, RIGHT, LEFT, ZERO, ZERO, ZERO, RIGHT, ZERO, LEFT, RIGHT, LEFT, ZERO, ZERO, ZERO, RIGHT, ZERO, LEFT, RIGHT, ZERO }
    },
    { "Pyramid",
        { ZERO, LEFT, PIN, RIGHT, ZERO, LEFT, PIN, RIGHT, ZERO, LEFT, PIN, RIGHT, ZERO, PIN, RIGHT, LEFT, LEFT, LEFT, PIN, RIGHT, RIGHT, RIGHT, LEFT, ZERO }
    },
    { "QuarterbackTiltedAndReadyToHut",
        { PIN, ZERO, RIGHT, RIGHT, LEFT, RIGHT, PIN, RIGHT, LEFT, RIGHT, ZERO, PIN, ZERO, LEFT, RIGHT, LEFT, PIN, LEFT, RIGHT, LEFT, LEFT, ZERO, PIN, ZERO }
    },
    { "Ra",
        { PIN, LEFT, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, ZERO, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, ZERO }
    },
    { "Rattlesnake",
        { LEFT, ZERO, LEFT, ZERO, LEFT, ZERO, LEFT, LEFT, ZERO, LEFT, ZERO, LEFT, ZERO, LEFT, RIGHT, ZERO, PIN, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, ZERO }
    },
    { "Revelation",
        { ZERO, ZERO, ZERO, PIN, ZERO, ZERO, PIN, RIGHT, LEFT, LEFT, LEFT, RIGHT, RIGHT, LEFT, LEFT, RIGHT, RIGHT, RIGHT, LEFT, PIN, ZERO, ZERO, PIN, ZERO }
    },
    { "Revolution1",
        { LEFT, LEFT, PIN, RIGHT, ZERO, PIN, ZERO, LEFT, PIN, RIGHT, RIGHT, PIN, LEFT, LEFT, PIN, RIGHT, ZERO, PIN, ZERO, LEFT, PIN, RIGHT, RIGHT, ZERO }
    },
    { "Ribbon",
        { RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, PIN, ZERO, PIN, PIN, ZERO, PIN, ZERO, PIN, PIN, ZERO, PIN, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, ZERO }
    },
    { "Rocket",
        { RIGHT, ZERO, LEFT, PIN, RIGHT, ZERO, RIGHT, ZERO, LEFT, ZERO, RIGHT, PIN, LEFT, ZERO, RIGHT, ZERO, LEFT, ZERO, LEFT, PIN, RIGHT, ZERO, LEFT, ZERO }
    },
    { "Roofed",
        { ZERO, LEFT, PIN, RIGHT, ZERO, PIN, LEFT, ZERO, PIN, ZERO, RIGHT, PIN, ZERO, LEFT, PIN, RIGHT, ZERO, PIN, LEFT, ZERO, PIN, ZERO, RIGHT, ZERO }
    },
    { "Roofs",
        { PIN, PIN, RIGHT, ZERO, LEFT, PIN, RIGHT, PIN, LEFT, PIN, LEFT, PIN, RIGHT, PIN, RIGHT, PIN, LEFT, PIN, RIGHT, ZERO, LEFT, PIN, PIN, ZERO }
    },
    { "RowHouses",
        { RIGHT, PIN, LEFT, PIN, RIGHT, PIN, RIGHT, PIN, LEFT, PIN, LEFT, PIN, RIGHT, PIN, RIGHT, PIN, LEFT, PIN, LEFT, PIN, RIGHT, PIN, LEFT, ZERO }
    },
    { "Sculpture",
        { RIGHT, LEFT, PIN, ZERO, ZERO, ZERO, LEFT, RIGHT, LEFT, PIN, ZERO, ZERO, PIN, LEFT, RIGHT, LEFT, ZERO, ZERO, ZERO, PIN, LEFT, RIGHT, LEFT, ZERO }
    },
    { "Seal",
        { LEFT, LEFT, LEFT, PIN, RIGHT, RIGHT, RIGHT, ZERO, LEFT, PIN, RIGHT, ZERO, LEFT, LEFT, LEFT, PIN, RIGHT, LEFT, ZERO, PIN, PIN, ZERO, LEFT, ZERO }
    },
    { "Seal2",
        { RIGHT, PIN, ZERO, LEFT, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, RIGHT, PIN, RIGHT, RIGHT, PIN, LEFT, RIGHT, ZERO, ZERO, LEFT, RIGHT, ZERO, ZERO, ZERO }
    },
    { "Sheep",
        { RIGHT, LEFT, LEFT, RIGHT, RIGHT, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, LEFT, LEFT, LEFT, RIGHT, LEFT, ZERO }
    },
    { "Shelter",
        { LEFT, RIGHT, LEFT, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, RIGHT, ZERO, ZERO, ZERO, ZERO, PIN, ZERO, ZERO, PIN, ZERO, ZERO, ZERO, ZERO, RIGHT, ZERO }
    },
    { "Ship",
        { PIN, RIGHT, LEFT, LEFT, LEFT, LEFT, PIN, RIGHT, RIGHT, RIGHT, RIGHT, LEFT, ZERO, LEFT, ZERO, RIGHT, PIN, LEFT, ZERO, LEFT, ZERO, PIN, PIN, ZERO }
    },
    { "Shpongle",
        { LEFT, RIGHT, ZERO, RIGHT, LEFT, RIGHT, ZERO, RIGHT, LEFT, RIGHT, ZERO, RIGHT, LEFT, RIGHT, ZERO, RIGHT, LEFT, RIGHT, ZERO, RIGHT, LEFT, RIGHT, ZERO, ZERO }
    },
    { "Slide",
        { LEFT, RIGHT, LEFT, RIGHT, ZERO, LEFT, RIGHT, LEFT, PIN, ZERO, ZERO, PIN, ZERO, ZERO, PIN, RIGHT, LEFT, ZERO, ZERO, RIGHT, LEFT, RIGHT, LEFT, ZERO }
    },
    { "SmallShip",
        { ZERO, LEFT, RIGHT, ZERO, RIGHT, LEFT, ZERO, LEFT, RIGHT, ZERO, LEFT, RIGHT, ZERO, LEFT, RIGHT, ZERO, RIGHT, LEFT, ZERO, LEFT, RIGHT, ZERO, LEFT, ZERO }
    },
    { "SnakeReadyToStrike",
        { LEFT, ZERO, LEFT, ZERO, LEFT, ZERO, LEFT, RIGHT, ZERO, RIGHT, ZERO, RIGHT, ZERO, LEFT, ZERO, ZERO, ZERO, PIN, ZERO, ZERO, ZERO, ZERO, LEFT, ZERO }
    },
    { "Snakes14",
        { RIGHT, RIGHT, PIN, ZERO, RIGHT, LEFT, RIGHT, ZERO, ZERO, ZERO, RIGHT, PIN, LEFT, PIN, ZERO, PIN, LEFT, PIN, RIGHT, ZERO, ZERO, LEFT, RIGHT, ZERO }
    },
    { "Snakes15",
        { ZERO, PIN, PIN, ZERO, PIN, PIN, ZERO, PIN, LEFT, LEFT, PIN, RIGHT, LEFT, ZERO, PIN, LEFT, PIN, RIGHT, ZERO, ZERO, ZERO, PIN, RIGHT, ZERO }
    },
    { "Snakes18",
        { PIN, PIN, LEFT, PIN, LEFT, PIN, RIGHT, ZERO, RIGHT, PIN, RIGHT, ZERO, RIGHT, PIN, LEFT, PIN, RIGHT, ZERO, PIN, PIN, ZERO, ZERO, PIN, ZERO }
    },
    { "Snowflake",
        { LEFT, LEFT, LEFT, RIGHT, RIGHT, RIGHT, RIGHT, LEFT, LEFT, LEFT, LEFT, RIGHT, RIGHT, RIGHT, RIGHT, LEFT, LEFT, LEFT, LEFT, RIGHT, RIGHT, RIGHT, RIGHT, ZERO }
    },
    { "Snowman",
        { ZERO, PIN, PIN, ZERO, PIN, PIN, ZERO, ZERO, ZERO, PIN, PIN, ZERO, PIN, PIN, ZERO, ZERO, ZERO, PIN, PIN, ZERO, PIN, PIN, ZERO, ZERO }
    },
    { "Source",
        { PIN, RIGHT, ZERO, PIN, ZERO, LEFT, PIN, RIGHT, PIN, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, PIN, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, PIN, ZERO }
    },
    { "Spaceship",
        { PIN, PIN, RIGHT, RIGHT, PIN, RIGHT, LEFT, PIN, LEFT, PIN, RIGHT, PIN, RIGHT, LEFT, PIN, LEFT, RIGHT, PIN, RIGHT, PIN, RIGHT, PIN, PIN, ZERO }
    },
    { "Spaceship2",
        { PIN, PIN, LEFT, PIN, LEFT, PIN, RIGHT, ZERO, PIN, PIN, ZERO, LEFT, PIN, RIGHT, ZERO, PIN, ZERO, LEFT, PIN, LEFT, LEFT, PIN, PIN, ZERO }
    },
    { "Speedboat",
        { LEFT, ZERO, ZERO, LEFT, PIN, RIGHT, ZERO, ZERO, LEFT, ZERO, ZERO, PIN, ZERO, ZERO, RIGHT, ZERO, ZERO, LEFT, PIN, RIGHT, ZERO, ZERO, RIGHT, ZERO }
    },
    { "Speedboat2",
        { PIN, RIGHT, LEFT, LEFT, RIGHT, RIGHT, RIGHT, ZERO, LEFT, PIN, RIGHT, ZERO, LEFT, LEFT, LEFT, RIGHT, RIGHT, LEFT, PIN, ZERO, RIGHT, PIN, LEFT, ZERO }
    },
    { "Spider",
        { RIGHT, RIGHT, ZERO, ZERO, LEFT, RIGHT, LEFT, PIN, ZERO, LEFT, ZERO, PIN, PIN, ZERO, RIGHT, ZERO, PIN, RIGHT, LEFT, RIGHT, ZERO, ZERO, LEFT, ZERO }
    },
    { "Spitzbergen",
        { PIN, LEFT, ZERO, RIGHT, RIGHT, LEFT, PIN, ZERO, LEFT, PIN, RIGHT, RIGHT, PIN, LEFT, LEFT, PIN, RIGHT, ZERO, PIN, RIGHT, LEFT, LEFT, ZERO, ZERO }
    },
    { "Square",
        { ZERO, ZERO, LEFT, LEFT, PIN, RIGHT, RIGHT, ZERO, ZERO, LEFT, LEFT, PIN, RIGHT, RIGHT, ZERO, ZERO, LEFT, LEFT, PIN, RIGHT, RIGHT, ZERO, ZERO, ZERO }
    },
    { "SquareHole",
        { PIN, ZERO, PIN, ZERO, ZERO, PIN, PIN, ZERO, PIN, ZERO, ZERO, PIN, ZERO, ZERO, PIN, ZERO, PIN, PIN, ZERO, ZERO, PIN, ZERO, PIN, ZERO }
    },
    { "Stage",
        { RIGHT, ZERO, LEFT, PIN, LEFT, RIGHT, PIN, RIGHT, LEFT, RIGHT, PIN, RIGHT, LEFT, PIN, LEFT, RIGHT, LEFT, PIN, LEFT, RIGHT, PIN, RIGHT, ZERO, ZERO }
    },
    { "Stairs",
        { ZERO, PIN, ZERO, PIN, ZERO, PIN, ZERO, PIN, ZERO, PIN, ZERO, PIN, ZERO, PIN, ZERO, PIN, ZERO, PIN, ZERO, PIN, ZERO, PIN, ZERO, ZERO }
    },
    { "Stairs2",
        { ZERO, PIN, ZERO, PIN, ZERO, PIN, PIN, ZERO, ZERO, PIN, ZERO, PIN, ZERO, PIN, ZERO, PIN, ZERO, PIN, PIN, ZERO, ZERO, PIN, ZERO, ZERO }
    },
    { "Straight",
        { ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO }
    },
    { "Swan",
        { ZERO, PIN, ZERO, PIN, LEFT, LEFT, PIN, LEFT, PIN, RIGHT, PIN, RIGHT, LEFT, PIN, LEFT, RIGHT, PIN, RIGHT, PIN, LEFT, PIN, LEFT, RIGHT, ZERO }
    },
    { "Swan2",
        { PIN, ZERO, PIN, RIGHT, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, RIGHT, PIN, ZERO, ZERO, ZERO, ZERO, ZERO, PIN, PIN, ZERO }
    },
    { "Swan3",
        { PIN, PIN, ZERO, ZERO, ZERO, RIGHT, ZERO, RIGHT, ZERO, ZERO, LEFT, PIN, RIGHT, ZERO, ZERO, RIGHT, PIN, LEFT, ZERO, ZERO, RIGHT, ZERO, RIGHT, ZERO }
    },
    { "Symbol",
        { RIGHT, RIGHT, PIN, ZERO, PIN, PIN, ZERO, PIN, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, PIN, ZERO, PIN, PIN, ZERO, PIN, LEFT, LEFT, RIGHT, ZERO }
    },
    { "Symmetry",
        { RIGHT, ZERO, LEFT, RIGHT, LEFT, ZERO, LEFT, RIGHT, LEFT, ZERO, RIGHT, PIN, LEFT, ZERO, RIGHT, LEFT, RIGHT, ZERO, RIGHT, LEFT, RIGHT, ZERO, LEFT, ZERO }
    },
    { "Symmetry2",
        { ZERO, PIN, LEFT, LEFT, PIN, ZERO, ZERO, LEFT, PIN, RIGHT, PIN, LEFT, LEFT, PIN, RIGHT, RIGHT, PIN, LEFT, LEFT, PIN, RIGHT, PIN, LEFT, ZERO }
    },
    { "TableFireworks",
        { ZERO, RIGHT, LEFT, PIN, RIGHT, RIGHT, RIGHT, PIN, LEFT, RIGHT, ZERO, RIGHT, LEFT, PIN, RIGHT, RIGHT, RIGHT, PIN, RIGHT, LEFT, ZERO, RIGHT, PIN, ZERO }
    },
    { "Tapering",
        { ZERO, ZERO, RIGHT, LEFT, PIN, LEFT, ZERO, PIN, PIN, ZERO, LEFT, PIN, RIGHT, ZERO, PIN, PIN, ZERO, RIGHT, PIN, RIGHT, LEFT, ZERO, ZERO, ZERO }
    },
    { "TaperingTurned",
        { ZERO, ZERO, RIGHT, LEFT, PIN, LEFT, ZERO, PIN, PIN, ZERO, LEFT, ZERO, RIGHT, ZERO, PIN, PIN, ZERO, RIGHT, PIN, RIGHT, LEFT, ZERO, ZERO, ZERO }
    },
    { "TeaLightStick",
        { RIGHT, ZERO, PIN, PIN, ZERO, LEFT, RIGHT, PIN, LEFT, LEFT, RIGHT, RIGHT, PIN, LEFT, LEFT, RIGHT, RIGHT, PIN, LEFT, LEFT, RIGHT, RIGHT, PIN, ZERO }
    },
    { "thighmaster",
        { RIGHT, ZERO, ZERO, RIGHT, LEFT, ZERO, ZERO, RIGHT, LEFT, ZERO, ZERO, LEFT, RIGHT, ZERO, ZERO, RIGHT, LEFT, ZERO, ZERO, RIGHT, LEFT, ZERO, ZERO, ZERO }
    },
    { "Terraces",
        { RIGHT, LEFT, ZERO, RIGHT, LEFT, PIN, LEFT, LEFT, PIN, LEFT, RIGHT, RIGHT, RIGHT, LEFT, LEFT, LEFT, RIGHT, PIN, RIGHT, RIGHT, PIN, RIGHT, LEFT, ZERO }
    },
    { "Terrier",
        { PIN, ZERO, PIN, PIN, ZERO, PIN, ZERO, ZERO, ZERO, PIN, PIN, ZERO, PIN, ZERO, ZERO, PIN, ZERO, PIN, PIN, ZERO, ZERO, ZERO, ZERO, ZERO }
    },
    { "Three-Legged",
        { RIGHT, ZERO, LEFT, RIGHT, ZERO, LEFT, PIN, RIGHT, ZERO, RIGHT, ZERO, PIN, ZERO, LEFT, ZERO, LEFT, PIN, RIGHT, ZERO, LEFT, RIGHT, ZERO, LEFT, ZERO }
    },
    { "ThreePeaks",
        { RIGHT, ZERO, ZERO, RIGHT, PIN, LEFT, PIN, RIGHT, PIN, RIGHT, RIGHT, PIN, LEFT, LEFT, PIN, LEFT, PIN, RIGHT, PIN, LEFT, ZERO, ZERO, LEFT, ZERO }
    },
    { "ToTheFront",
        { ZERO, PIN, RIGHT, LEFT, LEFT, LEFT, PIN, RIGHT, LEFT, ZERO, PIN, PIN, ZERO, LEFT, LEFT, PIN, ZERO, LEFT, RIGHT, ZERO, PIN, ZERO, LEFT, ZERO }
    },
    { "Top",
        { PIN, LEFT, LEFT, PIN, LEFT, ZERO, ZERO, RIGHT, LEFT, PIN, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, PIN, RIGHT, PIN, RIGHT, RIGHT, PIN, ZERO, ZERO }
    },
    { "Transport",
        { PIN, ZERO, ZERO, PIN, PIN, ZERO, PIN, PIN, ZERO, PIN, PIN, ZERO, PIN, PIN, ZERO, PIN, PIN, ZERO, ZERO, PIN, ZERO, ZERO, ZERO, ZERO }
    },
    { "Triangle",
        { ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, RIGHT, LEFT, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, RIGHT, LEFT, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, RIGHT, ZERO }
    },
    { "Tripple",
        { PIN, ZERO, PIN, LEFT, PIN, RIGHT, PIN, RIGHT, PIN, ZERO, PIN, LEFT, PIN, RIGHT, PIN, ZERO, PIN, LEFT, PIN, LEFT, PIN, RIGHT, PIN, ZERO }
    },
#if 0
    { "Turtle",
        { RIGHT, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, LEFT, PIN, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, PIN, LEFT, RIGHT, ZERO, ZERO, LEFT, RIGHT, ZERO, ZERO }
    },
#endif
    { "Twins",
        { ZERO, PIN, ZERO, LEFT, PIN, LEFT, RIGHT, PIN, RIGHT, PIN, ZERO, ZERO, PIN, LEFT, PIN, LEFT, RIGHT, PIN, RIGHT, ZERO, PIN, ZERO, ZERO, ZERO }
    },
    { "TwoSlants",
        { ZERO, PIN, ZERO, ZERO, PIN, PIN, ZERO, PIN, ZERO, RIGHT, PIN, RIGHT, LEFT, PIN, LEFT, PIN, RIGHT, PIN, LEFT, ZERO, ZERO, RIGHT, PIN, ZERO }
    },
    { "TwoWings",
        { PIN, LEFT, ZERO, RIGHT, ZERO, PIN, PIN, ZERO, PIN, PIN, ZERO, PIN, PIN, ZERO, LEFT, ZERO, RIGHT, PIN, LEFT, ZERO, RIGHT, LEFT, ZERO, ZERO }
    },
    { "UFO",
        { LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, LEFT, PIN, LEFT, LEFT, LEFT, RIGHT, LEFT, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, ZERO }
    },
    { "USS Enterprise",
        { LEFT, PIN, RIGHT, PIN, RIGHT, LEFT, ZERO, PIN, PIN, ZERO, RIGHT, LEFT, ZERO, PIN, PIN, ZERO, RIGHT, LEFT, PIN, LEFT, PIN, RIGHT, ZERO, ZERO }
    },
    { "UpAndDown",
        { ZERO, PIN, ZERO, PIN, ZERO, PIN, LEFT, PIN, RIGHT, PIN, ZERO, PIN, ZERO, PIN, ZERO, PIN, ZERO, PIN, LEFT, PIN, RIGHT, PIN, ZERO, ZERO }
    },
    { "Upright",
        { ZERO, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, PIN, ZERO, ZERO, LEFT, PIN, RIGHT, ZERO, ZERO, PIN, RIGHT, RIGHT, LEFT, RIGHT, LEFT, LEFT, ZERO, ZERO }
    },
    { "Upside-down",
        { PIN, ZERO, ZERO, ZERO, PIN, PIN, ZERO, RIGHT, RIGHT, LEFT, LEFT, PIN, RIGHT, RIGHT, LEFT, LEFT, ZERO, PIN, PIN, ZERO, ZERO, ZERO, PIN, ZERO }
    },
    { "Valley",
        { ZERO, RIGHT, PIN, LEFT, PIN, RIGHT, PIN, RIGHT, LEFT, RIGHT, ZERO, PIN, ZERO, LEFT, RIGHT, LEFT, PIN, LEFT, PIN, RIGHT, PIN, LEFT, ZERO, ZERO }
    },
    { "Viaduct",
        { PIN, RIGHT, PIN, LEFT, PIN, ZERO, ZERO, PIN, RIGHT, ZERO, RIGHT, RIGHT, ZERO, RIGHT, PIN, ZERO, ZERO, PIN, LEFT, PIN, RIGHT, PIN, ZERO, ZERO }
    },
    { "View",
        { ZERO, RIGHT, PIN, LEFT, PIN, RIGHT, ZERO, ZERO, RIGHT, PIN, LEFT, LEFT, RIGHT, RIGHT, PIN, LEFT, ZERO, ZERO, LEFT, PIN, RIGHT, PIN, LEFT, ZERO }
    },
    { "Waterfall",
        { LEFT, ZERO, RIGHT, PIN, LEFT, ZERO, RIGHT, PIN, LEFT, ZERO, RIGHT, PIN, LEFT, ZERO, RIGHT, PIN, LEFT, ZERO, RIGHT, PIN, LEFT, ZERO, RIGHT, ZERO }
    },
    { "windwheel",
        { PIN, RIGHT, RIGHT, PIN, ZERO, LEFT, PIN, RIGHT, RIGHT, PIN, ZERO, LEFT, PIN, RIGHT, RIGHT, PIN, ZERO, LEFT, PIN, RIGHT, RIGHT, PIN, ZERO, ZERO }
    },
    { "Window",
        { PIN, ZERO, PIN, PIN, ZERO, ZERO, PIN, ZERO, PIN, ZERO, PIN, ZERO, ZERO, PIN, ZERO, PIN, ZERO, PIN, PIN, ZERO, ZERO, ZERO, ZERO, ZERO }
    },
    { "WindowToTheWorld",
        { PIN, LEFT, ZERO, PIN, ZERO, ZERO, PIN, ZERO, ZERO, PIN, ZERO, RIGHT, PIN, LEFT, ZERO, PIN, ZERO, ZERO, PIN, ZERO, ZERO, PIN, ZERO, ZERO }
    },
    { "Windshield",
        { PIN, PIN, ZERO, RIGHT, PIN, LEFT, LEFT, PIN, RIGHT, ZERO, PIN, ZERO, LEFT, PIN, RIGHT, RIGHT, PIN, LEFT, ZERO, PIN, PIN, ZERO, PIN, ZERO }
    },
    { "WingNut",
        { ZERO, ZERO, ZERO, ZERO, PIN, RIGHT, RIGHT, RIGHT, PIN, RIGHT, LEFT, PIN, LEFT, RIGHT, PIN, RIGHT, RIGHT, RIGHT, PIN, ZERO, ZERO, ZERO, ZERO, ZERO }
    },
    { "Wings2",
        { RIGHT, ZERO, PIN, ZERO, LEFT, PIN, RIGHT, PIN, RIGHT, LEFT, RIGHT, RIGHT, LEFT, LEFT, RIGHT, LEFT, PIN, LEFT, PIN, RIGHT, ZERO, PIN, ZERO, ZERO }
    },
    { "WithoutName",
        { PIN, RIGHT, PIN, RIGHT, RIGHT, PIN, LEFT, LEFT, PIN, ZERO, PIN, RIGHT, PIN, LEFT, PIN, ZERO, PIN, RIGHT, RIGHT, PIN, LEFT, LEFT, PIN, ZERO }
    },
    { "Wolf",
        { ZERO, ZERO, PIN, PIN, ZERO, PIN, ZERO, ZERO, PIN, ZERO, PIN, PIN, ZERO, PIN, ZERO, ZERO, ZERO, PIN, PIN, ZERO, ZERO, ZERO, PIN, ZERO }
    },
    { "X",
        { LEFT, ZERO, ZERO, PIN, LEFT, RIGHT, RIGHT, PIN, LEFT, RIGHT, ZERO, PIN, PIN, ZERO, LEFT, RIGHT, PIN, LEFT, LEFT, RIGHT, PIN, ZERO, ZERO, ZERO }
    },
};

static size_t models = sizeof(model) / sizeof(struct model_s);

#define VOFFSET 0.045

#define X_MASK	1
#define Y_MASK	2
#define Z_MASK	4

/* the connecting string that holds the snake together */
#define MAGICAL_RED_STRING 0

#define GETSCALAR(vec,mask) ((vec)==(mask) ? 1 : ((vec)==-(mask) ? -1 : 0 ))

#ifndef MAX
# define MAX(x, y) ((x) > (y) ? (x) : (y))
#endif
#ifndef MIN
# define MIN(x, y) ((x) < (y) ? (x) : (y))
#endif

#define RAND(n) ((random() & 0x7fffffff) % ((long) (n)))
#define RANDSIGN() ((random() & 1) ? 1 : -1)

/* the triangular prism what makes up the basic unit */
static const float solid_prism_v[][3] = {
    /* first corner, bottom left front */
    { VOFFSET, VOFFSET, 1.0 },
    { VOFFSET, 0.00, 1.0 - VOFFSET },
    { 0.00, VOFFSET, 1.0 - VOFFSET },
    /* second corner, rear */
    { VOFFSET, VOFFSET, 0.00 },
    { VOFFSET, 0.00, VOFFSET },
    { 0.00, VOFFSET, VOFFSET },
    /* third, right front */
    { 1.0 - VOFFSET / M_SQRT1_2, VOFFSET, 1.0 },
    { 1.0 - VOFFSET / M_SQRT1_2, 0.0, 1.0 - VOFFSET },
    { 1.0 - VOFFSET * M_SQRT1_2, VOFFSET, 1.0 - VOFFSET },
    /* fourth, right rear */
    { 1.0 - VOFFSET / M_SQRT1_2, VOFFSET, 0.0 },
    { 1.0 - VOFFSET / M_SQRT1_2, 0.0, VOFFSET },
    { 1.0 - VOFFSET * M_SQRT1_2, VOFFSET, VOFFSET },
    /* fifth, upper front */
    { VOFFSET, 1.0 - VOFFSET / M_SQRT1_2, 1.0 },
    { VOFFSET / M_SQRT1_2, 1.0 - VOFFSET * M_SQRT1_2, 1.0 - VOFFSET },
    { 0.0, 1.0 - VOFFSET / M_SQRT1_2, 1.0 - VOFFSET},
    /* sixth, upper rear */
    { VOFFSET, 1.0 - VOFFSET / M_SQRT1_2, 0.0 },
    { VOFFSET / M_SQRT1_2, 1.0 - VOFFSET * M_SQRT1_2, VOFFSET },
    { 0.0, 1.0 - VOFFSET / M_SQRT1_2, VOFFSET }};

static const float solid_prism_n[][3] = {/* corners */
    { -VOFFSET, -VOFFSET, VOFFSET },
    { VOFFSET, -VOFFSET, VOFFSET },
    { -VOFFSET, VOFFSET, VOFFSET },
    { -VOFFSET, -VOFFSET, -VOFFSET },
    { VOFFSET, -VOFFSET, -VOFFSET },
    { -VOFFSET, VOFFSET, -VOFFSET },
    /* edges */
    { -VOFFSET, 0.0, VOFFSET },
    { 0.0, -VOFFSET, VOFFSET },
    { VOFFSET, VOFFSET, VOFFSET },
    { -VOFFSET, 0.0, -VOFFSET },
    { 0.0, -VOFFSET, -VOFFSET },
    { VOFFSET, VOFFSET, -VOFFSET },
    { -VOFFSET, -VOFFSET, 0.0 },
    { VOFFSET, -VOFFSET, 0.0 },
    { -VOFFSET, VOFFSET, 0.0 },
    /* faces */
    { 0.0, 0.0, 1.0 },
    { 0.0, -1.0, 0.0 },
    { M_SQRT1_2, M_SQRT1_2, 0.0 },
    { -1.0, 0.0, 0.0 },
    { 0.0, 0.0, -1.0 }};

static const float wire_prism_v[][3] = {{ 0.0, 0.0, 1.0 },
			   { 1.0, 0.0, 1.0 },
			   { 0.0, 1.0, 1.0 },
			   { 0.0, 0.0, 0.0 },
			   { 1.0, 0.0, 0.0 },
			   { 0.0, 1.0, 0.0 }};

/*
static const float wire_prism_n[][3] = {{ 0.0, 0.0, 1.0},
			   { 0.0,-1.0, 0.0},
			   { M_SQRT1_2, M_SQRT1_2, 0.0},
			   {-1.0, 0.0, 0.0},
			   { 0.0, 0.0,-1.0}};
*/

static struct glsnake_cfg * glc = NULL;
#ifdef HAVE_GLUT
# define bp glc
#endif

typedef float (*morphFunc)(long);

#ifdef HAVE_GLUT
/* forward definitions for GLUT functions */
static void calc_rotation();
static inline void ui_mousedrag();
#endif

static const GLfloat white_light[] = { 1.0, 1.0, 1.0, 1.0 };
static const GLfloat lmodel_ambient[] = { 0.2, 0.2, 0.2, 1.0 };
static const GLfloat mat_specular[] = { 0.1, 0.1, 0.1, 1.0 };
static const GLfloat mat_shininess[] = { 20.0 };

static void gl_init(
#ifndef HAVE_GLUT
	     ModeInfo * mi
#endif
	     ) 
{
    float light_pos[][3] = {{0.0, 10.0, 20.0}, {0.0, 20.0, -1.0}};
    float light_dir[][3] = {{0.0, -10.0,-20.0}, {0.0,-20.0, 1.0}};

    glEnable(GL_DEPTH_TEST);
    glShadeModel(GL_SMOOTH);
    glCullFace(GL_BACK);
    /*glEnable(GL_CULL_FACE);*/
    glEnable(GL_NORMALIZE);
    if (transparent) {
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_BLEND);
    }

    if (!wireframe) {
	/*glColor4f(1.0, 1.0, 1.0, 1.0);*/
	glLightfv(GL_LIGHT0, GL_POSITION, light_pos[0]);
	glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, light_dir[0]);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, white_light);
	/*glLightfv(GL_LIGHT0, GL_SPECULAR, white_light);*/
#if 1
	glLightfv(GL_LIGHT1, GL_POSITION, light_pos[1]);
	glLightfv(GL_LIGHT1, GL_SPOT_DIRECTION, light_dir[1]);
	glLightfv(GL_LIGHT1, GL_DIFFUSE, white_light);
	glLightfv(GL_LIGHT1, GL_SPECULAR, white_light);
#endif
	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
	glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
	glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_LIGHT1);
	/*glEnable(GL_COLOR_MATERIAL);*/
    }
}

static void gettime(snaketime *t)
{
#ifdef HAVE_GETTIMEOFDAY
#ifdef GETTIMEOFDAY_TWO_ARGS
	struct timezone tzp;
	gettimeofday(t, &tzp);
#else /* !GETTIMEOFDAY_TWO_ARGS */
	gettimeofday(t);
#endif /* !GETTIMEOFDAY_TWO_ARGS */
#else /* !HAVE_GETTIMEOFDAY */
#ifdef HAVE_FTIME
	ftime(t);
#endif /* HAVE_FTIME */
#endif /* !HAVE_GETTIMEOFDAY */
}


ENTRYPOINT void glsnake_reshape(
#ifndef HAVE_GLUT
		     ModeInfo * mi,
#endif
		     int w, int h);

static void start_morph(struct glsnake_cfg *,
                        unsigned int model_index, int immediate);

/* wot initialises it */
ENTRYPOINT void glsnake_init(
#ifndef HAVE_GLUT
ModeInfo * mi
#endif
) 
{
#ifndef HAVE_GLUT
    struct glsnake_cfg * bp;

    /* set up the conf struct and glx contexts */
    if (!glc) {
	glc = (struct glsnake_cfg *) calloc(MI_NUM_SCREENS(mi), sizeof(struct glsnake_cfg));
	if (!glc) {
	    fprintf(stderr, "%s: out of memory\n", progname);
	    exit(1);
	}
    }
    bp = &glc[MI_SCREEN(mi)];

    if ((bp->glx_context = init_GL(mi)) != NULL) {
	gl_init(mi);
	glsnake_reshape(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
    }
#else
    gl_init();
#endif

    /* initialise conf struct */
    memset(&bp->node, 0, sizeof(float) * NODE_COUNT);

    bp->selected = 11;
    bp->is_cyclic = 0;
    bp->is_legal = 1;
    bp->last_turn = -1;
    bp->morphing = 0;
    bp->paused = 0;
    bp->new_morph = 0;

    gettime(&bp->last_iteration);
    memcpy(&bp->last_morph, &bp->last_iteration, sizeof(bp->last_morph));

    bp->prev_colour = bp->next_colour = COLOUR_ACYCLIC;
    bp->next_model = RAND(models);
    bp->prev_model = START_MODEL;
    start_morph(bp, bp->prev_model, 1);

    /* set up a font for the labels */
#ifndef HAVE_GLUT
    if (titles)
	load_font(mi->dpy, "labelfont", &bp->font, &bp->font_list);
#endif
    
    /* build a solid display list */
    bp->node_solid = glGenLists(1);
    glNewList(bp->node_solid, GL_COMPILE);
    /* corners */
    glBegin(GL_TRIANGLES);
    glNormal3fv(solid_prism_n[0]);
    glVertex3fv(solid_prism_v[0]);
    glVertex3fv(solid_prism_v[2]);
    glVertex3fv(solid_prism_v[1]);
    
    glNormal3fv(solid_prism_n[1]);
    glVertex3fv(solid_prism_v[6]);
    glVertex3fv(solid_prism_v[7]);
    glVertex3fv(solid_prism_v[8]);
    
    glNormal3fv(solid_prism_n[2]);
    glVertex3fv(solid_prism_v[12]);
    glVertex3fv(solid_prism_v[13]);
    glVertex3fv(solid_prism_v[14]);
    
    glNormal3fv(solid_prism_n[3]);
    glVertex3fv(solid_prism_v[3]);
    glVertex3fv(solid_prism_v[4]);
    glVertex3fv(solid_prism_v[5]);
    
    glNormal3fv(solid_prism_n[4]);
    glVertex3fv(solid_prism_v[9]);
    glVertex3fv(solid_prism_v[11]);
    glVertex3fv(solid_prism_v[10]);
    
    glNormal3fv(solid_prism_n[5]);
    glVertex3fv(solid_prism_v[16]);
    glVertex3fv(solid_prism_v[15]);
    glVertex3fv(solid_prism_v[17]);
    glEnd();
    /* edges */
    glBegin(GL_QUADS);
    glNormal3fv(solid_prism_n[6]);
    glVertex3fv(solid_prism_v[0]);
    glVertex3fv(solid_prism_v[12]);
    glVertex3fv(solid_prism_v[14]);
    glVertex3fv(solid_prism_v[2]);
    
    glNormal3fv(solid_prism_n[7]);
    glVertex3fv(solid_prism_v[0]);
    glVertex3fv(solid_prism_v[1]);
    glVertex3fv(solid_prism_v[7]);
    glVertex3fv(solid_prism_v[6]);
    
    glNormal3fv(solid_prism_n[8]);
    glVertex3fv(solid_prism_v[6]);
    glVertex3fv(solid_prism_v[8]);
    glVertex3fv(solid_prism_v[13]);
    glVertex3fv(solid_prism_v[12]);
    
    glNormal3fv(solid_prism_n[9]);
    glVertex3fv(solid_prism_v[3]);
    glVertex3fv(solid_prism_v[5]);
    glVertex3fv(solid_prism_v[17]);
    glVertex3fv(solid_prism_v[15]);
    
    glNormal3fv(solid_prism_n[10]);
    glVertex3fv(solid_prism_v[3]);
    glVertex3fv(solid_prism_v[9]);
    glVertex3fv(solid_prism_v[10]);
    glVertex3fv(solid_prism_v[4]);
    
    glNormal3fv(solid_prism_n[11]);
    glVertex3fv(solid_prism_v[15]);
    glVertex3fv(solid_prism_v[16]);
    glVertex3fv(solid_prism_v[11]);
    glVertex3fv(solid_prism_v[9]);
    
    glNormal3fv(solid_prism_n[12]);
    glVertex3fv(solid_prism_v[1]);
    glVertex3fv(solid_prism_v[2]);
    glVertex3fv(solid_prism_v[5]);
    glVertex3fv(solid_prism_v[4]);
    
    glNormal3fv(solid_prism_n[13]);
    glVertex3fv(solid_prism_v[8]);
    glVertex3fv(solid_prism_v[7]);
    glVertex3fv(solid_prism_v[10]);
    glVertex3fv(solid_prism_v[11]);
    
    glNormal3fv(solid_prism_n[14]);
    glVertex3fv(solid_prism_v[13]);
    glVertex3fv(solid_prism_v[16]);
    glVertex3fv(solid_prism_v[17]);
    glVertex3fv(solid_prism_v[14]);
    glEnd();
    
    /* faces */
    glBegin(GL_TRIANGLES);
    glNormal3fv(solid_prism_n[15]);
    glVertex3fv(solid_prism_v[0]);
    glVertex3fv(solid_prism_v[6]);
    glVertex3fv(solid_prism_v[12]);
    
    glNormal3fv(solid_prism_n[19]);
    glVertex3fv(solid_prism_v[3]);
    glVertex3fv(solid_prism_v[15]);
    glVertex3fv(solid_prism_v[9]);
    glEnd();
    
    glBegin(GL_QUADS);
    glNormal3fv(solid_prism_n[16]);
    glVertex3fv(solid_prism_v[1]);
    glVertex3fv(solid_prism_v[4]);
    glVertex3fv(solid_prism_v[10]);
    glVertex3fv(solid_prism_v[7]);
    
    glNormal3fv(solid_prism_n[17]);
    glVertex3fv(solid_prism_v[8]);
    glVertex3fv(solid_prism_v[11]);
    glVertex3fv(solid_prism_v[16]);
    glVertex3fv(solid_prism_v[13]);
    
    glNormal3fv(solid_prism_n[18]);
    glVertex3fv(solid_prism_v[2]);
    glVertex3fv(solid_prism_v[14]);
    glVertex3fv(solid_prism_v[17]);
    glVertex3fv(solid_prism_v[5]);
    glEnd();
    glEndList();
    
    /* build wire display list */
    bp->node_wire = glGenLists(1);
    glNewList(bp->node_wire, GL_COMPILE);
    glBegin(GL_LINE_STRIP);
    glVertex3fv(wire_prism_v[0]);
    glVertex3fv(wire_prism_v[1]);
    glVertex3fv(wire_prism_v[2]);
    glVertex3fv(wire_prism_v[0]);
    glVertex3fv(wire_prism_v[3]);
    glVertex3fv(wire_prism_v[4]);
    glVertex3fv(wire_prism_v[5]);
    glVertex3fv(wire_prism_v[3]);
    glEnd();
    glBegin(GL_LINES);
    glVertex3fv(wire_prism_v[1]);
    glVertex3fv(wire_prism_v[4]);
    glVertex3fv(wire_prism_v[2]);
    glVertex3fv(wire_prism_v[5]);
    glEnd();
    glEndList();
    
#ifdef HAVE_GLUT
    /* initialise the rotation */
    calc_rotation();
#endif
}

static void draw_title(
#ifndef HAVE_GLUT
		ModeInfo * mi
#endif
		) 
{
#ifndef HAVE_GLUT
    struct glsnake_cfg * bp = &glc[MI_SCREEN(mi)];
#endif

    /* draw some text */
    glPushAttrib((GLbitfield) GL_TRANSFORM_BIT | GL_ENABLE_BIT);
    glDisable(GL_LIGHTING);
    glDisable(GL_DEPTH_TEST);
    if (transparent) {
	glDisable(GL_BLEND);
    }
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
#ifdef HAVE_GLUT
    gluOrtho2D((GLdouble) 0., (GLdouble) bp->width, (GLdouble) 0., (GLdouble) bp->height);
#else
    gluOrtho2D((GLdouble) 0., (GLdouble) mi->xgwa.width, (GLdouble) 0., (GLdouble) mi->xgwa.height);
#endif

    glColor4f(1.0, 1.0, 1.0, 1.0);
    {
	char interactstr[] = "interactive";
	const char * s;
#ifdef HAVE_GLUT
	int w;
#endif
	
	if (interactive)
	    s = interactstr;
	else
	    s = model[bp->next_model].name;
	
#ifdef HAVE_GLUT
	{
	    unsigned int i = 0;
	    
	    w = glutBitmapLength(GLUT_BITMAP_HELVETICA_12, (const unsigned char *) s);
	    glRasterPos2f((GLfloat) (bp->width - w - 3), 4.0);
	    while (s[i] != '\0')
		glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12, s[i++]);
	}
#else
	print_gl_string(mi->dpy, bp->font, bp->font_list,
			mi->xgwa.width, mi->xgwa.height,
			10.0, (float) mi->xgwa.height - 10.0,
			s);
#endif
    }
    glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glPopAttrib();
}

/* apply the matrix to the origin and stick it in vec */
static void matmult_origin(float rotmat[16], float vec[4]) 
{
#if 1
    vec[0] = 0.5 * rotmat[0] + 0.5 * rotmat[4] + 0.5 * rotmat [8] + 1 * rotmat[12];
    vec[1] = 0.5 * rotmat[1] + 0.5 * rotmat[5] + 0.5 * rotmat [9] + 1 * rotmat[13];
    vec[2] = 0.5 * rotmat[2] + 0.5 * rotmat[6] + 0.5 * rotmat[10] + 1 * rotmat[14];
    vec[3] = 0.5 * rotmat[3] + 0.5 * rotmat[7] + 0.5 * rotmat[11] + 1 * rotmat[15];
#else
    vec[0] = 0 * rotmat [0] + 0 * rotmat [1] + 0 * rotmat [2] + 1 * rotmat [3];
    vec[1] = 0 * rotmat [4] + 0 * rotmat [5] + 0 * rotmat [6] + 1 * rotmat [7];
    vec[2] = 0 * rotmat [8] + 0 * rotmat [9] + 0 * rotmat[10] + 1 * rotmat[11];
    vec[3] = 0 * rotmat[12] + 0 * rotmat[13] + 0 * rotmat[14] + 1 * rotmat[15];
#endif
    vec[0] /= vec[3];
    vec[1] /= vec[3];
    vec[2] /= vec[3];
    vec[3] = 1.0;
}

/* wot gets called when the winder is resized */
ENTRYPOINT void glsnake_reshape(
#ifndef HAVE_GLUT
		     ModeInfo * mi,
#endif
		     int w, int h) 
{
    glViewport(0, 0, (GLint) w, (GLint) h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    /* jwz: 0.05 was too close (left black rectangles) */
    gluPerspective(zoom, (GLdouble) w / (GLdouble) h, 1.0, 100.0);
    gluLookAt(0.0, 0.0, 20.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
    glMatrixMode(GL_MODELVIEW);
    /*gluLookAt(0.0, 0.0, 20.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);*/
    glLoadIdentity();
#ifdef HAVE_GLUT
    bp->width = w;
    bp->height = h;
#endif
}

/* Returns the new dst_dir for the given src_dir and dst_dir */
static int cross_product(int src_dir, int dst_dir) 
{
    return X_MASK*(GETSCALAR(src_dir,Y_MASK) * GETSCALAR(dst_dir,Z_MASK) -
		   GETSCALAR(src_dir,Z_MASK) * GETSCALAR(dst_dir,Y_MASK))+ 
	Y_MASK*(GETSCALAR(src_dir,Z_MASK) * GETSCALAR(dst_dir,X_MASK) -
		GETSCALAR(src_dir,X_MASK) * GETSCALAR(dst_dir,Z_MASK))+ 
	Z_MASK*(GETSCALAR(src_dir,X_MASK) * GETSCALAR(dst_dir,Y_MASK) -
		GETSCALAR(src_dir,Y_MASK) * GETSCALAR(dst_dir,X_MASK));
}

/* calculate orthogonal snake metrics
 *  is_legal  = true if model does not pass through itself
 *  is_cyclic = true if last node connects back to first node
 *  last_turn = for cyclic snakes, specifes what the 24th turn would be
 */
static void calc_snake_metrics(struct glsnake_cfg *bp) 
{
    int srcDir, dstDir;
    int i, x, y, z;
    int prevSrcDir = -Y_MASK;
    int prevDstDir = Z_MASK;
    int grid[25][25][25];
    
    /* zero the grid */
    memset(&grid, 0, sizeof(int) * 25*25*25);
    
    bp->is_legal = 1;
    x = y = z = 12;
    
    /* trace path of snake - and keep record for is_legal */
    for (i = 0; i < NODE_COUNT - 1; i++) {
	/*int ang_card;*/ /* cardinal direction of node angle */
	/* establish new state vars */
	srcDir = -prevDstDir;
	x += GETSCALAR(prevDstDir, X_MASK);
	y += GETSCALAR(prevDstDir, Y_MASK);
	z += GETSCALAR(prevDstDir, Z_MASK);

	switch ((int) model[bp->next_model].node[i]) {
	  case (int) (ZERO):
	    dstDir = -prevSrcDir;
	    break;
	  case (int) (PIN):
	    dstDir = prevSrcDir;
	    break;
	  case (int) (RIGHT):
	  case (int) (LEFT):
	    dstDir = cross_product(prevSrcDir, prevDstDir);
	    if (model[bp->next_model].node[i] == (int) (RIGHT))
		dstDir = -dstDir;
	    break;
	  default:
	    /* Prevent spurious "might be used 
	     * uninitialised" warnings when compiling
	     * with -O2 */
	    dstDir = 0;
	    break;
	}
	
	if (grid[x][y][z] == 0)
	    grid[x][y][z] = srcDir + dstDir;
	else if (grid[x][y][z] + srcDir + dstDir == 0)
	    grid[x][y][z] = 8;
	else
	    bp->is_legal = 0;
	
	prevSrcDir = srcDir;
	prevDstDir = dstDir;
    }	
    
    /* determine if the snake is cyclic */
    bp->is_cyclic = (dstDir == Y_MASK && x == 12 && y == 11 && z == 12);
    
    /* determine last_turn */
    bp->last_turn = -1;
    if (bp->is_cyclic)
	switch (srcDir) {
	  case -Z_MASK: bp->last_turn = ZERO; break;
	  case Z_MASK:  bp->last_turn = PIN; break;
	  case X_MASK:  bp->last_turn = LEFT; break;
	  case -X_MASK: bp->last_turn = RIGHT; break;
	}
}

/* work out how far through the current morph we are */
static float morph_percent(struct glsnake_cfg *bp) 
{
    float retval;
    int i;

    /* extend this function later with a case statement for each of the
     * morph schemes */

    /* when morphing all nodes at once, the longest morph will be the node
     * that needs to rotate 180 degrees.  For each node, work out how far it
     * has to go, and store the maximum rotation and current largest angular
     * difference, returning the angular difference over the maximum. */
    {
	float rot_max = 0.0, ang_diff_max = 0.0;

	for (i = 0; i < NODE_COUNT - 1; i++) {
	    float rot, ang_diff;

	    /* work out the maximum rotation this node has to go through
	     * from the previous to the next model, taking into account that
	     * the snake always morphs through the smaller angle */
	    rot = fabs(model[bp->prev_model].node[i] -
		       model[bp->next_model].node[i]);
	    if (rot > 180.0) rot = 180.0 - rot;
	    /* work out the difference between the current position and the
	     * target */
	    ang_diff = fabs(bp->node[i] -
			    model[bp->next_model].node[i]);
	    if (ang_diff > 180.0) ang_diff = 180.0 - ang_diff;
	    /* if it's the biggest so far, record it */
	    if (rot > rot_max) rot_max = rot;
	    if (ang_diff > ang_diff_max) ang_diff_max = ang_diff;
	}
	
	/* ang_diff / rot approaches 0, we want the complement */
	retval = 1.0 - (ang_diff_max / rot_max);
	/* protect against naan */

/* Apparently some systems (Solaris) don't have isinf() */
#undef isinf
#define isinf(x) (((x) > 999999999999.9) || ((x) < -999999999999.9))

	if (isnan(retval) || isinf(retval)) retval = 1.0;
    }
    /*printf("morph_pct = %f\n", retval);*/
    return retval;
}

static void morph_colour(struct glsnake_cfg *bp) 
{
    float percent, compct; /* complement of percentage */

    percent = morph_percent(bp);
    compct = 1.0 - percent;

    bp->colour[0][0] = colour[bp->prev_colour][0][0] * compct + colour[bp->next_colour][0][0] * percent;
    bp->colour[0][1] = colour[bp->prev_colour][0][1] * compct + colour[bp->next_colour][0][1] * percent;
    bp->colour[0][2] = colour[bp->prev_colour][0][2] * compct + colour[bp->next_colour][0][2] * percent;
    bp->colour[0][3] = colour[bp->prev_colour][0][3] * compct + colour[bp->next_colour][0][3] * percent;

    bp->colour[1][0] = colour[bp->prev_colour][1][0] * compct + colour[bp->next_colour][1][0] * percent;
    bp->colour[1][1] = colour[bp->prev_colour][1][1] * compct + colour[bp->next_colour][1][1] * percent;
    bp->colour[1][2] = colour[bp->prev_colour][1][2] * compct + colour[bp->next_colour][1][2] * percent;
    bp->colour[1][3] = colour[bp->prev_colour][1][3] * compct + colour[bp->next_colour][1][3] * percent;
}

/* Start morph process to this model */
static void start_morph(struct glsnake_cfg *bp, 
                        unsigned int model_index, int immediate)
{
    /* if immediate, don't bother morphing, go straight to the next model */
    if (immediate) {
	int i;

	for (i = 0; i < NODE_COUNT; i++)
	    bp->node[i] = model[model_index].node[i];
    }

    bp->prev_model = bp->next_model;
    bp->next_model = model_index;
    bp->prev_colour = bp->next_colour;

    calc_snake_metrics(bp);
    if (!bp->is_legal)
	bp->next_colour = COLOUR_INVALID;
    else if (altcolour)
	bp->next_colour = COLOUR_AUTHENTIC;
    else if (bp->is_cyclic)
	bp->next_colour = COLOUR_CYCLIC;
    else
	bp->next_colour = COLOUR_ACYCLIC;

    if (immediate) {
	bp->colour[0][0] = colour[bp->next_colour][0][0];
	bp->colour[0][1] = colour[bp->next_colour][0][1];
	bp->colour[0][2] = colour[bp->next_colour][0][2];
	bp->colour[0][3] = colour[bp->next_colour][0][3];
	bp->colour[1][0] = colour[bp->next_colour][1][0];
	bp->colour[1][1] = colour[bp->next_colour][1][1];
	bp->colour[1][2] = colour[bp->next_colour][1][2];
	bp->colour[1][3] = colour[bp->next_colour][1][3];
    }
    bp->morphing = 1;

    morph_colour(bp);
}

#if 0
/* Returns morph progress */
static float morph(long iter_msec) 
{
    /* work out the maximum angle for this iteration */
    int still_morphing;
    float iter_angle_max, largest_diff, largest_progress;
    int i;

    if (bp->new_morph)
	bp->new_morph = 0;
	
    iter_angle_max = 90.0 * (angvel/1000.0) * iter_msec;
	
    still_morphing = 0;
    largest_diff = largest_progress = 0.0;
    for (i = 0; i < NODE_COUNT; i++) {
	float curAngle = bp->node[i];
	float destAngle = model[bp->next_model].node[i];
	if (curAngle != destAngle) {
	    still_morphing = 1;
	    if (fabs(curAngle-destAngle) <= iter_angle_max)
		bp->node[i] = destAngle;
	    else if (fmod(curAngle-destAngle+360,360) > 180)
		bp->node[i] = fmod(curAngle + iter_angle_max, 360);
	    else
		bp->node[i] = fmod(curAngle+360 - iter_angle_max, 360);
	    largest_diff = MAX(largest_diff, fabs(destAngle-bp->node[i]));
	    largest_progress = MAX(largest_diff, fabs(bp->node[i] - model[bp->prev_model].node[i]));
	}
    }
	
    return MIN(largest_diff / largest_progress, 1.0);
}
#endif


#ifdef HAVE_GLUT
static void glsnake_idle();

static restore_idle(int v __attribute__((__unused__)))
{
    glutIdleFunc(glsnake_idle);
}
#endif

static void quick_sleep(void)
{
#ifdef HAVE_GLUT
    /* By using glutTimerFunc we can keep responding to 
     * mouse and keyboard events, unlike using something like
     * usleep. */
    glutIdleFunc(NULL);
    glutTimerFunc(1, restore_idle, 0);
#else
    usleep(1);
#endif
}

static void glsnake_idle(
#ifndef HAVE_GLUT
		  struct glsnake_cfg * bp
#endif
		  ) 
{
    /* time since last iteration */
    long iter_msec;
    /* time since the beginning of last morph */
    long morf_msec;
    float iter_angle_max;
    snaketime current_time;
    /*    morphFunc transition; */
    int still_morphing;
    int i;
    
    /* Do nothing to the model if we are paused */
    if (bp->paused) {
	/* Avoid busy waiting when nothing is changing */
	quick_sleep();
#ifdef HAVE_GLUT
	glutSwapBuffers();
	glutPostRedisplay();
#endif
	return;
    }

    /* <spiv> Well, ftime gives time with millisecond resolution.
     * <spiv> (or worse, perhaps... who knows what the OS will do)
     * <spiv> So if no discernable amount of time has passed:
     * <spiv>   a) There's no point updating the screen, because
     *             it would be the same
     * <spiv>   b) The code will divide by zero
     */
    gettime(&current_time);
    
    iter_msec = (long) GETMSECS(current_time) - GETMSECS(bp->last_iteration) + 
	((long) GETSECS(current_time) - GETSECS(bp->last_iteration)) * 1000L;

    if (iter_msec) {
	/* save the current time */
	memcpy(&bp->last_iteration, &current_time, sizeof(snaketime));
	
	/* work out if we have to switch models */
	morf_msec = GETMSECS(bp->last_iteration) - GETMSECS(bp->last_morph) +
	    ((long) (GETSECS(bp->last_iteration)-GETSECS(bp->last_morph)) * 1000L);

	if ((morf_msec > statictime) && !interactive && !bp->morphing) {
	    /*printf("starting morph\n");*/
	    memcpy(&bp->last_morph, &(bp->last_iteration), sizeof(bp->last_morph));
	    start_morph(bp, RAND(models), 0);
	}
	
	if (interactive && !bp->morphing) {
	    quick_sleep();
	    return;
	}
	
	/*	if (!bp->dragging && !bp->interactive) { */
	if (!interactive) {
	    
	    yspin += 360/((1000/yangvel)/iter_msec);
	    zspin += 360/((1000/zangvel)/iter_msec);
	    /*
	    yspin += 360 * (yangvel/1000.0) * iter_msec;
	    zspin += 360 * (zangvel/1000.0) * iter_msec;
	    */
	    
	    /*printf("yspin: %f, zspin: %f\n", yspin, zspin);*/

	}

	/* work out the maximum angle we could turn this node in this
	 * timeslice, iter_msec milliseconds long */
	iter_angle_max = 90.0 * (angvel/1000.0) * iter_msec;

	still_morphing = 0;
	for (i = 0; i < NODE_COUNT; i++) {
	    float cur_angle = bp->node[i];
	    float dest_angle = model[bp->next_model].node[i];
	    if (cur_angle != dest_angle) {
		still_morphing = 1;
		if (fabs(cur_angle - dest_angle) <= iter_angle_max)
		    bp->node[i] = dest_angle;
		else if (fmod(cur_angle - dest_angle + 360, 360) > 180)
		    bp->node[i] = fmod(cur_angle + iter_angle_max, 360);
		else
		    bp->node[i] = fmod(cur_angle + 360 - iter_angle_max, 360);
	    }
	}

	if (!still_morphing)
	    bp->morphing = 0;

	/* colour cycling */
	morph_colour(bp);
	
#ifdef HAVE_GLUT
	glutSwapBuffers();
	glutPostRedisplay();
#endif
    } else {
	/* We are going too fast, so we may as well let the 
	 * cpu relax a little by sleeping for a millisecond. */
	quick_sleep();
    }
}

/* wot draws it */
ENTRYPOINT void glsnake_display(
#ifndef HAVE_GLUT
		     ModeInfo * mi
#endif
		     ) 
{
#ifndef HAVE_GLUT
    struct glsnake_cfg * bp = &glc[MI_SCREEN(mi)];
    Display * dpy = MI_DISPLAY(mi);
    Window window = MI_WINDOW(mi);
#endif

    int i;
    float ang;
    float positions[NODE_COUNT][4]; /* origin points for each node */
    float com[4]; /* it's the CENTRE of MASS */

#ifndef HAVE_GLUT
    if (!bp->glx_context)
	return;

    glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
#endif
    
    /* clear the buffer */
    glClear((GLbitfield) GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    /* go into the modelview stack */
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    
    /* get the centre of each node, by moving through the snake and
     * performing the rotations, then grabbing the matrix at each point
     * and applying it to the origin */
    glPushMatrix();

#ifdef HAVE_GLUT
    /* apply the mouse drag rotation */
    ui_mousedrag();
#endif
    
    /* apply the continuous rotation */
    glRotatef(yspin, 0.0, 1.0, 0.0); 
    glRotatef(zspin, 0.0, 0.0, 1.0); 
    
    com[0] = 0.0;
    com[1] = 0.0;
    com[2] = 0.0;
    com[3] = 0.0;
    for (i = 0; i < NODE_COUNT; i++) {
	float rotmat[16];

	ang = bp->node[i];

	/*printf("ang = %f\n", ang);*/
	
	glTranslatef(0.5, 0.5, 0.5);		/* move to center */
	glRotatef(90.0, 0.0, 0.0, -1.0);	/* reorient  */
	glTranslatef(1.0 + explode, 0.0, 0.0);	/* move to new pos. */
	glRotatef(180.0 + ang, 1.0, 0.0, 0.0);	/* pivot to new angle */
	glTranslatef(-0.5, -0.5, -0.5);		/* return from center */

	glGetFloatv(GL_MODELVIEW_MATRIX, rotmat);

	matmult_origin(rotmat, positions[i]);

	/*printf("positions %f %f %f %f\n", positions[i][0], positions[i][1], positions[i][2], positions[i][3]);*/

	com[0] += positions[i][0];
	com[1] += positions[i][1];
	com[2] += positions[i][2];
	com[3] += positions[i][3];
    }
    glPopMatrix();
    com[0] /= NODE_COUNT;
    com[1] /= NODE_COUNT;
    com[2] /= NODE_COUNT;
    com[3] /= NODE_COUNT;

    com[0] /= com[3];
    com[1] /= com[3];
    com[2] /= com[3];

    /*printf("com: %f, %f, %f, %f\n", com[0], com[1], com[2], com[3]);*/

#if MAGICAL_RED_STRING
    glPushMatrix();
    glTranslatef(-com[0], -com[1], -com[2]);

    glDisable(GL_LIGHTING);
    glColor4f(1.0, 0.0, 0.0, 1.0);
    glBegin(GL_LINE_STRIP);
    for (i = 0; i < NODE_COUNT - 1; i++) {
	glVertex3fv(positions[i]);
    }
    glEnd();
    glEnable(GL_LIGHTING);
    /*glTranslatef(com[0], com[1], com[2]);*/
    glPopMatrix();
#endif

    glPushMatrix();
    glTranslatef(-com[0], -com[1], -com[2]);

#ifdef HAVE_GLUT
    /* apply the mouse drag rotation */
    ui_mousedrag();
#endif
    
    /* apply the continuous rotation */
    glRotatef(yspin, 0.0, 1.0, 0.0); 
    glRotatef(zspin, 0.0, 0.0, 1.0); 

    /* now draw each node along the snake -- this is quite ugly :p */
    for (i = 0; i < NODE_COUNT; i++) {
	/* choose a colour for this node */
	if ((i == bp->selected || i == bp->selected+1) && interactive)
	    /* yellow */
	    glColor4f(1.0, 1.0, 0.0, 1.0);
	else {
	    /*glColor4fv(bp->colour[(i+1)%2]);*/
	    glMaterialfv(GL_FRONT, GL_AMBIENT, bp->colour[(i+1)%2]);
	    glMaterialfv(GL_FRONT, GL_DIFFUSE, bp->colour[(i+1)%2]);
	    /*glMaterialfv(GL_FRONT, GL_SPECULAR, bp->colour[(i+1)%2]);*/
	}

	/* draw the node */
	if (wireframe)
	    glCallList(bp->node_wire);
	else
	    glCallList(bp->node_solid);

	/* now work out where to draw the next one */
	
	/* Interpolate between models */
	ang = bp->node[i];
	
	glTranslatef(0.5, 0.5, 0.5);		/* move to center */
	glRotatef(90.0, 0.0, 0.0, -1.0);	/* reorient  */
	glTranslatef(1.0 + explode, 0.0, 0.0);	/* move to new pos. */
	glRotatef(180.0 + ang, 1.0, 0.0, 0.0);	/* pivot to new angle */
	glTranslatef(-0.5, -0.5, -0.5);		/* return from center */
    }

    glPopMatrix();
    
    if (titles)
#ifdef HAVE_GLUT
	draw_title();
#else
	draw_title(mi);
#endif

#ifndef HAVE_GLUT
	glsnake_idle(bp);
#endif
    
    glFlush();
#ifdef HAVE_GLUT
    glutSwapBuffers();
#else
    glXSwapBuffers(dpy, window);
#endif
}

#ifdef HAVE_GLUT
/* anything that needs to be cleaned up goes here */
static void unmain() 
{
    glutDestroyWindow(bp->window);
    free(bp);
}

static void ui_init(int *, char **);

int main(int argc, char ** argv) 
{
    bp = malloc(sizeof(struct glsnake_cfg));
    memset(bp, 0, sizeof(struct glsnake_cfg));

    bp->width = 640;
    bp->height = 480;
    
    ui_init(&argc, argv);

    gettime(&bp->last_iteration);
    memcpy(&bp->last_morph, &bp->last_iteration, sizeof(snaketime));
    srand((unsigned int)GETSECS(bp->last_iteration));

    bp->prev_colour = bp->next_colour = COLOUR_ACYCLIC;
    bp->next_model = RAND(models);
    bp->prev_model = 0;
    start_morph(bp->prev_model, 1);	

    glsnake_init();
    
    atexit(unmain);
    glutSwapBuffers();
    glutMainLoop();
    
    return 0;
}
#endif

/*
 * GLUT FUNCTIONS
 */

#ifdef HAVE_GLUT

/* trackball quaternions */
static float cumquat[4] = {0.0,0.0,0.0,0.0}, oldquat[4] = {0.0,0.0,0.0,0.1};

/* rotation matrix */
static float rotation[16];

/* mouse drag vectors: start and end */
static float mouse_start[3], mouse_end[3];

/* dragging boolean */
static int dragging = 0;

/* this function calculates the rotation matrix based on the quaternions
 * generated from the mouse drag vectors */
static void calc_rotation() 
{
    double Nq, s;
    double xs, ys, zs, wx, wy, wz, xx, xy, xz, yy, yz, zz;

    /* this bit ripped from Shoemake's quaternion notes from SIGGRAPH */
    Nq = cumquat[0] * cumquat[0] + cumquat[1] * cumquat[1] +
	cumquat[2] * cumquat[2] + cumquat[3] * cumquat[3];
    s = (Nq > 0.0) ? (2.0 / Nq) : 0.0;
    xs = cumquat[0] *  s; ys = cumquat[1] *  s; zs = cumquat[2] * s;
    wx = cumquat[3] * xs; wy = cumquat[3] * ys; wz = cumquat[3] * zs;
    xx = cumquat[0] * xs; xy = cumquat[0] * ys; xz = cumquat[0] * zs;
    yy = cumquat[1] * ys; yz = cumquat[1] * zs; zz = cumquat[2] * zs;

    rotation[0] = 1.0 - (yy + zz);
    rotation[1] = xy + wz;
    rotation[2] = xz - wy;
    rotation[4] = xy - wz;
    rotation[5] = 1.0 - (xx + zz);
    rotation[6] = yz + wx;
    rotation[8] = xz + wy;
    rotation[9] = yz - wx;
    rotation[10] = 1.0 - (xx + yy);
    rotation[3] = rotation[7] = rotation[11] = 0.0;
    rotation[12] = rotation[13] = rotation[14] = 0.0;
    rotation[15] = 1.0;
}

static inline void ui_mousedrag() 
{
    glMultMatrixf(rotation);
}

static void ui_keyboard(unsigned char c, int x__attribute__((__unused__)), int y __attribute__((__unused__)))
{
    int i;
    
    switch (c) {
      case 27:  /* ESC */
      case 'q':
	exit(0);
	break;
      case 'e':
	explode += DEF_EXPLODE;
	glutPostRedisplay();
	break;
      case 'E':
	explode -= DEF_EXPLODE;
	if (explode < 0.0) explode = 0.0;
	glutPostRedisplay();
	break;
      case '.':
	/* next model */
	bp->next_model++;
	bp->next_model %= models;
	start_morph(bp->next_model, 0);
	
	/* Reset last_morph time */
	gettime(&bp->last_morph);			
	break;
      case ',':
	/* previous model */
	bp->next_model = (bp->next_model + (int)models - 1) % (int)models;
	start_morph(bp->next_model, 0);
	
	/* Reset bp->last_morph time */
	gettime(&bp->last_morph);			
	break;
      case '+':
	angvel += DEF_ANGVEL;
	break;
      case '-':
	if (angvel > DEF_ANGVEL)
	    angvel -= DEF_ANGVEL;
	break;
      case 'i':
	if (interactive) {
	    /* Reset last_iteration and last_morph time */
	    gettime(&bp->last_iteration);
	    gettime(&bp->last_morph);
	}
	interactive = 1 - interactive;
	glutPostRedisplay();
	break;
      case 'w':
	wireframe = 1 - wireframe;
	if (wireframe)
	    glDisable(GL_LIGHTING);
	else
	    glEnable(GL_LIGHTING);
	glutPostRedisplay();
	break;
      case 'a':
	transparent = 1 - transparent;
	if (transparent) {
	    glEnable(GL_BLEND);
	} else {
	    glDisable(GL_BLEND);
	}
	break;
      case 'p':
	if (bp->paused) {
	    /* unpausing, reset last_iteration and last_morph time */
	    gettime(&bp->last_iteration);
	    gettime(&bp->last_morph);
	}
	bp->paused = 1 - bp->paused;
	break;
      case 'd':
	/* dump the current model so we can add it! */
	printf("# %s\nnoname:\t", model[bp->next_model].name);
	{
	    int i;
	    
	    for (i = 0; i < NODE_COUNT; i++) {
		if (bp->node[i] == ZERO)
		    printf("Z");
		else if (bp->node[i] == LEFT)
		    printf("L");
		else if (bp->node[i] == PIN)
		    printf("P");
		else if (bp->node[i] == RIGHT)
		    printf("R");
		/*
		  else
		  printf("%f", node[i].curAngle);
		*/
		if (i < NODE_COUNT - 1)
		    printf(" ");
	    }
	}
	printf("\n");
	break;
      case 'f':
	bp->fullscreen = 1 - bp->fullscreen;
	if (bp->fullscreen) {
	    bp->old_width = bp->width;
	    bp->old_height = bp->height;
	    glutFullScreen();
	} else {
	    glutReshapeWindow(bp->old_width, bp->old_height);
	    glutPositionWindow(50,50);
	}
	break;
      case 't':
	titles = 1 - titles;
	if (interactive || bp->paused)
	    glutPostRedisplay();
	break;
      case 'c':
	altcolour = 1 - altcolour;
	break;
      case 'z':
	zoom += 1.0;
	glsnake_reshape(bp->width, bp->height);
	break;
      case 'Z':
	zoom -= 1.0;
	glsnake_reshape(bp->width, bp->height);
	break;
      default:
	break;
    }
}

static void ui_special(int key, int x__attribute__((__unused__)), int y __attribute__((__unused__)))
{
    float *destAngle = &(model[bp->next_model].node[bp->selected]);
    int unknown_key = 0;

    if (interactive) {
	switch (key) {
	  case GLUT_KEY_UP:
	    bp->selected = (bp->selected + (NODE_COUNT - 2)) % (NODE_COUNT - 1);
	    break;
	  case GLUT_KEY_DOWN:
	    bp->selected = (bp->selected + 1) % (NODE_COUNT - 1);
	    break;
	  case GLUT_KEY_LEFT:
	    *destAngle = fmod(*destAngle+(LEFT), 360);
	    bp->morphing = bp->new_morph = 1;
	    break;
	  case GLUT_KEY_RIGHT:
	    *destAngle = fmod(*destAngle+(RIGHT), 360);
	    bp->morphing = bp->new_morph = 1;
	    break;
	  case GLUT_KEY_HOME:
	    start_morph(STRAIGHT_MODEL, 0);
	    break;
	  default:
	    unknown_key = 1;
	    break;
	}
    }
    calc_snake_metrics();

    if (!unknown_key)
	glutPostRedisplay();
}

static void ui_mouse(int button, int state, int x, int y) 
{
    if (button==0) {
	switch (state) {
	  case GLUT_DOWN:
	    dragging = 1;
	    mouse_start[0] = M_SQRT1_2 * 
		(x - (bp->width / 2.0)) / (bp->width / 2.0);
	    mouse_start[1] = M_SQRT1_2 * 
		((bp->height / 2.0) - y) / (bp->height / 2.0);
	    mouse_start[2] = sqrt((double)(1-(mouse_start[0]*mouse_start[0]+mouse_start[1]*mouse_start[1])));
	    break;
	  case GLUT_UP:
	    dragging = 0;
	    oldquat[0] = cumquat[0];
	    oldquat[1] = cumquat[1];
	    oldquat[2] = cumquat[2];
	    oldquat[3] = cumquat[3];
	    break;
	  default:
	    break;
	}
    }
    glutPostRedisplay();
}

static void ui_motion(int x, int y) 
{
    double norm;
    float q[4];
    
    if (dragging) {
	/* construct the motion end vector from the x,y position on the
	 * window */
	mouse_end[0] = M_SQRT1_2 * (x - (bp->width/ 2.0)) / (bp->width / 2.0);
	mouse_end[1] = M_SQRT1_2 * ((bp->height / 2.0) - y) / (bp->height / 2.0);
	/* calculate the normal of the vector... */
	norm = mouse_end[0] * mouse_end[0] + mouse_end[1] * mouse_end[1];
	/* check if norm is outside the sphere and wraparound if necessary */
	if (norm > 1.0) {
	    mouse_end[0] = -mouse_end[0];
	    mouse_end[1] = -mouse_end[1];
	    mouse_end[2] = sqrt(norm - 1);
	} else {
	    /* the z value comes from projecting onto an elliptical spheroid */
	    mouse_end[2] = sqrt(1 - norm);
	}

	/* now here, build a quaternion from mouse_start and mouse_end */
	q[0] = mouse_start[1] * mouse_end[2] - mouse_start[2] * mouse_end[1];
	q[1] = mouse_start[2] * mouse_end[0] - mouse_start[0] * mouse_end[2];
	q[2] = mouse_start[0] * mouse_end[1] - mouse_start[1] * mouse_end[0];
	q[3] = mouse_start[0] * mouse_end[0] + mouse_start[1] * mouse_end[1] + mouse_start[2] * mouse_end[2];

	/* new rotation is the product of the new one and the old one */
	cumquat[0] = q[3] * oldquat[0] + q[0] * oldquat[3] + 
	    q[1] * oldquat[2] - q[2] * oldquat[1];
	cumquat[1] = q[3] * oldquat[1] + q[1] * oldquat[3] + 
	    q[2] * oldquat[0] - q[0] * oldquat[2];
	cumquat[2] = q[3] * oldquat[2] + q[2] * oldquat[3] + 
	    q[0] * oldquat[1] - q[1] * oldquat[0];
	cumquat[3] = q[3] * oldquat[3] - q[0] * oldquat[0] - 
	    q[1] * oldquat[1] - q[2] * oldquat[2];
	
	calc_rotation();
    }
    glutPostRedisplay();
}

static void ui_init(int * argc, char ** argv) 
{
    glutInit(argc, argv);
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
    glutInitWindowSize(bp->width, bp->height);
    bp->window = glutCreateWindow("glsnake");

    glutDisplayFunc(glsnake_display);
    glutReshapeFunc(glsnake_reshape);
    glutIdleFunc(glsnake_idle);
    glutKeyboardFunc(ui_keyboard);
    glutSpecialFunc(ui_special);
    glutMouseFunc(ui_mouse);
    glutMotionFunc(ui_motion);

    yangvel = DEF_YANGVEL;
    zangvel = DEF_ZANGVEL;
    explode = DEF_EXPLODE;
    angvel = DEF_ANGVEL;
    statictime = DEF_STATICTIME;
    altcolour = DEF_ALTCOLOUR;
    titles = DEF_TITLES;
    interactive = DEF_INTERACTIVE;
    zoom = DEF_ZOOM;
    wireframe = DEF_WIREFRAME;
    transparent = DEF_TRANSPARENT;
}
#endif /* HAVE_GLUT */

XSCREENSAVER_MODULE ("GLSnake", glsnake)
