/*
  VERSION INFO:
    000 first public release Jan 12 2004
    001
        fix: drops only counted as +1 level, regardless of the number of lines killed
*/

/** mac compile: gcc -framework OpenGL -framework GLUT -framework Foundation -o FallingUp fallingup.c */
/* OS X 
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#include <GLUT/glut.h>
#include <OpenAL/alut.h>
long getAbsoluteMillis(void) {
  return glutGet(GLUT_ELAPSED_TIME);  //fine on OS X... seemed bad on windows...
}
*/

/** windows compile: gcc -mwindows -o FallingUp.exe fallingup.c -lglut32 -lopengl32 -lglu32 -lalut -lopenal32 */
/* WINDOWS  */
#include <windows.h>

// OpenGL includes
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>

// OpenAL includes
#include <al.h>
#include <alc.h>
#include <alctypes.h>
#include <altypes.h>
#include <alu.h>
#include <alut.h>

long getAbsoluteMillis(void) {
  return glutGet(GLUT_ELAPSED_TIME);  //fine on OS X... seemed bad on windows...
  //return GetTickCount(); //TODO: not partable enough??? // needed for windows?
}
/**/

/**
  customizable 'dedication' or 'about' screen?
  customizable colors
  customizable shapes and number of shapes?
  splash screen?
  top 10? (resettable, save where/how?)


  tetris sounds: block tick, block drop, line clear (2,3,4?), board "lock"
  music???

  two modes of play? how to switch/choose?  menu option, changes at 'new game'

  status bar? standard glut thing, or not?  maybe I do need to go SDL?

  stages (levels?):
    1 (0 lines) -- normal normal
  2 (1 line)  -- swings upside-down
  3 (8 lines) -- swings left-right
  4 (15 lines)-- slow spin left-right
  5 (23 lines)-- slow spin top-bottom
  6 (31 lines)-- slow random spin [[ how to fade?? ]]
  7 (39 lines)-- slow random spin based on key presses [[ how to fade?? ]]
  8 (47 lines)-- medium *

*/
#include <stdlib.h>
#include <time.h>
#include <stdio.h>

#define true 1
#define false 0

#define INFO 0
#define RUNNING 1
#define PAUSED 2
#define OVER 3
#define HIGHSCORE 4

#define PIECES 7

#define TOPN 10

#define XOFF -6.0
#define YOFF -9.0
#define ZOFF -16.0

#define MAXFLAME 120.0f

#define ROT_NONE 0
#define ROT_BOUNCE 1
#define ROT_CONTINUE 2
#define ROT_STOP 3
#define ROT_ONCE 4

/* AL/ALUT stuff... */
//#define NULL      0
#define NUM_BUFFERS    13
#define NUM_SOURCES    NUM_BUFFERS
ALuint buffers[NUM_BUFFERS];
ALuint sources[NUM_SOURCES];
ALfloat listenerPos[]={0.0,0.0,4.0};
ALfloat listenerVel[]={0.0,0.0,0.0};
ALfloat listenerOri[]={0.0,0.0,1.0, 0.0,1.0,0.0};
ALfloat source0Pos[]={ -2.0, 0.0, 0.0};
ALfloat source0Vel[]={ 0.0, 0.0, 0.0};
char *sounds[NUM_BUFFERS] = {
  "sounds/move.wav", "sounds/bert.wav", "sounds/ready.wav", "sounds/water.wav", "sounds/beat.wav", "sounds/thunk.wav", "sounds/drop.wav", "sounds/line.wav", "sounds/gameover.wav", "sounds/highscore.wav", "sounds/keystroke.wav", "sounds/beat.wav", "sounds/welcome.wav"
};

#define SOUND_MOVE 0
#define SOUND_BAD 1
#define SOUND_READY 2
#define SOUND_WATER 3
#define SOUND_BEAT1 4
#define SOUND_THUNK 5
#define SOUND_DROP 6
#define SOUND_LINE 7
#define SOUND_GAMEOVER 8
#define SOUND_HIGHSCORE 9
#define SOUND_KEYSTROKE 10
#define SOUND_BEAT2 11
#define SOUND_WELCOME 12

int beat=0;


/* GL/GLUT stuff... */
int mainWindowId;
int font = (int)GLUT_BITMAP_9_BY_15;
int replacescore=-1, replacechar=0;

