A Pen by Markku Lehmonen

Tutorials of (A pen by markku lehmonen) by Markku lehmonen

<!DOCTYPE html>
<html >
<head>
  <meta charset="UTF-8">
  <title>A Pen by  Markku Lehmonen</title>
  
  
  
      <link rel="stylesheet" href="css/style.css">

  
</head>

<body>
  

  <div id="appContainer">
  <div id="fpsDisplay" style="position: absolute; top: -25px; left: 10px;">FPS Display</div>
  <!-- <button id="startButton" style="position: absolute; top: 225px; left: 10px;">Start</button>
  <button id="stopButton" style="position: absolute; top: 225px; left: 75px;">Stop</button> -->
  </div>
  <script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js'></script>

    <script  src="js/index.js"></script>

</body>
</html>
/* Downloaded from https://www.codeseek.co/ */
body {
  background-color: #888888;
  min-height: 100%;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: Calibri;
}

#appContainer {
  background-color: #000000;
  height: 200px;
  width: 400px;
  position:relative;
}

.appView {
  /*background-color: #4fc3f7;*/
  background-color: rgba(79,195,247,1.0);
  color: #ffffff;
  font-family: Calibri;
  position: absolute;
  width:100%;
  height:100%;
}

.sprite {
  background-color: #ffffff;
  position: absolute;
  width: 25px;
  height: 25px;
  top: 75px;
  left: 100px;
}

.label {
  position: absolute;
  color: #ffffff;
}

.menuContainer {
  border: 0px solid #ffffff;
  border-radius: 15px;
  position: absolute;
  height: 150px;
  width: 101px;
  left: 149px;
  top: 25px;
}

.menuItem {
  border: 2px solid #ffffff;
  border-radius: 10px;
  position: absolute;
  color: #ffffff;
  text-align: center;
}


/* DEBUG STUFF */

#anchorPoint {
  background-color:#666666;
  height:0px; width:0px;
  position:absolute; top:75px; left:75px;
  outline: 2px solid red;
  outline-offset: 1px;
}

.rectangle {
  /*background-color:#666666;*/
  height:30px; width:90px;
  position:absolute; top:100px; left:175px;
  outline: 1px solid red;
  outline-offset: 0px;
}
/* Downloaded from https://www.codeseek.co/ */
// == DOM OBJECTS == ////////////////////////////////////////////////////////////////////

