/*
    bomb - automatic interactive visual stimulation
    Copyright (C) 1994  Scott Draves <spot@cs.cmu.edu>

    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.
*/

#include "defs.h"
#include "image.h"
#include "image_db.h"
#include "bomb.h"
#include "match.h"
#include "sound.h"

board_t board[2];
board_t board2[2];
board_t board3[2];
int dbuf=0;
int remap[max_heat]; /* > largest possible mask */
int p1 = 0;

int auto_mode = 0;
int auto_rule = 0;
int delayed_fill = 0;

char status_string[status_string_length];


   int key;
   int frame = 0;
   double timer = 500;
   int overdrive_speed = 20;
   int dtime = 1;
   int flipper, flipper2;
   int param = 0;
   int curs = 0;
   int *gparam;
   int old_rule;
   int pulse = 0;
   int periodic_write = 0;
   int cmap_changed = 0;
   int image_rate = 0; 	/* should be in rule struct */
   int image_dst = 0;
   int rule_lock = 0;
   int pop_back = 0, back_to;
   int permuting = 0;
   double delay = 0.0;
   int kbd_mode = 0;
   int stabilize_fps;
   int display_fps = 0;
   int rule_mask;
   int last_clear = 0;

double fps;

/* for colormap changes */
static u_char target_cmap[256][3];
int current_cmap[256 * 3];
#define SET_TARGET_PALETTE(x, r, g, b) \
	target_cmap[x][0] = r; \
	target_cmap[x][1] = g; \
	target_cmap[x][2] = b


cmap_t cmap;
fill_t fill2;
rule_t rule;
#define RULEBUFSIZE 10
rule_t rules[RULEBUFSIZE];

image8_t fb;

int masks[max_heat];

#if mac_bomb && !use_sioux
#include <console.h>
short InstallConsole(short fd){return 0;}
void RemoveConsole(void){}
long WriteCharsToConsole(char *buffer, long n){return 0;}
long ReadCharsFromConsole(char *buffer, long n){return 0;}
#endif


#if (xws_bomb|ogl_bomb)
Display *disp;
Window win;
Colormap xws_cmap;
#if xws_bomb
GC gc;
XImage *xws_image;
char *image_buf;
#endif
#endif

#if mac_bomb
#include <AppleEvents.h>
#include <Types.h>
#include <Resources.h>
#include <QuickDraw.h>
#include <Fonts.h>
#include <Events.h>
#include <Windows.h>
#include <Menus.h>
#include <TextEdit.h>
#include <Dialogs.h>
#include <Desk.h>
#include <ToolUtils.h>
#include <Memory.h>
#include <SegLoad.h>
#include <Files.h>
#include <OSUtils.h>
#include <OSEvents.h>
#include <DiskInit.h>
#include <Packages.h>
#include <Traps.h>
#include <QDOffscreen.h>
#include <Palettes.h>
#include <LowMem.h>
#endif

void
ramp(int c, int i0, int i1, int n, int k)
{
   int x, r, g, b;
   for (x = 0; x < n; x++) {
      double alpha = x / (double) n;
      r = (int) (the_cmaps[c][i0][0] * (1 - alpha) +
	   the_cmaps[c][i1][0] * alpha);
      g = (int) (the_cmaps[c][i0][1] * (1 - alpha) +
	   the_cmaps[c][i1][1] * alpha);
      b = (int) (the_cmaps[c][i0][2] * (1 - alpha) +
	   the_cmaps[c][i1][2] * alpha);
      SET_TARGET_PALETTE(k + x, r, g, b);
   }
}

int
round_up8(int n)
{
   if (n < 0)
      n = (n - 7) >> 3;
   else
      n = (n + 7) >> 3;
   return n;
}

int
round_up5(int n)
{
   if (n < 0)
      n = (n - 31) >> 5;
   else
      n = (n + 31) >> 5;
   return n;
}

void
step_cmap()
{
   int i;
   for (i = 0; i < 256; i++) {
      current_cmap[3*i]   += round_up8(target_cmap[i][0] - current_cmap[3*i]);
      current_cmap[3*i+1] += round_up8(target_cmap[i][1] - current_cmap[3*i+1]);
      current_cmap[3*i+2] += round_up8(target_cmap[i][2] - current_cmap[3*i+2]);
   }
#define difff(x,y) ((x)!=(y))
   for (i = 0; i < 256; i++) {
      if (difff(current_cmap[3*i+0], target_cmap[i][0]) ||
	  difff(current_cmap[3*i+1], target_cmap[i][1]) ||
	  difff(current_cmap[3*i+2], target_cmap[i][2])) {
	 image8_set_cmap(0, 256, current_cmap);
	 return;
      }
   }
}

void brighten_cmap() {
   int i;
   for (i = 0; i < 256; i++) {
      int r = (255 + 4*current_cmap[3*i+0])/5;
      int g = (255 + 4*current_cmap[3*i+1])/5;
      int b = (255 + 4*current_cmap[3*i+2])/5;
      SET_TARGET_PALETTE(i, r, g, b);
   }
}

void pulse_cmap_rotate() {
   int i;
   for (i = 1; i < 220; i++) {
      current_cmap[3*i+0] = current_cmap[(3*i+0+90)%(3*256)];
      current_cmap[3*i+1] = current_cmap[(3*i+0+91)%(3*256)];
      current_cmap[3*i+2] = current_cmap[(3*i+0+92)%(3*256)];
   }
}

void pulse_cmap_black() {
   int i;
   for (i = 100; i < 150; i++) {
      current_cmap[3*i+0] = current_cmap[3*i+0] * (150 - i) / 50;
      current_cmap[3*i+1] = current_cmap[3*i+1] * (150 - i) / 50;
      current_cmap[3*i+2] = current_cmap[3*i+2] * (150 - i) / 50;
   }
   for (i = 150; i < 250; i++) {
      current_cmap[3*i+0] = 0;
      current_cmap[3*i+1] = 0;
      current_cmap[3*i+2] = 0;
   }
}

void pulse_cmap_white() {
   int i;
   for (i = 1; i < 10; i++) {
      current_cmap[3*i+0] = ((current_cmap[3*i+0] * (10 - i) / 10) +
			     (255 * i / 10));
      current_cmap[3*i+1] = ((current_cmap[3*i+1] * (10 - i) / 10) +
			     (255 * i / 10));
      current_cmap[3*i+2] = ((current_cmap[3*i+2] * (10 - i) / 10) +
			     (255 * i / 10));
   }
   for (i = 10; i < 20; i++) {
      current_cmap[3*i+0] = 255;
      current_cmap[3*i+1] = 255;
      current_cmap[3*i+2] = 255;
   }
   for (i = 20; i < 30; i++) {
      current_cmap[3*i+0] = ((current_cmap[3*i+0] * (i - 20) / 10) +
			     (255 * (30 - i) / 10));
      current_cmap[3*i+1] = ((current_cmap[3*i+1] * (i - 20) / 10) +
			     (255 * (30 - i) / 10));
      current_cmap[3*i+2] = ((current_cmap[3*i+2] * (i - 20) / 10) +
			     (255 * (30 - i) / 10));
   }
}