const short CHANGEY=0, CHANGEX=1, CHANGEROT=2;
const int BASE_SPEED=1200;
const short PC_EL=0, PC_ELB=1, PC_S=2, PC_Z=3, PC_BLOCK=4, PC_BAR=5, PC_TEE=6; //PC:Piece#
const short data[][4][16]= {
  { { 0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,1    } ,{ 0,0,0,0,0,0,1,0,0,0,1,0,0,1,1,0    } ,{ 0,0,0,0,0,1,0,0,0,1,1,1,0,0,0,0    } ,{ 0,0,0,0,0,0,1,1,0,0,1,0,0,0,1,0    } } , //PC_EL
  { { 0,0,0,0,0,0,0,0,0,1,1,1,0,1,0,0    } ,{ 0,0,0,0,0,0,1,0,0,0,1,0,0,0,1,1    } ,{ 0,0,0,0,0,0,0,1,0,1,1,1,0,0,0,0    } ,{ 0,0,0,0,0,1,1,0,0,0,1,0,0,0,1,0    } } , //PC_ELB
  { { 0,0,0,0,0,0,0,0,0,0,1,1,0,1,1,0    } ,{ 0,0,0,0,0,1,0,0,0,1,1,0,0,0,1,0    } ,{ 0,0,0,0,0,0,0,0,0,0,1,1,0,1,1,0    } ,{ 0,0,0,0,0,1,0,0,0,1,1,0,0,0,1,0    } } , //PC_S
  { { 0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1    } ,{ 0,0,0,0,0,0,1,0,0,1,1,0,0,1,0,0    } ,{ 0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1    } ,{ 0,0,0,0,0,0,1,0,0,1,1,0,0,1,0,0    } } , //PC_Z
  { { 0,0,0,0,0,0,1,1,0,0,1,1,0,0,0,0    } ,{ 0,0,0,0,0,0,1,1,0,0,1,1,0,0,0,0    } ,{ 0,0,0,0,0,0,1,1,0,0,1,1,0,0,0,0    } ,{ 0,0,0,0,0,0,1,1,0,0,1,1,0,0,0,0    } } , //PC_BLOCK
  { { 0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0    } ,{ 0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0    } ,{ 0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0    } ,{ 0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0    } } , //PC_BAR
  { { 0,0,0,0,0,0,0,0,0,1,1,1,0,0,1,0    } ,{ 0,0,0,0,0,0,1,0,0,0,1,1,0,0,1,0    } ,{ 0,0,0,0,0,0,1,0,0,1,1,1,0,0,0,0    } ,{ 0,0,0,0,0,0,1,0,0,1,1,0,0,0,1,0    } }  //PC_TEE

};

float colors[][3] = {
  { 0.2,0.2,0.6  } , { 0.6,0.2,0.2  } , { 0.4,0.2,0.4  } , { 0.2,0.6,0.2  } , { 0.8,0.2,0.1  } , { 0.8,0.8,0.8  } , { 0.2,0.2,0.2  }
};

const int BLOCKTIMER=0;
const int SCALE=128,SCALEWIDTH=256,SCALEHEIGHT=256;
int HEIGHT=600, WIDTH=425, BLOCKW=200, BLOCKH=400;
char PROGRAM_NAME[80] = "Falling Up";
char title[80], scoreString[80], lineString[80], levelString[80];

int MAXLEVEL_NAMES=8;
char *levels[8] = {
  "Deceptive beginnings...", //0
  "Flipped!", //1
  "Flip^2", //2
  "A hint of things to come", //3
  "Double gasp", //4
  "You think you know terror?", //5
  "Lightly whirled peas", //6
  "eight" //7
};

char *topnames[TOPN];
int  topscores[TOPN];
int  toplevels[TOPN];

int linelevels[8] = { 1, 2, 3, 4, 5, 6, 7, 10000000 };

char *levelstring;

//info for keeping track of the time and frames per second
int timeOfRender=0;
int timeAtLastFrame=0;
int timeAtLastSecond=0;
int timeAtLastRender=0;
int timeOfBlockDrop=0;
int timeAtLastTexture=0;
int timeAtLastFlameCount= 0;
int timeAtLastRotCount = 0;

int frame=0;
int fps=0;
int displayFPS = false, displayNext = false, displayTrails = false, displaySound = true;


int paused, gameOn;
int flameCount=1, flameDirection=1, rotCount=0, rotDirection=1, rotted=0;
int mode;

int lines, score, speed, level, nextlevel;

int rotMsec, rotMax, rotSpeed, rotStyle, rotDegrees;

short current, nextpiece;
short orientation;
short posx, posy; //position based on *center* of 3x3 grid. bar goes outside to the left/top
short grid[10][24]; //4 added to the top in the case of the bar being flipped up

unsigned char *image;

char* makeString(int i) {
   char* mystring = (char*)malloc(sizeof(char)*(i+1));
   for (;i>=0;i--) mystring[i]=0;
   return mystring;
}

void startSound(int i) {
    if (displaySound)
    alSourcePlay(sources[i]);
}

void stopSound(int i) {
    alSourceStop(sources[i]);
}

void displayOpenALError(char* s, ALenum e) {
  switch (e) {
    case AL_INVALID_NAME:
      printf("%s Invalid Name\n", s);
      break;
    case AL_INVALID_ENUM:
      printf("%s Invalid Enum\n", s);
      break;
    case AL_INVALID_VALUE:
      printf("%s Invalid Value\n", s);
      break;
    case AL_INVALID_OPERATION:
      printf("%s Invalid Operation\n", s);
      break;
    case AL_OUT_OF_MEMORY:
      printf("%s Out Of Memory\n", s);
      break;
    default:
      printf("*** ERROR *** Unknown error case (%d) in displayOpenALError\n",e);
      break;
  };
}

int LoadAndAssignWAV(char* file, ALuint buf) {
  int      error;
  ALenum    format; 
  ALsizei    size;
  ALsizei    freq; 
  ALboolean  loop;
  ALvoid*    data;

  // Load in the WAV file from disk
  alutLoadWAVFile(file, &format, &data, &size, &freq, &loop); 
  if ((error = alGetError()) != AL_NO_ERROR) { 
    displayOpenALError("alutLoadWAVFile : ", error);  
    return 0; 
  }

  // Copy the new WAV data into the buffer
  alBufferData(buf,format,data,size,freq); 
  if ((error = alGetError()) != AL_NO_ERROR) { 
    displayOpenALError("alBufferData :", error); 
    return 0; 
  }

  // Unload the WAV file
  alutUnloadWAV(format,data,size,freq); 
  if ((error = alGetError()) != AL_NO_ERROR) { 
    displayOpenALError("alutUnloadWAV :", error);
    return 0;
  }

  return 1;
}

