/*
  tumult.c

  Tumult - Destroy enemies down tubes before they reach you.

  by Bill Kendrick
  bill@newbreedsoftware.com
  http://www.newbreedsoftware.com/bill/

  September 15, 2001 - October 22, 2001
*/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <SDL.h>
#include <SDL_image.h>
#ifndef NOSOUND
#include <SDL_mixer.h>
#endif


/* Constants: */

#ifndef M_PI
#define M_PI 3.14159
#endif

enum { FALSE, TRUE };

#define LEFT_EDGE   0x0001
#define RIGHT_EDGE  0x0002
#define TOP_EDGE    0x0004
#define BOTTOM_EDGE 0x0008

enum { CMD_PLAY, CMD_QUIT, NUM_CMDS };

#define FPS 30

#define WIDTH 400
#define HEIGHT 400

#define X_WIDTH 200
#define Y_HEIGHT 200
#define Z_DEPTH 400

#define DISTANCE 250
#define ASPECT 80

#define ALLEY_LENGTH 200
#define CRAB_RES 24

#define MAX_STARS 100
#define MAX_EXPLOSION_BITS 100
#define MAX_BULLETS 5
#define MAX_ENEMIES 36


/* Types: */

typedef struct color_type {
  Uint8 r;
  Uint8 g;
  Uint8 b;
} color_type;

typedef struct alley_type {
  int x, y;
} alley_type;

typedef struct star_type {
  int x, y, z;
  color_type color;
} star_type;

typedef struct enemy_type {
  int alive, type;
  int alley, z;
} enemy_type;

typedef struct bullet_type {
  int alive;
  int alley, z;
} bullet_type;

typedef struct explosion_bit_type {
  int alive, time;
  int x, y, z;
  int xm, ym, zm;
} explosion_bit_type;


/* Flying text: */

enum {
  IMG_MASTERFUL,
  IMG_1UP,
  IMG_AUTHOR,
  IMG_TUMULT,
  IMG_CREDITS,
  IMG_PAUSED,
  IMG_SELECT,
  IMG_DIGITALDEATH,
  IMG_RECHARGE,
  NUM_IMAGES
};

char * image_filenames[NUM_IMAGES] = {
  DATA_PREFIX "images/masterful.png",
  DATA_PREFIX "images/1up.png",
  DATA_PREFIX "images/author.png",
  DATA_PREFIX "images/tumult.png",
  DATA_PREFIX "images/credits.png",
  DATA_PREFIX "images/paused.png",
  DATA_PREFIX "images/select.png",
  DATA_PREFIX "images/digitaldeath.png",
  DATA_PREFIX "images/recharge.png"
};


/* Sounds: */

enum {
  SND_BLEEP,
  SND_BUZZ,
  SND_DONK,
  SND_LASER,
  SND_POOF,
  SND_POW,
  SND_TWANG,
  SND_THRUST_LOW,
  SND_THRUST_MEDIUM,
  SND_THRUST_HIGH,
  NUM_SOUNDS
};

char * sound_filenames[NUM_SOUNDS] = {
  DATA_PREFIX "sounds/bleep.wav",         /* Movement */
  DATA_PREFIX "sounds/buzz.wav",          /* Mega-zapper */
  DATA_PREFIX "sounds/donk.wav",
  DATA_PREFIX "sounds/laser.wav",         /* Shooting */
  DATA_PREFIX "sounds/poof.wav",          /* Explosion */
  DATA_PREFIX "sounds/pow.wav",
  DATA_PREFIX "sounds/twang.wav",
  DATA_PREFIX "sounds/thrust-low.wav",    /* Warp */
  DATA_PREFIX "sounds/thrust-medium.wav", /*  ''  */
  DATA_PREFIX "sounds/thrust-high.wav"    /*  ''  */
};


/* Music: (songs) */

enum {
  MUS_TITLE,
  MUS_SELECT,
  MUS_HIGHSCORE,
  MUS_OTHER,
  MUS_GAME1,
  MUS_GAME2,
  MUS_GAME3,
  MUS_GAME4,
  NUM_MUSICS
};

char * music_filenames[NUM_MUSICS] = {
  DATA_PREFIX "music/getzznew.mod",
  DATA_PREFIX "music/diplomatic-intro.mod",
  DATA_PREFIX "music/quarter.mod",
  DATA_PREFIX "music/4est_fulla3s.mod",
  DATA_PREFIX "music/brainscan.mod",
  DATA_PREFIX "music/economy16.mod",
  DATA_PREFIX "music/magic.mod",
  DATA_PREFIX "music/byte_me.mod"
};


/* Global variables: */

SDL_Surface * screen;
SDL_Surface * images[NUM_IMAGES];
#ifndef NOSOUND
Mix_Chunk * sounds[NUM_SOUNDS];
Mix_Music * musics[NUM_MUSICS];
#endif
int use_sound;
float anglex, angley;
float cos_anglex, sin_anglex, cos_angley, sin_angley;
float cos_table[3600], sin_table[3600];
star_type stars[MAX_STARS];
bullet_type bullets[MAX_BULLETS];
enemy_type enemies[MAX_ENEMIES];
explosion_bit_type explosion_bits[MAX_EXPLOSION_BITS];
int level, max_level, alley_loops, num_alleys;
alley_type alley[36];


/* Local prototypes: */

void setshape(void);
int game(void);
void keep_playing_music(void);
int pause_screen(void);
int title(void);
int level_select(void);
void drawline3d(float x1, float y1, float z1, color_type c1,
		float x2, float y2, float z2, color_type c2);
void drawline3d_blur(float x1, float y1, float z1,
		     color_type c1, color_type c1b,
		     float x2, float y2, float z2,
		     color_type c2, color_type c2b);
void drawpoint3d(float x, float y, float z, color_type c, int thick);
int calc3d(int * sx, int * sy, float x, float y, float z);
Uint32 getpixel(SDL_Surface * surface, int x, int y);
void putpixel(SDL_Surface * surface, int x, int y, Uint32 pixel);
void recalctrig(void);
unsigned char encode(float x, float y);
int clip(int * x1, int * y1, int * x2, int * y2);
void drawline(int x1, int y1, color_type c1,
	      int x2, int y2, color_type c2);
void drawvertline(int x, int y1, color_type c1,
		  int y2, color_type c2);
color_type mkcolor(int r, int g, int b);
void setup(int argc, char * argv[]);
void seticon(void);
int sgn(int x);
void init_cos_table(void);
float my_cos(float ang);
float my_sin(float ang);
void set_rand_star(int i, int far);
void add_bullet(int alley);
void playsound(int chan, int snd);
void draw_zap(int alley1, int z1, int alley2, int z2);
void draw_lightning(int x1, int y1, int z1,
		    int x2, int y2, int z2);
void add_explosion(int al1, int z);
void add_explosion_bit(int x, int y, int z);


/* MAIN LOOP: */

int main(int argc, char * argv[])
{
  int cmd, done;
  

  /* Set-up */
  
  setup(argc, argv);

  
  /* Main program loop: */
  
  done = FALSE;

  do
  {
    cmd = title();

    if (cmd == CMD_PLAY)
      done = game();
  }
  while (cmd != CMD_QUIT && !done);
  
  
  /* Close up and quit: */
  
  SDL_Quit();
  
  return(0);
}


/* Level selection screen: */

