/* -*-mode: C++; style: K&R; c-basic-offset: 4 ; -*- */

/*
 *  mousetrap.cpp -  The main game's code.
 *
 *  mousetrap - A simple arcade game
 *
 * Homepage:
 *  http://www.steve.org.uk/Software/mousetrap
 *
 * Author:
 *  Steve Kemp <steve@steve.org.uk>
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 *
 *  Steve Kemp
 *  ---
 *  http://www.steve.org.uk/
 *
 */




/**
 *  Simple 'mousetrap' game by Steve.
 *
 *  The game is setup for a 640x480 resolution.
 *
 *  The playing field itself is 480x480 (square) which explains
 * why in some places you will see references to the screen height
 * in the x-axis calculations.
 *
 *  The code is nominally C++, although in practise it's mostly
 * C - except for the 'CEnemy' class.
 *
 *
 * Steve
 * --
 * www.steve.org.uk
 */


#include <unistd.h>
#include <SDL.h>                 /* SDL For everything */
#include <SDL_gfxPrimitives.h>   /* SDL_gfx for the circle drawing code. */


#include "mousetrap.h"           /* Definitions for our game */
#include "font.h"                /* Font drawing code. */
#include "enemy.h"               /* Definitions of the enemy objects */
#include "highscore.h"           /* Highscore reading code */
#include "soundeffects.h"        /* Sound effects code */
#include <defaults.h>
#include <math.h>
#include <vector>
#include <iostream>


/*
 * Silent?  (== no sound )
 */
int g_silent = 0;


float sdlgt=0;  /* Stores the time that has passed */
float dtime=0;  /* time we last died. */


#define TICK_INTERVAL   15
static Uint32 next_time;




/**
 *  Load an image.
 *
 * Either from:
 *
 *  ./
 *
 * or
 *
 * /usr/share/mousetrap/
 *
 * Failure to load is fatal.
 *
 */
SDL_Surface * loadImage(char *path)
{
    char f_path[1025];

    int i;
    i = access ( path, F_OK );

    if ( i == 0 )
    {
	/*
	 * Found it in the current directory.
	 */
	return( SDL_LoadBMP(path) );
    }
    else
    {
	snprintf(f_path, sizeof(f_path), "%s/%s/%s",DEFAULT_LIBDIR, PACKAGE, path);

	i = access( f_path, F_OK );
	if ( i == 0 )
	{
	    /*
	     * Found it beneath /usr/share/mousetrap.
	     */
	    return( SDL_LoadBMP( f_path ) );
	}
	else
	{
	    printf("Failed to load image.\n" );
	    printf("Neither %s nor %s/%s/%s exist\n",path, DEFAULT_LIBDIR,
	    PACKAGE, path );
	    exit( 0 );
	}
    }
}



Uint32 time_left(void)
{
    Uint32 now;

    now = SDL_GetTicks();
    if(next_time <= now)
        return 0;
    else
        return next_time - now;
}


/**
 * Draw the sidebar
 */
