//Game example 'Breakout'
//Tommy Grandefors
//2D32 Graphic library
//Version: Beta v1.0
//October 1997

#include <graphix.hh>
#include <videodef.hh>
#include <keydefs.hh>
#include <keyboard.hh>

#include <iostream.h>
#include <stdlib.h>

#define TRANSPARENCY_ON		1
#define TRANSPARENCY_OFF	0

int LoadPictures(void);
void DrawGameScreen(void);
void ReadUserInputKey(void);
int CheckCollisions(void);
void PlayerMissedTheBall(void);
void ShowGameOver(void);
void ActOnCollision(int retCode);
void PrintNoOfLives(void);
void PrintBorderText(void);

//A lot of globals!
Graphix myGfx; //Instantiate the main object

Bitmap theBackground;
Bitmap theBrick;
Bitmap thePlayerBat;
Bitmap theBall;
Bitmap theTile;
Bitmap *theSavedBackgroundBrick;
Bitmap *theBrickBmp;
Palette gamePal;

int noOfBats = 3;
const int screenWidth = 640;
const int screenHeight = 400;
int playerBatWidth;
int playerBatHeight;
int brickWidth;
int brickHeight;
int ballWidth;
int ballHeight;
int totalNoOfBricks = 60;
int playerXpos;
int playerYpos;
int ballXpos;
int ballYpos;
int ballSpeed = 3;
int batSpeed = 4;
int ballYdirection = ballSpeed;
int ballXdirection = ballSpeed;
char batText[] = "Number of bats: #";

int validBricks[60]; //Represents all bricks left in game

Keyboard myKbd; //This one will later unchain the old ISR interrupt
volatile int* kbdPtr = myKbd.InstallKeyboard(); //Set pointer to key-pressed/released vector

void main(void)
{
   if(myGfx.SetVideoMode(SVGA640x400x256) < 0)
   {
      cout<<"Can't initiate video mode"<<endl;
      return;
   }
   
   theBrickBmp = new Bitmap[totalNoOfBricks];

   if( LoadPictures() == -1 ) //Load pictures and set up tables etc.
   {
      myGfx.SetVideoMode(TEXT); //Go back to text mode.
      cout<<"Couldn't load PCX picture"<<endl;
      return;
   }

   myGfx.SetPalette(gamePal);

   myGfx.ClearVideoMemory(0); //Fill videomemory

   myGfx.SetDrawPage(0); //Set first draw page active. Each drawing operation will only affect this page.
   myGfx.SetVisiblePage(0);

   Bitmap theSavedBackgroundBat;
   Bitmap theSavedBackgroundBall;
   theSavedBackgroundBrick = new Bitmap[totalNoOfBricks];

   DrawGameScreen();

   //The 'while' loop below is the main game loop.
   while(1)
   {
      ReadUserInputKey(); //Get new x and y values for the bat

      ballXpos += ballXdirection; //Move the ball in x-direction.
      ballYpos += ballYdirection; //Move the ball in y-direction.

      ActOnCollision( CheckCollisions() ); //Selfexplanatory

      if( *(kbdPtr+PRESS_ESC) ) //Has the player pressed the 'Esc' key?
	 break; //Yes, quit game!	 

      //Save the background behind all moving objects
      myGfx.GetScreenBitmap(playerXpos, playerYpos, playerBatWidth, playerBatHeight, theSavedBackgroundBat);
      myGfx.GetScreenBitmap(ballXpos, ballYpos, ballWidth, ballHeight, theSavedBackgroundBall);

      //Draw all moving objects
      myGfx.DrawBitmap(playerXpos, playerYpos, thePlayerBat); //Draw player
      myGfx.DrawBitmap(ballXpos, ballYpos, theBall); //Draw the ball

      myGfx.WaitVerticalRetrace(); //Ensure flicker free movements.

      //Now, restore the backgrounds behind the moving objects
      myGfx.DrawBitmap(playerXpos, playerYpos, theSavedBackgroundBat);
      myGfx.DrawBitmap(ballXpos, ballYpos, theSavedBackgroundBall);
   }

   //Free dynamic allocated memory.
   delete [] theSavedBackgroundBrick;
   delete [] theBrickBmp;

   myKbd.UnInstallKeyboard(); //Restore the old keyboard handler.
   myGfx.SetVideoMode(TEXT); //Restore the text mode.
} //That's all folks!

