/*
	Rats
	Kang Hyun Han
	December 7th, 2001

	This game is for my CS248 final project.

	PREMISE OF THE GAME:
    You come home from work one day, and you find out that your pet lab rats
    have escaped and are running about wildly in your apartment. To make 
    matters worse, your date is supposed to come over in five minutes and 
	have a romantic evening together. Your job is simple: To get every single 
	rats into the empty room (no furniture inside), where they belong.
*/

#include <windows.h>
#include <GL\gl.h>
#include <GL\glu.h>
#include <gl\glut.h>
#include <math.h>
#include <stdio.h>
#include <time.h>
#include "parse.cpp"
#include "collision.cpp"

#define PI_OVER_180  0.0174532925f
#define NUM_RATS 5
#define LIST_SIZE 15	// number of display lists
#define NUM_WALLS 24	// used for collision detection
#define NUM_PLAYER_COL_DATA 20	// used for collision detection
#define NUM_RATS_COL_DATA 15	// used for collision detection
#define MAX_TIME 300 // number of seconds given to capture all rats

typedef struct {
	GLfloat cur_xpos;
	GLfloat cur_zpos;
	GLfloat dest_xpos;
	GLfloat dest_zpos;
	GLfloat angle;
	int idleTime;
	BoundingBox2D ratBoundingBox;
} RatInfo;

struct DIB2D{
 BITMAPINFOHEADER *Info;
 RGBQUAD *palette;
 BYTE    *bits;
};
struct GLTXTLOAD{
 GLint format;
 GLint perpixel;
 GLint Width;
 GLint Height;
 BYTE* bits;
};

typedef enum {GAME_NORMAL, GAME_DANGER, GAME_LOST, GAME_WON} stateT;

GLint walls;					// walls display list id
GLint rat;						// rat display list id
GLfloat prev_xpos;				// used for collision detection
GLfloat prev_zpos;				// used for collision detection
bool    g_gamemode;             // GLUT GameMode ON/OFF
bool    g_fullscreen;           // Fullscreen Mode ON/OFF (When g_gamemode Is OFF)
GLfloat g_xpos = 0.0f;          // Position In X-Axis 
GLfloat g_zpos = 0.0f;          // Position In Z-Axis 
GLfloat	g_yrot = 0.0f;          // Y Rotation 
GLfloat g_xspeed = 0.0f;	    // X Rotation Speed
GLfloat g_yspeed = 0.0f;	    // Y Rotation Speed
GLfloat	g_z = 0.0f;	            // Depth Into The Screen 
GLfloat g_heading = 0.0f;
GLfloat g_lookupdown = 0.0f;    // Look Position In The Z-Axis
bool key[255];		            // Lookup Table For Key's State
stateT gameState;
static BoundingBox2D *wallsCollisionBoxes;	//bounding box data structure
static BoundingBox2D *colDataForPlayer;
static BoundingBox2D *colDataForRats;
static BoundingBox2D playerCollisionBox;
static BoundingBox2D goalRoomBoundingBox;
static RatInfo *rats;			// rats information
time_t startTime,currentTime;	// for keeping track of time
double diffTime;
char timeElapsed[3],timeRecorded[3];

// the rest are for used for reading in 3d data
static sample_MATERIAL *materials;
static sample_TEXTURE *texture_maps;
static int num_texturemaps;
static short **face_indicies;
static GLfloat **vertices;
static GLfloat **normals;
static GLfloat **textures;
static int **material_ref;
static int num_triangles;

/*
LoadDIB is taken from 3D Exploration's code that is generated when exported as an
OpenGL file. Although functions used for reading in texture were used directly,
other functions for reading and parsing 3d data fils had to be heavily modified
to suit my needs.

BOOL LoadDIB(char*file,DIB2D*dib)
Only trueColor and 256 color ucompressed bitmaps supported
*/
BOOL LoadDIB(char*file,DIB2D*dib)
 {
  BOOL result=FALSE;
  HANDLE hfile=CreateFile(file,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING ,FILE_ATTRIBUTE_NORMAL,0);
  if(hfile!=INVALID_HANDLE_VALUE)
    {
     DWORD readed;
     int size=GetFileSize(hfile,0);
     if(size>sizeof(BITMAPFILEHEADER))
      {
       BITMAPFILEHEADER bmfh;
       ReadFile(hfile,&bmfh,sizeof(BITMAPFILEHEADER),&readed,0);
       if((readed==sizeof(BITMAPFILEHEADER)) && (bmfh.bfType==0x4d42))
        {
         dib->Info=(BITMAPINFOHEADER*)(new BYTE[size-sizeof(BITMAPFILEHEADER)]);
         ReadFile(hfile,dib->Info,size-sizeof(BITMAPFILEHEADER),&readed,0);
         dib->bits=(BYTE*)(dib->Info+1);

         if(dib->Info->biBitCount==8)
          {
           dib->palette=(RGBQUAD*)dib->bits;
           if(dib->Info->biClrUsed)dib->bits+=dib->Info->biClrUsed*4;else dib->bits+=1024;
          }else{
           dib->palette=NULL;
          }
         result=TRUE;
        }
      }
     CloseHandle(hfile);
    }
   return result;
 };