class DOMObject {
  constructor() {
    this.domElement = "<div></div>";
    
  }
  setDOMElement(className, str) {
     this.domElement = $("<div class='" + className + "'>" + str + "</div>");
  }
  setSize(height, width) {
    this.domElement.css("height",height);
    this.domElement.css("width",width);
  }
  setPosition(left, top) {
    this.domElement.css("top",top);
    this.domElement.css("left",left);
  }
  show() {
    this.domElement.css("visibility", "visible");
  }
  hide() {
    this.domElement.css("visibility", "hidden");
  }
  addChild(domObject) {
    // TODO: Lisää kokonainen objekti, älä vain dom-elementtiä
    $(this.domElement).append(domObject.domElement);
  }
}
  class AppView extends DOMObject {
    constructor() {
      super();
      this.setDOMElement("appView", "");
      $("#appContainer").append(this.domElement);
    }
    setBackgroundColor(color) {
      this.domElement.css("background-color", color);
    }
  }
  class Sprite extends DOMObject {
    constructor(parent) {
      super();
      this.setDOMElement("sprite", "");
      parent.addChild(this);
    }
  }
  class Label extends DOMObject {
    constructor(str, parent) {
      super();
      this.setDOMElement("label", str);
      parent.addChild(this);
    }
  }
  class MenuContainer extends DOMObject {
    constructor(parent) {
      super();
      this.setDOMElement("menuContainer", "");
      this.selectedMenuItem = 0;
      this.menuItems = [];
      parent.addChild(this);
    }
    addMenuItem(top, title) {
      var menuItem = new MenuItem(top, title);
      this.menuItems.push(menuItem);
      this.addChild(menuItem);
      this.selectMenuItem();
    }
    scrollSelectedUp() {
      if(this.selectedMenuItem > 0) { this.selectedMenuItem--; }
      this.selectMenuItem();
    }
    scrollSelectedDown() {
      if(this.selectedMenuItem < this.menuItems.length - 1) { this.selectedMenuItem++; }
      this.selectMenuItem();
    }
    selectMenuItem() {
      for(var i = 0; i < this.menuItems.length; i++) {
        this.menuItems[i].unselect();
      }
      this.menuItems[this.selectedMenuItem].select();
    }
    enterKeyDown() {
      this.menuItems[this.selectedMenuItem].enterSelected();
    }
    enterKeyUp() {
      this.menuItems[this.selectedMenuItem].select(); // TODO: HAJOAA JOS KÄYTTÄJÄ RÄPLÄÄ NUOLIA????
      this.trigger();
    }
    trigger() {
      if(this.menuItems[this.selectedMenuItem].action !== null) {
        var temp = this.selectedMenuItem;
        this.selectedMenuItem = 0;
        this.selectMenuItem();
        this.menuItems[temp].triggerAction();
      }
    }
  }
  class MenuItem extends DOMObject {
    constructor(top, title) {
      super();
      this.setDOMElement("menuItem", title);
      this.setSize("20px", "75px");
      this.setPosition(11,top);
      this.action = null;
    }
    select() {
      this.domElement.css("color", "#01579b");
      this.domElement.css("border-color", "#01579b");
    }
    unselect() {
      this.domElement.css("color", "#ffffff");
      this.domElement.css("border-color", "#ffffff");
    }
    enterSelected() {
      this.domElement.css("color", "#039be5");
      this.domElement.css("border-color", "#039be5");
    }
    bindAction(action) {
      this.action = action;
    }
    triggerAction() {
      this.action();
    }
  }

// == INPUT SYSTEM == ////////////////////////////////////////////////////////////////////

class Key {
  constructor(name) {
    this.name = name;
    this.isPressed = false;
    this.actionDown; //testDOWN;
    this.actionUp; //testUP;
  }
  down() {
    if(!this.isPressed) { 
      this.isPressed = true;
      this.actionDown();
    }
  }
  up() {
    this.isPressed = false;
    this.actionUp();
  }
  setAction(keyPhase, action) {
    if(keyPhase == KeyPhase.DOWN) {
      this.actionDown = action;
    }
    else if(keyPhase == KeyPhase.UP) {
      this.actionUp = action;
    }
  }
  nullActions() {
    this.actionDown = null;
    this.actionUp = null;
  }
  // KEY ON PRESSED
  /*keyListenerUpdate() {
  if (Key.isPressedKey(Key.UP)) { console.log("keyPressed: Up"); }
  if (Key.isPressedKey(Key.LEFT)) { console.log("keyyPressed: Left"); }
  if (Key.isPressedKey(Key.DOWN)) { console.log("keyyPressed: Down"); }
  if (Key.isPressedKey(Key.RIGHT)) { console.log("keyyPressed: Right"); }
  }*/
}
var KeyCodes = {
  ENTER:13,
  ESCAPE:27,
  ARROW_LEFT:37,
  ARROW_UP:38,
  ARROW_RIGHT:39,
  ARROW_DOWN:40
};
var KeyPhase = {
  UP : 0,
  DOWN : 1
};
class KeyBinding {
  constructor(keyPhase, keyCode, action) {
    this.keyPhase = keyPhase;
    this.keyCode = keyCode;
    this.action = action;
  }
}
class KeyListener {
  constructor() {
    this.keys = {}; // keyCode : KeyObject
   
    // Set Key Event Listeners
    window.addEventListener('keydown', this.onKeyDown.bind(this), false); // TODO: LINKITÄ KEY-OBJEKTIIN?!?!?
    window.addEventListener('keyup', this.onKeyUp.bind(this), false);
    
    // Add Key Objects
    this.addKey(KeyCodes.ENTER);
    this.addKey(KeyCodes.ESCAPE);
    this.addKey(KeyCodes.ARROW_LEFT);
    this.addKey(KeyCodes.ARROW_RIGHT);
    this.addKey(KeyCodes.ARROW_UP);
    this.addKey(KeyCodes.ARROW_DOWN);
  }
  addKey(name) {
    var key = new Key(name);
    this.keys[name] = key;
  }
  addKeyAction(keyPhase, key, action) {
    this.keys[key].setAction(keyPhase, action);
  }
  nullKeyActions() {
    for(var keyId in this.keys) {
      this.keys[keyId].nullActions();
    }
  }
  bindKeys(keyBindings) {
    this.nullKeyActions();
    for(var i = 0; i < keyBindings.length; i++) {
      //this.addKeyAction(keyBindings.keyPhase, keyBindings.keyCode, keyBindings.action);
      this.keys[keyBindings[i].keyCode].setAction(keyBindings[i].keyPhase, keyBindings[i].action);
    }
  }
  onKeyDown(event) {
    event.preventDefault();
    this.keys[event.keyCode].down();
  }
  onKeyUp(event) {
    event.preventDefault();
    this.keys[event.keyCode].up();
  }
}

