Core JavaScript

A Space Game

Instructions: Shoot incoming asteroids before they hit the ship. Use the mouse to aim and the left mouse button to fire at asteroids. Press any key to begin.

 

Output

 

SpaceGame.html

<!DOCTYPE html>
<html>
  <head>
    <title>XoaX.net HTML</title>
    <style>
      .cAsteroid {
        position:absolute;
        animation: kfExpandWidth linear 2.5s 1,
        kfMoveDown linear 2.5s 1,
        kfExpandHeight linear 2.5s 1,
        kfMoveRight linear 2.5s 1;
      }
      @keyframes kfExpandWidth {
        from {width: 10%;}
        to {width: 100%;}
      }
      @keyframes kfMoveDown {
        from {top: 45%;}
        to {top: 0%;}
      }
      @keyframes kfExpandHeight {
        from {height: 10%;}
        to {height: 100%;}
      }
      @keyframes kfMoveRight {
        from {left: 45%;}
        to {left: 0%;}
      }
      .cExplosion {
        position:absolute;
		animation: kfDisappear linear .5s 1;
      }
      @keyframes kfDisappear {
        from {opacity: 100%;}
        to {opacity: 0%;}
      }
    </style>
    <script>
      var gqOutside = null;
      var giScore = 0;
      var giDamage = 0;
      var gqaAsteroids = null;
      var gqaExplosions = null;
      var giCollisionFrame = 20;
      var gbGameOver = true;
      function fnStart() {
        // Prevent multiple starts from key presses
        if (gbGameOver) {
          gbGameOver  = false;
          giScore     = 0;
          giDamage    = 0;
          gqOutside   = document.getElementById("idOutside");
          // Allocate the asteroid and explosion arrays
          gqaAsteroids  = new Array();
          gqaExplosions = new Array();
          // Remove the start message
          var qMessageElement = document.getElementById("idStartMessage");
          qMessageElement.style.display = "none";
          // Start the timed game loop
          fnGameLoop();
        }
      }
      function fnGameLoop() {
        if (!gbGameOver) {
          setTimeout(fnGameLoop, 50);
          // Increment the asteroid frame counts
          for(var i = 0; i < gqaAsteroids.length; ++i) {
            gqaAsteroids[i].miFrame += 1;
		  }
          // Place an asteroid
          if (Math.floor(Math.random() * 100) < (3 + giScore/10)) {
            fnPlaceAsteroid();
          }
          // Move the screen during a collision
          if (giCollisionFrame < 20) {
            gqOutside.style.left = Math.floor((Math.random() * 6) - 3) + "px";
            gqOutside.style.top  = Math.floor((Math.random() * 6) - 3) + "px";
            ++giCollisionFrame;
          } else {
            gqOutside.style.left = 0 + "px";
            gqOutside.style.top  = 0 + "px";
            giCollisionFrame     = 20;
          }
        } else {
          // Remove the remaining asteroids and stop their collisions
          for(var i = 0; i < gqaAsteroids.length; ++i) {
            gqOutside.removeChild(gqaAsteroids[i].mqDiv);
            clearTimeout(gqaAsteroids[i].mqTimeOut);
		  }
		  // Reset the collision shake
		  giCollisionFrame     = 20;
          var qMessageElement = document.getElementById("idStartMessage");
          qMessageElement.style.display = "block";
        }
        // Update the score and damage
        var qScore = document.getElementById("idScore");
        qScore.innerHTML = giScore;
        var qDamage = document.getElementById("idDamage");
        qDamage.innerHTML = giDamage;
      }
      function CAsteroid() {
        // The frame counter is used to determine how big the asteroid is
        this.miFrame   = 0;
        // x between 30 and 640 - 30 - 28 = 582
        this.miCenterX = Math.floor((Math.random() * 552) + 30);
        // y between 30 and 388 - 30 - 28 = 330
        this.miCenterY = Math.floor((Math.random() * 300) + 30);
        // Create the container div for the image
        this.mqDiv                = document.createElement("div");
		this.mqDiv.style.position = "absolute";
		this.mqDiv.style.left     = this.miCenterX - 28 + "px";
		this.mqDiv.style.top      = this.miCenterY - 28 + "px";
		this.mqDiv.style.width    = "56px";
		this.mqDiv.style.height   = "56px";
		this.mqDiv.style.zIndex   = "1";
		this.mqDiv.innerHTML      = "<img class='cAsteroid' src='Rock.png' />";
		gqOutside.appendChild(this.mqDiv);
		// Create the collision event function. Asteroids collide after 2.5 seconds
        this.mqTimeOut = setTimeout(fnAsteroidCollision, 2500);
      }
      function fnPlaceAsteroid() {
		var qNewAsteroid = new CAsteroid();
		// Add the asteroid at the end of the array
		gqaAsteroids.push(qNewAsteroid);
		fnAsteroidResetZ();
      }
      function fnAsteroidCollision() {
        // Make the collision sound and shake the screen
        var qCollisionAudio         = document.getElementById("idCollision");
        qCollisionAudio.currentTime = 0;
        qCollisionAudio.play();
        // Initialize the screen shake
		giCollisionFrame = 0;
		// Increase the damage
		++giDamage;
        // Remove the first asteroid in the list. This one hit the ship.
        var qRemoved = gqaAsteroids.shift();
        // Remove the asteroid image from the HTML
        gqOutside.removeChild(qRemoved.mqDiv);
        fnAsteroidResetZ();
        if (giDamage >= 3) {
          gbGameOver = true;
        }
      }
      function fnAsteroidResetZ() {
        for(var i = 0; i < gqaAsteroids.length; ++i) {
		  gqaAsteroids[i].mqDiv.style.zIndex = gqaAsteroids.length - i;
		}
      }
      function fnAsteroidShot(iIndex, dPercent) {
        // Create the explosion image
        fnCreateExplosion(dPercent, gqaAsteroids[iIndex]);
        // Stop the collision event
        clearTimeout(gqaAsteroids[iIndex].mqTimeOut);
        // Remove element first because it is accessed via the array
        gqOutside.removeChild(gqaAsteroids[iIndex].mqDiv);
        // Remove the asteroid from the array
        gqaAsteroids.splice(iIndex, 1);
        // Reset the z values
        fnAsteroidResetZ();
        ++giScore;
      }
      function fnCreateExplosion(dPercent, qAsteroid) {
		// Create the explosion image. The image is 56x56
		var iImageSize             = Math.round(dPercent*56);
		var qImageElement          = document.createElement("img");
		qImageElement.className    = "cExplosion";
		qImageElement.src          = "ExplodingRock.png";
		qImageElement.style.left   = qAsteroid.miCenterX - (iImageSize/2) + "px";
		qImageElement.style.top    = qAsteroid.miCenterY - (iImageSize/2) + "px";
		qImageElement.style.width  = iImageSize + "px";
		qImageElement.style.height = iImageSize + "px";
		qImageElement.style.zIndex = qAsteroid.mqDiv.style.zIndex;
		gqOutside.appendChild(qImageElement);
		gqaExplosions.push(qImageElement);
		// Play the explosion shot sound
        var qAsteroidShotAudio = document.getElementById("idAsteroidShot");
        qAsteroidShotAudio.currentTime = 0;
        qAsteroidShotAudio.play();
		setTimeout(fnRemoveExplosion, 500);
      }
      function fnRemoveExplosion() {
        // Get the first explosion and remove it from the outside and array
        var qExplosionImage = gqaExplosions.shift();
        gqOutside.removeChild(qExplosionImage);
      }
      function fnOnClick(e, qElement) {
        // Play the shot sound
	    var qShotAudio = document.getElementById("idShot");
	    qShotAudio.currentTime = 0;
        qShotAudio.play();

        // This is all to calculate the click coordinates relative to the div
        var dElementOffsetX = 0;
        var dElementOffsetY = 0;
        do{
          dElementOffsetX += qElement.offsetLeft - qElement.scrollLeft;
          dElementOffsetY += qElement.offsetTop - qElement.scrollTop;
          qElement = qElement.offsetParent
        } while(qElement != document.body);
        var dRelX = e.pageX - dElementOffsetX;
        var dRelY = e.pageY - dElementOffsetY;

        // Check each asteroid for a hit
        for (var i = gqaAsteroids.length - 1; i >= 0; --i) {
          // Percentage complete - there are 2500/50 = 50 frames per asteroid
          var dPC = (gqaAsteroids[i].miFrame/50);
          // The max radius is 50 pixels. The min radius is 10% or 5 pixels
          var dHitRadius = ((50*dPC) + 5*(1 - dPC));
          var dDeltaX = dRelX - gqaAsteroids[i].miCenterX;
          var dDeltaY = dRelY - gqaAsteroids[i].miCenterY;
          var dClickDistance = Math.sqrt(dDeltaX*dDeltaX + dDeltaY*dDeltaY);
          if (dHitRadius > dClickDistance) {
            fnAsteroidShot(i, dPC);
            return;
          }
        }
      }
    </script>
  </head>
  <body onkeydown="fnStart()">
    <h1 id="idStartMessage"
      style="position:absolute;left:155px;top:130px;z-index:5001;color:white;text-align: center;">
      GAME OVER<br />Press any key to begin!</h1>
    <audio id="idCollision" src="Collision.mp3"></audio>
    <audio id="idAsteroidShot" src="AsteroidShot.mp3"></audio>
    <audio id="idShot" src="Shot.mp3"></audio>
    <!-- The background stars and asteroids -->
    <div id="idOutside" style="position:absolute;left:0px;top:0px;">
      <img style="position:absolute;left:0px;top:0px;z-index:-1;" src="Space.png" />
    </div>
    <!-- The window click region -->
    <div style="position:absolute;left:0px;top:0px;z-index:5000;width:640px;height:388px;cursor:crosshair;"
      onclick="fnOnClick(event, this)"></div>
    <!-- The ship's window, console, and scores -->
    <img style="position:absolute;left:0px;top:0px;z-index:1000;" src="Windshield.png" />
    <h1 style="position:absolute;left:20px;top:395px;z-index:5000;">Score: <span id="idScore">0</span></h1>
    <h1 style="position:absolute;left:340px;top:395px;z-index:5000;">Damage: <span id="idDamage">0</span></h1>
  </body>
</html>
 

© 2007–2025 XoaX.net LLC. All rights reserved.