void
fade2cmap(cmap_t *p)
{
   int r, g, b, x;
   int c, i, i0, i1, i2;

   switch (p->cmap) {
    case cmap_mono:
      for(x=0;x<256;x++) {
	 SET_TARGET_PALETTE(x,x,x,x);
      }
      break;
    case cmap_mono4:
      i0 = p->index;
      i1 = p->index2;
      for(x=0;x<256;x++) {
	 if (x == i0) {
	    r = (i1&3)<<4;
	    g = (i1&12)<<2;
	    b = (i1&48);
	 } else
	    r = g = b = (x&63)<<2;
	 SET_TARGET_PALETTE(x,r,g,b);
      }
      break;
    case cmap_loop:
      c = p->index;
      i0 = R%256;
      i1 = next_contrasting_color(c, i0, 60000);
      i2 = next_contrasting_color(c, i1, 60000);
      ramp(c, i0, i1, 64, 0);
      ramp(c, i1, i2, 64, 64);
      ramp(c, i2, i1, 64, 128);
      ramp(c, i1, i0, 64, 192);
      break;
    case cmap_path:
      if (1) {
	 int n = 10;
	 int len = 30000;
	 int each = 256/(n+1);
	 c = p->index;
	 i2 = i0 = R%256;
	 for (i = 0; i < n; i++) {
	    i1 = next_contrasting_color(c, i0, len);
	    ramp(c, i0, i1, each, i*each);
	    i0 = i1;
	 }
	 ramp(c, i1, i2, 256 - n*each, n*each);
      }
      break;
    case cmap_heat:
      /* black -> midred -> red -> yellow -> white
	 -> = 64 */
      for (x = 0; x < 128; x++) {
	 r = g = 255;
	 b = x << 1;
	 SET_TARGET_PALETTE(128 + x, r, g, b);
      }
      for (x = 0; x < 64 ;x++) {
	 r = 255;
	 b = 0;
	 g = x<<2;
	 SET_TARGET_PALETTE(64 + x, r, g, b);
      }
      for(x = 0; x < 64; x++) {
	 r = x<<2;
	 g = 0;
	 b = 0;
	 SET_TARGET_PALETTE(x, r, g, b);
      }
      break;
    case cmap_plain:
      i = p->index;
      for (x = 0; x < 256; x++) {
	 r = the_cmaps[i][x][0];
	 g = the_cmaps[i][x][1];
	 b = the_cmaps[i][x][2];
	 SET_TARGET_PALETTE(x,r,g,b);
      }
      break;
    case cmap_split:
      i = p->index;
      for(x=0;x<128;x++) {
	 r = the_cmaps[i][x][0];
	 g = the_cmaps[i][x][1];
	 b = the_cmaps[i][x][2];
	 SET_TARGET_PALETTE(x,r,g,b);
      }
      i = p->index2;
      for(x=128;x<256;x++) {
	 r = the_cmaps[i][x][0];
	 g = the_cmaps[i][x][1];
	 b = the_cmaps[i][x][2];
	 SET_TARGET_PALETTE(x,r,g,b);
      }
      break;
    case cmap_noise:
      for (x = 0; x < 256; x++) {
	 SET_TARGET_PALETTE(x,R,R,R);
      }
      break;
    case cmap_black:
      for (x = 0; x < 256; x++) {
	 SET_TARGET_PALETTE(x,0,0,0);
      }
      break;
   }
}

void set_remap(int mask, int rm) {
	int x;
    for (x = 0; x < mask; x++) {
	     int r;
	     r = rm
			 ? (int)(255 * x / (double)mask)
			 : x;
		 r = r & 255;
	     if (0 == r) r = 1;
	     if (255 == r) r = 254;
		 remap[x] = r;
    }
}


/* spook */
int
count_bits16(int i)
{
   int b, r = 0;
   for (b = 1; b < 0x10000; b = b << 1)
      if (b & i)
	 r++;
   return r;
}

/* find last set, like ffs */
int
fls(int i)
{
   int r = 1;
   int b = 1;
   while (b < i) {
      r++;
      b = b << 1;
   }
   return r;
}

void
init_masks()
{
   int i, c;
   c = 0;
   i = 1;
   while (c < max_heat) {
      if (8 < count_bits16(i)) {
	 masks[c++] = i;
      }
      i += 2;
   }
}

int
compare_integers(const void *k, const void *a)
{
   return *(int *)a - *(int *)k;
}

/* generally should just keep the index */
int
next_mask(int mask, int d)
{
   int *r;
   int i;
   int s = (d > 0) ? 2 : -2;
   d = abs(d);
   d = (d > 0) ? d : -d;
   while (d) {
      if (8 < count_bits16(mask))
	 d--;
      mask += s;
   }
   return mask;
#if 0
   r = bsearch(&mask, masks, max_heat, sizeof(int), compare_integers);
   if (NULL == r)
      return masks[0];
   i = (r - masks + d) % max_heat;
   return masks[i];
#endif
}

int load_title(char *fn)
{
   int i;
   Image im;
#if !use_sioux
   /* set cmaps to grayscale */
   for (i = 0; i < 256; i++) {
      int *v = &current_cmap[3*i];
      v[0] = v[1] = v[2] = i;
   }
   image8_set_cmap(0, 256, current_cmap);

   image_init(&im);
   if (TCL_ERROR == image_read(&im, fn))
      return 0;
   image8_blit(&im, &fb);
   image8_flush();
#endif
   return 1;
}

#if mac_bomb

int GetBit( const void *data, int offset )
{
   int d = ((unsigned char *)data)[offset >> 3];
   return (d & (1 << (offset & 7))) != 0;
}

#endif


int
bomb_getkey() {
#if win_bomb
	extern int win_getkey();
	return win_getkey();
#endif
#if vga_bomb
   return vga_getkey();
#endif
#if (xws_bomb|ogl_bomb)
   XEvent ev;
   if (XCheckWindowEvent(disp, win, KeyPressMask, &ev)) {
      KeySym s;
      s = XKeycodeToKeysym(disp, ev.xkey.keycode, 0);
      /* doesn't work for shifted digits */
      if (s > 255) return 0;
      if (ShiftMask&ev.xkey.state) {
	 return ((int)s) - ('a' - 'A');
      }
      if (ControlMask&ev.xkey.state) {
	 return ((int)s) - 'a' + 1;
      }
      return (int) s;
   } else
      return 0;
#endif
#if mac_bomb
#if use_sioux
#  define GetMacEvent GetNextEvent
#else
#  define GetMacEvent GetOSEvent
#endif
   static EventRecord gEvent;
   if (GetMacEvent( keyDownMask, &gEvent)) {
      int key = gEvent.message & charCodeMask;
      if ('q' == key && (gEvent.modifiers & cmdKey))
         return 3;
      else
         return(key);
   } else
      return(0);
#endif
}

void bomb_exit() {
	 exit_sound();
#if mac_bomb
	    if (1) {
	       GrafPtr wPort;
	       FlushEvents(everyEvent - osMask - diskMask, 0);
	    }
	    ExitToShell();
#endif
	    printf("\nbye\n");
	    exit(0);
}

#if ogl_bomb || vga_bomb
void init_random() {
   t = time(NULL);
   srandom(argd("seed", t));
}
#endif

#if mac_bomb
void
init_random()
{
   srand(argd("seed", TickCount()));
}

void
init_mac()
{

#if mac_bomb
   if (1) {
      Ptr base_address;
      SysEnvRec This_mac;
      Rect *rr;
      int cx, cy;
      CWindowPtr myWindow;

      printf("init bomb\n");
#if !use_sioux
      InitGraf(&qd.thePort);
      InitFonts();
      FlushEvents(everyEvent - osMask - diskMask, 0);
      InitWindows();
      InitCursor();
      MaxApplZone();
      MoreMasters();
      HideCursor();
#endif
      SysEnvirons( 1, &This_mac );
      if ( This_mac.hasColorQD ) {
         GDHandle the_monitor;
         the_monitor = GetMainDevice();         /* Get monitor with the menuBar on it. */
         if ((**(**the_monitor).gdPMap).pixelSize != 8)
            SetDepth(the_monitor, 8, 0, 0);	/* Make sure we have 8-bit pixels */
         base_address = (**(**the_monitor).gdPMap).baseAddr;
         fb.stride = ((**(**the_monitor).gdPMap).rowBytes & 0x3FFF);
         rr = &(**(**the_monitor).gdPMap).bounds;
         SetGDevice(the_monitor);
       //  world_x = (**the_monitor).gdRect.left;
       //  world_y = (**the_monitor).gdRect.top;
      } else {
	 ExitToShell();   /* Not much point if we don't have color quickdraw. */
   }
   
#if !use_sioux
   if (1) {
      int i, j;
     /* open window covering entire screen so things will be redrawn when the app quits */
      myWindow = (CWindowPtr) NewCWindow(0, rr, "\pBomb", 1, 0, (WindowPtr) -1, 0, 0);
      SetPort((WindowPtr)myWindow);
      for (i = rr->top; i < rr->bottom; i++)
         for (j = rr->left; j < rr->right; j++)
            *(((u_char*)base_address) + j + (i * fb.stride)) = 0;
   }
#endif

#if pix_rep
#ifdef copy_bits
      if (1) {
      extern PixMapPtr  offscreenPixMapP, windowPixMapP;
      extern Rect       windowRect, offscreenRect;
      PixMapHandle	offscreenPixMapH, windowPixMapH;
      GWorldPtr         offscreenWorldP;
      GWorldPtr         saveWorld;
      GDHandle      	saveDevice;
   
      SetRect(&windowRect, 0, 0, pix_rep*320, pix_rep*200);
      SetRect(&offscreenRect, 0, 0, 320, 200);
      OffsetRect(&windowRect, (rr->right - rr->left - (pix_rep*320)) / 2 & (~3), (rr->bottom - rr->top - (pix_rep*200)) / 2);
   // Get the Graphics World and Device associated with the window
      GetGWorld(&saveWorld, &saveDevice);
   // Set a pointer to the windows PixMap
      windowPixMapH = GetGWorldPixMap(saveWorld);
      HLockHi((Handle)windowPixMapH);
      LockPixels(windowPixMapH);
      windowPixMapP = *windowPixMapH;

   // Set Up Offscreen GWorld
      if (NewGWorld(&offscreenWorldP, 0, &offscreenRect, 0, saveDevice, noNewDevice))
         DebugStr("\pCan't get GWorld.");
   // Set a pointer to the Offscreen World's PixMap
      offscreenPixMapH = GetGWorldPixMap(offscreenWorldP);
      HLockHi((Handle)offscreenPixMapH);
      LockPixels(offscreenPixMapH);
      offscreenPixMapP = *offscreenPixMapH;
   // Set up fb.
      fb.p = (Byte*) (*offscreenPixMapH)->baseAddr;
      fb.stride = (*offscreenPixMapH)->rowBytes & 0x3fff;
   }
#else
   if (1) {
      extern unsigned char *real_screen_base;
      extern int real_screen_stride;
      cy = (rr->bottom - rr->top - (pix_rep*200)) / 2;
      cx = (rr->right - rr->left - (pix_rep*320)) / 2;
      real_screen_base = ((u_char *) base_address) + (cy * fb.stride) + cx;
      real_screen_stride = fb.stride;
      fb.p = malloc(320 * 200);
      fb.stride = 320;
   }
#endif
#else
   cy = (rr->bottom - rr->top - 200) / 2;
   cx = (rr->right - rr->left - 320) / 2;
   fb.p = ((u_char *) base_address) + (cy * fb.stride) + cx;
#endif
   fb.width = 320;
   fb.height = 200;
   }
#endif
}
#endif

