Appendix A. Full Code Listings

Code from Chapter 7

Example A-1. Space Raiders with optimized dynamic network sound and state loader
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CH7EX9: Space Raiders With Optimized Dynamic Network
       Sound And State Loader</title>
<script src="modernizr.js"></script>
<script type="text/javascript">
window.addEventListener('load', eventWindowLoaded, false);

function eventWindowLoaded() {

   canvasApp();

}

function supportedAudioFormat(audio) {
   var returnExtension = "";
   if (audio.canPlayType("audio/ogg") =="probably" ||
       audio.canPlayType("audio/ogg") == "maybe") {
         returnExtension = "ogg";   } else if(audio.canPlayType("audio/wav")
            =="probably" ||
       audio.canPlayType("audio/wav") == "maybe") {
         returnExtension = "wav";
   } else if(audio.canPlayType("audio/mp3") == "probably" ||
       audio.canPlayType("audio/mp3") == "maybe") {
         returnExtension = "mp3";
   }

   return returnExtension;

}

function canvasSupport () {
     return Modernizr.canvas;
}

function canvasApp() {

   var STATE_INIT  = 10;
   var STATE_LOADING = 20;
   var STATE_RESET  = 30;
   var STATE_PLAYING = 40;
   var appState = STATE_INIT;
   var loadCount= 0;
   var itemsToLoad = 0;
   var alienImage = new Image();
   var missileImage = new Image();
   var playerImage = new Image();

   var SOUND_EXPLODE = "explode1";
   var SOUND_SHOOT  = "shoot1";
   var MAX_SOUNDS = 6;
   var soundPool = new Array();
   var explodeSound ;
   var explodeSound2 ;
   var explodeSound3 ;
   var shootSound;
   var shootSound2;
   var shootSound3;
   var audioType;

   var mouseX;
   var mouseY;
   var player = {x:250,y:475};
   var aliens = new Array();
   var missiles = new Array();

   var ALIEN_START_X = 25;
   var ALIEN_START_Y = 25;
   var ALIEN_ROWS = 5;
   var ALIEN_COLS = 8;
   var ALIEN_SPACING = 40;
   if (!canvasSupport()) {
          return;
        }

   var theCanvas = document.getElementById("canvasOne");
   var context = theCanvas.getContext("2d");

  function itemLoaded(event) {

   loadCount++;
   if (loadCount >= itemsToLoad) {

      shootSound.removeEventListener("canplaythrough",itemLoaded, false);
      shootSound2.removeEventListener("canplaythrough",itemLoaded, false);
      shootSound3.removeEventListener("canplaythrough",itemLoaded, false);
      explodeSound.removeEventListener("canplaythrough",itemLoaded,false);
      explodeSound2.removeEventListener("canplaythrough",itemLoaded,false);
      explodeSound3.removeEventListener("canplaythrough",itemLoaded,false);
      soundPool.push({name:"explode1", element:explodeSound, played:false});
      soundPool.push({name:"explode1", element:explodeSound2, played:false});
      soundPool.push({name:"explode1", element:explodeSound3, played:false});
      soundPool.push({name:"shoot1", element:shootSound, played:false});
      soundPool.push({name:"shoot1", element:shootSound2, played:false});
      soundPool.push({name:"shoot1", element:shootSound3, played:false});

      appState = STATE_RESET;

   }

  }

  function initApp() {
   loadCount=0;
   itemsToLoad = 9;
   explodeSound = document.createElement("audio");
   document.body.appendChild(explodeSound);
   audioType = supportedAudioFormat(explodeSound);
   explodeSound.addEventListener("canplaythrough",itemLoaded,false);
   explodeSound.setAttribute("src", "explode1." + audioType);

   explodeSound2 = document.createElement("audio");
   document.body.appendChild(explodeSound2);
   explodeSound2.addEventListener("canplaythrough",itemLoaded,false);
   explodeSound2.setAttribute("src", "explode1." + audioType);

   explodeSound3 = document.createElement("audio");
   document.body.appendChild(explodeSound3);
   explodeSound3.addEventListener("canplaythrough",itemLoaded,false);
   explodeSound3.setAttribute("src", "explode1." + audioType);

   shootSound = document.createElement("audio");
   document.body.appendChild(shootSound);
   shootSound.addEventListener("canplaythrough",itemLoaded,false);
   shootSound.setAttribute("src", "shoot1." + audioType);

   shootSound2 = document.createElement("audio");
   document.body.appendChild(shootSound2);
   shootSound2.addEventListener("canplaythrough",itemLoaded,false);
   shootSound2.setAttribute("src", "shoot1." + audioType);

   shootSound3 = document.createElement("audio");
   document.body.appendChild(shootSound3);
   shootSound3.addEventListener("canplaythrough",itemLoaded,false);
   shootSound3.setAttribute("src", "shoot1." + audioType);

   alienImage = new Image();
   alienImage.onload = itemLoaded;
   alienImage.src = "alien.png";
   playerImage = new Image();
   playerImage.onload = itemLoaded;
   playerImage.src = "player.png";
   missileImage = new Image();
   missileImage.onload = itemLoaded;
   missileImage.src = "missile.png";
   appState = STATE_LOADING;
   }

  function startLevel() {

      for (var r = 0; r < ALIEN_ROWS; r++) {
         for( var c= 0; c < ALIEN_COLS; c++) {
            aliens.push({speed:2,x:ALIEN_START_X+c*ALIEN_SPACING, y:ALIEN_START_Y+r*
                ALIEN_SPACING,width:alienImage.width, height:alienImage.height});
         }
      }
   }

  function resetApp() {

   playSound(SOUND_EXPLODE,0);
   playSound(SOUND_SHOOT,0);
   startLevel();
   appState = STATE_PLAYING;

  }

  function  drawScreen () {

      //Move missiles
      for (var i=missiles.length1; i>= 0;i−−) {
         missiles[i].y = missiles[i].speed;
         if (missiles[i].y < (0-missiles[i].height)) {
            missiles.splice(i,1);
         }

      }

      //Move Aliens
      for (var i=aliens.length1; i>= 0;i−−) {
         aliens[i].x += aliens[i].speed;
         if (aliens[i].x > (theCanvas.width-aliens[i].width) || aliens[i].x < 0) {
            aliens[i].speed *= 1;
            aliens[i].y += 20;
         }
         if (aliens[i].y > theCanvas.height) {
            aliens.splice(i,1);
         }

      }

      //Detect Collisions
      missile: for (var i=missiles.length1; i>= 0;i−−) {
         var tempMissile = missiles[i]
         for (var j=aliens.length1; j>= 0;j−−) {
            var tempAlien =aliens[j];
            if (hitTest(tempMissile,tempAlien)) {
               playSound(SOUND_EXPLODE,.5);
               missiles.splice(i,1);
               aliens.splice(j,1);
               break missile;
             }
         }

         if (aliens.length <=0) {
            appState = STATE_RESET;
         }
      }

      //Background
      context.fillStyle = "#000000";
      context.fillRect(0, 0, theCanvas.width, theCanvas.height);
      //Box
      context.strokeStyle = "#EEEEEE";
      context.strokeRect(5,  5, theCanvas.width10, theCanvas.height10);

      //Draw Player
      context.drawImage(playerImage,player.x,player.y);

      //Draw Missiles
      for (var i=missiles.length1; i>= 0;i−−) {
         context.drawImage(missileImage,missiles[i].x,missiles[i].y);

      }

      //draw aliens
      for (var i=aliens.length1; i>= 0;i−−) {
         context.drawImage(alienImage,aliens[i].x,aliens[i].y);

      }

      //Draw Text
      context.fillStyle = "#FFFFFF";
      context.fillText  ("Active Sounds: " + soundPool.length,  200 ,480);

   }

   function hitTest(image1,image2)  {
      r1left = image1.x;
      r1top = image1.y;
      r1right = image1.x + image1.width;
      r1bottom = image1.y + image1.height;
      r2left = image2.x;
      r2top = image2.y;
      r2right = image2.x + image2.width;
      r2bottom = image2.y + image2.height;
      retval = false;

      if ( (r1left > r2right) || (r1right < r2left) || (r1bottom < r2top) ||
           (r1top > r2bottom) ) {
         retval = false;
      } else {
         retval = true;
      }

      return retval;
   }

   function eventMouseMove(event) {
      var x;
      var y;
      if (event.pageX || event.pageY) {
         x = event.pageX;
         y = event.pageY;
      } else {
         x = e.clientX + document.body.scrollLeft
            + document.documentElement.scrollLeft;
         y = e.clientY + document.body.scrollTop
            + document.documentElement.scrollTop;
      }
      x -= theCanvas.offsetLeft;
      y -= theCanvas.offsetTop;

      mouseX=x;
      mouseY=y;
      player.x = mouseX;
      player.y = mouseY;

   }

   function eventMouseUp(event) {

      missiles.push({speed:5, x: player.x+.5*playerImage.width,
         y:player.y-missileImage.height,width:missileImage.width,
         height:missileImage.height});

      playSound(SOUND_SHOOT,.5);
   }

   function playSound(sound,volume) {

      var soundFound = false;
      var soundIndex = 0;
      var tempSound;

      if (soundPool.length> 0) {
         while (!soundFound && soundIndex < soundPool.length) {
            var tSound = soundPool[soundIndex];
            if ((tSound.element.ended || !tSound.played) && tSound.name == sound) {
               soundFound = true;
               tSound.played = true;
            } else {
               soundIndex++;
            }

         }
      }
      if (soundFound) {
         tempSound = soundPool[soundIndex].element;
         tempSound.volume = volume;
         tempSound.play();

      } else if (soundPool.length < MAX_SOUNDS){
         tempSound = document.createElement("audio");
         tempSound.setAttribute("src", sound + "." + audioType);
         tempSound.volume = volume;
         tempSound.play();
         soundPool.push({name:sound, element:tempSound, type:audioType, played:true});
      }

   }

   function run() {
      switch(appState) {
      case STATE_INIT:
         initApp();
         break;
      case STATE_LOADING:
         //wait for call backs
         break;
      case STATE_RESET:
         resetApp();
         break;
      case STATE_PLAYING:
         drawScreen();
         break;

   }

  }

   theCanvas.addEventListener("mouseup",eventMouseUp, false);
   theCanvas.addEventListener("mousemove",eventMouseMove, false);

   function gameLoop() {
         window.setTimeout(gameLoop, 20);
         run()
    }

   gameLoop();
}