void DrawGameScreen(void)
{
   playerXpos = (640 - 128) / 2 - 32;
   playerYpos = 360;
   ballXpos = 200;
   ballYpos = 200;
   totalNoOfBricks = 60;
   int i, q = 0;

   myGfx.DrawBitmap(0, 0, theBackground);

   for(i = 0; i < 60; i++)
      validBricks[i] = 1; //1 = there is a brick present on the screen

   PrintNoOfLives();

   for(i = 0; i < 6; i++) //Draw six rows of bricks
      for(int j = 0; j < 10; j++) //Draw ten bricks in each row
      {
	 myGfx.GetScreenBitmap(86 + (j * 34), 50 + (i * 22), brickWidth, brickHeight, theSavedBackgroundBrick[q]); //Save the background for each brick!
	 myGfx.DrawBitmap(86 + (j * 34), 50 + (i * 22),  theBrickBmp[q]); //Draw all bricks (total 60 bricks)
	 //Save the x- and y-position of each brick.
	 theBrickBmp[q].SetXposition( 86 + (j * 34) );
	 theBrickBmp[q].SetYposition( 50 + (i * 22) );
	 q++;
      }

   //Draw the tiled marble border on the right side of the scren.
   for( int k = 0; k < 3; k++)
      myGfx.DrawBitmap(512, k * 128, theTile); //128 = y-size of the tile

   //3 * 128 pixels equals 384, but since the screen height is 400 pixels
   //we have to fill in the missing 16 pixels.
   Bitmap tmpBmp;
   myGfx.GetRAMBitmap(0, 0, 128, 16, theTile, tmpBmp);
   myGfx.DrawBitmap(512, 3 * 128, theTile); //Draw the missing 16 pixels

   //Draw the text on the marble border to the right of the screen
   unsigned char fontColor = myGfx.GetFontColor(); //Save current color.
   myGfx.SetFontColor(1); //Use a darker color (check the color table).
   PrintBorderText();
   myGfx.SetFontColor(fontColor); //Restore the old color.
}

int LoadPictures(void)
{
   int retCode;

   //Size of the bat is 64x20 pixels
   retCode = myGfx.LoadPCX("bat.pcx", thePlayerBat, gamePal, TRANSPARENCY_OFF, 0);
   if( retCode != 1)
      return -1;
   playerBatWidth = thePlayerBat.GetWidth();
   playerBatHeight = thePlayerBat.GetHeight();

   //Size of the ball is 12x12 pixels (transparent size is 10x10 pixels)
   retCode = myGfx.LoadPCX("ball.pcx", theBall, gamePal, TRANSPARENCY_ON, 15);
   if( retCode != 1)
      return -1;
   ballWidth = theBall.GetWidth();
   ballHeight = theBall.GetHeight();

   //Size of the brick is 32x20 pixels
   retCode = myGfx.LoadPCX("brick.pcx", theBrick, gamePal, TRANSPARENCY_OFF, 0);
   if( retCode != 1)
      return -1;
   brickWidth = theBrick.GetWidth();
   brickHeight = theBrick.GetHeight();
   for( int i = 0; i < totalNoOfBricks; i++ )
      theBrickBmp[i] = theBrick; //Let all the 60 bricks be the same bitmap!

   //The background picture has a size of 640x512 pixels
   retCode = myGfx.LoadPCX("backgr.pcx", theBackground, gamePal, TRANSPARENCY_OFF, 0);
   if( retCode != 1)
      return -1;

   //The tile picture has a size of 128x128 pixels
   retCode = myGfx.LoadPCX("tile.pcx", theTile, gamePal, TRANSPARENCY_OFF, 0);
   if( retCode != 1)
      return -1;

   return 1;
}

void ReadUserInputKey(void)
{
   if((*(kbdPtr+PRESS_LEFT))) //Left arrow key pressed. Decrease x-position.
      playerXpos -= batSpeed;
   else
      if((*(kbdPtr+PRESS_RIGHT))) //Right arrow key pressed. Increase x-position.
	 playerXpos += batSpeed;
}