// == CORE ENGINE == ////////////////////////////////////////////////////////////////////////
  
var lastFrameTimeMs = 0;
var maxFPS = 61;
var delta = 0;
var timestep = 1000 / 61;
var fps = 61;
var framesThisSecond = 0;
var lastFpsUpdate = 0;
var running = false;
var started = false;
var frameID = 0;
var gameObjects = [];

function update() {
  //console.log("update()");
  var numUpdateSteps = 0;
  while (delta >= timestep) {
    for(var i = 0; i < gameObjects.length; i++) {
      gameObjects[i].update(timestep);
    }
    delta -= timestep;
    if (++numUpdateSteps >= 240) {
      panic();
      break;
    }
  }
}
function draw(interp) {
  var i = gameObjects.length;
  while(i--) {
    gameObjects[i].draw(interp);
  }
  fpsDisplay.draw(fps);  
}
function panic() {
    delta = 0;
}
function stop() {
    running = false;
    started = false;
    cancelAnimationFrame(frameID);
    //console.log("Stop");
}
function start() {
    //console.log("Start");
    if (!started) {
        started = true;
        frameID = requestAnimationFrame(function(timestamp) {
            draw(1);
            running = true;
            lastFrameTimeMs = timestamp;
            lastFpsUpdate = timestamp;
            framesThisSecond = 0;
            frameID = requestAnimationFrame(mainLoop);
        });
    }
}
function updateFPSCounter(timestamp) {
  if (timestamp > lastFpsUpdate + 1000) {
    fps = 0.25 * framesThisSecond + 0.75 * fps;
    lastFpsUpdate = timestamp;
    framesThisSecond = 0;
  }
  framesThisSecond++;
}
function mainLoop(timestamp) {
    
  // Throttle the frame rate.    
  if (timestamp < lastFrameTimeMs + (1000 / maxFPS)) {
    frameID = requestAnimationFrame(mainLoop);
    return;
  }
  delta += timestamp - lastFrameTimeMs;
  lastFrameTimeMs = timestamp;
  ///////////////////////////////////////////////////
  
  //preUpdate(timestamp, delta);
  
  updateFPSCounter(timestamp);
  update();
  draw(delta / timestep);

  //postUpdate();

  // Loop this main loop function
  frameID = requestAnimationFrame(mainLoop);
  ///////////////////////////////////////////////////
  
    // TODO : ???
  gameLogic.update();
}

// == UI STUFF == ////////////////////////////////////////////////////////////////////

  //$("#startButton").click(start);   // == USER INTERFACE == //
  //$("#stopButton").click(stop);

  var fpsDisplay = {
    label: document.getElementById('fpsDisplay'),
    draw: function(fps) {
      this.label.textContent = Math.round(fps) + ' FPS';
    }
  }  // == DATA CONSOLE == //

// == APP STATES == ////////////////////////////////////////////////////////////////////

