<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>Pixi.JS Game</title>
  
  <!-- LOAD PIXI.JS & COLLISION DETECTION & jQuery SUPPORT -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.5.6/pixi.min.js"></script>
	<script src="https://cdn.rawgit.com/kittykatattack/bump/a06e59da/bin/bump.js"></script>
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
	
	<!-- LOAD ANY CUSTOM CSS RULES THAT MIGHT BE IN THE style.css FILE -->
	<link rel="stylesheet" type="text/css" href="style.css">
  
</head>
<body>
  
  <div id="stage">
    This will contain the PIXI.js stage, you shouldn't see this message when
    running the code a.k.a. Plunker Live Preview
  </div>
  
  <!-- LINK THE JavaScript FILE, DON'T WRITE ANY JavaSript HERE!! -->
  
  <!-- Generic helper functions -->
  <script type="text/javascript" src="scripts/helpers.js"></script>
  
  <!-- Pixi.JS general and stage setup code - if other setup is required at the start, link the code's file here -->
  <script type="text/javascript" src="scripts/setup/pixi.js"></script>
  <script type="text/javascript" src="scripts/setup/globals.js"></script>
  <script type="text/javascript" src="scripts/setup/stage.js"></script>
  
  <!-- Game helper functions -->
  <script type="text/javascript" src="scripts/game/helpers.js"></script>
  
  <!-- Collision detection trigger code -->
  <script type="text/javascript" src="scripts/game/collision.js"></script>
  
  <!-- Custom Classes representing game objects - link other class files here -->
  <script type="text/javascript" src="scripts/game/classes/star.js"></script>
  <script type="text/javascript" src="scripts/game/classes/score.js"></script>
  <script type="text/javascript" src="scripts/game/classes/asteroid.js"></script>
   <script type="text/javascript" src="scripts/game/classes/platform.js"></script>
  
  <!-- Class Factories - can create specific game objects - link other factories files here -->
  <script type="text/javascript" src="scripts/game/factories/star.js"></script>
  <script type="text/javascript" src="scripts/game/factories/score.js"></script>
  <script type="text/javascript" src="scripts/game/factories/asteroid.js"></script>
  <script type="text/javascript" src="scripts/game/factories/platform.js"></script>
  
  <!-- Game Background -->
  <script type="text/javascript" src="scripts/game/background.js"></script>
  
  <!-- Game Loop / Script -->
  <script type="text/javascript" src="scripts/game/loop.js"></script>
  
  <!-- Game Setup - creates objects and starts the Game Loop / Script (above) -->
  <script type="text/javascript" src="scripts/game/setup-objects.js"></script>
  
  <!-- Pixi Remote Texture Loader - once all textures have loaded, it runs the Game Setup (above) -->
  <script type="text/javascript" src="scripts/game/_loader.js"></script>
  
</body>
</html>
/* Styles go here */

* {
  padding: 0;
  margin: 0;
}

#stage {
  position: absolute;
  display: block;
  height: 100%;
  width: 100%;
}
// Raz: I'd recommend using semicolons just like you would in C#
// Raz: I'd also recommend you open up the Chrome Developer tools to check for JavaScript & PIXI errors

// put any non-Pixi related functions here

/** 
 * Normalise the requestAnimationFrame() function to account for 
 * different browsers having different implementations of it
 * https://gist.github.com/addyosmani/5434533
 */
window.requestAnimationFrame = function() {
  return window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    window.msRequestAnimationFrame ||
    window.oRequestAnimationFrame ||
    function(f) {
      window.setTimeout(f, 1e3 / 60);
    }
}();

/**
 * isNumeric function in Javascript
 * https://gist.github.com/pinalbhatt/9672790
 */
function IsNumeric(n) {
  return !isNaN(parseFloat(n)) && isFinite(n);
}

/**
 * RANDOM NUMBER GENERATOR WITH LIMITS 
 */
// taken from: http://stackoverflow.com/a/7228322
// returns a random integer between min and max
function RandomNumberBetween(min, max) {
  return Math.floor(Math.random() * (max - min + 1) + min);
}




// Raz: I'd recommend using semicolons just like you would in C#
// Raz: I'd also recommend you open up the Chrome Developer tools to check for JavaScript & PIXI errors

// use the WebGL Browser rendering technology by default as it's faster than HTML5 Canvas
var type = "WebGL";

// if it's not supported, then
if (!PIXI.utils.isWebGLSupported()) {
  // use the HTML5 Canvas Browser rendering technology
  type = "canvas";
}

// test PIXI, should output a test message in the Console
PIXI.utils.sayHello(type);

// Aliases to be used from this point forward, simplifies the code
// use http://pixijs.download/release/docs/index.html for more info on each of these
var
  Container = PIXI.Container, // keeps track of objects; also used as main stage
  ParticleContainer = PIXI.particles.ParticleContainer, // a highly optimised container that is capable of efficiently rendering fast moving sprites
  autoDetectRenderer = PIXI.autoDetectRenderer, // Pixi rendering engine
  loader = PIXI.loader, // loads external resources
  resources = PIXI.loader.resources, // loaded reasources/sprites repository
  Sprite = PIXI.Sprite, // a normal Pixi object
  Graphics = PIXI.Graphics, // a programmatically drawn object, used as a Sprite
  Texture = PIXI.Texture, // resources are typically rendered as Textures, then assigned to Sprites
  Text = PIXI.Text, // a programmatically drawn text object
  collisionDetection = new Bump(PIXI) // collision detection tool
;
// Raz: I'd recommend using semicolons just like you would in C#
// Raz: I'd also recommend you open up the Chrome Developer tools to check for JavaScript & PIXI errors