void writeScores(void) {
  FILE *out;
  int i;
  if ((out = fopen("fallingup.sco","w")) == NULL) {
    puts("Unable to open high score file for writing :(\n");
    exit(0);
  } else {
    for (i=0;i<TOPN;i++) {
      fprintf(out,"%s,%d,%d;",topnames[i],topscores[i],toplevels[i]+1);
    }
    fclose(out);
  }
}

void readScores(void) {
  FILE *in;
  int i,j;
  int c;
  if ((in = fopen("fallingup.sco","r")) == NULL) {
    writeScores();
  } else {
    for (i=0;i<TOPN;i++) {
      //free(topnames[i]); //TODO: can't free on first pass?
      topnames[i]=makeString(20);
      j=0;
      while ((c = fgetc(in)) != ',') {
        topnames[i][j]=c; j++;
      }
      fscanf(in,"%d,%d;",&topscores[i],&toplevels[i]);
    }
    fclose(in);
  }
}

void endGame(void) {
  int i,j;
  for (i=0, replacescore = -1;i<TOPN && replacescore == -1;i++) {
    if (score > topscores[i]) {
      replacescore = i;
      replacechar=0;
      mode = HIGHSCORE;
      free (topnames[TOPN-1]);
      for (j=TOPN-1;j>=i;j--) {
        topscores[j+1]=topscores[j];
        topnames[j+1]=topnames[j];
        toplevels[j+1]=toplevels[j];
      }
      topnames[replacescore]=makeString(20);
      topnames[replacescore][replacechar]='_';
      topscores[replacescore]=score;
      toplevels[replacescore]=level;
    }
  }
  if (mode != HIGHSCORE) {
    mode=OVER;
    startSound(SOUND_GAMEOVER);
  } else {
    startSound(SOUND_HIGHSCORE);
  }
  paused=false;
  //EnableMenuItem(menu,IDM_START,MF_ENABLED);
  //EnableMenuItem(menu,IDM_STOP,MF_GRAYED);
  //EnableMenuItem(menu,IDM_PAUSE,MF_GRAYED);
  gameOn=false;
  stopSound(SOUND_WATER);
}

int isHit(int myposx, int myposy, const short* tempblock) {
  int x,y;
  for (x=0;x<4;x++) for (y=0;y<4;y++) {
    if (tempblock[x+y*4] != 0) {
      //if this space exists in the grid, or is outside the left/right/bottom bounds... bip
      if (myposx+x < 0 || myposx+x > 9 || myposy-y < 0) return 1;
      if (grid[myposx+x][myposy-y] != -1) return 1;
    }
  }
  return 0;
}

void setBlock(int myposx, int myposy, int myorientation,int mycurrent) {
  int x,y,clear,numclear,suby;
  //TODO: reset timer?
  for (x=0;x<4;x++) for (y=0;y<4;y++) {
    if(data[mycurrent][myorientation][x+y*4] != 0) {
      grid[myposx+x][myposy-y] = current;
    }
  }
  current=nextpiece;
  nextpiece=rand()%PIECES;
  posx=3;
  posy=22;
  orientation=0;
  if (isHit(posx,posy,data[current][orientation])) {
    endGame();
    return;
  }
  /*test to see if we can clear some lines :)*/
  numclear=0;
  for (y=0;y<21;y++) {
    clear=1;
    for (x=0;x<10;x++) if (grid[x][y] == -1) clear=0;
    if (clear) {
      numclear++;
      lines+=1;
      //go up a level, depending
      //TODO: make this a formula?
      while (lines >= linelevels[nextlevel]) nextlevel++;
      for (suby=y+1;suby<21;suby++) for (x=0;x<10;x++) grid[x][suby-1]=grid[x][suby];
      y--;
    }
    //TODO: better speed alg?
    //speed is msecs to wait for a block drop
    speed=BASE_SPEED - lines * 35;
    if (speed < 125) speed=125;

    //TODO: better scoring system?
    score+=100*(numclear*numclear);
    if (numclear > 0) {
      startSound(SOUND_LINE);
    }
  }
  clear=1;
  for (x=0;x<10;x++) if (grid[x][21] != -1) clear=0;
  if (clear!=1) endGame();
  /*test to see if the game is over*/
}

int testBounds(short changeType, int amount) {
  //if we would hit something, and it's due to ydif, stick us there.
  //hitting something INCLUDES the bottom of the screen, BTW, just in case you
  //were wondering, this is where that check happens. Neener neener. :)
  //if we're rotating or xdiffing or ydiffing and not hitting anything, make it so.
  int i,mycurrent,myposx,myposy,myorientation, stuck;
  short tempblock[16];
  mycurrent=current;
  myposx=posx;
  myposy=posy;
  myorientation=orientation;
  stuck = false;
  if (changeType == CHANGEY) {
    for (i=0;i<16;i++) tempblock[i]=data[mycurrent][myorientation][i];
    if (isHit(myposx,myposy-amount,tempblock) == 1) {
      //stick us there
      setBlock(myposx,myposy,myorientation,mycurrent);
      stuck = true;
    }
    else {
      posy-=amount;
    }
  }
  else if (changeType == CHANGEX) {
    for (i=0;i<16;i++) tempblock[i]=data[current][orientation][i];
    if (isHit(myposx-amount,myposy,tempblock) != 1) {
      posx-=amount;
    } else {
      stuck = true;
    }
  }
  else { //changeType == CHANGEROT

    for (i=0;i<16;i++) tempblock[i]=data[current][(orientation+1)%4][i];
    if (isHit(myposx,myposy,tempblock) != 1) {
      orientation=(orientation+1)%4;
    } else {
      stuck = true;
    }
  }
  return stuck;
}