int level_select(void)
{
  int done, quit, x, y, xx, yy, xoff, exiting, i, j, burn;
  Uint8 r, g, b;
  Uint32 nr, ng, nb;
  Uint32 last_time, now_time, frame;
  SDLKey key;
  SDL_Rect dest;
  SDL_Event event;
  
  
  level = 0;
  setshape();
  
  SDL_FillRect(screen, NULL, SDL_MapRGB(screen->format, 0, 0, 0));
  
  done = FALSE;
  quit = FALSE;
  frame = 0;
  exiting = 0;
  
  burn = 0;

  for (i = 0; i < MAX_STARS; i++)
    {
      stars[i].x = rand() % WIDTH;
      stars[i].y = ((rand() % (HEIGHT - (images[IMG_SELECT]->h + 10))) +
		    (images[IMG_SELECT]->h + 10));
      stars[i].z = (rand() % 10);
      
      stars[i].color =
	mkcolor(255 - ((11 - stars[i].z) * 3),
		255 - ((11 - stars[i].z) * 3),
		255 - ((11 - stars[i].z) * 3));
    }
  
  
  angley = 0;
  recalctrig();
  
  do
    {
      frame++;
      last_time = SDL_GetTicks();
      

      while (SDL_PollEvent(&event) > 0)
	{
	  if (event.type == SDL_QUIT)
	    {
	      done = TRUE;
	      quit = TRUE;
	    }
	  else if (event.type == SDL_KEYDOWN)
	    {
	      key = event.key.keysym.sym;
	      
	      if (key == SDLK_RETURN)
		{
		  exiting = images[IMG_SELECT]->h / 2;
		}
	      else if (key == SDLK_ESCAPE)
		{
		  done = TRUE;
		}
	      else if (key == SDLK_LEFT && exiting == 0)
		{
		  if (level > 0)
		    {
		      level--;
		      setshape();
		      playsound(0, SND_LASER);
		      
		      burn = 3;
		    }
		}
	      else if (key == SDLK_RIGHT && exiting == 0)
		{
		  if (level < max_level)
		    {
		      level++;
		      setshape();
		      playsound(0, SND_LASER);
		      
		      burn = 3;
		    }
		}
	    }
	}

      
      /* Lock surface: */
      
      if (SDL_MUSTLOCK(screen))
	SDL_LockSurface(screen);

      
      for (y = 1; y < images[IMG_SELECT]->h + 10; y++)
	{
	  xoff = (rand() % 3) - 1;
	  
	  for (x = 0; x < WIDTH; x++)
	    {
	      nr = 0;
	      ng = 0;
	      nb = 0;
	      
	      
	      for (yy = 0; yy <= 1; yy++)
		{
		  for (xx = xoff - 1; xx <= xoff + 1; xx++)
		    {
		      SDL_GetRGB(getpixel(screen, x + xx,
					  y + yy),
				 screen->format, &r, &g, &b);
		      
		      
		      nr += (r + ((g + b) / 6));
		      ng += g;
		      nb += b;
		    }
		}
	      
	      nr = nr / (7 - burn);
	      ng = ng / (8 - burn);
	      nb = nb / (10 - burn);
	      
	      if (nr >= 0xFF)
		nr = 0xFF;
	      
	      
	      putpixel(screen, x, y - 1,
		       SDL_MapRGB(screen->format,
				  (Uint8) nr,
				  (Uint8) ng,
				  (Uint8) nb));
	    }
	}
      
      
      if (burn > 0)
	burn--;
      
      
      /* Unlock temp. surface: */

      if (SDL_MUSTLOCK(screen))
	SDL_UnlockSurface(screen);
      
      
      /* Draw "Select" text: */
      
      if (!exiting)
	{
	  dest.x = (WIDTH - images[IMG_SELECT]->w) / 2;
	  dest.y = 10;
	  dest.w = images[IMG_SELECT]->w;
	  dest.h = images[IMG_SELECT]->h;
	  
	  SDL_BlitSurface(images[IMG_SELECT], NULL,
			  screen, &dest);
	}
      else
	{
	  exiting--;
	  
	  if (exiting == 0)
	    done = TRUE;
	}
      
      
      dest.x = 0;
      dest.y = (images[IMG_SELECT]->h + 10);
      dest.w = WIDTH;
      dest.h = HEIGHT - dest.y;
      
      SDL_FillRect(screen, &dest,
		   SDL_MapRGB(screen->format, 0, 0, 0));

      
      /* Draw stars: */
      
      for (i = 0; i < MAX_STARS; i++)
	{
	  drawline(stars[i].x, stars[i].y, mkcolor(0, 0, 0),
		   stars[i].x - stars[i].z, stars[i].y, stars[i].color);
      
	  stars[i].x = stars[i].x - stars[i].z;
	  
	  if (stars[i].x < 0)
	    stars[i].x += WIDTH;
	}

      
      /* Draw level shape: */
      
      if (level > 0)
	{
	  level = level - 1;
	  setshape();
	  
	  anglex = 0;
	  angley = 0;
	  recalctrig();
	  
	  for (i = 0; i < num_alleys; i++)
	    {
	      if (i < num_alleys - 1)
		{
		  drawline3d(alley[i].x - 200, alley[i].y, 0,
			     mkcolor(0, 128, 0),
			     alley[i + 1].x - 200, alley[i + 1].y, 0,
			     mkcolor(0, 64, 0));
		}
	      else if (alley_loops)
		{
		  drawline3d(alley[i].x - 200, alley[i].y, 0,
			     mkcolor(0, 128, 0),
			     alley[0].x - 200, alley[0].y, 0,
			     mkcolor(0, 64, 0));
		}
	    }
	  
	  level++;
	  setshape();
	}
      
      
      for (j = 5; j >= 1; j--)
	{
	  anglex = 0;
	  angley = frame * 10 + 20 - (j * 5);
	  recalctrig();
	  
	  for (i = 0; i < num_alleys; i++)
	    {
	      if (i < num_alleys - 1)
		{
		  drawline3d(alley[i].x, alley[i].y, -125,
			     mkcolor(0, 255 / j, 0),
			     alley[i + 1].x, alley[i + 1].y, -125,
			     mkcolor(0, 192 / j, 0));
		}
	      else if (alley_loops)
		{
		  drawline3d(alley[i].x, alley[i].y, -125,
			     mkcolor(0, 255 / j, 0),
			     alley[0].x, alley[0].y, -125,
			     mkcolor(0, 192 / j, 0));
		}
	    }
	}


      if (level < max_level)
	{
	  level = level + 1;
	  setshape();
	  
	  anglex = 0;
	  angley = 0;
	  recalctrig();
	  
	  for (i = 0; i < num_alleys; i++)
	    {
	      if (i < num_alleys - 1)
		{
		  drawline3d(alley[i].x + 200, alley[i].y, 0,
			     mkcolor(0, 128, 0),
			     alley[i + 1].x + 200, alley[i + 1].y, 0,
			     mkcolor(0, 64, 0));
		}
	      else if (alley_loops)
		{
		  drawline3d(alley[i].x + 200, alley[i].y, 0,
			     mkcolor(0, 128, 0),
			     alley[0].x + 200, alley[0].y, 0,
			     mkcolor(0, 64, 0));
		}
	    }
	  
	  level--;
	  setshape();
	}
      


      /* Play title screen music: */
      
#ifndef NOSOUND
      if (use_sound)
	{
	  if (!Mix_PlayingMusic())
	    Mix_PlayMusic(musics[MUS_SELECT], 0);
	}
#endif


      /* Pause: */
      
      SDL_Flip(screen);
      
      now_time = SDL_GetTicks();

      if (now_time < last_time + (1000 / FPS))
      {
        SDL_Delay(last_time + 1000 / FPS - now_time);
      }
    }
  while (!done);

#ifndef NOSOUND
  if (use_sound)
    {
      Mix_HaltMusic();
    }
#endif


  return(quit);
}


/* Title screen: */