// prepare variables to be used for the Pixi stage and renderer
var stage = new Container(); // the main Pixi stage container
var containerElement = $("#stage"); // jQuery object, grab a handle on the <div id="stage"></div> from index.html
var containerWidth;
var containerHeight; // actual container height/width, determined later
var stageWidth, stageHeight; // stage height/width, determined later
var renderer = autoDetectRenderer(0, 0); // PIXI graphical renderer, don't care about size here
var stageBox; // a JSON object with stage boundaries, mainly used when checking stage wall collisions

// attach the drawing board to the View
containerElement.html(renderer.view);

// code to be run when stage dimensions need to be (re)set
var ResizeStage = function() {
  
  // get the actual height and width of the HTML container from index.html
  containerWidth = containerElement.innerWidth();
  containerHeight = containerElement.innerHeight();

  // set the stage final height and width - this can be artificially limited by changing these values
  stageWidth = containerWidth;
  stageHeight = containerHeight;

  // set the stage boundaries
  stageBox = {
    x: 0, // top left corner X position
    y: 0, // top left corner Y position
    height: stageHeight, // number of pixels
    width: stageWidth, // number of pixels
    halfHeight: stageHeight / 2, // number of pixels - convenience value
    halfWidth: stageWidth / 2 // number of pixels - convenience value
  };
  
  // check if we have a background and update its size accordingly
  if (objects.background) {
    objects.background.height = stageHeight;
    objects.background.width = stageWidth;
  }

  // make sure the drawing board has the size we want, width first, then height
  renderer.resize(stageWidth, stageHeight);
  
};

// if window dimensions change, resize the stage accordingly
$(window).on('resize', ResizeStage);

// make sure the stage has the right size straight away
ResizeStage();






// Raz: I'd recommend using semicolons just like you would in C#
// Raz: I'd also recommend you open up the Chrome Developer tools to check for JavaScript & PIXI errors

/**
 * Star Class - "options" object to be given when creating a new instance
 * e.g.: 
 * 
 *  // create a new object of type Star and save it in the "objects" container
 *  // ".star" is the identifier that needs to be used below with the "id:" attribute
 *  objects.star = new Star(
 *    {
 *      id: "star", // the identifier for this object, must be the same as the identifier given in the objects JSON
 *      dimensions: {
 *        height: 100, // pixels
 *        width: 100 // pixels
 *      },
 *      position: { // starting position
 *        x: stageHeight / 2,
 *        y: stageWidth / 2
 *      },
 *      speed: 5,
 *      speed: {
 *        x: 5, // how many pixels at a time will this move on the x axis
 *        y: 5 // how many pixels at a time will this move on the y axis
 *      },
 *      onHit: function () {
 *        // code to run if this particular object has been hit
 *      }
 *    }
 *  );
 * 
 */
