//========================================================================
// This is a small test application for GLFW.
// This is an OpenGL port of the famous "PONG" game (the first computer
// game ever?). It is very simple, and could be improved alot. It was
// created in order to show off the gaming capabilities of GLFW.
//========================================================================

import glfw;
import std.math;
import std.c.stdlib;

//========================================================================
// Constants
//========================================================================

// Screen resolution
const int WIDTH = 640;
const int HEIGHT = 480;

// Player size (units)
const float PLAYER_XSIZE = 0.05;
const float PLAYER_YSIZE = 0.15;

// Ball size (units)
const float BALL_SIZE = 0.02;

// Maximum player movement speed (units / second)
const float MAX_SPEED = 1.5;

// Player movement acceleration (units / seconds^2)
const float ACCELERATION = 4.0;

// Player movement deceleration (units / seconds^2)
const float DECELERATION = 2.0;

// Ball movement speed (units / second)
const float BALL_SPEED = 0.4;

// Menu options
const int MENU_NONE = 0;
const int MENU_PLAY = 1;
const int MENU_QUIT = 2;

// Game events
const int NOBODY_WINS = 0;
const int PLAYER1_WINS = 1;
const int PLAYER2_WINS = 2;

// Winner ID
const int NOBODY = 0;
const int PLAYER1 = 1;
const int PLAYER2 = 2;

// Camera positions
const int CAMERA_CLASSIC = 0;
const int CAMERA_ABOVE = 1;
const int CAMERA_SPECTATOR = 2;
const int CAMERA_DEFAULT = CAMERA_CLASSIC;


//========================================================================
// Textures
//========================================================================

const int TEX_TITLE = 0;
const int TEX_MENU = 1;
const int TEX_INSTR = 2;
const int TEX_WINNER1 = 3;
const int TEX_WINNER2 = 4;
const int TEX_FIELD = 5;
const int NUM_TEXTURES = 6;

// Texture names
const char[] tex_name[ NUM_TEXTURES ] = [
    "pong3d_title.tga",
    "pong3d_menu.tga",
    "pong3d_instr.tga",
    "pong3d_winner1.tga",
    "pong3d_winner2.tga",
    "pong3d_field.tga"
];

// OpenGL texture object IDs
GLuint tex_id[ NUM_TEXTURES ];


//========================================================================
// Global variables
//========================================================================

// Display information
int width, height;

// Frame information
double thistime, oldtime, dt, starttime;

// Camera information
int camerapos;

// Player information
struct Player {
    double ypos;     // -1.0 to +1.0
    double yspeed;   // -MAX_SPEED to +MAX_SPEED
}
Player player1, player2;

// Ball information
struct Ball {
    double xpos, ypos;
    double xspeed, yspeed;
}
Ball ball;

// And the winner is...
int winner;

// Lighting configuration
const GLfloat env_ambient[4]     = [1.0f,1.0f,1.0f,1.0f];
const GLfloat light1_position[4] = [-3.0f,3.0f,2.0f,1.0f];
const GLfloat light1_diffuse[4]  = [1.0f,1.0f,1.0f,0.0f];
const GLfloat light1_ambient[4]  = [0.0f,0.0f,0.0f,0.0f];

// Object material properties
const GLfloat player1_diffuse[4] = [1.0f,0.3f,0.3f,1.0f];
const GLfloat player1_ambient[4] = [0.3f,0.1f,0.0f,1.0f];
const GLfloat player2_diffuse[4] = [0.3f,1.0f,0.3f,1.0f];
const GLfloat player2_ambient[4] = [0.1f,0.3f,0.1f,1.0f];
const GLfloat ball_diffuse[4]    = [1.0f,1.0f,0.5f,1.0f];
const GLfloat ball_ambient[4]    = [0.3f,0.3f,0.1f,1.0f];
const GLfloat border_diffuse[4]  = [0.3f,0.3f,1.0f,1.0f];
const GLfloat border_ambient[4]  = [0.1f,0.1f,0.3f,1.0f];
const GLfloat floor_diffuse[4]   = [1.0f,1.0f,1.0f,1.0f];
const GLfloat floor_ambient[4]   = [0.3f,0.3f,0.3f,1.0f];


//========================================================================
// LoadTextures() - Load textures from disk and upload to OpenGL card
//========================================================================