class AppState {
  constructor() {
    this.appView = new AppView();
    this.keyBindings = [];
  }
  addChild(domObject) {
    //TODO: lisää vain dom-elementin!!!
    this.appView.addChild(domObject);
  }
  activate() {
    this.appView.show();
  }
  deactivate() {
    this.appView.hide();
  }
}

  // AppState Classes
  class StartMenu extends AppState {
    constructor() {
      super();
      this.label = new Label("Start Menu",this);
      this.menu = new MenuContainer(this);
        this.menu.addMenuItem(30, "START");
        this.menu.addMenuItem(60, "OPTIONS");
        this.menu.addMenuItem(90, "EXIT");
      
      // initKeyBindings();     // TODO: tee erillinen funktio
        this.keyBindings[0] = new KeyBinding(KeyPhase.DOWN, KeyCodes.ARROW_DOWN, MenuContainer.prototype.scrollSelectedDown.bind(this.menu) );
        this.keyBindings[1] = new KeyBinding(KeyPhase.DOWN, KeyCodes.ARROW_UP, MenuContainer.prototype.scrollSelectedUp.bind(this.menu) );
        this.keyBindings[2] = new KeyBinding(KeyPhase.DOWN, KeyCodes.ENTER, MenuContainer.prototype.enterKeyDown.bind(this.menu) );
        this.keyBindings[3] = new KeyBinding(KeyPhase.UP, KeyCodes.ENTER, function () { GameApp.appStates.StartMenu.menu.enterKeyUp(); });
      
      // TODO: dirtyyyyy!!!!
      this.menu.menuItems[0].bindAction(function() {  
        GameApp.switchAppState("Gameplay");
        GameApp.stackAppState("PreGameplay");
        //gameLogic.reset();
      });
    }    
  }
  class Gameplay extends AppState {
    constructor() {
      super();
      this.label = new Label("Gameplay", this);
      
      // initKeyBindings();     // TODO: tee erillinen funktio ja korvaa startMenuState. -> this. ( bind() ??? );
      this.keyBindings[0] = new KeyBinding(KeyPhase.UP, KeyCodes.ESCAPE, function () { GameApp.stackAppState("PauseMenu"); });
      this.keyBindings[1] = new KeyBinding(KeyPhase.DOWN, KeyCodes.ARROW_LEFT, function () { gameLogic.paddle.left(); });
      this.keyBindings[2] = new KeyBinding(KeyPhase.DOWN, KeyCodes.ARROW_RIGHT, function () { gameLogic.paddle.right(); });
      this.keyBindings[3] = new KeyBinding(KeyPhase.UP, KeyCodes.ARROW_LEFT, function () { gameLogic.paddle.reset(); });
      this.keyBindings[4] = new KeyBinding(KeyPhase.UP, KeyCodes.ARROW_RIGHT, function () { gameLogic.paddle.reset(); });
    }
    freeze() {
      // TODO:
    }
  }
  class PauseMenu extends AppState {
    constructor() {
      super();
      this.label = new Label("Pause Menu", this);
      this.appView.setBackgroundColor("rgba(0,0,0,0.7)");
      this.pauseMenu = new MenuContainer(this);
        this.pauseMenu.addMenuItem(50, "RESUME");
        this.pauseMenu.addMenuItem(80, "EXIT");
      
      // initKeyBindings();     // TODO: tee erillinen funktio ja korvaa startMenuState. -> this. ( bind() ??? );
        this.keyBindings[0] = new KeyBinding(KeyPhase.DOWN, KeyCodes.ARROW_DOWN, MenuContainer.prototype.scrollSelectedDown.bind(this.pauseMenu) );
        this.keyBindings[1] = new KeyBinding(KeyPhase.DOWN, KeyCodes.ARROW_UP, MenuContainer.prototype.scrollSelectedUp.bind(this.pauseMenu) );
        this.keyBindings[2] = new KeyBinding(KeyPhase.DOWN, KeyCodes.ENTER, MenuContainer.prototype.enterKeyDown.bind(this.pauseMenu) );
        this.keyBindings[3] = new KeyBinding(KeyPhase.UP, KeyCodes.ENTER, MenuContainer.prototype.enterKeyUp.bind(this.pauseMenu) );
        this.keyBindings[4] = new KeyBinding(KeyPhase.UP, KeyCodes.ESCAPE, function () { GameApp.unstackAppState("PauseMenu"); } );
      
      // TODO: dirty!!!
      this.pauseMenu.menuItems[0].bindAction(function() { GameApp.unstackAppState("PauseMenu"); });
      this.pauseMenu.menuItems[1].bindAction(function() { GameApp.switchAppState("StartMenu"); });
    }
  }
  class PreGameplay extends AppState {
    constructor() {
      super();
      // TODO:
      this.label = new Label("READY?", this);
      this.label.setPosition(120,64);
      this.label.domElement.css("font-size",50);
      this.label.domElement.css("font-weight","bold");
      /////////////////////
      this.appView.setBackgroundColor("rgba(0,0,0,0.5)");
    }
    activate() {
      super.activate();
      window.setTimeout(PreGameplay.prototype.unstack.bind(this),500);
    }
    unstack() {
      GameApp.unstackAppState("PreGameplay");
    }
  }
  class PostGameplay extends AppState {
    constructor() {
      super();
      // TODO:
      this.label = new Label("GAME OVER", this);
      this.label.setPosition(75,34);
      this.label.domElement.css("font-size",50);
      this.label.domElement.css("font-weight","bold");
      /////////////////////
      this.appView.setBackgroundColor("rgba(0,0,0,0.5)");
      /////////////////////
      this.pauseMenu = new MenuContainer(this);
        this.pauseMenu.addMenuItem(70, "AGAIN");
        this.pauseMenu.addMenuItem(100, "EXIT");
      
      // initKeyBindings();     // TODO: tee erillinen funktio ja korvaa startMenuState. -> this. ( bind() ??? );
        this.keyBindings[0] = new KeyBinding(KeyPhase.DOWN, KeyCodes.ARROW_DOWN, MenuContainer.prototype.scrollSelectedDown.bind(this.pauseMenu) );
        this.keyBindings[1] = new KeyBinding(KeyPhase.DOWN, KeyCodes.ARROW_UP, MenuContainer.prototype.scrollSelectedUp.bind(this.pauseMenu) );
        this.keyBindings[2] = new KeyBinding(KeyPhase.DOWN, KeyCodes.ENTER, MenuContainer.prototype.enterKeyDown.bind(this.pauseMenu) );
        this.keyBindings[3] = new KeyBinding(KeyPhase.UP, KeyCodes.ENTER, MenuContainer.prototype.enterKeyUp.bind(this.pauseMenu) );
        //this.keyBindings[4] = new KeyBinding(KeyPhase.UP, KeyCodes.ESCAPE, function () { GameApp.unstackAppState("PauseMenu","Gameplay"); } );
      
      // TODO: dirty!!!
      this.pauseMenu.menuItems[0].bindAction(function() {
        GameApp.switchAppState("Gameplay");
        GameApp.stackAppState("PreGameplay");
        /*gameLogic.reset();/*                           
        for(var i = 0; i < colliders.length; i++) {
          colliders[i].collider.isCollidible = true;
          colliders[i].sprite.domElement.css("visibility","visible");
        }
        stop();*/
      });
      
      this.pauseMenu.menuItems[1].bindAction(function() { GameApp.switchAppState("StartMenu"); });
    }
    unstack() {
      /*super.deactivate();
      start() // TODO
      // TODO:
      GameApp.unstackAppState("PauseMenu");*/
    }
  }