var Star = function(options) {

  /**
   * Current class instance
   */
  var $this = this;

  /**
   * Pixi.JS Sprite
   */
  var sprite;

  /**
   * Base Pixi.JS object config, a JSON structure
   */
  var config;

  /**
   * Default config values, a JSON structure 
   * If any of these keys/values are missing from the `options`, they will be used instead
   */
  var baseConfig = {
    id: "star",
    dimensions: {
      height: 100, // pixels
      width: 100 // pixels
    },
    position: { // starting position
      x: stageHeight / 2,
      y: stageWidth / 2
    },
    speed: {
      x: 5, // how many pixels at a time will this move on the x axis
      y: 5 // how many pixels at a time will this move on the y axis
    },
  };

  /**
   * Constructor. Will be called at the bottom of the class.
   */
  var Init = function() {

    // if the options object isn't there i.e. not given, then set it to a blank object
    if (!options) options = {};

    // allow the baseConfig values to be overridden by the options values, if any
    // Object.assign() will perform a shallow copy, with the right-most object's properties/keys overwriting the left-most object's properties
    config = Object.assign({}, baseConfig, options);

    // enable the following line to debug
    // console.log("default 'baseConfig' config: ", baseConfig, "options for current instance: ", options, "final config for current instance, based on the default 'baseConfig' and 'options' objects: ", config);

    // check "dimensions" option - if number given instead of {height, width} JSON, then use that
    if (IsNumeric(config.dimensions)) {
      config.dimensions = {
        height: config.dimensions,
        width: config.dimensions
      };
    }

    // check "speed" option - if number given instead of {x, y} JSON, then use that
    if (IsNumeric(config.speed)) {
      config.speed = {
        x: config.speed,
        y: config.speed
      };
    }

    // prepare the Pixi Sprite and add it to the stage
    PreparePixiSprite();

  };

  /**
   * Build the object and add it to the stage
   */
  var PreparePixiSprite = function() {

    // create the object using the "star" preloaded texture as its Sprite
    sprite = new Sprite(resources["star"].texture);

    // set the height
    sprite.height = config.dimensions.height;
    // set the width
    sprite.width = config.dimensions.width;

    // set its initial horizontal position
    sprite.x = config.position.x;
    // set its initial vertical position
    sprite.y = config.position.y;

    // set the horizontal movement speed/velocity
    sprite.vx = config.speed.x;
    // set the vertical movement speed/velocity
    sprite.vy = config.speed.y;

    /** 
     * Set the texture's anchor point relative to the object's dimensions - both values are between 0.0 and 1.0 (i.e. 0 to 100%)
     * First value refers to the horizontal anchor point, the second refers to the vertical anchor point
     * (0.5, 0.5) -> anchor point will be in the centre of the Sprite
     * By default, the anchor point is (0, 0) -> top-left of the object's corner
     */
    sprite.anchor.set(0.5, 0.5);

    // set some helper values on this object for later use
    sprite.halfHeight = sprite.height / 2;
    sprite.halfWidth = sprite.width / 2;

    // denote it as a circular shape - for collision detection purposes
    sprite.circular = true;

    // actually make the Sprite be circular by applying a mask to the Texture it uses
    // first, create the relevant shape
    var circleMask = new Graphics();
    circleMask.beginFill(0x9966FF);
    circleMask.drawCircle(0, 0, sprite.halfHeight + 2); // use the halfHeight property as the mask's radius - needs an extra 2 pixels to be accurate
    circleMask.endFill();
    // then add the shape as the Sprite's mask
    sprite.mask = circleMask; // comment this out to check if the mask shape actually fills the Sprite area as expected and modify the Graphics object accordingly
    // also add the mask as a child to our Sprite - doesn't work without it
    sprite.addChild(circleMask);

    // add it to the stage
    stage.addChild(sprite);

  };

  // getter method for the Pixi Sprite object
  $this.GetSprite = function() {
    return sprite;
  }

  // move object horizontally according to its speed/velocity
  $this.MoveX = function() {
    sprite.x += sprite.vx;
  };

  // move object vertically according to its speed/velocity
  $this.MoveY = function() {
    sprite.y += sprite.vy;
  };

  // reposition object to new x,y coordinates
  $this.MoveTo = function(x, y) {
    sprite.x = x;
    sprite.y = y;
  };

  $this.Animate = function() {
    
    // use Bump to contain this sprite within the stage
    // the third parameter makes it bounce off the walls
    collisionDetection.contain(sprite, stageBox, true);

    // move the object horizontally
    //$this.MoveX();

    // move the object vertically
    //$this.MoveY();

  };

  /** 
   * Collision logic against a foreign object
   */
  $this.CheckCollision = function(object) {

    // the collision detection tool's result - checks hits between this object's Sprite and the foreign object's Sprite
    var collisionResult = collisionDetection.movingCircleCollision(sprite, object.GetSprite(), false);
    // var collisionResult = collisionDetection.hit(sprite, object.GetSprite(), true, false, true);

    // check if the Star has collided with the foreign object's Sprite
    // result may be different depending on which type of collision it was
    // boolean (TRUE/FALSE) result if foreign sprite is also a circle
    // one of the following if the foreign sprite is a rectangle: 
    //    HIT: "topLeft", "topMiddle", "topRight", "leftMiddle", "rightMiddle", "bottomLeft", "bottomMiddle", or "bottomRight"
    //    NO HIT: undefined
    //
    // if collisionResult is NOT FALSE or undefined
    if (collisionResult) {

      // run the collision logic on this object - will update score as well
      $this.DoCollision();

      // run the collision logic on the foreign object - but don't update score again
      object.DoCollision(false);

    }

  };

  /**
   * Something to do if collided - called in the `CheckCollision` method above
   * If `alsoUpdateScore` is given a boolean value, it will update the score if parameter value is `TRUE`
   */
  $this.DoCollision = function(alsoUpdateScore) {

    // set the collision following flag on the pixi object
    sprite.hasBeenHit = true;

    // and add this object to the objects removal list
     globalObjectsToRemove.stars.objects.push($this);

    // spawn another in its place using the same config as at the start
    // effectively reset the star object, replace it with a fresh instance
    // objects[config.id] = CreateStar(config.id);

    // update game score if required, increment it by 1
    if (alsoUpdateScore === undefined || alsoUpdateScore === true) {
      objects.score.Update(1);
    }

  };

  /**
   * Call the instance constructor
   */
  Init();

};
// Raz: I'd recommend using semicolons just like you would in C#
// Raz: I'd also recommend you open up the Chrome Developer tools to check for JavaScript & PIXI errors

/**
 * Runs at 60fps
 * Every frame it calculates new positions/collisions/actions
 * Then it redraws the stage to reflect any changes
 * This is effectively the game script
 */
var GameLoop = function() {
  
  /**
   * Animate/Move objects
   * "objects" is defined in "scripts/setup/globals.js"
   */ 
  // go through all existing game objects
  // this kind of code structure can be used if you have a set of objects you want to group together and perform a set of actions (like Animate()) on all of them
  // for (var key in objects) {

  //   // get a handle on each object found in the objects JSON structure
  //   var object = objects[key];

  //   // all objects should have an Animate() public method
  //   // perform animation for current object
  //   if (object.Animate && typeof object.Animate == "function") {
  //     object.Animate();
  //   }

  // }
  
  // or just do it manually, as the case requires it
  //objects.star1.Animate();
  //objects.star2.Animate();
  // objects.asteroid.Animate();
  
  for (var key in objects.asteroids) {
    var asteroid = objects.asteroids[key];
    asteroid.Animate();
  }

  /**
   * Check all relevant collisions
   * See "scripts/game/collision.js"
   */ 
  CheckCollisions();

  /**
   * Remove all objects from the stage that have been added to the `globalObjectsToRemove` JSON structure's arrays
   * "globalObjectsToRemove" is defined in "scripts/setup/globals.js"
   */ 
  for (var key in globalObjectsToRemove) {
    
    // `key` will be, for example, `stars` from the `globalObjectsToRemove` JSON object
    // you can add more groups, like `stars`, to the qglobalObjectsToRemoveq JSON object in "scripts/setup/globals.js"
    CleanupSprites(globalObjectsToRemove[key]);
    
  }

  /**
   * Render the stage a.k.a. (re)construct the stage graphically
   */ 
  renderer.render(stage);
  
  /**
   * Loop this function at a default rate of 60 frames per second
   */ 
  requestAnimationFrame(GameLoop);

}
// Raz: I'd recommend using semicolons just like you would in C#
// Raz: I'd also recommend you open up the Chrome Developer tools to check for JavaScript & PIXI errors

/**
 * Creates objects and starts the game loop
 */
