<!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/score.js"></script>
  <script type="text/javascript" src="scripts/game/classes/brick.js"></script>
  <script type="text/javascript" src="scripts/game/classes/character.js"></script>
  
  <!-- Class Factories - can create specific game objects - link other factories files here -->
  <script type="text/javascript" src="scripts/game/factories/score.js"></script>
  <script type="text/javascript" src="scripts/game/factories/brick.js"></script>
  <script type="text/javascript" src="scripts/game/factories/character.js"></script>
  
  <!-- Game Levels -->
  <script type="text/javascript" src="scripts/game/levels/start.js"></script>
  <script type="text/javascript" src="scripts/game/levels/end.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>
  
  <!-- Game Interactions - Touch/Keyboard -->
  <script type="text/javascript" src="scripts/game/interactions.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);
}

/**
 * localStorage utility functions
 */ 
var LocalStorage = {
  set: function (key, value) {
    localStorage.setItem(key, JSON.stringify(value));
  },
  get: function (key) {
    return JSON.parse(localStorage.getItem(key));
  },
  remove: function (key) {
    localStorage.removeItem(key);
  },
  clear: function () {
    localStorage.clear();
  }
};


// 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
  TextureCache = PIXI.utils.TextureCache, // loaded textures repository
  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

/**
 * 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"
   * Use this kind of approach for a group of moving objects that all need to be constantly moving
   */

  // run the main character animation - it ensures the sprite's containment within the stage
  objects.mainChar.Animate();

  /**
   * Check all relevant collisions. See "scripts/game/collision.js"
   */

  CheckCollisions();

  /**
   * Specifically check if any of our bricks need to be removed 
   * i.e. have had `hasBeenHit` set to `true` on their `sprite` Sprite object
   * For bricks, this can happen in the `Brick` class, in the `$this.DoCollision` method
   */

  CleanupSprites({
    objects: objects.bricks,
    ifEmpty: function() { // if empty, run the game over script

      // end the game loop
      gameOver = true;

      // save the current score to local storage
      objects.points.Save();

      // hide the current level
      levels.start.stage.visible = false;

      // show the end game scene - see scripts/game/levels/end.js
      GameEndSetup();

    }
  });

  /**
   * 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
   * Only do this if the game is not Over
   */
  if (!gameOver) {
    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();
  
  // reset the gameOver variable
  gameOver = false;
  
  // setup the start of the game - see scripts/game/levels/start.js
  GameStartSetup();
  
  // setup interactions - see scripts/game/interactions.js
  Interactions();

  // start the game loop i.e. the game script - see scripts/game/loop.js
  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
  })
  .add({
    name: "character", // reference
    url: "resources/character-sheet.json", // 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 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 circular area within the stage: { x: number, y: number, radius: number }
 *        => OR a Pixi.js Sprite
 *        => OR an instance of a custom Class (Star, Brick, etc.)
 *    press - a "function(){}" that will be executed when the chosen stage part/area/Sprite has been 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() {};
 *    
 *    // obj is a Pixi Sprite or a custom object containing a Pixi Sprite
 *    // like objects.star1
 *    var touch = new Touch(obj, pressFn);
 * 
 *    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) {

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

  /**
   *  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 custom objects that contains a Pixi sprite?
  else if (itemToCheck.GetSprite && typeof itemToCheck.GetSprite === "function") {
    touchObject.object = itemToCheck.GetSprite();
    touchObject.type = "object";
  }

  // is it a rectangular area?
  else if (itemToCheck.x !== undefined && itemToCheck.y !== undefined && itemToCheck.height && itemToCheck.width) {
    touchObject.object = itemToCheck;
    touchObject.type = "box";
  }
  
  // is it a rectangular area?
  else if (itemToCheck.x !== undefined && itemToCheck.y !== undefined && itemToCheck.radius) {
    itemToCheck.circular = true;
    itemToCheck.diameter = itemToCheck.radius;
    touchObject.object = itemToCheck;
    touchObject.type = "circle";
  }

  // 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") {
        
        // call the `press` function
        // make sure that `this` called inside the `press` function will refer to the object that was just touched
        touchObject.press.call(itemToCheck, point);
        
      }

    } else {

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

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

    }

  }

  // See here for more information on jQuery.on() : http://api.jquery.com/on/
  containerElement.on("touchstart pointerdown touchend 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 () {
  
  // go through all bricks
  for (var key in objects.bricks) {
    
    // get a handle on each brick
    var brick = objects.bricks[key];
    
    // if for some reason we've already hit this brick, skip it
    // it can happen if lots of collisions happen at once
    if (brick.GetSprite().hasBeenHit) { 
      continue;
    }
    
    // check collision with the main character
    objects.mainChar.CheckCollision(brick);
    
  }
  
};
/**
 * 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["chest"].texture);
  bg.height = stageHeight;
  bg.width = stageWidth;
  
  // 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 = {};

// will contain the game levels, each containing a Pixi stage i.e. Container()
var levels = {
  start: {},
  end: {}
};

// game status
var gameOver;
/**
 * Score Factory
 */
var CreateScore = function() {

  var
    id = "points",
    options = {
      id: id,
      text: "Points: ",
      position: {
        x: 10,
        y: 30 // will appear just beneath the Hits text
      }
    }

  // create a new Score instance and return it should it be needed
  return (objects[id] = new Score(options));

}
/**
 * Score class. Keeps track of game score.
 * (optional) "options" JSON object to be given when creating a new instance
 * 
 * Parameter example:
 *  {
 *    id: "score",
 *    text: "Score: ", // text to precede the score value
 *    initialScore: 10, // optional
 *    position: { // optional
 *      x: 20,
 *      y: 20
 *    },
 *    font: { // optional; if given, all keys seen here must have values set
 *      fontFamily: "Arial",
 *      fontSize: 14,
 *      fill: "white"
 *    }
 *  }
 */
var Score = function(options) {

  /**
   * Current instance
   */ 
  var $this = this;
  
  /**
   * Pixi.JS text object
   */ 
  var sprite;
  
  /**
   * Score tracker
   */ 
  var score = 0;
  
  /**
   * 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: "score",
    text: "Score: ",
    initialScore: 0,
    position: { // starting position
      x: 20,
      y: 20
    },
    font: {
      fontFamily: "Arial",
      fontSize: 14,
      fill: "white"
    }
  };

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

    // set the initial score value, if it's a valid number
    if (config.initialScore && IsNumeric(config.initialScore)) {
      score = config.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(
      config.text + score, {
        fontFamily: "Arial",
        fontSize: 14,
        fill: "white"
      }
    );

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

    // add it to the stage
    levels.start.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 += parseInt(value);
    
    // update the sprite text
    sprite.text = "Hits: " + score;
    
  }
  
  /**
   * Returns numeric value of current score
   */ 
  $this.Get = function () {
    return score;
  };
  
  /**
   * Saves the current score to LocalStorage - i.e. persistent storage
   */ 
  $this.Save = function () {
    
    // extract the scores array from storage
    var scores = LocalStorage.get("scores");
    
    // if we've never had scores saved
    if (!scores) {
      
      // initialise the array
      scores = [];
      
    }
    
    // add the latest score
    scores.push(score);
    
    // sort them highest to lowest
    // see https://www.w3schools.com/js/js_array_sort.asp
    scores.sort(function (a, b) {
      return b - a;
    });
    
    // remove the oldest score if we have 11 or more, so we keep 10
    if (scores.length > 10) {
      
      // oldest score will be the last one after the above sort
      // Array.pop() will remove the last element
      scores.pop();
      
    }
    
    // save the scores array
    LocalStorage.set("scores", scores);
    
  };

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

};
/**
 * Brick class. Keeps track of a game brick.
 * (optional) "options" JSON object to be given when creating a new instance
 * 
 * Parameter example:
 *  {
 *    id: "brick",
 *    dimensions: { // optional
 *      height: 20,
 *      width: 20
 *    }
 *    position: { // optional
 *      x: 20,
 *      y: 20
 *    }
 *  }
 */
var Brick = function(options) {

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

  /**
   * Pixi.JS text object
   */
  var sprite;

  /**
   * 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: "brick",
    dimensions: {
      height: 20,
      width: 20
    },
    position: { // starting position
      x: 20,
      y: 20
    },
    scorePerHit: 10
  };

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

    // set the initial score value, if it's a valid number
    if (config.dimensions && IsNumeric(config.dimensions)) {
      config.dimensions = {
        height: config.dimensions,
        width: config.dimensions
      };
    }

    // 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 Sprite(resources["chest"].texture);

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

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

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

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

  /**
   * Method called in scripts/game/collision.js
   * Accepts an `object` custom class instance (Star, Character, etc.) parameter used to determine collision
   */
  $this.CheckCollision = function(object) {

    // perform collision detection between the object and the current Brick Sprite
    var collisionResult = collisionDetection.hit(object.GetSprite(), sprite, true, true);

    // collided!
    if (collisionResult) {

      // run the collision action
      $this.DoCollision();

    }

  }

  /**
   * Collision Action.
   * Called by the CheckCollision() method above
   */
  $this.DoCollision = function() {
    
    // mark it for removal
    sprite.hasBeenHit = true;
    
    // update score
    objects.points.Update(config.scorePerHit);

  };

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

};
/**
 * Bricks factory - creates many bricks
 */ 
var CreateBricks = function (bricksRows, bricksPerRow) {
  
  // will keep track of all bricks
  objects.bricks = [];
  
  // handle rows
  for (var row = 1; row <= bricksRows; row++) {
    
    // handle columns
    for (var column = 1; column <= bricksPerRow; column++) {
      
      // create a brick taking into account how many need to fit on each row
      CreateBrick(row, column, bricksPerRow);
      
    } // end columns

  } // end rows
  
};

/**
 * Brick Factory - creates one brick part of a row/column setup
 */
var CreateBrick = function(row, column, bricksPerRow) {

  // determine options for current brick
  var options = {
    position: {
      // Math Vuh-Doo
      x: column * stageWidth / (bricksPerRow + 2),
      y: row * stageHeight / 10 // considering stage has 10 parts
    },
    // Math Vuh-Doo
    dimensions: {
      width: stageWidth / (bricksPerRow + 2), // leave two parts, left and right, as gaps between the bricks and stage walls
      height: stageHeight / 10 // considering stage has 10 parts
    },
    scorePerHit: 30
  };
  
  var brick = new Brick(options);

  // create a new Brick instance
  objects.bricks.push(brick);
  
  // also return the instance if needed
  return brick;

}
/**
 * Here will take place the setup of various keyboard/touch interactions
 */
var Interactions = function () {
  
  
  
};
{"frames": {

"Layer_1":
{
	"frame": {"x":0,"y":0,"w":290,"h":914},
	"rotated": false,
	"trimmed": false,
	"spriteSourceSize": {"x":0,"y":0,"w":290,"h":914},
	"sourceSize": {"w":290,"h":914}
},
"Layer_2":
{
	"frame": {"x":290,"y":0,"w":332,"h":909},
	"rotated": false,
	"trimmed": false,
	"spriteSourceSize": {"x":0,"y":0,"w":332,"h":909},
	"sourceSize": {"w":332,"h":909}
},
"Layer_3":
{
	"frame": {"x":622,"y":0,"w":530,"h":906},
	"rotated": false,
	"trimmed": false,
	"spriteSourceSize": {"x":0,"y":0,"w":530,"h":906},
	"sourceSize": {"w":530,"h":906}
},
"Layer_4":
{
	"frame": {"x":1152,"y":0,"w":690,"h":918},
	"rotated": false,
	"trimmed": false,
	"spriteSourceSize": {"x":0,"y":0,"w":690,"h":918},
	"sourceSize": {"w":690,"h":918}
},
"Layer_5":
{
	"frame": {"x":0,"y":918,"w":512,"h":917},
	"rotated": false,
	"trimmed": false,
	"spriteSourceSize": {"x":0,"y":0,"w":512,"h":917},
	"sourceSize": {"w":512,"h":917}
},
"Layer_6":
{
	"frame": {"x":512,"y":918,"w":336,"h":909},
	"rotated": false,
	"trimmed": false,
	"spriteSourceSize": {"x":0,"y":0,"w":336,"h":909},
	"sourceSize": {"w":336,"h":909}
},
"Layer_7":
{
	"frame": {"x":848,"y":918,"w":529,"h":907},
	"rotated": false,
	"trimmed": false,
	"spriteSourceSize": {"x":0,"y":0,"w":529,"h":907},
	"sourceSize": {"w":529,"h":907}
},
"Layer_8":
{
	"frame": {"x":0,"y":1835,"w":688,"h":920},
	"rotated": false,
	"trimmed": false,
	"spriteSourceSize": {"x":0,"y":0,"w":688,"h":920},
	"sourceSize": {"w":688,"h":920}
},
"Layer_9":
{
	"frame": {"x":688,"y":1835,"w":525,"h":919},
	"rotated": false,
	"trimmed": false,
	"spriteSourceSize": {"x":0,"y":0,"w":525,"h":919},
	"sourceSize": {"w":525,"h":919}
},
"Layer_10":
{
	"frame": {"x":1213,"y":1835,"w":289,"h":913},
	"rotated": false,
	"trimmed": false,
	"spriteSourceSize": {"x":0,"y":0,"w":289,"h":913},
	"sourceSize": {"w":289,"h":913}
}},
"meta": {
	"app": "https://www.codeandweb.com/texturepacker",
	"version": "1.0",
	"image": "https://i.imgur.com/C5Fo19r.png",
	"format": "RGBA8888",
	"size": {"w":1842,"h":2755},
	"scale": "1",
	"smartupdate": "$TexturePacker:SmartUpdate:9bb2301b68abe277fcbf712ae31f2074:1d888e7d86ab5203e3a92f7d62bac124:c11f47e90ac3a31cb6fed31d32f496f1$"
}
}
// 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

/**
 * Character Class - "options" JSON 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
 *  // ".character" is the identifier that needs to be used below with the "id:" attribute
 *  objects.character = new Star(
 *    {
 *      id: "character", // 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, // a number to be used for both directions' velocities
 *      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 Character = function(options) {

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

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

  /**
   * Texture frame ID number from the atlas - will be changed each animation cycle (or less frequent potentially so the animation won't be too fast)
   */
  var currentTextureNumber = 1;

  /**
   * The texture frames ID prefix
   */
  var texturePrefix = "Layer_";

  /**
   * Frame change frequency i.e. once every how many game loops should this sprite's texture change
   */
  var frequency = 15; // once every 6 frames, so 10 times per second
  var frequencyCounter = 1;

  /**
   * Movement animation final position
   */
  var finalPosition;

  /**
   * 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: "character",
    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
      };
    }

    // ensure the starting position is the final movement position for now
    finalPosition = config.position;

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

    // prepare touch response
    PrepareTouchResponse();

  };

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

    // create the object using one of the "character"'s preloaded texture frames as its Sprite
    sprite = new Sprite(resources["character"].textures[texturePrefix + currentTextureNumber]);

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

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

  };

  /**
   * Touch response - ensure the character moves across the stage
   */ 
  var PrepareTouchResponse = function() {

    var touchStageBottomRight = new Touch(

      // if player touches the stage anywhere at all
      "anywhere",

      // execute the following when they do
      // `position` is a JSON object that looks like: { x: number, y: number }
      // indicates the position within the stage where the user just tapped
      function(position) {

        // move the character to that position
        finalPosition = position;

      }
    );

  };

  /**
   * Frame changing logic - private
   */
  var ChangeToNextFrame = function() {
    
    // increment the game loop frequency counter
    frequencyCounter++;
    
    // if it's not yet time
    if (frequencyCounter < frequency) {
      
      // don't do anything
      return;
      
    } else {
      
      // otherwise, reset the counter
      frequencyCounter = 1;
      
    }
    
    // increase the texture frame number
    currentTextureNumber++;
    
    // check if it needs to be reset i.e. the frame number is higher than the number of available frames
    if (currentTextureNumber >= Object.keys(resources["character"].textures).length) {
      
      // reset it to 1
      currentTextureNumber = 1;
      
    }
    
    // reset the sprite's texture with the newly calculated frame ID
    sprite.texture = resources["character"].textures[texturePrefix + currentTextureNumber];
    
  }

  /**
   * Movement logic
   */
  $this.Animate = function() {

    // if we're there or thereabout (speed dependent)
    // i.e. if we're within vx and vy pixels of the final position
    if (Math.abs(sprite.x - finalPosition.x) < sprite.vx && Math.abs(sprite.y - finalPosition.y) < sprite.vy) {

      // don't do anything
      return;

    }

    // if there's still room to move according to the character's movement speed
    if (Math.abs(sprite.x - finalPosition.x) > sprite.vx) {

      // if the character's X position is greater than the player's touch's X position
      if (sprite.x > finalPosition.x) {

        // then decrease the character's X position to bring it closer to the player's touch's X position
        sprite.x -= sprite.vx;

        // flip the sprite so it looks like it's moving left
        // the textures we're using for this Character are facing right
        if (sprite.scale.x > 0) {
          
          sprite.scale.x *= -1;
          
        }

        // otherwise if the character's X position is less than the player's touch's X position
      } else if (sprite.x < finalPosition.x) {

        // then increase the character's X position to bring it closer to the player's touch's X position
        sprite.x += sprite.vx;

        // flip the sprite so it looks like it's moving right
        // the textures we're using for this Character are facing right
        if (sprite.scale.x < 0) {
          
          sprite.scale.x *= -1;
          
        }

      }

    }

    // if there's still room to move according to the character's movement speed
    if (Math.abs(sprite.y - finalPosition.y) > sprite.vy) {

      // if the character's Y position is greater than the player's touch's Y position
      if (sprite.y > finalPosition.y) {

        // then decrease the character's Y position to bring it closer to the player's touch's Y position
        sprite.y -= sprite.vy;

        // otherwise if the character's Y position is less than the player's touch's Y position
      } else if (sprite.y < finalPosition.y) {

        // then increase the character's Y position to bring it closer to the player's touch's Y position
        sprite.y += sprite.vy;

      }

    }

    // if the code reached this place, it means we've been moving, so ensure the sprite's frame is changing
    ChangeToNextFrame();

    // use Bump to contain this sprite within the stage
    collisionDetection.contain(sprite, stageBox);

  };

  /** 
   * 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.hit(sprite, object.GetSprite());

    // Collided!
    if (collisionResult) {

      // run the collision logic on this object
      $this.DoCollision();

      // run the brick collision logic
      object.DoCollision();

    }

  };

  /**
   * Something to do if collided - called in the `CheckCollision` method above
   */
  $this.DoCollision = function() {

    // nothing as of yet, it all happens on the brick side

  };

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

};
/**
 * Character Factory
 * Accepts optional `options` JSON object that will be passed to the Character class
 * See Character class for more info on the `options` JSON object
 */ 
var CreateCharacter = function (id, options) {
  
  var baseConfig = {
    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: stageWidth / 2,
      // get a random number between a quarter and three quarters of the stage's width
      y: stageHeight - 60
    },
    speed: 3, // value can also be given as { x: X_SPEED, y: Y_SPEED } to set separate axis speeds
  };
  
  var config = Object.assign({}, baseConfig, options, { id: id });
  
  // create a new Character instance and return it
  return (objects[id] = new Character(config));
  
}
var GameStartSetup = function () {
  
  // add the first game level to the stage
  levels.start.stage = new Container();
  stage.addChild(levels.start.stage);
  
  // build a new character
  CreateCharacter("mainChar");

  // build the points text
  CreateScore();
  
  // create two 2 of bricks, 5 per row
  CreateBricks(2, 5);
  
}
/**
 * Prepares the end game scene
 */ 