void drawBlock(float x, float y, float z, float red, float green, float blue) {
  // save the current position and move to the new one

  glPushMatrix();
  x-=5;
    y-=7;
  glTranslatef(x, y, z);

  // begin to draw a block in this position

  glBegin(GL_QUADS);

  // we set the color to what was passed, but we also make our own shades

  glColor4f(red, green, blue,1.0);
  glVertex3f(-0.5, 0.5, 0.0);
  glVertex3f(0.5, 0.5, 0.0);
  glVertex3f(0.5, -0.5, 0.0);
  glColor4f((1.0-red)/2.0, (1.0-green)/2.0, (1.0-blue)/2.0,1.0);
  glVertex3f(-0.5, -0.5, 0.0);

  glEnd();

  // now, surround it with a pretty line border

  glBegin(GL_LINES);

  glColor4f(0.0, 0.0, 0.0, 1.0);
  glVertex3f(-0.5, 0.5, 0.0);
  glVertex3f(0.5, 0.5, 0.0);
  glVertex3f(-0.5, 0.5, 0.0);
  glVertex3f(-0.5, -0.5, 0.0);
  glVertex3f(0.5, 0.5, 0.0);
  glVertex3f(0.5, -0.5, 0.0);
  glVertex3f(-0.5, -0.5, 0.0);
  glVertex3f(0.5, -0.5, 0.0);

  glEnd();

  // before we finish, restore the matrix

  glPopMatrix();
}

void renderBitmapString(float x, float y, void *font,char *string)
{

  char *c;
  // set position to start drawing fonts
  glRasterPos2f(x, y);
  // loop all the characters in the string
  for (c=string; *c != '\0'; c++) {
    glutBitmapCharacter(font, *c);
  }
}


void setOrthographicProjection(void) {

  // switch to projection mode
  glMatrixMode(GL_PROJECTION);
  // save previous matrix which contains the
  //settings for the perspective projection
  glPushMatrix();
  // reset matrix
  glLoadIdentity();
  // set a 2D orthographic projection
  gluOrtho2D(0, WIDTH, 0, HEIGHT/2);
  glScalef(1, -1, 1);
  // mover the origin from the bottom left corner
  // to the upper left corner
  glTranslatef(0, -HEIGHT/2, 0);
  glMatrixMode(GL_MODELVIEW);
}

void resetPerspectiveProjection(void) {
  // set the current matrix to GL_PROJECTION
  glMatrixMode(GL_PROJECTION);
  // restore previous settings
  glPopMatrix();
  // get back to GL_MODELVIEW matrix
  glMatrixMode(GL_MODELVIEW);
}