</script>
</head>

<body>
<canvas id="canvasOne" width="500" height="500"
    style="position: absolute; top: 50px; left: 50px;">>
 Your browser does not support HTML5 Canvas.
</canvas>
</body>
</html>

Code from Chapter 9

Geo Blaster Extended Full Source

Example A-2. Geo Blaster Extended full source code listing
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CH9EX1: Geo Blaster Extended</title>
<script src="modernizr-1.6.min.js"></script>
<script type="text/javascript">
window.addEventListener('load', eventWindowLoaded, false);
function eventWindowLoaded() {

    canvasApp();

}

function canvasSupport () {
   return Modernizr.canvas;
}

function supportedAudioFormat(audio) {
   var returnExtension = "";
   if (audio.canPlayType("audio/ogg") =="probably" ||
       audio.canPlayType("audio/ogg") == "maybe") {
         returnExtension = "ogg";
   } else if(audio.canPlayType("audio/wav") =="probably" ||
       audio.canPlayType("audio/wav") == "maybe") {
         returnExtension = "wav";
   } else if(audio.canPlayType("audio/wav") == "probably" ||
       audio.canPlayType("audio/wav") == "maybe") {
         returnExtension = "mp3";
   }

   return returnExtension;

}

function canvasApp(){

   if (!canvasSupport()) {
          return;
     }else{
       theCanvas = document.getElementById("canvas");
       context = theCanvas.getContext("2d");
   }

   //sounds
   var SOUND_EXPLODE = "explode1";
   var SOUND_SHOOT = "shoot1";
   var SOUND_SAUCER_SHOOT = "saucershoot"
   var MAX_SOUNDS = 9;
   var soundPool = new Array();
   var explodeSound;
   var explodeSound2;
   var explodeSound3;
   var shootSound;
   var shootSound2;
   var shootSound3;
   var saucershootSound;
   var saucershootSound2;
   var saucershootSound3;
   var audioType;

   //application states
   var GAME_STATE_INIT = 0;
   var GAME_STATE_WAIT_FOR_LOAD = 5;
   var GAME_STATE_TITLE = 10;
   var GAME_STATE_NEW_GAME = 20;
   var GAME_STATE_NEW_LEVEL = 30;
   var GAME_STATE_PLAYER_START = 40;
   var GAME_STATE_PLAY_LEVEL = 50;
   var GAME_STATE_PLAYER_DIE = 60;
   var GAME_STATE_GAME_OVER = 70;
   var currentGameState = 0;
   var currentGameStateFunction = null;

   //title screen
   var titleStarted = false;

   //game over screen
   var gameOverStarted = false;

   //objects for game play

   //game environment
   var score = 0;
   var level = 0;   var extraShipAtEach = 10000;
   var extraShipsEarned = 0;
   var playerShips = 3;

   //playfield
   var xMin = 0;
   var xMax = 400;
   var yMin = 0;
   var yMax = 400;

   //score values
   var bigRockScore = 50;
   var medRockScore = 75;
   var smlRockScore = 100;
   var saucerScore = 300;

   //rock scale constants
   var ROCK_SCALE_LARGE = 1;
   var ROCK_SCALE_MEDIUM = 2;
   var ROCK_SCALE_SMALL = 3;

   //create game objects and arrays
   var player = {};
   var rocks = [];
   var saucers = [];
   var playerMissiles = [];
   var particles = [];
   var saucerMissiles = [];
   var particlePool = [];
   var maxParticles = 200;
   var newParticle;
   var tempParticle;

   //level specific
   var levelRockMaxSpeedAdjust = 1;
   var levelSaucerMax = 1;
   var levelSaucerOccurrenceRate = 25;
   var levelSaucerSpeed = 1;
   var levelSaucerFireDelay = 300;
   var levelSaucerFireRate = 30;
   var levelSaucerMissileSpeed = 1;

   //keyPresses
   var keyPressList=[];

   //tile sheets
   var shipTiles;
   var shipTiles2;
   var saucerTiles;
   var largeRockTiles;
   var mediumRockTiles;
   var smallRockTiles;
   var particleTiles;

    //loading
   var loadcCount=0;
   var itemsToLoad=0;

    function itemLoaded(event) {

      loadCount++;
      //console.log("loading:" + loadCount)
      if (loadCount >= itemsToLoad) {

         shootSound.removeEventListener("canplaythrough",itemLoaded, false);
         shootSound2.removeEventListener("canplaythrough",itemLoaded, false);
         shootSound3.removeEventListener("canplaythrough",itemLoaded, false);
         explodeSound.removeEventListener("canplaythrough",itemLoaded,false);
         explodeSound2.removeEventListener("canplaythrough",itemLoaded,false);
         explodeSound3.removeEventListener("canplaythrough",itemLoaded,false);
         saucershootSound.removeEventListener("canplaythrough",itemLoaded,false);
         saucershootSound2.removeEventListener("canplaythrough",itemLoaded,
          false);
         saucershootSound3.removeEventListener("canplaythrough",itemLoaded,
          false);

         soundPool.push({name:"explode1", element:explodeSound, played:false});
         soundPool.push({name:"explode1", element:explodeSound2, played:false});
         soundPool.push({name:"explode1", element:explodeSound3, played:false});
         soundPool.push({name:"shoot1", element:shootSound, played:false});
         soundPool.push({name:"shoot1", element:shootSound2, played:false});
         soundPool.push({name:"shoot1", element:shootSound3, played:false});
         soundPool.push({name:"saucershoot", element:saucershootSound,
          played:false});
         soundPool.push({name:"saucershoot", element:saucershootSound2,
          played:false});
         soundPool.push({name:"saucershoot", element:saucershootSound3,
          played:false});

         switchGameState(GAME_STATE_TITLE)

      }

   }
   function playSound(sound,volume) {
      ConsoleLog.log("play sound" + sound);
      var soundFound = false;
      var soundIndex = 0;
      var tempSound;

      if (soundPool.length> 0) {
         while (!soundFound && soundIndex < soundPool.length) {

            var tSound = soundPool[soundIndex];
            if ((tSound.element.ended || !tSound.played) && tSound.name == sound) {
               soundFound = true;
               tSound.played = true;
            } else {
               soundIndex++;
            }

         }
      }

    if (soundFound) {
         ConsoleLog.log("sound found");
         tempSound = soundPool[soundIndex].element;
         //tempSound.setAttribute("src", sound + "." + audioType);
         //tempSound.loop = false;
         //tempSound.volume = volume;
         tempSound.play();

      } else if (soundPool.length < MAX_SOUNDS){
         ConsoleLog.log("sound not found");
         tempSound = document.createElement("audio");
         tempSound.setAttribute("src", sound + "." + audioType);
         tempSound.volume = volume;
         tempSound.play();
         soundPool.push({name:sound, element:tempSound, type:audioType,
          played:true});
      }

   }
   function runGame(){
      currentGameStateFunction();
   }

   function switchGameState(newState) {
      currentGameState = newState;
      switch (currentGameState) {

         case GAME_STATE_INIT:
            currentGameStateFunction = gameStateInit;
            break;
         case GAME_STATE_WAIT_FOR_LOAD:
            currentGameStateFunction = gameStateWaitForLoad;
            break;
         case GAME_STATE_TITLE:
             currentGameStateFunction = gameStateTitle;
             break;
         case GAME_STATE_NEW_GAME:
             currentGameStateFunction = gameStateNewGame;
             break;
         case GAME_STATE_NEW_LEVEL:
             currentGameStateFunction = gameStateNewLevel;
             break;
         case GAME_STATE_PLAYER_START:
             currentGameStateFunction = gameStatePlayerStart;
             break;
         case GAME_STATE_PLAY_LEVEL:
             currentGameStateFunction = gameStatePlayLevel;
             break;
         case GAME_STATE_PLAYER_DIE:
             currentGameStateFunction = gameStatePlayerDie;
             break;
         case GAME_STATE_GAME_OVER:
             currentGameStateFunction = gameStateGameOver;
             break;

      }

   }

   function gameStateWaitForLoad(){
      //do nothing while loading events occur
      console.log("doing nothing...")
   }

   function createObjectPools(){
      for (var ctr=0;ctr<maxParticles;ctr++){
         var newParticle = {};
         particlePool.push(newParticle)
      }
      console.log("particlePool=" + particlePool.length)
   }

   function gameStateInit() {
      createObjectPools();

      loadCount = 0;
      itemsToLoad = 16; //change to 7 if experiencing problems in IE

      explodeSound = document.createElement("audio");
      document.body.appendChild(explodeSound);
      audioType = supportedAudioFormat(explodeSound);
      explodeSound.setAttribute("src", "explode1." + audioType);
      explodeSound.addEventListener("canplaythrough",itemLoaded,false);

      explodeSound2 = document.createElement("audio");
      document.body.appendChild(explodeSound2);
      explodeSound2.setAttribute("src", "explode1." + audioType);
      explodeSound2.addEventListener("canplaythrough",itemLoaded,false);

      explodeSound3 = document.createElement("audio");
      document.body.appendChild(explodeSound3);
      explodeSound3.setAttribute("src", "explode1." + audioType);
      explodeSound3.addEventListener("canplaythrough",itemLoaded,false);

      shootSound = document.createElement("audio");
      audioType = supportedAudioFormat(shootSound);
      document.body.appendChild(shootSound);
      shootSound.setAttribute("src", "shoot1." + audioType);
      shootSound.addEventListener("canplaythrough",itemLoaded,false);

      shootSound2 = document.createElement("audio");
      document.body.appendChild(shootSound2);
      shootSound2.setAttribute("src", "shoot1." + audioType);
      shootSound2.addEventListener("canplaythrough",itemLoaded,false);

      shootSound3 = document.createElement("audio");
      document.body.appendChild(shootSound3);
      shootSound3.setAttribute("src", "shoot1." + audioType);
      shootSound3.addEventListener("canplaythrough",itemLoaded,false);

      saucershootSound = document.createElement("audio");
      audioType = supportedAudioFormat(saucershootSound);
      document.body.appendChild(saucershootSound);
      saucershootSound.setAttribute("src", "saucershoot." + audioType);
      saucershootSound.addEventListener("canplaythrough",itemLoaded,false);

      saucershootSound2 = document.createElement("audio");
      document.body.appendChild(saucershootSound2);
      saucershootSound2.setAttribute("src", "saucershoot." + audioType);
      saucershootSound2.addEventListener("canplaythrough",itemLoaded,false);

      saucershootSound3 = document.createElement("audio");
      document.body.appendChild(saucershootSound3);
      saucershootSound3.setAttribute("src", "saucershoot." + audioType);
      saucershootSound3.addEventListener("canplaythrough",itemLoaded,false);

      shipTiles = new Image();
      shipTiles.src = "ship_tiles.png";
      shipTiles.onload = itemLoaded;

      shipTiles2 = new Image();
      shipTiles2.src = "ship_tiles2.png";
      shipTiles2.onload = itemLoaded;

      saucerTiles= new Image();
      saucerTiles.src = "saucer.png";
      saucerTiles.onload = itemLoaded;

      largeRockTiles = new Image();
      largeRockTiles.src = "largerocks.png";
      largeRockTiles.onload = itemLoaded;

      mediumRockTiles = new Image();
      mediumRockTiles.src = "mediumrocks.png";
      mediumRockTiles.onload = itemLoaded;

      smallRockTiles = new Image();
      smallRockTiles.src = "smallrocks.png";
      smallRockTiles.onload = itemLoaded;

      particleTiles = new Image();
      particleTiles.src = "parts.png";
      particleTiles.onload = itemLoaded;

      switchGameState(GAME_STATE_WAIT_FOR_LOAD);
   }

   function gameStateTitle() {
      if (titleStarted !=true){
         fillBackground();
         setTextStyleTitle();
         context.fillText  ("Geo Blaster X-ten-d", 120, 70);
         setTextStyle();
         context.fillText  ("Press Space To Play", 130, 140);

         setTextStyleCredits();
         context.fillText  ("An HTML5 Example Game", 125, 200);
         context.fillText  ("From our upcoming HTML5 Canvas", 100, 215);
         context.fillText  ("book on O'Reilly Press", 130, 230);

         context.fillText  ("Game Code - Jeff Fulton", 130, 260);
         context.fillText  ("Sound Manager - Steve Fulton", 120, 275);


         titleStarted = true;
      }else{
         //wait for space key click
         if (keyPressList[32]==true){
            ConsoleLog.log("space pressed");
            switchGameState(GAME_STATE_NEW_GAME);
            titleStarted = false;

         }

      }

   }

   function gameStateNewGame(){
      ConsoleLog.log("gameStateNewGame")
      //set up new game
      level = 0;
      score = 0;
      playerShips = 3;
      player.maxVelocity = 5;
      player.width = 32;
      player.height = 32;
      player.halfWidth = 16;
      player.halfHeight = 16;
      player.hitWidth = 24;
      player.hitHeight = 24;
      player.rotationalVelocity = 10; //how many degrees to turn the ship
      player.thrustAcceleration = .05;
      player.missileFrameDelay = 5;
      player.thrust = false;
      player.alpha = 1;
      player.rotation = 0;
      player.x = 0;
      player.y = 0;

      fillBackground();
      renderScoreBoard();
      switchGameState(GAME_STATE_NEW_LEVEL)

   }
   function gameStateNewLevel(){
      rocks = [];
      saucers = [];
      playerMissiles = [];
      particles = [];
      saucerMissiles = [];
      level++;
      levelRockMaxSpeedAdjust = level*.25;
      if (levelRockMaxSpeedAdjust > 3){
         levelRockMaxSpeed = 3;
      }

      levelSaucerMax = 1+Math.floor(level/10);
      if (levelSaucerMax > 5){
         levelSaucerMax = 5;
      }
      levelSaucerOccurrenceRate = 10+3*level;
      if (levelSaucerOccurrenceRate > 35){
         levelSaucerOccurrenceRate = 35;
      }
      levelSaucerSpeed = 1+.5*level;
      if (levelSaucerSpeed>5){
         levelSaucerSpeed = 5;
      }
      levelSaucerFireDelay = 120-10*level;
      if (levelSaucerFireDelay<20) {
         levelSaucerFireDelay = 20;
      }

      levelSaucerFireRate = 20 + 3*level;
      if (levelSaucerFireRate<50) {
         levelSaucerFireRate = 50;
      }

      levelSaucerMissileSpeed = 1+.2*level;
      if (levelSaucerMissileSpeed > 4){
         levelSaucerMissileSpeed = 4;
      }
      //create level rocks
      for (var newRockctr=0;newRockctr<level+3;newRockctr++){
            var newRock = {};

            newRock.scale = 1;
            //scale
            //1 = large
            //2 = medium
            //3 = small
            //these will be used as the divisor for the new size
            //50/1 = 50
            //50/2 = 25
            //50/3 = 16
            newRock.width = 64;
            newRock.height = 64;
            newRock.halfWidth = 32;
            newRock.halfHeight = 32;

            newRock.hitWidth = 48;
            newRock.hitHeight = 48;

            //start all new rocks in upper left for ship safety
            newRock.x = Math.floor(Math.random()*50);
            //ConsoleLog.log("newRock.x=" + newRock.x);
            newRock.y = Math.floor(Math.random()*50);
            //ConsoleLog.log("newRock.y=" + newRock.y);
            newRock.dx = (Math.random()*2)+levelRockMaxSpeedAdjust;
            if (Math.random()<.5){
               newRock.dx *= 1;
            }
            newRock.dy=(Math.random()*2)+levelRockMaxSpeedAdjust;
            if (Math.random()<.5){
               newRock.dy *= 1;
            }
            //rotation speed and direction

            if (Math.random()<.5){
               newRock.rotationInc = 1;
            }else{
               newRock.rotationInc = 1;
            }

            newRock.animationDelay = Math.floor(Math.random()*3+1);
            newRock.animationCount = 0;

            newRock.scoreValue = bigRockScore;
            newRock.rotation = 0;

            rocks.push(newRock);
            //ConsoleLog.log("rock created rotationInc=" + newRock.rotationInc);
         }
      resetPlayer();
      switchGameState(GAME_STATE_PLAYER_START);

   }

   function gameStatePlayerStart(){

      fillBackground();
      renderScoreBoard();
      if (player.alpha < 1){
         player.alpha += .01;

         ConsoleLog.log("player.alpha=" + context.globalAlpha)
      }else{
         switchGameState(GAME_STATE_PLAY_LEVEL);
         player.safe = false; // added chapter 9

      }

      //renderPlayerShip(player.x, player.y,270,1);
      context.globalAlpha = 1;
      //new in chapter 9
      checkKeys();
      update();
      render(); //added chapter 9
      checkCollisions();
      checkForExtraShip();
      checkForEndOfLevel();
      frameRateCounter.countFrames();

   }

   function gameStatePlayLevel(){
      checkKeys();
      update();
      render();
      checkCollisions();
      checkForExtraShip();
      checkForEndOfLevel();
      frameRateCounter.countFrames();

   }

   function resetPlayer() {
      player.rotation = 270;
      player.x = .5*xMax;
      player.y = .5*yMax;
      player.facingX = 0;
      player.facingY = 0;
      player.movingX = 0;
      player.movingY = 0;
      player.alpha = 0;
      player.missileFrameCount = 0;
      //added chapter 9
      player.safe = true;
   }

   function checkForExtraShip() {
      if (Math.floor(score/extraShipAtEach) > extraShipsEarned) {
         playerShips++
         extraShipsEarned++;
      }
   }

   function checkForEndOfLevel(){
      if (rocks.length==0) {
         switchGameState(GAME_STATE_NEW_LEVEL);
      }
   }

   function gameStatePlayerDie(){
      if (particles.length >0 || playerMissiles.length>0) {
         fillBackground();
         renderScoreBoard();
         updateRocks();
         updateSaucers();
         updateParticles();
         updateSaucerMissiles();
         updatePlayerMissiles();
         renderRocks();
         renderSaucers();
         renderParticles();
         renderSaucerMissiles();
         renderPlayerMissiles();
         frameRateCounter.countFrames();

      }else{
         playerShips--;
         if (playerShips<1) {
            switchGameState(GAME_STATE_GAME_OVER);
         }else{
            //resetPlayer();
            switchGameState(GAME_STATE_PLAYER_START);
         }
      }
   }

   function gameStateGameOver() {
      //ConsoleLog.log("Game Over State");
      if (gameOverStarted !=true){
         fillBackground();
         renderScoreBoard();
         setTextStyle();
         context.fillText  ("Game Over!", 160, 70);
         context.fillText  ("Press Space To Play", 130, 140);

         gameOverStarted = true;
      }else{
         //wait for space key click
         if (keyPressList[32]==true){
            ConsoleLog.log("space pressed");
            switchGameState(GAME_STATE_TITLE);
            gameOverStarted = false;

         }

      }
   }

   function fillBackground() {
      // draw background and text
      context.fillStyle = '#000000';
      context.fillRect(xMin, yMin, xMax, yMax);

   }

   function setTextStyle() {
      context.fillStyle = '#ffffff';
      context.font = '15px _sans';
      context.textBaseline = 'top';
   }

   function setTextStyleTitle() {
      context.fillStyle  = '#54ebeb';
      context.font = '20px _sans';
      context.textBaseline = 'top';
   }

   function setTextStyleCredits() {
      context.fillStyle = '#ffffff';
      context.font = '12px _sans';
      context.textBaseline = 'top';
   }

   function renderScoreBoard() {

      context.fillStyle = "#ffffff";
      context.fillText('Score ' + score, 10, 20);
      renderPlayerShip(200,16,270,.75)
      context.fillText('X ' + playerShips, 220, 20);

      context.fillText('FPS: ' + frameRateCounter.lastFrameCount, 300,20)

   }

   function checkKeys() {
      //check keys

      if (keyPressList[38]==true){
      //thrust
         var angleInRadians = player.rotation * Math.PI / 180;
         player.facingX = Math.cos(angleInRadians);
         player.facingY = Math.sin(angleInRadians);

         var movingXNew = player.movingX+player.thrustAcceleration*player.facingX;
         var movingYNew = player.movingY+player.thrustAcceleration*player.facingY;

         var currentVelocity = Math.sqrt ((movingXNew*movingXNew) +
         (movingXNew*movingXNew));

         if (currentVelocity < player.maxVelocity) {
            player.movingX = movingXNew;
            player.movingY = movingYNew;
         }
         player.thrust = true;

      }else{
         player.thrust = false;
      }

      if (keyPressList[37]==true) {
         //rotate counterclockwise
         player.rotation -= player.rotationalVelocity;
         if (player.rotation <0) {
            player.rotation = 350
         }

      }

      if (keyPressList[39]==true) {
         //rotate clockwise
         player.rotation += player.rotationalVelocity;
         if (player.rotation >350) {
            player.rotation = 10
         }
      }

      if (keyPressList[32]==true) {
            if (player.missileFrameCount>player.missileFrameDelay){
            playSound(SOUND_SHOOT,.5);
            firePlayerMissile();
            player.missileFrameCount = 0;

         }
      }
   }

   function update() {
      updatePlayer();
      updatePlayerMissiles();
      updateRocks();
      updateSaucers();
      updateSaucerMissiles();
      updateParticles();
   }

   function render() {
      fillBackground();
      renderScoreBoard();
      renderPlayerShip(player.x,player.y,player.rotation,1);
      renderPlayerMissiles();
      renderRocks();
      renderSaucers();
      renderSaucerMissiles();
      renderParticles();
   }

   function updatePlayer() {
      player.missileFrameCount++;

      player.x += player.movingX*frameRateCounter.step;
      player.y += player.movingY*frameRateCounter.step;

      if (player.x > xMax) {
         player.x =- player.width;
      }else if (player.x<-player.width){
         player.x = xMax;
      }

      if (player.y > yMax) {
         player.y =- player.height;
      }else if (player.y<-player.height){
         player.y = yMax;
      }
   }

   function updatePlayerMissiles() {
      var tempPlayerMissile = {};
      var playerMissileLength=playerMissiles.length-1;
      //ConsoleLog.log("update playerMissileLength=" + playerMissileLength);
      for (var playerMissileCtr=playerMissileLength;playerMissileCtr>=0;
       playerMissileCtr--){
         //ConsoleLog.log("update player missile" + playerMissileCtr)
         tempPlayerMissile = playerMissiles[playerMissileCtr];
         tempPlayerMissile.x += tempPlayerMissile.dx*frameRateCounter.step;
         tempPlayerMissile.y += tempPlayerMissile.dy*frameRateCounter.step;
         if (tempPlayerMissile.x > xMax) {
            tempPlayerMissile.x =- tempPlayerMissile.width;
         }else if (tempPlayerMissile.x<-tempPlayerMissile.width){
            tempPlayerMissile.x = xMax;
         }

         if (tempPlayerMissile.y > yMax) {
            tempPlayerMissile.y =- tempPlayerMissile.height;
         }else if (tempPlayerMissile.y<-tempPlayerMissile.height){
            tempPlayerMissile.y = yMax;
         }

         tempPlayerMissile.lifeCtr++;
         if (tempPlayerMissile.lifeCtr > tempPlayerMissile.life){
            //ConsoleLog.log("removing player missile");
            playerMissiles.splice(playerMissileCtr,1)
            tempPlayerMissile = null;
         }
      }
   }

   function updateRocks(){

      var tempRock = {};
      var rocksLength = rocks.length-1;
      //ConsoleLog.log("update rocks length=" + rocksLength);
      for (var rockCtr=rocksLength;rockCtr>=0;rockCtr--){
         tempRock = rocks[rockCtr];
         tempRock.x += tempRock.dx*frameRateCounter.step;
         tempRock.y += tempRock.dy*frameRateCounter.step;

         tempRock.animationCount++;
         if (tempRock.animationCount > tempRock.animationDelay){
            tempRock.animationCount = 0;
            tempRock.rotation += tempRock.rotationInc;

            if (tempRock.rotation > 4){
               tempRock.rotation = 0;
            }else if (tempRock.rotation <0){
               tempRock.rotation = 4;
            }
         }

         if (tempRock.x > xMax) {
            tempRock.x = xMin-tempRock.width;
         }else if (tempRock.x<xMin-tempRock.width){
            tempRock.x = xMax;
         }

         if (tempRock.y > yMax) {
            tempRock.y = yMin-tempRock.width;
         }else if (tempRock.y<yMin-tempRock.width){
            tempRock.y = yMax;
         }
      }
   }

   function updateSaucers() {
      //first check to see if we want to add a saucer

      if (saucers.length< levelSaucerMax){
         if (Math.floor(Math.random()*100)<=levelSaucerOccurrenceRate){
            //ConsoleLog.log("create saucer")
            var newSaucer = {};

            newSaucer.width = 30;
            newSaucer.height = 13;
            newSaucer.halfHeight = 6.5;
            newSaucer.halfWidth = 15;
            newSaucer.hitWidth = 30;
            newSaucer.hitHeight = 13;
            newSaucer.scoreValue = saucerScore;
            newSaucer.fireRate = levelSaucerFireRate;
            newSaucer.fireDelay = levelSaucerFireDelay;
            newSaucer.fireDelayCount = 0;
            newSaucer.missileSpeed = levelSaucerMissileSpeed;
            newSaucer.dy = (Math.random()*2);
            if (Math.floor(Math.random)*2==1){
               newSaucer.dy *= 1;
            }

            //choose betweeen left or right edge to start
            if (Math.floor(Math.random()*2)==1){
               //start on right and go left
               newSaucer.x = 450;
               newSaucer.dx = 1*levelSaucerSpeed;

            }else{
               //left to right
               newSaucer.x = 50;
               newSaucer.dx = levelSaucerSpeed;
            }

            newSaucer.missileSpeed = levelSaucerMissileSpeed;
            newSaucer.fireDelay = levelSaucerFireDelay;
            newSaucer.fireRate = levelSaucerFireRate;
            newSaucer.y = Math.floor(Math.random()*400);

            saucers.push(newSaucer);
         }

      }

      var tempSaucer = {};
      var saucerLength = saucers.length-1;
      //ConsoleLog.log("update rocks length=" + rocksLength);
      for (var saucerCtr=saucerLength;saucerCtr>=0;saucerCtr--){
         tempSaucer = saucers[saucerCtr];

         //should saucer fire
         tempSaucer.fireDelayCount++;
         if (Math.floor(Math.random()*100) <=tempSaucer.fireRate &&
          tempSaucer.fireDelayCount>tempSaucer.fireDelay ){
            playSound(SOUND_SAUCER_SHOOT,.5);
            fireSaucerMissile(tempSaucer);
            tempSaucer.fireDelayCount=0;
         }

         var remove = false;
         tempSaucer.x += tempSaucer.dx*frameRateCounter.step;
         tempSaucer.y += tempSaucer.dy*frameRateCounter.step;

         //remove saucers on left and right edges
         if (tempSaucer.dx > 0 && tempSaucer.x >xMax){
            remove = true;
         }else if (tempSaucer.dx <0 &&tempSaucer.x<xMin-tempSaucer.width){
            remove = true;
         }

         //bounce saucers off over vertical edges
         if (tempSaucer.y > yMax || tempSaucer.y<yMin-tempSaucer.width) {
            tempSaucer.dy *= 1;
         }


         if (remove==true) {
            //remove the saucer
            saucers.splice(saucerCtr,1);
            tempSaucer = null;
         }

      }

   }
   function updateSaucerMissiles() {
      var tempSaucerMissile = {};
      var saucerMissileLength = saucerMissiles.length-1;
      for (var saucerMissileCtr = saucerMissileLength;saucerMissileCtr>=0;
       saucerMissileCtr--){
         //ConsoleLog.log("update player missile" + playerMissileCtr)
         tempSaucerMissile = saucerMissiles[saucerMissileCtr];
         tempSaucerMissile.x += tempSaucerMissile.dx*frameRateCounter.step;
         tempSaucerMissile.y += tempSaucerMissile.dy*frameRateCounter.step;
         if (tempSaucerMissile.x > xMax) {
            tempSaucerMissile.x =- tempSaucerMissile.width;
         }else if (tempSaucerMissile.x<-tempSaucerMissile.width){
            tempSaucerMissile.x = xMax;
         }

         if (tempSaucerMissile.y > yMax) {
            tempSaucerMissile.y =- tempSaucerMissile.height;
         }else if (tempSaucerMissile.y<-tempSaucerMissile.height){
            tempSaucerMissile.y = yMax;
         }

         tempSaucerMissile.lifeCtr++;
         if (tempSaucerMissile.lifeCtr > tempSaucerMissile.life){
            //remove
            saucerMissiles.splice(saucerMissileCtr,1);
            tempSaucerMissile = null;
         }
      }
   }

   function updateParticles() {

      var particleLength=particles.length-1;
      for (var particleCtr=particleLength;particleCtr>=0;particleCtr--){
         var remove = false;
         tempParticle = particles[particleCtr];
         tempParticle.x += tempParticle.dx*frameRateCounter.step;
         tempParticle.y += tempParticle.dy*frameRateCounter.step;

         tempParticle.lifeCtr++;

         if (tempParticle.lifeCtr > tempParticle.life){
            remove = true;

         } else if ((tempParticle.x > xMax) || (tempParticle.x<xMin)
           || (tempParticle.y > yMax) || (tempParticle.y<yMin)){
            remove=true;

         }
         if (remove) {
            particlePool.push(tempParticle)
            particles.splice(particleCtr,1)

         }

      }

   }

   function renderPlayerShip(x,y,rotation, scale) {
      //transformation
      context.save(); //save current state in stack
      context.globalAlpha = parseFloat(player.alpha);
      var angleInRadians = rotation * Math.PI / 180;
      var sourceX = Math.floor((player.rotation/10) % 10) * 32;
      var sourceY = Math.floor((player.rotation/10) /10) *32;
      if (player.thrust){
         context.drawImage(shipTiles2, sourceX, sourceY, 32,32,
          player.x,player.y,32,32);
      }else{
         context.drawImage(shipTiles, sourceX, sourceY, 32,32,
          player.x,player.y,32,32);
      }

      //restore context
      context.restore(); //pop old state on to screen

      context.globalAlpha = 1;

   }

   function renderPlayerMissiles() {
      var tempPlayerMissile = {};
      var playerMissileLength=playerMissiles.length-1;
      //ConsoleLog.log("render playerMissileLength=" + playerMissileLength);
      for (var playerMissileCtr=playerMissileLength;playerMissileCtr>=0;
       playerMissileCtr--){
         //ConsoleLog.log("draw player missile " + playerMissileCtr)
         tempPlayerMissile = playerMissiles[playerMissileCtr];
         context.save(); //save current state in stack
         var sourceX=Math.floor(1 % 4) * tempPlayerMissile.width;
         var sourceY=Math.floor(1 / 4) * tempPlayerMissile.height;

         context.drawImage(particleTiles, sourceX, sourceY,
          tempPlayerMissile.width,tempPlayerMissile.height,
          tempPlayerMissile.x,tempPlayerMissile.y,
          tempPlayerMissile.width,tempPlayerMissile.height);

         context.restore(); //pop old state on to screen
      }
   }
   function renderRocks() {
      var tempRock = {};
      var rocksLength = rocks.length-1;
      for (var rockCtr = rocksLength;rockCtr>=0;rockCtr--){
         context.save(); //save current state in stack
         tempRock = rocks[rockCtr];
         var sourceX = Math.floor((tempRock.rotation) % 5) * tempRock.width;
         var sourceY = Math.floor((tempRock.rotation) /5) *tempRock.height;

         switch(tempRock.scale){
            case 1:
               context.drawImage(largeRockTiles, sourceX, sourceY,
                tempRock.width,tempRock.height,tempRock.x,tempRock.y,
                tempRock.width,tempRock.height);
               break;
            case 2:
               context.drawImage(mediumRockTiles, sourceX,
                sourceY,tempRock.width,tempRock.height,tempRock.x,tempRock.y,
                tempRock.width,tempRock.height);
               break;
            case 3:
               context.drawImage(smallRockTiles, sourceX,
                sourceY,tempRock.width,tempRock.height,tempRock.x,tempRock.y,
                tempRock.width,tempRock.height);
               break;

         }
         context.restore(); //pop old state on to screen

      }
   }

   function renderSaucers() {
      var tempSaucer = {};
      var saucerLength = saucers.length-1;
      for (var saucerCtr = saucerLength;saucerCtr>=0;saucerCtr--){
         //ConsoleLog.log("saucer: " + saucerCtr);
         tempSaucer = saucers[saucerCtr];

         context.save(); //save current state in stack
         var sourceX = 0;
         var sourceY = 0;
         context.drawImage(saucerTiles, sourceX, sourceY, 30,15,
          tempSaucer.x,tempSaucer.y,30,15);
         context.restore(); //pop old state on to screen
      }
   }
   function renderSaucerMissiles() {
      var tempSaucerMissile = {};
      var saucerMissileLength = saucerMissiles.length-1;
      //ConsoleLog.log("saucerMissiles= " + saucerMissiles.length)
      for (var saucerMissileCtr=saucerMissileLength;saucerMissileCtr>=0;
       saucerMissileCtr--){
         //ConsoleLog.log("draw player missile " + playerMissileCtr)
         tempSaucerMissile = saucerMissiles[saucerMissileCtr];
         context.save(); //save current state in stack
         var sourceX = Math.floor(0 % 4) * tempSaucerMissile.width;
         var sourceY = Math.floor(0 / 4) * tempSaucerMissile.height;

         context.drawImage(particleTiles, sourceX, sourceY,
          tempSaucerMissile.width,tempSaucerMissile.height,
          tempSaucerMissile.x,tempSaucerMissile.y,tempSaucerMissile.width,
          tempSaucerMissile.height);

         context.restore(); //pop old state on to screen

      }
   }

   function renderParticles() {

      var tempParticle = {};
      var particleLength = particles.length-1;
      for (var particleCtr=particleLength;particleCtr>=0;particleCtr--){
         tempParticle = particles[particleCtr];
         context.save(); //save current state in stack

         var tile;

         //console.log("part type=" + tempParticle.type)
         switch(tempParticle.type){
            case 0: // saucer
               tile = 0;
               break;
            case 1: //large rock
               tile = 2
               break;
            case 2: //medium rock
               tile = 3;
               break;
            case 3: //small rock
               tile = 0;
               break;
            case 4: //player
               tile = 1;
               break;

         }

         var sourceX = Math.floor(tile % 4) * tempParticle.width;
         var sourceY = Math.floor(tile / 4) * tempParticle.height;

         context.drawImage(particleTiles, sourceX, sourceY,
          tempParticle.width,tempParticle.height,tempParticle.x,
          tempParticle.y,tempParticle.width,tempParticle.height);

         context.restore(); //pop old state on to screen

      }
   }

   function checkCollisions() {

      //loop through rocks then missiles.
      //There will always be rocks and a ship,
      //but there will not always be missiles.
      var tempRock = {};
      var rocksLength = rocks.length-1;
      var tempPlayerMissile = {};
      var playerMissileLength = playerMissiles.length-1;
      var saucerLength = saucers.length-1;
      var tempSaucer = {};
      var saucerMissileLength = saucerMissiles.length-1;

      rocks: for (var rockCtr=rocksLength;rockCtr>=0;rockCtr--){
         tempRock = rocks[rockCtr];

         missiles:for (var playerMissileCtr=playerMissileLength;
          playerMissileCtr>=0;playerMissileCtr--){
            tempPlayerMissile = playerMissiles[playerMissileCtr];

            if (boundingBoxCollide(tempRock,tempPlayerMissile)){
                  //ConsoleLog.log("hit rock");
                  createExplode(tempRock.x+tempRock.halfWidth,
                   tempRock.y+tempRock.halfHeight,10,tempRock.scale);
                  if (tempRock.scale<3) {
                     splitRock(tempRock.scale+1, tempRock.x, tempRock.y);
                  }
                  addToScore(tempRock.scoreValue);
                  playerMissiles.splice(playerMissileCtr,1);
                  tempPlayerMissile = null;


                  rocks.splice(rockCtr,1);
                  tempRock = null;

                  break rocks;
                  break missiles;
               }
            }

         saucers:for (var saucerCtr=saucerLength;saucerCtr>=0;saucerCtr--){
            tempSaucer = saucers[saucerCtr];

            if (boundingBoxCollide(tempRock,tempSaucer)){
                  //ConsoleLog.log("hit rock");
                  createExplode(tempSaucer.x+tempSaucer.halfWidth,
                   tempSaucer.y+tempSaucer.halfHeight,10,0);
                  createExplode(tempRock.x+tempRock.halfWidth,
                   tempRock.y+tempRock.halfHeight,10,tempRock.scale);

                  if (tempRock.scale<3) {
                     splitRock(tempRock.scale+1, tempRock.x, tempRock.y);
                  }

                  saucers.splice(saucerCtr,1);
                  tempSaucer = null;

                  rocks.splice(rockCtr,1);
                  tempRock = null;

                  break rocks;
                  break saucers;
               }
            }
         //saucer missiles against rocks
         //this is done here so we don't have to loop through
         //rocks again as it would probably
         //be the biggest array
         saucerMissiles:for (var saucerMissileCtr=saucerMissileLength;
                          saucerMissileCtr>=0;saucerMissileCtr--){

            tempSaucerMissile = saucerMissiles[saucerMissileCtr];
            if (boundingBoxCollide(tempRock,tempSaucerMissile)){
                  //ConsoleLog.log("hit rock");

                  createExplode(tempRock.x+tempRock.halfWidth,
                   tempRock.y+tempRock.halfHeight,10,tempRock.scale);
                  if (tempRock.scale<3) {
                     splitRock(tempRock.scale+1, tempRock.x, tempRock.y);
                  }

                  saucerMissiles.splice(saucerCtr,1);
                  tempSaucerMissile = null;

                  rocks.splice(rockCtr,1);
                  tempRock = null;

                  break rocks;
                  break saucerMissiles;
               }
            }

         //check player against rocks

         if (boundingBoxCollide(tempRock,player) && player.safe==false){
            //ConsoleLog.log("hit player");
            createExplode(tempRock.x+tempRock.halfWidth,
             tempRock.halfHeight,10,tempRock.scale);
            addToScore(tempRock.scoreValue);
            if (tempRock.scale<3) {
               splitRock(tempRock.scale+1, tempRock.x, tempRock.y);
            }
            rocks.splice(rockCtr,1);
            tempRock=null;

            playerDie();
         }

      }

      //now check player against saucers and then saucers against player missiles
      //and finally player against saucer missiles
      playerMissileLength = playerMissiles.length-1;
      saucerLength = saucers.length-1;
      saucers:for (var saucerCtr=saucerLength;saucerCtr>=0;saucerCtr--){
         tempSaucer = saucers[saucerCtr];

         missiles:for (var playerMissileCtr=playerMissileLength;
          playerMissileCtr>=0;playerMissileCtr--){

         tempPlayerMissile = playerMissiles[playerMissileCtr];

            if (boundingBoxCollide(tempSaucer,tempPlayerMissile)){
               //ConsoleLog.log("hit rock");
               createExplode(tempSaucer.x+tempSaucer.halfWidth,
                tempSaucer.y+tempSaucer.halfHeight,10,0);
               addToScore(tempSaucer.scoreValue);

               playerMissiles.splice(playerMissileCtr,1);
               tempPlayerMissile = null;

               saucers.splice(saucerCtr,1);
               tempSaucer = null;

               break saucers;
               break missiles;
            }
         }

         //player against saucers
         if (boundingBoxCollide(tempSaucer,player) & player.safe==false){
            ConsoleLog.log("hit player");
            createExplode(tempSaucer.x+16,tempSaucer.y+16,10,tempRock.scale);
            addToScore(tempSaucer.scoreValue);

            saucers.splice(rockCtr,1);
            tempSaucer = null;

            playerDie();
         }
      }

      //saucerMissiles against player
      saucerMissileLength = saucerMissiles.length-1;

      saucerMissiles:for (var saucerMissileCtr=saucerMissileLength;
                      saucerMissileCtr>=0;saucerMissileCtr--){

         tempSaucerMissile = saucerMissiles[saucerMissileCtr];

         if (boundingBoxCollide(player,tempSaucerMissile) & player.safe==false){
            ConsoleLog.log("saucer missile hit player");

            playerDie();
            saucerMissiles.splice(saucerCtr,1);
            tempSaucerMissile = null;

            break saucerMissiles;
         }
      }

   }

   function firePlayerMissile(){

      //ConsoleLog.log("fire playerMissile");
      var newPlayerMissile = {};
      newPlayerMissile.dx = 5*Math.cos(Math.PI*(player.rotation)/180);
      newPlayerMissile.dy = 5*Math.sin(Math.PI*(player.rotation)/180);
      newPlayerMissile.x = player.x+player.halfWidth;
      newPlayerMissile.y = player.y+player.halfHeight;
      newPlayerMissile.life = 60;
      newPlayerMissile.lifeCtr = 0;
      newPlayerMissile.width = 2;
      newPlayerMissile.height = 2;
      newPlayerMissile.hitHeight = 2;
      newPlayerMissile.hitWidth = 2;
      playerMissiles.push(newPlayerMissile);
   }

   function fireSaucerMissile(saucer) {
      var newSaucerMissile = {};
      newSaucerMissile.x = saucer.x+.5*saucer.width;
      newSaucerMissile.y = saucer.y+.5*saucer.height;
      newSaucerMissile.width = 2;
      newSaucerMissile.height = 2;
      newSaucerMissile.hitHeight = 2;
      newSaucerMissile.hitWidth = 2;
      newSaucerMissile.speed = saucer.missileSpeed;

      //ConsoleLog.log("saucer fire");
      //fire at player from small saucer
      var diffx = player.x-saucer.x;
      var diffy = player.y-saucer.y;
      var radians = Math.atan2(diffy, diffx);
      var degrees = 360 * radians / (2 * Math.PI);
      newSaucerMissile.dx = saucer.missileSpeed*Math.cos(Math.PI*(degrees)/180);
      newSaucerMissile.dy = saucer.missileSpeed*Math.sin(Math.PI*(degrees)/180);
      newSaucerMissile.life = 160;
      newSaucerMissile.lifeCtr = 0;
      saucerMissiles.push(newSaucerMissile);
   }

   function playerDie() {

      ConsoleLog.log("player die");
      createExplode(player.x+player.halfWidth, player.y+player.halfWidth,50,4);
      resetPlayer();
      switchGameState(GAME_STATE_PLAYER_DIE);

   }

   function createExplode(x,y,num,type) {

      playSound(SOUND_EXPLODE,.5);
      for (var partCtr=0;partCtr<num;partCtr++){
         if (particlePool.length > 0){

              newParticle = particlePool.pop();
         newParticle.dx = Math.random()*3;
            if (Math.random()<.5){
               newParticle.dx *= 1;
            }
         newParticle.dy = Math.random()*3;
         if (Math.random()<.5){
            newParticle.dy *= 1;
         }

         newParticle.life = Math.floor(Math.random()*30+30);
         newParticle.lifeCtr = 0;
         newParticle.x = x;
         newParticle.width = 2;
         newParticle.height = 2;
         newParticle.y = y;
         newParticle.type = type;
         //ConsoleLog.log("newParticle.life=" + newParticle.life);
         particles.push(newParticle);
         }

      }

   }

   function boundingBoxCollide(object1, object2) {

      var left1 = object1.x;
      var left2 = object2.x;
      var right1 = object1.x + object1.hitWidth;
      var right2 = object2.x + object2.hitWidth;
      var top1 = object1.y;
      var top2 = object2.y;
      var bottom1 = object1.y + object1.hitHeight;
      var bottom2 = object2.y + object2.hitHeight;

      if (bottom1 < top2) return(false);
      if (top1 > bottom2) return(false);

      if (right1 < left2) return(false);
      if (left1 > right2) return(false);

      return(true);

   };

   function splitRock(scale,x,y){
      for (var newRockctr=0;newRockctr<2;newRockctr++){
         var newRock = {};
         //ConsoleLog.log("split rock");

         if (scale==2){
            newRock.scoreValue = medRockScore;
            newRock.width = 32;
            newRock.height = 32;
            newRock.halfWidth = 16;
            newRock.halfHeight = 16;
            newRock.hitWidth = 24;
            newRock.hitHeight = 24;

         }else {
            newRock.scoreValue = smlRockScore;
            newRock.width = 24;
            newRock.height = 24;
            newRock.halfWidth = 12;
            newRock.halfHeight = 12;
            newRock.hitWidth = 16;
            newRock.hitHeight = 16;
         }

         newRock.scale = scale;
         newRock.x = x;
         newRock.y = y;
         newRock.dx = Math.random()*3;
         if (Math.random()<.5){
            newRock.dx *= 1;
         }
         newRock.dy = Math.random()*3;
         if (Math.random()<.5){
            newRock.dy *= 1;
         }
         if (Math.random()<.5){
               newRock.rotationInc = 1;
         }else{
            newRock.rotationInc = 1;
         }

         newRock.animationDelay = Math.floor(Math.random()*3+1);
         newRock.animationCount = 0;

         newRock.rotation = 0;
         ConsoleLog.log("new rock scale"+(newRock.scale));
         rocks.push(newRock);

      }

   }
   function addToScore(value){
      score += value;
   }

   document.onkeydown = function(e){

      e = e?e:window.event;
      //ConsoleLog.log(e.keyCode + "down");
      keyPressList[e.keyCode] = true;
   }

   document.onkeyup = function(e){
   //document.body.onkeyup = function(e){
      e = e?e:window.event;
      //ConsoleLog.log(e.keyCode + "up");
      keyPressList[e.keyCode] =  false;
   };



   //*** application start

    switchGameState(GAME_STATE_INIT);
    var FRAME_RATE = 40;
    frameRateCounter = new FrameRateCounter(FRAME_RATE);
    //**** application loop
    var intervalTime = 1000/FRAME_RATE;
    setInterval(runGame, intervalTime );

}

