<commit_before>;(function () {

  var game = new Phaser.Game(GAME_WIDTH, GAME_HEIGHT, Phaser.AUTO, 'breakout', { preload: preload, create: create, update: update });
  var GAME_WIDTH = 800;
  var GAME_HEIGHT = 600;

  var background;

  var bricks;
  var BRICK_ROWS = 4;
  var BRICK_COLS = 15;
  var BRICK_START_X = 120;
  var BRICK_START_Y = 100;
  var BRICK_SPACING_X = 36;
  var BRICK_SPACING_Y = 52;

  var paddle;
  var PADDLE_Y = 500;
  var PADDLE_WIDTH = 48;

  var ball;
  var ballOnPaddle = true;
  var BALL_WIDTH = 16;
  var BALL_HEIGHT = 16;
  var BALL_RELEASE_VELOCITY_X = -60;
  var BALL_RELEASE_VELOCITY_Y = -240;
  var BALL_VELOCITY_MULTIPLIER_X = 8;

  var score = 0;
  var lives = 3;
  var scoreText;
  var livesText;
  var infoText;
  var TEXT_Y = 550;

  var socket;
  var remotePlayers = {};
  var SET_INTERVAL_DELAY = 50;

  function preload() {
    console.log('preload invoked');
    game.load.atlas('breakout', '/assets/breakout.png', '/assets/breakout.json');
    game.load.image('starfield', '/assets/starfield.jpg');
  }

  function create() {
    console.log('create invoked');

    // Prevent game from pausing when browser tab loses focus
    game.stage.disableVisibilityChange = true;

    game.physics.startSystem(Phaser.Physics.ARCADE);

    background = game.add.tileSprite(0, 0, GAME_WIDTH, GAME_HEIGHT, 'starfield');

    // Check bounds collisions on all walls except bottom
    game.physics.arcade.checkCollision.down = false;

    createBricks();
    createLocalPaddle();
    createLocalBall();
    createText();

    game.input.onDown.add(releaseBall, this);

    socket = io.connect(window.location.hostname);
    attachSocketHandlers();

    setInterval(function() {
      socket.emit('update ball', {
        x: ball.body.x,
        y: ball.body.y
      });
    }, SET_INTERVAL_DELAY);
  }

  function createBricks() {
    console.log('createBricks invoked');
    bricks = game.add.group();
    bricks.enableBody = true;
    bricks.physicsBodyType = Phaser.Physics.ARCADE;

    var brick;
    var brickCount = 0;

    for (var row = 0; row < BRICK_ROWS; row++) {
      for (var col = 0; col < BRICK_COLS; col++) {
        brick = bricks.create(
          BRICK_START_X + (col * BRICK_SPACING_X),
          BRICK_START_Y + (row * BRICK_SPACING_Y),
          'breakout',
          'brick_' + (row + 1) + '_1.png'
        );
        brick.body.bounce.set(1);
        brick.body.immovable = true;
        brick.brickIndex = brickCount++;
      }
    }
  }

  function createLocalPaddle() {
    console.log('createLocalPaddle invoked');
    paddle = game.add.sprite(game.world.centerX, PADDLE_Y, 'breakout', 'paddle_big.png');
    paddle.anchor.setTo(0.5, 0.5);

    game.physics.enable(paddle, Phaser.Physics.ARCADE);

    paddle.body.collideWorldBounds = true;
    paddle.body.bounce.set(1);
    paddle.body.immovable = true;
  }

  function createLocalBall() {
    console.log('createLocalBall invoked');
    ball = game.add.sprite(game.world.centerX, PADDLE_Y - BALL_HEIGHT, 'breakout', 'ball_1.png');
    ball.anchor.set(0.5);
    ball.checkWorldBounds = true;

    game.physics.enable(ball, Phaser.Physics.ARCADE);

    ball.body.collideWorldBounds = true;
    ball.body.bounce.set(1);

    ball.animations.add('spin', [ 'ball_1.png', 'ball_2.png', 'ball_3.png', 'ball_4.png', 'ball_5.png' ], 50, true, false);

    ball.events.onOutOfBounds.add(ballLost, this);
  }

  function onUpdateBall(data) {
    if (typeof remotePlayers[data.id] !== "undefined") {
      if (typeof remotePlayers[data.id].remotePlayerBall !== "undefined") {
        remotePlayers[data.id].remotePlayerBall.x = data.x;
        remotePlayers[data.id].remotePlayerBall.y = data.y;
        console.log('remote ball position updated');
      }
    }
  }

  function releaseRemoteBall(data) {
    console.log('releaseRemoteBall invoked');

    //Creating a remoteball
    var remoteBall = game.add.sprite(data.posX, PADDLE_Y - BALL_HEIGHT, 'breakout', 'ball_1.png');
    remoteBall.anchor.set(0.5);
    remoteBall.checkWorldBounds = true;
    game.physics.enable(remoteBall, Phaser.Physics.ARCADE);
    remoteBall.body.collideWorldBounds = true;
    remoteBall.body.bounce.set(1);
    remoteBall.animations.add('spin', [ 'ball_1.png', 'ball_2.png', 'ball_3.png', 'ball_4.png', 'ball_5.png' ], 50, true, false);
    remoteBall.animations.play('spin');

    remoteBall.body.x = data.posX;
    remoteBall.body.velocity.x = data.exitVelocityX;
    remoteBall.body.velocity.y = data.exitVelocityY;

    remotePlayers[data.remotePlayerID]["remotePlayerBall"] = remoteBall;
    // ball.events.onOutOfBounds.add(ballLost, this);
  }

  function createRemoteBall(data) {
    var remoteBall = game.add.sprite(data.posX, data.posY, 'breakout', 'ball_1.png');
    remoteBall.anchor.set(0.5);
    remoteBall.checkWorldBounds = true;
    game.physics.enable(remoteBall, Phaser.Physics.ARCADE);
    remoteBall.body.collideWorldBounds = true;
    remoteBall.body.bounce.set(1);
    remoteBall.animations.add('spin', [ 'ball_1.png', 'ball_2.png', 'ball_3.png', 'ball_4.png', 'ball_5.png' ], 50, true, false);
    remoteBall.animations.play('spin');

    remoteBall.body.velocity.x = data.velocityX;
    remoteBall.body.velocity.y = data.velocityY;

    remotePlayers[data.remotePlayerID]["remotePlayerBall"] = remoteBall;
  }

  function createText() {
    console.log('createText invoked');
    scoreText = game.add.text(32, TEXT_Y, 'score: 0',
      { font: '20px Arial', fill: '#ffffff', align: 'left' });
    livesText = game.add.text(GAME_WIDTH - 120, TEXT_Y, 'lives: 3',
      { font: '20px Arial', fill: '#ffffff', align: 'left' });
    infoText = game.add.text(game.world.centerX, GAME_HEIGHT * (2 / 3), 'Click to start',
      { font: '40px Arial', fill: '#ffffff', align: 'center' });
    infoText.anchor.setTo(0.5, 0.5);
  }

  function attachSocketHandlers() {
    console.log('attachSocketHandlers invoked');
    socket.on('connect', onSocketConnect);
    socket.on('disconnect', onSocketDisconnect);
    socket.on('new player', onNewPlayer);
    socket.on('remove player', onRemovePlayer);
    socket.on('initial bricks', onInitialBricks);
    socket.on('brick kill to other clients', onBrickKillToOtherClients);
    socket.on('paddle release ball', onPaddleReleaseBall);
    socket.on('ball hit paddle', onBallHitPaddle);
    socket.on('existing ball', onExistingBall);
    socket.on('kill remote ball', onKillRemoteBall);
    socket.on('update ball', onUpdateBall);
  }

  function onKillRemoteBall(data) {
    console.log('onKillRemoteBall invoked');

    remotePlayers[data.remotePlayerID]["remotePlayerBall"].kill();
  }

  function onExistingBall(data) {
    console.log('onExistingBall invoked');
    //Creating a remoteball

    var b = remotePlayers[data.remotePlayerID]["remotePlayerBall"];

    if (typeof b === "undefined") {
      createRemoteBall(data);
    }
  }

  function onBallHitPaddle(data) {
    // Identify remote player's ball and change its velocity accordingly
    var b = remotePlayers[data.remotePlayerID]["remotePlayerBall"];
    b.body.velocity.x = data.exitVelocityX;
    b.body.velocity.y = data.exitVelocityY;
  }

  function onPaddleReleaseBall(data) {
    console.log("received message that other client has released ball");
    releaseRemoteBall(data);
  }

  function onSocketConnect() {
    console.log('onSocketConnect invoked');
    console.log('emitting "new player" message with existingBricks = ' + bricksString(bricks));
    socket.emit('new player', { existingBricks: bricksString(bricks) });
  }

  function onSocketDisconnect() {
    console.log('onSocketDisconnect invoked');
  }

  function onNewPlayer(data) {

    // Notify new player of client's ball position and velocity, but only do so if player hasn't released ball

    if (!ballOnPaddle) {
      socket.emit('existing ball', {
        velocityX: ball.body.velocity.x,
        velocityY: ball.body.velocity.y,
        posX: ball.body.position.x,
        posY: ball.body.position.y
      });
    }

    // Commented out due to errors with Converting circular structure to JSON
    console.log('onNewPlayer invoked. data = ' + JSON.stringify(data));
    remotePlayers[data.id] = { score: data.score };
    // console.log(data.id + ' added to remotePlayers: ' + JSON.stringify(remotePlayers));
  }

  function onRemovePlayer(data) {
    console.log('onRemovePlayer invoked');

    var remotePlayerBall = remotePlayers[data.id].remotePlayerBall;
    if (typeof remotePlayerBall !== "undefined") {
      remotePlayerBall.kill();
    }

    var newRemotePlayers = {};
    for (var id in remotePlayers) {
      if (id !== data.id) {
        newRemotePlayers[id] = remotePlayers[id];
      }
    }
    remotePlayers = newRemotePlayers;
  }

  function onInitialBricks(data) {
    console.log('onInitialBricks invoked. data = ' + JSON.stringify(data));
    var i;
    for (var row = 0; row < BRICK_ROWS; row++) {
      for (var col = 0; col < BRICK_COLS; col++) {
        i = row * BRICK_COLS + col;
        if (data.initialBricks.charAt(i) === "0") {
          bricks.children[i].kill();
        }
      }
    }
  }

  function onBrickKillToOtherClients(data) {
    console.log('onBrickKillToOtherClients invoked');

    // Change the velocity of the remote ball
    if (typeof remotePlayers[data.remotePlayerID] !== "undefined") {
      if (typeof remotePlayers[data.remotePlayerID].remotePlayerBall !== "undefined") {
        var b = remotePlayers[data.remotePlayerID]["remotePlayerBall"];
        b.body.velocity.x = data.exitVelocityX;
        b.body.velocity.y = data.exitVelocityY;
      }
    }

    bricks.children[data.brickIndex].kill();
  }

  function update() {
    paddle.body.x = game.input.x - 0.5 * PADDLE_WIDTH;

    if (paddle.body.x < 0) {
      paddle.body.x = 0;
    } else if (paddle.body.x > game.width - PADDLE_WIDTH) {
      paddle.body.x = game.width - PADDLE_WIDTH;
    }

    if (ballOnPaddle) {
      ball.body.x = paddle.body.x + 0.5 * PADDLE_WIDTH - 0.5 * BALL_WIDTH;
    } else {
      game.physics.arcade.collide(ball, paddle, ballHitPaddle, null, this);
      game.physics.arcade.collide(ball, bricks, ballHitBrick, null, this);
    }

    if (bricks.countLiving() === 0) {
      startNewRound();
    }
  }

  function releaseBall() {
    if (ballOnPaddle) {
      ballOnPaddle = false;
      ball.body.velocity.x = BALL_RELEASE_VELOCITY_X * ( Math.random() * 4 - 1 );
      ball.body.velocity.y = BALL_RELEASE_VELOCITY_Y;
      ball.animations.play('spin');
      infoText.visible = false;

      // Tell other clients of the release of ball
      socket.emit('paddle release ball', {
        exitVelocityX: ball.body.velocity.x,
        exitVelocityY: ball.body.velocity.y,
        posX: ball.body.x,
      });
    }
  }

  function ballLost() {
    socket.emit('kill remote ball');

    lives--;
    livesText.text = 'lives: ' + lives;
    if (lives === 0) {
      gameOver();
    } else {
      putBallOnPaddle();
    }
  }

  function putBallOnPaddle() {
    ballOnPaddle = true;
    ball.reset(paddle.body.x + 0.5 * PADDLE_WIDTH - 0.5 * BALL_WIDTH, PADDLE_Y - BALL_HEIGHT);
    ball.animations.stop();
  }

  function startNewRound() {
    socket.emit('kill remote ball');

    console.log('startNewRound invoked');
    bricks.callAll('revive');
    if (lives !==0 ) {
      putBallOnPaddle();
      score += 1000;
      scoreText.text = 'score: ' + score;
      infoText.text = 'Next Round';
    }
  }

  function gameOver() {
    ball.body.velocity.setTo(0, 0);

    infoText.text = 'Game Over!';
    infoText.visible = true;
  }

  function ballHitBrick(_ball, _brick) {
    socket.emit('brick kill from client', {
      brickIndex: _brick.brickIndex,
      exitVelocityX: ball.body.velocity.x,
      exitVelocityY: ball.body.velocity.y
    });

    _brick.kill();

    score += 10;
    scoreText.text = 'score: ' + score;
  }

  function ballHitPaddle(_ball, _paddle) {
    var ballCenter = _ball.body.x + BALL_WIDTH / 2;
    var paddleCenter = _paddle.body.x + PADDLE_WIDTH / 2;
    var diff;

    if (ballCenter < paddleCenter) {
      diff = paddleCenter - ballCenter;
      _ball.body.velocity.x = (BALL_VELOCITY_MULTIPLIER_X * diff * -1);
    } else if (ballCenter > paddleCenter) {
      diff = ballCenter - paddleCenter;
      _ball.body.velocity.x = (BALL_VELOCITY_MULTIPLIER_X * diff);
    } else {
      _ball.body.velocity.x = BALL_VELOCITY_MULTIPLIER_X * 0.2 + Math.random() * BALL_VELOCITY_MULTIPLIER_X * 0.8;
    }

    socket.emit('ball hit paddle', {
      exitVelocityX: _ball.body.velocity.x,
      exitVelocityY: _ball.body.velocity.y
    });
  }

  function bricksString(bricksGroup) {
    var i;
    var length = bricksGroup.children.length;
    var result = "";
    for (i = 0; i < length; i++) {
      if (bricksGroup.children[i].alive) {
        result += "1";
      } else {
        result += "0";
      }
    }
    return result;
  }

}());
<commit_msg>Remove empty line<commit_after>;(function () {

  var game = new Phaser.Game(GAME_WIDTH, GAME_HEIGHT, Phaser.AUTO, 'breakout', { preload: preload, create: create, update: update });
  var GAME_WIDTH = 800;
  var GAME_HEIGHT = 600;

  var background;

  var bricks;
  var BRICK_ROWS = 4;
  var BRICK_COLS = 15;
  var BRICK_START_X = 120;
  var BRICK_START_Y = 100;
  var BRICK_SPACING_X = 36;
  var BRICK_SPACING_Y = 52;

  var paddle;
  var PADDLE_Y = 500;
  var PADDLE_WIDTH = 48;

  var ball;
  var ballOnPaddle = true;
  var BALL_WIDTH = 16;
  var BALL_HEIGHT = 16;
  var BALL_RELEASE_VELOCITY_X = -60;
  var BALL_RELEASE_VELOCITY_Y = -240;
  var BALL_VELOCITY_MULTIPLIER_X = 8;

  var score = 0;
  var lives = 3;
  var scoreText;
  var livesText;
  var infoText;
  var TEXT_Y = 550;

  var socket;
  var remotePlayers = {};
  var SET_INTERVAL_DELAY = 50;

  function preload() {
    console.log('preload invoked');
    game.load.atlas('breakout', '/assets/breakout.png', '/assets/breakout.json');
    game.load.image('starfield', '/assets/starfield.jpg');
  }

  function create() {
    console.log('create invoked');

    // Prevent game from pausing when browser tab loses focus
    game.stage.disableVisibilityChange = true;

    game.physics.startSystem(Phaser.Physics.ARCADE);

    background = game.add.tileSprite(0, 0, GAME_WIDTH, GAME_HEIGHT, 'starfield');

    // Check bounds collisions on all walls except bottom
    game.physics.arcade.checkCollision.down = false;

    createBricks();
    createLocalPaddle();
    createLocalBall();
    createText();

    game.input.onDown.add(releaseBall, this);

    socket = io.connect(window.location.hostname);
    attachSocketHandlers();

    setInterval(function() {
      socket.emit('update ball', {
        x: ball.body.x,
        y: ball.body.y
      });
    }, SET_INTERVAL_DELAY);
  }

  function createBricks() {
    console.log('createBricks invoked');
    bricks = game.add.group();
    bricks.enableBody = true;
    bricks.physicsBodyType = Phaser.Physics.ARCADE;

    var brick;
    var brickCount = 0;

    for (var row = 0; row < BRICK_ROWS; row++) {
      for (var col = 0; col < BRICK_COLS; col++) {
        brick = bricks.create(
          BRICK_START_X + (col * BRICK_SPACING_X),
          BRICK_START_Y + (row * BRICK_SPACING_Y),
          'breakout',
          'brick_' + (row + 1) + '_1.png'
        );
        brick.body.bounce.set(1);
        brick.body.immovable = true;
        brick.brickIndex = brickCount++;
      }
    }
  }

  function createLocalPaddle() {
    console.log('createLocalPaddle invoked');
    paddle = game.add.sprite(game.world.centerX, PADDLE_Y, 'breakout', 'paddle_big.png');
    paddle.anchor.setTo(0.5, 0.5);

    game.physics.enable(paddle, Phaser.Physics.ARCADE);

    paddle.body.collideWorldBounds = true;
    paddle.body.bounce.set(1);
    paddle.body.immovable = true;
  }

  function createLocalBall() {
    console.log('createLocalBall invoked');
    ball = game.add.sprite(game.world.centerX, PADDLE_Y - BALL_HEIGHT, 'breakout', 'ball_1.png');
    ball.anchor.set(0.5);
    ball.checkWorldBounds = true;

    game.physics.enable(ball, Phaser.Physics.ARCADE);

    ball.body.collideWorldBounds = true;
    ball.body.bounce.set(1);

    ball.animations.add('spin', [ 'ball_1.png', 'ball_2.png', 'ball_3.png', 'ball_4.png', 'ball_5.png' ], 50, true, false);

    ball.events.onOutOfBounds.add(ballLost, this);
  }

  function onUpdateBall(data) {
    if (typeof remotePlayers[data.id] !== "undefined") {
      if (typeof remotePlayers[data.id].remotePlayerBall !== "undefined") {
        remotePlayers[data.id].remotePlayerBall.x = data.x;
        remotePlayers[data.id].remotePlayerBall.y = data.y;
        console.log('remote ball position updated');
      }
    }
  }

  function releaseRemoteBall(data) {
    console.log('releaseRemoteBall invoked');

    //Creating a remoteball
    var remoteBall = game.add.sprite(data.posX, PADDLE_Y - BALL_HEIGHT, 'breakout', 'ball_1.png');
    remoteBall.anchor.set(0.5);
    remoteBall.checkWorldBounds = true;
    game.physics.enable(remoteBall, Phaser.Physics.ARCADE);
    remoteBall.body.collideWorldBounds = true;
    remoteBall.body.bounce.set(1);
    remoteBall.animations.add('spin', [ 'ball_1.png', 'ball_2.png', 'ball_3.png', 'ball_4.png', 'ball_5.png' ], 50, true, false);
    remoteBall.animations.play('spin');

    remoteBall.body.x = data.posX;
    remoteBall.body.velocity.x = data.exitVelocityX;
    remoteBall.body.velocity.y = data.exitVelocityY;

    remotePlayers[data.remotePlayerID]["remotePlayerBall"] = remoteBall;
    // ball.events.onOutOfBounds.add(ballLost, this);
  }

  function createRemoteBall(data) {
    var remoteBall = game.add.sprite(data.posX, data.posY, 'breakout', 'ball_1.png');
    remoteBall.anchor.set(0.5);
    remoteBall.checkWorldBounds = true;
    game.physics.enable(remoteBall, Phaser.Physics.ARCADE);
    remoteBall.body.collideWorldBounds = true;
    remoteBall.body.bounce.set(1);
    remoteBall.animations.add('spin', [ 'ball_1.png', 'ball_2.png', 'ball_3.png', 'ball_4.png', 'ball_5.png' ], 50, true, false);
    remoteBall.animations.play('spin');

    remoteBall.body.velocity.x = data.velocityX;
    remoteBall.body.velocity.y = data.velocityY;

    remotePlayers[data.remotePlayerID]["remotePlayerBall"] = remoteBall;
  }

  function createText() {
    console.log('createText invoked');
    scoreText = game.add.text(32, TEXT_Y, 'score: 0',
      { font: '20px Arial', fill: '#ffffff', align: 'left' });
    livesText = game.add.text(GAME_WIDTH - 120, TEXT_Y, 'lives: 3',
      { font: '20px Arial', fill: '#ffffff', align: 'left' });
    infoText = game.add.text(game.world.centerX, GAME_HEIGHT * (2 / 3), 'Click to start',
      { font: '40px Arial', fill: '#ffffff', align: 'center' });
    infoText.anchor.setTo(0.5, 0.5);
  }

  function attachSocketHandlers() {
    console.log('attachSocketHandlers invoked');
    socket.on('connect', onSocketConnect);
    socket.on('disconnect', onSocketDisconnect);
    socket.on('new player', onNewPlayer);
    socket.on('remove player', onRemovePlayer);
    socket.on('initial bricks', onInitialBricks);
    socket.on('brick kill to other clients', onBrickKillToOtherClients);
    socket.on('paddle release ball', onPaddleReleaseBall);
    socket.on('ball hit paddle', onBallHitPaddle);
    socket.on('existing ball', onExistingBall);
    socket.on('kill remote ball', onKillRemoteBall);
    socket.on('update ball', onUpdateBall);
  }

  function onKillRemoteBall(data) {
    console.log('onKillRemoteBall invoked');
    remotePlayers[data.remotePlayerID]["remotePlayerBall"].kill();
  }

  function onExistingBall(data) {
    console.log('onExistingBall invoked');
    //Creating a remoteball

    var b = remotePlayers[data.remotePlayerID]["remotePlayerBall"];

    if (typeof b === "undefined") {
      createRemoteBall(data);
    }
  }

  function onBallHitPaddle(data) {
    // Identify remote player's ball and change its velocity accordingly
    var b = remotePlayers[data.remotePlayerID]["remotePlayerBall"];
    b.body.velocity.x = data.exitVelocityX;
    b.body.velocity.y = data.exitVelocityY;
  }

  function onPaddleReleaseBall(data) {
    console.log("received message that other client has released ball");
    releaseRemoteBall(data);
  }

  function onSocketConnect() {
    console.log('onSocketConnect invoked');
    console.log('emitting "new player" message with existingBricks = ' + bricksString(bricks));
    socket.emit('new player', { existingBricks: bricksString(bricks) });
  }

  function onSocketDisconnect() {
    console.log('onSocketDisconnect invoked');
  }

  function onNewPlayer(data) {

    // Notify new player of client's ball position and velocity, but only do so if player hasn't released ball

    if (!ballOnPaddle) {
      socket.emit('existing ball', {
        velocityX: ball.body.velocity.x,
        velocityY: ball.body.velocity.y,
        posX: ball.body.position.x,
        posY: ball.body.position.y
      });
    }

    // Commented out due to errors with Converting circular structure to JSON
    console.log('onNewPlayer invoked. data = ' + JSON.stringify(data));
    remotePlayers[data.id] = { score: data.score };
    // console.log(data.id + ' added to remotePlayers: ' + JSON.stringify(remotePlayers));
  }

  function onRemovePlayer(data) {
    console.log('onRemovePlayer invoked');

    var remotePlayerBall = remotePlayers[data.id].remotePlayerBall;
    if (typeof remotePlayerBall !== "undefined") {
      remotePlayerBall.kill();
    }

    var newRemotePlayers = {};
    for (var id in remotePlayers) {
      if (id !== data.id) {
        newRemotePlayers[id] = remotePlayers[id];
      }
    }
    remotePlayers = newRemotePlayers;
  }

  function onInitialBricks(data) {
    console.log('onInitialBricks invoked. data = ' + JSON.stringify(data));
    var i;
    for (var row = 0; row < BRICK_ROWS; row++) {
      for (var col = 0; col < BRICK_COLS; col++) {
        i = row * BRICK_COLS + col;
        if (data.initialBricks.charAt(i) === "0") {
          bricks.children[i].kill();
        }
      }
    }
  }

  function onBrickKillToOtherClients(data) {
    console.log('onBrickKillToOtherClients invoked');

    // Change the velocity of the remote ball
    if (typeof remotePlayers[data.remotePlayerID] !== "undefined") {
      if (typeof remotePlayers[data.remotePlayerID].remotePlayerBall !== "undefined") {
        var b = remotePlayers[data.remotePlayerID]["remotePlayerBall"];
        b.body.velocity.x = data.exitVelocityX;
        b.body.velocity.y = data.exitVelocityY;
      }
    }

    bricks.children[data.brickIndex].kill();
  }

  function update() {
    paddle.body.x = game.input.x - 0.5 * PADDLE_WIDTH;

    if (paddle.body.x < 0) {
      paddle.body.x = 0;
    } else if (paddle.body.x > game.width - PADDLE_WIDTH) {
      paddle.body.x = game.width - PADDLE_WIDTH;
    }

    if (ballOnPaddle) {
      ball.body.x = paddle.body.x + 0.5 * PADDLE_WIDTH - 0.5 * BALL_WIDTH;
    } else {
      game.physics.arcade.collide(ball, paddle, ballHitPaddle, null, this);
      game.physics.arcade.collide(ball, bricks, ballHitBrick, null, this);
    }

    if (bricks.countLiving() === 0) {
      startNewRound();
    }
  }

  function releaseBall() {
    if (ballOnPaddle) {
      ballOnPaddle = false;
      ball.body.velocity.x = BALL_RELEASE_VELOCITY_X * ( Math.random() * 4 - 1 );
      ball.body.velocity.y = BALL_RELEASE_VELOCITY_Y;
      ball.animations.play('spin');
      infoText.visible = false;

      // Tell other clients of the release of ball
      socket.emit('paddle release ball', {
        exitVelocityX: ball.body.velocity.x,
        exitVelocityY: ball.body.velocity.y,
        posX: ball.body.x,
      });
    }
  }

  function ballLost() {
    socket.emit('kill remote ball');

    lives--;
    livesText.text = 'lives: ' + lives;
    if (lives === 0) {
      gameOver();
    } else {
      putBallOnPaddle();
    }
  }

  function putBallOnPaddle() {
    ballOnPaddle = true;
    ball.reset(paddle.body.x + 0.5 * PADDLE_WIDTH - 0.5 * BALL_WIDTH, PADDLE_Y - BALL_HEIGHT);
    ball.animations.stop();
  }

  function startNewRound() {
    socket.emit('kill remote ball');

    console.log('startNewRound invoked');
    bricks.callAll('revive');
    if (lives !==0 ) {
      putBallOnPaddle();
      score += 1000;
      scoreText.text = 'score: ' + score;
      infoText.text = 'Next Round';
    }
  }

  function gameOver() {
    ball.body.velocity.setTo(0, 0);

    infoText.text = 'Game Over!';
    infoText.visible = true;
  }

  function ballHitBrick(_ball, _brick) {
    socket.emit('brick kill from client', {
      brickIndex: _brick.brickIndex,
      exitVelocityX: ball.body.velocity.x,
      exitVelocityY: ball.body.velocity.y
    });

    _brick.kill();

    score += 10;
    scoreText.text = 'score: ' + score;
  }

  function ballHitPaddle(_ball, _paddle) {
    var ballCenter = _ball.body.x + BALL_WIDTH / 2;
    var paddleCenter = _paddle.body.x + PADDLE_WIDTH / 2;
    var diff;

    if (ballCenter < paddleCenter) {
      diff = paddleCenter - ballCenter;
      _ball.body.velocity.x = (BALL_VELOCITY_MULTIPLIER_X * diff * -1);
    } else if (ballCenter > paddleCenter) {
      diff = ballCenter - paddleCenter;
      _ball.body.velocity.x = (BALL_VELOCITY_MULTIPLIER_X * diff);
    } else {
      _ball.body.velocity.x = BALL_VELOCITY_MULTIPLIER_X * 0.2 + Math.random() * BALL_VELOCITY_MULTIPLIER_X * 0.8;
    }

    socket.emit('ball hit paddle', {
      exitVelocityX: _ball.body.velocity.x,
      exitVelocityY: _ball.body.velocity.y
    });
  }

  function bricksString(bricksGroup) {
    var i;
    var length = bricksGroup.children.length;
    var result = "";
    for (i = 0; i < length; i++) {
      if (bricksGroup.children[i].alive) {
        result += "1";
      } else {
        result += "0";
      }
    }
    return result;
  }

}());
<|endoftext|>