// == GAME APP SYSTEM == ////////////////////////////////////////////////////////////////////
  
var GameApp = {
  appStates: null,
  keyListener: null,
  currentAppState: null, // TODO
  stackedAppState: null, // TODO
  //overridenAppState: null, // TODO
  
  stackAppState: function(appStateName) { 
    this.currentAppState.freeze();
    this.stackedAppState = this.appStates[appStateName];
    this.stackedAppState.activate();
    this.keyListener.bindKeys(this.stackedAppState.keyBindings);
    
    stop(); // TODO
  },
  
  unstackAppState: function(appStateName) { 
    this.appStates[appStateName].deactivate();
    this.stackedAppState = null;
    this.currentAppState.activate();
    this.keyListener.bindKeys(this.currentAppState.keyBindings);
    
    start(); // TODO
  },
  
  switchAppState: function(appStateName) { 
    GameApp.hideAppStates();
    this.currentAppState = this.appStates[appStateName];
    this.currentAppState.activate();
    this.keyListener.bindKeys(this.currentAppState.keyBindings);
    
    stop(); // TODO
    if(appStateName == "Gameplay") { gameLogic.reset(); }
  },
  
  hideAppStates: function() {
    this.stackedAppState = null;
    this.currentAppState = null;
    for(var appStateName in this.appStates) {
      this.appStates[appStateName].deactivate();
    }  
  },
  
  setDefault: function() {
    this.keyListener = new KeyListener();
    this.appStates = {
      StartMenu : new StartMenu(),
      Gameplay : new Gameplay(),
      PauseMenu : new PauseMenu(),
      PreGameplay : new PreGameplay(),
      PostGameplay :  new PostGameplay()
    };
  }/*,
  
  setAppState(appStateName) {
    
  }*/
};