//***** object prototypes *****

//*** consoleLog util object
//create constructor
function ConsoleLog(){

}

//create function that will be added to the class
console_log = function(message) {
   if(typeof(console) !== 'undefined' && console != null) {
      console.log(message);
   }
}
//add class/static function to class by assignment
ConsoleLog.log = console_log;

//*** end console log object

//***  new FrameRateCounter object prototype
function FrameRateCounter(fps) {
   if (fps == undefined){

      this.fps = 40
   }else{
      this.fps = fps
   }

   this.lastFrameCount = 0;
   var dateTemp = new Date();
   this.frameLast = dateTemp.getTime();
   delete dateTemp;
   this.frameCtr = 0;
   this.lastTime = dateTemp.getTime();

   this.step = 1;

}

FrameRateCounter.prototype.countFrames = function() {

   var dateTemp = new Date();
   var timeDifference = dateTemp.getTime()-this.lastTime;
   this.step = (timeDifference/1000)*this.fps;
   this.lastTime = dateTemp.getTime();
   this.frameCtr++;

   if (dateTemp.getTime() >=this.frameLast+1000) {
      ConsoleLog.log("frame event");
      this.lastFrameCount = this.frameCtr;
      this.frameCtr = 0;
      this.frameLast = dateTemp.getTime();
   }
   delete dateTemp;

}
</script>
</head>
<body>
<div style="position: absolute; top: 50px; left: 50px;">