<commit_before>import {select as d3Select} from "d3-selection";
import {transition as d3Transition} from "d3-transition";
const d3 = {
  "select": d3Select,
  "transition": d3Transition
};

import {box} from "d3plus-text";
import {contrast} from "d3plus-color";

import {default as constant} from "./constant";

/**
    The default height accessor function.
    @private
*/
function rectHeight(d) {
  return d.height;
}

/**
    The default id accessor function.
    @private
*/
function rectId(d) {
  return d.id;
}

/**
    The default inner bounds function.
    @private
*/
function rectInnerBounds(s) {
  return {"width": s.width, "height": s.height, "x": -s.width / 2, "y": -s.height / 2};
}

/**
    The default width accessor function.
    @private
*/
function rectWidth(d) {
  return d.width;
}

/**
    The default x accessor function.
    @private
*/
function rectX(d) {
  return d.x;
}

/**
    The default y accessor function.
    @private
*/
function rectY(d) {
  return d.y;
}

/**
    @function rect
    @desc Creates SVG rectangles based on an array of data. If *data* is specified, immediately draws squares based on the specified array and returns this generator. If *data* is not specified on instantiation, it can be passed/updated after instantiation using the [data](#rect.data) method.
    @param {Array} [data = []]
    @example
a sample row of data

var data = {"id": 0, "x": 100, "y": 50, "width": 200, "height": 100};
@example
passed to the generator

rect([data]);
@example
creates the following

<g class="d3plus-shape-rect" id="d3plus-shape-rect-0" transform="translate(100,50)">
  <rect width="200" height="100" x="-100" y="-50" fill="black"></rect>
</g>
@example
this is shorthand for the following

rect().data([data])();
@example
which also allows a post-draw callback function

rect().data([data])(function() { alert("draw complete!"); })
*/
export default function(data = []) {

  /**
      The default font-color accessor function.
      @private
  */
  function rectFontColor(d, i) {
    return contrast(fill(d, i));
  }

  let duration = 600,
      fill = constant("black"),
      fontColor = rectFontColor,
      fontFamily,
      fontResize = constant(false),
      fontSize,
      height = rectHeight,
      id = rectId,
      innerBounds = rectInnerBounds,
      label,
      labelPadding = constant(5),
      select,
      textAnchor = constant("start"),
      verticalAlign = constant("top"),
      width = rectWidth,
      x = rectX,
      y = rectY;

  /**
      The inner return object and draw function that gets assigned the public methods.
      @private
  */
  function rect(callback) {

    if (select === void 0) rect.select(d3.select("body").append("svg").style("width", `${window.innerWidth}px`).style("height", `${window.innerHeight}px`).style("display", "block").node());

    const groups = select.selectAll(".d3plus-shape-rect").data(data, id);

    groups.exit().transition().delay(duration).remove();

    groups.exit().selectAll("rect").transition().duration(duration)
      .attr("width", 0)
      .attr("height", 0)
      .attr("x", (d, i) => x(d, i))
      .attr("y", (d, i) => y(d, i));

    const enter = groups.enter().append("g")
        .attr("class", "d3plus-shape-rect")
        .attr("id", (d, i) => `d3plus-shape-rect-${id(d, i)}`)
        .attr("transform", (d, i) => `translate(${x(d, i)},${y(d, i)})`);

    enter.append("rect")
        .attr("width", 0)
        .attr("height", 0)
        .attr("x", 0)
        .attr("y", 0)
        .attr("fill", (d, i) => fill(d, i));

    const update = enter.merge(groups);

    update.transition().duration(duration)
      .attr("transform", (d, i) => `translate(${x(d, i)},${y(d, i)})`);

    update.merge(enter).selectAll("rect").transition().duration(duration)
      .attr("width", (d, i) => width(d, i))
      .attr("height", (d, i) => height(d, i))
      .attr("x", (d, i) => -width(d, i) / 2)
      .attr("y", (d, i) => -height(d, i) / 2)
      .attr("fill", (d, i) => fill(d, i));

    /* Draw labels based on inner bounds */
    update.each(function(d, i) {

      if (label !== void 0) {

        const bounds = innerBounds({"width": width(d, i), "height": height(d, i)}, i),
              padding = labelPadding(d, i);
        bounds.height -= padding * 2;
        bounds.width -= padding * 2;
        bounds.x += padding;
        bounds.y += padding;

        box()
          .data([bounds])
          .delay(duration / 2)
          .duration(duration)
          .fontColor(fontColor(d, i))
          .fontFamily(fontFamily(d, i))
          .fontResize(fontResize(d, i))
          .fontSize(fontSize(d, i))
          .textAnchor(textAnchor(d, i))
          .verticalAlign(verticalAlign(d, i))
          .select(this).text(label(d, i))();

      }

    });

    if (callback) setTimeout(callback, duration + 100);

    return rect;

  }

  /**
      @memberof rect
      @desc If *data* is specified, sets the data array to the specified array and returns this generator. If *data* is not specified, returns the current data array. A rectangle will be drawn for each object in the array.
      @param {Array} [*data* = []]
  */
  rect.data = function(_) {
    return arguments.length ? (data = _, rect) : data;
  };

  /**
      @memberof rect
      @desc If *ms* is specified, sets the animation duration to the specified number and returns this generator. If *ms* is not specified, returns the current animation duration.
      @param {Number} [*ms* = 600]
  */
  rect.duration = function(_) {
    return arguments.length ? (duration = _, rect) : duration;
  };

  /**
      @memberof rect
      @desc If *value* is specified, sets the fill accessor to the specified function or string and returns this generator. If *value* is not specified, returns the current fill accessor.
      @param {Function|String} [*value* = "black"]
  */
  rect.fill = function(_) {
    return arguments.length ? (fill = typeof _ === "function" ? _ : constant(_), rect) : fill;
  };

  /**
      @memberof rect
      @desc If *value* is specified, sets the font-color accessor to the specified function or string and returns this generator. If *value* is not specified, returns the current font-color accessor, which by default returns a color that contrasts the fill color.
      @param {Function|String} [*value*]
  */
  rect.fontColor = function(_) {
    return arguments.length ? (fontColor = typeof _ === "function" ? _ : constant(_), rect) : fontColor;
  };

  /**
      @memberof rect
      @desc If *value* is specified, sets the font-family accessor to the specified function or string and returns this generator. If *value* is not specified, returns the current font-family accessor.
      @param {Function|String} [*value*]
  */
  rect.fontFamily = function(_) {
    return arguments.length ? (fontFamily = typeof _ === "function" ? _ : constant(_), rect) : fontFamily;
  };

  /**
      @memberof rect
      @desc If *value* is specified, sets the font resizing accessor to the specified function or boolean and returns this generator. If *value* is not specified, returns the current font resizing accessor. When font resizing is enabled, the font-size of the value returned by [label](#rect.label) will be resized the best fit the rectangle.
      @param {Function|Boolean} [*value*]
  */
  rect.fontResize = function(_) {
    return arguments.length ? (fontResize = typeof _ === "function" ? _ : constant(_), rect) : fontResize;
  };

  /**
      @memberof rect
      @desc If *value* is specified, sets the font-size accessor to the specified function or string and returns this generator. If *value* is not specified, returns the current font-size accessor.
      @param {Function|String} [*value*]
  */
  rect.fontSize = function(_) {
    return arguments.length ? (fontSize = typeof _ === "function" ? _ : constant(_), rect) : fontSize;
  };

  /**
      @memberof rect
      @desc If *value* is specified, sets the height accessor to the specified function or number and returns this generator. If *value* is not specified, returns the current height accessor.
      @param {Function|Number} [*value*]
      @example
function(d) {
  return d.height;
}
  */
  rect.height = function(_) {
    return arguments.length ? (height = typeof _ === "function" ? _ : constant(_), rect) : height;
  };

  /**
      @memberof rect
      @desc If *value* is specified, sets the id accessor to the specified function and returns this generator. If *value* is not specified, returns the current id accessor.
      @param {Function} [*value*]
      @example
function(d) {
  return d.id;
}
  */
  rect.id = function(_) {
    return arguments.length ? (id = _, rect) : id;
  };

  /**
      @memberof rect
      @desc If *bounds* is specified, sets the inner bounds to the specified function and returns this generator. If *bounds* is not specified, returns the current inner bounds accessor.
      @example
function(shape) {
  return {
    "width": shape.width,
    "height": shape.height,
    "x": -shape.width / 2,
    "y": -shape.height / 2
  };
}
      @param {Function} [*bounds*] Given a rectangle's width and height, the function should return an object containing the following values: `width`, `height`, `x`, `y`.
  */
  rect.innerBounds = function(_) {
    return arguments.length ? (innerBounds = _, rect) : innerBounds;
  };

  /**
      @memberof rect
      @desc If *value* is specified, sets the label accessor to the specified function or string and returns this generator. If *value* is not specified, returns the current text accessor, which is `undefined` by default.
      @param {Function|String} [*value*]
  */
  rect.label = function(_) {
    return arguments.length ? (label = typeof _ === "function" ? _ : constant(_), rect) : label;
  };

  /**
      @memberof rect
      @desc If *value* is specified, sets the label padding to the specified number and returns this generator. If *value* is not specified, returns the current label padding.
      @param {Number} [*value* = 10]
  */
  rect.labelPadding = function(_) {
    return arguments.length ? (labelPadding = typeof _ === "function" ? _ : constant(_), rect) : labelPadding;
  };

  /**
      @memberof rect
      @desc If *selector* is specified, sets the SVG container element to the specified d3 selector or DOM element and returns this generator. If *selector* is not specified, returns the current SVG container element.
      @param {String|HTMLElement} [*selector* = d3.select("body").append("svg")]
  */
  rect.select = function(_) {
    if (arguments.length) {
      select = d3.select(_);
      if (fontFamily === void 0) fontFamily = constant(select.style("font-family"));
      if (fontSize === void 0) fontSize = constant(parseFloat(select.style("font-size"), 10));
      return rect;
    }
    return select;
  };

  /**
      @memberof rect
      @desc If *value* is specified, sets the text-anchor accessor to the specified function or string and returns this generator. If *value* is not specified, returns the current text-anchor accessor, which is `"start"` by default. Accepted values are `"start"`, `"middle"`, and `"end"`.
      @param {Function|String} [*value* = "start"]
  */
  rect.textAnchor = function(_) {
    return arguments.length ? (textAnchor = typeof _ === "function" ? _ : constant(_), rect) : textAnchor;
  };

  /**
      @memberof rect
      @desc If *value* is specified, sets the vertical alignment accessor to the specified function or string and returns this generator. If *value* is not specified, returns the current vertical alignment accessor, which is `"top"` by default. Accepted values are `"top"`, `"middle"`, and `"bottom"`.
      @param {Function|String} [*value* = "start"]
  */
  rect.verticalAlign = function(_) {
    return arguments.length ? (verticalAlign = typeof _ === "function" ? _ : constant(_), rect) : verticalAlign;
  };

  /**
      @memberof rect
      @desc If *value* is specified, sets the width accessor to the specified function or number and returns this generator. If *value* is not specified, returns the current width accessor.
      @param {Function|Number} [*value*]
      @example
function(d) {
  return d.width;
}
  */
  rect.width = function(_) {
    return arguments.length ? (width = typeof _ === "function" ? _ : constant(_), rect) : width;
  };

  /**
      @memberof rect
      @desc If *value* is specified, sets the x accessor to the specified function or number and returns this generator. If *value* is not specified, returns the current x accessor. The number returned should correspond to the horizontal center of the rectangle.
      @param {Function|Number} [*value*]
      @example
function(d) {
  return d.x;
}
  */
  rect.x = function(_) {
    return arguments.length ? (x = typeof _ === "function" ? _ : constant(_), rect) : x;
  };

  /**
      @memberof rect
      @desc If *value* is specified, sets the y accessor to the specified function or number and returns this generator. If *value* is not specified, returns the current y accessor. The number returned should correspond to the vertical center of the rectangle.
      @param {Function|Number} [*value*]
      @example
function(d) {
  return d.y;
}
  */
  rect.y = function(_) {
    return arguments.length ? (y = typeof _ === "function" ? _ : constant(_), rect) : y;
  };

  return data.length ? rect() : rect;

}
<commit_msg>adds lineHeight accessor<commit_after>import {select as d3Select} from "d3-selection";
import {transition as d3Transition} from "d3-transition";
const d3 = {
  "select": d3Select,
  "transition": d3Transition
};