var Setup = function() {

  // set the background
  SetBackground();

  // build a new star
  objects.star1 = CreateStar("star1", 100, 800);
  objects.star2 = CreateStar("star2", 200, 800);
  objects.star3 = CreateStar("star3", 300, 800);
  objects.star4 = CreateStar("star4", 400, 800);
  objects.star5 = CreateStar("star5", 500, 800);

  // build the hits text
  objects.score = CreateScore();

  objects.platform1 = CreatePlatform("platform1", 100, 700);
  objects.platform2 = CreatePlatform("platform2", 300, 700);
  objects.platform3 = CreatePlatform("platform3", 500, 700);
  objects.platform4 = CreatePlatform("platform4", 700, 700);


  objects.asteroids = [
    CreateAsteroid("asteroid1"),
    CreateAsteroid("asteroid2"),
    CreateAsteroid("asteroid3"),
    CreateAsteroid("asteroid4"),
    CreateAsteroid("asteroid5")
  ];





  // start the game loop i.e. the game script - defined above
  GameLoop();

};
// Raz: I'd recommend using semicolons just like you would in C#
// Raz: I'd also recommend you open up the Chrome Developer tools to check for JavaScript & PIXI errors

/**
 * Load an image and run the `Setup()` function when it's done.
 * Textures loaded this way will be available to use as such:
 * 
 * // replace "star" with whatever `name` the required texture was given
 * var obj = new Sprite(resources["star"].texture);
 */ 
// Pixi resource loader
loader
  // use the following code as a template for loading more remote/external textures
  // .add({ // mind the dot at the start of this line
  //   name: "star", // used to uniquely identify this texture in the "resources" repository
  //   url: "http://i.imgur.com/A4jyRT0.png", // remote location
  //   crossOrigin: true // needs this to be able to load images remotely
  // })
  .add({ // mind the dot at the start of this line
    name: "star", // used to uniquely identify this texture in the "resources" repository
    url: "http://i.imgur.com/A4jyRT0.png", // remote location
    crossOrigin: true // needs this to be able to load images remotely
  })
  .add({
    name: "galaxy", // reference
    url: "https://i.imgur.com/Fm2qSGt.jpg", // remote location
    crossOrigin: true // needs this to be able to load images remotely
  })
  
  .add({
    name: "asteroid",
    url: "https://i.imgur.com/dMb74Tk.png",
    crossOrigin: true
  })
  
  .add({
    name: "Platform",
    url: "https://i.imgur.com/7JF2Vqa.png",
    crossOrigin: true
  })
  
  // run the Setup() function when all textures have finished loading
  .load(Setup);
// Raz: I'd recommend using semicolons just like you would in C#
// Raz: I'd also recommend you open up the Chrome Developer tools to check for JavaScript & PIXI errors

/** 
 * Write any functions that help with running/animating/collisions within the game.
 * Generally, anything that doesn't quite make sense to add to a particular class.
 * e.g. 
 *    a function to check collision between multiple/different sets of objects and 
 *    trigger public methods (actions/behaviour) on them upon collision.
 */

/**
 * Cleanup helper capable of destroying and removing from the stage one or multiple groups of custom objects (BRICKS, POWERUPS, ...) if they've been hit and need to disappear.
 * 
 * Notes:
 *  Each Sprite from the custom object from the array should have a property called "hasBeenHit" (boolean).
 *  This property needs to be set prior to this function call somewhere where collision detection happens for this particular group of objects.
 * 
 * Parameters:
 *  groupsOfObjectsToCheck - just one or array of JSON objects all looking like:
 *    {
 *      objects: array of custom objects containing a GetSprite() method that returns a Pixi sprite object to be check for collision
 *      ifEmpty: function to run if all objects have been removed
 *    }
 * 
 * Parameter example:
 *  groupsOfObjectsToCheck = [
 *    {
 *      objects: [], // array of objects
 *      ifEmpty: function() {} // function/behaviour to execute if all objects have been removed from the objects array
 *    },
 *    {
 *      objects: [], // array of objects
 *      ifEmpty: function() {} // function/behaviour to execute if all objects have been removed from the objects array
 *    }
 *  ]
 * 
 * Parameter example:
 * groupsOfObjectsToCheck = {
 *    objects: [], // array of objects
 *    ifEmpty: function() {} // function/behaviour to execute if all objects have been removed from the objects array
 *  }
 * 
 * Usage example:
 *    CleanupSprites(
 *      [
 *        {
 *          objects: arrayContainingCustomObjects,
 *          ifEmpty: function () {
 *            // code to run if all objects have been hit and are now gone from the stage
 *          }
 *        },
 *        {
 *          objects: arrayContainingCustomObjects,
 *          ifEmpty: function () {
 *            // code to run if all objects have been hit and are now gone from the stage
 *          }
 *        },
 *        ...
 *      ]
 *    );
 * 
 */
var CleanupSprites = function(groupsOfObjectsToCheck) {

  // don't change this unless you know exactly why you are doing it

  var
  // will be used below to store a reference to the objects & their group (defined above) that are currently being checked
    group, objects,
    // will be used below to store a reference to the object that is currently being checked
    sprite;

  if (!Array.isArray(groupsOfObjectsToCheck)) {
    groupsOfObjectsToCheck = [groupsOfObjectsToCheck];
  }

  for (var groupIndex = 0; groupIndex < groupsOfObjectsToCheck.length; groupIndex++) {

    group = groupsOfObjectsToCheck[groupIndex];
    objects = group.objects;

    for (var objectIndex = objects.length - 1; objectIndex >= 0; objectIndex--) {

      // grab a reference to the object
      sprite = objects[objectIndex].GetSprite();

      // object hit!
      if (sprite.hasBeenHit) {

        // destroy the object
        sprite.destroy();

        // remove the object from the group
        objects.splice(objectIndex, 1);

        // stop the current for loop
        break;

      }

    }

    // if there aren't any other objects left and there is some behaviour for this
    if (!objects.length && group.ifEmpty && typeof group.ifEmpty === "function") {

      // run the empty behaviour defined above
      group.ifEmpty();

    }

  }

}