////////////////////////////////////////////////////////////////////////////////////////


// == GAME OBJECTS == ////////////////////////////////////////////////////////////////////

  // == GAME MATH == ///////////////////////////////////////////////////////////////////
  function mirror(V,N) { // -2 * (V dot N) * N + V
  var b = -2 * dotProduct(V,N);
  return { x: N.x*b+V.x, y: N.y*b+V.y };
}
  function dotProduct(V,N) {
  return V.x * N.x + V.y * N.y;
}
  function SAME_SIGNS(a,b) { return (( a * b) >= 0); }
  function lines_intersect(segment1, segment2, ip) { // Returns: DONT_INTERSECT = 0; DO_INTERSECT = 1; COLLINEAR = 2;
    var x1 = segment1.x1, y1 = segment1.y1, x2 = segment1.x2, y2 = segment1.y2;
    var x3 = segment2.x1, y3 = segment2.y1, x4 = segment2.x2, y4 = segment2.y2;
  
    var a1, a2, b1, b2, c1, c2; // Coefficients of line eqns.
    var r1, r2, r3, r4;         // 'Sign' values
    var denom, offset, num;     // Intermediate values

    // Compute a1, b1, c1, where line joining points 1 and 2 is "a1 x  +  b1 y  +  c1  =  0".
    a1 = y2 - y1;
    b1 = x1 - x2;
    c1 = (x2 * y1) - (x1 * y2);

    // Compute r3 and r4.
    r3 = ((a1 * x3) + (b1 * y3) + c1);
    r4 = ((a1 * x4) + (b1 * y4) + c1);

    // Check signs of r3 and r4.  If both point 3 and point 4 lie on same side of line 1,
    // the line segments do not intersect.
    if ( r3 != 0 && r4 != 0 && SAME_SIGNS( r3, r4 )) return ( 0 );

    // Compute a2, b2, c2
    a2 = y4 - y3;
    b2 = x3 - x4;
    c2 = (x4 * y3) - (x3 * y4);

    // Compute r1 and r2
    r1 = (a2 * x1) + (b2 * y1) + c2;
    r2 = (a2 * x2) + (b2 * y2) + c2;

    // Check signs of r1 and r2.  If both point 1 and point 2 lie on same side of second line segment,
    // the line segments do not intersect.
    if ( r1 != 0 && r2 != 0 && SAME_SIGNS( r1, r2 )) return ( 0 );

    // Line segments intersect: compute intersection point.
    denom = (a1 * b2) - (a2 * b1);
    if ( denom == 0 ) return ( 2 );
  
    // Calculate intersection point
    ip.x = (b1 * c2 - b2 * c1) / denom;
    ip.y = (a2 * c1 - a1 * c2) / denom;

    return ( 1 );
}

  // == PHYSICS MODIFIERS == //////////////////////////////////////////////////////////
  class BoxCollider {
    constructor(h,w,l,t,i) {
      this.isCollidible = true;
      this.isBrick = true; // TODO: DIRTY
      this.height = h;
      this.width = w;
      this.position = {x:l,y:t};
      this.rect = [
        { x1: this.position.x, y1: this.position.y + this.height, x2: this.position.x + this.width, y2: this.position.y + this.height, normal: {x: 0, y: i*1} },
        { x1: this.position.x, y1: this.position.y, x2: this.position.x + this.width, y2: this.position.y, normal: {x: 0, y: i*-1} },
        { x1: this.position.x + this.width, y1: this.position.y + this.height, x2: this.position.x + this.width, y2: this.position.y, normal: {x: i*1, y: 0} },
        { x1: this.position.x, y1: this.position.y + this.height, x2: this.position.x, y2: this.position.y, normal: {x: i*-1, y: 0} }
      ];

      // Create DOM Element
      /*var $newDiw = $("<div class='rectangle'></div>");
      $newDiw.css("height",this.height);
      $newDiw.css("width",this.width);
      $newDiw.css("left",this.position.x);
      $newDiw.css("top",this.position.y);
      $("#appContainer").append($newDiw);*/
    }
  }
  class EdgeCollider {
    constructor(h,w,l,t,i) {
      this.isCollidible = true;
      this.isBrick = false; // TODO: DIRTY
      this.height = h;
      this.width = w;
      this.position = {x:l,y:t};
      this.rect = [{ x1: this.position.x, y1: this.position.y + this.height, x2: this.position.x + this.width, y2: this.position.y + this.height, normal: {x: 0, y: i*1} }];
    }
  }
  /*class Body {
    constructor() {}
  }*/

  // == GAME OBJECT BASE CLASS == ////////////////////////////////////////
  class GameObject {
    constructor() {
      this.sprite = new Sprite(GameApp.appStates.Gameplay);
    }
    setPosition(left, top) {
      this.sprite.setPosition(left,top);
    }
  }