import {box} from "d3plus-text";
import {contrast} from "d3plus-color";

import {default as constant} from "./constant";

/**
    The default height accessor function.
    @private
*/
function rectHeight(d) {
  return d.height;
}

/**
    The default id accessor function.
    @private
*/
function rectId(d) {
  return d.id;
}

/**
    The default inner bounds function.
    @private
*/
function rectInnerBounds(s) {
  return {"width": s.width, "height": s.height, "x": -s.width / 2, "y": -s.height / 2};
}

/**
    The default width accessor function.
    @private
*/
function rectWidth(d) {
  return d.width;
}

/**
    The default x accessor function.
    @private
*/
function rectX(d) {
  return d.x;
}

/**
    The default y accessor function.
    @private
*/
function rectY(d) {
  return d.y;
}

/**
    @function rect
    @desc Creates SVG rectangles based on an array of data. If *data* is specified, immediately draws squares based on the specified array and returns this generator. If *data* is not specified on instantiation, it can be passed/updated after instantiation using the [data](#rect.data) method.
    @param {Array} [data = []]
    @example
a sample row of data

var data = {"id": 0, "x": 100, "y": 50, "width": 200, "height": 100};
@example
passed to the generator

rect([data]);
@example
creates the following

<g class="d3plus-shape-rect" id="d3plus-shape-rect-0" transform="translate(100,50)">
  <rect width="200" height="100" x="-100" y="-50" fill="black"></rect>
</g>
@example
this is shorthand for the following

rect().data([data])();
@example
which also allows a post-draw callback function

rect().data([data])(function() { alert("draw complete!"); })
*/
export default function(data = []) {

  /**
      The default font-color accessor function.
      @private
  */
  function rectFontColor(d, i) {
    return contrast(fill(d, i));
  }

  let duration = 600,
      fill = constant("black"),
      fontColor = rectFontColor,
      fontFamily,
      fontResize = constant(false),
      fontSize,
      height = rectHeight,
      id = rectId,
      innerBounds = rectInnerBounds,
      label,
      labelPadding = constant(5),
      lineHeight,
      select,
      textAnchor = constant("start"),
      verticalAlign = constant("top"),
      width = rectWidth,
      x = rectX,
      y = rectY;

  /**
      The inner return object and draw function that gets assigned the public methods.
      @private
  */
  function rect(callback) {

    if (select === void 0) rect.select(d3.select("body").append("svg").style("width", `${window.innerWidth}px`).style("height", `${window.innerHeight}px`).style("display", "block").node());
    if (lineHeight === void 0) lineHeight = (d, i) => fontSize(d, i) * 1.1;

    const groups = select.selectAll(".d3plus-shape-rect").data(data, id);

    groups.exit().transition().delay(duration).remove();

    groups.exit().selectAll("rect").transition().duration(duration)
      .attr("width", 0)
      .attr("height", 0)
      .attr("x", (d, i) => x(d, i))
      .attr("y", (d, i) => y(d, i));

    const enter = groups.enter().append("g")
        .attr("class", "d3plus-shape-rect")
        .attr("id", (d, i) => `d3plus-shape-rect-${id(d, i)}`)
        .attr("transform", (d, i) => `translate(${x(d, i)},${y(d, i)})`);

    enter.append("rect")
        .attr("width", 0)
        .attr("height", 0)
        .attr("x", 0)
        .attr("y", 0)
        .attr("fill", (d, i) => fill(d, i));

    const update = enter.merge(groups);

    update.transition().duration(duration)
      .attr("transform", (d, i) => `translate(${x(d, i)},${y(d, i)})`);

    update.merge(enter).selectAll("rect").transition().duration(duration)
      .attr("width", (d, i) => width(d, i))
      .attr("height", (d, i) => height(d, i))
      .attr("x", (d, i) => -width(d, i) / 2)
      .attr("y", (d, i) => -height(d, i) / 2)
      .attr("fill", (d, i) => fill(d, i));

    /* Draw labels based on inner bounds */
    update.each(function(d, i) {

      if (label !== void 0) {

        const bounds = innerBounds({"width": width(d, i), "height": height(d, i)}, i),
              padding = labelPadding(d, i);
        bounds.height -= padding * 2;
        bounds.width -= padding * 2;
        bounds.x += padding;
        bounds.y += padding;

        box()
          .data([bounds])
          .delay(duration / 2)
          .duration(duration)
          .fontColor(fontColor(d, i))
          .fontFamily(fontFamily(d, i))
          .fontResize(fontResize(d, i))
          .fontSize(fontSize(d, i))
          .lineHeight(lineHeight(d, i))
          .textAnchor(textAnchor(d, i))
          .verticalAlign(verticalAlign(d, i))
          .select(this).text(label(d, i))();

      }

    });

    if (callback) setTimeout(callback, duration + 100);

    return rect;

  }

  /**
      @memberof rect
      @desc If *data* is specified, sets the data array to the specified array and returns this generator. If *data* is not specified, returns the current data array. A rectangle will be drawn for each object in the array.
      @param {Array} [*data* = []]
  */
  rect.data = function(_) {
    return arguments.length ? (data = _, rect) : data;
  };

  /**
      @memberof rect
      @desc If *ms* is specified, sets the animation duration to the specified number and returns this generator. If *ms* is not specified, returns the current animation duration.
      @param {Number} [*ms* = 600]
  */
  rect.duration = function(_) {
    return arguments.length ? (duration = _, rect) : duration;
  };

  /**
      @memberof rect
      @desc If *value* is specified, sets the fill accessor to the specified function or string and returns this generator. If *value* is not specified, returns the current fill accessor.
      @param {Function|String} [*value* = "black"]
  */
  rect.fill = function(_) {
    return arguments.length ? (fill = typeof _ === "function" ? _ : constant(_), rect) : fill;
  };

  /**
      @memberof rect
      @desc If *value* is specified, sets the font-color accessor to the specified function or string and returns this generator. If *value* is not specified, returns the current font-color accessor, which by default returns a color that contrasts the fill color.
      @param {Function|String} [*value*]
  */
  rect.fontColor = function(_) {
    return arguments.length ? (fontColor = typeof _ === "function" ? _ : constant(_), rect) : fontColor;
  };

  /**
      @memberof rect
      @desc If *value* is specified, sets the font-family accessor to the specified function or string and returns this generator. If *value* is not specified, returns the current font-family accessor.
      @param {Function|String} [*value*]
  */
  rect.fontFamily = function(_) {
    return arguments.length ? (fontFamily = typeof _ === "function" ? _ : constant(_), rect) : fontFamily;
  };

  /**
      @memberof rect
      @desc If *value* is specified, sets the font resizing accessor to the specified function or boolean and returns this generator. If *value* is not specified, returns the current font resizing accessor. When font resizing is enabled, the font-size of the value returned by [label](#rect.label) will be resized the best fit the rectangle.
      @param {Function|Boolean} [*value*]
  */
  rect.fontResize = function(_) {
    return arguments.length ? (fontResize = typeof _ === "function" ? _ : constant(_), rect) : fontResize;
  };

  /**
      @memberof rect
      @desc If *value* is specified, sets the font-size accessor to the specified function or string and returns this generator. If *value* is not specified, returns the current font-size accessor.
      @param {Function|String} [*value*]
  */
  rect.fontSize = function(_) {
    return arguments.length ? (fontSize = typeof _ === "function" ? _ : constant(_), rect) : fontSize;
  };

  /**
      @memberof rect
      @desc If *value* is specified, sets the height accessor to the specified function or number and returns this generator. If *value* is not specified, returns the current height accessor.
      @param {Function|Number} [*value*]
      @example
function(d) {
  return d.height;
}
  */
  rect.height = function(_) {
    return arguments.length ? (height = typeof _ === "function" ? _ : constant(_), rect) : height;
  };

  /**
      @memberof rect
      @desc If *value* is specified, sets the id accessor to the specified function and returns this generator. If *value* is not specified, returns the current id accessor.
      @param {Function} [*value*]
      @example
function(d) {
  return d.id;
}
  */
  rect.id = function(_) {
    return arguments.length ? (id = _, rect) : id;
  };

  /**
      @memberof rect
      @desc If *bounds* is specified, sets the inner bounds to the specified function and returns this generator. If *bounds* is not specified, returns the current inner bounds accessor.
      @example
function(shape) {
  return {
    "width": shape.width,
    "height": shape.height,
    "x": -shape.width / 2,
    "y": -shape.height / 2
  };
}
      @param {Function} [*bounds*] Given a rectangle's width and height, the function should return an object containing the following values: `width`, `height`, `x`, `y`.
  */
  rect.innerBounds = function(_) {
    return arguments.length ? (innerBounds = _, rect) : innerBounds;
  };

  /**
      @memberof rect
      @desc If *value* is specified, sets the label accessor to the specified function or string and returns this generator. If *value* is not specified, returns the current text accessor, which is `undefined` by default.
      @param {Function|String} [*value*]
  */
  rect.label = function(_) {
    return arguments.length ? (label = typeof _ === "function" ? _ : constant(_), rect) : label;
  };

  /**
      @memberof rect
      @desc If *value* is specified, sets the label padding to the specified number and returns this generator. If *value* is not specified, returns the current label padding.
      @param {Number} [*value* = 10]
  */
  rect.labelPadding = function(_) {
    return arguments.length ? (labelPadding = typeof _ === "function" ? _ : constant(_), rect) : labelPadding;
  };

  /**
      @memberof rect
      @desc If *value* is specified, sets the line-height accessor to the specified function or string and returns this generator. If *value* is not specified, returns the current line-height accessor.
      @param {Function|String} [*value*]
  */
  rect.lineHeight = function(_) {
    return arguments.length ? (lineHeight = typeof _ === "function" ? _ : constant(_), rect) : lineHeight;
  };

  /**
      @memberof rect
      @desc If *selector* is specified, sets the SVG container element to the specified d3 selector or DOM element and returns this generator. If *selector* is not specified, returns the current SVG container element.
      @param {String|HTMLElement} [*selector* = d3.select("body").append("svg")]
  */
  rect.select = function(_) {
    if (arguments.length) {
      select = d3.select(_);
      if (fontFamily === void 0) fontFamily = constant(select.style("font-family"));
      if (fontSize === void 0) fontSize = constant(parseFloat(select.style("font-size"), 10));
      return rect;
    }
    return select;
  };

  /**
      @memberof rect
      @desc If *value* is specified, sets the text-anchor accessor to the specified function or string and returns this generator. If *value* is not specified, returns the current text-anchor accessor, which is `"start"` by default. Accepted values are `"start"`, `"middle"`, and `"end"`.
      @param {Function|String} [*value* = "start"]
  */
  rect.textAnchor = function(_) {
    return arguments.length ? (textAnchor = typeof _ === "function" ? _ : constant(_), rect) : textAnchor;
  };

  /**
      @memberof rect
      @desc If *value* is specified, sets the vertical alignment accessor to the specified function or string and returns this generator. If *value* is not specified, returns the current vertical alignment accessor, which is `"top"` by default. Accepted values are `"top"`, `"middle"`, and `"bottom"`.
      @param {Function|String} [*value* = "start"]
  */
  rect.verticalAlign = function(_) {
    return arguments.length ? (verticalAlign = typeof _ === "function" ? _ : constant(_), rect) : verticalAlign;
  };

  /**
      @memberof rect
      @desc If *value* is specified, sets the width accessor to the specified function or number and returns this generator. If *value* is not specified, returns the current width accessor.
      @param {Function|Number} [*value*]
      @example
function(d) {
  return d.width;
}
  */
  rect.width = function(_) {
    return arguments.length ? (width = typeof _ === "function" ? _ : constant(_), rect) : width;
  };

  /**
      @memberof rect
      @desc If *value* is specified, sets the x accessor to the specified function or number and returns this generator. If *value* is not specified, returns the current x accessor. The number returned should correspond to the horizontal center of the rectangle.
      @param {Function|Number} [*value*]
      @example
function(d) {
  return d.x;
}
  */
  rect.x = function(_) {
    return arguments.length ? (x = typeof _ === "function" ? _ : constant(_), rect) : x;
  };

  /**
      @memberof rect
      @desc If *value* is specified, sets the y accessor to the specified function or number and returns this generator. If *value* is not specified, returns the current y accessor. The number returned should correspond to the vertical center of the rectangle.
      @param {Function|Number} [*value*]
      @example
function(d) {
  return d.y;
}
  */
  rect.y = function(_) {
    return arguments.length ? (y = typeof _ === "function" ? _ : constant(_), rect) : y;
  };

  return data.length ? rect() : rect;

}
<|endoftext|>