long ScanBytes(int pixWidth, int bitsPixel) {
  return (((long)pixWidth*bitsPixel+31) / 32) * 4;
}

BOOL  ScaleImage(DIB2D&dib,GLTXTLOAD&p)
 {
   GLint glMaxTexDim;     // OpenGL maximum texture dimension
   GLint XDMaxTexDim=512; // user maximum texture dimension
   GLint minsize =2;
   double xPow2, yPow2;
   int ixPow2, iyPow2;
   int xSize2, ySize2;
   GLint m_iWidth=dib.Info->biWidth;
   GLint m_iHeight=dib.Info->biHeight;
   glGetIntegerv(GL_MAX_TEXTURE_SIZE, &glMaxTexDim);

   glMaxTexDim = min(XDMaxTexDim, glMaxTexDim);

   if (m_iWidth <= glMaxTexDim)
      xPow2 = log((double)m_iWidth) / log(2.0);
   else
      xPow2 = log((double)glMaxTexDim) / log(2.0);

   if (m_iHeight <= glMaxTexDim)
      yPow2 = log((double)m_iHeight) / log(2.0);
   else
      yPow2 = log((double)glMaxTexDim) / log(2.0);

   ixPow2 = (int)xPow2;
   iyPow2 = (int)yPow2;

   if (xPow2 != (double)ixPow2)
      ixPow2++;
   if (yPow2 != (double)iyPow2)
      iyPow2++;

   xSize2 = 1 << ixPow2;
   ySize2 = 1 << iyPow2;

   if(xSize2<minsize)xSize2=minsize;
   if(ySize2<minsize)ySize2=minsize;

   if(((xSize2==m_iWidth) && (ySize2==m_iHeight)))
   {
     if(dib.Info->biBitCount==24){
       p.format=GL_BGR_EXT;
       p.perpixel=3;
       return FALSE;
      }
     if(dib.Info->biBitCount==32)
      {
       p.format=GL_BGRA_EXT;
       p.perpixel=4;
       return FALSE;
      }
   }

   BYTE *bits=(BYTE *)dib.bits;
   if(dib.Info->biBitCount==8){

    // convert to TRUECOLOR
    int _perline=ScanBytes(8,m_iWidth);
    int perline=ScanBytes(24,m_iWidth);
    bits= new BYTE[perline*m_iHeight * sizeof(BYTE)];
    for(int y=0;y<m_iHeight;y++){
     BYTE *_b=((BYTE *)dib.bits)+y*_perline;
     BYTE *b=bits+y*perline;
     for(int x=0;x<m_iWidth;x++){
      RGBQUAD _p=dib.palette[*_b];
      _b++;
      *b=_p.rgbBlue;b++;
      *b=_p.rgbGreen;b++;
      *b=_p.rgbRed;b++;
     }
    }
   }
   BOOL isAlpha=(dib.Info->biBitCount==32);
   int _mem_size=xSize2 * ySize2 *  sizeof(BYTE);
   if(isAlpha){
         _mem_size*=4;
          p.perpixel=4;
          p.format=GL_BGRA_EXT;
          }else {
          _mem_size*=3;
          p.perpixel=3;
          p.format=GL_BGR_EXT;
          }
   BYTE *pData = (BYTE*)new BYTE[_mem_size];
   if (!pData) return FALSE;

   if(isAlpha){
   gluScaleImage(GL_BGRA_EXT, m_iWidth, m_iHeight,
                 GL_UNSIGNED_BYTE, bits,
                 xSize2, ySize2, GL_UNSIGNED_BYTE, pData);
   }
   else
   gluScaleImage(GL_RGB, m_iWidth, m_iHeight,
                 GL_UNSIGNED_BYTE, bits,
                 xSize2, ySize2, GL_UNSIGNED_BYTE, pData);


   if(bits!=dib.bits)delete bits;

   m_iWidth = xSize2 ;
   m_iHeight = ySize2 ;
   p.Width=m_iWidth;
   p.Height=m_iHeight;
   p.bits=pData;

   return TRUE ;
 }