int title(void)
{
  int done, cmd, i, cz, fz, r1, g1, b1, r2, g2, b2, frame, fadein;
  Uint32 last_time, now_time;
  SDLKey key;
  SDL_Rect dest, src;
  SDL_Event event;
  
  
  done = FALSE;
  cmd = 0;
  frame = 0;
  
  level = -1;
  setshape(); 
 
  cz = Z_DEPTH * 2;
  fz = Z_DEPTH * 2;
  fadein = 100;
  
  anglex = 0;
  angley = 0;
  recalctrig();
  
  
  /* Title screen loop: */
  
  do
    {
      frame++;
      last_time = SDL_GetTicks();


      while (SDL_PollEvent(&event) > 0)
	{
	  if (event.type == SDL_QUIT)
	    {
	      done = TRUE;
	      cmd = CMD_QUIT;
	    }
          else if (event.type == SDL_KEYDOWN)
	    {
	      key = event.key.keysym.sym;
	      
	      if (key == SDLK_RETURN)
		{
		  done = TRUE;
		}
	      else if (key == SDLK_ESCAPE)
		{
		  cmd = CMD_QUIT;
		  done = TRUE;
		}
	    }
	}
      
      
      if (cz > -50)
	{
	  cz = cz - 10;
	}
      else
	{
	  if (fz > 50)
	    {
	      fz = fz - 20;
	    }
	  else
	    {
	      anglex = anglex + 5;
	      recalctrig();
	    }
	}
      
      
      /* Clear screen: */
      
      SDL_FillRect(screen, NULL, SDL_MapRGB(screen->format, 0, 0, 0));
      
      
      /* Draw "T" logo: */
      
      for (i = 0; i < num_alleys; i++)
	{
	  r1 = ((i + frame) * 20) % 255;
	  g1 = ((num_alleys - ((i + frame) % num_alleys)) * 10) % 255;
	  b1 = (255 - ((i * i) % 255));
	  
	  drawline3d(alley[i].x, alley[i].y, cz,
		     mkcolor(r1, g1, b1),
		     alley[i].x, alley[i].y, fz,
		     mkcolor(r1 / 2, g1 / 2, b1 / 2));
	  
	  if (i < num_alleys - 1)
	    {
	      r2 = (((i + 1) + frame) * 20) % 255;
	      g2 = ((num_alleys -
		     (((i + 1) + frame) % num_alleys)) * 10) % 255;
	      b2 = (255 - (((i + 1) * (i + 1)) % 255));
	  
	      drawline3d(alley[i].x, alley[i].y, cz,
			 mkcolor(r1, g1, b1),
			 alley[i + 1].x, alley[i + 1].y, cz,
			 mkcolor(r2, g2, b2));
	      
	      drawline3d(alley[i].x, alley[i].y, fz,
			 mkcolor(r1 / 2, g1 / 2, b1 / 2),
			 alley[i + 1].x, alley[i + 1].y, fz,
			 mkcolor(r2 / 2, g2 / 2, b2 / 2));
	    }
	  else
	    {
	      r2 = ((frame) * 20) % 255;
	      g2 = ((num_alleys - ((frame) % num_alleys)) * 10) % 255;
	      b2 = 255;
	  
	      drawline3d(alley[i].x, alley[i].y, cz,
			 mkcolor(r1, g1, b1),
			 alley[0].x, alley[0].y, cz,
			 mkcolor(r2, g2, b2));
	      
	      drawline3d(alley[i].x, alley[i].y, fz,
			 mkcolor(r1 / 2, g1 / 2, b1 / 2),
			 alley[0].x, alley[0].y, fz,
			 mkcolor(r2 / 2, g2 / 2, b2 / 2));
	  }
	}
      
      
      /* Draw title and credits: */
      
      if (fz <= 50)
	{
	  if (fadein > 0)
	    {
	      /* Fade title in: */
	      
	      fadein--;
	      
	      for (i = (frame % ((fadein / 2) + 1));
		   i < images[IMG_TUMULT]->h;
		   i = i + ((fadein / 2) + 1))
		{
		  dest.x = (((WIDTH - images[IMG_TUMULT]->w) / 2) +
			    my_cos(i * 2 + frame * 10) * fadein);
		  dest.y = i;
		  dest.w = images[IMG_TUMULT]->w;
		  dest.h = 1;
		  
		  src.x = 0;
		  src.y = i;
		  src.w = images[IMG_TUMULT]->w;
		  src.h = 1;
		  
		  SDL_BlitSurface(images[IMG_TUMULT], &src,
				  screen, &dest);
		}
	    }
	  else
	    {
	      dest.x = (WIDTH - images[IMG_TUMULT]->w) / 2;
	      dest.y = 0;
	      dest.w = images[IMG_TUMULT]->w;
	      dest.h = images[IMG_TUMULT]->h;
	      
	      SDL_BlitSurface(images[IMG_TUMULT], NULL,
			      screen, &dest);
	      
	      
	      dest.x = (WIDTH - images[IMG_CREDITS]->w) / 2;
	      dest.y = (HEIGHT - images[IMG_CREDITS]->h);
	      dest.w = images[IMG_CREDITS]->w;
	      dest.h = images[IMG_CREDITS]->h;
	      
	      SDL_BlitSurface(images[IMG_CREDITS], NULL,
			      screen, &dest);
	    }
	}
      
      
      
      /* Play title screen music: */
      
#ifndef NOSOUND
      if (use_sound)
	{
	  if (!Mix_PlayingMusic())
	    Mix_PlayMusic(musics[MUS_TITLE], 0);
	}
#endif


      /* Pause: */
      
      SDL_Flip(screen);
      
      now_time = SDL_GetTicks();

      if (now_time < last_time + (1000 / FPS))
      {
        SDL_Delay(last_time + 1000 / FPS - now_time);
      }
    }
  while (!done);
  
#ifndef NOSOUND
  if (use_sound)
    {
      Mix_HaltMusic();
    }
#endif
  
  return cmd;
}


/* Init the trig tables: */

void init_cos_table(void)
{
  int i;

  for (i = 0; i < 3600; i++)
  {
    cos_table[i] = cos(M_PI * (i / 10.0) / 180.0);
    sin_table[i] = sin(M_PI * (i / 10.0) / 180.0);
  }
}


/* COS using trig tables: */

float my_cos(float ang)
{
  while (ang < 0.0)
    ang = ang + 360.0;

  while (ang >= 360.0)
    ang = ang - 360.0;

  return cos_table[(int) ang * 10];
}


/* SIN using trig tables: */

float my_sin(float ang)
{
  while (ang < 0.0)
    ang = ang + 360.0;

  while (ang >= 360.0)
    ang = ang - 360.0;

  return sin_table[(int) ang * 10];
}


/* Set random star: */

void set_rand_star(int i, int far)
{
  stars[i].x = (rand() % (X_WIDTH * 8)) - (X_WIDTH * 4);
  stars[i].y = (rand() % (Y_HEIGHT * 8)) - (Y_HEIGHT * 4);
  if (far)
    stars[i].z = stars[i].z + (Z_DEPTH * 3);
  else
    stars[i].z = (rand() % (Z_DEPTH * 3)) - Z_DEPTH;
  stars[i].color = mkcolor((rand() % 3) * 127,
		           (rand() % 3) * 127,
			   (rand() % 3) * 127);
}


/* Decide the shape for this level's tunnel: */

void setshape(void)
{
  int i;
  
  
  if (level == 0)
    {
      num_alleys = 36;
      for (i = 0; i < 36; i++)
	{
	  alley[i].x = my_cos(i * 10) * 100 + my_sin(i * 40) * 20;
	  alley[i].y = my_sin(i * 10) * 100;
	}
      alley_loops = TRUE;
    }
  else if (level == 1)
    {
      num_alleys = 18;
      for (i = 0; i < 18; i++)
	{
	  alley[i].x = (9 - i) * 10;
	  alley[i].y = 100;
	}
      alley_loops = FALSE;
    }
  else if (level == 2)
    {
      num_alleys = 17;
      for (i = 0; i < 17; i++)
	{
	  alley[i].x = (9 - i) * 10;
	  alley[i].y = 100 - abs(i - 8) * 10;
	}
      alley_loops = FALSE;
    }
  else if (level == 3)
    {
      num_alleys = 32;
      for (i = 0; i < 17; i++)
	{
	  alley[i].x = (9 - i) * 10;
	  alley[i].y = 100 - abs(i - 8) * 10;
	  alley[32 - i].x = (9 - i) * 10;
	  alley[32 - i].y = -100 + abs(i - 8) * 15;
	}
      alley_loops = TRUE;
    }
  else if (level == -1)
    {
      /* (Title screen shape) */
      
      num_alleys = 18;
      
      alley[0].x = 0;
      alley[0].y = 160;
      
      alley[1].x = -50;
      alley[1].y = 200;
      
      alley[2].x = -40;
      alley[2].y = 100;

      alley[3].x = -30;
      alley[3].y = 0;

      alley[4].x = -20;
      alley[4].y = -100;

      alley[5].x = -150;
      alley[5].y = -80;

      alley[6].x = -140;
      alley[6].y = -120;

      alley[7].x = -150;
      alley[7].y = -160;

      alley[8].x = -100;
      alley[8].y = -140;

      
      /* (Right side is a mirror-image) */
      
      for (i = 0; i < 9; i++)
	{
	  alley[17 - i].x = -alley[i].x;
	  alley[17 - i].y = alley[i].y;
	}
      
      alley_loops = TRUE;
    }
}


/* MAIN GAME FUNCTION! */

