A Pen by Markku Lehmonen

Thumbnail
This awesome code was written by SharpDal, you can see more from this user in the personal repository.
You can find the original code on Codepen.io
Copyright SharpDal ©

Technologies

  • HTML
  • CSS
  • JavaScript
<!DOCTYPE html>
<html lang="en" >

<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/SharpDal/a-pen-by-markku-lehmonen-aBdGwR */
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/SharpDal/a-pen-by-markku-lehmonen-aBdGwR */
// == 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();



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

Comments