//***********************************************************
//FILE: Pong.cpp                                            *
//                                                          *
//AUTHOR:   Yee Hsu                                         *
//                                                          *
//DATE: 4/29/02                                             *
//                                                          *
//CLASS: CSc630                                             *
//                                                          *
//USAGE COMMENTS: This is the source file for Pong.cpp      *
//      Should be compiled using VC++                       *
//                                                          *
//LIBARIES:                                                 *
//      <GL/glut.h> - for OpenGL and glut tool kits         * 
//      <stdio.h>   - standard input/output                 *
//      <Windows.h> - Windows header file. for strlen       *
//      <ctype.h>   -                                       *
//      <gl/glaux.h>- glut tool kit                         *
//                                                          *
//FUNCTIONS/METHODS DECLARED:                               *
//      void init();                                        *
//      void addMenuFeatures(void);                         *
//      void loadTexture(char*);                            *
//      void showIntro(void);                               *
//      void display(void);                                 *
//      void keyboard(unsigned char, int, int);             *
//      void reshape(int, int);                             *
//      void animate(void);                                 *
//      void helpMenu(int);                                 *
//      void arrowKeys(int key, int x, int y);              *
//                                                          *
//GENERAL COMMENTS:                                         *
//      3D Pong game created for CSc 630 Project2.  Early   *
//  stage of the game development.  Once executed it only   *
//  shows the introduction of the game and the basic        *
//  structure of the game design.                           *
//***********************************************************

#include "pong.h"

//***********************************************************
//FUNCTION: int main(int argc, char** argv)                 *
//  Driver of the program                                   *
//***********************************************************

int main(int argc, char *argv[])
{
    // setup and init
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    mainwin = glutCreateWindow("3D Pong");
    glutFullScreen();
    init();

    // callback routines
    glutDisplayFunc(display);
    glutKeyboardFunc(keyboard);
    glutReshapeFunc(reshape);
    glutIdleFunc(animate);
    glutSpecialFunc(arrowKeys);

    // menu
    addMenuFeatures();

    glutMainLoop();
    return 0;
}

//***********************************************************
//FUNCTION: void myInit(void)                               *
//  Initializes the main program and load textures for      *
//  introduction object                                     *
//***********************************************************

void init(void)
{
    // intro textures
    loadTexture("side1.bmp");       // 6 sides of the cube
    loadTexture("side2.bmp");       //
    loadTexture("side3.bmp");       // stored into array
    loadTexture("side4.bmp");       // variable texture[6];
    loadTexture("side5.bmp");       // from:
    loadTexture("side6.bmp");       // texture[0]...texture[5]
    loadTexture("background1.bmp"); // background:  texture[6]

    // arena map 1 textures
    loadTexture("field.bmp");       // ground:      texture[7]
    loadTexture("stadium.bmp");     // 4 sides:     texture[8]
    loadTexture("sky.bmp");         // background:  texture[9]

    // arena map 2 textures
    loadTexture("tile.bmp");        // temple:      texture[10]
    loadTexture("dirtground.bmp");  // ground:      texture[11]
    loadTexture("acropolis.bmp");   // background:  texture[12]

    // arena map 3 textures
    loadTexture("moon.bmp");        // sphere:      texture[13]
    loadTexture("earth.bmp");       //              texture[14]
    loadTexture("pluto.bmp");       //              texture[15]
    loadTexture("sun.bmp");         //              texture[16]
    loadTexture("mercury.bmp");     //              texture[17]
    loadTexture("uranus.bmp");      //              texture[18]
    loadTexture("jupiter.bmp");     //              texture[19]
    loadTexture("saturn.bmp");      //              texture[20]
    loadTexture("mars.bmp");        //              texture[21]
    loadTexture("matrix.bmp");      // field:       texture[22]
    loadTexture("starfield.bmp");   // background:  texture[23]

    // arena map 4 textures
    loadTexture("rotary.bmp");      // rotary:      texture[24]
    loadTexture("flare.bmp");       // background:  texture[25]

    // gameover textures
    loadTexture("gameover.bmp");    // background:  texture[26]
    glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
    glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);

    // set color and depth
    glClearColor(1.0, 1.0, 1.0, 0.0);
    glClearDepth(1.0);

    // fog
    GLfloat fogColor[] = {0.8, 0.8, 0.8, 1.0};

    glFogi(GL_FOG_MODE, GL_LINEAR);
    glHint(GL_FOG_HINT, GL_NICEST);
    glFogf(GL_FOG_START, 25.0);
    glFogf(GL_FOG_END, 100.0);
    glFogfv(GL_FOG_COLOR, fogColor);

    // setup enviornment
    glShadeModel(GL_SMOOTH);
    glDepthFunc(GL_LEQUAL);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

    // enable environment effects
    glEnable(GL_LIGHT0);
    glEnable(GL_LIGHTING);
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_DEPTH_TEST);

    // init intro background flag points
    for (int x = 0; x < GRID_POINTS; x++)
    {
        for (int y = 0; y < GRID_POINTS; y++)
        {
            points[x][y][0] = GLfloat ((x/5.0f)-4.5f);
            points[x][y][1] = GLfloat ((y/5.0f)-4.5f);
            points[x][y][2] = GLfloat (sin((((x/5.0f)*40.0f)/360.0f)*3.141592654*2.0f));
        }
    }
    PlaySound("Warcraft3.wav", NULL, SND_FILENAME | SND_ASYNC | SND_LOOP);
}

//***********************************************************
//FUNCTION: void display(void)                              *
//  Draws paddles and displays introduction to game         *
//***********************************************************

void display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glPushMatrix();

    if (startgame)                                  // game in progress
    {
        glPushMatrix();
        glTranslatef(0.0, 0.0, pz + mapz_offset);   // perimeter zooming
        glRotatef(-pr, 1.0, 0.0, 0.0);              // perimeter rotation

        // draw top paddle [2nd player]
        glPushMatrix();
        glTranslatef(x2, PADDLE_POS, 0.0);
        glScalef(5.0, 0.5, 0.5);
        glutSolidCube(1.0);
        glPopMatrix();

        // draw ball
        glPushMatrix();
        glTranslatef(bx, by, 0.0);
        glutSolidSphere(0.6, 8, 4);
        glPopMatrix();

        // draw bottom paddle [1st player]
        glPushMatrix();
        glTranslatef(x1, -PADDLE_POS, 0.0);
        glScalef(5.0, 0.5, 0.5);
        glutSolidCube(1.0);
        glPopMatrix();

        // draw perimeters
        glPushMatrix();
        glBegin(GL_LINE_LOOP);
            glColor3f(0.0, 0.0, 0.0);
            glVertex2f( BORDER_X,  BORDER_Y);
            glVertex2f(-BORDER_X,  BORDER_Y);
            glVertex2f(-BORDER_X, -BORDER_Y);
            glVertex2f( BORDER_X, -BORDER_Y);
        glEnd();
        glPopMatrix();

        // draw map
        glPushMatrix();
        selectMap();
        glPopMatrix();

        glPopMatrix();
    }
    else if (gameover)                  // display epilogue
    {
        showBackground(texture[26]);
        showEnd();
    }
    else                                // display game intro
    {
        showFlag(texture[6]);           // background
        showIntro();                    // cube
    }
    glPopMatrix();
    glFlush();
    glutSwapBuffers();
    (fullscreen ? NULL : displayFPS());
}

//***********************************************************
//FUNCTION: void keyboard(unsigned char key, int x, int y)  *
//  Allows the user to input keyboard commands              *
//***********************************************************