void renderBlocks(void) {
  int x, y;
  float XLEFT, XRIGHT, YTOP, YBOTTOM;
  XLEFT = -5.5;
  XRIGHT= XLEFT+10.0;
  YTOP = -7.5;
  YBOTTOM = YTOP-0.5;

    glTranslatef(XOFF/2, YOFF/2, ZOFF);

  //increment 'display level'
  if (level != nextlevel) {
    if (rotStyle != ROT_STOP && rotCount > 0) rotStyle = ROT_STOP;
    if (rotCount == 0 || rotted) {
      rotted=false;
      rotCount = 0;
      //level = nextlevel;
      level++;
      levelstring=levels[level];
      if (level == 0) {
        //already set up?
      } else if (level == 1) {
        rotStyle=ROT_ONCE;
        rotMax=230;
        rotMsec=15;
        rotDirection = 1;
      } else if (level == 2) {
        rotStyle=ROT_ONCE;
        rotMax=230;
        rotMsec=15;
        rotDirection = 1;
      } else if (level == 3) {
        rotStyle=ROT_ONCE;
        rotMax=230;
        rotMsec=15;
        rotDirection = 1;
      } else if (level == 4) {
        rotStyle=ROT_ONCE;
        rotMax=230;
        rotMsec=15;
        rotDirection = 1;
      } else if (level == 5) {
        rotStyle=ROT_CONTINUE;
        rotMax=230;
        rotMsec=15;
        rotDirection = 1;
      } else if (level == 6) {
        rotStyle=ROT_ONCE;
        rotMax=230;
        rotMsec=15;
        rotDirection = 1;
      } else if (level == 7) {
        rotStyle=ROT_ONCE;
        rotMax=230;
        rotMsec=15;
        rotDirection = 1;
      } else if (level == 8) {
        rotStyle=ROT_ONCE;
        rotMax=230;
        rotMsec=15;
        rotDirection = 1;
      } else if (level == 9) {
        rotStyle=ROT_ONCE;
        rotMax=230;
        rotMsec=15;
        rotDirection = 1;
      } else {
        rotStyle=ROT_ONCE;
        rotMax=230;
        rotMsec=15;
        rotDirection = 1;
      }
    }
  }

  if (level == 0) {
    rotCount = 0;
  } else if (level == 1) {
    glRotatef(180.0f*((float)rotCount/(float)rotMax),5,0,0);
    glTranslatef(0.0f, -5.0*((float)rotCount/(float)rotMax), 0.0f);
  } else if (level == 2) {
    glRotatef(180.0f,5,0,0);
    glTranslatef(0.0f, -5.0, 0.0f);
    glRotatef(180.0f*((float)rotCount/(float)rotMax),0,5,0);
    //glRotatef(360.0f*((float)rotCount/(float)rotMax),1,1,1);
  } else if (level == 3) {
    glRotatef(180.0f*((float)(rotMax-rotCount)/(float)rotMax),5,0,0);
    glRotatef(180.0f*((float)(rotMax-rotCount)/(float)rotMax),0,5,0);
    glTranslatef(0.0f, -5.0*((float)(rotMax-rotCount)/(float)rotMax), 0.0f);
    glRotatef(360.0f*((float)rotCount/(float)rotMax),-10,0,0);
  } else if (level == 4) {
    glRotatef(360.0f*((float)rotCount/(float)rotMax),0,0,0);
    glRotatef(360.0f*((float)rotCount/(float)rotMax),5,0,0);
  } else if (level == 5) {
    glRotatef(360.0f*((float)rotCount/(float)rotMax),0,5,0);
/*
    glRotatef(360.0f*((float)rotCount/(float)rotMax),0,1,0);
    glRotatef(360.0f*((float)rotCount/(float)rotMax),1,0,0);
    glRotatef(360.0f*((float)rotCount/(float)rotMax),0,0,1);
*/
  } else if (level > 5) {
    glRotatef(180.0f*((float)rotCount/(float)rotMax),0,5,0);
    glRotatef(360.0f*((float)rotCount/(float)rotMax),0,1,0);
    glRotatef(360.0f*((float)rotCount/(float)rotMax),1,0,0);
    glRotatef(360.0f*((float)rotCount/(float)rotMax),0,0,1);
  }

  switch(mode) {
  case HIGHSCORE:
    break;
  case OVER:
  case RUNNING:
    //for each x/y of the grid, paint what's there... then paint our block in its position?
    for (x=0;x<10;x++) for (y=0;y<20;y++) {
      if (grid[x][y] > -1) {
        drawBlock(x,y,0,colors[grid[x][y]][0],colors[grid[x][y]][1],colors[grid[x][y]][2]);
      }
    }
    if (current != -1) {
      for (x=0;x<4;x++) for (y=0;y<4;y++) {
        if (data[current][orientation][y*4+x] != 0)
          drawBlock(posx+x,posy-y,0,colors[current][0],colors[current][1],colors[current][2]);
      }
    }

    // begin to draw the UNDERLINE

    glBegin(GL_QUADS);

    glColor3f(1.0, 1.0, 0.0);
    glVertex3f(XLEFT, YTOP, 0);
    glVertex3f(XRIGHT, YTOP, 0);
    glVertex3f(XRIGHT, YBOTTOM, 0);
    glVertex3f(XLEFT, YBOTTOM, 0);

    glEnd();

    // now, surround it with a pretty line border

    glBegin(GL_LINES);

    glColor3f(0.0, 0.0, 0.0);
    glVertex3f(XLEFT, YTOP, 0);
    glVertex3f(XRIGHT, YTOP, 0);
    glVertex3f(XLEFT, YTOP, 0);
    glVertex3f(XLEFT, YBOTTOM, 0);
    glVertex3f(XRIGHT, YTOP, 0);
    glVertex3f(XRIGHT, YBOTTOM, 0);
    glVertex3f(XLEFT, YBOTTOM, 0);
    glVertex3f(XRIGHT, YBOTTOM, 0);

    glEnd();

    glTranslatef(-XOFF/2, -YOFF/2, ZOFF);


    setOrthographicProjection();
    glPushMatrix();
    glLoadIdentity();
    sprintf(scoreString,"%d",score);
    sprintf(lineString,"%d",lines);
    glColor3f(0.0,0.0,0.0);
    renderBitmapString(14,20,(void *)font,levelstring);
    if (displayNext) {
      renderBitmapString(288,165,(void *)font,"Next:");
    }
    renderBitmapString(288,226,(void *)font,"Score:");
    renderBitmapString(298,241,(void *)font,scoreString);
    renderBitmapString(288,256,(void *)font,"Lines:");
    renderBitmapString(298,271,(void *)font,lineString);
    glColor3f(0.0,1.0,0.6);
    renderBitmapString(15,19,(void *)font,levelstring);
    if (displayNext) {
      renderBitmapString(289,166,(void *)font,"Next: (F5)");
    }
    renderBitmapString(289,227,(void *)font,"Score:");
    renderBitmapString(299,242,(void *)font,scoreString);
    renderBitmapString(289,257,(void *)font,"Lines:");
    renderBitmapString(299,272,(void *)font,lineString);

    glPopMatrix();
    resetPerspectiveProjection();
    break;
  case PAUSED: break;
  default:
    //TODO: error? probably not, really.
    break;
  }
}

void renderScores(void) {
  static char lineitem[80];
  int i;
  for (i=0;i<TOPN;i++) {
    sprintf(lineitem,"%-20s      %-9d  %d",topnames[i],topscores[i],toplevels[i]);
    renderBitmapString(30,180+i*10,(void *)font,lineitem);
  }
}