int game(void)
{
  int done, quit;
  Uint32 frame;
  SDL_Event event;
  SDL_Rect dest;
  SDLKey key;
  Uint32 last_time, now_time;
  Uint8 r, g, b;
  int i, j, levelzoom, levelzoomm, oldcrab, crab, crabd,
    oldx, oldy, red, rx, ry, fire, lastfire, warp, warpm,
    al1, al2, shimmy, flying_img, flying_z, flying_flaming, x, y,
    zapping;
  color_type tmp_color;
  float scale;
  

  level_select();
  setshape();
  
  for (i = 0; i < MAX_STARS; i++)
    set_rand_star(i, FALSE);

  for (i = 0; i < MAX_BULLETS; i++)
    bullets[i].alive = FALSE;

  for (i = 0; i < MAX_ENEMIES; i++)
    enemies[i].alive = FALSE;
  
  for (i = 0; i < MAX_EXPLOSION_BITS; i++)
    explosion_bits[i].alive = FALSE;
  
  anglex = 0;
  angley = 0;
  recalctrig();
 
  levelzoom = 1000;
  levelzoomm = -10;
  crab = 0;
  oldcrab = 0;
  fire = FALSE;
  lastfire = 0;
  oldx = 0;
  oldy = 0;
  frame = 0;
  warp = 0;
  warpm = 0;
  shimmy = 0;
  flying_img = -1;
  flying_z = 0;
  flying_flaming = FALSE;
  zapping = 0;
  
  done = FALSE;
  quit = FALSE;
  
  
  /* MAIN GAME LOOP! */
  
  do
    {
      frame++;
      last_time = SDL_GetTicks();
      
      while (SDL_PollEvent(&event) > 0)
	{
	  if (event.type == SDL_QUIT)
          {
	    quit = TRUE;
          }
          else if (event.type == SDL_KEYDOWN)
          {
            key = event.key.keysym.sym;

            if (key == SDLK_ESCAPE)
              done = TRUE;
	    else if (key == SDLK_TAB)
	      warpm = 2;
	    else if (key == SDLK_1)
	      shimmy = 10;
	    else if (key == SDLK_2)
	      {
		flying_img = IMG_MASTERFUL;
		flying_z = 400;
		flying_flaming = FALSE;
	      }
	    else if (key == SDLK_3)
	      {
		flying_img = IMG_1UP;
		flying_z = 400;
		flying_flaming = TRUE;
	      }
            else if (key == SDLK_4)
              {
                flying_img = IMG_AUTHOR;
                flying_z = 400;
		flying_flaming = TRUE;
              }
	    else if (key == SDLK_5)
	      {
		flying_img = IMG_RECHARGE;
		flying_z = 400;
		flying_flaming = FALSE;
	      }
            else if (key == SDLK_p)
	      {
		quit = pause_screen();
	      }
          }
	  else if (event.type == SDL_MOUSEMOTION)
          {
            if (event.motion.y < HEIGHT / 2)
              crabd = -(oldx - event.motion.x);
	    else
              crabd = (oldx - event.motion.x);

	    if (event.motion.x < WIDTH / 2)
	      crabd = crabd + (oldy - event.motion.y);
	    else
	      crabd = crabd - (oldy - event.motion.y);

	    
	    oldx = event.motion.x;
	    oldy = event.motion.y;

	    if (crabd < -CRAB_RES)
              crabd = -CRAB_RES;
	    else if (crabd > CRAB_RES)
	      crabd = CRAB_RES;

	    crab = crab + crabd;
	    
	    if (alley_loops)
	      {
		if (crab < 0)
		  crab = crab + (num_alleys * CRAB_RES);
		else if (crab >= (num_alleys * CRAB_RES) - 1)
		  crab = crab - ((num_alleys * CRAB_RES) - 1);
	      }
	    else
	      {
		if (crab < 0)
		  crab = 0;
		else if (crab >= (num_alleys - 1) * CRAB_RES)
		  crab = ((num_alleys - 1) * CRAB_RES) - 1;
	      }
	  }
	  else if (event.type == SDL_MOUSEBUTTONDOWN)
	    {
	      if (event.button.button == 1)
		fire = TRUE;
	      else if (event.button.button == 3)
		{
		  flying_img = IMG_DIGITALDEATH;
		  flying_z = 400;
		  flying_flaming = FALSE;
		  
		  zapping = 20;
		}
	    }
	  else if (event.type == SDL_MOUSEBUTTONUP)
	    {
	      if (event.button.button == 1)
		fire = FALSE;
	    }
	}
      
      
      /* Play crab-moving sound: */
      
      if ((crab / CRAB_RES) != (oldcrab / CRAB_RES))
	{
	  playsound(0, SND_BLEEP);
	}
      
      oldcrab = crab;
      
      
      /* Fire! */

      if (fire && (frame - lastfire >= 4))
	{
	  if (crab / CRAB_RES < num_alleys)
	    add_bullet(crab / CRAB_RES);
	  else
	    add_bullet(0);
	  
	  lastfire = frame;
	}


      /* Rotate view to keep focus on crab: */

      if (anglex < -(alley[crab / CRAB_RES].x) / 10)
	anglex = anglex + 0.5;
      else if (anglex > -(alley[crab / CRAB_RES].x) / 10)
	anglex = anglex - 0.5;

      if (angley < -(alley[crab / CRAB_RES].y) / 10)
	angley = angley + 0.5;
      else if (angley > -(alley[crab / CRAB_RES].y) / 10)
	angley = angley - 0.5;
      
      recalctrig();


      /* Move flying text: */
      
      if (flying_img != -1)
	{
	  flying_z = flying_z - 15;
	  if (flying_z <= -Z_DEPTH)
	    flying_img = -1;
	}


      /* Move stars: */

      for (i = 0; i < MAX_STARS; i++)
      {
	stars[i].z = stars[i].z - 5 - warp;
	
	if (stars[i].z < -Z_DEPTH)
          set_rand_star(i, TRUE);
      }
      
      
      /* Move explosion bits: */
      
      for (i = 0; i < MAX_EXPLOSION_BITS; i++)
	{
	  if (explosion_bits[i].alive == TRUE)
	    {
	      explosion_bits[i].x += explosion_bits[i].xm;
	      explosion_bits[i].y += explosion_bits[i].ym;
	      explosion_bits[i].z += explosion_bits[i].zm;
	      
	      explosion_bits[i].time--;
	      
	      if (explosion_bits[i].time <= 0)
		explosion_bits[i].alive = FALSE;
	    }
	}
      
      
      /* Handle warp: */
      
      if (warpm != 0)
	{
	  warp = warp + warpm;
	  
	  if (warpm > 0)
	    levelzoom = levelzoom - 10;
	  
	  if (warp > 255)
	    {
	      /* Un-warp: */
	      
	      warpm = -5;

	      levelzoom = 1000;
	      
	      level++;
	      setshape();


	      for (i = 0; i < MAX_ENEMIES; i++)
		{
		  enemies[i].alive = FALSE;
		}

#ifndef NOSOUND
              if (use_sound)
              {
                Mix_HaltMusic();
              }
#endif
	    }
	  else if (warp <= 0)
	    {
	      /* Stop warp: */
	      
	      warp = 0;
	      warpm = 0;

	      levelzoomm = -10;
	    }
	  
	  if ((frame % 10) == 0)
	    playsound(2, SND_THRUST_LOW + warp / 86);
	}
      
      
      /* Handle shimmying: */
      
      if (shimmy > 0)
	shimmy--;


      /* Move bullets: */

      for (i = 0; i < MAX_BULLETS; i++)
      {
	if (bullets[i].alive)
	{
          bullets[i].z = bullets[i].z + 10;

	  if (bullets[i].z > ALLEY_LENGTH)
            bullets[i].alive = FALSE;
	}
      }


      /* Move enemies: */
      
      for (i = 0; i < MAX_ENEMIES; i++)
	{
	  if (enemies[i].alive)
	    {
	      if (enemies[i].z > 0)
		enemies[i].z = enemies[i].z - 1;
	      
	      
	      /* Check for collision with bullets: */
	      
	      for (j = 0; j < MAX_BULLETS; j++)
		{
		  if (bullets[j].alive &&
		      bullets[j].alley == enemies[i].alley &&
		      bullets[j].z >= enemies[i].z - 5 &&
		      bullets[j].z <= enemies[i].z + 5)
		    {
		      bullets[j].alive = 0;
		      enemies[i].alive = 0;
		      
		      playsound(2, SND_POOF);
		      add_explosion(enemies[i].alley, enemies[i].z);
		    }
		}
	      
	      
	      if (zapping == 1)
		{
		  enemies[i].alive = FALSE;
		  add_explosion(enemies[i].alley, enemies[i].z);
		}
	    }
	  else
	    {
	      if ((rand() % 100) == 0 && warpm == 0 && levelzoomm == 0)
		{
		  enemies[i].alive = 1;
		  enemies[i].alley = (rand() % num_alleys);
		  enemies[i].z = ALLEY_LENGTH;
		}
	    }
	}
      
      
      /* Handle mega-zapper: */
      
      if (zapping > 0)
	{
	  zapping--;
	  
	  if ((zapping % 2) == 0)
	    {
	      playsound(3, SND_BUZZ);
	    }
	}
      

      /* Clear screen: */    
      
      if (!shimmy)
	{
	  if (warp == 0)
	    SDL_FillRect(screen, NULL, SDL_MapRGB(screen->format, 0, 0, 0));
	  else
	    SDL_FillRect(screen, NULL, SDL_MapRGB(screen->format,
						  0, 0, warp / 4));
	}
      else
	{
	  dest.x = -shimmy + ((shimmy * 2) * (frame % 2));
	  dest.y = 0;
	  dest.w = WIDTH;
	  dest.h = HEIGHT;
	  
	  SDL_BlitSurface(screen, NULL, screen, &dest);
	}



      if (SDL_MUSTLOCK(screen))
	SDL_LockSurface(screen);
      
      
      /* Draw stars: */
      
      for (i = 0; i < MAX_STARS; i++)
      {
	scale = (float)(Z_DEPTH * 2 - stars[i].z) / (float)(Z_DEPTH * 3);
	
	if (warp == 0)
	  {
	    tmp_color = mkcolor(stars[i].color.r * scale,
				stars[i].color.g * scale,
				stars[i].color.b * scale);
	  }
	else
	  {
	    tmp_color = mkcolor(stars[i].color.r - warp / 2,
				stars[i].color.g - warp / 2,
				stars[i].color.b + warp);
	  }
	
	drawline3d(stars[i].x, stars[i].y, stars[i].z, tmp_color,
		   stars[i].x, stars[i].y, stars[i].z - 5 - warp, tmp_color);
      }
      
      
      /* Draw explosion bits: */
      
      for (i = 0; i < MAX_EXPLOSION_BITS; i++)
	{
	  if (explosion_bits[i].alive &&
	      ((rand() % explosion_bits[i].time) > 1))
	    {
	      drawpoint3d(explosion_bits[i].x,
			  explosion_bits[i].y,
			  explosion_bits[i].z + levelzoom,
			  mkcolor(128 * (rand() % 3),
				  255,
				  128 * (rand() % 3)),
			  TRUE);
	    }
	}
    

      /* Draw tunnel: */
     
      for (i = 0; i < num_alleys; i++)
      {
	red = 0;

	if (crab / CRAB_RES == i || crab / CRAB_RES == i - 1)
          red = 1;
	
	drawline3d(alley[i].x, alley[i].y, levelzoom,
		   mkcolor(red * 255, 255, 0),
	           alley[i].x, alley[i].y, ALLEY_LENGTH + levelzoom,
		   mkcolor(red * 64, 64, 0));

	if (crab / CRAB_RES == i)
	  red = 1;
	else
	  red = 0;

	if (i < num_alleys - 1)
	  {
	    drawline3d(alley[i].x, alley[i].y, levelzoom,
		       mkcolor(red * 255, 255, 0),
		       alley[i + 1].x, alley[i + 1].y, levelzoom,
		       mkcolor(red * 255, 255, 0));
	    
	    drawline3d(alley[i].x, alley[i].y,
		       ALLEY_LENGTH + levelzoom,
		       mkcolor(red * 64, 64, 0),
		       alley[i + 1].x, alley[i + 1].y,
		       ALLEY_LENGTH + levelzoom,
		       mkcolor(red * 64, 64, 0));
	  }
	else if (alley_loops)
	  {
	    drawline3d(alley[i].x, alley[i].y, levelzoom,
		       mkcolor(red * 255, 255, 0),
		       alley[0].x, alley[0].y, levelzoom,
		       mkcolor(red * 255, 255, 0));
	    
	    drawline3d(alley[i].x, alley[i].y,
		       ALLEY_LENGTH + levelzoom,
		       mkcolor(red * 64, 64, 0),
		       alley[0].x, alley[0].y,
		       ALLEY_LENGTH + levelzoom,
		       mkcolor(red * 64, 64, 0));
	  }
      }

      
      /* Draw enemies: */
     
      for (i = 0; i < MAX_ENEMIES; i++)
	{
	  if (enemies[i].alive)
	    {
	      al1 = enemies[i].alley;
	      
	      if (enemies[i].alley + 1 < num_alleys)
		al2 = enemies[i].alley + 1;
	      else
		al2 = 0;
	      
	      drawline3d(alley[al1].x, alley[al1].y, enemies[i].z + levelzoom,
			 mkcolor(255, 0, 0),
			 alley[al2].x, alley[al2].y, enemies[i].z + levelzoom,
			 mkcolor(255, 0, 0));
	    }
	}


      /* Draw bullets: */

      for (i = 0; i < MAX_BULLETS; i++)
      {
	if (bullets[i].alive)
	{
          tmp_color = mkcolor(255 - (rand() % 64),
			      255 - (rand() % 64),
			      255 - (rand() % 64));

	  rx = (rand() % 5) + 5;
	  ry = (rand() % 5) + 5;


	  al1 = bullets[i].alley;
	  if (al1 < num_alleys - 1)
	    al2 = al1 + 1;
	  else
	    al2 = 0;
	  
          for (j = 0; j < 3; j++)
	  {
            drawline3d((alley[al1].x + alley[al2].x) / 2 - rx,
		       (alley[al1].y + alley[al2].y) / 2 - ry,
		       bullets[i].z + (rand() % 20) - 10 + levelzoom,
		       tmp_color,
                       (alley[al1].x + alley[al2].x) / 2 + rx,
		       (alley[al1].y + alley[al2].y) / 2 + ry,
		       bullets[i].z + (rand() % 20) - 10 + levelzoom,
		       tmp_color);

            drawline3d((alley[al1].x + alley[al2].x) / 2 + rx,
		       (alley[al1].y + alley[al2].y) / 2 - ry,
		       bullets[i].z + (rand() % 20) - 10 + levelzoom,
		       tmp_color,
                       (alley[al1].x + alley[al2].x) / 2 - rx,
		       (alley[al1].y + alley[al2].y) / 2 + ry,
		       bullets[i].z + (rand() % 20) - 10 + levelzoom,
		       tmp_color);
	  }
	}
      }


      /* Draw Mega-Zapper */
      
      if (zapping > 0)
	{
	  for (i = 0; i < MAX_ENEMIES; i++)
	    {
	      if (enemies[i].alive && ((rand() % 4) == 0))
		{
		  draw_zap(crab / CRAB_RES, levelzoom,
			   enemies[i].alley, enemies[i].z + levelzoom);
		}
	    }
	}
      
      
      /* Handle zoom: */
      
      if (levelzoom > -250 && levelzoomm < 0)
	{
	  levelzoom = levelzoom + levelzoomm;
	  if (levelzoom <= -250)
	    {
	      levelzoom = -250;
	      levelzoomm = 2;
	    }
	}
      else if (levelzoom < -200 && levelzoomm > 0)
	{
	  levelzoom = levelzoom + levelzoomm;
	  if (levelzoom >= -200)
	    {
	      levelzoom = -200;
	      levelzoomm = 0;
	    }
	}
      
      
      /* Draw any flying text: */
      
      if (flying_img != -1)
	{
	  if (flying_flaming)
	    {
	      for (i = 3; i >= 1; i--)
		{
		  for (y = (frame % 4); y < images[flying_img]->h; y = y + 4)
		    {
		      for (x = ((frame / 2) % 4);
			   x < images[flying_img]->w; x = x + 4)
			{
			  SDL_GetRGB(getpixel(images[flying_img], x, y),
				     images[flying_img]->format, &r, &g, &b);
			  
			  r = r / (i);
			  g = g / (i + 1);
			  b = b / (i + 1);
			  
			  if (r > 5 || g > 5 || b > 5)
			    {
			      drawpoint3d(x - (images[flying_img]->w / 2),
					  y - (images[flying_img]->h / 2) -
					  (20 * i + rand() % 5),
					  flying_z + 10 * i + (rand() % 5),
					  mkcolor(r, g, b), FALSE);
			    }
			}
		    }
		}
	    }
	  
	  
	  for (y = (frame % 2); y < images[flying_img]->h; y = y + 2)
	    {
	      for (x = ((frame / 2) % 2); x < images[flying_img]->w; x = x + 2)
		{
		  SDL_GetRGB(getpixel(images[flying_img], x, y),
			     images[flying_img]->format, &r, &g, &b);
		  
		  if (r > 5 || g > 5 || b > 5)
		    {
		      drawpoint3d(x - (images[flying_img]->w / 2),
				  y - (images[flying_img]->h / 2),
				  flying_z,
				  mkcolor(r, g, b), FALSE);
		    }
		}
	    }
	}

      
      /* Play music: */

      keep_playing_music();
      

      if (SDL_MUSTLOCK(screen))
	SDL_UnlockSurface(screen);

      
      /* Pause: */
      
      SDL_Flip(screen);
      
      now_time = SDL_GetTicks();

      if (now_time < last_time + (1000 / FPS))
      {
        SDL_Delay(last_time + 1000 / FPS - now_time);
      }
    }
  while (!done && !quit);
  
  
#ifndef NOSOUND
  if (use_sound)
    {
      Mix_HaltMusic();
    }
#endif
  
  return(quit);
}