#if vga_bomb
void
init_vga()
{
   vga_modeinfo *modeinfo;
   vga_init();
   vga_setmode(G320x200x256);
   modeinfo = vga_getmodeinfo(G320x200x256);
   fb.p = vga_getgraphmem();
   fb.stride = modeinfo->linewidth;
   fb.width = 320;
   fb.height = 200;
}
#endif

#if ogl_bomb || xws_bomb
init_ogl()
{

      Screen *scrn;
      XVisualInfo *vl;
      XVisualInfo template;
      int nfound;
      XSetWindowAttributes attr;
      XGCValues argh;
      disp = XOpenDisplay(NULL);
      if (NULL == disp) {
	 fprintf(stderr, "couldn't open display.\n");
	 exit(1);
      }
      scrn = DefaultScreenOfDisplay(disp);
#if xws_bomb
      template.depth = 8;
      template.class = PseudoColor;
      vl = XGetVisualInfo(disp, VisualDepthMask|VisualClassMask,
			  &template, &nfound);
      if (nfound < 1) {
	 fprintf(stderr, "couldn't find 8 bit visual.\n");
	 exit(1);
      }
#endif
#if ogl_bomb
      if (1) {
	 int attrs[] = {None};
	 vl = glXChooseVisual(disp, 0, attrs);
	 if (NULL == vl) {
	    fprintf(stderr, "couldn't find GLX visual.\n");
	    exit(1);
	 }
      }
#endif
      attr.event_mask = KeyPressMask;
      xws_cmap = XCreateColormap(disp, RootWindowOfScreen(scrn),
				 vl[0].visual, AllocAll);
      attr.colormap = xws_cmap;
      win = XCreateWindow(disp,
			  RootWindowOfScreen(scrn),
			  (WidthOfScreen(scrn)-pix_rep*320)/2,
			  (HeightOfScreen(scrn)-pix_rep*200)/2,
			  pix_rep*320, pix_rep*200, 5, 8, InputOutput,
			  vl[0].visual, CWEventMask|CWColormap, &attr);
      XMapWindow(disp, win);
#if xws_bomb
      argh.function = GXcopy;
      gc = XCreateGC(disp, win, GCFunction, &argh);
#endif

      fb.p = malloc(320 * 200);
      fb.stride = 320;
      fb.width = 320;
      fb.height = 200;

#if xws_bomb
      if (1 == pix_rep)
	 image_buf = (char *) fb.p;
      else
	 image_buf = malloc(320 * 200 * pix_rep * pix_rep);
      xws_image = XCreateImage(disp, vl[0].visual, 8,
			       ZPixmap, 0, image_buf,
			       pix_rep*320, pix_rep*200, 8, 0);
#endif
#if ogl_bomb
      if (1) {
	 GLXContext cx;
	 cx = glXCreateContext(disp, vl, 0, GL_TRUE);
	 glXMakeCurrent(disp, win, cx);
	 glRasterPos2f(-1.0, -1.0);
	 glPixelZoom(pix_rep, pix_rep);
      }
#endif
   delay = 3.5;
}
#endif

void
init()
{
   int i;
   long t;

   printf("bomb v1.13\n");

   init_random();

   stabilize_fps = !getenv("fast");
   fps = 10.0; // just a guess

   gparam = &rule.speed;

   init_sound();
   begin_timer();

   for (i = 0; i < status_string_length; i++)
	   status_string[i] = ' ';

#ifndef win_saver
   if (1) {
      char s[1000];
      int r, i = 0;
      do {
	 sprintf(s, "%stitle%d.tif", DATA_DIR, i++);
	 r = load_title(s);
	 if (1 == r)
	    while (0 == bomb_getkey());
      } while (r);
      sprintf(s, "%stitle.tif", DATA_DIR);
      load_title(s);
   }
   load_title("title.tif");
#endif
    init_masks();
    init_rotor();
    init_images();
    init_seq();
    init_cmaps();

#if use_mpeg
    mpeg_init();
#endif

   distrib(distrib_new, &rule, &cmap, &fill2);
   rule.drift_time = 0.0;
   drive_with_image(current_image);

#ifndef win_saver
   /* start out with basically the same rule */
   rule.rule = rule_rug;
   rule.speed = -1;
   rule.speed_base = -1;
   rule.mask = 255;
   rule.remap = 0;
   rule.randomized_underflow = 1;
   rule.speed_beat_size = 0;
   cmap.cmap = cmap_plain;
   cmap.index = 4%ncmaps;
   fade2cmap(&cmap);
   pix2heat(&fb, &board[dbuf]);
#else
   fade2cmap(&cmap);
   image8_set_cmap(0, 256, target_cmap);
   fill_board(&fill2);
#endif
   set_remap(rule.mask, rule.remap);
   delayed_fill = 0;

   /* this forces the next several rule changes */
   auto_rule = 4;

   // while (0 == bomb_getkey());
}

int
rule_symmetry(int rule)
{
   int r;
   switch (rule) {
    case rule_rd: r = sym_tile4_stack2; break;
    case rule_wave:
    case rule_rug_brain:
    case rule_rug_anneal: 
    case rule_rug_anneal2: r = sym_tile4; break;
    case rule_acidlife2: r = sym_mirror4; break;
    case rule_acidlife1: r = sym_mirror2; break;
    case rule_fuse:
    case rule_slip: r = sym_frame_buffer; break;
    case rule_static: /* false, but useful */
    default:
      r = sym_one; break;
   }
   return r;      
}

void
change_rules(int old, int new, image8_t *fb)
{
   int old_sym = rule_symmetry(old);
   int new_sym = rule_symmetry(new);

   if (new == rule_rug_image && old != new) {
      drive_with_image(current_image);
   }
   if (old_sym == new_sym)
      return;
   if (sym_frame_buffer == old_sym) {
      pix2heat(fb, &board[dbuf]);
      old_sym = sym_one;
   }
   change_rules2(old_sym, new_sym, &board[dbuf]);
   change_rules2(old_sym, new_sym, &board2[dbuf]);
   /* some of the rules don't copy board2
      rug_rug is always going to fail sometimes until
      dbuf/frame&1 is resolved XXX */
   change_rules2(old_sym, new_sym, &board2[1-dbuf]);
}