/**
 * Keyboard Class
 * Based on https://github.com/kittykatattack/learningPixi#keyboard
 * Monitors whether a given keyboard key (ASCII) has been pressed or released
 * 
 * Required Parameters:
 *    keyCode - ASCII number corresponding to a key on the user's keyboard
 * 
 * Optional Parameters:
 *    press - a function() {} to execute when the key is pressed
 *    release - a function() {} to execute when the key is released
 * 
 * Returns:
 *  {
 *    isDown: boolean,
 *    isUp: boolean
 *  }
 * 
 * Notes:
 *    keyboard.isDown and keyboard.isUp can be used to check the state of a key during a game frame/loop
 * 
 * Usage:
 *    var pressFn = function() {};
 *    var releaseFn = function() {};
 *    
 *    // replace 76 with your desired ASCII key code
 *    // uncomment "console.log()" further down to get the key code of any pressed key, for development use
 *    var kb = new Keyboard(76, pressFn, releaseFn);
 * 
 *    if (kb.isDown || kb.isUp) {
 *      // keyboard used!
 *    }
 */
var Keyboard = function(keyCode, press, release) {

  // don't change this unless you know exactly why you are doing it

  var key = {};

  key.code = keyCode;
  key.isDown = false;
  key.isUp = true;
  key.press = press;
  key.release = release;

  //The `downHandler`
  key.downHandler = function(event) {
    // console.log(event.keyCode); // enable this to test out key codes
    if (event.keyCode === key.code) {
      if (key.isUp && key.press && typeof key.press == "function") key.press();
      key.isDown = true;
      key.isUp = false;
    }
    event.preventDefault();
  };

  //The `upHandler`
  key.upHandler = function(event) {
    if (event.keyCode === key.code) {
      if (key.isDown && key.release && typeof key.release == "function") key.release();
      key.isDown = false;
      key.isUp = true;
    }
    event.preventDefault();
  };

  //Attach event listeners
  window.addEventListener(
    "keydown", key.downHandler.bind(key), false
  );
  window.addEventListener(
    "keyup", key.upHandler.bind(key), false
  );

  return key;

}

/**
 * Touch Class
 * Offers touch/mouse support. 
 * 
 * Parameters:
 *    itemToCheck - one of the following
 *        => a stage part, one of the following: "topLeft", "topRight", "bottomLeft", "bottomRight", "anywhere"
 *        => OR a rectangular area within the stage: { x: number, y: number, height: number, width: number }
 *        => OR a Pixi.js Sprite
 *    press - a "function(){}" that will be executed when the chosen stage part/area/Sprite has been touched
 *    release - a "function(){}" that will be executed when the chosen stage part/area/Sprite has stopped being touched
 * 
 * Returns:
 *  {
 *    object: the object that collisionDetection will actually check
 *    type: the type of the object ("part", "box", "sprite")
 *    isHit: boolean
 *    position: { // user's touch position within the stage
 *      x: int
 *      y: int
 *    }
 *  }
 * 
 * Usage:
 *    var pressFn = function() {};
 *    var releaseFn = function() {};
 *    
 *    // obj is a Pixi Sprite
 *    var touch = new Touch(obj, pressFn, releaseFn);
 * 
 *    if (touch.isHit || touch.isHit) {
 *      // object is being touched!
 *    }
 */
// don't change this unless you know exactly why you are doing it
var Touch = function(itemToCheck, press, release) {

  // object that will be returned from this function, similarly to how the keyboard support works
  var touchObject = {
    object: null,
    type: "",
    isHit: false,
    position: null,
    press: press,
    release: release
  };

  /**
   *  0 - x,y point indicating the top left corner of each area
   * 
   *                  halfWidth       halfWidth
   *              0 ------------ 0 --------------
   *              |              |               |
   *  halfHeight  |  topLeft     |  topRight     |   halfHeight
   *              |              |               |
   *              0 ------------ 0 --------------|
   *              |              |               |
   *  halfHeight  |  bottomLeft  |  bottomRight  |   halfHeight
   *              |              |               |
   *               ------------------------------
   *                  halfWidth       halfWidth
   */

  // split the stage into 4 areas
  // these will be used by the Bump library to determine collision with the point
  var stageParts = {
    "topLeft": {
      x: 0,
      y: 0, // area top left position
      width: stageBox.halfWidth,
      height: stageBox.halfHeight // area height and width
    },
    "topRight": {
      x: stageBox.halfWidth,
      y: 0, // area top left position
      width: stageBox.halfWidth,
      height: stageBox.halfHeight // area height and width
    },
    "bottomLeft": {
      x: 0,
      y: stageBox.halfHeight, // area top left position
      width: stageBox.halfWidth,
      height: stageBox.halfHeight // area height and width
    },
    "bottomRight": {
      x: stageBox.halfWidth,
      y: stageBox.halfHeight, // area top left position
      width: stageBox.halfWidth,
      height: stageBox.halfHeight // area height and width
    },
    "anywhere": {
      x: 0,
      y: 0, // area top left position
      width: stageBox.halfWidth * 2,
      height: stageBox.halfHeight * 2 // area height and width
    }
  };

  // is the object a stage part?
  if (stageParts[itemToCheck] !== undefined) {
    touchObject.object = stageParts[itemToCheck];
    touchObject.type = "part";
  }

  // is it a Pixi sprite?
  else if (itemToCheck.pluginName && itemToCheck.pluginName == "sprite") {
    touchObject.object = itemToCheck;
    touchObject.type = "sprite";
  }

  // is it a rectangular area?
  else if (itemToCheck.x && itemToCheck.y && itemToCheck.height && itemToCheck.width) {
    touchObject.object = itemToCheck;
    touchObject.type = "box";
  }

  // anything else won't work with this, so stop doing anything else
  else {
    throw new Error("Touch Helper: itemToCheck parameter is invalid. Please check the documentation in scripts/game/helpers.js. value=", itemToCheck);
  }

  // every time the user itneracts with the stage, check collision status
  var checkTouchCollision = function(event) {

    // get the point coordinates of the user's touch
    var point = getEventPos(this, event);

    // if the user touched within the configured object, then set it accordingly
    if (collisionDetection.hitTestPoint(point, touchObject.object)) {

      // mark the current touch object as being pressed / down
      touchObject.isHit = true;

      // point: { x: number, y: number }
      touchObject.position = point;

      // run the given function, if any
      if (touchObject.press && typeof touchObject.press == "function") {
        touchObject.press();
      }

    } else {

      // mark the current touch object as not being pressed / down
      touchObject.isHit = false;

      // point: { x: number, y: number }
      touchObject.position = point;

      // run the given function, if any
      if (touchObject.release && typeof touchObject.release == "function") {
        touchObject.release();
      }

    }

  }

  // See here for more information on jQuery.on() : http://api.jquery.com/on/
  containerElement.on("touchstart mousedown pointerdown touchend mouseup pointerup", checkTouchCollision);

  // return the touch information for later use
  return touchObject;

}

