You are previewing HTML5 Canvas.

HTML5 Canvas

Cover of HTML5 Canvas by Steve Fulton... Published by O'Reilly Media, Inc.
  1. HTML5 Canvas
    1. SPECIAL OFFER: Upgrade this ebook with O’Reilly
    2. A Note Regarding Supplemental Files
    3. Preface
      1. Running the Examples in the Book
      2. What You Need to Know
      3. How This Book Is Organized
      4. Conventions Used in This Book
      5. Using Code Examples
      6. We’d Like to Hear from You
      7. Safari® Books Online
      8. Acknowledgments
    4. 1. Introduction to HTML5 Canvas
      1. The Basic HTML Page
      2. Basic HTML We Will Use in This Book
      3. The Document Object Model (DOM) and Canvas
      4. JavaScript and Canvas
      5. HTML5 Canvas “Hello World!”
      6. Debugging with Console.log
      7. The 2D Context and the Current State
      8. The HTML5 Canvas Object
      9. Another Example: Guess The Letter
      10. What’s Next
    5. 2. Drawing on the Canvas
      1. The Basic File Setup for This Chapter
      2. The Basic Rectangle Shape
      3. The Canvas State
      4. Using Paths to Create Lines
      5. Advanced Path Methods
      6. Compositing on the Canvas
      7. Simple Canvas Transformations
      8. Filling Objects with Colors and Gradients
      9. Filling Shapes with Patterns
      10. Creating Shadows on Canvas Shapes
      11. What’s Next
    6. 3. The HTML5 Canvas Text API
      1. Displaying Basic Text
      2. Setting the Text Font
      3. Text and the Canvas Context
      4. Text with Gradients and Patterns
      5. Width, Height, Scale, and toDataURL() Revisited
      6. Final Version of Text Arranger
      7. What’s Next
    7. 4. Images on the Canvas
      1. The Basic File Setup for This Chapter
      2. Image Basics
      3. Simple Cell-Based Sprite Animation
      4. Advanced Cell-Based Animation
      5. Applying Rotation Transformations to an Image
      6. Creating a Grid of Tiles
      7. Zooming and Panning an Image
      8. Pixel Manipulation
      9. Copying from One Canvas to Another
      10. What’s Next
    8. 5. Math, Physics, and Animation
      1. Moving in a Straight Line
      2. Bouncing Off Walls
      3. Curve and Circular Movement
      4. Simple Gravity, Elasticity, and Friction
      5. Easing
      6. What’s Next?
    9. 6. Mixing HTML5 Video and Canvas
      1. HTML5 Video Support
      2. Converting Video Formats
      3. Basic HTML5 Video Implementation
      4. Preloading Video in JavaScript
      5. Video and the Canvas
      6. Video on the Canvas Examples
      7. Animation Revisited: Moving Videos
      8. What’s Next?
    10. 7. Working with Audio
      1. The Basic <audio> Tag
      2. Audio Formats
      3. Audio Tag Properties, Functions, and Events
      4. Playing a Sound with No Audio Tag
      5. Creating a Canvas Audio Player
      6. Case Study in Audio: Space Raiders Game
      7. What’s Next
    11. 8. Canvas Game Essentials
      1. Why Games in HTML5?
      2. Our Basic Game HTML5 File
      3. Our Game’s Design
      4. Game Graphics: Drawing with Paths
      5. Animating on the Canvas
      6. Applying Transformations to Game Graphics
      7. Game Graphic Transformations
      8. Game Object Physics and Animation
      9. A Basic Game Framework
      10. Putting It All Together
      11. The player Object
      12. Geo Blaster Game Algorithms
      13. The Geo Blaster Basic Full Source
      14. Rock Object Prototype
      15. What’s Next
    12. 9. Combining Bitmaps and Sound
      1. Geo Blaster Extended
      2. Creating a Dynamic Tile Sheet at Runtime
      3. A Simple Tile-Based Game
      4. What’s Next
    13. 10. Mobilizing Games with PhoneGap
      1. Going Mobile!
      2. Creating the iOS Application with PhoneGap
      3. Beyond the Canvas
      4. What’s Next
    14. 11. Further Explorations
      1. 3D with WebGL
      2. Multiplayer Applications with ElectroServer 5
      3. Conclusion
    15. Index
    16. About the Authors
    17. Colophon
    18. SPECIAL OFFER: Upgrade this ebook with O’Reilly