void
distrib(int dist, rule_t *rulep, cmap_t *cmap, fill_t *fill)
{
   int x, b, i, nbits;
   if (rulep) {
      static int distrib[] =
      {rule_rug, rule_rd, rule_rug_brain, rule_rotorug, rule_rug_image};
      if (!(distrib_continuous & dist)) {
	 init_rotate();
	 init_shade();
	 init_wave();
	 if (!(distrib_rule_lock & dist)) {
		do {
	      if (auto_rule) {
	         rulep->rule = distrib[auto_rule%alen(distrib)];
	         auto_rule--;
	      } else {
	         rulep->rule = (R%2) ? (R%nrules) : distrib[R%alen(distrib)];
	         if (rule_fuse == rulep->rule)
		       rulep->rule = distrib[auto_rule%alen(distrib)];
	      }
		} while (0 == (rule_mask & (1 << rulep->rule)));
	 }
	 rulep->cycle_bkg = (0 == R%10);
	 rulep->brain = R;
	 if (high_growth_game(rulep)) {
	    delayed_fill = 1;
	 }
	 if (rule_rug_brain == rulep->rule)
	    rulep->brain_shift = (R%4) ? 0 : ((R%3) ? (R%5) : (R%25));
	 else
	    rulep->brain_shift = R;
	 random_control_point(&rulep->flame_cp);
	 if (auto_rule) {
	    int i, j;
	    static int best[] = {0, 1, 2, 5};
	    int v = best[R%alen(best)];
	    for (i = 0; i < NXFORMS; i++) {
	       for (j = 0; j < NVARS; j++)
		  rulep->flame_cp.xform[i].var[j] = 0.0;
	       rulep->flame_cp.xform[i].var[v] = 1.0;
	    }	    
	 }
	 pick_liss_coefs();

	 if (auto_rule) {
	    rulep->drift = 4;
	 } else if (rule_fuse == rulep->rule)
	    rulep->drift = (R%5) ? (R%2) : 3;
	 else
	    rulep->drift = R;
	 rulep->image_window_size = 2 + R%5;
	 rulep->speed_beat_speed = (R%3) ? (5 + R%10) : (30 + R%30);
	 rulep->speed_beat_size = (R%3) ? 0 : (R%2) ? (4 + R%12) : (10 + R%22);
	 rulep->seq[0] = R;
	 seq_start(rulep->seq);
	 p1 = R;
	 current_image = R;
      }
      if (1) {
         int rbase = 4 + R%8;
         for (i = 0; i < MAXRHYTHM; i++)
	    rulep->rhythm[i] = (R%3 + 1)*rbase;
      }
      rulep->bsize = (R%4) ? (20 + R%20 + R%20) : (60 + R%80);
      rulep->randomized_underflow = R&1;
      rulep->search_time = 2 + R%5;
      if (R%2) {
	 rulep->hot = 200;
	 rulep->cool = 5 + R%20 + R%20;
      } else {
	 rulep->hot = 1 + 5 * (R%3);
	 rulep->cool = 5 + R%20 + R%10 + rulep->hot;
      }
      if ((distrib_original & dist) ||
	  rule_acidlife1 == rulep->rule ||
	  rule_acidlife2 == rulep->rule ||
	  rule_rotorug == rulep->rule) {
	 nbits = R%7 + 8;
	 rulep->mask = (1<<nbits) - 1;
	 for (b = R%4; b; b--)
	    rulep->mask &= ~(2<<(R%nbits));
      } else {
	 nbits = 8 + (R%3 + 1) * (R%2 + 1);
	 rulep->mask = (1<<nbits) - 1;
	 for (b = R%3 + 1; b; b--)
	    rulep->mask &= ~(2<<(R%nbits));
      }

      switch (rulep->rule) {
       case rule_acidlife1:
	 x = -(1 + (rulep->mask/100) + R%(rulep->mask/30));
	 break;
       case rule_acidlife2:
	 x = -(R%20);
	 break;
       case rule_rotorug:
	 x = -(9 + R%(rulep->mask/30));
	 break;
       case rule_wave:
	 x = -(10 + R%10);
	 break;
       default:
	 if (distrib_original & dist)
	    x = -(9 + R%(rulep->mask/30));
	 else
	    x = -1;
	 break;
      }
      rulep->speed_base = x;
      rulep->speed = x;

      if (R%4)
	 rulep->driver_slowdown = 1 + R%8 + R%7;
      else
	 rulep->driver_slowdown = 13 + R%8;


      rulep->drift_speed = 6 + R%10;
	  rulep->remap = R%4;

	  set_remap(rulep->mask, rulep->remap);
   
   }
   if (cmap) {
      static distrib0[] = {cmap_plain, cmap_plain, cmap_loop, cmap_path, cmap_path};
      static distrib[] = {cmap_mono, cmap_mono, cmap_mono4, cmap_heat};
      if (rule.rule == rule_acidlife1)
	 cmap->cmap = cmap_split;
      if ((rule.rule == rule_rd) && (R&1))
	 cmap->cmap = cmap_path;
      if ((rule.rule == rule_rd2))
	 cmap->cmap = cmap_plain;
      else if (R%7)
	 cmap->cmap = distrib0[R%alen(distrib0)];
      else if (R%100)
	 cmap->cmap = distrib[R%alen(distrib)];
      else
	 cmap->cmap = cmap_noise;

      cmap->index = R%ncmaps;
      cmap->index2 = R%ncmaps;
   }
   if (fill) {
      static distrib[] = {fill_vnoise, fill_noise};
	  if (rule.rule == rule_rd ||
		  rule.rule == rule_rd2 ||
		  R%2)
		fill->fill = distrib[R%alen(distrib)];
	  else
        fill->fill = R%nfills;
   }
}

double adjust_speed(double dt) {
	return dt * 10.0 / fps;
}

void print_state(rule_t *r) {
   printf("rule=%d\n", r->rule);
   printf("speed=%d\n", r->speed);
   printf("mask=%d\n", r->mask);
   printf("randomized_underflow=%d\n", r->randomized_underflow);
   printf("cycle_bkg=%d\n", r->cycle_bkg);
   printf("remap=%d\n", r->remap);
   printf("floor=%d\n", r->floor);
   printf("driver_slowdown=%d\n", r->driver_slowdown);
   printf("brain=%d (%d)\n", r->brain, iclamp(r->brain, 44));
   printf("brain_shift=%d (%d)\n", r->brain_shift, iclamp(r->brain_shift, 26));
   printf("hot=%d\n", r->hot);
   printf("cool=%d\n", r->cool);
   printf("speed_base=%d\n", r->speed_base);
   printf("speed_beast_speed=%d\n", r->speed_beat_speed);
   printf("speed_beat_size=%d\n", r->speed_beat_size);
   printf("drift=%d (%d)\n", r->drift, iclamp(r->drift, 9));
   printf("drift_speed=%d\n", r->drift_speed);
   printf("bsize=%d\n", r->bsize);
   
   printf("p1=%d\n", p1);
   printf("cmap=%d\n", cmap.index);
}
   


flip_image(int rule_lock)
{
   /* Z = zxC-b */ /* at one point, that meant something */
   int im = seq_next_image(rule.seq);
   file_to_image(im, current_image);
   distrib(distrib_new | distrib_continuous | rule_lock, &rule, 0, &fill2);
   fill_board(&fill2);
}

int grad_state;
#if 0

int grad_step(int steps) {
   int res;
   /* fprintf(stderr, "gs%d ", grad_state); */
   if (0 == grad_state)
      return 1;
   else if (grad_none == grad_state) {
      grad_state = steps;
   } else if (1 == grad_state ||
	      9 == ((steps - grad_state)%10)) {
      image2grad(&board2[dbuf], &board3[0], 1);
   } else {
      blur(&board2[dbuf], &board2[1-dbuf]);
   }

   grad_state--;
   res = ((steps - grad_state) > 10)
      || (0 == grad_state);
   fprintf(stderr, " %d ", res);
   return res;
}
#endif


cmap_permute_bits()
{
   int i;
   for (i = 0; i < 256; i++) {
      int j = (i >> 1) | ((i&1) << 7);
      int r = current_cmap[3*j+0];
      int g = current_cmap[3*j+1];
      int b = current_cmap[3*j+2];
      SET_TARGET_PALETTE(i, r, g, b);
   }
}

