HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptModerate

Asteroids game clone

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
asteroidsgameclone

Problem

I have recently been teaching a JavaScript module on a games programming undergraduate course and I made this game as a learning exercise and as an example for my students to study. I have made games before but never in JavaScript.

Could you have a look at my style and let me know if there are any obvious improvements?

the code is also on my github page

The .html file includes a canvas element, some styles and a simple game loop.


  
    Asteroids 2
    
      body {
        text-align: center;
        margin: 0;
      }

      canvas {
        background-color: rgba(0, 0, 0, 0.9);
        width: 600px;
        height: 600px;
      }

      h1 {
        margin: 0;
        margin-bottom: -0.21em;
        font-family: helvetica;
        letter-spacing: 2.5em;
        padding-left: 2.5em;
        font-size: 1.5em;
        font-variant: small-caps;
      }
    
  
  
    AsteroidS
    Mute
    
    
    
      var fps = 60;//frames per second
      var canvas = document.getElementById("asteroids");
      var game = new window.Asteroids(canvas);
      var framerate = 1000 / fps;//milliseconds between frames

      window.onkeydown = game.keyDown.bind(game);
      window.onkeyup = game.keyUp.bind(game);

      var previous;
      function step(timestamp) {
        if (!previous) previous = timestamp;
        var elapsed = timestamp - previous;
        game.update(elapsed/1000);
        if (elapsed >= framerate) {
          game.refresh();
          previous = timestamp;
        }
        window.requestAnimationFrame(step);
      }
      window.requestAnimationFrame(step);
    
  


The JavaScript library is intended to be portable. I use objects and inheritance wherever possible.

```
(function(window, undefined) {

function extend(ChildClass, ParentClass) {
var parent = new ParentClass();
ChildClass.prototype = parent;
ChildClass.prototype.super = parent.constructor;
ChildClass.prototype.constructor = ChildClass;
}
function random_position(canvas) {

Solution

When using HTML local storage, you should always check for the possibility of failure. Some possible failure modes are:

  • Local storage is not supported by the browser



  • Local storage is prohibited for that domain by policy



  • The storage quota has been exceeded



Therefore, instead of checking if(typeof(Storage) !== "undefined"), you should wrap the operations in a try-catch block. Otherwise, the failures above will crash your entire application.

Game.prototype.load_scores = function() {
  try {
    var local_data = JSON.parse(localStorage.getItem("asteroids")) || {};
    var version = local_data.version || 0.1;
    if(version < this.version) {
      alert("New version installed, clearing scores.");
      this.scores = [];
      this.save_scores();
    } else {
      this.scores = local_data['scores'] || [];
    }
  } catch (e) {
    // Local storage unsupported, or permission denied
    this.scores = [];
  }
}
Game.prototype.save_scores = function() {
  try {
    var local_data = JSON.parse(localStorage.getItem("asteroids")) || {};
    local_data['scores'] = this.scores;
    local_data['version'] = this.version;
    localStorage.setItem("asteroids", JSON.stringify(local_data));
  } catch (e) {
    // Local storage unsupported, quota exceeded, or permission 
  }
}


With that recommendation and one other change, I've reproduced your code verbatim as a Stack Snippet, so that you can play it live.

The other change is that I've put your ` in a window.onload function. That is not a problem with your code, but rather a consequence of the way Stack Snippets work: the JavaScript is injected at the end, so your initialization code would be called before the Game code is loaded if I didn't defer the initialization like that.