void LoadTextures( )
{
    int  i;

    // Generate texture objects
    glGenTextures( NUM_TEXTURES, tex_id );

    // Load textures
    for( i = 0; i < NUM_TEXTURES; i ++ )
    {
        // Select texture object
        glBindTexture( GL_TEXTURE_2D, tex_id[ i ] );

        // Set texture parameters
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );

        // Upload texture from file to texture memory
        glfwLoadTexture2D( tex_name[ i ], 0 );
    }
}


//========================================================================
// DrawImage() - Draw a 2D image as a texture
//========================================================================

void DrawImage( int texnum, float x1, float x2, float y1, float y2 )
{
    glEnable( GL_TEXTURE_2D );
    glBindTexture( GL_TEXTURE_2D, tex_id[ texnum ] );
    glBegin( GL_QUADS );
      glTexCoord2f( 0.0f, 1.0f );
      glVertex2f( x1, y1 );
      glTexCoord2f( 1.0f, 1.0f );
      glVertex2f( x2, y1 );
      glTexCoord2f( 1.0f, 0.0f );
      glVertex2f( x2, y2 );
      glTexCoord2f( 0.0f, 0.0f );
      glVertex2f( x1, y2 );
    glEnd();
    glDisable( GL_TEXTURE_2D );
}


//========================================================================
// GameMenu() - Game menu (returns menu option)
//========================================================================

int GameMenu( )
{
    int option;

    // Enable sticky keys
    glfwEnable( GLFW_STICKY_KEYS );

    // Wait for a game menu key to be pressed
    do
    {
        // Get window size
        glfwGetWindowSize( &width, &height );

        // Set viewport
        glViewport( 0, 0, width, height );

        // Clear display
        glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
        glClear( GL_COLOR_BUFFER_BIT );

        // Setup projection matrix
        glMatrixMode( GL_PROJECTION );
        glLoadIdentity();
        glOrtho( 0.0f, 1.0f, 1.0f, 0.0f, -1.0f, 1.0f );

        // Setup modelview matrix
        glMatrixMode( GL_MODELVIEW );
        glLoadIdentity();

        // Display title
        glColor3f( 1.0f, 1.0f, 1.0f );
        DrawImage( TEX_TITLE, 0.1f, 0.9f, 0.0f, 0.3f );

        // Display menu
        glColor3f( 1.0f, 1.0f, 0.0f );
        DrawImage( TEX_MENU, 0.38f, 0.62f, 0.35f, 0.5f );

        // Display instructions
        glColor3f( 0.0f, 1.0f, 1.0f );
        DrawImage( TEX_INSTR, 0.32f, 0.68f, 0.65f, 0.85f );

        // Swap buffers
        glfwSwapBuffers();

        // Check for keys
        if( glfwGetKey( 'Q' ) || !glfwGetWindowParam( GLFW_OPENED ) )
        {
            option = MENU_QUIT;
        }
        else if( glfwGetKey( GLFW_KEY_F1 ) )
        {
            option = MENU_PLAY;
        }
        else
        {
            option = MENU_NONE;
        }

        // To avoid horrible busy waiting, sleep for at least 20 ms
        glfwSleep( 0.02 );
    }
    while( option == MENU_NONE );

    // Disable sticky keys
    glfwDisable( GLFW_STICKY_KEYS );

    return option;
}


//========================================================================
// NewGame() - Initialize a new game
//========================================================================

void NewGame( )
{
    // Frame information
    starttime = thistime = glfwGetTime();

    // Camera information
    camerapos = CAMERA_DEFAULT;

    // Player 1 information
    player1.ypos   = 0.0;
    player1.yspeed = 0.0;

    // Player 2 information
    player2.ypos   = 0.0;
    player2.yspeed = 0.0;

    // Ball information
    ball.xpos = -1.0 + PLAYER_XSIZE;
    ball.ypos = player1.ypos;
    ball.xspeed = 1.0;
    ball.yspeed = 1.0;
}


//========================================================================
// PlayerControl() - Player control
//========================================================================