void keyboard(unsigned char key, int x, int y)
{
    static bool bMode   = true;
    static bool bFog    = false;

    switch (toupper(key))
    {
        case VK_INSTRUCT:           // get instructions
            if (startgame)
                PlaySound("Instruction.wav", NULL, SND_ASYNC | SND_FILENAME);

            MessageBox(NULL, "Instruction Menu\t\t\t\t\n\n"
                "I \t - Get instructions\n"
                "S \t - Start 3D Pong\n"
                "C \t - Change Map\n"
                "F \t - Toggle on/off full screen\n"
                "D \t - Toggle fog\n"
                "M \t - Toggle polygon/wireframe\n"
                "P \t - Toggle pause\n"
                "A \t - Get credits about 3D Pong\n"
                "Q \t - Quit 3D Pong\n"
                "\n"
                "1st player control: \n"
                "Left  arrow \t - Move Left\n"
                "Right arrow \t - Move Right\n"
                "\n"
                "CPU AI / 2nd player control: \n"
                "Z \t - Move Left\n"
                "X \t - Move Right\n"
                "\n"
                ,
                "Help Instructions", MB_OK | MB_ICONINFORMATION);
            break;

        case VK_FULLSCRN:       // toggle fullscreen
            if (startgame)
                PlaySound("Toggle.wav", NULL, SND_ASYNC | SND_FILENAME);

            if (fullscreen)
            {
                fullscreen = false;
                glutSetWindow(mainwin);
                glutPositionWindow(64, 64);
                glutReshapeWindow(640, 480);
            }
            else
            {
                fullscreen = true;
                glutFullScreen();
            }
            break;

        case VK_STARTG:
        case VK_NOVICE:             // starts the game
            gameInit(SPEED_NOVICE);
            dLevel = VK_NOVICE;
            break;

        case VK_SO_SO:
            gameInit(SPEED_SO_SO);
            dLevel = VK_SO_SO;
            break;

        case VK_ADVANCED: 
            gameInit(SPEED_ADVANCED);
            dLevel = VK_ADVANCED;
            break;

        case VK_P2_L:       // player2 left movement
            if (x2 > -BORDER_X)
                x2 -= PADDLE_VEL;
            break;

        case VK_P2_R:       // player2 right movement
            if (x2 <  BORDER_X)
                x2 += PADDLE_VEL;
            break;

        case VK_CH_MAP:     // changes the map
            if (startgame)
            {
                PlaySound("Map.wav", NULL, SND_ASYNC | SND_FILENAME);
                nMap = (nMap + 1) % MAX_MAP;
            }
            else
            {
                MessageBox(NULL, "\n3D Pong\t\t\t\t\t\n"
                    "\n"
                    "Map changes allowed only when game is in progress."
                    ,
                    "Arena changes", MB_OK | MB_ICONINFORMATION);
            }
            break;

        case VK_FOG:
            bFog = !bFog;

            if (startgame)
                PlaySound("Fog1.wav", NULL, SND_ASYNC | SND_FILENAME);

            if (bFog)
                glEnable(GL_FOG);
            else
                glDisable(GL_FOG);
            break;

        case VK_MODE:
            bMode = !bMode;

            if (startgame)
                PlaySound("Polymode1.wav", NULL, SND_ASYNC | SND_FILENAME);

            if (bMode)
            {
                glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
                polyMode = GLU_FILL;
                texMode  = GL_TRUE;
            }
            else
            {
                glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
                polyMode = GLU_LINE;
                texMode  = GL_FALSE;
            }
            break;

        case VK_PAUSEG:     // toggle pause
            pausegame = !pausegame;

            if (startgame)
                PlaySound("Pause1.wav", NULL, SND_ASYNC | SND_FILENAME);

            if (pausegame)
                glutIdleFunc(NULL);
            else
                glutIdleFunc(animate);
            break;

        case VK_ABOUT:      // get game info
            if (startgame)
                PlaySound("About.wav", NULL, SND_ASYNC | SND_FILENAME);

            MessageBox(NULL, "\n3D Pong\t\t\t\n"
                "\n"
                "Copyright Spring 2002\n"
                "All rights reserved.\n"
                "\n"
                "App. Version  2.0c\n"
                "\n"
                "Credits:\n"
                "- Yee Hsu\n"
                ,
                "About 3D Pong", MB_OK | MB_ICONINFORMATION);
            break;

        case VK_QUIT:       // quits app
        case VK_ESC:
            PlaySound("Quit.wav", NULL, SND_ASYNC | SND_FILENAME);
            Sleep(500);
            exit(0);
            break;

        default:
            break;
    }
    glutPostRedisplay();
}

//***********************************************************
//FUNCTION: void reshape(int w, int h)                      *
//  Maintains object and game in perspective view           *
//***********************************************************