<commit_before>// THE GAME STATE
// It is handled by game.js.
// This is the actual play area of the game.
// [Example Game: You control the snake, eat apples and have lots of fun.
// When you die, you transition to the Game_Over state.]

var sky;
var ground;
var kirby;

var cursors;

var stars;
var fireballs;
var score = 0;
var scoreText;
var gameOverText;

var Game = {
	preload: function() {
		game.load.image('sky', './imgs/sky.png');
		game.load.image('ground', './imgs/ground.png');
		game.load.image('star', './imgs/star.png');
		game.load.image('fireball', './imgs/fireball.png');
		game.load.spritesheet('kirby', './imgs/kirby.png', 28.75, 26);
		game.load.spritesheet('hurt-kirby', './imgs/hurt-kirby.png', 29.85, 26);

		game.load.image('play-again', './imgs/play-again.png');
	},

	create: function() {
		game.physics.startSystem(Phaser.Physics.ARCADE);

		// the sky ========================================
		game.add.sprite(0, 0, 'sky');

		// the ground =====================================
		ground = game.add.sprite(0, 700, 'ground');
		game.physics.arcade.enable(ground);
		ground.body.immovable = true;

		// the kirby ======================================
		kirby = game.add.sprite(game.world.centerX, game.world.height - 250, 'kirby');
		kirby.frame = 11

		game.physics.arcade.enable(kirby);
		kirby.body.bounce.y = 0.5;
		kirby.body.gravity.y = 350;
		kirby.body.collideWorldBounds = true;

		kirby.animations.add('left', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 10, true);
		kirby.animations.add('right', [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22], 10, true);

		// the key controls ==============================
		cursors = game.input.keyboard.createCursorKeys();

		// the star ======================================
		stars = game.add.group();
		stars.enableBody = true;

		for (var i = 0; i < 20; i++) {
			var star = stars.create(i * 50, 0, 'star');
			star.body.bounce.y = 0.5 + Math.random() * 0.2;
			star.body.gravity.y = 50 + Math.random() * 100;
		}

		// the fireball ==================================
		fireballs = game.add.group();
		fireballs.enableBody = true;

		for (var i = 0; i < 5; i++) {
			var fireball = fireballs.create(i * 100, 0, 'fireball');
			fireball.body.bounce.y = 0.1 + Math.random() * 0.2;
			fireball.body.gravity.y = 25 + Math.random() * 100;
		}

		// the score =====================================
		scoreText = game.add.text(20, 20, 'Score: 0', { fontSize: '20px', fill: '#fff' })
		// gameOverText = game.add.text(350, game.world.height - 500, '', { fontSize: '50px', fill: '#fff' })
	},

	update: function() {
		game.physics.arcade.collide(kirby, ground);
		game.physics.arcade.collide(stars, ground);
		game.physics.arcade.collide(fireballs, ground);

		game.physics.arcade.overlap(kirby, stars, collectStar, null, this);
		game.physics.arcade.overlap(kirby, fireballs, fireballCollision, null, this);

		// reset kirby's velocity ========================
		kirby.body.velocity.x = 0;

		if (cursors.left.isDown) {
			kirby.body.velocity.x = -150;
			kirby.animations.play('left');
		}
		else if (cursors.right.isDown) {
			kirby.body.velocity.x = 150;
			kirby.animations.play('right');
		}
		else if (cursors.up.isDown && kirby.body.touching.down) {
			kirby.body.velocity.y = -350;
		}
		else {
			kirby.animations.stop();
			kirby.frame = 11;
		}

		// game over ====================================
		// this.gameOver;

	},

	// gameOver: function() {
	// 	if (starCount === 0) {
	// 		game.state.start('GameOver');
	// 	}
	// }
}