void PlayerControl( )
{
    float joy1pos[ 2 ];
    float joy2pos[ 2 ];

    // Get joystick X & Y axis positions
    glfwGetJoystickPos( GLFW_JOYSTICK_1, joy1pos, 2 );
    glfwGetJoystickPos( GLFW_JOYSTICK_2, joy2pos, 2 );

    // Player 1 control
    if( glfwGetKey( 'A' ) || joy1pos[ 1 ] > 0.2f )
    {
        player1.yspeed += dt * ACCELERATION;
        if( player1.yspeed > MAX_SPEED )
        {
            player1.yspeed = MAX_SPEED;
        }
    }
    else if( glfwGetKey( 'Z' ) || joy1pos[ 1 ] < -0.2f )
    {
        player1.yspeed -= dt * ACCELERATION;
        if( player1.yspeed < -MAX_SPEED )
        {
            player1.yspeed = -MAX_SPEED;
        }
    }
    else
    {
        player1.yspeed /= exp( DECELERATION * dt );
    }

    // Player 2 control
    if( glfwGetKey( 'K' ) || joy2pos[ 1 ] > 0.2f )
    {
        player2.yspeed += dt * ACCELERATION;
        if( player2.yspeed > MAX_SPEED )
        {
            player2.yspeed = MAX_SPEED;
        }
    }
    else if( glfwGetKey( 'M' ) || joy2pos[ 1 ] < -0.2f )
    {
        player2.yspeed -= dt * ACCELERATION;
        if( player2.yspeed < -MAX_SPEED )
        {
            player2.yspeed = -MAX_SPEED;
        }
    }
    else
    {
        player2.yspeed /= exp( DECELERATION * dt );
    }

    // Update player 1 position
    player1.ypos += dt * player1.yspeed;
    if( player1.ypos > 1.0 - PLAYER_YSIZE )
    {
        player1.ypos = 1.0 - PLAYER_YSIZE;
        player1.yspeed = 0.0;
    }
    else if( player1.ypos < -1.0 + PLAYER_YSIZE )
    {
        player1.ypos = -1.0 + PLAYER_YSIZE;
        player1.yspeed = 0.0;
    }

    // Update player 2 position
    player2.ypos += dt * player2.yspeed;
    if( player2.ypos > 1.0 - PLAYER_YSIZE )
    {
        player2.ypos = 1.0 - PLAYER_YSIZE;
        player2.yspeed = 0.0;
    }
    else if( player2.ypos < -1.0 + PLAYER_YSIZE )
    {
        player2.ypos = -1.0 + PLAYER_YSIZE;
        player2.yspeed = 0.0;
    }
}


//========================================================================
// BallControl() - Ball control
//========================================================================

int BallControl( )
{
    int event;
    double ballspeed;

    // Calculate new ball speed
    ballspeed = BALL_SPEED * (1.0 + 0.02*(thistime-starttime));
    ball.xspeed = ball.xspeed > 0 ? ballspeed : -ballspeed;
    ball.yspeed = ball.yspeed > 0 ? ballspeed : -ballspeed;
    ball.yspeed *= 0.74321;

    // Update ball position
    ball.xpos += dt * ball.xspeed;
    ball.ypos += dt * ball.yspeed;

    // Did the ball hit a top/bottom wall?
    if( ball.ypos >= 1.0 )
    {
        ball.ypos = 2.0 - ball.ypos;
        ball.yspeed = -ball.yspeed;
    }
    else if( ball.ypos <= -1.0 )
    {
        ball.ypos = -2.0 - ball.ypos;
        ball.yspeed = -ball.yspeed;
    }

    // Did the ball hit/miss a player?
    event = NOBODY_WINS;

    // Is the ball entering the player 1 goal?
    if( ball.xpos < -1.0 + PLAYER_XSIZE )
    {
        // Did player 1 catch the ball?
        if( ball.ypos > (player1.ypos-PLAYER_YSIZE) &&
            ball.ypos < (player1.ypos+PLAYER_YSIZE) )
        {
            ball.xpos = -2.0 + 2.0*PLAYER_XSIZE - ball.xpos;
            ball.xspeed = -ball.xspeed;
        }
        else
        {
            event = PLAYER2_WINS;
        }
    }

    // Is the ball entering the player 2 goal?
    if( ball.xpos > 1.0 - PLAYER_XSIZE )
    {
        // Did player 2 catch the ball?
        if( ball.ypos > (player2.ypos-PLAYER_YSIZE) &&
            ball.ypos < (player2.ypos+PLAYER_YSIZE) )
        {
            ball.xpos = 2.0 - 2.0*PLAYER_XSIZE - ball.xpos;
            ball.xspeed = -ball.xspeed;
        }
        else
        {
            event = PLAYER1_WINS;
        }
    }

    return event;
}


//========================================================================
// DrawBox() - Draw a 3D box
//========================================================================

const float TEX_SCALE = 4.0f;