/** 
 * Supporting function. Returns a JSON object containing { x: number, y: number } coordinates of a JavaScript event.
 * Based on code found at: https://bencentra.com/code/2014/12/05/html5-canvas-touch-events.html
 */
function getEventPos(canvasEl, event) {

  // get the canvas element bounding coordinates
  var rect = canvasEl.getBoundingClientRect();

  // return an { x, y } object with the user's event coordinates 
  return {
    x: event.clientX - rect.left,
    y: event.clientY - rect.top
  };

}
/**
 * All relevant collision checks will be triggered from here
 */ 
var CheckCollisions = function () {
  
  // check collision between the two stars
  objects.star1.CheckCollision(objects.star2);
  
  var sprites = [];
  
  for (var key in objects.asteroids) {
    var asteroid = objects.asteroids[key];
    
    asteroid.CheckCollision(objects.star1);
    asteroid.CheckCollision(objects.star2);
    asteroid.CheckCollision(objects.star3);
    asteroid.CheckCollision(objects.star4);
    asteroid.CheckCollision(objects.star5);
    
    
    asteroid.CheckCollision(objects.platform1);
    asteroid.CheckCollision(objects.platform2);
    asteroid.CheckCollision(objects.platform3);
    asteroid.CheckCollision(objects.platform4);
    
    
   sprites = asteroid.GetSprite();

  }
  
  collisionDetection.multipleCircleCollision(sprites);
  
};
/**
 * Star Factory
 */ 
var CreateStar = function (id, xPos, yPos) {
  
  // create a new Star instance and return it
  return new Star({
    id: id, // must be the same as the identifier in the objects JSON
    dimensions: 60, // value can also be given as { height: number, width: number } to set separate dimensions
    position: { // starting position
      // get a random number between a quarter and three quarters of the stage's width
      x: xPos, //RandomNumberBetween(stageWidth / 4, stageWidth / 4 * 3),
      // get a random number between a quarter and three quarters of the stage's width
      y:  yPos// RandomNumberBetween(stageHeight / 4, stageHeight / 4 * 3)
    },
    speed: 3, // value can also be given as { x: X_SPEED, y: Y_SPEED } to set separate axis speeds
  });
  
}
/**
 * Creates the game background - should be called first when creating objects in the `Setup` function (scripts/game/setup-objects.js)
 */ 
var SetBackground = function () {
  
  // create the background using a single colour
  // var bg = new Graphics();
  // bg.beginFill(0x66CCFF); // set the background colour
  // bg.drawRect(0, 0, stageWidth, stageHeight);
  // bg.endFill();
  
  //create the background using an existing texture
  var bg = new Sprite(resources["galaxy"].texture);
  bg.height = stageHeight;
  bg.width = stageHeight;
  
  // make it globally available
  objects.background = bg;
  
  // add it to the stage
  stage.addChild(bg);
  
};
/**
 * Global variables/arrays/JSON objects go in here
 */ 
// a series of key-value pairs representing the game objects or object arrays
var objects = {};

// this is a convenience JSON structure that will serve as a killer for custom objects that have been "hit"
// see lecture notes for more information on this
// also see the `DoCollision` method of the `Star` class in "scripts/game/classes/star.js" for an example
var globalObjectsToRemove = { 
  stars: { 
    objects: [], 
    ifEmpty: function () {
      // if you would like to execute something by default once all stars that have been marked for remval are gone
      // can be replaced at any time
    } 
  },
  chests: {
    objects: [],
    ifEmpty: function () {
      //
    }
  }
  // ... add other groups here by following the above structure
};
/**
 * Score Factory
 * Accepts an `initialScore` as a number to set the starting score accordingly. The Class uses 0 by default if not set.
 */ 
var CreateScore = function (initialScore) {
 
  // create a new Score instance and return it
  return new Score(initialScore);
  
}
/**
 * Score class. Keeps track of game score.
 * Accepts `initialScore` as a starting score value when creating a new instance. Uses 0 by default if not set.
 */