void drawSideBar( SDL_Surface *screen, SDLFont *font, int score, int lives, int alive )
{
    /*
     * Offset for the scrolling message.
     */
    static int offset = 0;


    /*
     * Give the score panel a white background and a
     * black border.
     */
    SDL_Rect panel;
    panel.x = SCREEN_HEIGHT;
    panel.y= 0;
    panel.w = SCREEN_WIDTH - SCREEN_HEIGHT;
    panel.h = SCREEN_HEIGHT;
    SDL_FillRect(screen, &panel, 0xffffffff);

    panel.x = SCREEN_HEIGHT+5;
    panel.y= 0+5;
    panel.w = SCREEN_WIDTH - SCREEN_HEIGHT-10;
    panel.h = SCREEN_HEIGHT-10;
    SDL_FillRect(screen, &panel, 0);


    /**
     * If we're alive draw the scoreboard,
     * etc.
     */
    if ( alive )
    {
	offset = 0;

	/*
	 * Work out how long the time-based shield will last for.
	 */
	int percentageShield = 100 - (int)((sdlgt-dtime) / 40);
	if ( percentageShield < 0 )
	    percentageShield = 0;

	/*
	 * Fill the blue box with that amount.
	 */
	SDL_Rect shieldBox;
	shieldBox.x = 515;
	shieldBox.y = 210;
	shieldBox.w = percentageShield;
	shieldBox.h = 20;
	SDL_FillRect(screen, &shieldBox,0xff0000ff);


	/*
	 * Draw score.
	 */
	char scoreMsg[20];
	snprintf(scoreMsg, sizeof(scoreMsg), "Score:" );
	drawString(screen, font, 500, 20, scoreMsg );
	snprintf(scoreMsg, sizeof(scoreMsg), "%6d", score );
	drawString(screen, font, 500, 50, scoreMsg );

	/* Draw the lives.
	 */
	char livesMsg[20];
	snprintf(livesMsg, sizeof(livesMsg), "Lives:" );
	drawString(screen, font, 500, 100, livesMsg );
	snprintf(livesMsg, sizeof(livesMsg), "%6d", lives );
	drawString(screen, font, 500, 130, livesMsg );

	char shieldMsg[20];
	snprintf(shieldMsg, sizeof(shieldMsg), "Shield" );
	drawString(screen, font, 500, 180, shieldMsg );


	/*
	 * Draw the highscore
	 */
	char highscoreMsg[20];
	snprintf(highscoreMsg, sizeof(scoreMsg), "Highscore" );
	drawString(screen, font, 500, 400, highscoreMsg );

	CHighScore *high = CHighScore::getInstance();
	snprintf(highscoreMsg, sizeof(highscoreMsg), "%6d", high->readHighScore() );	drawString(screen, font, 500, 430, highscoreMsg );
    }
    else
    {
	/*
	 * Game over.
	 */

	/*
	 * Animate message once.
	 *
	 * Then just show the highscore.
	 *
	 */
	offset++;
	if ( offset > SCREEN_HEIGHT / 2 - ( 45 ) )
	{

	    offset --;

	    /*
	     * Draw score.
	     */
	    char scoreMsg[20];
	    snprintf(scoreMsg, sizeof(scoreMsg), "Score:" );
	    drawString(screen, font, 500, 20, scoreMsg );
	    snprintf(scoreMsg, sizeof(scoreMsg), "%6d", score );
	    drawString(screen, font, 500, 50, scoreMsg );

	    /*
	     * Draw the highscore.
	     */
	    char highscoreMsg[20];
	    snprintf(highscoreMsg, sizeof(highscoreMsg), "Highscore" );
	    drawString(screen, font, 500, 400, highscoreMsg );

	    CHighScore *high = CHighScore::getInstance();
	    snprintf(highscoreMsg, sizeof(highscoreMsg), "%6d", high->readHighScore() );
	    drawString(screen, font, 500, 430, highscoreMsg );

	}


	char msg[][24] = {
	    {"Mousetrap"},
	    { "by" },
	    { "Steve Kemp"}
	};

	for ( int i = 0; i < 3; i++ )
	    {
		int y = 480+(160/2) - stringWidth(font,msg[i])/2;
		drawString(screen, font, y, offset+(i*30), msg[i]);
	    }
    }
}



/**
 * Entry point to the code.
 *
 * Parse any optional arguments, and begin the game.
 *
 */
