/* game.c --- game itself
 *
 * This file is gift-ware.  This file is given to you freely
 * as a gift.  You may use, modify, redistribute, and generally hack
 * it about in any way you like, and you do not have to give anyone
 * anything in return.
 *
 * I do not accept any responsibility for any effects, adverse or
 * otherwise, that this code may have on just about anything that
 * you can think of.  Use it at your own risk.
 *
 * Copyright (C) 1999  Michael Bukin
 */

#include <stdio.h>
#include <stdlib.h>
#include <allegro.h>

#include "data.h"
#include "game.h"
#include "level.h"

/* Double buffer bitmap.  */
BITMAP *buffer;
BITMAP *map_buffer;

int level;
int score;
static int ghost_color[MAX_GHOST] = { 9, 10, 12, 13, 3 };

/* Timer related variables.  */
static volatile int elapsed_time = 0;
static int max_skipped_frames = 3;

static int frame_count = 0;
static int fps_count = 0;

/*
 * Game speed timer.
 */
static void
game_timer (void)
{
  elapsed_time++;
}
END_OF_FUNCTION (game_timer);

/*
 * FPS calculation timer.
 */
static void
fps_counter (void)
{
  fps_count = frame_count;
  frame_count = 0;
}
END_OF_FUNCTION(fps_counter);

static int game_over = 0;
static int game_paused = 0;

/*
 * Dialog description for "Are you sure?" dialog.
 */
static DIALOG are_you_sure_dialog[] =
{
  { d_shadow_box_proc, 0,  0,  130, 70, 15, 1, 0,   0,      0, 0, NULL, NULL, NULL },
  { d_ctext_proc,      65, 15, 0,   16, 15, 1, 0,   0,      0, 0, "Really Quit?", NULL, NULL },
  { d_button_proc,     15, 35, 45,  16, 15, 1, 'y', D_EXIT, 0, 0, "&Yes", NULL, NULL },
  { d_button_proc,     65, 35, 45,  16, 15, 1, 'n', D_EXIT, 0, 0, "&No", NULL, NULL },
  { NULL,              0,  0,  0,   0,  0,  0, 0,   0,      0, 0, NULL, NULL, NULL }
};

/*
 * Are you sure you want to quit?
 */
static int
are_you_sure (void)
{
  centre_dialog (are_you_sure_dialog);
  return ((do_dialog (are_you_sure_dialog, 2) == 2) ? 1 : 0);
}

/*
 * Handle input.
 */
static void
handle_input (void)
{
  while (keypressed ())
    {
      poll_keyboard ();
      poll_joystick ();

      switch (readkey () >> 8)
	{
	case KEY_ESC:
	  game_over = are_you_sure ();
	  break;
	case KEY_P:
	  game_paused = !game_paused;
	  break;

	  /* Eater movements.  */
	case KEY_LEFT:
	  eater.ndx = -1;
	  eater.ndy = 0;
	  break;
	case KEY_RIGHT:
	  eater.ndx = 1;
	  eater.ndy = 0;
	  break;
	case KEY_UP:
	  eater.ndy = -1;
	  eater.ndx = 0;
	  break;
	case KEY_DOWN:
	  eater.ndy = 1;
	  eater.ndx = 0;
	  break;
	case KEY_SPACE:
	  eater.cdx = 0;
	  eater.cdy = 0;
	  eater.ndx = 0;
	  eater.ndy = 0;
	  break;
	}
    }
}

/*
 * Move everything.
 */