<canvas id="canvas" width="400" height="400">
 Your browser does not support HTML5 Canvas.
</canvas>

</div>
</body>
</html>

Code from Chapter 11

Example A-3 gives the full code listing for CH11EX1.html. Notice that many of the code styles and constructs we have used through the course of this book are still in place in this application. Besides the obvious inclusion of code related directly to WebGL, this application operates essentially the same way as the other apps we discussed in this book.

Example A-3. WebGL test
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CH11EX1: WebGL Test </title>
<script src="modernizr.js"></script>
<script type="text/javascript" src="sylvester.js"></script>
<script type="text/javascript" src="glUtils.js"></script>

<script id="shader-fs" type="x-shader/x-fragment">
   #ifdef GL_ES
   precision highp float;
   #endif

   varying vec4 vColor;

   void main(void) {
     gl_FragColor = vColor;
   }
</script>

<script id="shader-vs" type="x-shader/x-vertex">
   attribute vec3 aVertexPosition;
   attribute vec4 aVertexColor;

   uniform mat4 uMVMatrix;
   uniform mat4 uPMatrix;

   varying vec4 vColor;   void main(void) {
     gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
     vColor = aVertexColor;
   }
</script>

<script type="text/javascript">
window.addEventListener("load", eventWindowLoaded, false);