var Score = function(initialScore) {

  /**
   * Current instance
   */ 
  var $this = this;
  
  /**
   * Pixi.JS text object
   */ 
  var sprite;
  
  /**
   * Score tracker
   */ 
  var score = 0;

  /**
   * Constructor
   */ 
  var Init = function() {

    // set the initial score value, if it's a valid number
    if (initialScore && IsNumeric(initialScore)) {
      score = initialScore;
    }

    // prepare the Pixi object and add it to the stage
    PreparePixiObject();

  };

  /**
   * Build the object and add it to the stage
   */ 
  var PreparePixiObject = function() {

    // create the text Sprite
    sprite = new Text(
      "Hits: " + score, {
        fontFamily: "Arial",
        fontSize: 14,
        fill: "white"
      }
    );

    // determine an [x,y] position for the text
    sprite.position.set(20, 20);

    // add it to the stage
    stage.addChild(sprite);

  };
  
  /**
   * Method called whenever the score needs changing.
   * Accepts a `value` number parameter used to update the score.
   * If the number is negative, it will change the score accordingly.
   */ 
  $this.Update = function(value) {
    
    // change the value
    score += value;
    
    // update the sprite text
    sprite.text = "Hits: " + score;
    
  }

  /**
   * Run the constructor
   */ 
  Init();

};
/**
 * Asteroid Factory
 */ 
var CreateAsteroid = function (id) {
  
  // create a new ASTEROID instance and return it
  return new Asteroid({
    id: id, // must be the same as the identifier in the objects JSON
    dimensions: 40, // value can also be given as { height: number, width: number } to set separate dimensions
    position: { // starting position
      // get a random number between a quarter and three quarters of the stage's width
      x: RandomNumberBetween(stageWidth / 4, stageWidth / 4 * 3),
      // get a random number between a quarter and three quarters of the stage's width
      y: RandomNumberBetween(stageHeight / 4, stageHeight / 4 * 3)
    },
    speed: 5, // value can also be given as { x: X_SPEED, y: Y_SPEED } to set separate axis speeds
  });
  
}
var Asteroid = function(options)
{
  var $this = this;
  var sprite;
  var config;
  
  var baseConfig = 
  {
    id: "asteroid",
    dimensions:
      {
        height: 20,
        width: 20
      },
      position:
      {
        x: stageHeight / 2,
        y: stageWidth / 2
      },
      speed:
      {
        x: 5,
        y: 5
      },
  };
  
  var Init = function()
    {
    if(!options) options = {};
    
    config = Object.assign({}, baseConfig, options);
    
     if (IsNumeric(config.dimensions)) 
     {
      config.dimensions = 
      {
        height: config.dimensions,
        width: config.dimensions
      };
     }
     
     if (IsNumeric(config.speed))
     {
      config.speed = 
      {
        x: config.speed,
        y: config.speed
      };
     }

    // prepare the Pixi Sprite and add it to the stage
    PreparePixiSprite();
};

var PreparePixiSprite = function() {

    // create the object using the "star" preloaded texture as its Sprite
    sprite = new Sprite(resources["asteroid"].texture);

    // set the height
    sprite.height = config.dimensions.height;
    // set the width
    sprite.width = config.dimensions.width;

    // set its initial horizontal position
    sprite.x = config.position.x;
    // set its initial vertical position
    sprite.y = config.position.y;

    // set the horizontal movement speed/velocity
    sprite.vx = config.speed.x;
    // set the vertical movement speed/velocity
    sprite.vy = config.speed.y;

    /** 
     * Set the texture's anchor point relative to the object's dimensions - both values are between 0.0 and 1.0 (i.e. 0 to 100%)
     * First value refers to the horizontal anchor point, the second refers to the vertical anchor point
     * (0.5, 0.5) -> anchor point will be in the centre of the Sprite
     * By default, the anchor point is (0, 0) -> top-left of the object's corner
     */
    sprite.anchor.set(0.5, 0.5);

    // set some helper values on this object for later use
    sprite.halfHeight = sprite.height / 2;
    sprite.halfWidth = sprite.width / 2;

    // denote it as a circular shape - for collision detection purposes
    sprite.circular = true;

    // actually make the Sprite be circular by applying a mask to the Texture it uses
    // first, create the relevant shape
    // var circleMask = new Graphics();
    // circleMask.beginFill(0x9966FF);
    // circleMask.drawCircle(0, 0, sprite.height); // use the halfHeight property as the mask's radius - needs an extra 2 pixels to be accurate
    // circleMask.endFill();
    // // then add the shape as the Sprite's mask
    // sprite.mask = circleMask; // comment this out to check if the mask shape actually fills the Sprite area as expected and modify the Graphics object accordingly
    // // also add the mask as a child to our Sprite - doesn't work without it
    // sprite.addChild(circleMask);

    // add it to the stage
    stage.addChild(sprite);

  };
  
  // getter method for the Pixi Sprite object
  $this.GetSprite = function() {
    return sprite;
  }

  // move object horizontally according to its speed/velocity
  $this.MoveX = function() {
    sprite.x += sprite.vx;
  };

  // move object vertically according to its speed/velocity
  $this.MoveY = function() {
    sprite.y += sprite.vy;
  };

  // reposition object to new x,y coordinates
  $this.MoveTo = function(x, y) {
    sprite.x = x;
    sprite.y = y;
  };

  $this.Animate = function() {
    
    // use Bump to contain this sprite within the stage
    // the third parameter makes it bounce off the walls
    collisionDetection.contain(sprite, stageBox, true);

    // move the object horizontally
    $this.MoveX();

    // move the object vertically
    $this.MoveY();

  };

  /** 
   * Collision logic against a foreign object
   */
  $this.CheckCollision = function(object, doAction) {

    // the collision detection tool's result - checks hits between this object's Sprite and the foreign object's Sprite
    var collisionResult = collisionDetection.circleCollision(sprite, object.GetSprite());
    // var collisionResult = collisionDetection.hit(sprite, object.GetSprite(), true, false, true);

    // check if the Star has collided with the foreign object's Sprite
    // result may be different depending on which type of collision it was
    // boolean (TRUE/FALSE) result if foreign sprite is also a circle
    // one of the following if the foreign sprite is a rectangle: 
    //    HIT: "topLeft", "topMiddle", "topRight", "leftMiddle", "rightMiddle", "bottomLeft", "bottomMiddle", or "bottomRight"
    //    NO HIT: undefined
    //
    // if collisionResult is NOT FALSE or undefined
    if (collisionResult && (doAction === undefined || doAction === true)) {
      // run the collision logic on this object - will update score as well
      $this.DoCollision();

      // run the collision logic on the foreign object - but don't update score again
      object.DoCollision(false);

    }

  };

  /**
   * Something to do if collided - called in the `CheckCollision` method above
   * If `alsoUpdateScore` is given a boolean value, it will update the score if parameter value is `TRUE`
   */
  $this.DoCollision = function(alsoUpdateScore) {

    // set the collision following flag on the pixi object
    sprite.hasBeenHit = true;

    // and add this object to the objects removal list
    // globalObjectsToRemove.stars.objects.push($this);

    // spawn another in its place using the same config as at the start
    // effectively reset the star object, replace it with a fresh instance
    // objects[config.id] = CreateStar(config.id);

    // update game score if required, increment it by 1
    if (alsoUpdateScore === undefined || alsoUpdateScore === true) {
      objects.score.Update(1);
    }

  };

  /**
   * Call the instance constructor
   */
  Init();

};