/* Play current song if it's finished (or hasn't started yet)... */

void keep_playing_music(void)
{
#ifndef NOSOUND
  if (use_sound)
    {
      if (!Mix_PlayingMusic())
	{
	  Mix_PlayMusic(musics[MUS_GAME1 +
			      (level % (NUM_MUSICS - MUS_GAME1))], 0);
	}
    }
#endif
}


/* Pause screen loop! */

int pause_screen(void)
{
  SDL_Event event;
  SDL_Rect dest;
  int done, quit, x, y, xx, yy;
  Uint8 tr, tg, tb;
  Uint32 pix, r, g, b;
  SDL_Surface * fuzz;
  
  done = FALSE;
  quit = FALSE;
  
  
  fuzz = SDL_DisplayFormat(screen);
  
  
  /* Blur the screen: */
  
  for (y = 0; y < HEIGHT; y++)
    {
      for (x = 0; x < WIDTH; x++)
	{
	  r = 0;
	  g = 0;
	  b = 0;
	  
	  for (yy = 0; yy < 4; yy++)
	    {
	      for (xx = 0; xx < 4; xx++)
		{
		  SDL_GetRGB(getpixel(screen, x + xx, y + yy),
			     screen->format, &tr, &tg, &tb);
		  
		  r = r + tr;
		  g = g + tg;
		  b = b + tb;
		}
	    }
	  
	  r = r / 16;
	  g = g / 16;
	  b = b / 16;
	  
	  pix = SDL_MapRGB(fuzz->format, r, g, b);
	  
	  putpixel(fuzz, x, y, pix);
	}
    }
  
  SDL_BlitSurface(fuzz, NULL, screen, NULL);
  
  
  /* Place "PAUSED" text on screen: */
  
  dest.x = (WIDTH - images[IMG_PAUSED]->w) / 2;
  dest.y = (HEIGHT - images[IMG_PAUSED]->h) / 2;
  dest.w = images[IMG_PAUSED]->w;
  dest.h = images[IMG_PAUSED]->h;
  
  SDL_BlitSurface(images[IMG_PAUSED], NULL,
		  screen, &dest);
  
  SDL_Flip(screen);
  
  
  /* Pause loop! */
  
  do
    {
      while (SDL_PollEvent(&event) > 0)
	{
	  if (event.type == SDL_QUIT)
	    {
	      done = TRUE;
	      quit = TRUE;
	    }
	  else if (event.type == SDL_KEYDOWN)
	    done = TRUE;
	}
      
      SDL_Delay(50);
      keep_playing_music();
    }
  while (!done);
  
  SDL_FreeSurface(fuzz);
  
  return(quit);
}