void reshape(int w, int h)
{
    glViewport(0,0,(GLsizei)w, (GLsizei)h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    if (w <= h)
        gluPerspective(60.0f,(GLfloat)h / (GLfloat)w,0.1f,500.0f);
    else
        gluPerspective(60.0f,(GLfloat)w / (GLfloat)h,0.1f,500.0f);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    gluLookAt(0.0, 0.0, 50.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
}

//***********************************************************
//FUNCTION: void animate(void)                              *
//  Animation of introduction 3D object and game play       *
//***********************************************************

void animate(void)
{
    if (!startgame)
    {
        static int wiggle_count = 0;

        xr += (GLfloat) 0.3f;       // cube animation
        yr += (GLfloat) 0.2f;
        zr += (GLfloat) 0.4f;

        // animate intro background flag effect
        if (wiggle_count == 2)
        {
            for (int y = 0; y < GRID_POINTS; y++)
            {
                GLfloat hold = points[0][y][2];
                for (int x = 0; x < GRID_POINTS-1; x++)
                {
                    points[x][y][2] = points[x+1][y][2];
                }
                points[GRID_POINTS-1][y][2] = hold;
            }
            wiggle_count = 0;
        }
        wiggle_count++;
    }
    else if (startgame && !gameover)
    {
        // this statements simulates the computer AI that
        // controls the 2nd player [top paddle]. Comment
        // it out to disable the AI so 2nd player can
        // manually control it. [Z = left, X = right]
        // AI is currently invincible.
        x2 = bx;
        //x1 = bx;                  // AI controls 1st player

        bx += xv;                   // ball animation
        by += yv;
        collisionDetection();       // collision dectection
    }
    glutPostRedisplay();
}

//***********************************************************
//FUNCTION: void addMenuFeatures(void)                      *
//  Adds mouse menu features                                *
//***********************************************************
void addMenuFeatures(void)
{
    helpSubMenu = glutCreateMenu(helpMenu);
    glutAddMenuEntry("3D Pong Instruction", IDM_INSTRUCT);
    glutAddMenuEntry("About 3D Pong",       IDM_ABOUT);

    gameSubMenu = glutCreateMenu(helpMenu);
    glutAddMenuEntry("Novice Player",       IDM_NOVICE);
    glutAddMenuEntry("Intermediate Player", IDM_SO_SO);
    glutAddMenuEntry("Advanced Player",     IDM_ADVANCED);

    toggleSubMenu = glutCreateMenu(helpMenu);
    glutAddMenuEntry("Fog",                 IDM_FOG);
    glutAddMenuEntry("Polygon/Wireframe",   IDM_MODE);
    glutAddMenuEntry("Pause",               IDM_PAUSE);
    glutAddMenuEntry("Screen",              IDM_FSCR);

    mainMenu = glutCreateMenu(helpMenu);
    glutAddSubMenu("Help", helpSubMenu);
    glutAddSubMenu("Start", gameSubMenu);
    glutAddSubMenu("Toggle ...", toggleSubMenu);
    glutAddMenuEntry("Change Map",          IDM_CHANGE_MAP);
    glutAddMenuEntry("Quit",                IDM_QUIT);
    glutAttachMenu(GLUT_RIGHT_BUTTON);
}

//***********************************************************
//FUNCTION: void helpMenu(int id)                           *
//  Help menu callback function                             *
//***********************************************************

void helpMenu(int id)
{
    switch (id)
    {
        case IDM_INSTRUCT:
            keyboard(VK_INSTRUCT, 0, 0);
            break;

        case IDM_NOVICE:
            keyboard(VK_NOVICE, 0, 0);
            break;

        case IDM_SO_SO:
            keyboard(VK_SO_SO, 0, 0);
            break;

        case IDM_ADVANCED:
            keyboard(VK_ADVANCED, 0, 0);
            break;

        case IDM_CHANGE_MAP:
            keyboard(VK_CH_MAP, 0, 0);
            break;

        case IDM_PAUSE:
            keyboard(VK_PAUSEG, 0, 0);
            break;

        case IDM_FSCR:
            keyboard(VK_FULLSCRN, 0, 0);
            break;

        case IDM_FOG:
            keyboard(VK_FOG, 0, 0);
            break;

        case IDM_MODE:
            keyboard(VK_MODE, 0, 0);
            break;

        case IDM_ABOUT:
            keyboard(VK_ABOUT, 0, 0);
            break;

        case IDM_QUIT:
            keyboard(VK_QUIT, 0, 0);
            break;

        default:
            break;
    }
}

//***********************************************************
//FUNCTION: void loadTexture(char *filename)                *
//  Loads textures for texture mapping                      *
//***********************************************************

void loadTexture(char *filename)
{
    static unsigned int texture_id = 0;

    AUX_RGBImageRec *TextureImage = NULL;
    TextureImage = auxDIBImageLoad(filename);

    glGenTextures(1, &texture[texture_id]);
    glBindTexture(GL_TEXTURE_2D, texture[texture_id]);

    glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage->sizeX,
        TextureImage->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE,
        TextureImage->data);

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

    free(TextureImage->data);   // prevents memory leak
    free(TextureImage);

    texture_id++;
}

//***********************************************************
//FUNCTION: void showBackground()                           *
//  Shows background image                                  *
//***********************************************************

void showBackground(const GLuint tex)
{
    glLoadIdentity();

    glBindTexture(GL_TEXTURE_2D, tex);
    glBegin(GL_QUADS);
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-90.0f, -90.0f, -90.0f);
        glTexCoord2f(1.0f, 0.0f); glVertex3f( 90.0f, -90.0f, -90.0f);
        glTexCoord2f(1.0f, 1.0f); glVertex3f( 90.0f,  90.0f, -90.0f);
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-90.0f,  90.0f, -90.0f);
    glEnd();
}

//***********************************************************
//FUNCTION: void showIntro()                                *
//  Shows introduction before game play begins              *
//***********************************************************

void showIntro()
{
    static GLfloat r = 0.0f, g = 0.0f, b = 0.0f;

    // lighting
    GLfloat light_color[]   = {0.0, 0.0, 0.0, 0.0};
    GLfloat diffuse_light[] = {1.0, 1.0, 1.0, 0.0};
    GLfloat ambient_light[] = {0.5, 0.5, 0.5, 0.0};
    GLfloat light_pos[]     = {0.0, 0.0, 0.0, 9.9};

    r += (GLfloat) 0.005;
    if ((light_color[0] = r) >= 1.0) { r = 1.0; g += 0.005;}
    if ((light_color[1] = g) >= 1.0) { g = 1.0; b += 0.005;}
    if ((light_color[2] = b) >= 1.0) { r = g =  b =  0.000;}

    glMaterialfv(GL_FRONT, GL_AMBIENT, light_color);
    glLightfv(GL_LIGHT0, GL_POSITION, light_pos);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse_light);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient_light);

    // blending
    glEnable(GL_BLEND);
    glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR);

    glLoadIdentity();
    glTranslatef(0.0f,0.0f,-5.0f);

    // rotates the cube
    glRotatef(xr,1.0f,0.0f,0.0f);
    glRotatef(yr,0.0f,1.0f,0.0f);
    glRotatef(zr,0.0f,0.0f,1.0f);

    glBindTexture(GL_TEXTURE_2D, texture[0]);
    glBegin(GL_QUADS);
        // Front Face
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
        glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
        glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
    glEnd();

    glBindTexture(GL_TEXTURE_2D, texture[1]);
    glBegin(GL_QUADS);
        // Back Face
        glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
        glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
        glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
        glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
    glEnd();

    glBindTexture(GL_TEXTURE_2D, texture[2]);
    glBegin(GL_QUADS);
        // Top Face
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
        glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
        glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
    glEnd();

    glBindTexture(GL_TEXTURE_2D, texture[3]);
    glBegin(GL_QUADS);
        // Bottom Face
        glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
        glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
        glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
        glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
    glEnd();

    glBindTexture(GL_TEXTURE_2D, texture[4]);
    glBegin(GL_QUADS);
        // Right face
        glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
        glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
        glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
        glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
    glEnd();

    glBindTexture(GL_TEXTURE_2D, texture[5]);
    glBegin(GL_QUADS);
        // Left Face
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
        glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
        glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
    glEnd();

    glDisable(GL_BLEND);
}

void showEnd(void)
{
    static GLfloat angle = 0.0f;

    // enable env mapping
    glEnable(GL_TEXTURE_GEN_S);
    glEnable(GL_TEXTURE_GEN_T);

    glPushMatrix();
    glTranslatef(0.0, 0.0, -7.0);
    glRotatef(angle * 1.3f, 1.0f, 0.0f, 0.0f);
    glRotatef(angle * 1.1f, 0.0f, 1.0f, 0.0f);
    glRotatef(angle * 1.2f, 0.0f, 0.0f, 1.0f);
    angle += (GLfloat) 0.5f;
    glutSolidSphere(4.0, 16, 8);
    glPopMatrix();

    // disable env mapping
    glDisable(GL_TEXTURE_GEN_S);
    glDisable(GL_TEXTURE_GEN_T);
}

void selectMap(void)
{
    switch (nMap)
    {
        case 0:
            mapz_offset = (GLfloat) 0.0f;
            drawArena1();
            showBackground(texture[9]);
            break;

        case 1:
            mapz_offset = (GLfloat) -9.0f;
            drawArena2();
            showBackground(texture[12]);
            break;

        case 2:
            mapz_offset = (GLfloat) -5.0f;
            drawArena3();
            showBackground(texture[23]);
            break;

        case 3:
            mapz_offset = (GLfloat) -3.0f;
            drawArena4();
            showBackground(texture[25]);
            break;

        default:
            // in case an error occurs [unlikely],
            // forces the map to be changed
            nMap = (nMap + 1) % MAX_MAP;
            break;
    }
}