void LoadTexture(char*filename)
 {
  DIB2D dib;
  GLTXTLOAD load;
  if(LoadDIB(filename,&dib))
   {

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    if(ScaleImage(dib,load))
     {
		gluBuild2DMipmaps(GL_TEXTURE_2D,load.perpixel,
                load.Width,load.Height,
                load.format,GL_UNSIGNED_BYTE,
                load.bits);
                delete load.bits;
     }else{

   glTexImage2D(GL_TEXTURE_2D,0,load.perpixel,
                dib.Info->biWidth,dib.Info->biHeight,
                0,load.format,GL_UNSIGNED_BYTE,dib.bits);
     }
   delete dib.Info;
   }
 }


void MyMaterial(GLenum mode,GLfloat *f,GLfloat alpha)
{
 GLfloat d[4];
 d[0]=f[0];
 d[1]=f[1];
 d[2]=f[2];
 d[3]=alpha;
 glMaterialfv (GL_FRONT_AND_BACK,mode,d);
}
/*
 *  SelectMaterial uses OpenGL commands to define facet colors.
 *
 */

void SelectMaterial(int i)
{
  //
  // Define the reflective properties of the 3D Object faces.
  //
	glEnd();
	GLfloat alpha=materials[i].alpha;
	MyMaterial (GL_AMBIENT, materials[i].ambient,alpha);
	MyMaterial (GL_DIFFUSE, materials[i].diffuse,alpha);
	MyMaterial (GL_SPECULAR, materials[i].specular,alpha);
	//MyMaterial (GL_EMISSION, materials[i].emission,alpha); // omitted 
	glMaterialf (GL_FRONT_AND_BACK,GL_SHININESS,materials[i].phExp);	
	glEnd();

	if(materials[i].texture>-1)
	{
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D,texture_maps[materials[i].texture].id);
	}
	else
	glDisable(GL_TEXTURE_2D);
	glBegin(GL_TRIANGLES);

	glBegin(GL_TRIANGLES);

};

// This creates a display list from a data file, which is parsed by ParseObjectFile().

GLint CreateDisplayList(char *dataFile)
{
	int i, j;
	GLuint texture_name;
	GLint list_id;
	int mcount=0;
	int mindex=0;

	ParseObjectFile(dataFile, &materials, &texture_maps, &face_indicies, &vertices, &normals, &textures, &material_ref, &num_triangles, &num_texturemaps);


	for(i=0;i<num_texturemaps;i++)
	{
		glGenTextures(1,&texture_name);
		texture_maps[i].id=texture_name;
		glBindTexture(GL_TEXTURE_2D,texture_name);
		LoadTexture(texture_maps[i].name);
	}


	list_id = glGenLists(LIST_SIZE);
	glNewList(list_id, GL_COMPILE);

    glBegin (GL_TRIANGLES);
		for(i=0;i<num_triangles;i++)
		{
			if(!mcount)
			{
				SelectMaterial(material_ref[mindex][0]);
				mcount=material_ref[mindex][1];
				mindex++;
			}
			mcount--;
			for(j=0;j<3;j++)
			{
				int vi=face_indicies[i][j];
				int ni=face_indicies[i][j+3];//Normal index
				int ti=face_indicies[i][j+6];//Texture index
				glNormal3f (normals[ni][0],normals[ni][1],normals[ni][2]);
				glTexCoord2f(textures[ti][0],textures[ti][1]);
				glVertex3f (vertices[vi][0],vertices[vi][1],vertices[vi][2]);
			}
       }
	glEnd ();

    glEndList();
	return list_id;
}


// RandomInteger() is taken from Programming Abstractions in C.
int RandomInteger(int low, int high)
{
	int k;
	double d;

	d = (double) rand()/ ((double) RAND_MAX +1);
	k = (int) (d* (high - low +1));
	return (low +k);
}

// This function initializes all data structures related to collision detection
void InitializeCollisionData(void)
{
	ReadCollisionData("walls.col", &wallsCollisionBoxes); // collision data for both the player and rats
	ReadCollisionData("player.col", &colDataForPlayer); // for the player
	ReadCollisionData("rats.col", &colDataForRats); // rats

	//goalRoomBoundingBox is used for checking whether all mice are captured
	//by means of collision detection
	goalRoomBoundingBox.xmin = 15.7 ;
	goalRoomBoundingBox.xmax = 84.54;
	goalRoomBoundingBox.zmin = -156.4;
	goalRoomBoundingBox.zmax = -99.43;
}