int main ( int argc, char *argv[] )
{
    SDL_Surface *screen, *temp, *sprite, *grass;
    SDL_Rect rcSprite, rcGrass;
    SDLFont *font;
    int cheat  = 0;

    /* The vector of enemy objects. */
    std::vector<CEnemy *> baddies;

    /* The single 'fruit' */
    SDL_Rect fruit;

    /* The single powerup. */
    SDL_Rect powerup;

    /* The user doesn't have the powerup - so it's non-visible. */
    powerup.x        = -1;
    powerup.y        = -1;
    int g_hasPowerup = 0;

    /* Current score. */
    int score = 0;

    /* Current number of lives */
    int lives = 3;

    SDL_Event event;
    Uint8 *keystate;
    int colorkey, gameover;


    /* Cheat? */
    for ( int i = 1; i < argc; i++ )
    {
	if (strcmp(argv[i],"--cheat")== 0 )
	    cheat += 1;
	if (strcmp(argv[i],"--no-sound")== 0 )
	    g_silent += 1;
	if (strcmp(argv[i],"--silent")== 0 )
	    g_silent += 1;
    }

    /* Initialise random seed */
    srand( (unsigned)time( NULL ) );

    /* initialize SDL. */
    SDL_Init(SDL_INIT_VIDEO);

    /* set the title bar */
    SDL_WM_SetCaption("MouseTrap", "MouseTrap");

    /* create window */
    screen = SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0);

    /* load sprite */
    temp   = loadImage((char *)"data/images/sprite.bmp");
    sprite = SDL_DisplayFormat(temp);
    SDL_FreeSurface(temp);

    /* setup sprite colorkey and turn on RLE */
    colorkey = SDL_MapRGB(screen->format, 255, 0, 255);
    SDL_SetColorKey(sprite, SDL_SRCCOLORKEY | SDL_RLEACCEL, colorkey);

    /* load grass */
    temp  = loadImage((char *)"data/images/grass.bmp");
    grass = SDL_DisplayFormat(temp);
    SDL_FreeSurface(temp);


    /* Load the font we use for text display */
    font = initFont((char *)"data/font1");
    if ( font == NULL )
    {
        char path_font[30];
        sprintf(path_font, "%s/%s/data/font1", DEFAULT_LIBDIR, PACKAGE);
	font = initFont( path_font );
	if ( font == NULL )
	{
	    printf("Failed to load font.\n");
	    exit(1);
	}
    }


    /* Initial enemy */
    CEnemy *initial = new CEnemy();
    baddies.push_back( initial );


    /*
     * set sprite position - human player.
     * Start game immediately.
     *
     * The player is invulnerable for three seconds, so its starting
     * position is irrelevent.
     *
     */
    rcSprite.x = 0;
    rcSprite.y = 0;
    gameover   = 0;


    /* Initial fruit is in a fixed position. */
    fruit.x = 200;
    fruit.y = 200;


    /* message pump */
    while (1)
    {
	/*
	 * With the following block of code we check how much time has passed
	 * between two frames. We store that time in millisecodns in 'dt', divided
	 * by 10. If we move some thing dt units in some direction every frame,
	 * then after 1 second, the thing has moved 100 pixels.
	 * If we move it 2.5*dt units, after 1 second it has moved 250 pixels, etc
	 * In order to have frame-rate independent movement, everything must
	 * move x*dt pixels.
	 */
	float td2=SDL_GetTicks();
	float td;
	float dt=((float)(td2-td))*0.1;
	td=td2;

	sdlgt+=dt*10;


	/* look for an event */
	if (SDL_PollEvent(&event))
	{
	    /* an event was found */
	    switch (event.type)
	    {
		/* close button clicked */
	    case SDL_QUIT:
		exit(0);
		break;

		/* handle the keyboard */
	    case SDL_KEYDOWN:
		switch (event.key.keysym.sym)
		{

		case SDLK_ESCAPE:
		case SDLK_q:
		    exit(0);
		    break;

		case SDLK_SPACE:
		    /* pause.
		     *
		     * TODO: Pressing pause when game is over
		     *       has bad side effects ...
		     */
		    gameover = !gameover;
		    break;

		case SDLK_n:
		    /*
		     * New game.
		     */
		    gameover     = 0;
		    g_hasPowerup = 0;
		    powerup.x    = -1;
		    powerup.y    = -1;
		    score        = 0;
		    lives        = 3;
		    baddies.clear();

		    /*
		     * Save the died time so the initial invulnerability works.
		     */
		    dtime = sdlgt;

		    /* Initial enemy */
		    CEnemy *initial = new CEnemy();
		    baddies.push_back( initial );

		    /* set sprite position */
		    rcSprite.x = 0;
		    rcSprite.y = 0;
		    fruit.x = 200;
		    fruit.y = 200;
		    break;

		}
		break;
	    }
	}



	/*
	 * Following code is only live when "game on".
	 */
	if ( !gameover )
	{
	    /*
	     * For the initial game we need to setup the fake 'died time'
	     * so that the starting three seconds of invulnerability works.
	     */
	    if ( dtime == 0 )
		dtime = sdlgt;


	    /*
	     * handle sprite movement
	     */
	    keystate = SDL_GetKeyState(NULL);

	    if (keystate[SDLK_LEFT] )
		rcSprite.x -= 2;

	    if (keystate[SDLK_RIGHT] )
		rcSprite.x += 2;

	    if (keystate[SDLK_UP] )
		rcSprite.y -= 2;

	    if (keystate[SDLK_DOWN] )
		rcSprite.y += 2;


	    /* collide with edges of screen */
	    if ( rcSprite.x < 0 )
		rcSprite.x = 0;
	    else if ( rcSprite.x > SCREEN_HEIGHT-SPRITE_SIZE )
		rcSprite.x = SCREEN_HEIGHT-SPRITE_SIZE;
	    if ( rcSprite.y < 0 )
		rcSprite.y = 0;
	    else if ( rcSprite.y > SCREEN_HEIGHT-SPRITE_SIZE )
		rcSprite.y = SCREEN_HEIGHT-SPRITE_SIZE;


	    /* draw the grass background */
	    for (int x = 0; x < SCREEN_HEIGHT / SPRITE_SIZE; x++)
	    {
		for (int y = 0; y < SCREEN_HEIGHT / SPRITE_SIZE; y++)
		{
		    rcGrass.x = x * SPRITE_SIZE;
		    rcGrass.y = y * SPRITE_SIZE;
		    SDL_BlitSurface(grass, NULL, screen, &rcGrass);
		}
	    }

	    /* draw the player's sprite */
	    SDL_BlitSurface(sprite, NULL, screen, &rcSprite);


	    /*
	     * If the user has a powerup draw the circle around
	     * him/her.
	     *
	     * Also do this if the time since they just died is
	     * less than three seconds.
	     *
	     */
	    if ( (sdlgt-dtime)<4000 )
	    {
		circleColor(screen, rcSprite.x+(SPRITE_SIZE/2), rcSprite.y+(SPRITE_SIZE)/2, SPRITE_SIZE, 0x0000ffff );

		if ( g_hasPowerup )
		{
		    /* Don't draw the powerup any more! */
		    powerup.x = -1;
		}
	    }
	    else
	    {
		g_hasPowerup = 0;
	    }


	    /* Draw the fruit */
	    filledCircleColor(screen, fruit.x, fruit.y, CIRCLE_SIZE, 0xffffffff );

	    /* Draw the powerup */
	    if ( powerup.x >= 0 )
		filledCircleColor(screen, powerup.x, powerup.y, 4, 0x00ffffff );

	    /* Draw the baddies */
	    std::vector<CEnemy *>::const_iterator i;
	    for(i=baddies.begin(); i!= baddies.end(); i++ )
		filledCircleColor(screen, (*i)->get_x(), (*i)->get_y(), CIRCLE_SIZE, 0xff0000ff );


	    /*
	     * Move each of the existing baddies.
	     *
	     * Do the collision testing first!
	     *
	     */
	    {
		std::vector<CEnemy *>::const_iterator i;
		for(i=baddies.begin(); i!= baddies.end(); i++ )
		{
		    CEnemy *e = (*i);

		    /*
		     * If we have the powerup, or we've spawned in
		     * the past three seconds we can kill the baddies.
		     */
		    if ( ( g_hasPowerup ) || (sdlgt-dtime<4000) )
		    {
			/*
			 * If we have the powerup then it's the
			 * baddie who will die on collision!
			 */
			if ( ( e->get_x() <= rcSprite.x + SPRITE_SIZE+(SPRITE_SIZE/2) ) &&
			     ( e->get_x() >= rcSprite.x - (SPRITE_SIZE/2)) &&
			     ( e->get_y() <= rcSprite.y + SPRITE_SIZE+(SPRITE_SIZE/2) ) &&
			     ( e->get_y() >= rcSprite.y - (SPRITE_SIZE/2)) )
			{
			    /*
			     * Kill / Remove baddie.
			     */
			    e->kill();

			    /*
			     * Get points.
			     */
			    score += 10;


			    /*
			     * New life every 100 points
			     */
			    if ( score % 100 == 0 )
			    {
				if ( !g_silent )
				    CSoundEffects::getInstance()->oneUp();

				lives += 1;
			    }


			    /* Kill noise */
			    if ( !g_silent )
				CSoundEffects::getInstance()->laser();

			}

		    }
		    else
		    {
			/*
			 * We don't have a powerup.
			 * Test for collision.
			 */
			if ( (e->get_x() >= rcSprite.x ) &&
			     (e->get_x() <= (rcSprite.x + 32)  ) &&
			     (e->get_y() >= rcSprite.y )&&
			     (e->get_y() <= (rcSprite.y+32)))
			{

			    /* Lose a life on collision.
			     * Unless you're cheating.
			     */
			    if ( !cheat )
				lives = lives - 1;

			    /*
			     * Save the died time
			     */
			    dtime = sdlgt;

			    /* Play the "lost life". */
			    if ( (lives == 0 ) &&  ( !g_silent ) )
				CSoundEffects::getInstance()->lastLife();

			    /* Game over? */
			    if ( lives < 0 )
			    {
				lives    = 0;
				gameover = 1;
				char string1[] = "Game Over";
				char string2[] = "Press 'n' to Play Again";

				drawString(screen, font, 240-stringWidth(font,string1)/2, 200, string1 );

				drawString(screen, font, 240-stringWidth(font,string2)/2, 240, string2 );

				CHighScore *high = CHighScore::getInstance();

				if ( score > high->readHighScore() )
				{
				    high->saveHighScore( score );
				}

			    }
			    else
			    {
				/*
				 * set sprite position back to "home".
				 */
				rcSprite.x     = 0;
				rcSprite.y     = 0;
			    }
			}
		    }

		    /*
		     * Our collision testing is done.
		     *
		     * Move this baddie.
		     */

		    e->step();
		}
	    }



	    /*
	     * Has the brave player managed to eat the powerup ?
	     */
	    if ( (powerup.x >= rcSprite.x ) &&
		 (powerup.x <= (rcSprite.x + 32)  ) &&
		 (powerup.y >= rcSprite.y )&&
		 (powerup.y <= (rcSprite.y+32)))
	    {
		int nBaddies = 0;

		std::vector<CEnemy *>::const_iterator i;
		for(i=baddies.begin(); i!= baddies.end(); i++ )
		{
		    if ( !(*i)->is_dead() )
			nBaddies++;
		}

		g_hasPowerup = 1;
		dtime        = sdlgt;
	    }


	    /*
	     * Has the brave player managed to eat the fruit ?
	     */
	    if ( (fruit.x >= rcSprite.x ) &&
		 (fruit.x <= (rcSprite.x + 32)  ) &&
		 (fruit.y >= rcSprite.y )&&
		 (fruit.y <= (rcSprite.y+32)))
	    {

		if ( ! g_silent )
		    CSoundEffects::getInstance()->eat();

		/*
		 * Score up.
		 */
		score += 10;

		/*
		 * New life every 100 points
		 */
		if ( score % 100 == 0 )
		{
		    if ( !g_silent )
			CSoundEffects::getInstance()->oneUp();

		    lives += 1;
		}

		/*
		 * New random fruit position.
		 */
		fruit.x = (int)(SCREEN_HEIGHT * (rand()/(RAND_MAX+1.0)));
		fruit.y = (int)(SCREEN_HEIGHT * (rand()/(RAND_MAX+1.0)));

		/*
		 * New enemy since we've eaten a fruit
		 */
		CEnemy *added = new CEnemy();
		baddies.push_back( added );

		/*
		 * One in ten chance that the powerup will become
		 * available when we kill a baddie - if we don't
		 * already have the powerup.
		 *
		 */
		if ( ( g_hasPowerup == 0 ) && ( powerup.x < 0 ) )
		{
		    if ( 1 == (int)(10 * (rand()/(RAND_MAX+1.0)))  )
		    {
			powerup.x = (int)(SCREEN_HEIGHT * (rand()/(RAND_MAX+1.0)));
			powerup.y = (int)(SCREEN_HEIGHT * (rand()/(RAND_MAX+1.0)));
		    }
		}
	    }

	    drawSideBar( screen, font,  score,  lives, 1 );

	    /* update the screen */
	    SDL_UpdateRect(screen, 0, 0, 0, 0);
	}
	else
	{

	    drawSideBar( screen, font,  score,  lives, 0 );


	    SDL_UpdateRect(screen, 0, 0, 0, 0);
	}
	SDL_Delay(time_left());
        next_time += TICK_INTERVAL;
    }

    /* clean up */
    SDL_FreeSurface(sprite);
    SDL_FreeSurface(grass);
    SDL_Quit();

    return 0;
}

/* EOF */