void renderScene(void) {
  //TODO: this is my idle function!
  glutSetWindow(mainWindowId); // not really needed, only one window
  timeAtLastRender = timeOfRender;
  timeOfRender = getAbsoluteMillis();
  char lineitem[80];
  int i, x, y, temp;
  unsigned char* imagetemp;

  frame++;

  glViewport(0,0,WIDTH,HEIGHT);
  glLoadIdentity(); // necessary!
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //TODO: needed? YES!

  //texturize(); //TODO -- figure out why this second texturize kills rotation on SHOUT

  if (timeOfRender - timeAtLastFlameCount > 100) {
    flameCount+=flameDirection;
    //if (flameCount > 15 || flameCount < 1) flameDirection *= -1;
    //if (flameCount >= MAXFLAME) flameCount = 0;
    if (flameCount > MAXFLAME || flameCount < 1) flameDirection *= -1;
    timeAtLastFlameCount = timeOfRender;
    temp = WIDTH*HEIGHT*3;
    imagetemp=image;
    if (displayTrails && mode != HIGHSCORE) {
      if (level < 1) {
        for (i=0;i<temp;i++,imagetemp++) {
          if (image[i] >= 240) image[i] = 255;
          else image[i]+=15;
        }
      } else if (level < 3) {
        for (i=0;i<temp;i++,imagetemp++) {
          if (image[i] >= 45 && image[i] <= 75) {
            image[i]=60;
          } else if (image[i] < 45) {
            image[i]+=15;
          } else {
            image[i]-=15;
          }
        }
      } else {
        for (i=0;i<temp;i++,imagetemp++) {
          if (image[i] < 15) image[i] = 0;
          else image[i]-=15;
        }
      }
    }
  }

  if (timeOfRender - timeAtLastRotCount > rotMsec) {
    if (!rotted && rotStyle != ROT_NONE) {
      rotCount +=rotDirection;
      if (rotCount < 0) {
        if (rotStyle == ROT_ONCE || rotStyle == ROT_STOP) { rotted = true; rotCount = 0;}
        else if (rotStyle == ROT_BOUNCE) { rotDirection *=-1; rotCount = rotDirection; }
        else if (rotStyle == ROT_CONTINUE) { rotCount += rotMax; }
      } else {
        if (rotCount > rotMax) {
          if (rotStyle == ROT_ONCE || rotStyle == ROT_STOP) { rotted = true; rotCount = rotMax; }
          else if (rotStyle == ROT_BOUNCE) { rotDirection *=-1; rotCount = rotMax+rotDirection; }
          else if (rotStyle == ROT_CONTINUE) { rotCount -= rotMax; }
        }
      }
      timeAtLastRotCount = timeOfRender;
    }
  }

  if (timeOfRender - timeAtLastSecond > 1000) {
    fps = frame*1000.0/(timeOfRender - timeAtLastSecond);
    timeAtLastSecond = timeOfRender;
    frame = 0;
    sprintf(title,"%s [%3d FPS], %d, %d, %d, %d",PROGRAM_NAME,fps,flameCount,rotCount, level, nextlevel);
    if (displayFPS) glutSetWindowTitle(title);
  }

  glPixelStorei(GL_UNPACK_ALIGNMENT,4);
/*
  glPushMatrix();
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(0.0f,WIDTH-1,0,HEIGHT-1,-1,-20);
*/
    //setOrthographicProjection();
if (displayTrails) {
if (mode != HIGHSCORE) {
  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  glOrtho(0, WIDTH-1, 0, HEIGHT-1,-1,20);
  glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
  //glRasterPos2i(-WIDTH,-HEIGHT);
  glRasterPos2i(0,0);
  glDrawPixels(WIDTH,HEIGHT,GL_RGB,GL_UNSIGNED_BYTE,image);
    glPopMatrix();
    resetPerspectiveProjection();
}
}
/*
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glPopMatrix();
*/

  //TODO: swap renderScenes out depending on mode!! seems more OO.
  switch (mode) {
  case INFO:
    setOrthographicProjection();
    glPushMatrix();
    glLoadIdentity();
    glColor3f(0.0,0.0,0.0);
    renderBitmapString(20,15,(void *)font,"Falling Up");
    renderBitmapString(20,30,(void *)font,"(c) 2004 Kaolin Fire [http://erif.org]");
    renderBitmapString(20,160,(void *)font,"Top scores");
    glColor3f(0.0,1.0,0.6);
    renderBitmapString(21,16,(void *)font,"Falling Up");
    renderBitmapString(21,31,(void *)font,"(c) 2004 Kaolin Fire [http://erif.org]");
    renderBitmapString(21,161,(void *)font,"Top scores");
    glColor3f(1.0,1.0,0.0);
    renderBitmapString(40,60,(void *)font,"F1      --  start");
    renderBitmapString(40,70,(void *)font,"F2      --  pause");
    sprintf(lineitem,"F3      --  toggle FPS display (%s)",displayFPS?"on":"off");
    renderBitmapString(40,80,(void *)font,lineitem);
    sprintf(lineitem,"F5      --  toggle 'next piece' (%s)",displayNext?"on":"off");
    renderBitmapString(40,90,(void *)font,lineitem);
    sprintf(lineitem,"F6      --  toggle 'trails' (%s)",displayTrails?"on":"off");
    renderBitmapString(40,100,(void *)font,lineitem);
    sprintf(lineitem,"F7      --  toggle sound (%s)",displaySound?"on":"off");
    renderBitmapString(40,110,(void *)font,lineitem);
    renderBitmapString(40,120,(void *)font,"F12/ESC --  bosskey (pause/minimize)");
    renderBitmapString(40,130,(void *)font,"ALT-F4  --  quit :)");

    renderScores();
 
    glPopMatrix();
    resetPerspectiveProjection();
    break;
  case RUNNING:
    if (timeOfRender > timeOfBlockDrop + speed) {
      if (testBounds(CHANGEY,1)) {
        startSound(SOUND_THUNK);
      } else {
        //beat -- but it's on two channels so as to not cut itself off
        startSound(beat?SOUND_BEAT1:SOUND_BEAT2);
        beat = !beat;
      }
      timeOfBlockDrop = timeOfRender;
    }
    renderBlocks();
    if (displayNext) {
      if (nextpiece != -1) {
        glPushMatrix();
        glLoadIdentity();
        glTranslatef(9.5, 1.0, ZOFF);
        for (x=0;x<4;x++) for (y=0;y<4;y++) {
          if (data[nextpiece][0][y*4+x] != 0)
            drawBlock(x,4-y,0,colors[nextpiece][0],colors[nextpiece][1],colors[nextpiece][2]);
        }
        glPopMatrix();
      }
    }
    break;
  case OVER:
    renderBlocks();
    setOrthographicProjection();
    glPushMatrix();
    glLoadIdentity();
    glColor3f(0.0,0.0,0.0);
    renderBitmapString(63,135,(void *)font," -- GAME OVER: F1 to play again --");
    glColor3f(0.5,0.5,0.9);
    renderBitmapString(64,136,(void *)font," -- GAME OVER: F1 to play again --");
    glPopMatrix();
    resetPerspectiveProjection();
    break;
  case PAUSED:
    timeOfBlockDrop += (timeOfRender - timeAtLastRender); // extend block drop
    setOrthographicProjection();
    glPushMatrix();
    glLoadIdentity();
    glColor3f(0.0,0.0,0.0);
    renderBitmapString(83,135,(void *)font," -- PAUSED: F2 to continue --");
    glColor3f(0.5,0.5,0.9);
    renderBitmapString(84,136,(void *)font," -- PAUSED: F2 to continue --");
    glPopMatrix();
    resetPerspectiveProjection();
    break;
  case HIGHSCORE:
    setOrthographicProjection();
    glPushMatrix();
    glLoadIdentity();
    renderBitmapString(114,136,(void *)font,"!!! HIGH SCORE !!!");
    renderScores();
    glPopMatrix();
    resetPerspectiveProjection();
    break;
 

  default:
    break; //TODO: error?
  }

  if (displayTrails) glReadPixels(0,0,WIDTH,HEIGHT,GL_RGB,GL_UNSIGNED_BYTE,image);
  glutSwapBuffers();

  //glutPostRedisplay(); // TODO: necessary?  only for really cool stuff?
  
}