int CheckCollisions(void)
{
   //Has the player moved the bat into the left border?
   if(playerXpos < 0)
      playerXpos = 0; //Yes, don't cross the border.
   else//or has the player moved the bat into the left border?
      if(playerXpos > 448) //448 = 512 (right border) - 64 (width of bat)
	 playerXpos = 448;//Yes, don't cross the border.

   //Has the ball hit the left border?
   if(ballXpos <= 0)
   {
      ballXpos = 0; //Yes, don't cross the border.
      ballXdirection = ballSpeed;
   }
   else//or has the ball hit the left border?
      if(ballXpos >= 500) //500 = 512 (right border) - 12 (width of bll)
      {
	 ballXpos = 500;//Yes, don't cross the border.
	 ballXdirection = -ballSpeed;
      }
   else
      if(ballYpos <= 10) //Has the ball hit the upper border?
      {
	 ballYpos = 10; //Yes, don't cross the border.
	 ballYdirection = ballSpeed;
      }
      else//or has the ball hit the lower border?
	 if(ballYpos >= 388) //388 = 400 (height of screen) - 12 (height of ball)
	    return 1; //The player has missed the ball.

   //Has the ball collided with the bat?
   if( thePlayerBat.DetectBitmapCollision(playerXpos, playerYpos, theBall, ballXpos, ballYpos) )
      ballYdirection = -ballSpeed; //We know that the ball must always go upwards after a collision with the bat

   //Check if the ball has hit a brick!
   if( theBall.DetectAreaCollision(ballXpos, ballYpos, 340, 132 ,86, 50) )
   {
      unsigned short int brickXpos;
      unsigned short int brickYpos;
      //Note: this is a very slow method for checking collisions (i.e. there are 60 collision checks for every possible collision since there are 60 bricks) and is only showed for you for education purposes. 
      int i = 60;
      while(i--)
      {
	 if( validBricks[i] ) //Don't check with already collided bricks
	 {
	    brickXpos = theBrickBmp[i].GetXposition();
	    brickYpos = theBrickBmp[i].GetYposition();
	    if( theBall.DetectBitmapCollision(ballXpos, ballYpos, theBrickBmp[i], brickXpos, brickYpos) )
	    {
	       //Remove the brick, i.e. restore the background.
	       myGfx.DrawBitmap(brickXpos, brickYpos, theSavedBackgroundBrick[i]);
	       totalNoOfBricks--; //One brick less in the game.
	       if( totalNoOfBricks == 0 )
		  return 2; //Player has completed the level. No more bricks.

	       if( ((ballYpos + 10) > brickYpos) && (ballYpos < (brickYpos + 16)) )
		  ballXdirection = -ballXdirection; //Reverse the x-direct.
	       else
		  ballYdirection = -ballYdirection; //Reverse the y-direct.
	       validBricks[i] = 0; //Mark the brick as invalid.
	       i = 0; //End loop since we found a collision
	    }
	 }
      }
   }
   return 0;
}

void ActOnCollision(int retCode)
{
   if(retCode == 0)
      return;

   if(retCode == 1) //Has the player missed the ball?
   {
      noOfBats--;
      if( noOfBats == 0) //Is there any lives left?
	 ShowGameOver(); //No, it's game over!
      else
	 PlayerMissedTheBall(); //The player will get another chance.
   }
   else
      if( retCode == 2) //No more bricks left on screen?
      {//Redraw the game scene again and continue the game
	 for(int i = 0; i < 6400000; i++) ; //Just a little pause!
	 DrawGameScreen();
      }
}

void ShowGameOver(void)
{
   PrintNoOfLives();
   noOfBats = 3; //Restore the counter
   myGfx.Print(90, 200, "Game over! Press SPACE key to play again!" );
   while( (!*(kbdPtr+PRESS_SPACE)) && (!*(kbdPtr+PRESS_ESC)) ) ;
   if( *(kbdPtr+PRESS_SPACE) )
      DrawGameScreen();
}

void PlayerMissedTheBall(void)
{
   PrintNoOfLives();
   playerXpos = (640 - 128) / 2 - 32;
   playerYpos = 360;
   ballXpos = 200;
   ballYpos = 200;
   for(int i = 0; i < 6400000; i++) ; //Just a little pause!
}

void PrintNoOfLives(void)
{
   char number[2];
   itoa( noOfBats, number, 10); //Convert the integer to a character.
   batText[16] = number[0]; //Position 16 states the number of lives
   //Remove the old text by filling its space with the color 0.
   myGfx.FilledBox(0, 0, 17*8, 8, 0); //Width of character = 8 pixels
   myGfx.Print(0, 0, batText ); //Print out the remaining lives.
}

void PrintBorderText(void)
{
   int x = 520;
   int y = 8;
   myGfx.Print(x, y        , "A simple test" );
   myGfx.Print(x, (y += 10), "game based on" );
   myGfx.Print(x, (y += 10), "the 2D32 lib." );
   myGfx.Print(x, (y += 20), "Resolution is" );
   myGfx.Print(x, (y += 10), "640x400 pixels" );
   myGfx.Print(x, (y += 20), "Following keys" );
   myGfx.Print(x, (y += 10), "are valid:" );
   myGfx.Print(x, (y += 20), "Arrow key LEFT" );
   myGfx.Print(x, (y += 10), "Arrow key RIGHT" );
   myGfx.Print(x, (y += 10), "'Esc' = quit" );
   myGfx.Print(x, (y += 30), "Don't forget to" );
   myGfx.Print(x, (y += 10), "read the source" );
   myGfx.Print(x, (y += 10), "code!" );
   myGfx.Print(x, (y += 30), "Enjoy the game!" );
   myGfx.Print(x, (y += 50), "2D32 library" );
   myGfx.Print(x, (y += 10), "Beta v1.0 1997" );
}
