<!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/stage.js"></script>
  
  <!-- Game helper functions -->
  <script type="text/javascript" src="scripts/game/helpers.js"></script>
  
  <!-- Custom Classes representing game objects - link other class files here -->
  <script type="text/javascript" src="scripts/game/classes/star.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-loop.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() // 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 objects = {}; // a series of key-value pairs representing the game objects or object arrays
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
  };

  // 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
 *  objects.star = new Star(
 *    {
 *      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
 *      }
 *    }
 *  );
 * 
 */
var Star = function(options) {

  // current instance
  var $this = this;
  // Pixi.JS object
  var obj;

  // base Pixi.JS object config
  var config;

  // default config values if any of them are missing from the options
  var baseConfig = {
    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
  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 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 object and add it to the stage
    PreparePixiObject();

  };

  // build the object
  var PreparePixiObject = function() {

    // create the object using the "star" preloaded texture as its Sprite
    obj = new Sprite(resources["chest"].texture);
    
    // set the height
    obj.height = config.dimensions.height;
    // set the width
    obj.width = config.dimensions.width;
    
    // set its initial horizontal position
    obj.x = config.position.x;
    // set its initial vertical position
    obj.y = config.position.y;
    
    // set the horizontal movement speed/velocity
    obj.vx = config.speed.x;
    // set the vertical movement speed/velocity
    obj.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
     */ 
    obj.anchor.set(0.5, 0.5);
    
    // set some helper values on this object for later use
    obj.halfHeight = obj.height / 2;
    obj.halfWidth = obj.width / 2;
    
    // denote it as a circular shape - for collision detection purposes
    obj.circular = true;
    
    // add it to the stage
    stage.addChild(obj);

  };

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

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

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

  // change the object horizontal movement direction
  $this.InvertXDirection = function() {
    obj.vx *= -1;
  };

  // change the object vertical movement direction
  $this.InvertYDirection = function() {
    obj.vy *= -1;
  };

  // returns TRUE if object has moved off stage left, FALSE otherwise
  $this.IsOffStageXLeft = function() {
    return ((obj.x - obj.halfWidth) <= 0);
  };

  // returns TRUE if object has moved off stage right, FALSE otherwise
  $this.IsOffStageXRight = function() {
    return ((obj.x + obj.halfWidth) >= stageWidth);
  };

  // returns TRUE if object has moved off stage top, FALSE otherwise
  $this.IsOffStageYTop = function() {
    return (obj.y - obj.halfHeight <= 0);
  };

  // returns TRUE if object has moved off stage bottom, FALSE otherwise
  $this.IsOffStageYBottom = function() {
    return ((obj.y + obj.halfHeight) >= stageHeight);
  };

  $this.Animate = function() {

    // check if object is off stage left
    if ($this.IsOffStageXLeft()) {

      // if so, then reposition it to just inside the left stage boundary, taking into account its half-width (circular object)
      $this.MoveTo(obj.halfWidth, obj.y);

      // reverse its horizontal direction
      $this.InvertXDirection();

    }

    // check if object is off stage right
    if ($this.IsOffStageXRight()) {

      // if so, then reposition it to just inside the right stage boundary, taking into account its half-width (circular object)
      $this.MoveTo(stageWidth - obj.halfWidth, obj.y);

      // reverse its horizontal direction
      $this.InvertXDirection();

    }

    // check if object is off stage top
    if ($this.IsOffStageYTop()) {

      // if so, then reposition it to just inside the top stage boundary, taking into account its half-height (circular object)
      $this.MoveTo(obj.x, obj.halfHeight);

      // reverse its vertical direction
      $this.InvertYDirection();

    }

    // check if object is off stage bottom
    if ($this.IsOffStageYBottom()) {

      // if so, then reposition it to just inside the bottom stage boundary, taking into account its half-height (circular object)
      $this.MoveTo(obj.x, stageHeight - obj.halfHeight);

      // reverse its vertical direction
      $this.InvertYDirection();

    }

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

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

  };

  // 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
 */
var GameLoop = function() {

  // Loop this function at a default rate of 60 frames per second
  requestAnimationFrame(GameLoop);

  // go through all existing game objects
  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();
    }
    
  }

  // render the stage a.k.a. (re)construct the stage graphically
  renderer.render(stage);

}
// 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() {

  // build a new star
  objects.star = new Star({
    dimensions: {
      height: 50, // pixels
      width: 50 // pixels
    },
    position: { // starting position
      x: stageHeight / 2,
      y: stageWidth / 2
    },
    speed: 5, // value can also be given as { x: X_SPEED, y: Y_SPEED } to set separate axis speeds
  });

  // build a new star
  objects.star2 = new Star({
    dimensions: {
      height: 20, // pixels
      width: 10 // pixels
    },
    position: { // starting position
      x: stageHeight / 3,
      y: stageWidth / 2
    },
    speed: 15 // value can also be given as { x: X_SPEED, y: Y_SPEED } to set separate axis speeds
  });
  
  // build a new star
  objects.star3 = new Star({
    dimensions: {
      height: 60, // pixels
      width: 60 // pixels
    },
    position: { // starting position
      x: stageHeight / 2,
      y: stageWidth / 2
    },
    speed: 2 // value can also be given as { x: X_SPEED, y: Y_SPEED } to set separate axis speeds
  });

  // start the game loop - 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: "chest", // reference
    url: "https://i.imgur.com/FKjaY7a.png", // remote location
    crossOrigin: true // needs this to be able to load images remotely
  })
  // 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 a group of sprites (BRICKS, POWERUPS, ...) if they've been hit and need to disappear.
 * 
 * Parameters:
 *  groupsOfSpritesToCheck (JSON object):
 *  {
 *    sprites: array of Pixi sprite objects to check
 *    ifEmpty: function to run if all sprites have been removed
 *  }
 * 
 * Parameter example:
 *  groupsOfSpritesToCheck = {
 *    sprites: [], // array of sprites
 *    ifEmpty: function() {} // function/behaviour to execute if all objects have been removed from the sprites array
 *  }
 * 
 * Usage example:
 *    CleanupSprites(
 *      {
 *        sprites: arrayContainingPixiSprites,
 *        ifEmpty: function () {
 *          // code to run if all sprites have been hit and are now gone from the stage
 *        }
 *      }
 *    );
 * 
 * Notes:
 *  Each Sprite from the sprites 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 sprites.
 */