(function(window, undefined) {

function extend(ChildClass, ParentClass) {
var parent = new ParentClass();
ChildClass.prototype = parent;
ChildClass.prototype.super = parent.constructor;
ChildClass.prototype.constructor = ChildClass;
}
function random_position(canvas) {
return {
x: Math.random() * canvas.width,
y: Math.random() * canvas.height
}
}
function copy_coords(coords) {
return { x: coords.x, y: coords.y };
}
function middle(canvas) {
return {
x: canvas.width / 2,
y: canvas.height / 2
}
}
function random_velocity(canvas) {
//cross canvas in about 10s
return {
x: (Math.random() - 0.5) canvas.width 0.1,
y: (Math.random() - 0.5) canvas.height 0.1
}
}
function random_angle() {
return Math.random() 2 Math.PI;
}
function random_rotation() {
return (0.5 - Math.random()) Math.PI 0.5;
}
function distance_between(obj1, obj2) {
return Math.sqrt(Math.pow(obj1.position.x - obj2.position.x,2) + Math.pow(obj1.position.y - obj2.position.y,2));
}
function compare(a,b) {
if (a.score === b.score) return 0;
if (a.score > b.score) {
return -1;
} else {
return 1;
}
}
// THE GAME===========================

function Game(canvas) {
this.version = 0.3;
this.canvas = canvas;
this.c = this.canvas.getContext("2d");
this.load_scores();
this.ship = new Ship(this, this.canvas);
this.waiting = true;
this.speed = 1;
this.muted = true;
}
Game.prototype.restart = function() {
this.waiting = false;
this.asteroids = [];
this.projectiles = [];
this.powerups = [];
this.objects = [];
this.level = 1;
this.reset_asteroids();
this.ship.reset();
this.score = 0;
}
Game.prototype.reset_asteroids = function() {
this.asteroids = [];
for (var i=0;i this.scores[this.scores.length-1]['score']) this.save_score();
this.waiting = true;
}
if (Math.random() > 0.999) {
var possible = "AFMWLR"; //Ammo, Friction, Manouverability, Weapon, Life, Rate
var bonus = possible.charAt(Math.floor(Math.random() * possible.length));
this.powerups.push(new PowerUp(this, bonus));
}
}
Game.prototype.save_score = function() {
this.load_scores();
var name = window.prompt('Please enter your name');
if (name !== null && name !== "") {
this.scores.push({
name: name,
score: this.score
})
this.scores = this.scores.sort(compare);
this.scores = this.scores.slice(0, 10);
}
this.save_scores();
}
Game.prototype.load_scores = function() {
try {
var local_data = JSON.parse(localStorage.getItem("asteroids")) || {};
var version = local_data.version || 0.1;
if(version this.canvas.width) {
this.position.x -= this.canvas.width;
} else if (this.position.x this.canvas.height) {
this.position.y -= this.canvas.height;
} else if (this.position.y 2 * Math.PI) {
this.angle -= 2 * Math.PI;
}
if(this.angle = 100) {
var new_vel = copy_coords(this.velocity);
new_vel.x *= 0.95;
new_vel.y *= 0.95;
var a1 = new Asteroid(this.game, new_mass, copy_coords(this.position), new_vel, this.rotation_speed);
var a2 = new Asteroid(this.game, new_mass, copy_coords(this.position), copy_coords(new_vel), this.rotation_speed);
a1.density = th

Code Snippets

Game.prototype.load_scores = function() {
  try {
    var local_data = JSON.parse(localStorage.getItem("asteroids")) || {};
    var version = local_data.version || 0.1;
    if(version < this.version) {
      alert("New version installed, clearing scores.");
      this.scores = [];
      this.save_scores();
    } else {
      this.scores = local_data['scores'] || [];
    }
  } catch (e) {
    // Local storage unsupported, or permission denied
    this.scores = [];
  }
}
Game.prototype.save_scores = function() {
  try {
    var local_data = JSON.parse(localStorage.getItem("asteroids")) || {};
    local_data['scores'] = this.scores;
    local_data['version'] = this.version;
    localStorage.setItem("asteroids", JSON.stringify(local_data));
  } catch (e) {
    // Local storage unsupported, quota exceeded, or permission 
  }
}

Context

StackExchange Code Review Q#84898, answer score: 10

Revisions (0)

No revisions yet.