// == GAME LOGIC == ////////////////////////////////////////////////////////////////////
var colliders = []; // TODO - rename and stuff
var bricksBroken = 0;

class GameLogic {
  constructor() {
    this.paddle =  new Paddle(2,60,255,175,-1);
      colliders.push(this.paddle);
      gameObjects.push(this.paddle);
      
    this.ball = new Ball();
      gameObjects.push(this.ball); // TODO
    
    colliders.push(new Brick(20,60,75,25,1)); // TODO
    colliders.push(new Brick(20,60,205,55,1)); // TODO
    colliders.push(new Brick(20,60,155,95,1)); // TODO
    
    this.borders = new BorderObject(200,400,0,0,-1);
      colliders.push(this.borders); // TODO
      this.borders.setPosition(0,0);
    
    this.reset();
  }
  reset() {
    this.ball.setPosition(70,70);
    this.ball.position = {x:70, y:70};
    this.ball.lastPosition = {x:0, y:0};
    this.ball.velocity = {x:0.15, y:-0.20};
   
    for(var i = 0; i < colliders.length; i++) {
      colliders[i].collider.isCollidible = true;
      colliders[i].sprite.domElement.css("visibility","inherit");
    }
  }
  update() {
    // console.log("GameLogic::update()");
    if(this.ball.position.y > 198) {
      //console.log("Game Over");
    }else if(bricksBroken == 3) {
      //console.log("You Won!");
      bricksBroken = 0; // TODO: dirty quick fix
      GameApp.stackAppState("PostGameplay");
    }
    
  }
}