// stadium
void drawArena1(void)
{
    // lighting
    GLfloat white_light[]   = {1.0, 1.0, 1.0, 1.0};
    GLfloat diffuse_light[] = {1.0, 1.0, 1.0, 1.0};
    GLfloat ambient_light[] = {0.6, 0.6, 0.6, 1.0};
    GLfloat light_pos[]     = {0.0, 0.0, 0.0, 9.0};

    glMaterialfv(GL_FRONT, GL_AMBIENT, white_light);
    glLightfv(GL_LIGHT0, GL_POSITION, light_pos);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse_light);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient_light);

    // field
    glBindTexture(GL_TEXTURE_2D, texture[7]);
    glBegin(GL_QUADS);
        glTexCoord2f(0.0f, 0.0f); glVertex3f( BORDER_X + 10,  BORDER_Y + 3, -5);
        glTexCoord2f(1.0f, 0.0f); glVertex3f(-BORDER_X - 10,  BORDER_Y + 3, -5);
        glTexCoord2f(1.0f, 1.0f); glVertex3f(-BORDER_X - 10, -BORDER_Y - 3, -5);
        glTexCoord2f(0.0f, 1.0f); glVertex3f( BORDER_X + 10, -BORDER_Y - 3, -5);
    glEnd();

    // top side
    glBindTexture(GL_TEXTURE_2D, texture[8]);
    glBegin(GL_QUADS);
        glTexCoord2f(0.0f, 0.0f); glVertex3f( BORDER_X + 20,  BORDER_Y + 20, +15);
        glTexCoord2f(1.0f, 0.0f); glVertex3f(-BORDER_X - 20,  BORDER_Y + 20, +15);
        glTexCoord2f(1.0f, 1.0f); glVertex3f(-BORDER_X - 10,  BORDER_Y + 3,  -5 );
        glTexCoord2f(0.0f, 1.0f); glVertex3f( BORDER_X + 10,  BORDER_Y + 3,  -5 );
    glEnd();

    // left side
    glBegin(GL_QUADS);
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-BORDER_X - 20,  BORDER_Y + 20, +15);
        glTexCoord2f(1.0f, 0.0f); glVertex3f(-BORDER_X - 20, -BORDER_Y - 20, +15);
        glTexCoord2f(1.0f, 1.0f); glVertex3f(-BORDER_X - 10, -BORDER_Y - 3,  -5 );
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-BORDER_X - 10,  BORDER_Y + 3,  -5 );
    glEnd();

    // bottom side
    glBegin(GL_LINE_LOOP);
        glTexCoord2f(0.0f, 0.0f); glVertex3f( BORDER_X + 20, -BORDER_Y - 20, +15);
        glTexCoord2f(1.0f, 0.0f); glVertex3f(-BORDER_X - 20, -BORDER_Y - 20, +15);
        glTexCoord2f(1.0f, 1.0f); glVertex3f(-BORDER_X - 10, -BORDER_Y - 3,  -5 );
        glTexCoord2f(0.0f, 1.0f); glVertex3f( BORDER_X + 10, -BORDER_Y - 3,  -5 );
    glEnd();

    // right side
    glBegin(GL_QUADS);
        glTexCoord2f(0.0f, 0.0f); glVertex3f( BORDER_X + 20,  BORDER_Y + 20, +15);
        glTexCoord2f(1.0f, 0.0f); glVertex3f( BORDER_X + 20, -BORDER_Y - 20, +15);
        glTexCoord2f(1.0f, 1.0f); glVertex3f( BORDER_X + 10, -BORDER_Y - 3,  -5 );
        glTexCoord2f(0.0f, 1.0f); glVertex3f( BORDER_X + 10,  BORDER_Y + 3,  -5 );
    glEnd();
}

// temple
void drawArena2(void)
{
    // lighting
    GLfloat white_light[]   = {1.0, 1.0, 1.0, 1.0};
    GLfloat diffuse_light[] = {1.0, 1.0, 1.0, 1.0};
    GLfloat ambient_light[] = {0.5, 0.5, 0.5, 1.0};
    GLfloat light_pos[]     = {0.0, 0.0, 0.0, 9.0};

    glMaterialfv(GL_FRONT, GL_AMBIENT, white_light);
    glLightfv(GL_LIGHT0, GL_POSITION, light_pos);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse_light);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient_light);

    const int NUM_STEP = 4;

    glBindTexture(GL_TEXTURE_2D, texture[10]);

    // draws the stairs under the perimeter
    for (int i = 1; i < NUM_STEP; i++)
    {
        // top face
        glBegin(GL_QUADS);
            glTexCoord2f(0.0f, 0.0f); glVertex3f( BORDER_X + i,  BORDER_Y + i, -i);
            glTexCoord2f(1.0f, 0.0f); glVertex3f(-BORDER_X - i,  BORDER_Y + i, -i);
            glTexCoord2f(1.0f, 1.0f); glVertex3f(-BORDER_X - i, -BORDER_Y - i, -i);
            glTexCoord2f(0.0f, 1.0f); glVertex3f( BORDER_X + i, -BORDER_Y - i, -i);
        glEnd();

        // back face
        glBegin(GL_QUADS);
            glTexCoord2f(0.0f, 0.0f); glVertex3f( BORDER_X + i,  BORDER_Y + i, -i);
            glTexCoord2f(1.0f, 0.0f); glVertex3f(-BORDER_X - i,  BORDER_Y + i, -i);
            glTexCoord2f(1.0f, 1.0f); glVertex3f(-BORDER_X - i,  BORDER_Y + i, -(i+1));
            glTexCoord2f(0.0f, 1.0f); glVertex3f( BORDER_X + i,  BORDER_Y + i, -(i+1));
        glEnd();

        // left face
        glBegin(GL_QUADS);
            glTexCoord2f(0.0f, 0.0f); glVertex3f(-BORDER_X - i,  BORDER_Y + i, -i);
            glTexCoord2f(1.0f, 0.0f); glVertex3f(-BORDER_X - i,  BORDER_Y + i, -(i+1));
            glTexCoord2f(1.0f, 1.0f); glVertex3f(-BORDER_X - i, -BORDER_Y - i, -(i+1));
            glTexCoord2f(0.0f, 1.0f); glVertex3f(-BORDER_X - i, -BORDER_Y - i, -i);
        glEnd();

        // front face
        glBegin(GL_QUADS);
            glTexCoord2f(0.0f, 0.0f); glVertex3f(-BORDER_X - i, -BORDER_Y - i, -i);
            glTexCoord2f(1.0f, 0.0f); glVertex3f(-BORDER_X - i, -BORDER_Y - i, -(i+1));
            glTexCoord2f(1.0f, 1.0f); glVertex3f( BORDER_X + i, -BORDER_Y - i, -(i+1));
            glTexCoord2f(0.0f, 1.0f); glVertex3f( BORDER_X + i, -BORDER_Y - i, -i);
        glEnd();

        // right face
        glBegin(GL_QUADS);
            glTexCoord2f(0.0f, 0.0f); glVertex3f( BORDER_X + i, -BORDER_Y - i, -i);
            glTexCoord2f(1.0f, 0.0f); glVertex3f( BORDER_X + i, -BORDER_Y - i, -(i+1));
            glTexCoord2f(1.0f, 1.0f); glVertex3f( BORDER_X + i,  BORDER_Y + i, -(i+1));
            glTexCoord2f(0.0f, 1.0f); glVertex3f( BORDER_X + i,  BORDER_Y + i, -i);
        glEnd();
    }

    // init the quadric object
    GLUquadricObj *qobj = gluNewQuadric();
    gluQuadricDrawStyle(qobj, polyMode);
    gluQuadricNormals(qobj, GLU_SMOOTH);
    gluQuadricTexture(qobj, texMode);

    // top right pillar
    glPushMatrix();
    glTranslatef( BORDER_X + (2 * NUM_STEP),  BORDER_Y, -NUM_STEP);
    gluCylinder(qobj, 3.0, 3.0, 15.0, 15, 5);
    glTranslatef(0.0, 0.0, 15.0);
    glScalef(7.0, 7.0, 3.0);
    glutSolidCube(1.0);
    glPopMatrix();

    // top left pillar
    glPushMatrix();
    glTranslatef(-BORDER_X - (2 * NUM_STEP),  BORDER_Y, -NUM_STEP);
    gluCylinder(qobj, 3.0, 3.0, 15.0, 15, 5);
    glTranslatef(0.0, 0.0, 15.0);
    glScalef(7.0, 7.0, 3.0);
    glutSolidCube(1.0);
    glPopMatrix();

    // bottom left pillar
    glPushMatrix();
    glTranslatef(-BORDER_X - (2 * NUM_STEP), -BORDER_Y, -NUM_STEP);
    gluCylinder(qobj, 3.0, 3.0, 15.0, 15, 5);
    glTranslatef(0.0, 0.0, 15.0);
    glScalef(7.0, 7.0, 3.0);
    glutSolidCube(1.0);
    glPopMatrix();

    // bottom right pillar
    glPushMatrix();
    glTranslatef( BORDER_X + (2 * NUM_STEP), -BORDER_Y, -NUM_STEP);
    gluCylinder(qobj, 3.0, 3.0, 15.0, 15, 5);
    glTranslatef(0.0, 0.0, 15.0);
    glScalef(7.0, 7.0, 3.0);
    glutSolidCube(1.0);
    glPopMatrix();

    // draws the ground
    glBindTexture(GL_TEXTURE_2D, texture[11]);
    glBegin(GL_QUADS);
        glTexCoord2f(0.0f, 0.0f); glVertex3f( BORDER_X + 50,  BORDER_Y + 50, -NUM_STEP);
        glTexCoord2f(1.0f, 0.0f); glVertex3f(-BORDER_X - 50,  BORDER_Y + 50, -NUM_STEP);
        glTexCoord2f(1.0f, 1.0f); glVertex3f(-BORDER_X - 50, -BORDER_Y - 50, -NUM_STEP);
        glTexCoord2f(0.0f, 1.0f); glVertex3f( BORDER_X + 50, -BORDER_Y - 50, -NUM_STEP);
    glEnd();

    // deletes the object to prevent memory leak
    gluDeleteQuadric(qobj);
}