void startGame(void) {
  int x, y;
  mode=RUNNING; //TODO: start in INFO mode
  gameOn=true;
  srand(time(NULL));
  lines=0;
  level=0;
  nextlevel=0;
  score=0;
  levelstring=levels[0];
  speed=BASE_SPEED;
  for (x=0;x<10;x++) for (y=0;y<24;y++) grid[x][y]=-1;
  current=(short)(rand()%PIECES);
  nextpiece=(short)(rand()%PIECES);
  posx=(short)3;
  posy=(short)22;
  orientation=(short)0;
  timeOfBlockDrop=0;
  rotCount=0;
  rotStyle=ROT_NONE;
  rotMax=180;
  startSound(SOUND_READY);
  startSound(SOUND_WATER);
}

void bosskey(void) {
    if (mode == RUNNING) {
      mode=PAUSED;
      stopSound(SOUND_WATER);
    }
    glutIconifyWindow();
}

void processNormalKeys(unsigned char key, int x, int y) {
  if (key == 27) {
    bosskey();
  } else if (mode == RUNNING) {
    if (key == ' ') {
      while (!testBounds(CHANGEY,1));
      startSound(SOUND_DROP);
      startSound(SOUND_THUNK);
    }
  } else if (mode == OVER) {
    if (key == '\n' || key == ' ') mode = INFO;
  } else if (mode == HIGHSCORE) {
    if (key == 8 || key == 14 || key == 127 || key == '_') { // TODO: what other ascii characters (backspace vs delete?)
      startSound(SOUND_KEYSTROKE);
      if (replacechar > 0) {
      topnames[replacescore][replacechar]=0;
      replacechar--;
      topnames[replacescore][replacechar]='_';
      }
    } else if ((key >= 'a' && key <= 'z') || (key >= 'A' && key <= 'Z')) {
      startSound(SOUND_KEYSTROKE);
      if (replacechar < 20 - 1) {
        topnames[replacescore][replacechar]=key;
        replacechar++;
        topnames[replacescore][replacechar]='_';
      }
    } else if (key == '\n' || key == 13 || key == 10) {
      startSound(SOUND_LINE);
      topnames[replacescore][replacechar]=0;
      writeScores();
      mode = INFO;
    }
  }
}