// This function initializes the rat data structure.
void InitializeRats(void)
{
	rats = (RatInfo *)malloc(sizeof(RatInfo) * NUM_RATS);
	rats[0].cur_xpos = 20;
	rats[0].cur_zpos = -30;
	rats[0].dest_xpos = 5;
	rats[0].dest_zpos = -30;
	rats[1].cur_xpos = -20;
	rats[1].cur_zpos = -100;
	rats[1].dest_xpos = 0;
	rats[1].dest_zpos = -10;
	rats[2].cur_xpos = 0;
	rats[2].cur_zpos = -120;
	rats[2].dest_xpos = 10;
	rats[2].dest_zpos = -100;
	rats[3].cur_xpos = 5;
	rats[3].cur_zpos = -100;
	rats[3].dest_xpos = 5;
	rats[3].dest_zpos = -100;
	rats[4].cur_xpos = 0;
	rats[4].cur_zpos = -70;
	rats[4].dest_xpos = 0;
	rats[4].dest_zpos = -40;

	for(int i=0; i<NUM_RATS; i++)
	{
		rats[i].idleTime = 0;
		rats[i].angle = 0;
	}
}

// This function initializes the lights for the games
void InitializeLights (void)
{

    //GL_LIGHT0 living room
	//GL_LIGHT1 parents room
	//GL_LIGHT2 dad's room
	//GL_LIGHT3 my room
	//GL_LIGHT4 dining hall

    GLfloat glfLightAmbient[] = { 0.05f, 0.05f, 0.05f, 1.0f};
    GLfloat glfLightSpecular[] = { 1.0f, 1.0f, 1.0f, 1.0f };

	GLfloat glfLightDiffuse[] = { 0.255f, 0.568f, 0.157f, 1.0f };
	GLfloat glfLightDiffuse1[] = { 0.157f, 0.619f, 0.898f, 1.0f };
	GLfloat glfLightDiffuse2[] = { 0.8f, 0.8f, 0.8f, 1.0f };
	GLfloat glfLightDiffuse3[] = { 0.980f, 0.729f, 0.368f, 1.0f };
	GLfloat glfLightDiffuse4[] = { 1.0f, 1.0f, 1.0f, 1.0f };

	//GLfloat glfLightPosition2[]
	//GLfloat glfLightPosition3[]
    //
    // Add a light to the scene.
    //
    glLightfv (GL_LIGHT0, GL_AMBIENT, glfLightAmbient);
    glLightfv (GL_LIGHT0, GL_DIFFUSE, glfLightDiffuse);
    glLightfv (GL_LIGHT0, GL_SPECULAR, glfLightSpecular);

	glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, .25);
	glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.0005);
	glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.0005);

    glEnable (GL_LIGHTING);
    glEnable (GL_LIGHT0);

    glLightfv (GL_LIGHT1, GL_AMBIENT, glfLightAmbient);
    glLightfv (GL_LIGHT1, GL_DIFFUSE, glfLightDiffuse1);
    glLightfv (GL_LIGHT1, GL_SPECULAR, glfLightSpecular);

	glLightf(GL_LIGHT1, GL_CONSTANT_ATTENUATION, .25);
	glLightf(GL_LIGHT1, GL_LINEAR_ATTENUATION, 0.0005);
	glLightf(GL_LIGHT1, GL_QUADRATIC_ATTENUATION, 0.0005);

	glEnable (GL_LIGHT1);


    glLightfv (GL_LIGHT2, GL_AMBIENT, glfLightAmbient);
    glLightfv (GL_LIGHT2, GL_DIFFUSE, glfLightDiffuse2);
    glLightfv (GL_LIGHT2, GL_SPECULAR, glfLightSpecular);

	glLightf(GL_LIGHT2, GL_CONSTANT_ATTENUATION, .25);
	glLightf(GL_LIGHT2, GL_LINEAR_ATTENUATION, 0.0005);
	glLightf(GL_LIGHT2, GL_QUADRATIC_ATTENUATION, 0.0005);

	glEnable (GL_LIGHT2);

    glLightfv (GL_LIGHT3, GL_AMBIENT, glfLightAmbient);
    glLightfv (GL_LIGHT3, GL_DIFFUSE, glfLightDiffuse3);
    glLightfv (GL_LIGHT3, GL_SPECULAR, glfLightSpecular);

	glLightf(GL_LIGHT3, GL_CONSTANT_ATTENUATION, .25);
	glLightf(GL_LIGHT3, GL_LINEAR_ATTENUATION, 0.0005);
	glLightf(GL_LIGHT3, GL_QUADRATIC_ATTENUATION, 0.0005);

	glEnable (GL_LIGHT3);

	
    glLightfv (GL_LIGHT4, GL_AMBIENT, glfLightAmbient);
    glLightfv (GL_LIGHT4, GL_DIFFUSE, glfLightDiffuse4);
    glLightfv (GL_LIGHT4, GL_SPECULAR, glfLightSpecular);

	glLightf(GL_LIGHT4, GL_CONSTANT_ATTENUATION, .25);
	glLightf(GL_LIGHT4, GL_LINEAR_ATTENUATION, 0.0005);
	glLightf(GL_LIGHT4, GL_QUADRATIC_ATTENUATION, 0.0005);

	glEnable (GL_LIGHT4);

    glEnable (GL_DEPTH_TEST);    
    glEnable (GL_CULL_FACE);
    //glLightModeli(GL_LIGHT_MODEL_TWO_SIDE ,1);
	//glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER ,1);
}

/*
   This function updates all rats' positions. They run away from the player when he gets close;
   If the rat is not at its destination, it moves a step closer to it. Then each rat is checked
   for possible collisions with any of the walls and the collision data for rats. Finally, the rats'
   orientation is calculated so that it will be drawn correctly in the Display() function.
*/
void UpdateRatsLocation()
{
	int prev_xpos, prev_zpos;
	int xpos_offset, zpos_offset;
	double tempAngle;

	for (int i=0; i< NUM_RATS; i++) //for each rat
	{
		prev_xpos = rats[i].cur_xpos;
		prev_zpos = rats[i].cur_zpos;

		rats[i].idleTime++;
		//printf("rat[0] xpos = %f rat[0] zpos = %f\n", rats[0].cur_xpos, rats[0].cur_zpos);
		//printf("rat[1] xpos = %f rat[1] zpos = %f\n", rats[1].cur_xpos, rats[1].cur_zpos);
		//printf("player xpos = %f player zpos = %f\n", g_xpos, g_zpos);

		if (pow(fabs(rats[i].cur_xpos- g_xpos),2) + pow(fabs(rats[i].cur_zpos- g_zpos),2) < 100)
		{
			
			if(rats[i].cur_xpos > g_xpos)
				rats[i].dest_xpos = rats[i].cur_xpos + RandomInteger(5,15);
			else rats[i].dest_xpos = rats[i].cur_xpos - RandomInteger(5,15);

			if(rats[i].cur_zpos > g_zpos)
				rats[i].dest_zpos = rats[i].cur_zpos + RandomInteger(5,15);
			else rats[i].dest_zpos = rats[i].cur_zpos - RandomInteger(5,15);
		}


		if (fabs(rats[i].dest_xpos- rats[i].cur_xpos) > 1)
		{
			if(rats[i].cur_xpos > rats[i].dest_xpos)
				rats[i].cur_xpos = rats[i].cur_xpos - 2;
			else rats[i].cur_xpos = rats[i].cur_xpos + 2;
		}

		if (fabs(rats[i].dest_zpos- rats[i].cur_zpos) > 1)
		{
			if(rats[i].cur_zpos > rats[i].dest_zpos)
				rats[i].cur_zpos = rats[i].cur_zpos - 2;
			else rats[i].cur_zpos = rats[i].cur_zpos + 2;
		}

		// generate bounding box
		rats[i].ratBoundingBox.xmin = rats[i].cur_xpos-4;
		rats[i].ratBoundingBox.xmax = rats[i].cur_xpos+4;
		rats[i].ratBoundingBox.zmin = rats[i].cur_zpos-4;
		rats[i].ratBoundingBox.zmax = rats[i].cur_zpos+4;

		for (int k=0; k< NUM_RATS; k++)
		{
			if (i != k) // so that you don't compare it to the same rat
			{
				if (CollisionDetected(rats[i].ratBoundingBox, rats[k].ratBoundingBox))
				{
					rats[i].cur_xpos = prev_xpos;
					rats[i].cur_zpos = prev_zpos;
				}
			}
		}

		for(int j=0; j< NUM_WALLS; j++)
		{
			if (CollisionDetected(rats[i].ratBoundingBox, wallsCollisionBoxes[j]))
			{
				rats[i].cur_xpos = prev_xpos;
				rats[i].cur_zpos = prev_zpos;

				xpos_offset = RandomInteger(0,30);
				if(RandomInteger(0,1)) xpos_offset *= -1;

				zpos_offset = RandomInteger(0,30);
				if(RandomInteger(0,1)) zpos_offset *= -1;

				rats[i].dest_xpos = rats[i].cur_xpos + xpos_offset;
				rats[i].dest_zpos = rats[i].cur_zpos + zpos_offset;
				rats[i].idleTime = 0;
			}
		}

		for(int m=0; m< NUM_RATS_COL_DATA; m++) 
		{
			if (CollisionDetected(rats[i].ratBoundingBox, colDataForRats[m]))
			{
				rats[i].cur_xpos = prev_xpos;
				rats[i].cur_zpos = prev_zpos;

				xpos_offset = RandomInteger(0,30);
				if(RandomInteger(0,1)) xpos_offset *= -1;

				zpos_offset = RandomInteger(0,30);
				if(RandomInteger(0,1)) zpos_offset *= -1;

				rats[i].dest_xpos = rats[i].cur_xpos + xpos_offset;
				rats[i].dest_zpos = rats[i].cur_zpos + zpos_offset;
				rats[i].idleTime = 0;
			}
		}

		if (rats[i].idleTime == 200)
		{
			rats[i].cur_xpos = prev_xpos;
			rats[i].cur_zpos = prev_zpos;

			xpos_offset = RandomInteger(0,40);
			if(RandomInteger(0,1)) xpos_offset *= -1;

			zpos_offset = RandomInteger(0,40);
			if(RandomInteger(0,1)) zpos_offset *= -1;

			rats[i].dest_xpos = rats[i].cur_xpos + xpos_offset;
			rats[i].dest_zpos = rats[i].cur_zpos + zpos_offset;
			rats[i].idleTime = 0;
		}

		tempAngle = asin((double)(fabs(rats[i].dest_zpos-rats[i].cur_zpos))/(double)(fabs(rats[i].dest_zpos-rats[i].cur_zpos))) / (2* 3.14) * 360;
		if(rats[i].cur_xpos == rats[i].dest_xpos)
		{
			if(rats[i].cur_zpos > rats[i].dest_zpos) rats[i].angle = 0;
			if(rats[i].cur_zpos < rats[i].dest_zpos) rats[i].angle = 180;
		}
		if(rats[i].cur_xpos < rats[i].dest_xpos)
		{
			if(rats[i].cur_zpos > rats[i].dest_zpos) rats[i].angle = tempAngle;
			if(rats[i].cur_zpos == rats[i].dest_zpos) rats[i].angle = 90;
			if(rats[i].cur_zpos < rats[i].dest_zpos) rats[i].angle = 180-tempAngle;
		}
		else if(rats[i].cur_xpos > rats[i].dest_xpos)
		{
			if(rats[i].cur_zpos > rats[i].dest_zpos) rats[i].angle = tempAngle;
			if(rats[i].cur_zpos == rats[i].dest_zpos) rats[i].angle = 270;
			if(rats[i].cur_zpos < rats[i].dest_zpos) rats[i].angle = 180+tempAngle;
		}
	}
	//printf("%f ", rats[1].angle);
}