// spheres
void drawArena3(void)
{
    // lighting
    GLfloat lmodel_ambient[] = {0.9, 0.9, 0.9, 1.0};
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);

    // init the quadric object
    GLUquadricObj *qobj = gluNewQuadric();
    gluQuadricDrawStyle(qobj, polyMode);
    gluQuadricNormals(qobj, GLU_SMOOTH);
    gluQuadricTexture(qobj, texMode);

    // multiple random sphere placements
    glPushMatrix();
    glBindTexture(GL_TEXTURE_2D, texture[16]);
    glTranslatef(30.0, 25.0, 15.0);
    gluSphere(qobj, 5.0, 15.0, 15.0);
    glPopMatrix();

    glPushMatrix();
    glBindTexture(GL_TEXTURE_2D, texture[18]);
    glTranslatef(25.0, 10.0, 0.7);
    gluSphere(qobj, 3.0, 15.0, 15.0);
    glPopMatrix();

    glPushMatrix();
    glBindTexture(GL_TEXTURE_2D, texture[20]);
    glTranslatef(-25.0, 10.0, 0.7);
    gluSphere(qobj, 4.0, 15.0, 15.0);
    glPopMatrix();

    glPushMatrix();
    glBindTexture(GL_TEXTURE_2D, texture[20]);
    glTranslatef(-25.0, 10.0, 0.7);
    glRotatef(45.0, -25.0, 10.0, 0.7);
    gluDisk(qobj, 5.0, 7.0, 25.0, 25.0);
    glPopMatrix();

    glPushMatrix();
    glBindTexture(GL_TEXTURE_2D, texture[19]);
    glTranslatef(-25.0, -20.0, 5.0);
    gluSphere(qobj, 5.0, 15.0, 15.0);
    glPopMatrix();

    glPushMatrix();
    glBindTexture(GL_TEXTURE_2D, texture[21]);
    glTranslatef(-28.0, -5.0, 10.0);
    gluSphere(qobj, 1.0, 15.0, 15.0);
    glPopMatrix();

    glPushMatrix();
    glBindTexture(GL_TEXTURE_2D, texture[17]);
    glTranslatef(17.0, -8.0, 15.0);
    gluSphere(qobj, 1.5, 15.0, 15.0);
    glPopMatrix();

    glPushMatrix();
    glBindTexture(GL_TEXTURE_2D, texture[15]);
    glTranslatef(17.0, -8.0, 15.0);
    gluDisk(qobj, 2.0, 2.5, 25.0, 25.0);
    glPopMatrix();

    glPushMatrix();
    glBindTexture(GL_TEXTURE_2D, texture[13]);
    glTranslatef(23.0, -13.0, 15.0);
    gluSphere(qobj, 2.5, 15.0, 15.0);
    glPopMatrix();

    glPushMatrix();
    glBindTexture(GL_TEXTURE_2D, texture[15]);
    glTranslatef(-32.0, -27.0, 15.0);
    gluSphere(qobj, 3.5, 15.0, 15.0);
    glPopMatrix();

    glPushMatrix();
    glBindTexture(GL_TEXTURE_2D, texture[14]);
    glTranslatef(-23.0, 22.0, 15.0);
    gluSphere(qobj, 5.0, 15.0, 15.0);
    glPopMatrix();

    // field
    glBindTexture(GL_TEXTURE_2D, texture[22]);
    glBegin(GL_QUADS);
        glTexCoord2f(0.0f, 0.0f); glVertex3f( BORDER_X,  BORDER_Y, -1);
        glTexCoord2f(1.0f, 0.0f); glVertex3f(-BORDER_X,  BORDER_Y, -1);
        glTexCoord2f(1.0f, 1.0f); glVertex3f(-BORDER_X, -BORDER_Y, -1);
        glTexCoord2f(0.0f, 1.0f); glVertex3f( BORDER_X, -BORDER_Y, -1);
    glEnd();

    // deletes the object to prevent memory leak
    gluDeleteQuadric(qobj);
}

// cylinder
void drawArena4(void)
{
    static GLfloat rot = 0.0f;

    // lighting
    GLfloat lmodel_ambient[] = {0.9, 0.9, 0.9, 1.0};
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);

    // init the quadric object
    GLUquadricObj *qobj = gluNewQuadric();
    gluQuadricDrawStyle(qobj, polyMode);
    gluQuadricNormals(qobj, GLU_SMOOTH);
    gluQuadricTexture(qobj, texMode);

    // rotates the rotary
    glRotatef(rot, 0.0, 1.0, 0.0);
    rot = rot + (GLfloat) 0.1f;

    // draws the rotary
    glPushMatrix();
    glTranslatef(0.0, BORDER_Y, 0.0);
    glRotatef(90.0f, 1.0, 0.0, 0.0);
    glBindTexture(GL_TEXTURE_2D, texture[24]);
    gluCylinder(qobj, 12.0, 12.0, 50.0, 30, 10);
    glPopMatrix();

    // prevents memory leak
    gluDeleteQuadric(qobj);
}