var CreatePlatform = function (id, xPos, yPos) {
  
  return new Platform({
    
    id: id,
    //dimensions: 50,
    
    position: 
    {
      x: xPos, //RandomNumberBetween(stageWidth / 4, stageWidth / 4 * 3),
      y: yPos //RandomNumberBetween(stageHeight / 4, stageHeight / 4 * 3)
    },
    speed: 2,
  });
}
var Platform = function(options)
{
  var $this = this;
  var sprite;
  var config; 
  
  var baseConfig = //This method gives the platform its details such as name, size, position and speed 
  {
    id: "Platform",
    
    dimensions:
    {
      height: 50,
      width: 100
    },
    
    position:
    {
      x: stageWidth / 2,
      y: stageHeight / 2
    },
    
    speed:
    {
      x: 0,
      y: 0
    },
  };
  
  var Init = function() //This method is like the "Start/Awake method"
  {
    if(!options) options = {};
    
    config = Object.assign({}, baseConfig, options);
    
    if(IsNumeric(config.dimensions))
    {
      config.dimensions =
      {
        height: config.dimensions,
        width: config.dimensions
      };
    }
    
    if(IsNumeric(config.speed))
    {
      config.speed =
      {
        x: config.speed,
        y: config.speed
      };
    }
    
    PreparePixiSprite();
    
  };
  
  
  var PreparePixiSprite = function()
  {
    sprite = new Sprite(resources["Platform"].texture);
    
    sprite.height = config.dimensions.height;
    sprite.width = config.dimensions.width;
    
    sprite.x = config.position.x;
    sprite.y = config.position.y;
    
    sprite.vx = config.speed.x;
    sprite.vy = config.speed.y;
    
    sprite.anchor.set(0.5,0.5);
    
    sprite.halfHeight = sprite.height /2;
    sprite.halfWidth = sprite.width / 2;
    
    //console.log('sprite x:', sprite.x, 'y:', sprite.y);
    
    sprite.circular = true;
    stage.addChild(sprite);
  };
  
  $this.MoveTo = function(x, y) {
    sprite.x = x;
    sprite.y = y;
  };

  
  // move object horizontally according to its speed/velocity
  $this.MoveX = function() {
    sprite.x += sprite.vx;
  };

  // move object vertically according to its speed/velocity
  $this.MoveY = function() {
    sprite.y += sprite.vy;
  };

  
  $this.GetSprite = function()
  {
    return sprite;
  };
  
  $this.CheckCollision = function(object) {

    // the collision detection tool's result - checks hits between this object's Sprite and the foreign object's Sprite
    var collisionResult = collisionDetection.movingCircleCollision(sprite, object.GetSprite(), false);
    // var collisionResult = collisionDetection.hit(sprite, object.GetSprite(), true, false, true);

    // check if the Star has collided with the foreign object's Sprite
    // result may be different depending on which type of collision it was
    // boolean (TRUE/FALSE) result if foreign sprite is also a circle
    // one of the following if the foreign sprite is a rectangle: 
    //    HIT: "topLeft", "topMiddle", "topRight", "leftMiddle", "rightMiddle", "bottomLeft", "bottomMiddle", or "bottomRight"
    //    NO HIT: undefined
    //
    // if collisionResult is NOT FALSE or undefined
    if (collisionResult) {

      // run the collision logic on this object - will update score as well
      $this.DoCollision();

      // run the collision logic on the foreign object - but don't update score again
      object.DoCollision(false);

    }

  };
  
   $this.DoCollision = function(alsoUpdateScore) {

    // set the collision following flag on the pixi object
    sprite.hasBeenHit = true;

    // and add this object to the objects removal list
    // globalObjectsToRemove.stars.objects.push($this);

    // spawn another in its place using the same config as at the start
    // effectively reset the star object, replace it with a fresh instance
    // objects[config.id] = CreateStar(config.id);

    // update game score if required, increment it by 1
   // if (alsoUpdateScore === undefined || alsoUpdateScore === false) {
    //  objects.score.Update(0);
    //}

  };
  
  Init();
  
}