void DrawBox( float x1, float y1, float z1, float x2, float y2, float z2 )
{
    // Draw six sides of a cube
    glBegin( GL_QUADS );
      // Side 1 (down)
      glNormal3f( 0.0f, 0.0f, -1.0f );
      glTexCoord2f( 0.0f, 0.0f );
      glVertex3f( x1,y2,z1 );
      glTexCoord2f( TEX_SCALE, 0.0f );
      glVertex3f( x2,y2,z1 );
      glTexCoord2f( TEX_SCALE, TEX_SCALE );
      glVertex3f( x2,y1,z1 );
      glTexCoord2f( 0.0f, TEX_SCALE );
      glVertex3f( x1,y1,z1 );
      // Side 2 (up)
      glNormal3f( 0.0f, 0.0f, 1.0f );
      glTexCoord2f( 0.0f, 0.0f );
      glVertex3f( x1,y1,z2 );
      glTexCoord2f( TEX_SCALE, 0.0f );
      glVertex3f( x2,y1,z2 );
      glTexCoord2f( TEX_SCALE, TEX_SCALE );
      glVertex3f( x2,y2,z2 );
      glTexCoord2f( 0.0f, TEX_SCALE );
      glVertex3f( x1,y2,z2 );
      // Side 3 (backward)
      glNormal3f( 0.0f, -1.0f, 0.0f );
      glTexCoord2f( 0.0f, 0.0f );
      glVertex3f( x1,y1,z1 );
      glTexCoord2f( TEX_SCALE, 0.0f );
      glVertex3f( x2,y1,z1 );
      glTexCoord2f( TEX_SCALE, TEX_SCALE );
      glVertex3f( x2,y1,z2 );
      glTexCoord2f( 0.0f, TEX_SCALE );
      glVertex3f( x1,y1,z2 );
      // Side 4 (forward)
      glNormal3f( 0.0f, 1.0f, 0.0f );
      glTexCoord2f( 0.0f, 0.0f );
      glVertex3f( x1,y2,z2 );
      glTexCoord2f( TEX_SCALE, 0.0f );
      glVertex3f( x2,y2,z2 );
      glTexCoord2f( TEX_SCALE, TEX_SCALE );
      glVertex3f( x2,y2,z1 );
      glTexCoord2f( 0.0f, TEX_SCALE );
      glVertex3f( x1,y2,z1 );
      // Side 5 (left)
      glNormal3f( -1.0f, 0.0f, 0.0f );
      glTexCoord2f( 0.0f, 0.0f );
      glVertex3f( x1,y1,z2 );
      glTexCoord2f( TEX_SCALE, 0.0f );
      glVertex3f( x1,y2,z2 );
      glTexCoord2f( TEX_SCALE, TEX_SCALE );
      glVertex3f( x1,y2,z1 );
      glTexCoord2f( 0.0f, TEX_SCALE );
      glVertex3f( x1,y1,z1 );
      // Side 6 (right)
      glNormal3f( 1.0f, 0.0f, 0.0f );
      glTexCoord2f( 0.0f, 0.0f );
      glVertex3f( x2,y1,z1 );
      glTexCoord2f( TEX_SCALE, 0.0f );
      glVertex3f( x2,y2,z1 );
      glTexCoord2f( TEX_SCALE, TEX_SCALE );
      glVertex3f( x2,y2,z2 );
      glTexCoord2f( 0.0f, TEX_SCALE );
      glVertex3f( x2,y1,z2 );
    glEnd();
}


//========================================================================
// UpdateDisplay() - Draw graphics (all game related OpenGL stuff goes
// here)
//========================================================================