class Ball extends GameObject{
  constructor() {
    super();
    this.sprite.setSize(5,5);
    
    this.velocity = {x:0.15, y:-0.20};
    this.position = {x:75, y:75};
    this.lastPosition = {x:0, y:0};
    this.nextPosition =  {x:0, y:0};
    
    this.ip = {x:0, y:0};
    this.velSegment = { x1:0, y1:0, x2:0, y2:0 };
  }
  update(delta) {
    this.lastPosition.x = this.position.x;
    this.lastPosition.y = this.position.y;

    this.nextPosition.x = this.position.x + this.velocity.x * delta;
    this.nextPosition.y = this.position.y + this.velocity.y * delta;

    this.velSegment.x1 = this.lastPosition.x;
    this.velSegment.y1 = this.lastPosition.y;
    
    this.velSegment.x2 = this.nextPosition.x;
    this.velSegment.y2 = this.nextPosition.y;

    for(var i = 0; i < colliders.length; i++) {        // TODO: FIND NEAREST RECTANGLE TO COLLIDE (and other stuff...)
      for(var ii = 0; ii < colliders[i].collider.rect.length; ii++) {
        if( 0 > dotProduct(this.velocity, colliders[i].collider.rect[ii].normal)) {
          if(lines_intersect(colliders[i].collider.rect[ii], this.velSegment, this.ip) && colliders[i].collider.isCollidible) { 
            var newVel = mirror(this.velocity, colliders[i].collider.rect[ii].normal);
            this.velocity.x = newVel.x;
            this.velocity.y = newVel.y;
            this.nextPosition.x = this.ip.x;
            this.nextPosition.y = this.ip.y;
            
            if(colliders[i].collider.isBrick) {
              colliders[i].collider.isCollidible = false;
              colliders[i].sprite.domElement.css("visibility","hidden");
              bricksBroken++;
            }
            
            break;
          }          
        }
      }
    }
    
    this.position.x = this.nextPosition.x;
    this.position.y = this.nextPosition.y;
  }
  draw(interpolation) {
    var left = (this.lastPosition.x + (this.position.x - this.lastPosition.x) * interpolation);
    var top = (this.lastPosition.y + (this.position.y - this.lastPosition.y) * interpolation);
    this.sprite.setPosition(left - 5 + "px", top - 5 + "px");
  }
}
class Brick extends GameObject{
  constructor(h,w,l,t,i) {
    super();
    this.sprite.setSize(h,w);
    this.setPosition(l,t);
    this.collider = new BoxCollider(h,w,l,t,i);
  }
}
class Paddle extends GameObject{
  constructor(h,w,l,t,i) {
    super();
    this.sprite.setSize(h,w);
    this.setPosition(l,t);
    this.collider = new EdgeCollider(h,w,l,t,i);
    
    this.velocity = {x:0.0, y:0.0};
    this.position = {x:l, y:t};
    this.lastPosition = {x:0, y:0};
    this.nextPosition =  {x:0, y:0};
    
    this.width = w;
    this.height = h;
  }
  update(delta) {
    this.lastPosition.x = this.position.x;
    this.lastPosition.y = this.position.y;

    this.nextPosition.x = this.position.x + this.velocity.x * delta;
    this.nextPosition.y = this.position.y + this.velocity.y * delta;

    this.position.x = this.nextPosition.x;
    this.position.y = this.nextPosition.y;
    
    
    this.collider.rect = [{  x1: this.position.x,
                    y1: this.position.y + this.height,
                    x2: this.position.x + this.width,
                    y2: this.position.y + this.height,
                    normal: {x: 0, y: -1} }];
    
  }
  draw(interpolation) {
    var left = (this.lastPosition.x + (this.position.x - this.lastPosition.x) * interpolation);
    var top = (this.lastPosition.y + (this.position.y - this.lastPosition.y) * interpolation);
    this.sprite.setPosition(left - 5 + "px", top - 5 + "px");
  }
  left() {
    this.velocity = {x:-0.4, y:0.0};
  }
  right() {
    this.velocity = {x:0.4, y:0.0};
  }
  reset() {
    this.velocity = {x:0.0, y:0.0};
  }
}
class BorderObject extends GameObject{
  constructor(h,w,l,t,i) {
    super();
    this.sprite.setSize(200,400);
    this.sprite.domElement.css("opacity", "0");
    this.collider = new BoxCollider(h,w,l,t,i);
    this.collider.isBrick = false;
  }
}



GameApp.setDefault();
GameApp.switchAppState("StartMenu"); // "StartMenu" | "Gameplay" | "PauseMenu"
var gameLogic = new GameLogic();



//////////////////////////////////////////////////////////////////////

This awesome code ( A Pen by Markku Lehmonen ) is write by Markku Lehmonen, you can se more from this user in the personal repository

You can find the original code on Codepen.io

2018 © Markku Lehmonen