static void
move_everything (void)
{
  int x, y;

  /* Eater can turn back.  */
  if ((eater.cdx != 0) && (eater.ndx != 0))
    {
      eater.cdx = eater.ndx;
      eater.cdy = eater.ndy;
    }
  if ((eater.cdy != 0) && (eater.ndy != 0))
    {
      eater.cdx = eater.ndx;
      eater.cdy = eater.ndy;
    }

  /* Eater can turn into passage.  */
  x = eater.x / (20 << SPEED_SHIFT);
  y = eater.y / (20 << SPEED_SHIFT);
  if ((eater.y % (20 << SPEED_SHIFT)) == 0)
    {
      if (((eater.ndx == -1)
	   && ((x == 0)
	       || (map_info[y][x - 1] == CELL_VOID)
	       || (map_info[y][x - 1] == CELL_COIN)))
	  || ((eater.ndx == 1)
	      && ((x == (MAP_WIDTH - 1))
		  || (map_info[y][x + 1] == CELL_VOID)
		  || (map_info[y][x + 1] == CELL_COIN))))
	{
	  eater.cdx = eater.ndx;
	  eater.cdy = eater.ndy;
	}
    }
  if ((eater.x % (20 << SPEED_SHIFT)) == 0)
    {
      if (((eater.ndy == -1)
	   && ((y == 0)
	       || (map_info[y - 1][x] == CELL_VOID)
	       || (map_info[y - 1][x] == CELL_COIN)))
	  || ((eater.ndy == 1)
	      && ((y == (MAP_HEIGHT - 1))
		  || (map_info[y + 1][x] == CELL_VOID)
		  || (map_info[y + 1][x] == CELL_COIN))))
	{
	  eater.cdx = eater.ndx;
	  eater.cdy = eater.ndy;
	}
    }

  /* Move eater.  */
  if (eater.cdx != 0)
    {
      x = eater.x + eater.cdx * eater_speed;
      if ((eater.cdx == -1) && ((x < 0)
				|| (map_info[y][x / (20 << SPEED_SHIFT)] == CELL_VOID)
				|| (map_info[y][x / (20 << SPEED_SHIFT)] == CELL_COIN)))
	{
	  eater.x = x;
	  eater.frame = (eater.frame + 1) % EATER_FRAMES;
	}
      if ((eater.cdx == 1) && ((x > ((MAP_WIDTH - 1) * (20 << SPEED_SHIFT)))
			       || (map_info[y][(x + (20 << SPEED_SHIFT) - 1) / (20 << SPEED_SHIFT)] == CELL_VOID)
			       || (map_info[y][(x + (20 << SPEED_SHIFT) - 1) / (20 << SPEED_SHIFT)] == CELL_COIN)))
	{
	  eater.x = x;
	  eater.frame = (eater.frame + 1) % EATER_FRAMES;
	}
    }
  else if (eater.cdy != 0)
    {
      y = eater.y + eater.cdy * eater_speed;
      if ((eater.cdy == -1) && ((y < 0)
				|| (map_info[y / (20 << SPEED_SHIFT)][x] == CELL_VOID)
				|| (map_info[y / (20 << SPEED_SHIFT)][x] == CELL_COIN)))
	{
	  eater.y = y;
	  eater.frame = (eater.frame + 1) % EATER_FRAMES;
	}
      if ((eater.cdy == 1) && ((y > ((MAP_HEIGHT - 1) * (20 << SPEED_SHIFT)))
			       || (map_info[(y + (20 << SPEED_SHIFT) - 1) / (20 << SPEED_SHIFT)][x] == CELL_VOID)
			       || (map_info[(y + (20 << SPEED_SHIFT) - 1) / (20 << SPEED_SHIFT)][x] == CELL_COIN)))
	{
	  eater.y = y;
	  eater.frame = (eater.frame + 1) % EATER_FRAMES;
	}
    }

  if (eater.x < 0)
    eater.x += MAP_WIDTH * (20 << SPEED_SHIFT);
  else if (eater.x >= (MAP_WIDTH * (20 << SPEED_SHIFT)))
    eater.x -= MAP_WIDTH * (20 << SPEED_SHIFT);
  if (eater.y < 0)
    eater.y += MAP_HEIGHT * (20 << SPEED_SHIFT);
  else if (eater.y >= (MAP_HEIGHT * (20 << SPEED_SHIFT)))
    eater.y -= MAP_HEIGHT * (20 << SPEED_SHIFT);

  /* Eat coin, bonus or prize.  */
  if (((eater.x % (20 << SPEED_SHIFT)) == 0) && ((eater.y % (20 << SPEED_SHIFT)) == 0))
    {
      x = eater.x / (20 << SPEED_SHIFT);
      y = eater.y / (20 << SPEED_SHIFT);
      if (map_info[y][x] == CELL_COIN)
	{
#ifdef USE_BEL
	  /* Thanks god, I have "xset b off" in .xinitrc!  */
	  /* And I'm not inclined to even test it.  */
	  printf ("\a\n");
#endif
	  score++;
	  map_info[y][x] = CELL_VOID;
	  rectfill (map_buffer, x * 20, y * 20, x * 20 + 19, y * 20 + 19, 0);
	  coin_count--;
	  if (coin_count == 0)
	    game_over = 1;
	}
    }

  /* Move ghosts.  */

  /* Test for collisions.  */
}

/*
 * Draw everything.
 */