var GameEndSetup = function() {

  // create a new container for the end stage
  levels.end.stage = new Container();

  // create the final score text
  var finalScore = new Text(
    "Final score: " + objects.points.Get(), {
      fontFamily: "Arial",
      fontSize: 14,
      fill: "white"
    }
  );

  // put it in the top-middle (ish)
  finalScore.position.set(stageWidth / 2 - finalScore.width / 2, stageHeight / 13);

  // add it to the stage
  levels.end.stage.addChild(finalScore);

  // prepare leaderboard
  var leaderboardText = new Text(
    "Top 10", {
      fontFamily: "Arial",
      fontSize: 14,
      fill: "white"
    }
  );

  // position it below the final score
  leaderboardText.position.set(stageWidth / 2 - leaderboardText.width / 2, stageHeight / 13 * 2);
  
  // add it to the stage
  levels.end.stage.addChild(leaderboardText);

  // now let's prepare the score list
  var scores = LocalStorage.get("scores");

  // iterate over all scores
  for (var i = 0; i < scores.length; i++) {

    // extract each score value
    var score = scores[i];

    // prepare a new pixi object
    // prepare leaderboard
    var scoreText = new Text(
      score, {
        fontFamily: "Arial",
        fontSize: 14,
        fill: "white"
      }
    );

    // position it below the previous bit of text
    scoreText.position.set(stageWidth / 2 - scoreText.width / 2, stageHeight / 13 * (i + 3));
    
    // add it to the stage
    levels.end.stage.addChild(scoreText);

  }

  // add the whole thing to main stage
  stage.addChild(levels.end.stage);

}