var CleanupSprites = function(groupsOfSpritesToCheck) {

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

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

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

    group = groupsOfSpritesToCheck[groupIndex];
    sprites = group.sprites;

    for (var spriteIndex = sprites.length - 1; spriteIndex >= 0; spriteIndex--) {

      // grab a reference to the object
      sprite = sprites[spriteIndex];

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

        // destroy the object
        sprite.destroy();

        // remove the object from the group
        sprites.splice(spriteIndex, 1);

        // trigger another while loop
        removed = true;

        // stop the current for loop
        break;

      }

    }

    // if there aren't any other objects left and there is some behaviour for this
    if (!sprites.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
 */
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:
 *    part - one of the following: "topLeft", "topRight", "bottomLeft", "bottomRight", "anywhere"
 * 
 * Returns:
 *  {
 *    isDown: boolean
 *    isUp: boolean
 *    position: { // user's touch position within the stage
 *      x: int
 *      y: int
 *    }
 *  }
 */
var Touch = function(part) {
  
  // don't change this unless you know exactly why you are doing it

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

  // if the given part is not defined above, throw an error in the console
  if (!stageParts[part]) {
    throw new Error("Stage part ('" + part + "') not recognised!");
  }

  // object that will be returned from this function, similarly to how the keyboard support works
  var touchObject = {
    part: part,
    isDown: false,
    isUp: false
  };

  // See here for more information on jQuery.on() : http://api.jquery.com/on/
  containerElement.on("touchstart mousedown pointerdown", function(event) {

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

    // if the user touched within the configured part, then set it accordingly
    if (collisionDetection.hitTestPoint(point, stageParts[part])) {

      // mark the current touch object as being pressed / down
      touchObject.isDown = true;
      touchObject.isUp = false;

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

    }

  });

  // See here for more information on jQuery.on() : http://api.jquery.com/on/
  containerElement.on("touchend mouseup pointerup", function(event) {

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

    // if the user touched within the configured part, then set it accordingly
    if (collisionDetection.hitTestPoint(point, stageParts[part])) {

      // mark the current touch object as NOT being pressed / down
      touchObject.isUp = true;
      touchObject.isDown = false;

    }

  });

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

}