var collectStar = function(player, star) {
	star.kill()

	score += 5;
	scoreText.text = 'Score: ' + score;
}

var fireballCollision = function(player, fireball) {
	kirby.kill()
	hurtKirby = game.add.sprite(kirby.position.x, kirby.position.y, 'hurt-kirby');

	hurtKirby.animations.add('hurt', [0, 1, 2, 3, 4, 5, 6], 5, false);
	hurtKirby.animations.play('hurt');

	gameOverText = game.add.text(350, game.world.height - 500, 'GAME OVER', { fontSize: '50px', fill: '#fff' })
	// gameOverText.text = 'GAME OVER!';
	game.add.button(435, game.world.height - 450, 'play-again', this.restartGame, this);
}

var restartGame = function() {
	this.state.start('Game');
}<commit_msg>remove gameOverText variable since only called in collision function<commit_after>// THE GAME STATE
// It is handled by game.js.
// This is the actual play area of the game.
// [Example Game: You control the snake, eat apples and have lots of fun.
// When you die, you transition to the Game_Over state.]

var sky;
var ground;
var kirby;

var cursors;

var stars;
var fireballs;
var score = 0;
var scoreText;

var Game = {
	preload: function() {
		game.load.image('sky', './imgs/sky.png');
		game.load.image('ground', './imgs/ground.png');
		game.load.image('star', './imgs/star.png');
		game.load.image('fireball', './imgs/fireball.png');
		game.load.spritesheet('kirby', './imgs/kirby.png', 28.75, 26);
		game.load.spritesheet('hurt-kirby', './imgs/hurt-kirby.png', 29.85, 26);

		game.load.image('play-again', './imgs/play-again.png');
	},

	create: function() {
		game.physics.startSystem(Phaser.Physics.ARCADE);

		// the sky ========================================
		game.add.sprite(0, 0, 'sky');

		// the ground =====================================
		ground = game.add.sprite(0, 700, 'ground');
		game.physics.arcade.enable(ground);
		ground.body.immovable = true;

		// the kirby ======================================
		kirby = game.add.sprite(game.world.centerX, game.world.height - 250, 'kirby');
		kirby.frame = 11

		game.physics.arcade.enable(kirby);
		kirby.body.bounce.y = 0.5;
		kirby.body.gravity.y = 350;
		kirby.body.collideWorldBounds = true;

		kirby.animations.add('left', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 10, true);
		kirby.animations.add('right', [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22], 10, true);

		// the key controls ==============================
		cursors = game.input.keyboard.createCursorKeys();

		// the star ======================================
		stars = game.add.group();
		stars.enableBody = true;

		for (var i = 0; i < 20; i++) {
			var star = stars.create(i * 50, 0, 'star');
			star.body.bounce.y = 0.5 + Math.random() * 0.2;
			star.body.gravity.y = 50 + Math.random() * 100;
		}

		// the fireball ==================================
		fireballs = game.add.group();
		fireballs.enableBody = true;

		for (var i = 0; i < 5; i++) {
			var fireball = fireballs.create(i * 100, 0, 'fireball');
			fireball.body.bounce.y = 0.1 + Math.random() * 0.2;
			fireball.body.gravity.y = 25 + Math.random() * 100;
		}

		// the score =====================================
		scoreText = game.add.text(20, 20, 'Score: 0', { fontSize: '20px', fill: '#fff' })
	},

	update: function() {
		game.physics.arcade.collide(kirby, ground);
		game.physics.arcade.collide(stars, ground);
		game.physics.arcade.collide(fireballs, ground);

		game.physics.arcade.overlap(kirby, stars, collectStar, null, this);
		game.physics.arcade.overlap(kirby, fireballs, fireballCollision, null, this);

		// reset kirby's velocity ========================
		kirby.body.velocity.x = 0;

		if (cursors.left.isDown) {
			kirby.body.velocity.x = -150;
			kirby.animations.play('left');
		}
		else if (cursors.right.isDown) {
			kirby.body.velocity.x = 150;
			kirby.animations.play('right');
		}
		else if (cursors.up.isDown && kirby.body.touching.down) {
			kirby.body.velocity.y = -350;
		}
		else {
			kirby.animations.stop();
			kirby.frame = 11;
		}

		// game over ====================================
		// this.gameOver;

	},

	// gameOver: function() {
	// 	if (starCount === 0) {
	// 		game.state.start('GameOver');
	// 	}
	// }
}