image_to_heat(Image *src_img, image8_t *fb, board_t *board)
{
   int bsize = rule.bsize;
   int dest_x, dest_y;
   Image src_rect;
   image8_t dest = *fb;
   int move_heat = 1;

   switch (rule_symmetry(rule.rule)) {
    case sym_tile4:
    case sym_mirror4:
      dest_x = R%((fb->width>>1) - bsize);
      dest_y = R%((fb->height>>1) - bsize);
      break;
    case sym_mirror2:
      dest_x = R%((fb->width>>1) - bsize);
      dest_y = R%(fb->height - bsize);
      break;

    case sym_frame_buffer:
      move_heat = 0;
    default:
      dest_x = R%(fb->width - bsize);
      dest_y = R%(fb->height - bsize);
      break;
   }
   image_random_tile(&src_rect, src_img, bsize);
   dest.p = fb->p + fb->stride * dest_y + dest_x;
   dest.width = src_rect.width;
   dest.height = src_rect.height;
   /* could be any operation here, not just copy */
   if (1) {
      int i, j;
      for (i = 0; i < src_rect.height; i++)
	 for (j = 0; j < src_rect.width; j++)
	    dest.p[dest.stride * i + j] =
	       255 - src_rect.pixels[src_rect.stride * i + j].r;
   }

   if (move_heat)
      pix2heat2(fb, board,
		dest_x, dest_y,
		dest_x + src_rect.width, dest_y + src_rect.height);
}


Image *pick_image(rule_t *p)
{
   Image *r;
   int n = iclamp(R, p->image_window_size);

   switch (p->rule) {
    case rule_fuse:
    case rule_static:
    case rule_slip:
      r = &global_images[n];
      break;
    default:
      r = &global_images_small[n];
      break;
   }
   return r;
}
      

void
changed_gparam(int *gparam)
{
   if (&cmap.index == gparam) {
      cmap.index = iclamp(cmap.index, ncmaps);
      fade2cmap(&cmap);
   } else if (&rule.cycle_bkg == gparam ||
	      &rule.remap == gparam ||
	      &rule.randomized_underflow == gparam)
      *gparam = (*gparam)&1;
   else if (&rule.image_window_size == gparam) {
      if (*gparam > N_RAM_IMAGES)
	 *gparam = N_RAM_IMAGES;
      else if (*gparam < 1)
	 *gparam = 1;
   } else if (&rule.seq[0] == gparam) {
      seq_start(rule.seq);
   }
   
   if (&rule.remap == gparam ||
	          &rule.mask == gparam) {
	   set_remap(rule.mask, rule.remap);
   }

   /* printf("changed gparam %#x to %d\n", gparam, *gparam); */
}


void do_mouse(int x, int y) {
  switch (rule.rule) {
     case rule_rotorug:
        rule.bsize = iclamp(x, 320);
        if (0) {
           extern double spinner_time;
           spinner_time = x + y*10;
           rule.drift_time = x/32.0 + y;
        }
        break;
     case rule_rug:
        rule.speed = iclamp(x,320)/8-20;
        break;
     case rule_rug_brain:
        pen(&board2[dbuf], 1, iclamp(x, 160), iclamp(y, 100), 10);
        break;
     case rule_rd:
        pen(&board[dbuf], 0, iclamp(x,160), iclamp(y, 100), 5);
        break;
     default:
        pen(&board[dbuf], rule.mask, iclamp(x,300), iclamp(y,180), 12);
        break;
   }
}



#if mac_bomb
static long start_time;
void begin_timer() {
  start_time = TickCount();
}
double end_timer() {
  return (TickCount() - start_time) / 60.0; /* just a guess */
}
#elif win_bomb
#else
#include <sys/time.h>

static struct timeval start_time;

void
begin_timer()
{
   struct timezone tzp;
   gettimeofday(&start_time, &tzp);
}

double
end_timer()
{
   static struct timeval end_time;
   struct timezone tzp;
   double t;
   gettimeofday(&end_time, &tzp);
   return
      ((end_time.tv_sec - start_time.tv_sec) +
       (end_time.tv_usec - start_time.tv_usec) * 1e-6);
}
#endif

void step_rule_rug(int frame, rule_t *p, image8_t *fb);
void step_rule_rug2(int frame, rule_t *p, image8_t *fb);
void step_rule_static(int frame, rule_t *p, image8_t *fb);
void step_rule_rotorug(int frame, rule_t *p, image8_t *fb);
void step_rule_acidlife1(int frame, rule_t *p, image8_t *fb);
void step_rule_acidlife2(int frame, rule_t *p, image8_t *fb);
void step_rule_rug_anneal(int frame, rule_t *p, image8_t *fb);
void step_rule_rug_anneal2(int frame, rule_t *p, image8_t *fb);
void step_rule_rug_rug(int frame, rule_t *p, image8_t *fb);
void step_rule_rug_brain(int frame, rule_t *p, image8_t *fb);
void step_rule_shade(int frame, rule_t *p, image8_t *fb);
void step_rule_wave(int frame, rule_t *p, image8_t *fb);
void step_rule_rug_image(int frame, rule_t *p, image8_t *fb);
void step_rule_slip(int frame, rule_t *p, image8_t *fb);
void step_rule_fuse(int frame, rule_t *p, image8_t *fb);
void step_rule_rug_multi(int frame, rule_t *p, image8_t *fb);
void step_rule_rd(int frame, rule_t *p, image8_t *fb);
void step_rule_rd2(int frame, rule_t *p, image8_t *fb);


void self_play() {

      if (0 >= timer) {
	 auto_mode = 1;
	 if (pop_back) {
	    rule.rule = back_to;
	    pop_back = 0;
	    timer = 100;
	 } else if (permuting) {
	    permuting--;
	    cmap_permute_bits();
	    timer = 150;
	 } else if (flipper && (rule_rug_brain == rule.rule) && (R%10)) {
	    switch (flipper) {
	     case 1:
	       rule.brain++;
	       break;
	     case 2:
	       distrib(distrib_new|distrib_continuous|rule_lock, &rule, 0, 0);
	       break;
	     case 3:
	       rule.brain_shift =
		  (R%8) ? (rule.brain_shift + ((R&1) ? -1 : 1)) : 0;
	       break;
	    }
	    timer = 50;
	 } else if (flipper && (rule_rug == rule.rule) && (R%5)) {
	    if (2 == flipper) {
	       rule.rule = rule_rug_image;
	       pop_back = 1;
	       back_to = rule_rug;
	       timer = (R%2) ? 4 : 30;
	       current_image = iclamp(current_image + 1,
				      rule.image_window_size);
	       drive_with_image(current_image);	       
	    }
	 } else if (flipper && (rule_rd == rule.rule) && (R%5)) {
	    timer = 100;
	    switch (flipper) {
	     case 1:
	       if (1) {
		  int im = seq_next_image(rule.seq);
		  file_to_image(im, current_image);
		  drive_with_image(current_image);
	       }
	       break;
	     case 2:
	       rule.brain = R;
	       break;
	     case 3:
	       rule.brain_shift = R;
	       break;
	    }
	 } else if (flipper && (rule_rug_image == rule.rule) && (R%5)) {
	    static here = 0;
	    /* create an echo */
	    if (here = 1-here) {
	       current_image = iclamp(current_image + 1,
				      rule.image_window_size);
	       drive_with_image(current_image);	       
	    }
	    timer = 100;
	    switch (flipper) {
	     case 0:
	       break;
	     case 1:
	       if (1) {
		  int im = seq_next_image(rule.seq);
		  file_to_image(im, current_image);
		  drive_with_image(current_image);
	       }
	       break;
	     case 2:
	       flip_image(rule_lock);
	       break;
	     case 3:
	       rule.rule = rule_rug;
	       pop_back = 1;
	       back_to = rule_rug_image;
	       timer = (R%2) ? 4 : 30;
	       break;
	    }
	 } else if (flipper && (rule_fuse == rule.rule) && (R%10)) {
	    /* timer = 100; */ /* why? */
	    rotate_images();
	    switch (flipper) {
	     case 1:
	       file_to_image(p1++, 0);
	       break;
	     case 2:
	       file_to_image(R, 0);
	       break;
	    }
	 } else if (flipper && (rule_rotorug == rule.rule) && (R%5)) {
	    timer = 100;
	    switch (flipper) {
	     case 1:
	       p1 = R;
	       rule.flame_cp = flames[iclamp(p1, nflames)];
	       break;
	     case 2:
	       random_control_point(&rule.flame_cp);
	       break;
	    }
	 } else if (flipper && (rule_rug_rug == rule.rule) && (R%5)) {
	    distrib(distrib_new|distrib_continuous|rule_lock, &rule, 0, 0);
	    timer = 200;
	 } else if (R%2 && (frame - cmap_changed) > 200) {
	    /* ugh */
	    if (rule.rule == rule_acidlife1) {
	       cmap.cmap = cmap_split;
	       fade2cmap(&cmap);
	    } else if (rule.rule == rule_fuse && R%4) {
	       cmap.cmap = cmap_mono;
	       fade2cmap(&cmap);
	    } else {
	       if (frame > 10000 && !(R%20)) {
		  cmap_permute_bits();
		  permuting = 7;
	       } else {
		  distrib(distrib_new, 0, &cmap, 0);
		  fade2cmap(&cmap);
	       }
	    }
	    timer = 300;
	    cmap_changed = frame;
	 } else if (R%4) {
	    int t = rule_lock;
	    /* this is the same condition as used for overdrive */
	    if (3 == flipper)
	       t |= distrib_rule_lock;
	    distrib(distrib_new | t, &rule, 0, 0);
	    timer = 300;
	    /* ugh */
	    if (rule.rule == rule_acidlife1) {
	       cmap.cmap = cmap_split;
	       fade2cmap(&cmap);
	    }
	 } else {
	    distrib(distrib_new, 0, 0, &fill2);
	    delayed_fill = 1;
	    timer = 300;
	 }
	 flipper = (frame >> 7) & 0x3;
	 if (!(R%20))
	    overdrive_speed = 5 + R%10 + R%20;
	 if (3 == flipper && !auto_rule)
	    timer = overdrive_speed;
      }

      if (auto_mode && !(R%500)) image_dst = !image_dst;
      if (auto_mode && !(frame%1000)) {
	 rule.seq[0] = R;
	 seq_start(rule.seq);
      }

      /* i really don't know how much i like this stuff */
      if (1) {
	 int tween;
	 static int tab1[] = {3000, 200, 100, 50};
	 static int tab2[] = {10000, 200, 100, 50};
	 if (auto_mode)
	    switch (rule.rule) {
	     case rule_slip:
	     case rule_fuse:
	       if (!(R%tab1[image_rate]))
		  image_rate = R&3;
	       break;
	     default:
	       if (frame < 10000)
		  image_rate = 0;
	       else if (!(R%tab2[image_rate]))
		  image_rate = R&3;
	       break;
	    }
	 if (image_rate) {
	    tween = 1 << ((3-image_rate) * 3);
	    if (!(frame%tween)) {
	       if ((3 == flipper2) && (frame > 20000)) {
		  if (image_dst)
		     image_to_heat(pick_image(&rule), &fb, &board[dbuf]);
		  else
		     image_to_heat(pick_image(&rule), &fb, &board2[dbuf]);
	       } else {
		  double_board(image_dst, &board[dbuf], rule_symmetry(rule.rule));
		  if (flipper2&1)
		     image_dst = 1-image_dst;
	       }
	    }
	 }
      }

}