// This is for exiting the game
void keyboard(unsigned char key, int x, int y)
{
  switch (key) {
	case 27:
      exit(0);
      break;
   }
}

// This calls other initialization functions
void InitializeWorld()
{
	srand((int) time(NULL)); // initializing random generator
	rat = CreateDisplayList("rat.dat");
	walls = CreateDisplayList("walls.dat");

	glEnable(GL_NORMALIZE);
	gameState = GAME_NORMAL;
	InitializeCollisionData();
	InitializeRats();
	InitializeLights();
}

/*
   Each rat is checked for collision with the bounding box of the room that they
   must be captured in. gameState's value becomes GAME_WON if all rats are found to be
   colliding with the box.
*/
void UpdateGoalState()
{
	int count;

	count = 0;

	for (int i=0; i< NUM_RATS; i++) //for each rat
	{
		if(CollisionDetected(rats[i].ratBoundingBox, goalRoomBoundingBox))
			count++;
	}

	if(count == NUM_RATS)
	{
		gameState = GAME_WON;
		strcpy(timeRecorded, timeElapsed);
	}
}

/*
   If the elapsed time goes over MAX_TIME that is given, gameState's value becomes
   GAME_LOST
*/

void UpdateTime()
{
	currentTime = time(NULL);
	diffTime = difftime(currentTime,startTime);
	sprintf(timeElapsed, "%d", (int)diffTime);
	if(diffTime > MAX_TIME) gameState = GAME_LOST;
}

void InitializeTime()
{
	startTime = time(NULL);
	UpdateTime();
}