// field with avi
void drawArena5(void)
{
    // lighting
    GLfloat white_light[]   = {1.0, 1.0, 1.0, 1.0};
    GLfloat diffuse_light[] = {1.0, 1.0, 1.0, 1.0};
    GLfloat ambient_light[] = {0.6, 0.6, 0.6, 1.0};
    GLfloat light_pos[]     = {0.0, 0.0, 0.0, 9.0};

    glMaterialfv(GL_FRONT, GL_AMBIENT, white_light);
    glLightfv(GL_LIGHT0, GL_POSITION, light_pos);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse_light);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient_light);

    // field
    glBindTexture(GL_TEXTURE_2D, texture[10]);
    glBegin(GL_QUADS);
        glTexCoord2f(0.0f, 0.0f); glVertex3f( BORDER_X + 10,  BORDER_Y + 3, -5);
        glTexCoord2f(1.0f, 0.0f); glVertex3f(-BORDER_X - 10,  BORDER_Y + 3, -5);
        glTexCoord2f(1.0f, 1.0f); glVertex3f(-BORDER_X - 10, -BORDER_Y - 3, -5);
        glTexCoord2f(0.0f, 1.0f); glVertex3f( BORDER_X + 10, -BORDER_Y - 3, -5);
    glEnd();

    // display board
    glPushMatrix();
    glTranslatef(-10, 30, 5);
    glBindTexture(GL_TEXTURE_2D, texture[13]);
    gluCube(0.5, 0.5, 5);
    glTranslatef(20, 0, 0);
    gluCube(0.5, 0.5, 5);

    // AVI movie
    glTranslatef(-10, -1, 7);
    glRotatef(90.0, 1.0, 0, 0);
    pAVI.GrabAVIFrame(texture[14]);
    glBegin(GL_QUADS);
        glTexCoord2f(1.0f, 1.0f); glVertex3f( 12,  8, 0);
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-12,  8, 0);
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-12, -8, 0);
        glTexCoord2f(1.0f, 0.0f); glVertex3f( 12, -8, 0);
    glEnd();
    glPopMatrix();

    // top side
    glBindTexture(GL_TEXTURE_2D, texture[11]);
    glBegin(GL_QUADS);
        glTexCoord2f(0.0f, 0.0f); glVertex3f( BORDER_X + 20,  BORDER_Y + 20, +15);
        glTexCoord2f(1.0f, 0.0f); glVertex3f(-BORDER_X - 20,  BORDER_Y + 20, +15);
        glTexCoord2f(1.0f, 1.0f); glVertex3f(-BORDER_X - 10,  BORDER_Y + 3,  -5 );
        glTexCoord2f(0.0f, 1.0f); glVertex3f( BORDER_X + 10,  BORDER_Y + 3,  -5 );
    glEnd();

    // left side
    glBegin(GL_QUADS);
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-BORDER_X - 20,  BORDER_Y + 20, +15);
        glTexCoord2f(1.0f, 0.0f); glVertex3f(-BORDER_X - 20, -BORDER_Y - 20, +15);
        glTexCoord2f(1.0f, 1.0f); glVertex3f(-BORDER_X - 10, -BORDER_Y - 3,  -5 );
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-BORDER_X - 10,  BORDER_Y + 3,  -5 );
    glEnd();

    // bottom side
    glBegin(GL_LINE_LOOP);
        glTexCoord2f(0.0f, 0.0f); glVertex3f( BORDER_X + 20, -BORDER_Y - 20, +15);
        glTexCoord2f(1.0f, 0.0f); glVertex3f(-BORDER_X - 20, -BORDER_Y - 20, +15);
        glTexCoord2f(1.0f, 1.0f); glVertex3f(-BORDER_X - 10, -BORDER_Y - 3,  -5 );
        glTexCoord2f(0.0f, 1.0f); glVertex3f( BORDER_X + 10, -BORDER_Y - 3,  -5 );
    glEnd();

    // right side
    glBegin(GL_QUADS);
        glTexCoord2f(0.0f, 0.0f); glVertex3f( BORDER_X + 20,  BORDER_Y + 20, +15);
        glTexCoord2f(1.0f, 0.0f); glVertex3f( BORDER_X + 20, -BORDER_Y - 20, +15);
        glTexCoord2f(1.0f, 1.0f); glVertex3f( BORDER_X + 10, -BORDER_Y - 3,  -5 );
        glTexCoord2f(0.0f, 1.0f); glVertex3f( BORDER_X + 10,  BORDER_Y + 3,  -5 );
    glEnd();
}

// space rotary
void drawArena6()
{
    static GLfloat rot1 = 0.0f;
    static GLfloat rot2 = 0.0f;
    static GLfloat rot3 = 0.0f;

    // init the quadric object
    GLUquadricObj *qobj = gluNewQuadric();
    gluQuadricDrawStyle(qobj, polyMode);
    gluQuadricNormals(qobj, GLU_SMOOTH);
    gluQuadricTexture(qobj, texMode);

    // small rotary
    glPushMatrix();
    glRotatef(rot1, 0.0, 1.0, 0.0);
    rot1 = rot1 + (GLfloat) 0.1f;

    glRotatef(90, 1, 0, 0);
    glTranslatef(0, 0, 15);
    glBindTexture(GL_TEXTURE_2D, texture[15]);
    gluCylinder(qobj, 12.0, 12.0, 5.0, 20, 10);
    gluCylinder(qobj, 13.0, 13.0, 5.0, 20, 10);
    gluDisk(qobj, 12.0, 13.0, 20, 1);
    glTranslatef(0, 0, 5);
    gluDisk(qobj, 12.0, 13.0, 20, 1);
    glPopMatrix();

    // two circling spheres
    glPushMatrix();
    glRotatef(rot3, 0.0, 1.0, 0.0);
    rot3 = rot3 + (GLfloat) 2.0f;

    // enable blend and env mapping
    glEnable(GL_TEXTURE_GEN_S);
    glEnable(GL_TEXTURE_GEN_T);
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_SRC_COLOR);

    glRotatef(90, 1, 0, 0);
    glTranslatef(0, 30, 0);
    glBindTexture(GL_TEXTURE_2D, texture[15]);
    gluSphere(qobj, 5, 10, 10);
    glTranslatef(0, -60, 0);
    gluSphere(qobj, 5, 10, 10);

    // disable blend and env mapping
    glDisable(GL_BLEND);
    glDisable(GL_TEXTURE_GEN_S);
    glDisable(GL_TEXTURE_GEN_T);
    glPopMatrix();

    // big rotary
    glPushMatrix();
    glRotatef(rot2, 0.0, 1.0, 0.0);
    rot2 = rot2 - (GLfloat) 0.5f;

    glRotatef(90, 1, 0, 0);
    glTranslatef(0, 0, -20);
    glBindTexture(GL_TEXTURE_2D, texture[15]);
    gluCylinder(qobj, 10.0, 10.0, 10.0, 20, 10);
    gluCylinder(qobj, 15.0, 15.0, 10.0, 20, 10);
    gluDisk(qobj, 10.0, 15.0, 20, 1);
    glTranslatef(0, 0, 10);
    gluDisk(qobj, 10.0, 15.0, 20, 1);
    glTranslatef(22, 0, -5);
    gluCube(10, 1, 1);
    glTranslatef(-44, 0, 0);
    gluCube(10, 1, 1);
    glTranslatef(22, 22, 0);
    glRotatef(90, 0, 0, 1);
    gluCube(10, 1, 1);
    glTranslatef(-44, 0, 0);
    gluCube(10, 1, 1);
    glPopMatrix();

    gluDeleteQuadric(qobj);
}