O'Reilly logo

The Geo Blaster Basic Full Source

Example 8-12 shows the entire set of code for our game. You can download this and the entire set of example files from the book’s website.

Example 8-12. The Geo Blaster Basic full source listing

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Geo Blaster Basic Game</title>
<script type="text/javascript">
window.addEventListener('load', eventWindowLoaded, false);
function eventWindowLoaded() {
       canvasApp();
}

function canvasApp(){
   var theCanvas = document.getElementById("canvas");
   if (!theCanvas || !theCanvas.getContext) {
       return;
   }

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

   if (!context) {
       return;
   }

   //application states
   const GAME_STATE_TITLE = 0;
   const GAME_STATE_NEW_GAME = 1;
   const GAME_STATE_NEW_LEVEL = 2;
   const GAME_STATE_PLAYER_START = 3;
   const GAME_STATE_PLAY_LEVEL = 4;
   const GAME_STATE_PLAYER_DIE = 5;
   const GAME_STATE_GAME_OVER = 6;
   var currentGameState = 0;
   var currentGameStateFunction = null;

   //title screen
   var titleStarted = false;

   //gameover 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
   const ROCK_SCALE_LARGE = 1;
   const ROCK_SCALE_MEDIUM = 2;
   const ROCK_SCALE_SMALL = 3;

   //create game objects and arrays
   var player = {};
   var rocks = [];
   var saucers = [];
   var playerMissiles = [];
   var particles = []
   var saucerMissiles = [];

   //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 = [];

   function runGame(){
      currentGameStateFunction();
   }

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

         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 gameStateTitle() {
      if (titleStarted !=true){
         fillBackground();
         setTextStyle();
         context.fillText  ("Geo Blaster Basic", 130, 70);
         context.fillText  ("Press Space To Play", 120, 140);

         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 = 20;
      player.height = 20;
      player.halfWidth = 10;
      player.halfHeight = 10;
      player.rotationalVelocity = 5; //how many degrees to turn the ship
      player.thrustAcceleration = .05;
      player.missileFrameDelay = 5;
      player.thrust = false;

      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 = 50;
         newRock.height = 50;
         newRock.halfWidth = 25;
         newRock.halfHeight = 25;

         //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
         newRock.rotationInc = (Math.random()*5)+1;

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

         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 += .02;
         context.globalAlpha = player.alpha;
      }else{
         switchGameState(GAME_STATE_PLAY_LEVEL);
      }

      renderPlayerShip(player.x, player.y,270,1);
      context.globalAlpha = 1;
      updateRocks();
      renderRocks();
   }

   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;
   }

   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!", 150, 70);
         context.fillText  ("Press Space To Play", 120, 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 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 (keyPressList[39]==true) {
         //rotate clockwise
         player.rotation += player.rotationalVelocity;;
      }

      if (keyPressList[32]==true) {
         //ConsoleLog.log("player.missileFrameCount=" + player.missileFrameCount);
         //ConsoleLog.log("player.missileFrameDelay=" + player.missileFrameDelay);         if (player.missileFrameCount>player.missileFrameDelay){
            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;
      player.y += player.movingY;

      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;
         tempPlayerMissile.y += tempPlayerMissile.dy;
         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;
         tempRock.y += tempRock.dy;
         tempRock.rotation += tempRock.rotationInc;
         //ConsoleLog.log("rock rotationInc="+ tempRock.rotationInc)
         //ConsoleLog.log("rock rotation="+ tempRock.rotation)
         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;
         }

         //ConsoleLog.log("update rock "+ rockCtr)
      }
   }

   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 = 28;
            newSaucer.height = 13;
            newSaucer.halfHeight = 6.5;
            newSaucer.halfWidth = 14;
            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 ){

            fireSaucerMissile(tempSaucer)
            tempSaucer.fireDelayCount= 0;
         }

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

         //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
            ConsoleLog.log("saucer removed")
            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;
         tempSaucerMissile.y += tempSaucerMissile.dy;
         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 tempParticle = {};
      var particleLength = particles.length-1;
      //ConsoleLog.log("particle=" + particleLength)
      for (var particleCtr=particleLength;particleCtr>=0;particleCtr--){
         var remove = false;
         tempParticle = particles[particleCtr];
         tempParticle.x += tempParticle.dx;
         tempParticle.y += tempParticle.dy;

         tempParticle.lifeCtr++;
         //ConsoleLog.log("particle.lifeCtr=" + tempParticle.lifeCtr);

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

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

            remove = true;

         }
         //}
         //catch(err) {
         //   ConsoleLog.log ("error in particle");
         //   ConsoleLog.log("particle:" + particleCtr);

         //}

         if (remove) {
            particles.splice(particleCtr,1)
            tempParticle = null;
         }

      }
   }

   function renderPlayerShip(x,y,rotation, scale) {
      //transformation
      var angleInRadians = rotation * Math.PI / 180;
      context.save(); //save current state in stack
      context.setTransform(1,0,0,1,0,0); // reset to identity

      //translate the canvas origin to the center of the player
      context.translate(x+player.halfWidth,y+player.halfHeight);
      context.rotate(angleInRadians);
      context.scale(scale,scale);

      //drawShip
      context.strokeStyle = '#ffffff';
      context.beginPath();

      //hardcoding in locations
      //facing right
      context.moveTo(-10,-10);
      context.lineTo(10,0);
      context.moveTo(10,1);
      context.lineTo(-10,10);
      context.lineTo(1,1);
      context.moveTo(1,-1);
      context.lineTo(-10,-10);

      if (player.thrust==true && scale==1) {
      //check for scale==1 for ship indicator does not display with thrust
         context.moveTo(-4,-2);
         context.lineTo(-4,1);
         context.moveTo(-5,-1);
         context.lineTo(-10,-1);
         context.moveTo(-5,0);
         context.lineTo(-10,0);
      }
      context.stroke();
      context.closePath();

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

   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
         context.setTransform(1,0,0,1,0,0); // reset to identity

         //translate the canvas origin to the center of the player
         context.translate(tempPlayerMissile.x+1,tempPlayerMissile.y+1);
         context.strokeStyle = '#ffffff';

         context.beginPath();

         //draw everything offset by 1/2. Zero Relative 1/2 is 15
         context.moveTo(-1,-1);
         context.lineTo(1,-1);
         context.lineTo(1,1);
         context.lineTo(-1,1);
         context.lineTo(-1,-1);
         context.stroke();
         context.closePath();
         context.restore(); //pop old state on to screen
      }
   }

   function renderRocks() {
      var tempRock = {};
      var rocksLength = rocks.length-1;
      for (var rockCtr=rocksLength;rockCtr>=0;rockCtr--){

         tempRock = rocks[rockCtr];
         var angleInRadians  = tempRock.rotation * Math.PI / 180;
         //ConsoleLog.log("render rock rotation"+(tempRock.rotation));
         context.save(); //save current state in stack
         context.setTransform(1,0,0,1,0,0); // reset to identity

         //translate the canvas origin to the center of the player
         context.translate(tempRock.x+tempRock.halfWidth,
                           tempRock.y+tempRock.halfHeight);

         //ConsoleLog.log("render rock x"+(tempRock.x+tempRock.halfWidth));
         //ConsoleLog.log("render rock y"+(tempRock.y+tempRock.halfHeight));
         context.rotate(angleInRadians);
         context.strokeStyle = '#ffffff';

         context.beginPath();

         //draw everything offset by 1/2.
         //Zero Relative 1/2 is if .5*width -1. Same for height

         context.moveTo(-(tempRock.halfWidth-1),-(tempRock.halfHeight-1));
         context.lineTo((tempRock.halfWidth-1),-(tempRock.halfHeight-1));
         context.lineTo((tempRock.halfWidth-1),(tempRock.halfHeight-1));
         context.lineTo(-(tempRock.halfWidth-1),(tempRock.halfHeight-1));
         context.lineTo(-(tempRock.halfWidth-1),-(tempRock.halfHeight-1));

         context.stroke();
         context.closePath();
         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
         context.setTransform(1,0,0,1,0,0); // reset to identity

         //translate the canvas origin to the center of the player
         //context.translate(this.x+halfWidth,this.y+halfHeight);
         context.translate(tempSaucer.x,tempSaucer.y);
         context.strokeStyle = '#ffffff';

         context.beginPath();

         //did not move to middle because it is drawn in exact space

         context.moveTo(4,0);
         context.lineTo(9,0);
         context.lineTo(12,3);
         context.lineTo(13,3);
         context.moveTo(13,4);
         context.lineTo(10,7);
         context.lineTo(3,7);
         context.lineTo(1,5);
         context.lineTo(12,5);
         context.moveTo(0,4);
         context.lineTo(0,3);
         context.lineTo(13,3);
         context.moveTo(5,1);
         context.lineTo(5,2);
         context.moveTo(8,1);
         context.lineTo(8,2);
         context.moveTo(2,2);
         context.lineTo(4,0);

         context.stroke();
         context.closePath();
         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
         context.setTransform(1,0,0,1,0,0); // reset to identity

         //translate the canvas origin to the center of the player
         context.translate(tempSaucerMissile.x+1,tempSaucerMissile.y+1);
         context.strokeStyle = '#ffffff';

         context.beginPath();

         //draw everything offset by 1/2. Zero Relative 1/2 is 15
         context.moveTo(-1,-1);
         context.lineTo(1,-1);
         context.lineTo(1,1);
         context.lineTo(-1,1);
         context.lineTo(-1,-1);
         context.stroke();
         context.closePath();
         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
         context.setTransform(1,0,0,1,0,0); // reset to identity

         //translate the canvas origin to the center of the player
         context.translate(tempParticle.x,tempParticle.y);
         context.strokeStyle = '#ffffff';

         context.beginPath();

         //draw everything offset by 1/2. Zero Relative 1/2 is 15
         context.moveTo(0,0);
         context.lineTo(1,1);
         context.stroke();
         context.closePath();
         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);

               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);

               createExplode(tempRock.x+tempRock.halfWidth,
                                tempRock.y+tempRock.halfHeight,10);

               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);
               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 aginst rocks

         if (boundingBoxCollide(tempRock,player)){
            //ConsoleLog.log("hit player");
            createExplode(tempRock.x+tempRock.halfWidth,tempRock.halfHeight,10);
            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);

               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)){
            ConsoleLog.log("hit player");
            createExplode(tempSaucer.x+16,tempSaucer.y+16,10);
            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)){
            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;
      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.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);
      switchGameState(GAME_STATE_PLAYER_DIE);

   }

   function createExplode(x,y,num) {
      //create 10 particles
      for (var partCtr=0;partCtr<num;partCtr++){
         var newParticle = new Object();
         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.y = y;
         //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.width;
      var right2 = object2.x + object2.width;
      var top1 = object1.y;
      var top2 = object2.y;
      var bottom1 = object1.y + object1.height;
      var bottom2 = object2.y + object2.height;

      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 = 25;
            newRock.height = 25;
            newRock.halfWidth = 12.5;
            newRock.halfHeight = 12.5;

         }else {
            newRock.scoreValue = smlRockScore;
            newRock.width = 16;
            newRock.height = 16;
            newRock.halfWidth = 8;
            newRock.halfHeight = 8;
         }

         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;
         }
         newRock.rotationInc = (Math.random()*5)+1;
         if (Math.random()<.5){
            newRock.rotationInc*=-1;
         }
         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_TITLE);
   frameRateCounter = new FrameRateCounter();
   //**** application loop
   const FRAME_RATE = 40;
   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

//*** FrameRateCounter  object prototype
function FrameRateCounter() {

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

FrameRateCounter.prototype.countFrames = function() {
   var dateTemp = new Date();
   this.frameCtr++;

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

   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>

Figure 8-7 shows a screenshot of the game in action.

Geo Blaster Basic in action

Figure 8-7. Geo Blaster Basic in action

The best content for your career. Discover unlimited learning on demand for around $1/day.