void UpdateDisplay( )
{
    // Get window size
    glfwGetWindowSize( &width, &height );

    // Set viewport
    glViewport( 0, 0, width, height );

    // Clear display
    glClearColor( 0.02f, 0.02f, 0.02f, 0.0f );
    glClearDepth( 1.0f );
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    // Setup projection matrix
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    gluPerspective(
        55.0f,                            // Angle of view
        cast(GLfloat)width/cast(GLfloat)height,   // Aspect
        1.0f,                             // Near Z
        100.0f                            // Far Z
    );

    // Setup modelview matrix
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();
    switch( camerapos )
    {
    default:
    case CAMERA_CLASSIC:
        gluLookAt(
            0.0f, 0.0f, 2.5f,
            0.0f, 0.0f, 0.0f,
            0.0f, 1.0f, 0.0f
        );
        break;
    case CAMERA_ABOVE:
        gluLookAt(
            0.0f, 0.0f, 2.5f,
            cast(float)ball.xpos, cast(float)ball.ypos, 0.0f,
            0.0f, 1.0f, 0.0f
        );
        break;
    case CAMERA_SPECTATOR:
        gluLookAt(
            0.0f, -2.0, 1.2f,
            cast(float)ball.xpos, cast(float)ball.ypos, 0.0f,
            0.0f, 0.0f, 1.0f
        );
        break;
    }

    // Enable depth testing
    glEnable( GL_DEPTH_TEST );
    glDepthFunc( GL_LEQUAL );

    // Enable lighting
    glEnable( GL_LIGHTING );
    glLightModelfv( GL_LIGHT_MODEL_AMBIENT, env_ambient );
    glLightModeli( GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE );
    glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE );
    glLightfv( GL_LIGHT1, GL_POSITION, light1_position );
    glLightfv( GL_LIGHT1, GL_DIFFUSE,  light1_diffuse );
    glLightfv( GL_LIGHT1, GL_AMBIENT,  light1_ambient );
    glEnable( GL_LIGHT1 );

    // Front face is counter-clock-wise
    glFrontFace( GL_CCW );

    // Enable face culling (not necessary, but speeds up rendering)
    glCullFace( GL_BACK );
    glEnable( GL_CULL_FACE );

    // Draw Player 1
    glMaterialfv( GL_FRONT, GL_DIFFUSE, player1_diffuse );
    glMaterialfv( GL_FRONT, GL_AMBIENT, player1_ambient );
    DrawBox( -1.0,              cast(GLfloat)player1.ypos-PLAYER_YSIZE, 0.0,
             -1.0+PLAYER_XSIZE, cast(GLfloat)player1.ypos+PLAYER_YSIZE, 0.1 );

    // Draw Player 2
    glMaterialfv( GL_FRONT, GL_DIFFUSE, player2_diffuse );
    glMaterialfv( GL_FRONT, GL_AMBIENT, player2_ambient );
    DrawBox( 1.0-PLAYER_XSIZE, cast(GLfloat)player2.ypos-PLAYER_YSIZE, 0.0,
             1.0,              cast(GLfloat)player2.ypos+PLAYER_YSIZE, 0.1 );

    // Draw Ball
    glMaterialfv( GL_FRONT, GL_DIFFUSE, ball_diffuse );
    glMaterialfv( GL_FRONT, GL_AMBIENT, ball_ambient );
    DrawBox( cast(GLfloat)ball.xpos-BALL_SIZE, cast(GLfloat)ball.ypos-BALL_SIZE, 0.0,
             cast(GLfloat)ball.xpos+BALL_SIZE, cast(GLfloat)ball.ypos+BALL_SIZE, BALL_SIZE*2 );

    // Top game field border
    glMaterialfv( GL_FRONT, GL_DIFFUSE, border_diffuse );
    glMaterialfv( GL_FRONT, GL_AMBIENT, border_ambient );
    DrawBox( -1.1f, 1.0f, 0.0f,  1.1f, 1.1f, 0.1f );
    // Bottom game field border
    glColor3f( 0.0f, 0.0f, 0.7f );
    DrawBox( -1.1f, -1.1f, 0.0f,  1.1f, -1.0f, 0.1f );
    // Left game field border
    DrawBox( -1.1f, -1.0f, 0.0f,  -1.0f, 1.0f, 0.1f );
    // Left game field border
    DrawBox( 1.0f, -1.0f, 0.0f,  1.1f, 1.0f, 0.1f );

    // Enable texturing
    glEnable( GL_TEXTURE_2D );
    glBindTexture( GL_TEXTURE_2D, tex_id[ TEX_FIELD ] );

    // Game field floor
    glMaterialfv( GL_FRONT, GL_DIFFUSE, floor_diffuse );
    glMaterialfv( GL_FRONT, GL_AMBIENT, floor_ambient );
    DrawBox( -1.01f, -1.01f, -0.01f,  1.01f, 1.01f, 0.0f );

    // Disable texturing
    glDisable( GL_TEXTURE_2D );

    // Disable face culling
    glDisable( GL_CULL_FACE );

    // Disable lighting
    glDisable( GL_LIGHTING );

    // Disable depth testing
    glDisable( GL_DEPTH_TEST );
}


//========================================================================
// GameOver()
//========================================================================