void param_tweeking(int key) {
	       /* parameter tweeking */
	       switch (key) {
		  /* boy this is really dumb */
		case '0': case '1': case '2': case '3': case '4':
		case '5': case '6': case '7': case '8': case '9':
		  param = 10 * param + (key - '0');
		  break;
		case '\n':
		  *gparam = param;
		  param = 0;
		  changed_gparam(gparam);
		  break;
		case '.':
		incr:
		  (*gparam)++;
		  changed_gparam(gparam);
		  break;
		case ',':
		  (*gparam)--;
		  changed_gparam(gparam);
		  break;

#define dogparm(field) \
		  if (gparam == &(field)) goto incr; gparam = &(field); break;

		case 'q': dogparm(cmap.index);
		case 'w': dogparm(rule.speed_beat_size);
		case 'e': dogparm(rule.speed_beat_speed);
		case 'r': dogparm(rule.speed_base);
		case 't': dogparm(rule.image_window_size);
		case 'y': dogparm(rule.seq[0]);
		case 'u': dogparm(rule.remap);
		  /* should skip patterns without enough bits */
		case 'i': dogparm(rule.mask);
		case 'o': dogparm(p1); break;
		case 'p': dogparm(rule.bsize);
		case '[':
		case ']': dogparm(rule.driver_slowdown);
		case '\\':dogparm(rule.search_time);
		case '{': dogparm(rule.hot);
		case '}': dogparm(rule.cool);
		case '|': dogparm(rule.brain);
		case 'P': dogparm(rule.brain_shift);
		case 'O': dogparm(rule.cycle_bkg);
		case 'I': dogparm(rule.randomized_underflow);


		case 'a':
		  if (rule_rug == rule.rule)
		     rule.rule = rule_rug2;
		  else 
		     rule.rule = rule_rug;
		  break;
		case 's':
		  rule.rule = rule_rug_image;
		  break;
		case 'd':
		  if (rule.rule == rule_rd)
			  rule.rule = rule_rd2;
		  else
			  rule.rule =  rule_rd;
		  break;
		case 'f':
		  if (rule.rule == rule_rotorug)
		     rule.drift++;
		  else
		     rule.rule = rule_rotorug;
		  break;
		case 'g':
		  if (rule.rule == rule_acidlife2)
		     rule.rule = rule_acidlife1;
		  else
		     rule.rule = rule_acidlife2;
		  break;
		case 'h':
		  rule.rule = rule_rug_multi;
		  break;
		case 'j':
		  if (rule.rule == rule_rug_anneal)
		     rule.rule = rule_rug_anneal2;
		  else
		     rule.rule = rule_rug_anneal;
		  break;
		case 'k':
		  rule.rule = rule_slip;
		  break;
		case 'l':
		  rule.rule = rule_rug_rug;
		  break;
		case ';':
		  rule.rule = rule_rug_brain;
		  break;
		case '\'':
		  if (rule.rule == rule_shade)
		     rule.rule = rule_wave;
		  else
		     rule.rule = rule_shade;
		  break;
		case 'A':
		  rule.rule = rule_static;
		  break;

		case 'F':
		  if (rule.rule == rule_fuse)
		     rule.drift++;
		  else
		     rule.rule = rule_fuse;
		  break;
		case 'L':
		  rule_lock = rule_lock ? 0 : distrib_rule_lock;
		  break;

		case 'z':
		  distrib(distrib_new | distrib_continuous | rule_lock, &rule, 0, 0);
		  break;
		case 'x':
		  distrib(distrib_new, 0, 0, &fill2);
		  delayed_fill = 1;
		  break;
		case 'c':
		  distrib(distrib_new, 0, &cmap, 0);
		  if (rule.rule == rule_acidlife1)
		     cmap.cmap = cmap_split;
		  else if (rule.rule == rule_fuse)
		     cmap.cmap = cmap_mono; 
		  fade2cmap(&cmap);
		  break;
		case 'v':
		  switch (rule.rule) {
		   case rule_rug:
		   case rule_rug2:
		     rule.floor = 0;
		     rule.cool = 999999; /* XXX */
		     pulse = 1;
		     break;
		   case rule_rug_image:
		     current_image = iclamp(current_image + 1,
					    rule.image_window_size);
		     drive_with_image(current_image);
		     break;
		   case rule_rotorug:
		     if (1) {
			int i;
			random_control_point(&rule.flame_cp);
			pick_liss_coefs();
			init_rotate();
			for (i = 0; i < MAXRHYTHM; i++)
			   rule.rhythm[i] = (R%3 + 1)*8;
		     }
		     break;
		   default:
		     rule.brain++;
		     break;
		   case rule_fuse:
		     rotate_images();
		     file_to_image(seq_next_image(rule.seq), 0);
		     break;
		  }
		  break;
		case 'b':
		  switch (rule.rule) {
		   case rule_rug_brain:
		     rule.brain_shift = rule.brain_shift ? 0 : (R%7);
		     break;
		   case rule_rug_multi:
		     rule.brain_shift++;
		     break;
		   default:
		   case rule_rd:
		     /* maybe this should use seq_next_image, or at least
                        we want the capability (maybe use m) */
		     file_to_image(p1++, current_image);
		     drive_with_image(current_image);
		     break;
		  }
		  break;
		case 'n':
		  rule.speed_beat_size = rule.speed_beat_size ? 0 : (R%22);
		  rule.speed_beat_speed = (R%3) ? (5 + R%10) : (30 + R%30);
		  break;
		case 'm':
		  if (rule_rug_image == rule.rule) {
		     int im = seq_next_image(rule.seq);
		     file_to_image(im, current_image);
		     drive_with_image(current_image);
		  } else
		     rule.brain_shift++;
		  break;
	     
		case '/':
		  drive_with_image(current_image);
		  break;
		case '?':
		  file_to_image(p1, current_image);
		  drive_with_image(current_image);
		  break;
		case '-':
		  *gparam = - (*gparam);
		  changed_gparam(gparam);
		  break;
		case '=':
		  write_fb_ppm(&fb);
		  break;
		case 22: /* ^V */
#if use_mpeg
		  if (periodic_write) {
		     mpeg_end();
		     periodic_write = 0;
		  } else {
		     char buf[20];
		     sprintf(buf, "dribble/%03d.mpg", frame);
		     mpeg_begin(320, 200, buf);
		     periodic_write = 1;
		  }
#else
		  periodic_write = 10 - periodic_write;
#endif
		  break;
		case 'Z':
		  flip_image(rule_lock);
		  break;
		case 'X':
		  /* X = zcm */
		  distrib(distrib_new | distrib_continuous | rule_lock, &rule, &cmap, 0);
		  init_rotate();
		  if (rule.rule == rule_acidlife1)
		     cmap.cmap = cmap_split;
		  fade2cmap(&cmap);
		  random_control_point(&rule.flame_cp);
		  pick_liss_coefs();
		  break;
		case 'Q':
		  timer = 10;
		  break;
		case 'W':
		  pulse_cmap_white();
		  break;
		case 'E':
		  pulse_cmap_black();
		  break;
		case 'R':
		  pulse_cmap_rotate();
		  break;
		case 'T':
		  brighten_cmap();
		  break;
		case 'D':
		  cmap_permute_bits();
		  break;
		case 'V':
		  rule.drift--;
		  break;
		case 'C':
		  invert_board();
		  break;
		case 'N':
		  if (1) {
		     extern smooth_checkers;
		     smooth_checkers = 1;
		  }
		  break;
		case 'S':
		  switch (cmap.cmap) {
		  case cmap_plain: cmap.cmap = cmap_loop; break;
		  case cmap_loop: cmap.cmap = cmap_path; break;
		  case cmap_path: cmap.cmap = cmap_plain; break;
		  }
		  fade2cmap(&cmap);
		  break;
		case 'Y':
		  cool(&fb);
		  break;
		case 'U':
		  warm(&fb);
		  break;
		case 'H':
		  double_board(1, &board[dbuf], rule_symmetry(rule.rule));
		  break;
		case 'J':
		  double_board(0, &board[dbuf], rule_symmetry(rule.rule));
		  break;
		case 2:		/* ^B */
		  rotate_images();
		  file_to_image(R, current_image);
		  drive_with_image(current_image);
		  break;
		case 15:	/* ^O */
		  rotate_images();
		  file_to_image(R, 0);
		  break;
		case 26:	/* ^Z */
		  random_image_set();
		  break;
		case 14:	/* ^N */
		  rule = rules[curs];
		  if (++curs == RULEBUFSIZE) curs = 0;
		  break;
		case 'M':
		  rule.flame_cp = flames[iclamp(p1, nflames)];
		  break;
		case 9:		/* ^I */
		  image_to_heat(pick_image(&rule), &fb, &board[dbuf]);
		  image_dst = 1;
		  break;
		case 21:	/* ^U */
		  image_to_heat(pick_image(&rule), &fb, &board2[dbuf]);
		  image_dst = 0;
		  break;
		case 25:	/* ^Y */
		  image_rate = (image_rate+1)&0x3;
		  break;

		  /*
		    for rbd's ballet music
	       case 'P':
		 rule.speed = -1;
		 rule.speed_base = -1;
		 rule.mask = 767;
		 rule.driver_slowdown = 9;
		 cmap.cmap = cmap_loop;
		 fade2cmap(&cmap);
		 break;
	       case 'O':
		 rule.speed = -1;
		 rule.speed_base = -1;
		 rule.mask = 1023;
		 rule.driver_slowdown = 60;
		 cmap.cmap = cmap_plain;
		 cmap.index = 58;
		 fade2cmap(&cmap);
		 break;
	       case 'K':
		 cmap.cmap = cmap_mono;
		 fade2cmap(&cmap);
		 break;
	       case 'J':
		 cmap.cmap = cmap_heat;
		 fade2cmap(&cmap);
		 break;
		 */
		case 'B':
		  cmap.cmap = cmap_black;
		  fade2cmap(&cmap);
		  break;
		case 18:	/* ^R */
		  dtime++;
		  if (dtime == 8)
		     dtime = 1;
		  break;
	    
		default:
		  printf("unknown key = %d\n", key);
		  break;
	       }
}