/*
	This function is responsible for drawing things on the screen. According to gameState,
	this function displays what is appropriate. In the normal mode, the time elapsed is 
	displayed, and when gameState is GAME_WON, a congratuatory message is display. Conversely,
	when gameState is GAME_LOST, a message of contempt is displayed. ("You Lose")

*/
void Display(void)
{
	char *ptr;
    GLfloat glfLightPosition[] = {-87.4, 36.3, -65.8, 1.0}; // living room
	GLfloat glfLightPosition1[] = {-119.4, 52.4,-241.4, 1.0}; // parents room
	GLfloat glfLightPosition2[] = {-63.0, 49.6,-21.7, 1.0}; // dad's room
	GLfloat glfLightPosition3[] = {46.8, 48.7,-202.3, 1.0}; // my room
	GLfloat glfLightPosition4[] = {48.5, 60.8, -70.9, 1.0}; // dining hall
	
	ptr = (char *)timeElapsed;
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();

	GLfloat xtrans = -g_xpos;
	GLfloat ztrans = -g_zpos;
	GLfloat ytrans = -30;
	GLfloat sceneroty = 360.0f - g_yrot;

	glPushMatrix();
	glRotatef(g_lookupdown,1.0,0,0);
	glRotatef(sceneroty,0,1.0,0);
	glTranslatef(xtrans, ytrans, ztrans);
	glLightfv(GL_LIGHT0, GL_POSITION, glfLightPosition);
	glLightfv(GL_LIGHT1, GL_POSITION, glfLightPosition1);
	glLightfv(GL_LIGHT2, GL_POSITION, glfLightPosition2);
	glLightfv(GL_LIGHT3, GL_POSITION, glfLightPosition3);
	glLightfv(GL_LIGHT4, GL_POSITION, glfLightPosition4);
	glCallList(walls);

	for(int i=0; i < NUM_RATS; i++)
	{
		glPushMatrix();
			
			glTranslatef(rats[i].cur_xpos, 2 ,rats[i].cur_zpos);
			glRotatef(-rats[i].angle, 0, 1.0, 0);
			glCallList(rat);
		glPopMatrix();
	}
	glPopMatrix();

	glDisable (GL_DEPTH_TEST);

	if(gameState == GAME_LOST)
	{
		glPushMatrix();

   		glColor3f (1.0, 1.0, 1.0);
		glTranslatef(-.5, 0, -2);
		glScalef(.0025,.0025,.0025);
		glLineWidth(5.0);
		glutStrokeCharacter(GLUT_STROKE_ROMAN, 'Y'); 
		glutStrokeCharacter(GLUT_STROKE_ROMAN, 'O');
		glutStrokeCharacter(GLUT_STROKE_ROMAN, 'U');
		glutStrokeCharacter(GLUT_STROKE_ROMAN, ' ');
		glutStrokeCharacter(GLUT_STROKE_ROMAN, 'L');
		glutStrokeCharacter(GLUT_STROKE_ROMAN, 'O');
		glutStrokeCharacter(GLUT_STROKE_ROMAN, 'S');
		glutStrokeCharacter(GLUT_STROKE_ROMAN, 'E');
		glLineWidth(1.0);
		glPopMatrix();
	}
	else if (gameState == GAME_WON)
	{
		glPushMatrix();

   		glColor3f (1.0, 1.0, 1.0);
		glTranslatef(-.5, 0, -2);
		glScalef(.0025,.0025,.0025);
		glLineWidth(5.0);
		glutStrokeCharacter(GLUT_STROKE_ROMAN, 'Y'); 
		glutStrokeCharacter(GLUT_STROKE_ROMAN, 'O');
		glutStrokeCharacter(GLUT_STROKE_ROMAN, 'U');
		glutStrokeCharacter(GLUT_STROKE_ROMAN, ' ');
		glutStrokeCharacter(GLUT_STROKE_ROMAN, 'W');
		glutStrokeCharacter(GLUT_STROKE_ROMAN, 'I');
		glutStrokeCharacter(GLUT_STROKE_ROMAN, 'N');
		glutStrokeCharacter(GLUT_STROKE_ROMAN, '!');
		glLineWidth(1.0);
		glPopMatrix();
	}
	else if (gameState == GAME_NORMAL)
	{
	glPushMatrix();
   	glColor3f (1.0, 1.0, 1.0);
	glTranslatef(.5, 1.5, -2);
	glScalef(.0025,.0025,.0025);
	glLineWidth(5.0);
	glutStrokeCharacter(GLUT_STROKE_ROMAN, 'T'); 
	glutStrokeCharacter(GLUT_STROKE_ROMAN, 'I');
	glutStrokeCharacter(GLUT_STROKE_ROMAN, 'M');
	glutStrokeCharacter(GLUT_STROKE_ROMAN, 'E');
	glutStrokeCharacter(GLUT_STROKE_ROMAN, ':');
	glutStrokeCharacter(GLUT_STROKE_ROMAN, ' ');
	
	while(*ptr != '\0')
	{
		glutStrokeCharacter(GLUT_STROKE_ROMAN, (int) *ptr);
		ptr++;
	}
	glLineWidth(1.0);
	glPopMatrix();
	}

	glEnable (GL_DEPTH_TEST);
	glFlush();
	glutSwapBuffers();
}