function eventWindowLoaded () {
   canvasApp();
}

function canvasSupport () {
   return Modernizr.canvas;
}

function webglSupport() {
   return Modernizr.webgl;
}
function canvasApp () {

function drawScreen() {

      webGLContext.viewport(0, 0, webGLContext.viewportWidth,
          webGLContext.viewportHeight);
      webGLContext.clear(webGLContext.COLOR_BUFFER_BIT | webGLContext.DEPTH_BUFFER_BIT);

      perspective(25, (webGLContext.viewportWidth / webGLContext.viewportHeight),
          0.1, 100.0);
      loadIdentity();

      mvTranslate([0, 0.0, 10.0]);

      mvPushMatrix();
      mvRotate(rotateCube, [0, .5, .5]);

      webGLContext.bindBuffer(webGLContext.ARRAY_BUFFER, cubeVertexPositionBuffer);
      webGLContext.vertexAttribPointer(shaderProgram.vertexPositionAttribute,
          cubeVertexPositionBuffer.itemSize, webGLContext.FLOAT, false, 0, 0);

      webGLContext.bindBuffer(webGLContext.ARRAY_BUFFER, cubeVertexColorBuffer);
      webGLContext.vertexAttribPointer(shaderProgram.vertexColorAttribute,
          cubeVertexColorBuffer.itemSize, webGLContext.FLOAT, false, 0, 0);

      webGLContext.bindBuffer(webGLContext.ELEMENT_ARRAY_BUFFER,
         cubeVertexIndexBuffer);
      setMatrixUniforms();
      webGLContext.drawElements(webGLContext.TRIANGLES,
         cubeVertexIndexBuffer.numItems, webGLContext.UNSIGNED_SHORT, 0);

      mvPopMatrix();
      rotateCube += 2;

      }

      if (!canvasSupport() ) {
           alert("Unable to initialize Canvas");
           return;
        }

      if ( !webglSupport()) {
          alert("Unable to initialize WebGL");
          return;
        }

      var webGLContext;
      var rotateCube = 0;

      var theCanvas = document.getElementById("canvasOne");
      webGLContext =theCanvas.getContext("experimental-webgl");
      webGLContext.viewportWidth =theCanvas.width;
      webGLContext.viewportHeight = theCanvas.height;

      initShaders();
      initBuffers();

      webGLContext.clearColor(0.0, 0.0, 0.0, 1.0);
      webGLContext.clearDepth(1.0);
      webGLContext.enable(webGLContext.DEPTH_TEST);
      webGLContext.depthFunc(webGLContext.LEQUAL);

      setInterval(drawScreen, 33);

   function getShader(webglcontext, id) {
      var shaderScript = document.getElementById(id);
      if (!shaderScript) {
        return null;
      }

      var str = "";
      var scriptChild = shaderScript.firstChild;
      while (scriptChild) {
        if (scriptChild.nodeType == 3) {
         str += scriptChild.textContent;
        }
        scriptChild = scriptChild.nextSibling;
      }

      var shader;
      if (shaderScript.type == "x-shader/x-fragment") {
        shader = webGLContext.createShader(webGLContext.FRAGMENT_SHADER);
      } else if (shaderScript.type == "x-shader/x-vertex") {
        shader = webGLContext.createShader(webGLContext.VERTEX_SHADER);
      } else {
        return null;
      }

      webGLContext.shaderSource(shader, str);
      webGLContext.compileShader(shader);

      if (!webGLContext.getShaderParameter(shader, webGLContext.COMPILE_STATUS)) {
        alert(webGLContext.getShaderInfoLog(shader));
        return null;
      }

      return shader;
     }

     var shaderProgram;
   function initShaders() {
      var fragmentShader = getShader(webGLContext, "shader-fs");
      var vertexShader = getShader(webGLContext, "shader-vs");

      shaderProgram = webGLContext.createProgram();
      webGLContext.attachShader(shaderProgram, vertexShader);
      webGLContext.attachShader(shaderProgram, fragmentShader);
      webGLContext.linkProgram(shaderProgram);

      if (!webGLContext.getProgramParameter(shaderProgram, webGLContext.LINK_STATUS)) {
        alert("Could not initialize shaders");
      }

      webGLContext.useProgram(shaderProgram);

      shaderProgram.vertexPositionAttribute = webGLContext.getAttribLocation
          (shaderProgram, "aVertexPosition");
      webGLContext.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);

      shaderProgram.vertexColorAttribute = webGLContext.getAttribLocation
          (shaderProgram, "aVertexColor");
      webGLContext.enableVertexAttribArray(shaderProgram.vertexColorAttribute);

      shaderProgram.pMatrixUniform = webGLContext.getUniformLocation
          (shaderProgram, "uPMatrix");
      shaderProgram.mvMatrixUniform = webGLContext.getUniformLocation
          (shaderProgram, "uMVMatrix");
     }

     var mvMatrix;
     var mvMatrixStack = [];

   function mvPushMatrix(matrix) {
      if (matrix) {
        mvMatrixStack.push(matrix.dup());
        mvMatrix = matrix.dup();
      } else {
        mvMatrixStack.push(mvMatrix.dup());
      }
     }

   function mvPopMatrix() {
      if (mvMatrixStack.length == 0) {
        throw "Invalid popMatrix!";
      }      mvMatrix = mvMatrixStack.pop();
      return mvMatrix;
     }

   function loadIdentity() {
      mvMatrix = Matrix.I(4);
     }

   function multMatrix(matrix) {
      mvMatrix = mvMatrix.x(matrix);
     }

   function mvTranslate(vector) {
      var matrix = Matrix.Translation($V([vector[0], vector[1],
         vector[2]])).ensure4x4();
      multMatrix(matrix);
     }

   function mvRotate(angle, vector) {
      var radians = angle * Math.PI / 180.0;
      var matrix = Matrix.Rotation(radians, $V([vector[0],
          vector[1], vector[2]])).ensure4x4();
      multMatrix(matrix);
     }

     var pMatrix;
     function perspective(fovy, aspect, znear, zfar) {
      pMatrix = makePerspective(fovy, aspect, znear, zfar);
     }

   function setMatrixUniforms() {
      webGLContext.uniformMatrix4fv(shaderProgram.pMatrixUniform, false,
          new Float32Array(pMatrix.flatten()));
      webGLContext.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false,
          new Float32Array(mvMatrix.flatten()));
     }

     var cubeVertexPositionBuffer;
     var cubeVertexColorBuffer;
     var cubeVertexIndexBuffer;
   function initBuffers() {

      cubeVertexPositionBuffer = webGLContext.createBuffer();
      webGLContext.bindBuffer(webGLContext.ARRAY_BUFFER, cubeVertexPositionBuffer);
      vertices = [
        // Front face
        1.0, 1.0,  1.0,
         1.0, 1.0,  1.0,
         1.0,  1.0,  1.0,
        1.0,  1.0,  1.0,

        // Back face
        1.0, 1.0, 1.0,
        1.0,  1.0, 1.0,
         1.0,  1.0, 1.0,
         1.0, 1.0, 1.0,

        // Top face
        1.0,  1.0, 1.0,
        1.0,  1.0,  1.0,
         1.0,  1.0,  1.0,
         1.0,  1.0, 1.0,

        // Bottom face
        1.0, 1.0, 1.0,
         1.0, 1.0, 1.0,
         1.0, 1.0,  1.0,
        1.0, 1.0,  1.0,

        // Right face
         1.0, 1.0, 1.0,
         1.0,  1.0, 1.0,
         1.0,  1.0,  1.0,
         1.0, 1.0,  1.0,

        // Left face
        1.0, 1.0, 1.0,
        1.0, 1.0,  1.0,
        1.0,  1.0,  1.0,
        1.0,  1.0, 1.0,
      ];
      webGLContext.bufferData(webGLContext.ARRAY_BUFFER, new Float32Array(vertices),
          webGLContext.STATIC_DRAW);
      cubeVertexPositionBuffer.itemSize = 3;
      cubeVertexPositionBuffer.numItems = 24;

      cubeVertexColorBuffer = webGLContext.createBuffer();
      webGLContext.bindBuffer(webGLContext.ARRAY_BUFFER, cubeVertexColorBuffer);
      var colors = [
        [1.0, 1.0, 1.0, 1.0],     // Front face
        [0.9, 0.0, 0.0, 1.0],     // Back face
        [0.6, 0.6, 0.6, 1.0],     // Top face
        [0.6, 0.0, 0.0, 1.0],     // Bottom face
        [0.3 ,0.0, 0.0, 1.0],     // Right face
        [0.3, 0.3, 0.3, 1.0],     // Left face
      ];

      var unpackedColors = []
      for (var i in colors) {
        var color = colors[i];
        for (var j=0; j < 4; j++) {
         unpackedColors = unpackedColors.concat(color);
        }
      }
      webGLContext.bufferData(webGLContext.ARRAY_BUFFER,
         new Float32Array(unpackedColors),
          webGLContext.STATIC_DRAW);
      cubeVertexColorBuffer.itemSize = 4;
      cubeVertexColorBuffer.numItems = 24;

      cubeVertexIndexBuffer = webGLContext.createBuffer();
      webGLContext.bindBuffer(webGLContext.ELEMENT_ARRAY_BUFFER,
         cubeVertexIndexBuffer);
      var cubeVertexIndices = [
        0, 1, 2,      0, 2, 3,    // Front face
        4, 5, 6,      4, 6, 7,    // Back face
        8, 9, 10,     8, 10, 11,  // Top face
        12, 13, 14,   12, 14, 15, // Bottom face
        16, 17, 18,   16, 18, 19, // Right face
        20, 21, 22,   20, 22, 23  // Left face
      ]
      webGLContext.bufferData(webGLContext.ELEMENT_ARRAY_BUFFER,
          new Uint16Array(cubeVertexIndices), webGLContext.STATIC_DRAW);
      cubeVertexIndexBuffer.itemSize = 1;
      cubeVertexIndexBuffer.numItems = 36;

     }

}

</script>
</head>
<body>
<div style="position: absolute; top: 50px; left: 50px;">
<canvas id="canvasOne" width="500" height="500">
 Your browser does not support HTML5 Canvas or WebGLContext.
</canvas>
</div>
</body>
</html>