/* Draw vector: */

void drawline3d(float x1, float y1, float z1, color_type c1,
		float x2, float y2, float z2, color_type c2)
{
  int sx1, sy1, sx2, sy2;
  
  if (calc3d(&sx1, &sy1, x1, y1, z1) &&
      calc3d(&sx2, &sy2, x2, y2, z2))
    drawline(sx1, sy1, c1, sx2, sy2, c2);
}


/* Draw vector with blur: */

void drawline3d_blur(float x1, float y1, float z1,
		     color_type c1, color_type c1b,
		     float x2, float y2, float z2,
		     color_type c2, color_type c2b)
{
  int sx1, sy1, sx2, sy2;
  
  if (calc3d(&sx1, &sy1, x1, y1, z1) &&
      calc3d(&sx2, &sy2, x2, y2, z2))
    {
      drawline(sx1 + 1, sy1, c1b, sx2 + 1, sy2, c2b);
      drawline(sx1 - 1, sy1, c1b, sx2 - 1, sy2, c2b);
      drawline(sx1, sy1 + 1, c1b, sx2, sy2 + 1, c2b);
      drawline(sx1, sy1 - 1, c1b, sx2, sy2 - 1, c2b);
      drawline(sx1, sy1, c1, sx2, sy2, c2);
    }
}


/* Draw point: */

void drawpoint3d(float x, float y, float z, color_type c, int thick)
{
  int sx, sy;
  Uint32 pix;
  
  if (calc3d(&sx, &sy, x, y, z))
    {
      if (sx >= 0 && sx < WIDTH &&
	  sy >= 0 && sy < HEIGHT)
	{
	  pix = SDL_MapRGB(screen->format, c.r, c.g, c.b);
	  putpixel(screen, sx, sy, pix);
	  
	  if (thick)
	    {
	      putpixel(screen, sx + 1, sy, pix);
	      putpixel(screen, sx, sy + 1, pix);
	      putpixel(screen, sx + 1, sy + 1, pix);
	    }
	}
    }
}


/* Where does this line clip? */

unsigned char encode(float x, float y)
{
  unsigned char code;
  
  code = 0x00;
  
  if (x < 0.0)
    code = code | LEFT_EDGE;
  else if (x >= (float) WIDTH)
    code = code | RIGHT_EDGE;
  
  if (y < 0.0)
    code = code | TOP_EDGE;
  else if (y >= (float) HEIGHT)
    code = code | BOTTOM_EDGE;
  
  return code;
}


/* Clip lines to window: */

int clip(int * x1, int * y1, int * x2, int * y2)
{
  float fx1, fx2, fy1, fy2, tmp;
  unsigned char code1, code2;
  int done, draw, swapped;
  unsigned char ctmp;
  float m;
  
  fx1 = (float) *x1;
  fy1 = (float) *y1;
  fx2 = (float) *x2;
  fy2 = (float) *y2;
  
  done = FALSE;
  draw = FALSE;
  m = 0;
  swapped = FALSE;
  
  while (!done)
    {
      code1 = encode(fx1, fy1);
      code2 = encode(fx2, fy2);
      
      if (!(code1 | code2))
	{
	  done = TRUE;
	  draw = TRUE;
	}
      else if (code1 & code2)
	{
	  done = TRUE;
	}
      else
	{
	  if (!code1)
	    {
	      swapped = TRUE;
	      tmp = fx1;
	      fx1 = fx2;
	      fx2 = tmp;
	      
	      tmp = fy1;
	      fy1 = fy2;
	      fy2 = tmp;
	      
	      ctmp = code1;
	      code1 = code2;
	      code2 = ctmp;
	    }
	  
	  
	  if (fx2 != fx1)
	    m = (fy2 - fy1) / (fx2 - fx1);
	  else
	    m = 1;
	  
	  if (code1 & LEFT_EDGE)
	    {
	      fy1 += ((0 - (fx1)) * m);
	      fx1 = 0;
	    }
	  else if (code1 & RIGHT_EDGE)
	    {
	      fy1 += (((WIDTH - 1) - (fx1)) * m);
	      fx1 = (WIDTH - 1);
	    }
	  else if (code1 & TOP_EDGE)
	    {
	      if (fx2 != fx1)
		fx1 += ((0 - (fy1)) / m);
	      fy1 = 0;
	    }
	  else if (code1 & BOTTOM_EDGE)
	    {
	      if (fx2 != fx1)
		fx1 += (((HEIGHT - 1) - (fy1)) / m);
	      fy1 = (HEIGHT - 1);
	    }
	}
    }
  
  
  if (swapped)
    {
      tmp = fx1;
      fx1 = fx2;
      fx2 = tmp;
      
      tmp = fy1;
      fy1 = fy2;
      fy2 = tmp;
    }
  
  
  *x1 = (int) fx1;
  *y1 = (int) fy1;
  *x2 = (int) fx2;
  *y2 = (int) fy2;
  
  return(draw);
}


/* Draw a line: */