var collectStar = function(player, star) {
	star.kill()

	score += 5;
	scoreText.text = 'Score: ' + score;
}

var fireballCollision = function(player, fireball) {
	kirby.kill()
	hurtKirby = game.add.sprite(kirby.position.x, kirby.position.y, 'hurt-kirby');

	hurtKirby.animations.add('hurt', [0, 1, 2, 3, 4, 5, 6], 5, false);
	hurtKirby.animations.play('hurt');

	game.add.text(350, game.world.height - 500, 'GAME OVER', { fontSize: '50px', fill: '#fff' })
	game.add.button(435, game.world.height - 450, 'play-again', this.restartGame, this);
}

var restartGame = function() {
	this.state.start('Game');
}<|endoftext|>


<commit_before>
(function(){
  Express = {
    version : '0.0.1',
    routes  : [],

    // --- Break

    Break : function(){
      this.toString = function() {
        return 'Express.Break'
      }
    },

    // --- Response

    response : {
      headers : {},
      cookie : {}
    },

    // --- Defaults

    defaults : {
      cookie : {
        maxAge : 3600
      },
      route : {
        callback : function() {
          Express.respond('Page or file cannot be found', 'Not Found')
        }
      }
    },

    hookCallbacks : {
      request : [

        // Cookie parsing

        function (request, response) {
          Express.response.cookie = request.cookie = Express.parseCookie(request.headers['Cookie'])
        }
      ],

      response : [

        // Content length

        function(request, response) {
          Express.header('Content-Length', (Express.response.body || '').length)
        }
      ]
    },

    /**
     * Invoke hook _name_ with _args_.
     *
     * @param  {string} name
     * @param  {...} args
     * @api public
     */

    hook : function(name, args) {
      if (this.hookCallbacks[name])
        for (i = 0, len = this.hookCallbacks[name].length; i < len; ++i)
          if (typeof this.hookCallbacks[name][i] == 'function')
              this.hookCallbacks[name][i].apply(this, this.argsArray(arguments, 1))
    },

    /**
     * Add middlware _callback_ for hook _name_.
     *
     * @param  {string} name
     * @param  {function} callback
     * @api public
     */

    use : function(name, callback) {
      Express.hookCallbacks[name].push(callback)
    },

    /**
     * Start express, binding to _host_ / _port_.
     *
     * @param {int} port
     * @param {string} host
     * @api public
     */

    start : function(port, host) {
      this.server.listen(port || 3000, function(request, response){
        Express.response.body = null
        Express.response.status = 200
        Express.request = request
        request.headers = Express.arrayToHash(request.headers)
        Express.hook('request', request, response)
        request.uri.params = Express.parseNestedParams(request.uri.params)
        request.uri.path = Express.normalizePath(request.uri.path)
        try { Express.response.body = Express.callRouteFor(request) }
        catch (e) { Express.fail(e) }
        Express.prepareCookie()
        Express.hook('response', Express.request, Express.response)
        response.sendHeader(Express.response.status, Express.hashToArray(Express.response.headers))
        response.sendBody(Express.response.body || '')
        response.finish()
      })
    },

    server : {

      /**
       * Listen to _host_ / _port_ with _callback_.
       *
       * @param {int} port
       * @param {string} host
       * @param {function} callback
       * @api private
       */

      listen : function(port, host, callback) {
        node.http.createServer(callback).listen(port, host)
        puts('Express started at http://' + (host || '127.0.0.1') + ':' + port + '/')
      }
    },

    /**
     * Fail Express with exception _e_.
     *
     * @param  {object} e
     * @api private
     */

    fail : function(e) {
      if (e.constructor == Express.Break) return
      this.header('Content-Type', 'text/html')
      this.response.body = e.message ? e.name + ': ' + e.message : e.toString()
      this.response.status = 500
    },

    prepareCookie : function() {
      var cookie = []
      for (var key in this.response.cookie)
        if (this.response.cookie.hasOwnProperty(key))
          cookie.push(key + '=' + this.response.cookie[key])
      if (cookie.length)
        this.header('Set-Cookie', cookie.join('; '))
    },

    /**
     * Return an associative array generated
     * from the key / value pairs in _hash_.
     *
     * @param  {hash} hash
     * @return {array}
     * @api public
     */

    hashToArray : function(hash) {
      var array = []
      for (var key in hash)
        if (hash.hasOwnProperty(key))
          array.push([key, hash[key]])
      return array
    },

    /**
     * Return hash generated from an
     * associative array.
     *
     * @param  {array} array
     * @return {hash}
     * @api public
     */

    arrayToHash : function(array) {
      var hash = {}
      for (i = 0, len = array.length; i < len; ++i)
        hash[array[i][0]] = array[i][1]
      return hash
    },

    /**
     * JSON encode _object_
     *
     * @param  {mixed} object
     * @return {string}
     * @api public
     */

    jsonEncode : function(object) {
      return JSON.stringify(object)
    },

    /**
     * JSON decode _string_.
     *
     * @param  {string} string
     * @return {mixed}
     * @api public
     */

     jsonDecode : function(string) {
       return JSON.parse(string)
     },

    /**
     * Redirect to _url_.
     *
     * @param  {string} url
     * @api public
     */

    redirect : function(url) {
      this.header('Location', url)
      this.respond("Page or file has moved to `" + url + "'", 'Moved Temporarily')
    },

    /**
     * Set response status to _value_. Where value may be
     * a case-insensitive string such as 'Not Found', 'forbidden',
     * or a numeric HTTP response status code.
     *
     * @param  {int, string} value
     * @return {int}
     * @api public
     */

    status : function(value) {
      if (value == undefined) return
      if (typeof value == 'number') return this.response.status = value
      for (var code in node.http.STATUS_CODES)
        if (node.http.STATUS_CODES.hasOwnProperty(code))
          if (node.http.STATUS_CODES[code].toLowerCase() == value.toLowerCase())
            return this.response.status = code
    },

    /**
     * Set response _status_ and _body_.
     *
     * @param  {string} body
     * @param  {string, int} status
     * @api public
     */

    respond : function(body, status) {
      this.response.body = body
      this.status(status)
      throw new Express.Break()
    },

    /**
     * Sets _name_ header to _value_. When _value_
     * is not present the request header will be returned.
     *
     * @param {string} name
     * @param {string} value
     * @api public
     */

    header : function(name, value) {
      return value ? this.response.headers[name] = value :
               this.request.headers[name] || this.response.headers[name]
    },

    /**
     * Return param _key_'s value or _defaultValue_ when
     * the param has not been set.
     *
     * @param  {string} key
     * @param  {mixed} defaultValue
     * @return {mixed}
     * @api public
     */

    param : function(key, defaultValue) {
      return this.request.uri.params[key] || defaultValue
    },

    /**
     * Return cookie _key_. When _value_ is present
     * then a response cookie field will be set.
     *
     * @param  {string} key
     * @param  {string} value
     * @return {string, hash}
     * @api public
     */

    cookie : function(key, value) {
      return value ? this.response.cookie[key] = value :
               this.response.cookie[key]
    },

    /**
     * Parse _params_, splitting { user[info][name] : 'tj' } into
     * { user : { info : { name : 'tj' }}}
     *
     * @param  {hash} params
     * @return {hash}
     * @api public
     */

    parseNestedParams : function(params) {
      for (var key in params)
        if (params.hasOwnProperty(key))
          if (parts = key.split('['))
            if (parts.length > 1)
              for (i = 0, prop = params, len = parts.length; i < len; ++i) {
                if (i == len - 1) {
                  var name = parts[i].replace(']', '')
                  prop[name] = params[key]
                  prop = params, delete params[key]
                }
                else {
                  var name = parts[i].replace(']', '')
                  prop = prop[name] = prop[name] || {}
                }
              }

      return params
    },

    /**
     * Parse _cookie_ string into an object (hash).
     *
     * @param  {string} cookie
     * @return {hash}
     * @api public
     */

    parseCookie : function(cookie) {
      var hash = {}
      if (!cookie) return hash
      var attrs = cookie.split(/\s*;\s*/)
      for (i = 0; i < attrs.length; ++i)
        hash[attrs[i].split('=')[0]] = unescape(attrs[i].split('=')[1])
      return hash
    },

    /**
     * Normalize a path. Strips trailing / leading whitespace
     * and slashes ('/').
     *
     * @param  {string} path
     * @return {string}
     * @api public
     */

    normalizePath : function(path) {
      if (typeof path != 'string') return path
      return path.replace(/^(\s|\/)*|(\s|\/)*$/g, '')
    },

    /**
     * Normalize _path_. When a RegExp it is simply returned,
     * otherwise a string is converted to a regular expression
     * surrounded by ^$. So /user/:id/edit would become:
     *
     * \^/user/(.*?)\/edit\$/
     *
     * Each param key (:id) will be captured and placed in the
     * params array, so param('id') would give the string captured.
     *
     * @param  {string} path
     * @return {regexp}
     * @api public
     */

    pathToRegexp : function(path) {
      Express.regexpKeys = []
      if (path.constructor == RegExp) return path
      path = path.replace(/:(\w+)/g, function(_, key){
        Express.regexpKeys.push(key)
        return '(.*?)'
      })
      return new RegExp('^' + this.escapeRegexp(path, '/ [ ]') + '$', 'i')
    },

    /**
     * Escape RegExp _chars_ in _string_. Where _chars_
     * defaults to regular expression special characters.
     *
     * _chars_ should be a space delimited list of characters,
     * for example '[ ] ( )'.
     *
     * @param  {string} string
     * @param  {string} chars
     * @return {Type}
     * @api public
     */

    escapeRegexp : function(string, chars) {
      var specials = (chars || '/ . * + ? | ( ) [ ] { } \\').split(' ').join('|\\')
      return string.replace(new RegExp('(\\' + specials + ')', 'g'), '\\$1')
    },

    /**
     * Return the contents of a function body.
     *
     * @param  {function} body
     * @return {string}
     * @api public
     */

    contentsOf : function(body) {
      return body.toString().match(/^[^\{]*{((.*\n*)*)}/m)[1]
    },

    /**
     * Eval _string_ in context of Express.
     *
     * @param  {string} string
     * @return {mixed}
     * @api public
     */

    eval : function(string) {
      return eval('with (Express){' + string + '}')
    },

    /**
     * Escape special characters in _html_.
     *
     * @param  {string} html
     * @return {string}
     * @api public
     */

    escape : function(html) {
      return html.
        replace(/&/g, '&').
        replace(/"/g, '"').
        replace(/</g, '<').
        replace(/>/g, '>')
    },

    /**
     * Convert native arguments object into an
     * array with optional _offset_.
     *
     * @param  {arguments} args
     * @param  {int} offset
     * @return {array}
     * @api public
     */

    argsArray : function(args, offset) {
      return Array.prototype.slice.call(args, offset)
    },

    /**
     * Check if _route_ matches the proper _request_ method.
     *
     * @param  {hash} route
     * @param  {hash} request
     * @return {bool}
     * @api private
     */

    routeMethod : function(route, request) {
      return route.method.toUpperCase() == request.method
    },

    /**
     * Check if _route_ matches provides the proper
     * encoding types listed in _request_'s Accept header.
     *
     * @param  {hash} route
     * @param  {hash} request
     * @return {bool}
     * @api private
     */

    routeProvides : function(route, request) {
      if (!route.options.provides) return true
      if (route.options.provides.constructor != Array)
        route.options.provides = [route.options.provides]
      for (i = 0, len = route.options.provides.length; i < len; ++i)
        if (request.headers['Accept'].match(route.options.provides[i]))
          return true
      return false
    },

    /**
     * Attept to match and call a route for
     * the given _request_, returning the data
     * returned by the route callback.
     *
     * @param  {object} request
     * @return {mixed}
     * @api private
     */

    callRouteFor : function(request) {
      var route = this.findRouteFor(request) || this.defaults.route
      if (route.keys)
        for (i = 0, len = route.keys.length; i < len; ++i)
          this.request.uri.params[route.keys[i]] = Express.captures[i+1] || null
      return this.eval(Express.contentsOf(route.callback)).toString()
    },

    /**
     * Attemp to find and return a route matching _request_.
     *
     * @param  {object} request
     * @return {object}
     * @api private
     */

    findRouteFor : function(request) {
      for (i = 0, len = this.routes.length; i < len; ++i)
        if (this.routeMatches(this.routes[i], request))
          return this.routes[i]
    },

    /**
     * Check if _route_ matches _request_
     *
     * @param  {object} route
     * @param  {object} request
     * @return {bool}
     * @api private
     */

    routeMatches : function(route, request) {
      // var conditions = [Express.routeMethod]
      // for (i = 0, len = conditions.length; i < len; ++i)
      //   if (!conditions[i](route, request))
      //     return false
      switch (route.path.constructor) {
        case String: return route.path == request.uri.path
        case RegExp: return !! (Express.captures = request.uri.path.match(route.path))
      }
    },

    /**
     * Return a routing function for _method_.
     *
     * @param  {string} method
     * @return {function}
     * @api private
     */

    routeFunctionFor : function(method) {
      return function(path, options, callback) {
        if (options.constructor == Function) callback = options, options = {}
        path = Express.pathToRegexp(Express.normalizePath(path))
        Express.routes.push({
          keys : Express.regexpKeys,
          path : path,
          method : method,
          options : options,
          callback : callback
        })
      }
    },

    readFile : function(path, callback, errorCallback) {
      var promise = node.fs.cat(path, "utf8")
      errorCallback = errorCallback || function(){
        throw "failed to read file `" + path + "'"
      }
      promise.addCallback(callback)
      promise.addErrback(errorCallback)
    },

    parseView : function(contents, context) {

    },

    display : function(path) {

    },

    Logger : function(request, response){
      puts('"' + request.method + ' /' + request.uri.path + ' HTTP/' + request.httpVersion + '" - ' + Express.response.status)
    }
  }

  get = Express.routeFunctionFor('get')
  post = Express.routeFunctionFor('post')
  del = Express.routeFunctionFor('delete')
  put = Express.routeFunctionFor('put')
  use = Express.use

})()

use('response', Express.Logger)

get('user/:name/:operation', function(){
 p(request.headers['User-Agent'])
 p(request.headers['Accept'])
 p(request.headers['Accept-Encoding'])
 p(request.headers['Cookie'])
 p(request.cookie)
 param('operation') + 'ing ' + param('name')
})

get('cookies', function(){
  cookie('foo', 'bar')
  cookie('some-thing', 'some value')
  cookie('max-age', '1000')
  p(request.headers)
  p(response.headers)
  'Wahoo!'
})

get('articles', function(){
  display('spec/data/example.html.js')
  'list of articles'
})

get(/articles\/(\d+)/, function(){
  redirect('http://google.com') // TODO: remove / fix
  header('Content-Type', 'text/html')
  'article id ' + captures[1]
})

get('articles/:name', function(){
  if (param('name') == 'tj') {
    respond('Halted execution')
    'hello tj'
  }
  else
    'article name ' + param('name')
})

Express.start()
<commit_msg>Fixed host<commit_after>
(function(){
  Express = {
    version : '0.0.1',
    routes  : [],

    // --- Break

    Break : function(){
      this.toString = function() {
        return 'Express.Break'
      }
    },

    // --- Response

    response : {
      headers : {},
      cookie : {}
    },

    // --- Defaults

    defaults : {
      cookie : {
        maxAge : 3600
      },
      route : {
        callback : function() {
          Express.respond('Page or file cannot be found', 'Not Found')
        }
      }
    },

    hookCallbacks : {
      request : [

        // Cookie parsing

        function (request, response) {
          Express.response.cookie = request.cookie = Express.parseCookie(request.headers['Cookie'])
        }
      ],

      response : [

        // Content length

        function(request, response) {
          Express.header('Content-Length', (Express.response.body || '').length)
        }
      ]
    },

    /**
     * Invoke hook _name_ with _args_.
     *
     * @param  {string} name
     * @param  {...} args
     * @api public
     */

    hook : function(name, args) {
      if (this.hookCallbacks[name])
        for (i = 0, len = this.hookCallbacks[name].length; i < len; ++i)
          if (typeof this.hookCallbacks[name][i] == 'function')
              this.hookCallbacks[name][i].apply(this, this.argsArray(arguments, 1))
    },

    /**
     * Add middlware _callback_ for hook _name_.
     *
     * @param  {string} name
     * @param  {function} callback
     * @api public
     */

    use : function(name, callback) {
      Express.hookCallbacks[name].push(callback)
    },

    /**
     * Start express, binding to _host_ / _port_.
     *
     * @param {int} port
     * @param {string} host
     * @api public
     */

    start : function(port, host) {
      this.server.listen(port || 3000, host, function(request, response){
        Express.response.body = null
        Express.response.status = 200
        Express.request = request
        request.headers = Express.arrayToHash(request.headers)
        Express.hook('request', request, response)
        request.uri.params = Express.parseNestedParams(request.uri.params)
        request.uri.path = Express.normalizePath(request.uri.path)
        try { Express.response.body = Express.callRouteFor(request) }
        catch (e) { Express.fail(e) }
        Express.prepareCookie()
        Express.hook('response', Express.request, Express.response)
        response.sendHeader(Express.response.status, Express.hashToArray(Express.response.headers))
        response.sendBody(Express.response.body || '')
        response.finish()
      })
    },

    server : {

      /**
       * Listen to _host_ / _port_ with _callback_.
       *
       * @param {int} port
       * @param {string} host
       * @param {function} callback
       * @api private
       */

      listen : function(port, host, callback) {
        node.http.createServer(callback).listen(port, host)
        puts('Express started at http://' + (host || '127.0.0.1') + ':' + port + '/')
      }
    },

    /**
     * Fail Express with exception _e_.
     *
     * @param  {object} e
     * @api private
     */

    fail : function(e) {
      if (e.constructor == Express.Break) return
      this.header('Content-Type', 'text/html')
      this.response.body = e.message ? e.name + ': ' + e.message : e.toString()
      this.response.status = 500
    },

    prepareCookie : function() {
      var cookie = []
      for (var key in this.response.cookie)
        if (this.response.cookie.hasOwnProperty(key))
          cookie.push(key + '=' + this.response.cookie[key])
      if (cookie.length)
        this.header('Set-Cookie', cookie.join('; '))
    },

    /**
     * Return an associative array generated
     * from the key / value pairs in _hash_.
     *
     * @param  {hash} hash
     * @return {array}
     * @api public
     */

    hashToArray : function(hash) {
      var array = []
      for (var key in hash)
        if (hash.hasOwnProperty(key))
          array.push([key, hash[key]])
      return array
    },

    /**
     * Return hash generated from an
     * associative array.
     *
     * @param  {array} array
     * @return {hash}
     * @api public
     */

    arrayToHash : function(array) {
      var hash = {}
      for (i = 0, len = array.length; i < len; ++i)
        hash[array[i][0]] = array[i][1]
      return hash
    },

    /**
     * JSON encode _object_
     *
     * @param  {mixed} object
     * @return {string}
     * @api public
     */

    jsonEncode : function(object) {
      return JSON.stringify(object)
    },

    /**
     * JSON decode _string_.
     *
     * @param  {string} string
     * @return {mixed}
     * @api public
     */

     jsonDecode : function(string) {
       return JSON.parse(string)
     },

    /**
     * Redirect to _url_.
     *
     * @param  {string} url
     * @api public
     */

    redirect : function(url) {
      this.header('Location', url)
      this.respond("Page or file has moved to `" + url + "'", 'Moved Temporarily')
    },

    /**
     * Set response status to _value_. Where value may be
     * a case-insensitive string such as 'Not Found', 'forbidden',
     * or a numeric HTTP response status code.
     *
     * @param  {int, string} value
     * @return {int}
     * @api public
     */

    status : function(value) {
      if (value == undefined) return
      if (typeof value == 'number') return this.response.status = value
      for (var code in node.http.STATUS_CODES)
        if (node.http.STATUS_CODES.hasOwnProperty(code))
          if (node.http.STATUS_CODES[code].toLowerCase() == value.toLowerCase())
            return this.response.status = code
    },

    /**
     * Set response _status_ and _body_.
     *
     * @param  {string} body
     * @param  {string, int} status
     * @api public
     */

    respond : function(body, status) {
      this.response.body = body
      this.status(status)
      throw new Express.Break()
    },

    /**
     * Sets _name_ header to _value_. When _value_
     * is not present the request header will be returned.
     *
     * @param {string} name
     * @param {string} value
     * @api public
     */

    header : function(name, value) {
      return value ? this.response.headers[name] = value :
               this.request.headers[name] || this.response.headers[name]
    },

    /**
     * Return param _key_'s value or _defaultValue_ when
     * the param has not been set.
     *
     * @param  {string} key
     * @param  {mixed} defaultValue
     * @return {mixed}
     * @api public
     */

    param : function(key, defaultValue) {
      return this.request.uri.params[key] || defaultValue
    },

    /**
     * Return cookie _key_. When _value_ is present
     * then a response cookie field will be set.
     *
     * @param  {string} key
     * @param  {string} value
     * @return {string, hash}
     * @api public
     */

    cookie : function(key, value) {
      return value ? this.response.cookie[key] = value :
               this.response.cookie[key]
    },

    /**
     * Parse _params_, splitting { user[info][name] : 'tj' } into
     * { user : { info : { name : 'tj' }}}
     *
     * @param  {hash} params
     * @return {hash}
     * @api public
     */

    parseNestedParams : function(params) {
      for (var key in params)
        if (params.hasOwnProperty(key))
          if (parts = key.split('['))
            if (parts.length > 1)
              for (i = 0, prop = params, len = parts.length; i < len; ++i) {
                if (i == len - 1) {
                  var name = parts[i].replace(']', '')
                  prop[name] = params[key]
                  prop = params, delete params[key]
                }
                else {
                  var name = parts[i].replace(']', '')
                  prop = prop[name] = prop[name] || {}
                }
              }

      return params
    },

    /**
     * Parse _cookie_ string into an object (hash).
     *
     * @param  {string} cookie
     * @return {hash}
     * @api public
     */

    parseCookie : function(cookie) {
      var hash = {}
      if (!cookie) return hash
      var attrs = cookie.split(/\s*;\s*/)
      for (i = 0; i < attrs.length; ++i)
        hash[attrs[i].split('=')[0]] = unescape(attrs[i].split('=')[1])
      return hash
    },

    /**
     * Normalize a path. Strips trailing / leading whitespace
     * and slashes ('/').
     *
     * @param  {string} path
     * @return {string}
     * @api public
     */

    normalizePath : function(path) {
      if (typeof path != 'string') return path
      return path.replace(/^(\s|\/)*|(\s|\/)*$/g, '')
    },

    /**
     * Normalize _path_. When a RegExp it is simply returned,
     * otherwise a string is converted to a regular expression
     * surrounded by ^$. So /user/:id/edit would become:
     *
     * \^/user/(.*?)\/edit\$/
     *
     * Each param key (:id) will be captured and placed in the
     * params array, so param('id') would give the string captured.
     *
     * @param  {string} path
     * @return {regexp}
     * @api public
     */

    pathToRegexp : function(path) {
      Express.regexpKeys = []
      if (path.constructor == RegExp) return path
      path = path.replace(/:(\w+)/g, function(_, key){
        Express.regexpKeys.push(key)
        return '(.*?)'
      })
      return new RegExp('^' + this.escapeRegexp(path, '/ [ ]') + '$', 'i')
    },

    /**
     * Escape RegExp _chars_ in _string_. Where _chars_
     * defaults to regular expression special characters.
     *
     * _chars_ should be a space delimited list of characters,
     * for example '[ ] ( )'.
     *
     * @param  {string} string
     * @param  {string} chars
     * @return {Type}
     * @api public
     */

    escapeRegexp : function(string, chars) {
      var specials = (chars || '/ . * + ? | ( ) [ ] { } \\').split(' ').join('|\\')
      return string.replace(new RegExp('(\\' + specials + ')', 'g'), '\\$1')
    },

    /**
     * Return the contents of a function body.
     *
     * @param  {function} body
     * @return {string}
     * @api public
     */

    contentsOf : function(body) {
      return body.toString().match(/^[^\{]*{((.*\n*)*)}/m)[1]
    },

    /**
     * Eval _string_ in context of Express.
     *
     * @param  {string} string
     * @return {mixed}
     * @api public
     */

    eval : function(string) {
      return eval('with (Express){' + string + '}')
    },

    /**
     * Escape special characters in _html_.
     *
     * @param  {string} html
     * @return {string}
     * @api public
     */

    escape : function(html) {
      return html.
        replace(/&/g, '&').
        replace(/"/g, '"').
        replace(/</g, '<').
        replace(/>/g, '>')
    },

    /**
     * Convert native arguments object into an
     * array with optional _offset_.
     *
     * @param  {arguments} args
     * @param  {int} offset
     * @return {array}
     * @api public
     */

    argsArray : function(args, offset) {
      return Array.prototype.slice.call(args, offset)
    },

    /**
     * Check if _route_ matches the proper _request_ method.
     *
     * @param  {hash} route
     * @param  {hash} request
     * @return {bool}
     * @api private
     */

    routeMethod : function(route, request) {
      return route.method.toUpperCase() == request.method
    },

    /**
     * Check if _route_ matches provides the proper
     * encoding types listed in _request_'s Accept header.
     *
     * @param  {hash} route
     * @param  {hash} request
     * @return {bool}
     * @api private
     */

    routeProvides : function(route, request) {
      if (!route.options.provides) return true
      if (route.options.provides.constructor != Array)
        route.options.provides = [route.options.provides]
      for (i = 0, len = route.options.provides.length; i < len; ++i)
        if (request.headers['Accept'].match(route.options.provides[i]))
          return true
      return false
    },

    /**
     * Attept to match and call a route for
     * the given _request_, returning the data
     * returned by the route callback.
     *
     * @param  {object} request
     * @return {mixed}
     * @api private
     */

    callRouteFor : function(request) {
      var route = this.findRouteFor(request) || this.defaults.route
      if (route.keys)
        for (i = 0, len = route.keys.length; i < len; ++i)
          this.request.uri.params[route.keys[i]] = Express.captures[i+1] || null
      return this.eval(Express.contentsOf(route.callback)).toString()
    },

    /**
     * Attemp to find and return a route matching _request_.
     *
     * @param  {object} request
     * @return {object}
     * @api private
     */

    findRouteFor : function(request) {
      for (i = 0, len = this.routes.length; i < len; ++i)
        if (this.routeMatches(this.routes[i], request))
          return this.routes[i]
    },

    /**
     * Check if _route_ matches _request_
     *
     * @param  {object} route
     * @param  {object} request
     * @return {bool}
     * @api private
     */

    routeMatches : function(route, request) {
      // var conditions = [Express.routeMethod]
      // for (i = 0, len = conditions.length; i < len; ++i)
      //   if (!conditions[i](route, request))
      //     return false
      switch (route.path.constructor) {
        case String: return route.path == request.uri.path
        case RegExp: return !! (Express.captures = request.uri.path.match(route.path))
      }
    },

    /**
     * Return a routing function for _method_.
     *
     * @param  {string} method
     * @return {function}
     * @api private
     */

    routeFunctionFor : function(method) {
      return function(path, options, callback) {
        if (options.constructor == Function) callback = options, options = {}
        path = Express.pathToRegexp(Express.normalizePath(path))
        Express.routes.push({
          keys : Express.regexpKeys,
          path : path,
          method : method,
          options : options,
          callback : callback
        })
      }
    },

    readFile : function(path, callback, errorCallback) {
      var promise = node.fs.cat(path, "utf8")
      errorCallback = errorCallback || function(){
        throw "failed to read file `" + path + "'"
      }
      promise.addCallback(callback)
      promise.addErrback(errorCallback)
    },

    parseView : function(contents, context) {

    },

    display : function(path) {

    },

    Logger : function(request, response){
      puts('"' + request.method + ' /' + request.uri.path + ' HTTP/' + request.httpVersion + '" - ' + Express.response.status)
    }
  }

  get = Express.routeFunctionFor('get')
  post = Express.routeFunctionFor('post')
  del = Express.routeFunctionFor('delete')
  put = Express.routeFunctionFor('put')
  use = Express.use

})()

use('response', Express.Logger)

get('/user/:name', function(){

  'User ' + param('name')
})

Express.start()
<|endoftext|>