// Our Keyboard Handler For Special Keys (Like Arrow Keys And Function Keys)
void special_keys(int key_index, int x, int y)
{
	key[key_index] = true;
}

void special_keys_up(int key_index, int x, int y)
{
	key[key_index] = false;
}

/*
   This function updates the data structures associated with the game. nehe.gamedev.net was
   very helpful for this section.
*/

void UpdateScene()
{
	prev_xpos = g_xpos;
	prev_zpos = g_zpos;

	// Process User Input
	if (key[GLUT_KEY_PAGE_UP]) {
		if(g_lookupdown > -70)
		{
			g_z -= 0.02f;
			g_lookupdown -= 1.0f;
		}
	}
	if (key[GLUT_KEY_PAGE_DOWN]) {
		if(g_lookupdown < 70)
		{
			g_z += 0.02f;
			g_lookupdown += 1.0f;
		}
	}
	if (key[GLUT_KEY_UP]) {
		g_xpos -= (float)sin(g_heading*PI_OVER_180) * 2.0f;
		g_zpos -= (float)cos(g_heading*PI_OVER_180) * 2.0f;
	}
	if (key[GLUT_KEY_DOWN]) {
		g_xpos += (float)sin(g_heading*PI_OVER_180) * 2.0f;
		g_zpos += (float)cos(g_heading*PI_OVER_180) * 2.0f;
	}
	if (key[GLUT_KEY_RIGHT]) {
		g_heading -= 4.0f;
		g_yrot = g_heading;
	}
	if (key[GLUT_KEY_LEFT]) {
		g_heading += 4.0f;
		g_yrot = g_heading;
	}

	playerCollisionBox.xmin = g_xpos-3;
	playerCollisionBox.xmax = g_xpos+3;
	playerCollisionBox.zmin = g_zpos-3;
	playerCollisionBox.zmax = g_zpos+3;

	for (int i=0; i<NUM_WALLS; i++)
	{
		if (CollisionDetected(wallsCollisionBoxes[i], playerCollisionBox))
		{
			g_xpos = prev_xpos;
			g_zpos = prev_zpos;
			//printf("Collision Detected!");
		}
	}

	for (int j=0; j< NUM_PLAYER_COL_DATA; j++)
	{
		if (CollisionDetected(colDataForPlayer[j], playerCollisionBox))
		{
			g_xpos = prev_xpos;
			g_zpos = prev_zpos;
			//printf("Collision Detected!");
		}
	}

	UpdateRatsLocation();
	UpdateGoalState();
	UpdateTime();
	Display(); 
}


void Reshape(int w, int h)
{ 
	glViewport(0,0, (GLsizei) w, (GLsizei) h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(90, (GLfloat) w/(GLfloat) h, 1, 1000.0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

// This full screen mode function is taken from nehe.gamedev.net
void ask_gamemode()
{
	int answer;
	// Use Windows MessageBox To Ask The User For Game Or Windowed Mode
	answer = MessageBox(NULL, "Would you like to play fullscreen?", "Question",
						MB_ICONQUESTION | MB_YESNO);
	g_gamemode = (answer == IDYES);
	g_fullscreen = false; 
}

int main(int argc, char **argv)
{
	ask_gamemode();
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DEPTH | GLUT_RGBA | GLUT_DOUBLE);

	if (g_gamemode) {
		glutGameModeString("640x480:16");            // Select The 640x480 In 16bpp Mode
		if (glutGameModeGet(GLUT_GAME_MODE_POSSIBLE))
			glutEnterGameMode();                     // Enter Full Screen
		else g_gamemode = false;                     // Cannot Enter Game Mode, Switch To Windowed
	}
	if (!g_gamemode) {
		glutInitWindowSize(640, 480);                // Window Size If We Start In Windowed Mode
		glutCreateWindow("Rats"); // Window Title 
	}

	InitializeWorld();
	glutIgnoreKeyRepeat(true);
	glutReshapeFunc(Reshape);
	glutKeyboardFunc(keyboard);
	glutSpecialFunc(special_keys);
	glutSpecialUpFunc(special_keys_up);
	glutDisplayFunc(Display);
	glutIdleFunc(UpdateScene);
	InitializeTime();
	glutMainLoop();

	return 1;
}