void mood_organ(int key) {
	       switch (key) {
		case 'a':
		  rule.rule = rule_rug;
		  rule.speed = -1;
		  rule.speed_base = -1;
		  rule.mask = 255;
		  rule.remap = 0;
		  rule.randomized_underflow = 1;
		  rule.speed_beat_size = 0;
		  rule.hot = 1;
		  rule.cool = 100;
		  cmap.cmap = cmap_plain;
		  fade2cmap(&cmap);
		  break;
		case 'b':
		  rule.rule = rule_rug_image;
		  rule.speed = -1;
		  rule.mask = 255;
		  rule.remap = 0;
		  rule.randomized_underflow = 1;
		  rule.speed_base = -1;
		  rule.speed_beat_size = 10;
		  rule.speed_beat_speed = 50;
		  rule.hot = 1;
		  rule.cool = 100;
		  cmap.cmap = cmap_plain;
		  fade2cmap(&cmap);
		  break;
		case 'c':
		  rule.rule = rule_rug;
		  rule.speed = -1;
		  rule.speed_base = -1;
		  rule.randomized_underflow = 0;
		  rule.mask = 759;
		  rule.remap = 0;
		  rule.hot = 1;
		  rule.cool = 20;
		  rule.speed_beat_size = 0;
		  break;
		case 'd':
		  rule.rule = rule_rug2;
		  rule.speed = -1;
		  rule.randomized_underflow = 1;
		  rule.mask = 1023;
		  rule.remap = 0;
		  rule.hot = 6;
		  rule.cool = 32;
		  rule.speed_base = -1;
		  rule.speed_beat_size = 0;
		  fill2.fill = fill_vnoise;
		  fill_board(&fill2);
		  break;
		case 'e':
		  rule.rule = rule_rug;
		  rule.speed = -1;
		  rule.speed_base = -1;
		  rule.mask = 1791;
		  rule.randomized_underflow = 1;
		  rule.hot = 6;
		  rule.remap = 0;
		  rule.cool = 2;
		  rule.speed_beat_size = 0;
		  fill2.fill = fill_noise;
		  fill_board(&fill2);
		  break;
		case 'f':
		  rule.rule = rule_rug_image;
		  rule.speed = -1;
		  rule.mask = 255;
		  rule.remap = 0;
		  rule.randomized_underflow = 1;
		  rule.speed_base = -1;
		  rule.speed_beat_size = 0;
		  file_to_image(p1++, current_image);
		  drive_with_image(current_image);
		  fill2.fill = fill_noise;
		  fill_board(&fill2);
		  break;
		case 'g':
		  rule.rule = rule_rd;
		  rule.brain = 0;
		  rule.brain_shift = 0;
		  fill2.fill = fill_noise;
		  fill_board(&fill2);
		  cmap.cmap = cmap_loop;
		  fade2cmap(&cmap);
		  break;
		case 'h':
		  rule.rule = rule_rd;
		  rule.brain = 1;
		  rule.brain_shift = 3;
		  fill2.fill = fill_noise;
		  fill_board(&fill2);
		  cmap.cmap = cmap_loop;
		  fade2cmap(&cmap);
		  break;
		case 'i':
		  rule.rule = rule_rd;
		  rule.brain = 1;
		  rule.brain_shift = 5;
		  fill2.fill = fill_noise;
		  fill_board(&fill2);
		  file_to_image(p1++, current_image);
		  drive_with_image(current_image);
		  cmap.cmap = cmap_loop;
		  fade2cmap(&cmap);
		  break;
		case 'j':
		  rule.rule = rule_rotorug;
		  rule.speed = -18;
		  rule.speed_base = -18;
		  rule.speed_beat_size = 0;
		  rule.mask = 367;
		  rule.remap = 1;
		  rule.drift = 0;
		  rule.drift_speed = 13;
		  rule.bsize = 47;
		  cmap.cmap = cmap_plain;
		  fade2cmap(&cmap);
		  break;
		case 'k':
		  rule.rule = rule_rotorug;
		  rule.mask = 255;
		  rule.speed = -1;
		  rule.speed_base = -1;
		  rule.speed_beat_size = 0;
		  rule.drift = 4;
		  rule.drift_speed = 10;
		  rule.bsize = 56;
		  rule.remap = 0;
		  cmap.cmap = cmap_plain;
		  fade2cmap(&cmap);
		  break;
		case 'l':
		  rule.rule = rule_rotorug;
		  rule.mask = 509;
		  rule.speed_beat_size = 0;
		  rule.speed = -11;
		  rule.speed_base = -11;
		  rule.drift = 8;
		  rule.drift_speed = 8;
		  rule.bsize = 89;
		  rule.remap = 0;
		  cmap.cmap = cmap_plain;
		  fade2cmap(&cmap);
		  break;
		case 'm':
		  rule.rule = rule_acidlife2;
		  rule.mask = 1919;
		  rule.speed_beat_size = 9;
		  rule.speed_beat_speed = 40;
		  rule.speed_base = -2;
		  rule.speed = -2;
		  rule.brain = 0;
		  break;
		case 'n':
		  rule.rule = rule_acidlife1;
		  rule.mask = 511;
		  rule.brain = 0;
		  rule.speed_base = -19;
		  rule.speed = -19;
		  cmap.cmap = cmap_split;
		  fade2cmap(&cmap);
		  fill2.fill = fill_noise;
		  fill_board(&fill2);
		  break;
		default:
		  printf("undefined mood = %d\n", key);
		  break;
	       }

}