static void
draw_everything (void)
{
  int i;
  BITMAP *tmp;

  blit (map_buffer, buffer, 0, 0, 0, 0, map_buffer->w, map_buffer->h);

  /* Draw visible bonus.  */
  for (i = 0; i < bonus_count; i++)
    if (bonus[i].state == BONUS_VISIBLE)
      draw_sprite (buffer, bonus_sprite, bonus[i].x >> SPEED_SHIFT, bonus[i].y >> SPEED_SHIFT);

  /* Draw visible prize.  */
  if (prize.state == PRIZE_VISIBLE)
    draw_sprite (buffer, prize_sprite[level % MAX_PRIZE], prize.x >> SPEED_SHIFT, prize.y >> SPEED_SHIFT);

  /* Draw ghosts.  */
  for (i = 0; i < ghost_count; i++)
    {
      if (ghost[i].state == GHOST_BONES)
	{
	  if (ghost[i].cdy == -1)
	    draw_sprite (buffer, bones_up_sprite, ghost[i].x >> SPEED_SHIFT, ghost[i].y >> SPEED_SHIFT);
	  else if (ghost[i].cdy == 1)
	    draw_sprite (buffer, bones_down_sprite, ghost[i].x >> SPEED_SHIFT, ghost[i].y >> SPEED_SHIFT);
	  else if (ghost[i].cdx == -1)
	    draw_sprite (buffer, bones_left_sprite, ghost[i].x >> SPEED_SHIFT, ghost[i].y >> SPEED_SHIFT);
	  else
	    draw_sprite (buffer, bones_right_sprite, ghost[i].x >> SPEED_SHIFT, ghost[i].y >> SPEED_SHIFT);
	}
      else
	{
	  if (ghost[i].cdy == -1)
	    tmp = ghost_up_sprite;
	  else if (ghost[i].cdy == 1)
	    tmp = ghost_down_sprite;
	  else if (ghost[i].cdx == -1)
	    tmp = ghost_left_sprite;
	  else
	    tmp = ghost_right_sprite;

	  if (ghost[i].state == GHOST_ESCAPE)
	    draw_colored_sprite (buffer, tmp, ghost[i].x >> SPEED_SHIFT, ghost[i].y >> SPEED_SHIFT, 15);
	  else
	    draw_colored_sprite (buffer, tmp, ghost[i].x >> SPEED_SHIFT, ghost[i].y >> SPEED_SHIFT, ghost_color[i]);
	}
    }

  /* Draw eater.  */
  if (eater.cdy == -1)
    tmp = eater_up_sprite[eater.frame];
  else if (eater.cdy == 1)
    tmp = eater_down_sprite[eater.frame];
  else if (eater.cdx == -1)
    tmp = eater_left_sprite[eater.frame];
  else
    tmp = eater_right_sprite[eater.frame];

  draw_sprite (buffer, tmp, eater.x >> SPEED_SHIFT, eater.y >> SPEED_SHIFT);

#if 0
  rectfill (buffer, 0, 0, SCREEN_W - 1, y - 1, 0);
  rectfill (buffer, 0, y, x - 1, y + w - 1, 0);
  rectfill (buffer, x + w, y, SCREEN_W - 1, y + w - 1, 0);
  rectfill (buffer, 0, y + w, SCREEN_W - 1, SCREEN_H - 1, 0);
#endif
  text_mode (0);
  textprintf (screen, font, 6, 6, 15, "FPS: %d          ", fps_count);
  textprintf (screen, font, 166, 6, 15, "SCORE: %d          ", score);

  if (game_paused)
    textout_centre (buffer, font, "*PAUSE*", (prize.x >> SPEED_SHIFT) + 10, (prize.y >> SPEED_SHIFT) + 6, 15);
}

/*
 * Start game.
 */
void
game_start (void)
{
  int processed_time;
  int skipped_frames;

  /* Create double buffer bitmap.  */
  buffer = create_bitmap (MAP_WIDTH * 20, MAP_HEIGHT * 20);
  if (buffer == 0)
    {
      allegro_exit ();
      allegro_message ("Can not create double buffer bitmap!");
      exit (EXIT_FAILURE);
    }
  map_buffer = create_bitmap (MAP_WIDTH * 20, MAP_HEIGHT * 20);
  if (map_buffer == 0)
    {
      allegro_exit ();
      allegro_message ("Can not create bitmap for map!");
      exit (EXIT_FAILURE);
    }

  /* Install game speed timer.  */
  LOCK_FUNCTION (game_timer);
  LOCK_VARIABLE (elapsed_time);
  install_int (game_timer, 20);
  processed_time = elapsed_time;
  skipped_frames = 0;

  /* Install fps counter.  */
  LOCK_FUNCTION (fps_counter);
  LOCK_VARIABLE (frame_count);
  LOCK_VARIABLE (fps_count);
  install_int (fps_counter, 1000);
  frame_count = 0;
  fps_count = 0;

  /* Start game.  */
  game_over = 0;
  game_paused = 0;

  /* Initialize player state.  */

  level = 0;
  score = 0;
  game_map_init (level);
  game_draw_map (map_buffer);
  game_level_restart (level);

  clear (screen);

  /* Main game loop.  */
  while (!game_over)
    {
      /* Handle input.  */
      handle_input ();

      if (!game_paused)
	{
	  while (elapsed_time != processed_time)
	    {
	      /* Move everything and increment time.  */
	      move_everything ();
	      processed_time++;

	      /* Limit max number of skipped frames.  */
	      if (skipped_frames++ > max_skipped_frames)
		{
		  /* Too much frames skipped.  */
		  processed_time = elapsed_time;
		  break;
		}
	    }

	  /* This frame is not skipped.  */
	  skipped_frames = 0;
	}

      /* Draw everything and increment number of frames.  */
      draw_everything ();
      blit (buffer, screen, 0, 0, 30, 20, buffer->w, buffer->h);
      frame_count++;
    }

  /* Cleanup.  */
  remove_int (fps_counter);
  remove_int (game_timer);

  destroy_bitmap (map_buffer);
  destroy_bitmap (buffer);

  clear_keybuf ();
}

/*
 * game.c ends here
 */