//***********************************************************
//FUNCTION: void arrowKeys(int key, int x, int y)           *
//  Arrow keys are used for game play                       *
//***********************************************************

void arrowKeys(int key, int x, int y)
{
    const GLfloat PROT_FACTOR = 1.00F;
    const GLfloat ZOOM_FACTOR = 0.15F;

    switch (key)
    {
        case GLUT_KEY_LEFT:     // player1 left movement
            if (x1 > -BORDER_X)
                x1 -= PADDLE_VEL;
            break;

        case GLUT_KEY_RIGHT:    // player1 right movement
            if (x1 <  BORDER_X)
                x1 += PADDLE_VEL;
            break;

        case GLUT_KEY_UP:       // rotate and zoom in
            if (pr < 85.0f)
            {
                pr += PROT_FACTOR;
                pz += ZOOM_FACTOR;
            }
            break;

        case GLUT_KEY_DOWN:     // rotate and zoom out
            if (pr > 0.0f)
            {
                pr -= PROT_FACTOR;
                pz -= ZOOM_FACTOR;
            }
            break;

        default:
            break;
    }
}

void gameInit(const GLfloat difficulty_speed)
{
    // restart game
    startgame = true;
    gameover  = false;

    bx = (GLfloat) 0.0F;
    by = (GLfloat) 0.0F;

    srand(time(NULL));

    // attach a random (x,y) speed on the ball
    xv = (GLfloat) (((GLfloat) (rand() % 10)) / 100);
    yv = (GLfloat) (((GLfloat) (rand() % 10)) / 100);

    // append the difficulty speed
    xv += difficulty_speed;
    yv += difficulty_speed;

    // append random direction (velocity) of the ball
    xv = (((rand() % 2) == 1) ? xv : -xv);
    yv = (((rand() % 2) == 1) ? yv : -yv);
}

void collisionDetection(void)
{
    char score[128];
    static int  score_p1 = 0, score_p2 = 0;

    //  bounce the ball if it collides with the
    //  left or right walls [left and right side of screen]
    //
    if (bx > BORDER_X || bx < -BORDER_X)
    {
        PlaySound("Bounce.wav", NULL, SND_ASYNC | SND_FILENAME);
        xv = -xv;
    }

    //  test the ball if it collides with the top or bottom wall
    if (by > PADDLE_POS - 1 || by < -PADDLE_POS + 1)
    {
        // bounce the ball if it collides with the
        // top or bottom paddle
        //
        if ((bx > x1 - 3.5 && bx < x1 + 3.5 && by < 0) ||
            (bx > x2 - 3.5 && bx < x2 + 3.5 && by > 0))
        {
            PlaySound("Bounce.wav", NULL, SND_ASYNC | SND_FILENAME);
            yv = -yv;
        }
        else
        {
            // someone lost the match, display current score
            PlaySound("Out.wav", NULL, SND_ASYNC | SND_FILENAME);
            memset(score, '0', sizeof(score));

            if(by > PADDLE_POS-1)               // if P1 wins
            {
                score_p1++;
                sprintf(score, "You win!\t\t\t\n\n");
                sprintf(score, "%sComputer:  \t%d\n", score, score_p2);
                sprintf(score, "%sPlayer 1:\t\t%d\n", score, score_p1);
                MessageBox(NULL, score, "Score", MB_OK | MB_ICONINFORMATION);
                keyboard(dLevel, 0, 0);
            }
            else if (by < -PADDLE_POS+1)        // if P2 wins [computer]
            {
                score_p2++;
                sprintf(score, "Computer wins!\t\t\n\n");
                sprintf(score, "%sComputer:  \t%d\n", score, score_p2);
                sprintf(score, "%sPlayer 1:\t\t%d\n", score, score_p1);
                MessageBox(NULL, score, "Score", MB_OK | MB_ICONINFORMATION);
                keyboard(dLevel, 0, 0);
            }

            // gameover, reset score and enter epilogue
            if (score_p1 >= MAX_SCORE || score_p2 >= MAX_SCORE)
            {
                score_p1  = score_p2 = 0;
                gameover  = true;
                startgame = false;
                PlaySound("Gameover.wav", NULL, SND_FILENAME | SND_ASYNC);
            }
        }
    }
}

void showFlag(const GLuint tex)
{
    GLfloat float_x, float_y, float_xb, float_yb;

    glLoadIdentity();
    glTranslatef(0.0f,0.0f,-4.5f);
    glBindTexture(GL_TEXTURE_2D, tex);

    // draws the background with with a flag effect
    glBegin(GL_QUADS);
        for (int x = 0; x < GRID_POINTS-1; x++)
        {
            for (int y = 0; y < GRID_POINTS-1; y++)
            {
                float_x  = GLfloat(x)   / ((GLfloat)(GRID_POINTS-1));
                float_y  = GLfloat(y)   / ((GLfloat)(GRID_POINTS-1));
                float_xb = GLfloat(x+1) / ((GLfloat)(GRID_POINTS-1));
                float_yb = GLfloat(y+1) / ((GLfloat)(GRID_POINTS-1));

                glTexCoord2f(float_x, float_y);
                glVertex3f(points[x][y][0],points[x][y][1],points[x][y][2]);

                glTexCoord2f(float_x, float_yb);
                glVertex3f(points[x][y+1][0],points[x][y+1][1],points[x][y+1][2]);

                glTexCoord2f(float_xb, float_yb);
                glVertex3f(points[x+1][y+1][0],points[x+1][y+1][1],points[x+1][y+1][2]);

                glTexCoord2f(float_xb, float_y);
                glVertex3f(points[x+1][y][0],points[x+1][y][1],points[x+1][y][2]);
            }
        }
    glEnd();
}

//***********************************************************
//FUNCTION: void displayFPS(void)                           *
//  Determines the Frames Per Second (FPS) when graphics    *
//  are rendered to the screen. Handy for determining       *
//  hardware performance over 3D graphics acceleration.     *
//                                                          *
//  Standard FPS range: 30-60 fps                           *
//***********************************************************

void displayFPS(void)
{
    static float framesPerSecond    = 0.0f;
    static float lastTime           = 0.0f;
    static char strFrameRate[32]    = {0};

    float currentTime = GetTickCount() * 0.001f;
    ++framesPerSecond;

    if (currentTime - lastTime > 1.0f)
    {
        lastTime = currentTime;     
        sprintf(strFrameRate, "3D Pong [FPS: %d]", int(framesPerSecond));
        glutSetWindowTitle(strFrameRate);
        framesPerSecond = 0;
    }
}