void processSpecialKeys(int key, int x, int y) {
  switch (key) {
  case GLUT_KEY_DOWN:
    if (mode == RUNNING) {
      if (testBounds(CHANGEY,1)) {
        startSound(SOUND_THUNK);
      } else {
        startSound(SOUND_MOVE);
      }
    }
    break;
  case GLUT_KEY_LEFT:
    if (mode == RUNNING) {
      if (testBounds(CHANGEX,1)) {
        startSound(SOUND_BAD);
      } else {
        startSound(SOUND_MOVE);
      }
    }
    break;
  case GLUT_KEY_RIGHT:
    if (mode == RUNNING) {
      if (testBounds(CHANGEX,-1)) {
        startSound(SOUND_BAD);
      } else {
        startSound(SOUND_MOVE);
      }
    }
    break;
  case GLUT_KEY_UP:
    if (mode == RUNNING) {
      if (testBounds(CHANGEROT,1)) {
        startSound(SOUND_BAD);
      } else {
        startSound(SOUND_MOVE);
      }
    }
    break;
  case GLUT_KEY_F3:
    displayFPS = !displayFPS;
    glutSetWindowTitle(PROGRAM_NAME);
    break;
  case GLUT_KEY_F5:
    displayNext = !displayNext;
    break;
  case GLUT_KEY_F6:
    displayTrails = !displayTrails;
    break;
  case GLUT_KEY_F7:
    displaySound = !displaySound;
    if (displaySound && mode == RUNNING) {
      startSound(SOUND_WATER);
    } else {
      stopSound(SOUND_WATER);
    }
    break;
  case GLUT_KEY_F1:
    if (mode != RUNNING) {
      startGame();
    }
    break;
  case GLUT_KEY_F4:
    if (glutGetModifiers() & GLUT_ACTIVE_ALT) exit(0);
    break;
  case GLUT_KEY_F2:
    if (mode == RUNNING) {
      mode=PAUSED;
      stopSound(SOUND_WATER);
    } else if (mode == PAUSED) {
      mode=RUNNING;
      startSound(SOUND_WATER);
    }
    break;
  case GLUT_KEY_F12:  bosskey(); break;
  default: break;
  }
}


void fallingupInit(void) {
  int i;
  image = (unsigned char*) malloc((HEIGHT*WIDTH)*3*sizeof(unsigned char));
  mode=INFO;

  glEnable(GL_LINE_SMOOTH);
  //glEnable(GL_POLYGON_SMOOTH);

  for (i=0;i<TOPN;i++) {
    topnames[i]=makeString(20);
    strcpy(topnames[i],(i%2)?"george":"jenny");
    topscores[i]=(TOPN-i)*(TOPN-i)*1000;
    toplevels[i]=0;
  }
  readScores();
  startSound(SOUND_WELCOME);
}

void windowResize(int w, int h) {
  glutSetWindow(mainWindowId);
  glViewport(0,0,w,h);
  if (w != WIDTH || h != HEIGHT) {
    glutReshapeWindow(WIDTH,HEIGHT);
  }
  else {
    //glutSetWindow(blockWindowId);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(80,(float)WIDTH/(float)HEIGHT,1.0,5000.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
  }
}

int main (int argc, char **argv) {
  int i,error;
  alutInit(0,NULL); // init openAL
  alGetError(); // clear error code
  alGenBuffers(NUM_BUFFERS,buffers); // create buffers
  if ((error = alGetError()) != AL_NO_ERROR) { 
    displayOpenALError("alutUnloadWAV :", error);
    return 0;
  }
  alGenSources(NUM_SOURCES,sources); // create sources
  if ((error = alGetError()) != AL_NO_ERROR) { 
    displayOpenALError("alutUnloadWAV :", error);
    return 0;
  }

  
  // Load in the WAV and store it in a buffer, then make the source and link 'em
  for (i=0;i<NUM_BUFFERS;i++) {
    if (!LoadAndAssignWAV(sounds[i], buffers[i])) {
      // Error loading in the WAV so quit
      printf("Unable to find file: '%s'\n",sounds[i]);
      alDeleteBuffers(NUM_BUFFERS, buffers); 
      return 0;
    }
    alSourcef(sources[i], AL_PITCH, 1.0f);
    alSourcef(sources[i], AL_GAIN, 1.0f);
    alSourcefv(sources[i], AL_POSITION, source0Pos);
    alSourcefv(sources[i], AL_VELOCITY, source0Vel);
    alSourcei(sources[i], AL_BUFFER,buffers[i]);
    alSourcei(sources[i], AL_LOOPING, AL_FALSE);
  }
  //override above defaults
  alSourcei(sources[3],AL_LOOPING,AL_TRUE); // water loops

  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
  glutInitWindowPosition(100,100); //TODO: CENTER?
  glutInitWindowSize(WIDTH,HEIGHT);
  mainWindowId = glutCreateWindow(PROGRAM_NAME);

  glShadeModel(GL_SMOOTH);
  glClearColor(0.5f,0.5f,0.55f,0.0f);

  fallingupInit();

  //glutIgnoreKeyRepeat(1);
  glutKeyboardFunc(processNormalKeys);
  glutSpecialFunc(processSpecialKeys);
  //glutSpecialUpFunc(releaseKey);

  glutDisplayFunc(renderScene);
  glutIdleFunc(renderScene);
  glutReshapeFunc(windowResize);

  glutMainLoop();

  //wcl.hIcon = LoadIcon(NULL, IDI_APPLICATION); /* standard icon */
  //wcl.hIconSm = LoadIcon(NULL, IDI_WINLOGO); /* small icon */
  //wcl.hCursor = LoadCursor(NULL, IDC_ARROW); /* cursor style */
  //wcl.lpszMenuName = "Tetris"; /* NULL if no menu */
  /*load menu accelerators*/
  //hAccel = LoadAccelerators(hThisInst, "Tetris");

  return(0);
}