void drawline(int x1, int y1, color_type c1,
	      int x2, int y2, color_type c2)
{
  int dx, dy;
  float cr, cg, cb, rd, gd, bd;
  float m, b;
  
  if (clip(&x1, &y1, &x2, &y2))
    {
      dx = x2 - x1;
      dy = y2 - y1;
      
      if (dx != 0)
	{
	  m = ((float) dy) / ((float) dx);
	  b = y1 - m * x1;
	  
	  if (x2 >= x1)
	    dx = 1;
	  else
	    dx = -1;
	  
	  cr = c1.r;
	  cg = c1.g;
	  cb = c1.b;
	  
	  rd = (float) (c2.r - c1.r) / (float) (x2 - x1) * dx;
	  gd = (float) (c2.g - c1.g) / (float) (x2 - x1) * dx;
	  bd = (float) (c2.b - c1.b) / (float) (x2 - x1) * dx;
	  
	  while (x1 != x2)
	    {
	      y1 = m * x1 + b;
	      y2 = m * (x1 + dx) + b;
	      
	      drawvertline(x1, y1, mkcolor(cr, cg, cb),
			   y2, mkcolor(cr + rd, cg + gd, cb + bd));
	      
	      x1 = x1 + dx;
	      
	      cr = cr + rd;
	      cg = cg + gd;
	      cb = cb + bd;
	    }
	}
      else
	drawvertline(x1, y1, c1, y2, c2);
    }
}


/* Draw a verticle line: */

void drawvertline(int x, int y1, color_type c1,
		  int y2, color_type c2)
{
  int tmp, dy;
  float cr, cg, cb, rd, gd, bd;
  
  if (y1 > y2)
    {
      tmp = y1;
      y1 = y2;
      y2 = tmp;
      
      tmp = c1.r;
      c1.r = c2.r;
      c2.r = tmp;
      
      tmp = c1.g;
      c1.g = c2.g;
      c2.g = tmp;
      
      tmp = c1.b;
      c1.b = c2.b;
      c2.b = tmp;
    }
  
  cr = c1.r;
  cg = c1.g;
  cb = c1.b;
  
  if (y1 != y2)
    {
      rd = (float) (c2.r - c1.r) / (float) (y2 - y1);
      gd = (float) (c2.g - c1.g) / (float) (y2 - y1);
      bd = (float) (c2.b - c1.b) / (float) (y2 - y1);
    }
  else
    {
      rd = 0;
      gd = 0;
      bd = 0;
    }
  
  for (dy = y1; dy <= y2; dy++)
    {
      putpixel(screen, x, dy, SDL_MapRGB(screen->format,
					 (Uint8) cr,
					 (Uint8) cg,
					 (Uint8) cb));
      cr = cr + rd;
      cg = cg + gd;
      cb = cb + bd;
    } 
}


/* Get a pixel: */

Uint32 getpixel(SDL_Surface * surface, int x, int y)
{
  int bpp;
  Uint8 * p;
  
  
  /* Determine bytes-per-pixel for the surface in question: */
  
  bpp = surface->format->BytesPerPixel;
  
  
  /* Set a pointer to the exact location in memory of the pixel
     in question: */
  
  p = (Uint8 *) (surface->pixels +       /* Start at beginning of RAM */
                 (y * surface->pitch) +  /* Go down Y lines */
                 (x * bpp));             /* Go in X pixels */
  
  
  /* Assuming the X/Y values are within the bounds of this surface... */
  
  if (x >= 0 && y >= 0 && x < surface -> w && y < surface -> h)
    {
      /* Return the correctly-sized piece of data containing the
         pixel's value (an 8-bit palette value, or a 16-, 24- or 32-bit
         RGB value) */
      
      if (bpp == 1)         /* 8-bit display */
        return *p;
      else if (bpp == 2)    /* 16-bit display */
        return *(Uint16 *)p;
      else if (bpp == 3)    /* 24-bit display */
        {
          /* Depending on the byte-order, it could be stored RGB or BGR! */
          
          if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
            return p[0] << 16 | p[1] << 8 | p[2];
          else
            return p[0] | p[1] << 8 | p[2] << 16;
        }
      else if (bpp == 4)    /* 32-bit display */
        return *(Uint32 *)p;
      else
        return 0;           /* (Should never occur) */
    }
  else
    return 0;               /* (Out of bounds?  Just return zero) */
}



/* Draw a single pixel into the surface: */

void putpixel(SDL_Surface * surface, int x, int y, Uint32 pixel)
{
  int bpp;
  Uint8 * p;
  

  /* Assuming the X/Y values are within the bounds of this surface... */
  
  if (x >= 0 && y >= 0 && x < WIDTH && y < HEIGHT)
    {
      /* Determine bytes-per-pixel for the surface in question: */
      
      bpp = surface->format->BytesPerPixel;
      
      
      /* Set a pointer to the exact location in memory of the pixel
	 in question: */
      
      p = (Uint8 *) (surface->pixels +       /* Start at beginning of RAM */
		     (y * surface->pitch) +  /* Go down Y lines */
		     (x * bpp));             /* Go in X pixels */
      
      
      /* Set the (correctly-sized) piece of data in the surface's RAM
         to the pixel value sent in: */
      
      if (bpp == 1)
        *p = pixel;
      else if (bpp == 2)
        *(Uint16 *)p = pixel;
      else if (bpp == 3)
        {
          if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
            {
              p[0] = (pixel >> 16) & 0xff;
              p[1] = (pixel >> 8) & 0xff;
              p[2] = pixel & 0xff;
            }
          else
            {
              p[0] = pixel & 0xff;
              p[1] = (pixel >> 8) & 0xff;
              p[2] = (pixel >> 16) & 0xff;
            }
        }
      else if (bpp == 4)
        {
          *(Uint32 *)p = pixel;
        }
    }
}


/* Convert 3D coordinate into screen coordinate: */

int calc3d(int * sx, int * sy, float x, float y, float z)
{
  float xx, yy, zz;
  int ok;
  
  ok = FALSE;
  
  xx = x * cos_anglex - z * sin_anglex;
  zz = x * sin_anglex + z * cos_anglex;
  
  yy = y * cos_angley - zz * sin_angley;
  zz = y * sin_angley + zz * cos_angley;
  
  if (zz > -DISTANCE * 2)
    {
      if (zz <= -DISTANCE)
	zz = -DISTANCE + 1;

      
      /* Convert (x,y,z) into (x,y): */
      
      *sx = xx / ((zz + DISTANCE) / ASPECT);
      *sy = yy / ((zz + DISTANCE) / ASPECT);
      
      
      /* Transpose (0,0) origin to the center of the window: */
      
      *sx = *sx + WIDTH / 2;
      *sy = *sy + HEIGHT / 2;
      
      ok = TRUE;
    }
  
  return ok;
}


/* Recalculate the trig. values: */

void recalctrig(void)
{
  cos_anglex = my_cos(anglex);
  sin_anglex = my_sin(anglex);

  cos_angley = my_cos(angley);
  sin_angley = my_sin(angley);
}


/* Create a color_type struct out of RGB values: */

color_type mkcolor(int r, int g, int b)
{
  color_type c;
  
  if (r > 255)
    r = 255;
  if (g > 255)
    g = 255;
  if (b > 255)
    b = 255;

  c.r = (Uint8) r;
  c.g = (Uint8) g;
  c.b = (Uint8) b;
  
  return c;
}


/* Setup function: */