void gluCube(const GLfloat l, const GLfloat w, const GLfloat h)
{
    glBegin(GL_QUADS);
        // Front Face
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-l, -w,  h);   // Bottom Left Of The Texture and Quad
        glTexCoord2f(1.0f, 0.0f); glVertex3f( l, -w,  h);   // Bottom Right Of The Texture and Quad
        glTexCoord2f(1.0f, 1.0f); glVertex3f( l,  w,  h);   // Top Right Of The Texture and Quad
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-l,  w,  h);   // Top Left Of The Texture and Quad
        // Back Face
        glTexCoord2f(1.0f, 0.0f); glVertex3f(-l, -w, -h);   // Bottom Right Of The Texture and Quad
        glTexCoord2f(1.0f, 1.0f); glVertex3f(-l,  w, -h);   // Top Right Of The Texture and Quad
        glTexCoord2f(0.0f, 1.0f); glVertex3f( l,  w, -h);   // Top Left Of The Texture and Quad
        glTexCoord2f(0.0f, 0.0f); glVertex3f( l, -w, -h);   // Bottom Left Of The Texture and Quad
        // Top Face
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-l,  w, -h);   // Top Left Of The Texture and Quad
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-l,  w,  h);   // Bottom Left Of The Texture and Quad
        glTexCoord2f(1.0f, 0.0f); glVertex3f( l,  w,  h);   // Bottom Right Of The Texture and Quad
        glTexCoord2f(1.0f, 1.0f); glVertex3f( l,  w, -h);   // Top Right Of The Texture and Quad
        // Bottom Face
        glTexCoord2f(1.0f, 1.0f); glVertex3f(-l, -w, -h);   // Top Right Of The Texture and Quad
        glTexCoord2f(0.0f, 1.0f); glVertex3f( l, -w, -h);   // Top Left Of The Texture and Quad
        glTexCoord2f(0.0f, 0.0f); glVertex3f( l, -w,  h);   // Bottom Left Of The Texture and Quad
        glTexCoord2f(1.0f, 0.0f); glVertex3f(-l, -w,  h);   // Bottom Right Of The Texture and Quad
        // Right face
        glTexCoord2f(1.0f, 0.0f); glVertex3f( l, -w, -h);   // Bottom Right Of The Texture and Quad
        glTexCoord2f(1.0f, 1.0f); glVertex3f( l,  w, -h);   // Top Right Of The Texture and Quad
        glTexCoord2f(0.0f, 1.0f); glVertex3f( l,  w,  h);   // Top Left Of The Texture and Quad
        glTexCoord2f(0.0f, 0.0f); glVertex3f( l, -w,  h);   // Bottom Left Of The Texture and Quad
        // Left Face
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-l, -w, -h);   // Bottom Left Of The Texture and Quad
        glTexCoord2f(1.0f, 0.0f); glVertex3f(-l, -w,  h);   // Bottom Right Of The Texture and Quad
        glTexCoord2f(1.0f, 1.0f); glVertex3f(-l,  w,  h);   // Top Right Of The Texture and Quad
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-l,  w, -h);   // Top Left Of The Texture and Quad
    glEnd();
}

// flip the RED and BLUE bits of the movie
void AVI_Player::flipIt(void* buffer) const
{
    void* b = buffer;                                           // Pointer To The Buffer
    __asm                                                       // Assembler Code To Follow
    {
        mov ecx, 256*256                                        // Counter Set To Dimensions Of Our Memory Block
        mov ebx, b                                              // Points ebx To Our Data (b)
        label:                                                  // Label Used For Looping
            mov al,[ebx+0]                                      // Loads Value At ebx Into al
            mov ah,[ebx+2]                                      // Loads Value At ebx+2 Into ah
            mov [ebx+2],al                                      // Stores Value In al At ebx+2
            mov [ebx+0],ah                                      // Stores Value In ah At ebx
            
            add ebx,3                                           // Moves Through The Data By 3 Bytes
            dec ecx                                             // Decreases Our Loop Counter
            jnz label                                           // If Not Zero Jump Back To Label
    }
}

AVI_Player::AVI_Player()
{
    this->data  = 0;
    this->frame = 0;
    this->hdc  = CreateCompatibleDC(0);
    this->hdd  = DrawDibOpen();                                // Grab A Device Context For Our Dib
}

void AVI_Player::OpenAVI(const LPCSTR szFile)
{
    AVIFileInit();                                              // Opens The AVIFile Library

    // Opens The AVI Stream
    if (AVIStreamOpenFromFile(&pavi, szFile, streamtypeVIDEO, 0, OF_READ, NULL) !=0)
    {
        // An Error Occurred Opening The Stream
        MessageBox (HWND_DESKTOP, "Failed To Open The AVI Stream", "Error", MB_OK | MB_ICONEXCLAMATION);
    }

    AVIStreamInfo(pavi, &psi, sizeof(psi));                     // Reads Information About The Stream Into psi
    width=psi.rcFrame.right-psi.rcFrame.left;                   // Width Is Right Side Of Frame Minus Left
    height=psi.rcFrame.bottom-psi.rcFrame.top;                  // Height Is Bottom Of Frame Minus Top

    lastframe=AVIStreamLength(pavi);                            // The Last Frame Of The Stream

    mpf=AVIStreamSampleToTime(pavi,lastframe)/lastframe;        // Calculate Rough Milliseconds Per Frame

    bmih.biSize = sizeof (BITMAPINFOHEADER);                    // Size Of The BitmapInfoHeader
    bmih.biPlanes = 1;                                          // Bitplanes  
    bmih.biBitCount = 24;                                       // Bits Format We Want (24 Bit, 3 Bytes)
    bmih.biWidth = 256;                                         // Width We Want (256 Pixels)
    bmih.biHeight = 256;                                        // Height We Want (256 Pixels)
    bmih.biCompression = BI_RGB;                                // Requested Mode = RGB

    hBitmap = CreateDIBSection (hdc, (BITMAPINFO*)(&bmih), DIB_RGB_COLORS, (void**)(&data), NULL, NULL);
    SelectObject (hdc, hBitmap);                                // Select hBitmap Into Our Device Context (hdc)

    pgf=AVIStreamGetFrameOpen(pavi, NULL);                      // Create The PGETFRAME  Using Our Request Mode
    if (pgf==NULL)
    {
        // An Error Occurred Opening The Frame
        MessageBox (HWND_DESKTOP, "Failed To Open The AVI Frame", "Error", MB_OK | MB_ICONEXCLAMATION);
    }
}

AVI_Player::~AVI_Player()
{
    DeleteObject(hBitmap);                                      // Delete The Device Dependant Bitmap Object
    DrawDibClose(hdd);                                          // Closes The DrawDib Device Context
    AVIStreamGetFrameClose(pgf);                                // Deallocates The GetFrame Resources
    AVIStreamRelease(pavi);                                     // Release The Stream
    AVIFileExit();                                              // Release The File
}

void AVI_Player::GrabAVIFrame(const GLuint tex)
{
    this->UpdateAVI();

    LPBITMAPINFOHEADER lpbi;                                    // Holds The Bitmap Header Information
    lpbi = (LPBITMAPINFOHEADER)AVIStreamGetFrame(pgf, this->frame);     // Grab Data From The AVI Stream
    pdata=(char *)lpbi+lpbi->biSize+lpbi->biClrUsed * sizeof(RGBQUAD);  // Pointer To Data Returned By AVIStreamGetFrame

    // Convert Data To Requested Bitmap Format
    DrawDibDraw (hdd, hdc, 0, 0, 256, 256, lpbi, pdata, 0, 0, width, height, 0);

    flipIt(data);                                               // Swap The Red And Blue Bytes (GL Compatability)

    // Update The Texture
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, 256, 256, GL_RGB, GL_UNSIGNED_BYTE, data);
}

void AVI_Player::UpdateAVI()
{
    DWORD milliseconds;
    static DWORD tickCount, lastTickCount;

    tickCount = GetTickCount();                                 // Get The Tick Count
    milliseconds = tickCount - lastTickCount;

    next+=milliseconds;                                         // Increase next Based On The Timer
    frame=next/mpf;                                             // Calculate The Current Frame

    if (frame>=lastframe)                                       // Are We At Or Past The Last Frame?
    {
        frame=0;                                                // Reset The Frame Back To Zero (Start Of Video)
        next=0;                                                 // Reset The Animation Timer (next)
    }
    lastTickCount = tickCount;                                  // Set Last Count To Current Count
}