void GameOver( )
{
    // Enable sticky keys
    glfwEnable( GLFW_STICKY_KEYS );

    // Until the user presses ESC or SPACE
    while( !glfwGetKey( GLFW_KEY_ESC ) && !glfwGetKey( ' ' ) &&
           glfwGetWindowParam( GLFW_OPENED ) )
    {
        // Draw display
        UpdateDisplay();

        // Setup projection matrix
        glMatrixMode( GL_PROJECTION );
        glLoadIdentity();
        glOrtho( 0.0f, 1.0f, 1.0f, 0.0f, -1.0f, 1.0f );

        // Setup modelview matrix
        glMatrixMode( GL_MODELVIEW );
        glLoadIdentity();

        // Enable blending
        glEnable( GL_BLEND );

        // Dim background
        glBlendFunc( GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA );
        glColor4f( 0.3f, 0.3f, 0.3f, 0.3f );
        glBegin( GL_QUADS );
          glVertex2f( 0.0f, 0.0f );
          glVertex2f( 1.0f, 0.0f );
          glVertex2f( 1.0f, 1.0f );
          glVertex2f( 0.0f, 1.0f );
        glEnd();

        // Display winner text
        glBlendFunc( GL_ONE, GL_ONE_MINUS_SRC_COLOR );
        if( winner == PLAYER1 )
        {
            glColor4f( 1.0f, 0.5f, 0.5f, 1.0f );
            DrawImage( TEX_WINNER1, 0.35f, 0.65f, 0.46f, 0.54f );
        }
        else if( winner == PLAYER2 )
        {
            glColor4f( 0.5f, 1.0f, 0.5f, 1.0f );
            DrawImage( TEX_WINNER2, 0.35f, 0.65f, 0.46f, 0.54f );
        }

        // Disable blending
        glDisable( GL_BLEND );

        // Swap buffers
        glfwSwapBuffers();
    }

    // Disable sticky keys
    glfwDisable( GLFW_STICKY_KEYS );
}


//========================================================================
// GameLoop() - Game loop
//========================================================================

void GameLoop( )
{
    int playing, event;

    // Initialize a new game
    NewGame();

    // Enable sticky keys
    glfwEnable( GLFW_STICKY_KEYS );

    // Loop until the game ends
    playing = GL_TRUE;
    while( playing && glfwGetWindowParam( GLFW_OPENED ) )
    {
        // Frame timer
        oldtime = thistime;
        thistime = glfwGetTime();
        dt = thistime - oldtime;

        // Get user input and update player positions
        PlayerControl();

        // Move the ball, and check if a player hits/misses the ball
        event = BallControl();

        // Did we have a winner?
        switch( event )
        {
        case PLAYER1_WINS:
            winner = PLAYER1;
            playing = GL_FALSE;
            break;
        case PLAYER2_WINS:
            winner = PLAYER2;
            playing = GL_FALSE;
            break;
        default:
            break;
        }

        // Did the user press ESC?
        if( glfwGetKey( GLFW_KEY_ESC ) )
        {
            playing = GL_FALSE;
        }

        // Did the user change camera view?
        if( glfwGetKey( '1' ) )
        {
            camerapos = CAMERA_CLASSIC;
        }
        else if( glfwGetKey( '2' ) )
        {
            camerapos = CAMERA_ABOVE;
        }
        else if( glfwGetKey( '3' ) )
        {
            camerapos = CAMERA_SPECTATOR;
        }

        // Draw display
        UpdateDisplay();

        // Swap buffers
        glfwSwapBuffers();
    }

    // Disable sticky keys
    glfwDisable( GLFW_STICKY_KEYS );

    // Show winner
    GameOver();
}


//========================================================================
// main() - Program entry point
//========================================================================

int main( )
{
    int menuoption;

    // Initialize GLFW
    if( !glfwInit() )
    {
        exit( 0 );
    }

    // Open OpenGL window
    if( !glfwOpenWindow( WIDTH, HEIGHT, 0,0,0,0, 16,0, GLFW_FULLSCREEN ) )
    {
        glfwTerminate();
        exit( 0 );
    }

    // Load all textures
    LoadTextures();

    // Main loop
    do
    {
        // Get menu option
        menuoption = GameMenu();

        // If the user wants to play, let him...
        if( menuoption == MENU_PLAY )
        {
            GameLoop();
        }
    }
    while( menuoption != MENU_QUIT );

    // Unload all textures
    if( glfwGetWindowParam( GLFW_OPENED ) )
    {
        glDeleteTextures( NUM_TEXTURES, tex_id );
    }

    // Terminate GLFW
    glfwTerminate();

    return 0;
}