void setup(int argc, char * argv[])
{
  int fullscreen, i;


  /* Set default settings: */

  fullscreen = FALSE;
  use_sound = TRUE;
  max_level = 3;
  

  /* Get command-line arguments: */

  /* FIXME: do it! */
  
  
  /* Init video: */

  if (SDL_Init(SDL_INIT_VIDEO) < 0)
    {
      fprintf(stderr,
              "\nError: I could not initialize video!\n"
              "The Simple DirectMedia error that occured was:\n"
              "%s\n\n", SDL_GetError());
      exit(1);
    }

  

  /* Open window: */

  if (fullscreen)
  {
    screen = SDL_SetVideoMode(WIDTH, HEIGHT, 16,
			      SDL_FULLSCREEN | SDL_HWSURFACE);

    if (screen == NULL)
    {
      fprintf(stderr,
	      "\nWarning: I could not open the display in fullscreen mode.\n"
	      "The Simple DirectMedia error that occured was:\n"
	      "%s\n\n", SDL_GetError());
      fullscreen = FALSE;
    }
  }


  if (!fullscreen)
  {
    screen = SDL_SetVideoMode(WIDTH, HEIGHT, 16,
			      SDL_HWSURFACE);

    if (screen == NULL)
    {
      fprintf(stderr,
              "\nError: I could not open the display.\n"
	      "The Simple DirectMedia error that occured was:\n"
	      "%s\n\n", SDL_GetError());
      exit(1);
    }
  }


  /* Load images: */
  
  for (i = 0; i < NUM_IMAGES; i++)
    {
      images[i] = IMG_Load(image_filenames[i]);
      if (images[i] == NULL)
	{
	  fprintf(stderr,
		  "\nError: I could not load the image file:\n"
		  "%s\n"
		  "The Simple DirectMedia error that occured was:\n"
		  "%s\n\n", image_filenames[i], SDL_GetError());
	  exit(1);
	}
    }
  

  /* Set window's icon and title: */

  seticon();
  SDL_WM_SetCaption("Tumult", "Tumult");


  /* Init. audio: */

#ifndef NOSOUND
  if (use_sound)
  {
    if (SDL_Init(SDL_INIT_AUDIO) < 0)
      {
	fprintf(stderr,
		"\nWarning: I could not initialize audio!\n"
		"The Simple DirectMedia error that occured was:\n"
		"%s\n\n", SDL_GetError());
	use_sound = 0;
      }
  }

  
  if (use_sound)
    {
      if (Mix_OpenAudio(44100, AUDIO_S16, 2, 256) < 0)
	{
	  fprintf(stderr,
                  "\nWarning: I could not set up audio for 44100 Hz "
                  "16-bit stereo.\n"
                  "The Simple DirectMedia error that occured was:\n"
                  "%s\n\n", SDL_GetError());
          use_sound = 0;
	}
    }
  
  
  if (use_sound)
    {
      /* Load sounds: */
      
      for (i = 0; i < NUM_SOUNDS; i++)
        {
          sounds[i] = Mix_LoadWAV(sound_filenames[i]);
          if (sounds[i] == NULL)
            {
              fprintf(stderr,
                      "\nWarning: I could not load the sound file:\n"
                      "%s\n"
                      "The Simple DirectMedia error that occured was:\n"
                      "%s\n\n", sound_filenames[i], SDL_GetError());
	      use_sound = 0;
	      i = NUM_SOUNDS;
            }
        }
    }
  
  
  if (use_sound)
    {
      /* Load musics: */
      
      for (i = 0; i < NUM_MUSICS; i++)
        {
          musics[i] = Mix_LoadMUS(music_filenames[i]);
          if (musics[i] == NULL)
            {
              fprintf(stderr,
                      "\nWarning: I could not load the music file:\n"
                      "%s\n"
                      "The Simple DirectMedia error that occured was:\n"
                      "%s\n\n", music_filenames[i], SDL_GetError());
	      use_sound = 0;
	      i = NUM_MUSICS;
            }
        }
    }
#endif


  /* Seed random-number generator: */
    
  srand(SDL_GetTicks());


  /* Init trig stuff: */
  
  init_cos_table();
  
  
  /* Init 3D vars: */

  anglex = 0.0;
  angley = 0.0;
  
  recalctrig();
}



/* Set icon: */

void seticon(void)
{
  int masklen;
  Uint8 * mask;
  SDL_Surface * icon;
  
  
  /* Load icon into a surface: */
  
  icon = IMG_Load(DATA_PREFIX "/images/icon.png");
  if (icon == NULL)
    {
      fprintf(stderr,
	      "\nWarning: I could not load the icon image: %s\n"
	      "The Simple DirectMedia error that occured was:\n"
	      "%s\n\n", DATA_PREFIX "images/icon.png", SDL_GetError());
      return;
    }
  
  
  /* Create mask: */
  
  masklen = (((icon -> w) + 7) / 8) * (icon -> h);
  mask = malloc(masklen * sizeof(Uint8));
  memset(mask, 0xFF, masklen);
  
  
  /* Set icon: */
  
  SDL_WM_SetIcon(icon, mask);
  
  
  /* Free icon surface & mask: */
  
  free(mask);
  SDL_FreeSurface(icon);
}


/* My own sign func: */

int sgn(int x)
{
  if (x < 0)
    return -1;
  else if (x == 0)
    return 0;
  else
    return 1;
}


/* Add a player's bullet: */

void add_bullet(int alley)
{
  int i, found;

  found = -1;

  for (i = 0; i < MAX_BULLETS && found == -1; i++)
  {
    if (!bullets[i].alive)
      found = i;
  }


  if (found != -1)
  {
    bullets[found].alive = TRUE;
    bullets[found].alley = alley;
    bullets[found].z = 0;
    
    playsound(1, SND_LASER);
  }
}


/* Play a sound effect: */

void playsound(int chan, int snd)
{
#ifndef NOSOUND
  if (use_sound)
    {
      Mix_PlayChannel(chan, sounds[snd], 0);
    }
#endif
}


/* Draw a zap bolt: */

void draw_zap(int alley1, int z1, int alley2, int z2)
{
  int al11, al12, al21, al22;
  
  
  /* Star from crab: */
  
  if (alley1 < num_alleys)
    al11 = alley1;
  else
    al11 = 0;

  if (alley1 < num_alleys - 1)
    al12 = alley1 + 1;
  else
    al12 = 0;
  
  
  /* Go to target: */
  
  al21 = alley2;
  if (alley2 < num_alleys - 1)
    al22 = al21 + 1;
  else
    al22 = 0;
  
  draw_lightning((alley[al11].x + alley[al12].x) / 2,
		 (alley[al11].y + alley[al12].y) / 2,
		 z1,
		 (alley[al21].x + alley[al22].x) / 2,
		 (alley[al21].y + alley[al22].y) / 2,
		 z2);
}


/* Draw a lightning bolt from one x,y,z point to another (moving along z) */

void draw_lightning(int x1, int y1, int z1,
		    int x2, int y2, int z2)
{
  float z, x, y, xm, ym;
  int off_x, off_y, off_z, last_off_x, last_off_y, last_off_z;
  float colr, colrm;
  
  x = x1;
  y = y1;
  colr = 128;
  
  xm = (x2 - x1) / ((z2 - z1) / 10.0);
  ym = (y2 - y1) / ((z2 - z1) / 10.0);
  colrm = (255 - colr) / ((z2 - z1) / 10.0);
  
  last_off_x = (rand() % 5) - 2;
  last_off_y = (rand() % 5) - 2;
  last_off_z = (rand() % 3) - 1;
  
  for (z = z1; z < z2; z = z + 10)
    {
      off_x = (rand() % 11) - 5;
      off_y = (rand() % 11) - 5;
      off_z = (rand() % 5) - 2;
      
      drawline3d_blur(x + last_off_x, y + last_off_y, z + last_off_z,
		      mkcolor(colr, colr, 255),
		      mkcolor(colr / 2, 128, 128),
		      x + off_x + xm, y + off_y + ym, z + off_z + 10,
		      mkcolor(colr + colrm, colr + colrm, 255),
		      mkcolor((colr + colrm) / 2, 128, 128));
      
      last_off_x = off_x;
      last_off_y = off_y;
      last_off_z = off_z;
      
      x = x + xm;
      y = y + ym;
      colr = colr + colrm;
    }
}


void add_explosion(int al1, int z)
{
  int i, x, y, al2;
  
  
  if (al1 < num_alleys - 1)
    al2 = al1 + 1;
  else
    al2 = 0;
  
  
  x = (alley[al1].x + alley[al2].x) / 2;
  y = (alley[al1].y + alley[al2].y) / 2;
  
  
  for (i = 0; i < 6; i++)
    {
      add_explosion_bit(x + (rand() % 5) - 2,
			y + (rand() % 5) - 2,
			z + (rand() % 5) - 2);
    }
}


void add_explosion_bit(int x, int y, int z)
{
  int i, found;
  
  found = -1;
  
  for (i = 0; i < MAX_EXPLOSION_BITS && found == -1; i++)
    {
      if (explosion_bits[i].alive == FALSE)
	found = i;
    }
  
  
  if (found != -1)
    {
      explosion_bits[found].alive = TRUE;
      explosion_bits[found].time = 50;
      explosion_bits[found].x = x;
      explosion_bits[found].y = y;
      explosion_bits[found].z = z;
      explosion_bits[found].xm = (rand() % 11) - 5;
      explosion_bits[found].ym = (rand() % 11) - 5;
      explosion_bits[found].zm = (rand() % 11) - 5;
    }
}