void
work()
{
  if (!((frame+1)%100)) {
	 int bs;
	 int notty = NULL != getenv("EMACS");
	 fps = 100.0/end_timer();

	 sprintf(status_string, "%04.1lf ", fps);
	    
	 if (!notty)
	    putchar(13);
	 printf ("frame=%d fps=%.3g delay=%.3g      ",
		 frame+1, fps, delay);
	 if (notty)
	    printf("\n");
	 else
	    fflush(stdout);
	 begin_timer();
	 /* here is the target frames per second */
	 delay += 0.2 * (fps - 15.0);
	 if (delay < 0.0) delay = 0.0;
      }
      if (delay >= 1.0 && stabilize_fps) {
#ifdef sgi
	 sginap(quantize(delay));
#elif defined(linux)
	 usleep((int)(delay * 1e4));
#else
	 ;
#endif
      }
	  
   if (pulse) {
	 pulse = 0;
	 rule.floor = 0;
      } else {
	   int n = rule.hot + rule.cool;
	   if (0 == n) rule.floor = 0;
	   else rule.floor = (frame % (rule.hot + rule.cool)) > rule.hot;
      }

      if (periodic_write &&
	  0 == (frame % periodic_write)) {
#if use_mpeg
	 write_fb_mpeg(&fb);
#else
	 write_fb_ppm(&fb);
#endif
      }


      if ( auto_mode && run_hist(&fb) &&
			frame - last_clear > 400) {

		    fill2.fill = fill_noise;
	        delayed_fill = 1;
      }
	  
      step_cmap();
	  

#if win_bomb
	  if (1) {
		  extern int mouse_down, mouse_x, mouse_y;
		  if (mouse_down) {
			  do_mouse(mouse_x, mouse_y);
		  }
	  }
#endif
#if mac_bomb
      if(1) {
        Point p;
        EventRecord gEvent;
        if (GetOSEvent( mDownMask, &gEvent) || StillDown()) {
           GetMouse(&p);
           do_mouse(p.h, p.v);
         }
         GetOSEvent( mUpMask, &gEvent);
      }
#endif

      switch (rule.rule) {
       case rule_rug:
	 step_rule_rug(frame, &rule, &fb);
	 break;
       case rule_rug2:
	 step_rule_rug2(frame, &rule, &fb);
	 break;
       case rule_static:
	 step_rule_static(frame, &rule, &fb);
	 break;
       case rule_rotorug:
	 step_rule_rotorug(frame, &rule, &fb);
	 break;
       case rule_acidlife1:
	 step_rule_acidlife1(frame, &rule, &fb);
	 break;
       case rule_acidlife2:
	 step_rule_acidlife2(frame, &rule, &fb);
	 break;
       case rule_rug_anneal:
	 step_rule_rug_anneal(frame, &rule, &fb);
	 break;
       case rule_rug_anneal2:
	 step_rule_rug_anneal2(frame, &rule, &fb);
	 break;
       case rule_rug_rug:
	 step_rule_rug_rug(frame, &rule, &fb);
	 break;
       case rule_rug_brain:
	 step_rule_rug_brain(frame, &rule, &fb);
	 break;
       case rule_shade:
	 step_rule_shade(frame, &rule, &fb);
	 break;
       case rule_wave:
	 step_rule_wave(frame, &rule, &fb);
	 break;
       case rule_rug_image:
	 step_rule_rug_image(frame, &rule, &fb);
	 break;
       case rule_slip:
	 step_rule_slip(frame, &rule, &fb);
	 break;
       case rule_fuse:
	 step_rule_fuse(frame, &rule, &fb);
	 break;
       case rule_rug_multi:
	 step_rule_rug_multi(frame, &rule, &fb);
	 break;
       case rule_rd:
	 step_rule_rd(frame, &rule, &fb);
	 break;
       case rule_rd2:
	 step_rule_rd2(frame, &rule, &fb);
	 break;
       default:
	 printf("bad rule: %d\n", rule.rule);
	 step_rule_rug(frame, &rule, &fb);
	 break;
      }

      frame++;
      timer = timer - adjust_speed(dtime);
      old_rule = rule.rule;

      if ((key = bomb_getkey()) > 0) {
	 timer = 100000;
	 auto_mode = 0;
	 image_rate = 0;
	 /* kbd_mode independent controls */
	 switch (key) {
	  case ' ':
	    auto_rule = 0;
	    distrib(distrib_new | rule_lock, &rule, &cmap, &fill2);
	    fade2cmap(&cmap);
	    delayed_fill = 1;
	    timer = 2000;
	    break;
	  case '`':
	    print_state(&rule);
	    break;
	  case 6: /* ^F */
		display_fps = !display_fps;
		break;

	  case '1':
	    kbd_mode = 0;
	    break;
	  case '2':
	    kbd_mode = 1;
	    break;
	  case '3':
	    kbd_mode = 2;
	    break;
	  case '4':
	    kbd_mode = 3;
	    break;
		  
	  case 27:
	  case 3:
	    bomb_exit();
	    break;
	  default:
	    switch (kbd_mode) {
	     case 0:
		   param_tweeking(key);
	       break;
	     case 1:
		   mood_organ(key);
	       break;
	     case 2:
	       /* image organ */
	       file_to_image(key, current_image);
	       drive_with_image(current_image);
	       break;
	     case 3:
	       /* color organ */
	       cmap.index = (key>>1)%ncmaps;
	       cmap.cmap = (key&1) ? cmap_plain : cmap_loop;
	       fade2cmap(&cmap);
	       break;
	    }	    
	 }
      }

	  self_play();

      if (rule.drift)
	 rule.drift_time += adjust_speed(rule.drift_speed);

      flipper = (frame >> 9) & 0x3;
      flipper2 = (frame >> 10) & 0x3;

      change_rules(old_rule, rule.rule, &fb);

      if (delayed_fill) {
	      fill_board(&fill2);
	      delayed_fill = 0;
	 	  last_clear = frame;
      }
#if !use_sioux
     image8_flush();
#endif

      if (1) {
         int s;         
  
         if (rule.rule == rule_rotorug) {
           s = get_beat(1);
           if (!sound_present)
             s = 10;
           rule.drift_speed = s;
           rule.drift_time = frame + 1000 + 2*s;
         } else {
             s = get_beat(0);
             if (sound_present) {
               rule.speed = rule.speed_base - (rule.speed_beat_size * s / 3);
           } else {
               rule.speed = rule.speed_base + 0.5 * rule.speed_beat_size *
	  	       sin(2.0 * M_PI * frame / rule.speed_beat_speed);
	  }
	}

#if mac_bomb
        if (0) {
            Rect r;
            r.top = 0;
            r.bottom = 100;
            r.left = 0;
            r.right = 800;
            if (frame%800 == 0)
               EraseRect(&r);
            PenNormal();
            MoveTo(frame%800, 50+s);
            LineTo(frame%800, 50+0);
         }
#endif

#if 0
         if (1 == s) {
            rule.speed = -(R&7);
         }
         if (-1 == s) {
            file_to_image(p1++, current_image);
	    drive_with_image(current_image); 
         }
#endif
	
      }

}

#if !win_bomb
void
main()
{
   init();
   
   while (1) {
	  work();
   }
}
#endif
