1: /* File: snake3d.c
     2:    Submitted as part of Project 4 for EECS 672, Spring 2007
     3:    by Douglas McClendon, KUID 0536810
     4: */
     5: 
     6: 
     7: /*
     8:  * snake3d.c
     9:  *
    10:  * 3-D snake game, inspired by the presumed unoriginal
    11:  * snake game on my nokia 5190
    12:  *
    13:  * Doug McClendon
    14:  */
    15: 
    16: /*
    17:  * Headers
    18:  */
    19: #include <stdlib.h> 
    20: #include <GL/glut.h> 
    21: #include <GL/gl.h> 
    22: #include <sys/time.h> 
    23: #include <unistd.h> 
    24: #include <sys/ioctl.h> 
    25: #include <sys/types.h> 
    26: #include <fcntl.h> 
    27: #include <stdio.h> 
    28: #include <errno.h> 
    29: #include <string.h> 
    30: #include <linux/joystick.h> 
    31: 
    32: #include "TexFont.h" 
    33: #include "fbn.h" 
    34: #include "dmcgl.h" 
    35: #include "snake3d.h" 
    36: 
    37: /*
    38:  * fbnSound
    39:  */
    40: #define SOUNDSET_MUNCH_FOOD 0 
    41: #define SOUNDSET_REPLACE_FOOD 1  
    42: #define SOUNDSET_GROW_SNAKE 2  
    43: #define SOUNDSET_GAME_OVER 3  
    44: #define SOUNDSET_NEW_GAME 4  
    45: #define SOUNDSET_WINNER 5  
    46: // HACK PACKAGING should probably go in PREFIX/lib/snake3d/*
    47: SoundSet soundset = {6,
    48: 		       "./media/munch_food2.wav", NULL, 0,
    49: 		       "./media/replace_food.snd", NULL, 0,
    50: 		       "./media/grow_snake.snd", NULL, 0,
    51: 		       "./media/game_over.snd", NULL, 0,
    52: 		       "./media/new_game.snd", NULL, 0,
    53: 		       "./media/winner.snd", NULL, 0};
    54: 
    55: /*
    56:  * Globals
    57:  */
    58: 
    59: JoystickState jstate;
    60: 
    61: Game game;
    62: GameHistory history;
    63: 
    64: Camera camera = {50.0, 50.0, 75.0f, 
    65: 		 50.0, 50.0, 0.0,
    66: 		 0.0, 1.0, 0.0};
    67: Camera txt_camera = {0.0, 0.0, 75.0f, 
    68: 		 0.0, 0.0, 0.0,
    69: 		 0.0, 1.0, 0.0};
    70: 
    71: // textures
    72: GLuint texSnakeSkin;
    73: GLuint texGrass;
    74: GLuint texMarble;
    75: 
    76: // fonts
    77: TexFont *txf;
    78: 
    79: /*
    80:  * Main 
    81:  */
    82: int main(int argc, char *argv[])
    83: {
    84:   // main: the game of snake in 3D 
    85: 
    86:   int i;
    87: 
    88:   /* 
    89:    * Set initial values for globals
    90:    */
    91:   game.previous_state = OFF;
    92:   game.mainmenu = MENU_CLASSIC;
    93: 
    94:   /* 
    95:    * Set defaults for options
    96:    */
    97:   game.options.classic.init_snake_length = 9;
    98:   game.options.classic.init_snake_belly = 0;
    99:   game.options.classic.game_width = 20;
   100:   game.options.classic.game_height = 11;
   101:   game.options.classic.food_amount = 1;
   102:   game.options.classic.timeslice = 333333;
   103: 
   104:   game.options.vs.init_snake_length = 13;
   105:   game.options.vs.init_snake_belly = 0;
   106:   game.options.vs.game_width = 40;
   107:   game.options.vs.game_height = 22;
   108:   game.options.vs.food_amount = 3;
   109:   game.options.vs.timeslice = 333333;
   110: 
   111:   game.options.quest.timeslice = 333333;
   112:   game.options.quest.level1.length = 9;
   113:   game.options.quest.level1.food = 3;
   114:   game.options.quest.level1.width = 20;
   115:   game.options.quest.level1.height = 11;
   116:   game.options.quest.level2.length = 18;
   117:   game.options.quest.level2.food = 3;
   118:   game.options.quest.level2.width = 40;
   119:   game.options.quest.level2.height = 22;
   120: 
   121:   game.options.win_width = 640;
   122:   game.options.win_height = 480;
   123:   // 672p4 - don't try to use sound by default
   124:   // game.options.use_sound = 1;
   125:   game.options.use_sound = 0;
   126:   game.options.fullscreen = 0;
   127:   // HACK HACK HACK change default upon initial release
   128:   //  game.options.show_fps = 0;
   129:   game.options.show_fps = 1;
   130: 
   131:   // HACK HACK HACK get rid of this
   132:   game.options.num_snakes = 1;
   133: 
   134:   // QUESTION(C compiler): I tried color[0] = {255, 0, 0, 0} 
   135:   // but got a syntax error, why?  And why does it work in C++?
   136:   game.options.snake_segment_color[0][0] = 255;
   137:   game.options.snake_segment_color[0][1] = 0;
   138:   game.options.snake_segment_color[0][2] = 0;
   139:   game.options.snake_segment_color[0][3] = 0;
   140:   game.options.snake_segment_color[1][0] = 0;
   141:   game.options.snake_segment_color[1][1] = 255;
   142:   game.options.snake_segment_color[1][2] = 0;
   143:   game.options.snake_segment_color[1][3] = 0;
   144: 
   145:   /* 
   146:    * Parse arguments
   147:    */
   148:   for(i = 1; i < argc; ++i){
   149:     if (argv[i][0] == '-'){
   150:       switch (argv[i][1]){
   151:       case 'w': // game width 
   152: 	if (++i >= argc) Usage();
   153: 	game.options.classic.game_width = atoi(argv[i]);
   154: 	break;
   155:       case 'h': // game height
   156: 	if (++i >= argc) Usage();
   157: 	game.options.classic.game_height = atoi(argv[i]);
   158: 	break;
   159:       case 'l': // snake length
   160: 	if (++i >= argc) Usage();
   161: 	game.options.classic.init_snake_length = atoi(argv[i]);
   162: 	if(game.options.classic.init_snake_length < 2) Usage();
   163: 	break;
   164:       case 't': // time slice
   165: 	if (++i >= argc) Usage();
   166: 	game.options.classic.timeslice = atoi(argv[i]);
   167: 	break;
   168:       case 's': // disable sound 
   169: 	game.options.use_sound = 0;
   170: 	break;
   171:       case 'f': // fullscreen 
   172: 	game.options.fullscreen = 1;
   173: 	break;
   174:       case 'r': // frameRate
   175: 	game.options.show_fps = !game.options.show_fps;
   176: 	break;
   177:       default:
   178: 	Usage();
   179: 	break;
   180:       }
   181:     } else { 
   182: 	/* no non-dashed argments are used by snake3d */
   183:     }
   184:   }
   185: 
   186:   // Initialize window in windowing environment (glut)
   187:   glutInit(&argc, argv);
   188:   glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE);
   189:   glutInitWindowPosition(0, 0);
   190:   glutInitWindowSize(game.options.win_width, game.options.win_height);
   191:   glutCreateWindow("Snake3D");
   192:   if(game.options.fullscreen) glutFullScreen();
   193: 
   194:   // NOTE: reshape is called once initially when window created, and once 
   195:   // whenever window is resized 
   196:   glutReshapeFunc(reshape); 
   197: 
   198:   // special just translates to 'normal' keys and then invokes key_* 
   199:   glutSpecialFunc(special);
   200: 
   201:   // initialize sound engine
   202:   if(game.options.use_sound){
   203:     fbnSoundInit();
   204:     fbnSoundLoadData(&soundset);
   205:   }
   206: 
   207:   // initialize font engine (only using 1 font)
   208:   // HACK PACKAGING should probably go in PREFIX/lib/snake3d/font1.txf
   209:   txf = txfLoadFont("./media/font1.txf");
   210:   if(txf == NULL){
   211:     fprintf(stderr, "Problem loading %s, %s\n", 
   212: 	    "./media/font1.txf", txfErrorString());
   213:     exit(1);
   214:   }
   215:   txfEstablishTexture(txf, 0, GL_TRUE);
   216: 
   217:   // initialize joystick engine 
   218:   JoystickInit();
   219: 
   220:   // load hi scores and other data from "the past"
   221:   ReadGameHistory();
   222: 
   223:   // initialize OpenGL stuff
   224:   GraphicsInit();
   225: 
   226:   // initialize misc stuff (currently just fps memory allocation)
   227:   S3DInit();
   228: 
   229:   // initialize game.state and let the state machine and event processing loop 
   230:   // take care of everything else
   231:   SetState(MENU);
   232: 
   233:   glutMainLoop();
   234: 
   235:   return 0;             /* ANSI C requires main to return int. */
   236: }
   237: 
   238: /*
   239:  * Functions
   240:  */
   241: 
   242: void GraphicsInit(void){
   243:   // initialize rendering system
   244: 
   245:   int i;
   246:   int texwidth, texheight;
   247:   GLubyte *texture;
   248: 
   249:   GLfloat light_ambient[] = {0.2, 0.2, 0.2, 1.0};
   250:   GLfloat light_diffuse[] = {0.8, 0.8, 0.8, 1.0};
   251:   GLfloat light_specular[] = {0.8, 0.8, 0.8, 1.0};
   252:   GLfloat light_position[] = {50.0, 50.0, 50.0, 0.0}; 
   253:   GLfloat mat_specular[] = {0.8, 0.8, 0.8, 1.0};
   254:   GLfloat mat_shininess[] = {50.0};
   255: 
   256: 
   257:   // Duh... (is it this by default?)
   258:   glShadeModel(GL_SMOOTH);
   259: 
   260:   //
   261:   // Initialize depth testing
   262:   //
   263:   glEnable(GL_DEPTH_TEST); 
   264:   glDepthMask(GL_TRUE);
   265: 
   266:   //
   267:   // Initialize lighting
   268:   // (note initial values above)
   269:   //
   270:   glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
   271:   glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
   272:   glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
   273:   glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
   274:   glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
   275:   glLightfv(GL_LIGHT0, GL_POSITION, light_position);
   276: 
   277:   glEnable(GL_LIGHTING); 
   278:   glEnable(GL_LIGHT0); 
   279: 
   280:   glEnable(GL_COLOR_MATERIAL);
   281: 
   282:   //
   283:   // Initialize Textures
   284:   //
   285: 
   286:   // initialize snakeskin texture
   287:   read_ppm_to_buffer(&texture,&texwidth,&texheight, "./media/snakeskin.ppm");
   288:   glGenTextures(1, &texSnakeSkin);
   289:   glBindTexture(GL_TEXTURE_2D, texSnakeSkin);
   290:   glTexImage2D(GL_TEXTURE_2D, 0,GL_RGBA,texwidth,texheight,0,GL_RGBA,\
   291: 	       GL_UNSIGNED_BYTE,texture);
   292:   gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA, texwidth, texheight, \
   293: 		    GL_RGBA, GL_UNSIGNED_BYTE, texture);
   294: 
   295:   // initialize grass texture
   296:   read_ppm_to_buffer(&texture,&texwidth,&texheight, "./media/grass.ppm");
   297:   glGenTextures(1, &texGrass);
   298:   glBindTexture(GL_TEXTURE_2D, texGrass);
   299:   glTexImage2D(GL_TEXTURE_2D, 0,GL_RGBA,texwidth,texheight,0,GL_RGBA,\
   300: 	       GL_UNSIGNED_BYTE,texture);
   301:   gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA, texwidth, texheight, \
   302: 		    GL_RGBA, GL_UNSIGNED_BYTE, texture);
   303: 
   304:   // initialize marble texture
   305:   read_ppm_to_buffer(&texture,&texwidth,&texheight, "./media/marble.ppm");
   306:   glGenTextures(1, &texMarble);
   307:   glBindTexture(GL_TEXTURE_2D, texMarble);
   308:   glTexImage2D(GL_TEXTURE_2D, 0,GL_RGBA,texwidth,texheight,0,GL_RGBA,\
   309: 	       GL_UNSIGNED_BYTE,texture);
   310:   gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA, texwidth, texheight, \
   311: 		    GL_RGBA, GL_UNSIGNED_BYTE, texture);
   312: 
   313:   //
   314:   // Initialize the texture environment that we will always use
   315:   //
   316:   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
   317:   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
   318:   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
   319:   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
   320:   // NOTE:  REPLACE is what I thought DECAL was, driver is not broken
   321:   glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
   322: }
   323: 
   324: void S3DInit(void){
   325:   // S3DInit: initialize (non graphics, non engine) application stuff
   326: 
   327:   int i;
   328: 
   329:   if(!(game.fps_history = malloc(FPS_ARRAY_SIZE * sizeof(int)))){
   330:     debug("failed to malloc fps_history\n");
   331:     exit(1);
   332:   }
   333: 
   334:   for(i=0; i<FPS_ARRAY_SIZE; i++) game.fps_history[i] = FPS_INIT_VALUE;
   335:   
   336:   game.fps_history_total = FPS_ARRAY_SIZE * FPS_INIT_VALUE;
   337:   game.fps_history_pointer = 0;
   338:   game.fps_recent_worst_value = FPS_INIT_VALUE;
   339:   game.fps_recent_worst_index = 0;
   340: }
   341: 
   342: void SetState(GameState new_state)
   343: {
   344:   // SetState: Whenever an event occurs that necessitates a change
   345:   //     in the game state, the initial point of entry to a new state is 
   346:   //     in a switch statement of this function.
   347: 
   348:   switch(new_state){
   349:   case INSTRUCTIONS:
   350:     glutDisplayFunc(draw_nothing);
   351:     glutIdleFunc(idle_shared);
   352:     glutKeyboardFunc(key_shared);
   353:     break;
   354:   case MENU:
   355:     glutDisplayFunc(draw_menu);
   356:     glutIdleFunc(idle_shared);
   357:     glutKeyboardFunc(key_menu);
   358:     break;
   359:   case GAME_CLASSIC :
   360:     // a new gametick timer call
   361:     glutTimerFunc((int)(game.timeslice / 1000), timer_classic, 0);
   362:     glutDisplayFunc(draw_game);
   363:     glutIdleFunc(idle_shared);
   364:     glutKeyboardFunc(key_game_shared);
   365:     SetTimeIfUnpausing();
   366:     break;
   367:   case GAME_VS:
   368:     // a new gametick timer call
   369:     glutTimerFunc((int)(game.timeslice / 1000), timer_vs, 0);
   370:     glutDisplayFunc(draw_game);
   371:     glutIdleFunc(idle_shared);
   372:     glutKeyboardFunc(key_game_shared);
   373:     SetTimeIfUnpausing();
   374:     break;
   375:   case GAME_QUEST_LEVEL1:
   376:     // a new gametick timer call
   377:     glutTimerFunc((int)(game.timeslice / 1000), timer_quest_level1, 0);
   378:     glutDisplayFunc(draw_game);
   379:     glutIdleFunc(idle_shared);
   380:     glutKeyboardFunc(key_game_shared);
   381:     SetTimeIfUnpausing();
   382:     break;
   383:   case GAME_QUEST_LEVEL2:
   384:     // record the score from the first level
   385:     game.statedata.quest.score += game.snake[0].length;
   386:     // a new gametick timer call
   387:     glutTimerFunc((int)(game.timeslice / 1000), timer_quest_level2, 0);
   388:     glutDisplayFunc(draw_game);
   389:     glutIdleFunc(idle_shared);
   390:     glutKeyboardFunc(key_game_shared);
   391:     SetTimeIfUnpausing();
   392:     break;
   393:   case GAME_PAUSED:
   394:     // ASSUMPTION: the current gamestate is GAME_*
   395:     SetTimePausing();
   396:     glutDisplayFunc(draw_game_paused);
   397:     glutIdleFunc(idle_shared);
   398:     glutKeyboardFunc(key_game_paused);
   399:     break;
   400:   case GAME_OVER:
   401:     switch(game.state){
   402:     case GAME_CLASSIC:
   403:       // yes this line of code is unnecessary except to maintain a style
   404:       game.statedata.classic.score = game.snake[0].length;
   405:       if(game.statedata.classic.score >= 
   406: 	 history.classic_hiscores[NUM_HI_SCORES - 1].score){
   407: 	if(game.options.use_sound) fbnSoundPlay(SOUNDSET_WINNER, &soundset); 
   408: 	game.statedata.enter_initials.score = game.statedata.classic.score;
   409: 	game.statedata.enter_initials.gametype = CLASSIC;
   410: 	strcpy(game.statedata.enter_initials.gametype_string, "Classic");
   411: 	game.statedata.enter_initials.hiscores = history.classic_hiscores;
   412: 	game.statedata.enter_initials.place = InsertNewHiScore();
   413:       } else {
   414: 	if(game.options.use_sound) fbnSoundPlay(SOUNDSET_GAME_OVER, &soundset);
   415:       }
   416:       break;
   417:     case GAME_VS:
   418:       // HACK should come up with something better, such as "P1 blew it but
   419:       // P1 died with a longer snake"
   420:       if(game.options.use_sound) fbnSoundPlay(SOUNDSET_GAME_OVER, &soundset);
   421:       break;
   422:     case GAME_QUEST_LEVEL2:
   423:       game.statedata.quest.score += game.snake[0].length;
   424:       if(game.statedata.quest.score >= 
   425: 	 history.quest_hiscores[NUM_HI_SCORES - 1].score){
   426: 	if(game.options.use_sound) fbnSoundPlay(SOUNDSET_WINNER, &soundset); 
   427: 	game.statedata.enter_initials.score = game.statedata.quest.score;
   428: 	game.statedata.enter_initials.gametype = QUEST;
   429: 	strcpy(game.statedata.enter_initials.gametype_string, "Quest");
   430: 	game.statedata.enter_initials.hiscores = history.quest_hiscores;
   431: 	game.statedata.enter_initials.place = InsertNewHiScore();
   432:       } else {
   433: 	if(game.options.use_sound) fbnSoundPlay(SOUNDSET_GAME_OVER, &soundset);
   434:       }
   435:       break;
   436:     }
   437:     glutDisplayFunc(draw_game_over);
   438:     glutIdleFunc(idle_shared);
   439:     glutKeyboardFunc(key_game_over);
   440:     break;
   441:   case ENTER_INITIALS:
   442:     game.statedata.enter_initials.current_initial = 0;
   443:     glutDisplayFunc(draw_enter_initials);
   444:     glutIdleFunc(idle_shared);
   445:     glutKeyboardFunc(key_enter_initials);
   446:     break;
   447:   case HI_SCORES:
   448:     // record starttime for animations
   449:     gettimeofday(&game.statedata.hi_scores.starttime, NULL);
   450:     // if a hi score was recently entered, start the display with the
   451:     // corresponding gametype
   452:     if(game.statedata.enter_initials.place){
   453:        switch(game.statedata.enter_initials.gametype){
   454:        case CLASSIC:
   455: 	 // classic is the default first hiscore screen
   456: 	 break;
   457:        case QUEST:
   458: 	 game.statedata.hi_scores.starttime.tv_sec -= HI_SCORE_CYCLE / 2;
   459: 	 break;
   460:        default:
   461: 	 output("assertion failed in SetState(HI_SCORES) gametype was %d\n",
   462: 		game.statedata.enter_initials.gametype);
   463:        }
   464:     }
   465:     glutDisplayFunc(draw_hi_scores);
   466:     glutIdleFunc(idle_shared);
   467:     glutKeyboardFunc(key_hi_scores);
   468:     break;
   469:   case OFF:
   470:     // shut down joystick engine
   471:     JoystickFini();
   472: 
   473:     // shut down font engine
   474:     txfUnloadFont(txf);
   475: 
   476:     // shut down sound engine
   477:     if(game.options.use_sound){
   478:       fbnSoundUnloadData(&soundset);
   479:       fbnSoundFini();
   480:     }
   481: 
   482:     // and begone...
   483:     exit(0);
   484:     break;
   485:   default:
   486:     // error
   487:     debug("Entering unknown game state %d\n", new_state);
   488:     debug("previous state was %d\n", game.state);
   489:     exit(1);
   490:     break;
   491:   }
   492: 
   493:   // set new state 
   494:   game.previous_state = game.state;
   495:   game.state = new_state;
   496: 
   497:   glutPostRedisplay();
   498: }
   499: 
   500: void SetTimePausing(void){
   501:   // SetTimeIfPausing: called when changing state to paused.  This function 
   502:   //     saves the timestate which will be restored when unpausing
   503: 
   504:   struct timeval temptime;
   505: 
   506:   gettimeofday(&temptime, NULL);
   507:   game.time_of_last_gametick.tv_usec = 
   508:     TIMEDIFF(temptime, game.time_of_last_gametick);
   509: }
   510: 
   511: void SetTimeIfUnpausing(void){
   512:   // SetTimeIfUnpausing: called from every state change to a game state.  If 
   513:   //     the previous state was paused, fix the timer stuff
   514: 
   515:   struct timeval temptime;
   516: 
   517:   if(game.state == GAME_PAUSED){
   518:     gettimeofday(&temptime, NULL);
   519:     game.time_of_last_gametick.tv_usec = 
   520:       TIMEDIFF(temptime, game.time_of_last_gametick);
   521:   }
   522: }
   523: 
   524: void draw_nothing(void)
   525: {
   526:   // draw_nothing: placeholder, draw a blank screen
   527: 
   528:   // viewing transformation 
   529:   glLoadIdentity();
   530:   // gluLookAt 
   531:   gluLookAt(camera.eyex, camera.eyey, camera.eyez,
   532: 	    camera.centerx, camera.centery, camera.centerz,
   533: 	    camera.upx, camera.upy, camera.upz);
   534:  
   535:   // clear the frame 
   536:   glClearColor(0.0, 0.0, 0.0, 0.0);
   537:   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   538: 
   539:   // thats it
   540:   glutSwapBuffers();
   541: }
   542: 
   543: void draw_game(void)
   544: {
   545:   // draw_game: render a "normal" game screen, i.e. during play.
   546: 
   547:   struct timeval newtime;
   548:   long game_usecdiff;
   549:   long previous_render_sec, previous_render_usec;
   550:   float partial_factor;
   551:   int i,j;
   552: 
   553:   ++game.frames_rendered;
   554: 
   555:   // What time is it? 
   556:   gettimeofday(&newtime, NULL);
   557: 
   558:   // Get time since gametick for partial movement rendering
   559:   
   560:   /*
   561:   HACK HACK HACK, why are the MIN's necessary here, I can understand missing 
   562:   the gametick by a <1% of a timeslice, but during pause it is often >25% of 
   563:   a timeslice over???  Though it does seem to only matter for GAME_PAUSED 
   564:   (I suspect I could visually see the problem if not)  
   565: 
   566:   AS OF 1-3-00 I EVEN SEE THE PROBLEM IN THE GAME PART OF THE FOLLOWING IF 
   567:   STATEMENT.  I NEED TO RESEARCH WTF IS GOING ON HERE.  I think the game 
   568:   could be made to run much more smoothly.
   569: 
   570:   AS OF 7-19-00 I still see the problem
   571:   */
   572: 
   573:   if(game.state == GAME_PAUSED){
   574:     game_usecdiff = MIN(game.time_of_last_gametick.tv_usec, 
   575: 			game.timeslice);
   576:     //    game_usecdiff = game.time_of_last_gametick.tv_usec;
   577:   } else if(game.state == GAME_OVER){
   578:     // gameover happens at the end of a cycle, because the next cycle would
   579:     // have caused an illegal move
   580:     game_usecdiff = game.timeslice;
   581:   } else { // THE normal case of GAME_CLASSIC or GAME_VS
   582:     game_usecdiff = MIN(TIMEDIFF(newtime, game.time_of_last_gametick), 
   583:         			game.timeslice);
   584:     //    game_usecdiff = TIMEDIFF(newtime, game.time_of_last_gametick);
   585:   }
   586: 
   587:   // Calculate partial factor
   588:   partial_factor = (float)game_usecdiff / (float)game.timeslice;
   589: 
   590:   // fps calculations
   591:   if(game.options.show_fps){
   592:     game.fps_last_frame = TIMEDIFF(newtime, game.time_of_last_render);
   593:     if(!game.fps_last_frame){
   594:       // rendering time should never have really been 0
   595:       game.fps_last_frame = 666;
   596:       debug("fps_last_frame 666\n");
   597:     } else {
   598:       game.fps_last_frame = 1000000 / game.fps_last_frame;
   599:     }
   600:     game.fps_history_total -= game.fps_history[game.fps_history_pointer];
   601:     game.fps_history_total += game.fps_last_frame;
   602:     // do we need to set a new recent_worst, or expire the old one, or not
   603:     if((game.fps_last_frame < game.fps_recent_worst_value)||
   604:        (game.fps_history[game.fps_history_pointer] ==  
   605: 	game.fps_recent_worst_value)){
   606:       game.fps_recent_worst_value = game.fps_last_frame;
   607:       game.fps_recent_worst_index = game.fps_history_pointer;
   608:     }
   609:     game.fps_history[game.fps_history_pointer] = game.fps_last_frame;
   610:     game.fps_history_pointer = (game.fps_history_pointer + 1) % FPS_ARRAY_SIZE;
   611:   }  
   612: 
   613:   // Get time since last render for partial movement rendering
   614:   game.time_of_last_render = newtime;
   615: 
   616:   // viewing transformation 
   617:   glLoadIdentity();
   618:   // gluLookAt 
   619:   gluLookAt(camera.eyex, camera.eyey, camera.eyez,
   620: 	    camera.centerx, camera.centery, camera.centerz,
   621: 	    camera.upx, camera.upy, camera.upz);
   622:  
   623:   // clear the frame 
   624:   glClearColor(0.0, 0.0, 0.0, 0.0);
   625:   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   626: 
   627:   glTranslatef((SCREEN_WIDTH - game.render_width) / 2.0f, 
   628: 	       (SCREEN_HEIGHT - game.render_height) / 2.0f, 
   629: 	       0.0f);
   630: 
   631:   draw_arena();
   632: 
   633:   for(i=0; i<MAX_SNAKES; i++){
   634:     if(game.snake[i].active){
   635:       draw_snake(i, partial_factor);
   636:     }
   637:   }
   638: 
   639:   draw_food();
   640:  
   641:   draw_text();
   642: 
   643:   glutSwapBuffers();
   644: 
   645:   // draw as often as possible
   646:   glutPostRedisplay();
   647: }
   648: 
   649: void draw_game_paused(void)
   650: {
   651:   // draw_game_paused: currently, we are boring and just show the frozen
   652:   //     normal image.
   653: 
   654:   draw_game();
   655: }
   656: 
   657: void draw_menu(void)
   658: {
   659:   // draw_menu: draw a menu when in MENU gamestate.  Keystrokes are
   660:   //     being waited for to trigger state change functions.
   661: 
   662:   char screen_string[1024];
   663:   char temp_string[1024];
   664:   int i;
   665:   MainMenuState menustate;
   666:   float r, g, b, w, h;
   667:   float zoom;
   668:   float delta;
   669: 
   670:   // clear the frame 
   671:   glClearColor(0.0, 0.0, 0.0, 0.0);
   672:   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   673: 
   674:   // viewing transformation 
   675:   glLoadIdentity();
   676:   // gluLookAt 
   677:   gluLookAt(50.0f, 50.0f, 50.0f,
   678: 	    50.0f, 50.0f, 0.0f,
   679:   	    0.0f, 1.0f, 0.0f);
   680: 
   681:   // Draw the background texture
   682:   zoom = 5.0f;
   683:   glEnable(GL_TEXTURE_2D);
   684:   glBindTexture(GL_TEXTURE_2D, texGrass);
   685:   glBegin(GL_POLYGON);
   686:     glColor3f(1.0f, 1.0f, 1.0f);
   687:     glNormal3f(0.0f, 0.0f, 1.0f);
   688:     glTexCoord2f(0.0f, 0.0f);
   689:     glVertex3f(0.0f, 0.0f, 0.0f);
   690:     glTexCoord2f(zoom, 0.0f);
   691:     glVertex3f(100.0f, 0.0f, 0.0f);
   692:     glTexCoord2f(zoom, zoom);
   693:     glVertex3f(100.0f, 100.0f, 0.0f);
   694:     glTexCoord2f(0.0f, zoom);
   695:     glVertex3f(0.0f, 100.0f, 0.0f);
   696:   glEnd();
   697:   glDisable(GL_TEXTURE_2D);
   698: 
   699:   fbnTxfGLStateSet();
   700: 
   701:   // text: Snake3d
   702:   /*
   703:   glDisable(GL_DEPTH_TEST);
   704:   glDisable(GL_TEXTURE_2D);
   705:   glBegin(GL_POLYGON);
   706:     glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
   707:     glVertex3f(0.0f, 0.0f, 0.0f);
   708:     glVertex3f(50.0f, 0.0f, 0.0f);
   709:     glVertex3f(50.0f, 100.0f, 0.0f);
   710:     glVertex3f(0.0f, 100.0f, 0.0f);
   711:   glEnd();
   712:   glEnable(GL_TEXTURE_2D);
   713:   glEnable(GL_DEPTH_TEST);
   714:   */
   715:   fbnTxfRenderStringExtruded("Snake3D", 0, 50.0f, 85.0f, 60.0f, 20.0f,
   716: 			    2.0f, 2.0f, 10,
   717: 			    0.2f, 0.9f, 0.2f, 1.0f,
   718: 			    0.2f, 0.0f, 0.2f, 1.0f, txf);
   719: 
   720:   for(menustate=0; menustate<MAINMENUSTATES; menustate++){
   721:     switch(menustate){
   722:     case MENU_INSTRUCTIONS:
   723:       strcpy(temp_string, "Instructions");
   724:       break;
   725:     case MENU_CLASSIC:
   726:       strcpy(temp_string, "Classic");
   727:       break;
   728:     case MENU_VS:
   729:       strcpy(temp_string, "VS");
   730:       break;
   731:     case MENU_QUEST:
   732:       strcpy(temp_string, "Quest");
   733:       break;
   734:     case MENU_HI_SCORES:
   735:       strcpy(temp_string, "Hi Scores");
   736:       break;
   737:     case MENU_QUIT:
   738:       strcpy(temp_string, "Quit");
   739:       break;
   740:     default:
   741:       debug("Error, unknown menustate\n");
   742:       exit(1);
   743:     }
   744:     w = 4.0f * strlen(temp_string);
   745:     h = 40.0f / (MAINMENUSTATES * 1.2f);
   746:     r = 0.7f; g = 0.7f; b = 0.7f;
   747:     if(game.mainmenu == menustate){
   748:       r = 1.0f; g = 0.0f; b = 0.0f;
   749:       w = w * 1.25f;
   750:       h = h * 1.25f;
   751:     }
   752:     fbnTxfRenderString(temp_string, 0,
   753:     		       50.0f, 60.0f - (menustate * 
   754:     				       (50.0f / (MAINMENUSTATES - 1))), 
   755:     		       w, h, r, g, b, 1.0f, txf);
   756:   }
   757: 
   758:   fbnTxfGLStateUnset();
   759: 
   760:   glutSwapBuffers();
   761: 
   762:   // draw as often as possible
   763:   glutPostRedisplay();
   764: }
   765: 
   766: void draw_game_over(void)
   767: {
   768:   // draw_game_over: for now, be boring and just draw the frozen
   769:   //     normal game screen.  
   770: 
   771:   draw_game();
   772: }
   773: 
   774: void draw_enter_initials(void)
   775: {
   776:   // draw_enter_initials
   777: 
   778:   float bg_tex_zoom;
   779:   float tx, ty, tw, th;
   780:   float r, g, b, a;
   781:   char screen_string[256];
   782:   char place_string[8];
   783:   char gametype_string[256];
   784:   int i;
   785: 
   786:   // clear the frame 
   787:   glClearColor(0.0, 0.0, 0.0, 0.0);
   788:   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   789: 
   790:   // viewing transformation 
   791:   glLoadIdentity();
   792:   // gluLookAt 
   793:   gluLookAt(txt_camera.eyex, txt_camera.eyey, txt_camera.eyez,
   794: 	    txt_camera.centerx, txt_camera.centery, txt_camera.centerz,
   795: 	    txt_camera.upx, txt_camera.upy, txt_camera.upz);
   796: 
   797:   // Draw the background texture
   798:   glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
   799:   bg_tex_zoom = 3.0f;
   800:   glEnable(GL_TEXTURE_2D);
   801:   glBindTexture(GL_TEXTURE_2D, texGrass);
   802:   glBegin(GL_POLYGON);
   803:     glColor3f(1.0f, 1.0f, 1.0f);
   804:     glNormal3f(0.0f, 0.0f, 1.0f);
   805:     glTexCoord2f(0.0f, 0.0f);
   806:     glVertex3f(-SCREEN_WIDTH, -SCREEN_HEIGHT, 0.0f);
   807:     glTexCoord2f(bg_tex_zoom, 0.0f);
   808:     glVertex3f(SCREEN_WIDTH, -SCREEN_HEIGHT, 0.0f);
   809:     glTexCoord2f(bg_tex_zoom, bg_tex_zoom);
   810:     glVertex3f(SCREEN_WIDTH, SCREEN_HEIGHT, 0.0f);
   811:     glTexCoord2f(0.0f, bg_tex_zoom);
   812:     glVertex3f(-SCREEN_WIDTH, SCREEN_HEIGHT, 0.0f);
   813:   glEnd();
   814:   glDisable(GL_TEXTURE_2D);
   815: 
   816:   // start drawing text
   817:   fbnTxfGLStateSet();
   818: 
   819:   // message: New Hi Score
   820:   fbnTxfRenderString("New Hi Score", 0, 
   821: 		      50.0f, 85.0f, 90.0f, 20.0f,
   822: 		      0.1f, 0.9f, 0.1f, 1.0f, txf);
   823: 
   824:   // message: gametype, score, rank
   825:   fbnSetPlaceString(place_string, game.statedata.enter_initials.place);
   826:   sprintf(screen_string, "Game: %s   Score: %3d   Rank:%5s",
   827: 	  game.statedata.enter_initials.gametype_string, 
   828: 	  game.statedata.enter_initials.score, place_string);
   829:   fbnTxfRenderString(screen_string, 0, 
   830: 		      50.0f, 65.0f, 90.0f, 8.0f,
   831: 		      0.9f, 0.1f, 0.1f, 1.0f, txf);
   832: 
   833:   // message: current initials
   834:   for(i=0; i<3; i++){
   835:     sprintf(screen_string, "%c", 
   836: 	    game.statedata.enter_initials.hiscores[game.statedata.enter_initials.place - 1].initials[i]);
   837:     if(game.statedata.enter_initials.current_initial == i){
   838:       r = 0.9f; g = 0.1f, b = 0.1f, a = 1.0f;
   839:     } else {
   840:       r = 0.9f; g = 0.1f, b = 0.9f, a = 1.0f;
   841:     }
   842:     fbnTxfRenderString(screen_string, 0, 
   843: 		       20.0f + i * 20.0f, 40.0f, 20.0f, 30.0f,
   844: 		       r, g, b, a, txf);
   845:   }
   846: 
   847:   // message: end
   848:   if(game.statedata.enter_initials.current_initial == 3){
   849:     r = 0.9f; g = 0.1f, b = 0.1f, a = 1.0f;
   850:   } else {
   851:     r = 0.9f; g = 0.1f, b = 0.9f, a = 1.0f;
   852:   }
   853:   fbnTxfRenderString("D", 0, 
   854: 		    72.5f, 47.5f, 5.0f, 5.0f,
   855: 		    r, g, b, a, txf);
   856:   fbnTxfRenderString("o", 0, 
   857: 		    77.5f, 42.5f, 5.0f, 5.0f,
   858: 		    r, g, b, a, txf);
   859:   fbnTxfRenderString("n", 0, 
   860: 		    82.5f, 37.5f, 5.0f, 5.0f,
   861: 		    r, g, b, a, txf);
   862:   fbnTxfRenderString("e", 0, 
   863: 		    87.5f, 32.5f, 5.0f, 5.0f,
   864: 		    r, g, b, a, txf);
   865: 
   866:   
   867:   // message: 
   868:   fbnTxfRenderString("enter initials using snake controls", 0, 
   869: 		      50.0f, 10.0f, 90.0f, 10.0f,
   870: 		      0.9f, 0.9f, 0.9f, 1.0f, txf);
   871: 
   872:   // done drawing text
   873:   fbnTxfGLStateUnset();
   874: 
   875:   // flush out the frame 
   876:   glutSwapBuffers();
   877:   // draw as often as possible
   878:   glutPostRedisplay();
   879: }
   880: 
   881: 
   882: 
   883: void draw_arena(void){
   884:   // draw_arena: draw an "arena".  Currently just a textured background
   885:   //     and some marble textured bricks on the border of the game
   886: 
   887:   float zoom;
   888:   float x1, y1, x2, y2;
   889:   int i;
   890: 
   891:   // Draw the background texture
   892:   glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
   893:   zoom = 30.0f;
   894:   glEnable(GL_TEXTURE_2D);
   895:   glBindTexture(GL_TEXTURE_2D, texGrass);
   896:   glBegin(GL_POLYGON);
   897: #define ARENA_SPREAD_WIDTH (10 * game.render_width) 
   898: #define ARENA_SPREAD_HEIGHT (10 * game.render_height) 
   899:     glColor3f(1.0f, 1.0f, 1.0f);
   900:     glNormal3f(0.0f, 0.0f, 1.0f);
   901:     glTexCoord2f(0.0f, 0.0f);
   902:     glVertex3f(0.0f - ARENA_SPREAD_WIDTH, 
   903: 	       0.0f - ARENA_SPREAD_HEIGHT, 0.0f);
   904:     glTexCoord2f(zoom, 0.0f);
   905:     glVertex3f(game.render_width + ARENA_SPREAD_WIDTH, 
   906: 	       0.0f - ARENA_SPREAD_HEIGHT, 0.0f);
   907:     glTexCoord2f(zoom, zoom);
   908:     glVertex3f(game.render_width + ARENA_SPREAD_WIDTH, 
   909: 	       game.render_height + ARENA_SPREAD_HEIGHT, 0.0f);
   910:     glTexCoord2f(0.0f, zoom);
   911:     glVertex3f(0.0f - ARENA_SPREAD_WIDTH, 
   912: 	       game.render_height + ARENA_SPREAD_HEIGHT, 0.0f);
   913:   glEnd();
   914:   glDisable(GL_TEXTURE_2D);
   915: 
   916:   // draw the brick wall.  left, top, right, bottom
   917:   for(i=0; i<(game.game_height + 2); i++)
   918:     draw_brick(-1.0f, -1.0f + (float)i);
   919:   for(i=0; i<(game.game_width); i++)
   920:     draw_brick((float)i, (float)game.game_height);
   921:   for(i=0; i<(game.game_height + 2); i++)
   922:     draw_brick ((float)game.game_width, -1.0f + (float)i);
   923:   for(i=0; i<(game.game_width); i++)
   924:     draw_brick((float)i, -1.0f);
   925: }
   926: 
   927: void draw_snake(int snake_id, float partial_factor){
   928:   // draw_snake: draw the given snake, given the current percentage/partial-
   929:   //     factor difference from the last gametick, toward the next.
   930: 
   931:   float partial_x_offset, partial_y_offset;
   932:   Position current, previous;
   933:   Snake *snake;
   934: 
   935:   // for code readability
   936:   snake = &(game.snake[snake_id]);
   937: 
   938:   // skin definition
   939:   glColor3ubv(game.options.snake_segment_color[snake_id]);
   940: 
   941:   glEnable(GL_TEXTURE_2D);
   942:   glBindTexture(GL_TEXTURE_2D, texSnakeSkin);
   943: 
   944:   current = snake->tail;
   945:   previous = snake->last_tail;
   946:   while(!((current.x == snake->head.x)&&(current.y == snake->head.y))){
   947:     // Calculate partial offset
   948:     if(current.y == previous.y){
   949:       partial_x_offset = 
   950: 	(current.x > previous.x) ? partial_factor : -partial_factor;
   951:       partial_y_offset = 0.0f;
   952:     } else {
   953:       partial_x_offset = 0.0f;
   954:       partial_y_offset = 
   955: 	(current.y > previous.y) ? partial_factor : -partial_factor;
   956:     }
   957: 
   958:     /* 
   959:      * draw a segment 
   960:      */
   961:     glPushMatrix();
   962:     glTranslatef((2.5f + previous.x + partial_x_offset) * 
   963: 		 game.screen_unit_length, 
   964: 		 (2.5f + previous.y + partial_y_offset) * 
   965: 		 game.screen_unit_length,
   966: 		 game.screen_unit_length / 2.0f);
   967:     glRotatef(45.0f, 1.0f, 0.0f, 0.0f);
   968:     glRotatef(45.0f, 0.0f, 1.0f, 0.0f);
   969:     glRotatef(45.0f, 0.0f, 0.0f, 1.0f);
   970:     //    glutSolidCube(game.screen_unit_length / 2);
   971:     fbnSolidTexturedCube(game.screen_unit_length / 2);
   972:     glPopMatrix();
   973: 
   974:     // setup for next iteration
   975:     previous = current;
   976:     current = game.matrix[current.x][current.y].next;
   977:   }
   978: 
   979:   glDisable(GL_TEXTURE_2D);
   980: 
   981:   // Calculate partial offset
   982:   if(current.y == previous.y){
   983:     partial_x_offset = 
   984:       (current.x > previous.x) ? partial_factor : -partial_factor;
   985:     partial_y_offset = 0.0f;
   986:   } else {
   987:     partial_x_offset = 0.0f;
   988:     partial_y_offset = 
   989:       (current.y > previous.y) ? partial_factor : -partial_factor;
   990:   }
   991: 
   992:   // draw the head 
   993:   glPushMatrix();
   994:   glTranslatef((2.5f + previous.x + partial_x_offset) * 
   995: 	       game.screen_unit_length, 
   996: 	       (2.5f + previous.y + partial_y_offset) * 
   997: 	       game.screen_unit_length,
   998: 	       game.screen_unit_length / 2.0f);
   999:   glRotatef(45.0f, 1.0f, 0.0f, 0.0f);
  1000:   glRotatef(45.0f, 0.0f, 1.0f, 0.0f);
  1001:   glRotatef(45.0f, 0.0f, 0.0f, 1.0f);
  1002:   glutSolidCube(game.screen_unit_length / 2); 
  1003:   glPopMatrix();
  1004: }
  1005: 
  1006: void draw_food(void){
  1007:   // draw_food: draw the food item for the snake, currently a boring yellow
  1008:   //     geometric shape, I guess a poorly tesselated sphere
  1009: 
  1010:   // yellow
  1011:   glColor3ub(255, 255, 0);
  1012:   glPushMatrix();
  1013:   glTranslatef((2.5f + game.food.x) * game.screen_unit_length, 
  1014: 	       (2.5f + game.food.y) * game.screen_unit_length,
  1015: 	       game.screen_unit_length / 2.0f);
  1016:   // Perhaps should make these params depend on current framerate?
  1017:   //  glutSolidSphere(game.screen_unit_length / 2, 5, 4); 
  1018:   glutSolidSphere(game.screen_unit_length / 2, 10, 8); 
  1019:   glPopMatrix();
  1020: }
  1021: 
  1022: void draw_text(void){
  1023:   // draw_text:  This function contains all the text overlay drawing 
  1024:   //     of the GAME_* gamestates.
  1025: 
  1026:   char screen_string[1024];
  1027:   char temp_string[1024];
  1028:   int i;
  1029:   float txf_scale_factor;
  1030:   int width, ascent, descent;
  1031:   GameState state;
  1032: 
  1033:   fbnTxfGLStateSet();
  1034: 
  1035:   // message: P1 score 
  1036:   sprintf(screen_string, "P1:%3d ", game.snake[0].length);
  1037:   fbnTxfRenderString(screen_string, "P1:012 ", 
  1038:  		  16.0f, 10.0f, 28.0f, 9.0f,
  1039: 		  0.9f, 0.1f, 0.1f, 1.0f, txf);
  1040: 
  1041:   // message: P2 score if VS
  1042:   if(game.options.num_snakes > 1){
  1043:     sprintf(screen_string, "P2:%3d ", game.snake[1].length);
  1044:     fbnTxfRenderString(screen_string, "P2:012 ", 
  1045: 		      84.0f, 10.0f, 28.0f, 9.0f,
  1046: 		      0.1f, 0.9f, 0.1f, 1.0f, txf);
  1047:   }
  1048: 
  1049:   // message: fps
  1050:   if(game.options.show_fps){
  1051:     //    sprintf(screen_string, "fps:%4d", game.fps_last_frame);
  1052:     sprintf(screen_string, "fps:%4d ave:%4d worst:%4d", 
  1053: 	    game.fps_last_frame,
  1054: 	    game.fps_history_total / FPS_ARRAY_SIZE,
  1055: 	    game.fps_recent_worst_value);
  1056:     fbnTxfRenderString(screen_string, "fps:0123 ave:0123 worst:0123", 
  1057: 		      37.5f, 92.5f, 75.0f, 5.0f,
  1058: 		      0.9f, 0.9f, 0.9f, 1.0f, txf);
  1059:   }
  1060: 
  1061:   // message: game mode and/or level
  1062:   if((game.state == GAME_PAUSED)||(game.state == GAME_OVER)){
  1063:     state = game.previous_state;
  1064:   } else {
  1065:     state = game.state;
  1066:   }
  1067: 
  1068:   switch(state){
  1069:   case GAME_CLASSIC:
  1070:     sprintf(screen_string, "   Classic    ");
  1071:     break;
  1072:   case GAME_VS:
  1073:     sprintf(screen_string, "      VS      ");
  1074:     break;
  1075:   case GAME_QUEST_LEVEL1:
  1076:     sprintf(screen_string, "Quest: Level 1");
  1077:     break;
  1078:   case GAME_QUEST_LEVEL2:
  1079:     sprintf(screen_string, "Quest: Level 2");
  1080:     break;
  1081:   default:
  1082:     debug("draw_text called from unknown gamestate: %d\n",
  1083: 	  game.state);
  1084:   }
  1085:   fbnTxfRenderString(screen_string, "              ", 
  1086: 		    50.0f, 93.0f, 50.0f, 10.0f,
  1087: 		    0.0f, 0.9f, 0.0f, 1.0f, txf);
  1088: 
  1089:   /* draw the game_over or game_paused MESSAGES 
  1090:      (probably will end up as draw_game_paused_text()
  1091:      and draw_game_text() etc...  */
  1092:   if(game.state == GAME_OVER){
  1093:     switch(game.previous_state){
  1094:     case GAME_CLASSIC:
  1095:     case GAME_QUEST_LEVEL2:
  1096:       if(game.statedata.enter_initials.place == 1){
  1097: 	sprintf(screen_string, "New Hi Score");
  1098:       } else if(game.statedata.enter_initials.place){
  1099: 	sprintf(screen_string, "New Ranking Score");
  1100:       } else {
  1101: 	sprintf(screen_string, "Game Over");
  1102:       }
  1103:       break;
  1104:     case GAME_VS:
  1105:       sprintf(screen_string, "Good Game");
  1106:       break;
  1107:     }
  1108: 
  1109:     fbnTxfRenderString(screen_string, 0, 
  1110: 			50.0f, 50.0f, 75.0f, 40.0f,
  1111: 			1.0f, 0.0f, 1.0f, 1.0f, txf);
  1112: 
  1113:     fbnTxfRenderString("Press enter key", 0,
  1114: 		    50.0f, 4.5f, 30.0f, 9.0f,
  1115: 		    0.8f, 0.8f, 0.8f, 1.0f, txf);
  1116:   } else if(game.state == GAME_PAUSED){
  1117:     fbnTxfRenderString("PAUSED", 0, 
  1118: 		    50.0f, 50.0f, 60.0f, 40.0f,
  1119: 		    1.0f, 1.0f, 1.0f, 1.0f, txf);
  1120:     fbnTxfRenderString("Press enter key", 0,
  1121: 		    50.0f, 4.5f, 30.0f, 9.0f,
  1122: 		    0.8f, 0.8f, 0.8f, 1.0f, txf);
  1123:   }
  1124:   fbnTxfGLStateUnset(); 
  1125: }
  1126: 
  1127: void draw_brick(float x, float y)
  1128: {
  1129:   // draw_brick: draw a brick on the gameboard at game position x,y
  1130: 
  1131: #define INV_SQRT_TWO 0.707106f 
  1132:   static GLfloat n[10][3] =
  1133:   {
  1134:     {0.0, 0.0, 1.0},
  1135:     {0.0, 0.0, -1.0},
  1136:     {-1.0, 0.0, 0.0},
  1137:     {1.0, 0.0, 0.0},
  1138:     {0.0, 1.0, 0.0},
  1139:     {0.0, -1.0, 0.0},
  1140:     {-INV_SQRT_TWO, 0.0, INV_SQRT_TWO},
  1141:     {INV_SQRT_TWO, 0.0, INV_SQRT_TWO},
  1142:     {0.0f, INV_SQRT_TWO, INV_SQRT_TWO},
  1143:     {0.0f, -INV_SQRT_TWO, INV_SQRT_TWO},
  1144:   };
  1145:   static GLint faces[10][4] =
  1146:   {
  1147:     // top, bot, lt, rt, fr, bk, 
  1148:     // lt_up, rt_up, fr_up, bk_up
  1149:     {8, 9, 10, 11},
  1150:     {0, 2, 1, 3},
  1151:     {4, 6, 0, 2},
  1152:     {7, 5, 3, 1},
  1153:     {5, 4, 1, 0},
  1154:     {6, 7, 2, 3},
  1155:     {8, 10, 4, 6},
  1156:     {11, 9, 7, 5},
  1157:     {9, 8, 5, 4},
  1158:     {10, 11, 6, 7}
  1159:   };
  1160: #define BRICK_BOTTOM_Z 0.0f 
  1161: #define BRICK_MIDDLE_Z 0.5f 
  1162: #define BRICK_TOP_Z 0.625f 
  1163:   static GLfloat v[12][3] =
  1164:   {
  1165:     {0.0f, 1.0f, BRICK_BOTTOM_Z},
  1166:     {1.0f, 1.0f, BRICK_BOTTOM_Z},
  1167:     {0.0f, 0.0f, BRICK_BOTTOM_Z},
  1168:     {1.0f, 0.0f, BRICK_BOTTOM_Z},
  1169:     {0.0f, 1.0f, BRICK_MIDDLE_Z},
  1170:     {1.0f, 1.0f, BRICK_MIDDLE_Z},
  1171:     {0.0f, 0.0f, BRICK_MIDDLE_Z},
  1172:     {1.0f, 0.0f, BRICK_MIDDLE_Z},
  1173:     {0.125f, 0.875f, BRICK_TOP_Z},
  1174:     {0.875f, 0.875f, BRICK_TOP_Z},
  1175:     {0.125f, 0.125f, BRICK_TOP_Z},
  1176:     {0.875f, 0.125f, BRICK_TOP_Z},
  1177:   };
  1178: 
  1179:   GLint i;
  1180: 
  1181:   glEnable(GL_TEXTURE_2D);
  1182:   glBindTexture(GL_TEXTURE_2D, texMarble);
  1183: 
  1184:   glColor3f(1.0f, 1.0f, 1.0f);
  1185: 
  1186:   glPushMatrix();
  1187:   glTranslatef((x + 2) * game.screen_unit_length,
  1188:  	       (y + 2) * game.screen_unit_length,
  1189: 	       0.0f);
  1190:   glScalef(game.screen_unit_length, game.screen_unit_length, 
  1191: 	   game.screen_unit_length);
  1192: 
  1193:   for (i = 0; i < 10; i++) {
  1194:     glBegin(GL_TRIANGLE_STRIP);
  1195:     glNormal3fv(&n[i][0]);
  1196:     glTexCoord2f(0.0, 1.0);
  1197:     glVertex3fv(&v[faces[i][0]][0]);
  1198:     glTexCoord2f(1.0, 1.0);
  1199:     glVertex3fv(&v[faces[i][1]][0]);
  1200:     glTexCoord2f(0.0, 0.0);
  1201:     glVertex3fv(&v[faces[i][2]][0]);
  1202:     glTexCoord2f(1.0, 0.0);
  1203:     glVertex3fv(&v[faces[i][3]][0]);
  1204:     glEnd();
  1205:   }
  1206:   glPopMatrix();
  1207: 
  1208:   glDisable(GL_TEXTURE_2D);
  1209: }
  1210: 
  1211: void draw_hi_scores(void)
  1212: {
  1213:   // draw_hi_scores: draw the hi score screen
  1214: 
  1215:   float zoom;
  1216:   float r,g,b,a;
  1217:   int i;
  1218:   char placestring[8];
  1219:   char tempstring[32];
  1220:   float tx, ty, tw, th;
  1221: 
  1222:   char gametypestring[32];
  1223:   HiScoreEntry *hiscores;
  1224:   struct timeval current_time;
  1225:   int timediff;
  1226:   float alpha;
  1227: 
  1228:   int special_place; 
  1229: 
  1230:   // get the time since starttime for animation
  1231:   gettimeofday(&current_time, NULL);
  1232:   timediff = TIMEDIFF(current_time, game.statedata.hi_scores.starttime);
  1233:   // increment starttime by cycle seconds so that the timediff doesn't
  1234:   // ever roll over the max value for int
  1235:   if(timediff > HI_SCORE_CYCLE_USEC){
  1236:     game.statedata.hi_scores.starttime.tv_sec += HI_SCORE_CYCLE;
  1237:     timediff = timediff % HI_SCORE_CYCLE_USEC;
  1238:   }
  1239: 
  1240:   // set up variables based on the timediff.  6 possible states
  1241:   // a) display classic his
  1242:   // b) classic dimming
  1243:   // c) quest brightening
  1244:   // d) display quest his
  1245:   // e) his dimming
  1246:   // f) classics brightening
  1247:   if((timediff > (HI_SCORE_DISPLAY_TIME_USEC + HI_SCORE_FADE_TIME_USEC)) 
  1248:       && (timediff < (HI_SCORE_CYCLE_USEC - HI_SCORE_FADE_TIME_USEC))){
  1249:     hiscores = history.quest_hiscores;
  1250:     strcpy(gametypestring, "Quest");
  1251:     if((game.statedata.enter_initials.place) &&
  1252:        (game.statedata.enter_initials.gametype == QUEST)){
  1253:       special_place = game.statedata.enter_initials.place;
  1254:     } else {
  1255:       special_place = 0;
  1256:     }
  1257:   } else {
  1258:     if((game.statedata.enter_initials.place) &&
  1259:        (game.statedata.enter_initials.gametype == CLASSIC)){
  1260:       special_place = game.statedata.enter_initials.place;
  1261:     } else {
  1262:       special_place = 0;
  1263:     }
  1264:     hiscores = history.classic_hiscores;
  1265:     strcpy(gametypestring, "Classic");
  1266:   }
  1267: 
  1268:   if(timediff < HI_SCORE_DISPLAY_TIME_USEC){
  1269:     alpha = 1.0f;
  1270:   } else if(timediff < 
  1271: 	    (HI_SCORE_DISPLAY_TIME_USEC + HI_SCORE_FADE_TIME_USEC)){
  1272:     alpha = 1.0f - ((timediff - HI_SCORE_DISPLAY_TIME_USEC) / 1000000.0f);
  1273:   } else if(timediff < 
  1274: 	    (HI_SCORE_DISPLAY_TIME_USEC + 2 * HI_SCORE_FADE_TIME_USEC)){
  1275:     alpha = (timediff - 
  1276: 	     (HI_SCORE_DISPLAY_TIME_USEC + HI_SCORE_FADE_TIME_USEC)) / 
  1277: 	     1000000.0f;
  1278:   } else if(timediff < 
  1279: 	    (2 * HI_SCORE_DISPLAY_TIME_USEC + 2 * HI_SCORE_FADE_TIME_USEC)){
  1280:     alpha = 1.0f;
  1281:   } else if(timediff < 
  1282: 	    (2 * HI_SCORE_DISPLAY_TIME_USEC + 3 * HI_SCORE_FADE_TIME_USEC)){
  1283:     alpha = 1.0f - ((timediff - 
  1284: 		     (2 * HI_SCORE_DISPLAY_TIME_USEC + 
  1285: 		      2 * HI_SCORE_FADE_TIME_USEC)) / 1000000.0f);
  1286:   } else if(timediff < 
  1287: 	    (2 * HI_SCORE_DISPLAY_TIME_USEC + 4 * HI_SCORE_FADE_TIME_USEC)){
  1288:     alpha = (timediff - 
  1289: 	     (2 * HI_SCORE_DISPLAY_TIME_USEC + 
  1290: 	      3 * HI_SCORE_FADE_TIME_USEC)) / 1000000.0f;
  1291:   } else {
  1292:     debug("assertion failed in draw_hi_scores timediff was %d\n", timediff);
  1293:     exit(1);
  1294:   }
  1295: 
  1296:   // viewing transformation 
  1297:   glLoadIdentity();
  1298:   // gluLookAt 
  1299:   gluLookAt(50.0f, 50.0f, 50.0f,
  1300: 	    50.0f, 50.0f, 0.0f,
  1301: 	    0.0f, 1.0f, 0.0f);
  1302:  
  1303:   // clear the frame 
  1304:   glClearColor(0.0, 0.0, 0.0, 0.0);
  1305:   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  1306: 
  1307:   // Draw the background texture
  1308:   zoom = 5.0f;
  1309:   glEnable(GL_TEXTURE_2D);
  1310:   glBindTexture(GL_TEXTURE_2D, texGrass);
  1311:   glBegin(GL_POLYGON);
  1312:     glColor3f(1.0f, 1.0f, 1.0f);
  1313:     glNormal3f(0.0f, 0.0f, 1.0f);
  1314:     glTexCoord2f(0.0f, 0.0f);
  1315:     glVertex3f(0.0f, 0.0f, 0.0f);
  1316:     glTexCoord2f(zoom, 0.0f);
  1317:     glVertex3f(100.0f, 0.0f, 0.0f);
  1318:     glTexCoord2f(zoom, zoom);
  1319:     glVertex3f(100.0f, 100.0f, 0.0f);
  1320:     glTexCoord2f(0.0f, zoom);
  1321:     glVertex3f(0.0f, 100.0f, 0.0f);
  1322:   glEnd();
  1323:   glDisable(GL_TEXTURE_2D);
  1324: 
  1325:   fbnTxfGLStateSet();
  1326: 
  1327: #define HI_SCORE_WIDTH 55.0f 
  1328: #define HI_SCORE_LOWER_BOUNDRY 15.0f 
  1329: #define HI_SCORE_UPPER_BOUNDRY 75.0f 
  1330: #define HI_SCORE_LEFT_BOUNDRY 35.0f 
  1331: #define HI_SCORE_RIGHT_BOUNDRY 65.0f 
  1332: 
  1333:   sprintf(tempstring, "%s Hi Scores", gametypestring);
  1334:   fbnTxfRenderString(tempstring, 0,
  1335: 		     50.0f, (100.0f + HI_SCORE_UPPER_BOUNDRY) / 2.0f, 
  1336: 		     80.0f, 100.0f - HI_SCORE_UPPER_BOUNDRY,
  1337: 		     0.0f, 1.0f, 0.0f, alpha, txf);
  1338: 
  1339:   for(i=0; i<NUM_HI_SCORES; i++){
  1340:     fbnSetPlaceString(placestring, i + 1);
  1341:     sprintf(tempstring, "%s  %c%c%c  %d", placestring,
  1342: 	    hiscores[i].initials[0],
  1343: 	    hiscores[i].initials[1],
  1344: 	    hiscores[i].initials[2],
  1345: 	    hiscores[i].score);
  1346: 
  1347:     tx = HI_SCORE_LEFT_BOUNDRY + i * 
  1348:       ((HI_SCORE_RIGHT_BOUNDRY - HI_SCORE_LEFT_BOUNDRY) / 
  1349:        ((float)NUM_HI_SCORES - 1.0f));
  1350:     ty = HI_SCORE_LOWER_BOUNDRY + ((float)NUM_HI_SCORES - (float)i - 0.5f) *
  1351:       ((HI_SCORE_UPPER_BOUNDRY - HI_SCORE_LOWER_BOUNDRY) / 
  1352:        (float)NUM_HI_SCORES);
  1353:     tw = HI_SCORE_WIDTH;
  1354:     th = (HI_SCORE_UPPER_BOUNDRY - HI_SCORE_LOWER_BOUNDRY) / 
  1355:       (float)NUM_HI_SCORES;
  1356: 
  1357:     if((i + 1) == (special_place)){
  1358:       fbnTxfRenderString(tempstring, "10th  fbn  999",
  1359: 			 tx, ty, tw, th,
  1360: 			 0.9f, 0.2f, 0.2f, alpha, txf);
  1361:     } else {
  1362:       fbnTxfRenderString(tempstring, "10th  fbn  999",
  1363: 			 tx, ty, tw, th,
  1364: 			 0.8f, 0.0f, 0.8f, alpha, txf);
  1365:     }
  1366:   }
  1367: 
  1368:   fbnTxfRenderString("press enter key to return to menu", 0,
  1369: 		    50.0f, (HI_SCORE_LOWER_BOUNDRY - 0.0f) / 2.0f, 
  1370: 		    80.0f, HI_SCORE_LOWER_BOUNDRY,
  1371: 		    0.8f, 0.8f, 0.8f, 1.0f, txf);
  1372: 
  1373:   fbnTxfGLStateUnset();
  1374: 
  1375:   // thats it
  1376:   glutSwapBuffers();
  1377: 
  1378:   // draw as often as possible for animation
  1379:   glutPostRedisplay();
  1380: }
  1381: 
  1382: void reshape(int w, int h){
  1383:   glViewport(0, 0, (GLsizei)w, (GLsizei)h);
  1384:   glMatrixMode(GL_PROJECTION);
  1385:   glLoadIdentity();
  1386:   /* gluPerspective(field of view in xz plane, aspect ration w/h, 
  1387:                     near clipping plane - distance from camera,
  1388: 		    far clipping plane - distance from camera); */
  1389:   /* perhaps better is gluPerspective(60.0, 1.0, 0.000001, 1.0); ??? */
  1390:   gluPerspective(60.0, (float)w / (float)h, 0.01, 999.9);
  1391:   glMatrixMode(GL_MODELVIEW);
  1392: }
  1393: 
  1394: void idle_shared(void){
  1395:   // idle_nothing: do nothing
  1396:   joy();
  1397: }
  1398: 
  1399: void timer_classic(int value){
  1400:   // timer: this is our timer callback.  Currently for CLASSIC
  1401:   //     gamestate uses this, for it's animation and gameplay 
  1402:   //     stepping.  
  1403:   // NOTE: Usually one wants to set a new timer func
  1404:   //     at the end of whatever this one did.  This is done by
  1405:   //     a final call to glutTimerFunc
  1406:   int i; 
  1407: 
  1408:   if(game.state == GAME_CLASSIC){
  1409:     movesnake(0);
  1410:     glutTimerFunc((int)(game.timeslice / 1000), timer_classic, 0);
  1411:   }
  1412: }
  1413: 
  1414: void timer_vs(int value){
  1415:   // timer_vs: this is our timer callback.  Currently for VS.
  1416:   //     gamestate uses this, for it's animation and gameplay 
  1417:   //     stepping.  
  1418:   int i; 
  1419: 
  1420:   if(game.state == GAME_VS){
  1421:     movesnake(0);
  1422:     movesnake(1);
  1423:     glutTimerFunc((int)(game.timeslice / 1000), timer_vs, 0);
  1424:   }
  1425: }
  1426: 
  1427: void timer_quest_level1(int value){
  1428:   // timer_quest: 
  1429:   int i; 
  1430: 
  1431:   if(game.state == GAME_QUEST_LEVEL1){
  1432:     movesnake(0);
  1433:     glutTimerFunc((int)(game.timeslice / 1000), timer_quest_level1, 0);
  1434:   }
  1435: }
  1436: 
  1437: void timer_quest_level2(int value){
  1438:   // timer_quest: 
  1439:   int i; 
  1440: 
  1441:   if(game.state == GAME_QUEST_LEVEL2){
  1442:     movesnake(0);
  1443:     glutTimerFunc((int)(game.timeslice / 1000), timer_quest_level2, 0);
  1444:   }
  1445: }
  1446: 
  1447: /*
  1448:  * "key" functions. Generally one for each GAMESTATE as well
  1449:  *     as one that does nothing.  
  1450:  */
  1451: 
  1452: void key_shared(unsigned char k, int x, int y){
  1453:   // key_shared: every key_* calls this first
  1454:   switch (k) {
  1455:   case ESCAPEKEY:  /* Escape */
  1456:     SetState(OFF);
  1457:     break;
  1458:   case TRANSLATE_CAMERA_FORWARD:
  1459:     camera.eyez -= 0.1f * SCREEN_DEPTH;
  1460:     txt_camera.eyez -= 0.1f * SCREEN_DEPTH;
  1461:     break;
  1462:   case TRANSLATE_CAMERA_BACK:
  1463:     camera.eyez += 0.1f * SCREEN_DEPTH;
  1464:     txt_camera.eyez += 0.1f * SCREEN_DEPTH;
  1465:     break;
  1466:   case TRANSLATE_CAMERA_LEFT:
  1467:     camera.eyex -= 0.1f * SCREEN_WIDTH;
  1468:     txt_camera.eyex -= 0.1f * SCREEN_WIDTH;
  1469:     break;
  1470:   case TRANSLATE_CAMERA_RIGHT:
  1471:     camera.eyex += 0.1f * SCREEN_WIDTH;
  1472:     txt_camera.eyex += 0.1f * SCREEN_WIDTH;
  1473:     break;
  1474:   case TRANSLATE_CAMERA_DOWN:
  1475:     camera.eyey -= 0.1f * SCREEN_HEIGHT;
  1476:     txt_camera.eyey -= 0.1f * SCREEN_HEIGHT;
  1477:     break;
  1478:   case TRANSLATE_CAMERA_UP:
  1479:     camera.eyey += 0.1f * SCREEN_HEIGHT;
  1480:     txt_camera.eyey += 0.1f * SCREEN_HEIGHT;
  1481:     break;
  1482:   case DEBUG_DUMP_MATRIX_KEY:
  1483:     debug_dump_matrix();
  1484:     break;
  1485:   default:
  1486:     break;
  1487:   }
  1488:   return;
  1489: }
  1490: 
  1491: void key_menu(unsigned char k, int x, int y){
  1492:   // key_menu: the keyboard glut input event callback
  1493: 
  1494:   key_shared(k, x, y);
  1495: 
  1496:   switch (k) {
  1497:   case P1_UPKEY:
  1498:     game.mainmenu--;
  1499:     if(game.mainmenu == -1) 
  1500:       game.mainmenu = MAINMENUSTATES - 1;
  1501:     break;
  1502:   case P1_DOWNKEY:
  1503:     game.mainmenu++;
  1504:     if(game.mainmenu == MAINMENUSTATES) 
  1505:       game.mainmenu = 0;
  1506:     break;
  1507:   case ENTERKEY:
  1508:     switch(game.mainmenu){
  1509:     case MENU_INSTRUCTIONS:
  1510:       SetState(INSTRUCTIONS);
  1511:       break;
  1512:     case MENU_CLASSIC:
  1513:       game.options.num_snakes = 1;
  1514:       StartNewGameClassic();
  1515:       break;
  1516:     case MENU_VS:
  1517:       game.options.num_snakes = 2;
  1518:       StartNewGameVS();
  1519:       break;
  1520:     case MENU_QUEST:
  1521:       game.options.num_snakes = 1;
  1522:       StartNewGameQuest();
  1523:       break;
  1524:     case MENU_HI_SCORES:
  1525:       SetState(HI_SCORES);
  1526:       break;
  1527:     case MENU_QUIT:
  1528:       SetState(OFF);
  1529:       break;
  1530:     default:
  1531:       debug("Error unknown game.mainmenu state\n");
  1532:       break;
  1533:     }
  1534:   }
  1535: }
  1536: 
  1537: void key_game_shared(unsigned char k, int x, int y){
  1538:   // key_game_shared: the shared key callback, good during any level 
  1539:   //     of any game.  (since the various levels/games all have the 
  1540:   //     same basic input)
  1541:   struct timeval temptime;
  1542:   long tempusecdiff;
  1543: 
  1544:   key_shared(k, x, y);
  1545: 
  1546:   switch (k) {
  1547:   case ENTERKEY:
  1548:     SetState(GAME_PAUSED);
  1549:     break;
  1550:   case P1_UPKEY:
  1551:     if(game.snake[0].current!=DOWN) game.snake[0].next_dir = UP;
  1552:     break;
  1553:   case P1_RIGHTKEY:
  1554:     if(game.snake[0].current!=LEFT) game.snake[0].next_dir = RIGHT;
  1555:     break;
  1556:   case P1_LEFTKEY:
  1557:     if(game.snake[0].current!=RIGHT) game.snake[0].next_dir = LEFT;
  1558:     break;
  1559:   case P1_DOWNKEY:
  1560:     if(game.snake[0].current!=UP) game.snake[0].next_dir = DOWN;
  1561:     break;
  1562:   case P2_UPKEY:
  1563:     if(game.snake[1].current!=DOWN) game.snake[1].next_dir = UP;
  1564:     break;
  1565:   case P2_RIGHTKEY:
  1566:     if(game.snake[1].current!=LEFT) game.snake[1].next_dir = RIGHT;
  1567:     break;
  1568:   case P2_LEFTKEY:
  1569:     if(game.snake[1].current!=RIGHT) game.snake[1].next_dir = LEFT;
  1570:     break;
  1571:   case P2_DOWNKEY:
  1572:     if(game.snake[1].current!=UP) game.snake[1].next_dir = DOWN;
  1573:     break;
  1574:   default:
  1575:     break;
  1576:   }
  1577:   return;
  1578: }
  1579: 
  1580: void key_game_paused(unsigned char k, int x, int y){
  1581:   // key_game_paused: the keyboard glut input event callback
  1582: 
  1583:   key_shared(k, x, y);
  1584: 
  1585:   switch (k) {
  1586:   case ENTERKEY:
  1587:     SetState(game.previous_state); 
  1588:     break;
  1589:   default:
  1590:     break;
  1591:   }
  1592:   return;
  1593: }
  1594: 
  1595: void key_game_over(unsigned char k, int x, int y){
  1596:   // key_game_over: the keyboard glut input event callback
  1597: 
  1598:   key_shared(k, x, y);
  1599: 
  1600:   switch (k) {
  1601:   case ENTERKEY:
  1602:     // before going to next state wait for all sounds to finish playing
  1603:     fbnSoundWait();
  1604:     if(game.statedata.enter_initials.place){
  1605:       SetState(ENTER_INITIALS);
  1606:     } else {
  1607:       SetState(MENU);
  1608:     }
  1609:     break;
  1610:   default:
  1611:     break;
  1612:   }
  1613: }
  1614: 
  1615: void key_enter_initials(unsigned char k, int x, int y){
  1616:   // key_enter_initials
  1617: 
  1618:   // HACK HACK HACK there has to be a more graceful way to do this function
  1619: 
  1620:   key_shared(k, x, y);
  1621: 
  1622:   switch (k) {
  1623:   case P1_UPKEY:
  1624:     if(game.statedata.enter_initials.current_initial != 3)
  1625:       game.statedata.enter_initials.hiscores[game.statedata.enter_initials.place - 1].initials[game.statedata.enter_initials.current_initial] += 1;
  1626:       if(game.statedata.enter_initials.hiscores[game.statedata.enter_initials.place - 1].initials[game.statedata.enter_initials.current_initial] == 91) game.statedata.enter_initials.hiscores[game.statedata.enter_initials.place - 1].initials[game.statedata.enter_initials.current_initial] = 48;
  1627:       if(game.statedata.enter_initials.hiscores[game.statedata.enter_initials.place - 1].initials[game.statedata.enter_initials.current_initial] == 58) game.statedata.enter_initials.hiscores[game.statedata.enter_initials.place - 1].initials[game.statedata.enter_initials.current_initial] = 32;
  1628:       if(game.statedata.enter_initials.hiscores[game.statedata.enter_initials.place - 1].initials[game.statedata.enter_initials.current_initial] == 33) game.statedata.enter_initials.hiscores[game.statedata.enter_initials.place - 1].initials[game.statedata.enter_initials.current_initial] = 65;
  1629:     break;
  1630:   case P1_RIGHTKEY:
  1631:     game.statedata.enter_initials.current_initial = (game.statedata.enter_initials.current_initial + 1) % 4;
  1632:     break;
  1633:   case P1_LEFTKEY:
  1634:     game.statedata.enter_initials.current_initial = (game.statedata.enter_initials.current_initial + 3) % 4;
  1635:     break;
  1636:   case P1_DOWNKEY:
  1637:     if(game.statedata.enter_initials.current_initial != 3){
  1638:       game.statedata.enter_initials.hiscores[game.statedata.enter_initials.place - 1].initials[game.statedata.enter_initials.current_initial] -= 1;
  1639:       if(game.statedata.enter_initials.hiscores[game.statedata.enter_initials.place - 1].initials[game.statedata.enter_initials.current_initial] == 64) game.statedata.enter_initials.hiscores[game.statedata.enter_initials.place - 1].initials[game.statedata.enter_initials.current_initial] = 32;
  1640:       if(game.statedata.enter_initials.hiscores[game.statedata.enter_initials.place - 1].initials[game.statedata.enter_initials.current_initial] == 31) game.statedata.enter_initials.hiscores[game.statedata.enter_initials.place - 1].initials[game.statedata.enter_initials.current_initial] = 57;
  1641:       if(game.statedata.enter_initials.hiscores[game.statedata.enter_initials.place - 1].initials[game.statedata.enter_initials.current_initial] == 47) game.statedata.enter_initials.hiscores[game.statedata.enter_initials.place - 1].initials[game.statedata.enter_initials.current_initial] = 90;
  1642:     }
  1643:     break;
  1644:   case ENTERKEY:
  1645:     if(game.statedata.enter_initials.current_initial != 3){
  1646:       game.statedata.enter_initials.current_initial += 1;
  1647:     } else {
  1648:       // write out new hiscorefile, wait for sounds to finish, and goto hiscore
  1649:       WriteGameHistory();
  1650:       fbnSoundWait();
  1651:       SetState(HI_SCORES);
  1652:     }
  1653:     break;
  1654:   default:
  1655:     break;
  1656:   }
  1657: }
  1658: 
  1659: void key_hi_scores(unsigned char k, int x, int y){
  1660:   // key_hi_scores: the keyboard glut input event callback
  1661: 
  1662:   key_shared(k, x, y);
  1663: 
  1664:   switch (k) {
  1665:   case ENTERKEY:
  1666:     SetState(MENU);
  1667:     break;
  1668:   default:
  1669:     break;
  1670:   }
  1671: }
  1672: 
  1673: /*
  1674:  * "special" functions. Generally one for each GAMESTATE as well
  1675:  *     as one that does nothing.  
  1676:  */
  1677: 
  1678: void special(int k, int x, int y){
  1679:   // special_game: the "special" keyboard glut input event callback
  1680:   switch (k) {
  1681:   case GLUT_KEY_UP:
  1682:     key(P1_UPKEY, x, y);
  1683:     break;
  1684:   case GLUT_KEY_RIGHT:
  1685:     key(P1_RIGHTKEY, x, y);
  1686:     break;
  1687:   case GLUT_KEY_LEFT:
  1688:     key(P1_LEFTKEY, x, y);
  1689:     break;
  1690:   case GLUT_KEY_DOWN:
  1691:     key(P1_DOWNKEY, x, y);
  1692:     break;
  1693:   default:
  1694:     return;
  1695:   }
  1696: }
  1697: 
  1698: void key(unsigned char k, int x, int y){
  1699:   // special_game: the "special" keyboard glut input event callback
  1700:   switch (game.state) {
  1701:   case MENU:
  1702:     key_menu(k, x, y);
  1703:     break;
  1704:   case GAME_CLASSIC:
  1705:   case GAME_VS:
  1706:   case GAME_QUEST_LEVEL1:
  1707:   case GAME_QUEST_LEVEL2:
  1708:     key_game_shared(k, x, y);
  1709:     break;
  1710:   case GAME_PAUSED:
  1711:     key_game_paused(k, x, y);
  1712:     break;
  1713:   case GAME_OVER:
  1714:     key_game_over(k, x, y);
  1715:     break;
  1716:   case ENTER_INITIALS:
  1717:     key_enter_initials(k, x, y);
  1718:     break;
  1719:   case HI_SCORES:
  1720:     key_hi_scores(k, x, y);
  1721:     break;
  1722:   default:
  1723:     return;
  1724:   }
  1725: }
  1726: 
  1727: void movesnake(int snake_id){
  1728:   // movesnake: This function moves a snake in it's current direction.  Based
  1729:   //     on the contents of the snake's belly, the snake may grow as well.
  1730:   //     If the snake's next coordinate is the food, the belly is filled.
  1731:   //     This function will jump to GameOver if the snake is blocked.
  1732: 
  1733:   int i,j,k;
  1734:   int num_free;
  1735: 
  1736:   // Update the direction the snake is going to move in.  The players
  1737:   // move is now considered final, future input is relevent for the next
  1738:   // move.
  1739:   SNAKE.current = SNAKE.next_dir;
  1740: 
  1741:   // Shrink the tail if the belly is not full.  Shrink the tail first so that
  1742:   // the player may legally move into the space vacated by the tail.  But save
  1743:   // enough information that if gameover occurs, the tail can be regrown since
  1744:   // the move could not have actually occurred.
  1745:   //  
  1746:   // RULE CLARIFICATION: Suppose the snake is running straight into a wall
  1747:   //     and the last section before wall contained a food.  In this case
  1748:   //     the score (SNAKE.length) will increase, but visually the snake will
  1749:   //     not lengthen since a snake can only visually lengthen by "growing 
  1750:   //     forward".
  1751:   if(SNAKE.belly){
  1752:     SNAKE.belly--;
  1753:     SNAKE.length++;
  1754:   } else {
  1755:     HideTail(snake_id);
  1756:   }
  1757: 
  1758:   // Grow the head of the snake, checking for gameover conditions.  If gameover
  1759:   // has occurred, and the snake was not growing, regrow the tail from saved
  1760:   // info since the current move is nullified by gameover
  1761:   switch(SNAKE.current){
  1762:   case LEFT:
  1763:     if(SNAKE.head.x <= 0)
  1764:       {movesnake_gameover(snake_id); return;}
  1765:     if(game.matrix[SNAKE.head.x - 1][SNAKE.head.y].snake != 0)
  1766:       {movesnake_gameover(snake_id); return;}
  1767:     game.matrix[SNAKE.head.x][SNAKE.head.y].next.x = SNAKE.head.x - 1;
  1768:     game.matrix[SNAKE.head.x][SNAKE.head.y].next.y = SNAKE.head.y;
  1769:     SNAKE.head.x = SNAKE.head.x - 1;
  1770:     break;
  1771:   case RIGHT:
  1772:     if(SNAKE.head.x >= (game.game_width - 1)) 
  1773:       {movesnake_gameover(snake_id); return;}
  1774:     if(game.matrix[SNAKE.head.x + 1][SNAKE.head.y].snake != 0) 
  1775:       {movesnake_gameover(snake_id); return;}
  1776:     game.matrix[SNAKE.head.x][SNAKE.head.y].next.x = SNAKE.head.x + 1;
  1777:     game.matrix[SNAKE.head.x][SNAKE.head.y].next.y = SNAKE.head.y;
  1778:     SNAKE.head.x = SNAKE.head.x + 1;
  1779:     break;
  1780:   case UP:
  1781:     if(SNAKE.head.y >= (game.game_height - 1)) 
  1782:       {movesnake_gameover(snake_id); return;}
  1783:     if(game.matrix[SNAKE.head.x][SNAKE.head.y + 1].snake != 0) 
  1784:       {movesnake_gameover(snake_id); return;}
  1785:     game.matrix[SNAKE.head.x][SNAKE.head.y].next.x = SNAKE.head.x;
  1786:     game.matrix[SNAKE.head.x][SNAKE.head.y].next.y = SNAKE.head.y + 1;
  1787:     SNAKE.head.y = SNAKE.head.y + 1;
  1788:     break;
  1789:   case DOWN:
  1790:     if(SNAKE.head.y <= 0) {movesnake_gameover(snake_id); return;}
  1791:     if(game.matrix[SNAKE.head.x][SNAKE.head.y - 1].snake != 0) 
  1792:       {movesnake_gameover(snake_id); return;}
  1793:     game.matrix[SNAKE.head.x][SNAKE.head.y].next.x = SNAKE.head.x;
  1794:     game.matrix[SNAKE.head.x][SNAKE.head.y].next.y = SNAKE.head.y - 1;
  1795:     SNAKE.head.y = SNAKE.head.y - 1;
  1796:     break;
  1797:   default:
  1798:     exit(1);
  1799:   }
  1800: 
  1801:   // Finish shrinking the tail (shrinkage will only occur if tail_hidden
  1802:   ShrinkTail(snake_id);
  1803: 
  1804:   game.matrix[SNAKE.head.x][SNAKE.head.y].snake = snake_id + 1;
  1805:   
  1806:   // did the snake just eat the food? 
  1807:   if((SNAKE.head.x == game.food.x)&&(SNAKE.head.y == game.food.y)){
  1808:     fbnSoundPlay(SOUNDSET_MUNCH_FOOD, &soundset);
  1809:     SNAKE.belly = SNAKE.belly + game.food_amount;
  1810:     num_free = 
  1811:       (game.game_width * game.game_height) - SNAKE.length;
  1812:     if(!num_free){SetState(GAME_OVER); return;}
  1813:     i = (int)(((float)rand()/(float)RAND_MAX) * num_free);
  1814:     j = 0;
  1815:     k = 0;
  1816:     while(j<=i){
  1817:       if(game.matrix[k % game.game_width][k / game.game_width].snake == 0) j++;
  1818:       if(j<=i) k++;
  1819:     }
  1820:     game.food.x = k % game.game_width;
  1821:     game.food.y = k / game.game_width;
  1822:   }
  1823: 
  1824:   // set time_of_last_gametick 
  1825:   gettimeofday(&(game.time_of_last_gametick), NULL);
  1826: }
  1827: 
  1828: void movesnake_gameover(int snake_id){
  1829:   // movesnake_gameover: take care of the gameover condition from movesnake.
  1830:   //     this includes 'unhiding the tail' if necessary
  1831: 
  1832:   UnhideTail(snake_id);
  1833: 
  1834:   switch(game.state){
  1835:   case GAME_CLASSIC:
  1836:   case GAME_VS:
  1837:   case GAME_QUEST_LEVEL2:
  1838:     SetState(GAME_OVER); 
  1839:     break;
  1840:   case GAME_QUEST_LEVEL1:
  1841:     StartQuestLevel2();
  1842:     break;
  1843:   default:
  1844:     debug("movesnake_gameover called from unhandled gamestate %d.\n", 
  1845: 	  game.state);
  1846:     break;
  1847:   }
  1848: }
  1849: 
  1850: void HideTail(int snake_id){
  1851:   // HideTail: temporarily hide the tail so that movesnake() may move
  1852:   //     the head of the snake into the "to-be-vacated" space of the 
  1853:   //     current tail
  1854: 
  1855:   // Hide the tail
  1856:   SNAKE.tail_hidden = 1;
  1857:   game.matrix[SNAKE.tail.x][SNAKE.tail.y].snake = 0;
  1858: }
  1859: 
  1860: void ShrinkTail(int snake_id){
  1861:   // ShrinkTail: if the tail was temporarily hidden then:  A legal move 
  1862:   //     occurred.  Now it is time to finish the job of shrinking the 
  1863:   //     currently "hidden" tail
  1864:   Position temptail;
  1865: 
  1866:   temptail.x = SNAKE.tail.x;
  1867:   temptail.y = SNAKE.tail.y;
  1868: 
  1869:   if(game.matrix[temptail.x][temptail.y].snake != 0){
  1870:     return;
  1871:   } else {
  1872:     game.matrix[temptail.x][temptail.y].snake = 0;
  1873:   }
  1874: 
  1875:   SNAKE.last_tail.x = temptail.x;
  1876:   SNAKE.last_tail.y = temptail.y;
  1877:   
  1878:   SNAKE.tail.x = game.matrix[temptail.x][temptail.y].next.x;
  1879:   SNAKE.tail.y = game.matrix[temptail.x][temptail.y].next.y;
  1880:   
  1881:   game.matrix[temptail.x][temptail.y].next.x = -1;
  1882:   game.matrix[temptail.x][temptail.y].next.y = -1;
  1883: }
  1884: 
  1885: void UnhideTail(int snake_id){
  1886:   // UnhideTail: Looks like gameover happened, so we "unhide" the tail rather
  1887:   //     than shrinking it as we would do if a legal move had occurred
  1888: 
  1889:   // Hide the tail
  1890:   SNAKE.tail_hidden = 0;
  1891:   game.matrix[SNAKE.tail.x][SNAKE.tail.y].snake = snake_id;
  1892: }
  1893: 
  1894: int InsertNewHiScore(void){
  1895:   // InsertNewHiScore: take the current score and AAA and
  1896:   //     insert this new hi score into history.  Return the calculated rank
  1897:   //     of the new score
  1898:   
  1899:   int newplace = 1;
  1900:   int done = 0;
  1901:   int i;
  1902: 
  1903:   while(!done){
  1904:     if(game.statedata.enter_initials.score >= 
  1905:        game.statedata.enter_initials.hiscores[newplace - 1].score){
  1906:       done = 1;
  1907:     } else {
  1908:       newplace++;
  1909:     }
  1910:   }
  1911: 
  1912:   // shift the bottom section of scores down to make space for the new score
  1913:   for(i=(NUM_HI_SCORES - 1);i>=newplace;i--) 
  1914:     game.statedata.enter_initials.hiscores[i] =
  1915:       game.statedata.enter_initials.hiscores[i - 1];
  1916: 
  1917:   // enter the new score
  1918:   game.statedata.enter_initials.hiscores[newplace - 1].score = 
  1919:     game.statedata.enter_initials.score;
  1920:   game.statedata.enter_initials.hiscores[newplace - 1].initials[0] = 'A';
  1921:   game.statedata.enter_initials.hiscores[newplace - 1].initials[1] = 'A';
  1922:   game.statedata.enter_initials.hiscores[newplace - 1].initials[2] = 'A';
  1923: 
  1924:   return(newplace);
  1925: }
  1926: 
  1927: void StartNewGameClassic(void){
  1928:   // StartNewGameClassic: name says it all, the user just did something to 
  1929:   //     invoke the beginning of a new game.
  1930:   int i,j;
  1931: 
  1932:   // 
  1933:   // Initialize game state
  1934:   //
  1935: 
  1936:   // set up the level
  1937:   game.game_width = game.options.classic.game_width;
  1938:   game.game_height = game.options.classic.game_height;
  1939:   InitLevel();
  1940: 
  1941:   game.statedata.enter_initials.place = 0;
  1942: 
  1943:   game.timeslice = game.options.classic.timeslice;
  1944:   game.food_amount = game.options.classic.food_amount;
  1945: 
  1946:   // initialize food placement
  1947:   game.food.x = game.game_width / 2;
  1948:   game.food.y = game.game_height / 2;
  1949:   
  1950:   // NOTE: the reason the head starts out at 1,0 instead of 0,0 is a result
  1951:   //       of the game semantics, via which at any given point in time the
  1952:   //       snake is always rendered as moving towards where the head is.
  1953:   //       thus to prevent the tail from hanging off the edge of the world,
  1954:   //       the snake starts out with its tail at 1,0 instead of 0,0.  Same
  1955:   //       basic thing is done for the second snake.  This has the side
  1956:   //       effect of making the initial bogus values for last_tail fall 
  1957:   //       within the normal matrix range.
  1958: 
  1959:   // create initial snake[0] of length 1, originated in the bottom left, 
  1960:   // going right
  1961:   game.snake[0].current = RIGHT;
  1962:   game.snake[0].next_dir = RIGHT;
  1963:   game.snake[0].head.x = 1;
  1964:   game.snake[0].head.y = 0;
  1965:   game.snake[0].last_tail.x = 0;
  1966:   game.snake[0].last_tail.y = 0;
  1967: 
  1968:   game.snake[0].tail.x = game.snake[0].head.x;
  1969:   game.snake[0].tail.y = game.snake[0].head.y;
  1970:   game.snake[0].length = 1;
  1971:   game.snake[0].belly = 0;
  1972:   game.snake[0].active = 1;
  1973:   game.snake[0].tail_hidden = 0;
  1974:   game.matrix[game.snake[0].head.x][game.snake[0].head.y].snake = 1; 
  1975: 
  1976:   // now grow the snake to the initial desired length
  1977:   for(j=1;j<game.options.classic.init_snake_length;j++){
  1978:     game.snake[0].belly = 1;
  1979:     movesnake(0);
  1980:   } 
  1981: 
  1982:    // play the new game sound
  1983:   if(game.options.use_sound) fbnSoundPlay(SOUNDSET_NEW_GAME, &soundset);
  1984: 
  1985:   SetState(GAME_CLASSIC);
  1986: }
  1987: 
  1988: void StartNewGameVS(void){
  1989:   // StartNewGameVS: name says it all, the user just did something to 
  1990:   //     invoke the beginning of a new game.
  1991:   int i,j;
  1992: 
  1993:   // 
  1994:   // Initialize game state
  1995:   //
  1996: 
  1997:   // set up the level
  1998:   game.game_width = game.options.vs.game_width;
  1999:   game.game_height = game.options.vs.game_height;
  2000:   InitLevel();
  2001: 
  2002:   game.statedata.enter_initials.place = 0;
  2003: 
  2004:   game.timeslice = game.options.vs.timeslice;
  2005:   game.food_amount = game.options.vs.food_amount;
  2006: 
  2007:   // initialize food placement
  2008:   game.food.x = game.game_width / 2;
  2009:   game.food.y = game.game_height / 2;
  2010:   
  2011:   // NOTE: the reason the head starts out at 1,0 instead of 0,0 is a result
  2012:   //       of the game semantics, via which at any given point in time the
  2013:   //       snake is always rendered as moving towards where the head is.
  2014:   //       thus to prevent the tail from hanging off the edge of the world,
  2015:   //       the snake starts out with its tail at 1,0 instead of 0,0.  Same
  2016:   //       basic thing is done for the second snake.  This has the side
  2017:   //       effect of making the initial bogus values for last_tail fall 
  2018:   //       within the normal matrix range.
  2019: 
  2020:   // create initial snake[0] of length 1, originated in the bottom left, 
  2021:   // going right
  2022:   game.snake[0].current = RIGHT;
  2023:   game.snake[0].next_dir = RIGHT;
  2024:   game.snake[0].head.x = 1;
  2025:   game.snake[0].head.y = 0;
  2026:   game.snake[0].last_tail.x = 0;
  2027:   game.snake[0].last_tail.y = 0;
  2028: 
  2029:   // create initial snake[1] of length 1, originated in the top right, 
  2030:   // going left
  2031:   game.snake[1].current = LEFT;
  2032:   game.snake[1].next_dir = LEFT;
  2033:   game.snake[1].head.x = game.game_width - 2;
  2034:   game.snake[1].head.y = game.game_height - 1;
  2035:   game.snake[1].last_tail.x = game.game_width - 1;
  2036:   game.snake[1].last_tail.y = game.game_height - 1;
  2037: 
  2038:   game.snake[0].tail.x = game.snake[0].head.x;
  2039:   game.snake[0].tail.y = game.snake[0].head.y;
  2040:   game.snake[0].length = 1;
  2041:   game.snake[0].belly = 0;
  2042:   game.snake[0].active = 1;
  2043:   game.snake[0].tail_hidden = 0;
  2044:   game.matrix[game.snake[0].head.x][game.snake[0].head.y].snake = 1; 
  2045: 
  2046:   game.snake[1].tail.x = game.snake[1].head.x;
  2047:   game.snake[1].tail.y = game.snake[1].head.y;
  2048:   game.snake[1].length = 1;
  2049:   game.snake[1].belly = 0;
  2050:   game.snake[1].active = 1;
  2051:   game.snake[1].tail_hidden = 0;
  2052:   game.matrix[game.snake[1].head.x][game.snake[1].head.y].snake = 2; 
  2053: 
  2054:   // now grow the snake to the initial desired length
  2055:   for(j=1;j<game.options.vs.init_snake_length;j++){
  2056:     game.snake[0].belly = 1;
  2057:     movesnake(0);
  2058:     game.snake[1].belly = 1;
  2059:     movesnake(1);
  2060:   } 
  2061: 
  2062:   // play the new game sound
  2063:   if(game.options.use_sound) fbnSoundPlay(SOUNDSET_NEW_GAME, &soundset);
  2064: 
  2065:   SetState(GAME_VS);
  2066: }
  2067: 
  2068: void StartNewGameQuest(void){
  2069:   // StartNewGameQuest: name says it all, the user just did something to 
  2070:   //     invoke the beginning of a new game.
  2071: 
  2072:   // first pass, add intro and level intro later
  2073:   StartQuestLevel1();
  2074: }
  2075: 
  2076: void StartQuestLevel1(void){
  2077:   // StartQuestLevel1: We are in QUEST mode, and it is time to start level1
  2078: 
  2079:   int i,j;
  2080: 
  2081:   // 
  2082:   // Initialize game state
  2083:   //
  2084: 
  2085:   // set up the level
  2086:   game.food_amount = game.options.quest.level2.food;
  2087:   game.game_width = game.options.quest.level1.width;
  2088:   game.game_height = game.options.quest.level1.height;
  2089:   InitLevel();
  2090: 
  2091:   // init statedata.quest
  2092:   game.statedata.quest.score = 0;
  2093: 
  2094:   game.statedata.enter_initials.place = 0;
  2095: 
  2096:   game.timeslice = game.options.quest.timeslice / 2;
  2097: 
  2098:   // initialize food placement
  2099:   game.food.x = game.game_width / 2;
  2100:   game.food.y = game.game_height / 2;
  2101:   
  2102:   // NOTE: the reason the head starts out at 1,0 instead of 0,0 is a result
  2103:   //       of the game semantics, via which at any given point in time the
  2104:   //       snake is always rendered as moving towards where the head is.
  2105:   //       thus to prevent the tail from hanging off the edge of the world,
  2106:   //       the snake starts out with its tail at 1,0 instead of 0,0.  Same
  2107:   //       basic thing is done for the second snake.  This has the side
  2108:   //       effect of making the initial bogus values for last_tail fall 
  2109:   //       within the normal matrix range.
  2110: 
  2111:   // create initial snake[0] of length 1, originated in the bottom left, 
  2112:   // going right
  2113:   game.snake[0].current = RIGHT;
  2114:   game.snake[0].next_dir = RIGHT;
  2115:   game.snake[0].head.x = 1;
  2116:   game.snake[0].head.y = 0;
  2117:   game.snake[0].last_tail.x = 0;
  2118:   game.snake[0].last_tail.y = 0;
  2119: 
  2120:   game.snake[0].tail.x = game.snake[0].head.x;
  2121:   game.snake[0].tail.y = game.snake[0].head.y;
  2122:   game.snake[0].length = 1;
  2123:   game.snake[0].belly = 0;
  2124:   game.snake[0].active = 1;
  2125:   game.snake[0].tail_hidden = 0;
  2126:   game.matrix[game.snake[0].head.x][game.snake[0].head.y].snake = 1; 
  2127: 
  2128:   // now grow the snake to the initial desired length
  2129:   for(j=1;j<game.options.quest.level1.length;j++){
  2130:     game.snake[0].belly = 1;
  2131:     movesnake(0);
  2132:   } 
  2133: 
  2134:    // play the new game sound
  2135:   if(game.options.use_sound) fbnSoundPlay(SOUNDSET_NEW_GAME, &soundset);
  2136: 
  2137:   SetState(GAME_QUEST_LEVEL1);
  2138: }
  2139: 
  2140: void StartQuestLevel2(void){
  2141:   // StartQuestLevel2: We are in QUEST mode, and it is time to start level2
  2142: 
  2143:   int i,j;
  2144: 
  2145:   // 
  2146:   // Initialize game state
  2147:   //
  2148: 
  2149:   // set up the level
  2150:   game.food_amount = game.options.quest.level2.food;
  2151:   game.game_width = game.options.quest.level2.width;
  2152:   game.game_height = game.options.quest.level2.height;
  2153:   InitLevel();
  2154: 
  2155:   // init statedata.quest
  2156:   game.statedata.quest.score = 0;
  2157: 
  2158:   game.statedata.enter_initials.place = 0;
  2159: 
  2160:   game.timeslice = game.options.quest.timeslice / 2;
  2161: 
  2162:   // initialize food placement
  2163:   game.food.x = game.game_width / 2;
  2164:   game.food.y = game.game_height / 2;
  2165:   
  2166:   // NOTE: the reason the head starts out at 1,0 instead of 0,0 is a result
  2167:   //       of the game semantics, via which at any given point in time the
  2168:   //       snake is always rendered as moving towards where the head is.
  2169:   //       thus to prevent the tail from hanging off the edge of the world,
  2170:   //       the snake starts out with its tail at 1,0 instead of 0,0.  Same
  2171:   //       basic thing is done for the second snake.  This has the side
  2172:   //       effect of making the initial bogus values for last_tail fall 
  2173:   //       within the normal matrix range.
  2174: 
  2175:   // create initial snake[0] of length 1, originated in the bottom left, 
  2176:   // going right
  2177:   game.snake[0].current = RIGHT;
  2178:   game.snake[0].next_dir = RIGHT;
  2179:   game.snake[0].head.x = 1;
  2180:   game.snake[0].head.y = 0;
  2181:   game.snake[0].last_tail.x = 0;
  2182:   game.snake[0].last_tail.y = 0;
  2183: 
  2184:   game.snake[0].tail.x = game.snake[0].head.x;
  2185:   game.snake[0].tail.y = game.snake[0].head.y;
  2186:   game.snake[0].length = 1;
  2187:   game.snake[0].belly = 0;
  2188:   game.snake[0].active = 1;
  2189:   game.snake[0].tail_hidden = 0;
  2190:   game.matrix[game.snake[0].head.x][game.snake[0].head.y].snake = 1; 
  2191: 
  2192:   // now grow the snake to the initial desired length
  2193:   for(j=1;j<game.options.quest.level2.length;j++){
  2194:     game.snake[0].belly = 1;
  2195:     movesnake(0);
  2196:   } 
  2197: 
  2198:   SetState(GAME_QUEST_LEVEL2);
  2199: }
  2200: 
  2201: void InitLevel(void){
  2202:   // InitLevel: Initialize the game structure once a level has been defined 
  2203:   //     by such things as game.game_width, game.game_height.
  2204: 
  2205:   int i,j;
  2206: 
  2207:   // initialize all snakes to be inactive 
  2208:   for(i=0;i<MAX_SNAKES;i++){
  2209:     game.snake[i].active = 0;
  2210:   }
  2211: 
  2212:   game.frames_rendered = 0;
  2213: 
  2214:   // calculate some global values 
  2215:   if(game.game_width > game.game_height){
  2216:     game.screen_unit_length = SCREEN_WIDTH / (game.game_width + 4);
  2217:   } else {
  2218:     game.screen_unit_length = SCREEN_HEIGHT / (game.game_height + 4);
  2219:   }
  2220: 
  2221:   // initialize/calculate game_render_width and game_render_height
  2222:   if(game.game_width > game.game_height){
  2223:     game.render_width = SCREEN_WIDTH;
  2224:     game.render_height = SCREEN_HEIGHT * 
  2225:       (((float)game.game_height + 4.0f) / 
  2226:        ((float)game.game_width + 4.0f));
  2227:   } else {
  2228:     game.render_height = SCREEN_HEIGHT;
  2229:     game.render_width = SCREEN_WIDTH * 
  2230:       (((float)game.game_width + 4.0f) / 
  2231:        ((float)game.game_height + 4.0f));
  2232:   }
  2233: 
  2234:   // initialize timetracking state
  2235:   game.time_of_last_gametick.tv_sec = 0; 
  2236:   game.time_of_last_gametick.tv_usec = 0;
  2237:   game.time_of_last_render.tv_sec = 0; 
  2238:   game.time_of_last_render.tv_usec = 0;
  2239: 
  2240:   // initialize the matrix
  2241:   for(i=0;i<game.game_width;i++){
  2242:     for(j=0;j<game.game_height;j++){
  2243:       game.matrix[i][j].snake = 0;
  2244:       game.matrix[i][j].next.x = -1;
  2245:       game.matrix[i][j].next.y = -1;
  2246:     }
  2247:   }
  2248: }
  2249: 
  2250: void Usage(void){
  2251:   // snake3d --help
  2252:   fprintf(stderr,
  2253: 	  "Usage: snake3d [OPTION]\n"
  2254: 	  "-w NUM = set initial classic game width to NUM\n"
  2255: 	  "-h NUM = set initial classic game height to NUM\n"
  2256: 	  "-l NUM = set initial classic snake length to NUM (must be >= 2)\n"
  2257: 	  "-t NUM = set classic timeslice to NUM (in usec)\n"
  2258: 	  "-s = disable sound\n"
  2259: 	  "-f = fullscreen\n"
  2260: 	  "-r = show framerate and other statistics\n"
  2261: 	  );
  2262:   exit(1);
  2263: }
  2264: 
  2265: void ReadGameHistory(void){
  2266:   // ReadGameHistory: read the 'saved' game data file.  Create a 
  2267:   //     new initialized file if one doesn't already exist
  2268: 
  2269:   FILE *f;
  2270:   int i;
  2271: 
  2272:   // HACK HACK HACK (PACKAGING)
  2273:   f = fopen( "/tmp/snake3d_data", "r" );
  2274:   if(f){
  2275:     fread((void*)&history, sizeof(GameHistory), 1, f);
  2276:     // HACK HACK HACK, need to check basic integrity of datafile
  2277:     // CheckGameHistory();
  2278:     fclose(f);
  2279:   } else {
  2280:     // initialize history
  2281:     for(i=0; i<NUM_HI_SCORES; i++){
  2282:       history.classic_hiscores[i].score = DEFAULT_CLASSIC_HI_SCORE - i;
  2283:       history.classic_hiscores[i].initials[0] = 'f';
  2284:       history.classic_hiscores[i].initials[1] = 'b';
  2285:       history.classic_hiscores[i].initials[2] = 'n';
  2286: 
  2287:       history.quest_hiscores[i].score = DEFAULT_QUEST_HI_SCORE - i;
  2288:       history.quest_hiscores[i].initials[0] = 'f';
  2289:       history.quest_hiscores[i].initials[1] = 'b';
  2290:       history.quest_hiscores[i].initials[2] = 'n';
  2291:     }
  2292: 
  2293:     // write history to a file
  2294:     WriteGameHistory();
  2295:   }
  2296: }
  2297: 
  2298: void WriteGameHistory(void){
  2299:   // WriteGameHistory: write the current contents of history to the
  2300:   //     persistent datafile
  2301: 
  2302:   FILE *f;
  2303: 
  2304:   // write history to a file
  2305:   f = fopen( "/tmp/snake3d_data", "w" );
  2306:   if(f){
  2307:     fwrite((void*)&history, sizeof(GameHistory), 1, f);
  2308:     fclose(f);
  2309:   } else {
  2310:     debug("failed to write game data, please check the permissions of /tmp/snake3d_data\n");
  2311:   }
  2312: }
  2313: 
  2314: void debug_dump_matrix(void){
  2315:   // debug_dump_matrix: for debugging, one can wire a hotkey to dump 
  2316:   //     the game state
  2317:   int i, j;
  2318: 
  2319:   for(i=0;i<game.game_height;i++){
  2320:     for(j=0;j<game.game_width;j++){
  2321:       debug("%d ", game.matrix[j][game.game_height - 1 - i].snake);
  2322:     }
  2323:     debug("\n");
  2324:   }
  2325: 
  2326:   /*
  2327:   // TEMPORARY FOR EXTRA INFO
  2328:   debug("\n");
  2329:   debug("\n");
  2330:   for(i=0;i<game.game_height;i++){
  2331:     for(j=0;j<game.game_width;j++){
  2332:       debug("(%d,%d) ", 
  2333: 	    game.matrix[j][game.game_height - 1 - i].next.x,
  2334: 	    game.matrix[j][game.game_height - 1 - i].next.x);
  2335:     }
  2336:     debug("\n");
  2337:   }
  2338:   */
  2339: }
  2340: 
  2341: void JoystickInit(void){
  2342:   // JoystickInit: name says it all
  2343: 
  2344:   int version = 0x000800;
  2345:   char name[128] = "Unknown";
  2346:   unsigned char axes = 2;
  2347:   unsigned char buttons = 2;
  2348:   int i;
  2349: 
  2350:   for(i=0;i<10;i++) jstate.j1_button_buffer[i] = 0;
  2351:   for(i=0;i<10;i++) jstate.j2_button_buffer[i] = 0;
  2352: 
  2353:   if ((jstate.j1_fd = open("/dev/js0", O_RDONLY)) < 0) 
  2354:     output("Snake3D: 1st joystick not found (/dev/js0), Player 1 joystick not being used\n");
  2355:   if ((jstate.j2_fd = open("/dev/js1", O_RDONLY)) < 0) 
  2356:     output("Snake3D: 2nd joystick not found (/dev/js1), Player 2 joystick not being used\n");
  2357: 
  2358:   ioctl(jstate.j1_fd, JSIOCGVERSION, &version);
  2359:   ioctl(jstate.j1_fd, JSIOCGAXES, &axes);
  2360:   ioctl(jstate.j1_fd, JSIOCGBUTTONS, &buttons);
  2361:   ioctl(jstate.j1_fd, JSIOCGNAME(128), name);
  2362:   
  2363:   //  debug("Joystick (%s) has %d axes and %d buttons. Driver version is %d.%d.%d.\n",
  2364:   //	 name, axes, buttons, version >> 16, (version >> 8) & 0xff, version & 0xff);
  2365: 
  2366:   ioctl(jstate.j2_fd, JSIOCGVERSION, &version);
  2367:   ioctl(jstate.j2_fd, JSIOCGAXES, &axes);
  2368:   ioctl(jstate.j2_fd, JSIOCGBUTTONS, &buttons);
  2369:   ioctl(jstate.j2_fd, JSIOCGNAME(128), name);
  2370:   
  2371:   //  debug("Joystick (%s) has %d axes and %d buttons. Driver version is %d.%d.%d.\n",
  2372:   //	 name, axes, buttons, version >> 16, (version >> 8) & 0xff, version & 0xff);
  2373: 
  2374:   /* read initial events */
  2375:   // used to be JoystickProcess(), which became joy_game, for now we'll assume 
  2376:   //     perhaps incorrectly that initial events will not cause odd behaviour
  2377: }
  2378: 
  2379: void JoystickFini(void){
  2380:   // Joystick Fini: opposite of JoystickInit
  2381: 
  2382:   if(jstate.j1_fd >= 0){
  2383:     if (close(jstate.j1_fd) < 0) {
  2384:       perror("snake3d");
  2385:       exit(1);
  2386:     }
  2387:   }
  2388: 
  2389:   if(jstate.j2_fd >= 0){
  2390:     if (close(jstate.j2_fd) < 0) {
  2391:       perror("snake3d");
  2392:       exit(1);
  2393:     }
  2394:   }
  2395: }
  2396: 
  2397: void joy(void){
  2398:   // joy:  Effectively our registered callback routine to be executed
  2399:   //     whenever the user has inputted with the gamepad/joystick
  2400: 
  2401:   struct js_event js;
  2402: 
  2403:   // 
  2404:   // J1
  2405:   //
  2406:   if(jstate.j1_fd >= 0){
  2407:     fcntl(jstate.j1_fd, F_SETFL, O_NONBLOCK);
  2408:     
  2409:     while (read(jstate.j1_fd, &js, sizeof(struct js_event)) == 
  2410: 	   sizeof(struct js_event))  {
  2411:       switch(js.type & ~JS_EVENT_INIT) {
  2412:       case JS_EVENT_BUTTON:
  2413: 	jstate.j1_button[js.number] = js.value;
  2414: 	if(js.value){
  2415: 	  /* handle rising edge events here */
  2416: 	  switch(js.number){
  2417: 	  default:
  2418: 	    key(ENTERKEY, 0, 0);
  2419: 	    break;
  2420: 	  }
  2421: 	} else {
  2422: 	  /* handle falling edge events here */
  2423: 	}
  2424: 	break;
  2425:       case JS_EVENT_AXIS:
  2426: 	jstate.j1_axis[js.number] = js.value;
  2427: 	switch(js.number){
  2428: 	case 0:
  2429: 	  /* x axis */
  2430: 	  if(js.value < 0) key(P1_LEFTKEY, 0, 0);
  2431: 	  if(js.value > 0) key(P1_RIGHTKEY, 0, 0);
  2432: 	  break;
  2433: 	case 1:
  2434: 	  /* y axis */
  2435: 	  if(js.value < 0) key(P1_UPKEY, 0, 0);
  2436: 	  if(js.value > 0) key(P1_DOWNKEY, 0, 0);
  2437: 	  break;
  2438: 	default:
  2439: 	  break;
  2440: 	}
  2441: 	break;
  2442:       }
  2443:     }
  2444:     
  2445:     if (errno != EAGAIN) {
  2446:       perror("snake3d: error reading");
  2447:       exit (1);
  2448:     }
  2449: 
  2450:   }
  2451: 
  2452:   // 
  2453:   // J2
  2454:   //
  2455:   if(jstate.j2_fd >= 0){
  2456:     fcntl(jstate.j2_fd, F_SETFL, O_NONBLOCK);
  2457:     
  2458:     while (read(jstate.j2_fd, &js, sizeof(struct js_event)) == 
  2459: 	   sizeof(struct js_event))  {
  2460:       switch(js.type & ~JS_EVENT_INIT) {
  2461:       case JS_EVENT_BUTTON:
  2462: 	jstate.j2_button[js.number] = js.value;
  2463: 	if(js.value){
  2464: 	  /* handle rising edge events here */
  2465: 	  switch(js.number){
  2466: 	  default:
  2467: 	    key(ENTERKEY, 0, 0);
  2468: 	    break;
  2469: 	  }
  2470: 	} else {
  2471: 	  /* handle falling edge events here */
  2472: 	}
  2473: 	break;
  2474:       case JS_EVENT_AXIS:
  2475: 	jstate.j2_axis[js.number] = js.value;
  2476: 	switch(js.number){
  2477: 	case 0:
  2478: 	  /* x axis */
  2479: 	  if(js.value < 0) key(P2_LEFTKEY, 0, 0);
  2480: 	  if(js.value > 0) key(P2_RIGHTKEY, 0, 0);
  2481: 	  break;
  2482: 	case 1:
  2483: 	  /* y axis */
  2484: 	  if(js.value < 0) key(P2_UPKEY, 0, 0);
  2485: 	  if(js.value > 0) key(P2_DOWNKEY, 0, 0);
  2486: 	  break;
  2487: 	default:
  2488: 	  break;
  2489: 	}
  2490: 	break;
  2491:       }
  2492:     }
  2493:     
  2494:     if (errno != EAGAIN) {
  2495:       perror("snake3d: error reading");
  2496:       exit (1);
  2497:     }
  2498:   }
  2499: }
  2500: 
  2501: