<commit_before>'use strict'

let io = null
let connectionInfo
let pingInfo
let states = []

function setPingInfo(ping) {
    pingInfo.text = 'Ping: ' + ping
    let fill
    if (ping < 100)
        fill = 0x57fc20
    else if (ping < 200)
        fill = 0xfc9520
    else
        fill = 0xef1c2a
    pingInfo.style.fill = fill
}

let ownId
function connect(address) {
    io = new WebSocket(address)
    io.onopen = () => {
        resetWait()
        connectionInfo.visible = false
    };

    io.onmessage = (serializedMsg) => {
        const msg = JSON.parse(serializedMsg.data, (key, value) => value === "" ? 0 : value)

        switch (msg.opcode) {
            case 'Greeting':
                ownId = msg.payload[0]
                const actors = msg.payload[1]

                for (let actor of actors) {
                    spawnActor(actor)
                }
                break;
            case 'Connect':
                const player = {id: msg.payload.id, kind: "Player"}
                spawnActor(player)
                break;
            case 'Disconnect':
                removeActor(msg.payload)
                break;
            case 'WorldUpdate':
                const state = {}
                state.actors = msg.payload
                state.timestamp = Date.now()
                states.push(state)
                // Todo: Exchange for real queue library
                setTimeout(states.shift, 100)
                break;
            default:
                throw 'Received invalid opcode: ' + msg.opcode
        }
    }

    io.onclose = () => {
        connectionInfo.text = 'Attempting to reconnect'
        connectionInfo.visible = true
        io = null
        setTimeout(() => {
            incrementWait()
            connect(address);
        }, wait)
    };

    io.onerror = () => {
        connectionInfo.text = 'Lost connection to server'
        connectionInfo.visible = true
        for (let id of Object.keys(actors))
            removeActor(id)
        io.close()
        io = null
    };
}

const MIN_WAIT = 100
let wait = MIN_WAIT

function resetWait() {
    wait = MIN_WAIT
}

function incrementWait() {
    const MAX_WAIT = 2000
    if (wait < MAX_WAIT)
        wait *= 1.25
    else
        wait = MAX_WAIT
}

function send(data) {
    if (io && io.readyState === 1) {
        console.log('sending data: ', data)
        io.send(JSON.stringify(data))
    }
}

const Application = PIXI.Application,
    loader = PIXI.loader,
    resources = PIXI.loader.resources,
    Sprite = PIXI.Sprite

const GAME_WIDTH = 1000
const GAME_HEIGHT = 1000
const app = new Application(
    screen.availWidth, screen.availHeight, {
        backgroundColor: 0xFFFFFF,
        antialias: true,
    },
)

document.body.appendChild(app.view)

loader
    .add([{
        name: 'pong',
        url: 'assets/pong.json'
    }, ])
    .on('progress', loadProgressHandler)
    .load(setup)

let validKeys = ["ArrowUp", "ArrowDown"]
let keyPressed = {
    ArrowLeft: false,
    ArrowRight: false
}
document.addEventListener("keydown", (event) => {
    sendKeyWithVal(event.key, true)
})
document.addEventListener("keyup", (event) => {
    sendKeyWithVal(event.key, false)
})

function sendKeyWithVal(key, val) {
    if (validKeys.indexOf(key) > -1) {
        sendIfNew(key, val)
    }
}

let msgId = 0

function sendIfNew(key, val) {
    if (keyPressed[key] !== val) {
        keyPressed[key] = val
        let msg = {
            id: msgId,
            key: key,
            pressed: val
        }
        msgId++
        send(msg)
    }
}

function loadProgressHandler(loader, resource) {
    console.log('loading: ' + resource.name + ' (' + resource.url + ')')
    console.log('progress: ' + loader.progress + '%')
    if (resource.error)
        console.error(resource.error)
}

function setup() {
    const background = new Sprite(resources.pong.textures['fancy-court.png'])
    background.width = GAME_WIDTH
    background.height = GAME_HEIGHT
    app.stage.addChild(background)

    connectionInfo = new PIXI.Text('')
    connectionInfo.style.fill = 0xe3e3ed
    connectionInfo.style.dropShadow = true
    connectionInfo.style.dropShadowAlpha = 0.7
    connectionInfo.y = 30
    connectionInfo.x = GAME_WIDTH - 300
    app.stage.addChild(connectionInfo)

    pingInfo = new PIXI.Text('Ping: Calculating...')
    pingInfo.style.fill = 0xe3e3ed
    pingInfo.style.dropShadow = true
    pingInfo.style.dropShadowAlpha = 0.7
    pingInfo.y = 30
    pingInfo.x = 40
    app.stage.addChild(pingInfo)

    resize()
    window.addEventListener('resize', resize);

    setInterval(() => setPingInfo(getOwnPing()), 1000)
    const addr = window.location.hostname === 'localhost' ? 'ws://localhost:8081' : 'wss://beta.jnferner.com/socket'
    connect(addr)
    app.ticker.add(gameLoop)
}

function getOwnPing() {
    if (states.length === 0 || !ownId)
        return 0
    const players = states[states.length - 1].actors
    return players[ownId].delay
}

function resize() {
    let ratio = window.innerWidth / GAME_WIDTH
    if (GAME_HEIGHT * ratio > window.innerHeight)
        ratio = window.innerHeight / GAME_HEIGHT
    app.width = app.stage.width = GAME_WIDTH * ratio
    app.height = app.stage.height = GAME_HEIGHT * ratio
}

let state = connecting

function gameLoop(delta) {
    state()
}

function connecting() {
    const txt = 'Connecting...'
    if (connectionInfo.text != txt) {
        connectionInfo.text = txt
        connectionInfo.visible = true
    }

    const renderTime = getRenderTime(getOwnPing())
    const index = getIndexOfRenderState(states, renderTime)
    if (index >= 0) {
        connectionInfo.visible = false
        state = play
    }
}

function play() {
    render(states)
}

function render(states) {
    const renderTime = getRenderTime(getOwnPing())
    const index = getIndexOfRenderState(states, renderTime)
    if (index < 0)
        return
    let interpolatedState = getInterpolatedState(states[index], states[index + 1], renderTime)
    setWorld(interpolatedState)
}

function getRenderTime(ping) {
    const now = new Date().getTime()
    const minDelay = 100
    const maxDelay = 300
    const delay = Math.max(minDelay, Math.min(maxDelay, ping))
    return now - delay
}

function getIndexOfRenderState(states, renderTime) {
    const found = states.findIndex((state) => state.timestamp >= renderTime)
    return found - 1
}

function getInterpolatedState(from, to, renderTime) {
    const total = to.timestamp - from.timestamp
    const progress = renderTime - from.timestamp
    if (total === 0 || progress === 0)
        return from
    const fraction = progress / total
    let state = JSON.parse(JSON.stringify(from)).actors
    for (let id of Object.keys(state)) {
        const actor = state[id]
        const toActor = to[id]
        if (!toActor)
            continue
        const fromActor = from[id]

        actor.pos.x += (toActor.pos.x - fromActor.pos.x) * fraction
        actor.pos.y += (toActor.pos.y - fromActor.pos.y) * fraction
        actor.vel.x += (toActor.vel.x - fromActor.vel.x) * fraction
        actor.vel.y += (toActor.vel.y - fromActor.vel.y) * fraction
    }
    state.timestamp = renderTime
    return state
}

let actors = {}
function setWorld(state) {
    for (let id of Object.keys(state)) {
        const liveActor = actors[id]
        const stateActor = state[id]
        if (!liveActor || !stateActor)
            continue
        liveActor.x = stateActor.pos.x
        liveActor.y = stateActor.pos.y
        if (stateActor.vel)
            addBlur(liveActor, stateActor.vel)
    }
}

function addBlur(obj, vel) {
    const maxVel = Math.max(vel.x, vel.y)
    const strength = Math.pow(Math.atan(Math.pow((maxVel / 10), 1.5)), 2) - 0.2
    if (strength > 0.5) {
        const blurFilter = new PIXI.filters.BlurFilter(strength, 1, 1)
        obj.filters = [blurFilter]
    }
}

function spawnActor(actor) {
    let texture
    switch (actor.kind) {
        case "Player":
            texture = 'fancy-paddle-green.png'
            break;
        case "Ball":
            texture = 'fancy-ball.png'
            break;
        default:
            throw 'Tried to spawn invalid kind of actor: ' + actor.kind
    }
    const sprite = new Sprite(resources.pong.textures[texture])
    sprite.anchor.set(0.5)
    app.stage.addChild(sprite)
    actors[actor.id] = sprite
}

function removeActor(id) {
    const actor = actors[id]
    app.stage.removeChild(actor)
    delete actors[id]
}<commit_msg>Client: Use server timestamp instead of own for interpolation<commit_after>'use strict'

let io = null
let connectionInfo
let pingInfo
let states = []

function setPingInfo(ping) {
    pingInfo.text = 'Ping: ' + ping
    let fill
    if (ping < 100)
        fill = 0x57fc20
    else if (ping < 200)
        fill = 0xfc9520
    else
        fill = 0xef1c2a
    pingInfo.style.fill = fill
}

let ownId
function connect(address) {
    io = new WebSocket(address)
    io.onopen = () => {
        resetWait()
        connectionInfo.visible = false
    };

    io.onmessage = (serializedMsg) => {
        const msg = JSON.parse(serializedMsg.data, (key, value) => value === "" ? 0 : value)

        switch (msg.opcode) {
            case 'Greeting':
                ownId = msg.payload[0]
                const actors = msg.payload[1]

                for (let actor of actors) {
                    spawnActor(actor)
                }
                break;
            case 'Connect':
                const player = {id: msg.payload.id, kind: "Player"}
                spawnActor(player)
                break;
            case 'Disconnect':
                removeActor(msg.payload)
                break;
            case 'WorldUpdate':
                const state = {}
                state.actors = msg.payload
                state.timestamp = msg.timestamp
                states.push(state)
                // Todo: Exchange for real queue library
                setTimeout(states.shift, 100)
                break;
            default:
                throw 'Received invalid opcode: ' + msg.opcode
        }
    }

    io.onclose = () => {
        connectionInfo.text = 'Attempting to reconnect'
        connectionInfo.visible = true
        io = null
        setTimeout(() => {
            incrementWait()
            connect(address);
        }, wait)
    };

    io.onerror = () => {
        connectionInfo.text = 'Lost connection to server'
        connectionInfo.visible = true
        for (let id of Object.keys(actors))
            removeActor(id)
        io.close()
        io = null
    };
}

const MIN_WAIT = 100
let wait = MIN_WAIT

function resetWait() {
    wait = MIN_WAIT
}

function incrementWait() {
    const MAX_WAIT = 2000
    if (wait < MAX_WAIT)
        wait *= 1.25
    else
        wait = MAX_WAIT
}

function send(data) {
    if (io && io.readyState === 1) {
        console.log('sending data: ', data)
        io.send(JSON.stringify(data))
    }
}

const Application = PIXI.Application,
    loader = PIXI.loader,
    resources = PIXI.loader.resources,
    Sprite = PIXI.Sprite

const GAME_WIDTH = 1000
const GAME_HEIGHT = 1000
const app = new Application(
    screen.availWidth, screen.availHeight, {
        backgroundColor: 0xFFFFFF,
        antialias: true,
    },
)

document.body.appendChild(app.view)

loader
    .add([{
        name: 'pong',
        url: 'assets/pong.json'
    }, ])
    .on('progress', loadProgressHandler)
    .load(setup)

let validKeys = ["ArrowUp", "ArrowDown"]
let keyPressed = {
    ArrowLeft: false,
    ArrowRight: false
}
document.addEventListener("keydown", (event) => {
    sendKeyWithVal(event.key, true)
})
document.addEventListener("keyup", (event) => {
    sendKeyWithVal(event.key, false)
})

function sendKeyWithVal(key, val) {
    if (validKeys.indexOf(key) > -1) {
        sendIfNew(key, val)
    }
}

let msgId = 0

function sendIfNew(key, val) {
    if (keyPressed[key] !== val) {
        keyPressed[key] = val
        let msg = {
            id: msgId,
            key: key,
            pressed: val
        }
        msgId++
        send(msg)
    }
}

function loadProgressHandler(loader, resource) {
    console.log('loading: ' + resource.name + ' (' + resource.url + ')')
    console.log('progress: ' + loader.progress + '%')
    if (resource.error)
        console.error(resource.error)
}

function setup() {
    const background = new Sprite(resources.pong.textures['fancy-court.png'])
    background.width = GAME_WIDTH
    background.height = GAME_HEIGHT
    app.stage.addChild(background)

    connectionInfo = new PIXI.Text('')
    connectionInfo.style.fill = 0xe3e3ed
    connectionInfo.style.dropShadow = true
    connectionInfo.style.dropShadowAlpha = 0.7
    connectionInfo.y = 30
    connectionInfo.x = GAME_WIDTH - 300
    app.stage.addChild(connectionInfo)

    pingInfo = new PIXI.Text('Ping: Calculating...')
    pingInfo.style.fill = 0xe3e3ed
    pingInfo.style.dropShadow = true
    pingInfo.style.dropShadowAlpha = 0.7
    pingInfo.y = 30
    pingInfo.x = 40
    app.stage.addChild(pingInfo)

    resize()
    window.addEventListener('resize', resize);

    setInterval(() => setPingInfo(getOwnPing()), 1000)
    const addr = window.location.hostname === 'localhost' ? 'ws://localhost:8081' : 'wss://beta.jnferner.com/socket'
    connect(addr)
    app.ticker.add(gameLoop)
}

function getOwnPing() {
    if (states.length === 0 || !ownId)
        return 0
    const players = states[states.length - 1].actors
    return players[ownId].delay
}

function resize() {
    let ratio = window.innerWidth / GAME_WIDTH
    if (GAME_HEIGHT * ratio > window.innerHeight)
        ratio = window.innerHeight / GAME_HEIGHT
    app.width = app.stage.width = GAME_WIDTH * ratio
    app.height = app.stage.height = GAME_HEIGHT * ratio
}

let state = connecting

function gameLoop(delta) {
    state()
}

function connecting() {
    const txt = 'Connecting...'
    if (connectionInfo.text != txt) {
        connectionInfo.text = txt
        connectionInfo.visible = true
    }

    const renderTime = getRenderTime(getOwnPing())
    const index = getIndexOfRenderState(states, renderTime)
    if (index >= 0) {
        connectionInfo.visible = false
        state = play
    }
}

function play() {
    render(states)
}

function render(states) {
    const renderTime = getRenderTime(getOwnPing())
    const index = getIndexOfRenderState(states, renderTime)
    if (index < 0)
        return
    let interpolatedState = getInterpolatedState(states[index], states[index + 1], renderTime)
    setWorld(interpolatedState)
}

function getRenderTime(ping) {
    const now = new Date().getTime()
    const minDelay = 100
    const maxDelay = 300
    const delay = Math.max(minDelay, Math.min(maxDelay, ping))
    return now - delay
}

function getIndexOfRenderState(states, renderTime) {
    const found = states.findIndex((state) => state.timestamp >= renderTime)
    return found - 1
}

function getInterpolatedState(from, to, renderTime) {
    const total = to.timestamp - from.timestamp
    const progress = renderTime - from.timestamp
    if (total === 0 || progress === 0)
        return from
    const fraction = progress / total
    let state = JSON.parse(JSON.stringify(from)).actors
    for (let id of Object.keys(state)) {
        const actor = state[id]
        const toActor = to[id]
        if (!toActor)
            continue
        const fromActor = from[id]

        actor.pos.x += (toActor.pos.x - fromActor.pos.x) * fraction
        actor.pos.y += (toActor.pos.y - fromActor.pos.y) * fraction
        actor.vel.x += (toActor.vel.x - fromActor.vel.x) * fraction
        actor.vel.y += (toActor.vel.y - fromActor.vel.y) * fraction
    }
    state.timestamp = renderTime
    return state
}

let actors = {}
function setWorld(state) {
    for (let id of Object.keys(state)) {
        const liveActor = actors[id]
        const stateActor = state[id]
        if (!liveActor || !stateActor)
            continue
        liveActor.x = stateActor.pos.x
        liveActor.y = stateActor.pos.y
        if (stateActor.vel)
            addBlur(liveActor, stateActor.vel)
    }
}

function addBlur(obj, vel) {
    const maxVel = Math.max(vel.x, vel.y)
    const strength = Math.pow(Math.atan(Math.pow((maxVel / 10), 1.5)), 2) - 0.2
    if (strength > 0.5) {
        const blurFilter = new PIXI.filters.BlurFilter(strength, 1, 1)
        obj.filters = [blurFilter]
    }
}

function spawnActor(actor) {
    let texture
    switch (actor.kind) {
        case "Player":
            texture = 'fancy-paddle-green.png'
            break;
        case "Ball":
            texture = 'fancy-ball.png'
            break;
        default:
            throw 'Tried to spawn invalid kind of actor: ' + actor.kind
    }
    const sprite = new Sprite(resources.pong.textures[texture])
    sprite.anchor.set(0.5)
    app.stage.addChild(sprite)
    actors[actor.id] = sprite
}

function removeActor(id) {
    const actor = actors[id]
    app.stage.removeChild(actor)
    delete actors[id]
}<|endoftext|>


<commit_before>var config = {};
config.store = {};
config.annotator = {};
config.elastico = {};

// postgres db
config.postgres = process.env.DATABASE_URL || 'postgres://<username>:<password>@<hostname/ip addresss>:<port>/<schema name>';

// dbmi annotator
config.annotator.host = 'host name or ip address';
config.annotator.port = 'port';

// annotator store
config.store.host = 'host name or ip address';
config.store.port = 'port';

// elastico
config.elastico.host = 'host name or ip address';
config.elastico.port = 'port';

// user default profile
config.profile.def = 'MP';
config.profile.pluginSetL = [];
config.profile.userProfile = {};

module.exports = config;
<commit_msg>trivial<commit_after>var config = {};
config.store = {};
config.annotator = {};
config.elastico = {};
config.profile = {};

// postgres db
config.postgres = process.env.DATABASE_URL || 'postgres://<username>:<password>@<hostname/ip addresss>:<port>/<schema name>';

// dbmi annotator
config.annotator.host = 'host name or ip address';
config.annotator.port = 'port';

// annotator store
config.store.host = 'host name or ip address';
config.store.port = 'port';

// elastico
config.elastico.host = 'host name or ip address';
config.elastico.port = 'port';

// user default profile
config.profile.def = 'MP';
config.profile.pluginSetL = [];
config.profile.userProfile = {};

module.exports = config;
<|endoftext|>


<commit_before>'use strict'

// ===================================================================

var Bluebird = require('bluebird')
var EventEmitter = require('events').EventEmitter
var inherits = require('util').inherits
var jsonRpc = require('json-rpc')
var MethodNotFound = require('json-rpc/errors').MethodNotFound
var startsWith = require('lodash.startswith')
var WebSocket = require('ws')

var ConnectionError = require('./connection-error')
var fixUrl = require('./fix-url')

// ===================================================================

function getCurrentUrl () {
  /* global window: false */

  if (typeof window === undefined) {
    throw new Error('cannot get current URL')
  }

  return String(window.location)
}

function makeDeferred () {
  var resolve, reject
  var promise = new Bluebird(function (resolve_, reject_) {
    resolve = resolve_
    reject = reject_
  })

  return {
    promise: promise,
    reject: reject,
    resolve: resolve
  }
}

// -------------------------------------------------------------------

// Low level interface to XO.
function Api (url) {
  // Super constructor.
  EventEmitter.call(this)

  // Fix the URL (ensure correct protocol and /api/ path).
  this._url = fixUrl(url || getCurrentUrl())

  // Will contains the WebSocket.
  this._socket = null

  // The JSON-RPC server.
  var this_ = this
  this._jsonRpc = jsonRpc.createServer(function (message) {
    if (message.type === 'notification') {
      this_.emit('notification', message)
    } else {
      // This object does not support requests.
      throw new MethodNotFound(message.method)
    }
  }).on('data', function (message) {
    this_._socket.send(JSON.stringify(message))
  })
}
inherits(Api, EventEmitter)

Api.prototype.close = function () {
  if (this._socket) {
    this._socket.close()
  }
}

Api.prototype.connect = Bluebird.method(function () {
  if (this._socket) {
    return
  }

  var deferred = makeDeferred()

  var opts = {}
  if (startsWith(this._url, 'wss')) {
    // Due to imperfect TLS implementation in XO-Server.
    opts.rejectUnauthorized = false
  }
  var socket = this._socket = new WebSocket(this._url, '', opts)

  // Used to avoid binding listeners to this object.
  var this_ = this

  // When the socket opens, send any queued requests.
  socket.addEventListener('open', function () {
    // Resolves the promise.
    deferred.resolve()

    this_.emit('connected')
  })

  socket.addEventListener('message', function (message) {
    this_._jsonRpc.write(message.data)
  })

  socket.addEventListener('close', function () {
    this_._socket = null

    this_._jsonRpc.failPendingRequests(new ConnectionError())

    // Only emit this event if connected before.
    if (deferred.promise.isFulfilled()) {
      this_.emit('disconnected')
    }
  })

  socket.addEventListener('error', function (error) {
    // Fails the connect promise if possible.
    deferred.reject(error)
  })

  return deferred.promise
})

Api.prototype.call = function (method, params) {
  var jsonRpc = this._jsonRpc

  return this.connect().then(function () {
    return jsonRpc.request(method, params)
  })
}

module.exports = Api
<commit_msg>Fix missing quotes.<commit_after>'use strict'

// ===================================================================

var Bluebird = require('bluebird')
var EventEmitter = require('events').EventEmitter
var inherits = require('util').inherits
var jsonRpc = require('json-rpc')
var MethodNotFound = require('json-rpc/errors').MethodNotFound
var startsWith = require('lodash.startswith')
var WebSocket = require('ws')

var ConnectionError = require('./connection-error')
var fixUrl = require('./fix-url')

// ===================================================================

function getCurrentUrl () {
  /* global window: false */

  if (typeof window === 'undefined') {
    throw new Error('cannot get current URL')
  }

  return String(window.location)
}

function makeDeferred () {
  var resolve, reject
  var promise = new Bluebird(function (resolve_, reject_) {
    resolve = resolve_
    reject = reject_
  })

  return {
    promise: promise,
    reject: reject,
    resolve: resolve
  }
}

// -------------------------------------------------------------------

// Low level interface to XO.
function Api (url) {
  // Super constructor.
  EventEmitter.call(this)

  // Fix the URL (ensure correct protocol and /api/ path).
  this._url = fixUrl(url || getCurrentUrl())

  // Will contains the WebSocket.
  this._socket = null

  // The JSON-RPC server.
  var this_ = this
  this._jsonRpc = jsonRpc.createServer(function (message) {
    if (message.type === 'notification') {
      this_.emit('notification', message)
    } else {
      // This object does not support requests.
      throw new MethodNotFound(message.method)
    }
  }).on('data', function (message) {
    this_._socket.send(JSON.stringify(message))
  })
}
inherits(Api, EventEmitter)

Api.prototype.close = function () {
  if (this._socket) {
    this._socket.close()
  }
}

Api.prototype.connect = Bluebird.method(function () {
  if (this._socket) {
    return
  }

  var deferred = makeDeferred()

  var opts = {}
  if (startsWith(this._url, 'wss')) {
    // Due to imperfect TLS implementation in XO-Server.
    opts.rejectUnauthorized = false
  }
  var socket = this._socket = new WebSocket(this._url, '', opts)

  // Used to avoid binding listeners to this object.
  var this_ = this

  // When the socket opens, send any queued requests.
  socket.addEventListener('open', function () {
    // Resolves the promise.
    deferred.resolve()

    this_.emit('connected')
  })

  socket.addEventListener('message', function (message) {
    this_._jsonRpc.write(message.data)
  })

  socket.addEventListener('close', function () {
    this_._socket = null

    this_._jsonRpc.failPendingRequests(new ConnectionError())

    // Only emit this event if connected before.
    if (deferred.promise.isFulfilled()) {
      this_.emit('disconnected')
    }
  })

  socket.addEventListener('error', function (error) {
    // Fails the connect promise if possible.
    deferred.reject(error)
  })

  return deferred.promise
})

Api.prototype.call = function (method, params) {
  var jsonRpc = this._jsonRpc

  return this.connect().then(function () {
    return jsonRpc.request(method, params)
  })
}

module.exports = Api
<|endoftext|>


<commit_before>module.exports = {
  WIDTH: 15,
  HEIGHT: 20,
  LEFT_WALL: 1,
  RIGHT_WALL: 1,
  BOTTOM_WALL: 1,
  START_RATE: 800,
  MIN_RATE: 400,
  DEBUG: true,
  TILE_SIZE: {
    x: 32,
    y: 32
  },
  getDropRate: function(totalDelta) {
    var rate = this.START_RATE - Math.pow(0.22*totalDelta, 2);
    return rate > this.MIN_RATE ? rate : this.MIN_RATE;
  }
};
<commit_msg>Disable debug.<commit_after>module.exports = {
  WIDTH: 15,
  HEIGHT: 20,
  LEFT_WALL: 1,
  RIGHT_WALL: 1,
  BOTTOM_WALL: 1,
  START_RATE: 800,
  MIN_RATE: 400,
  DEBUG: false,
  TILE_SIZE: {
    x: 32,
    y: 32
  },
  getDropRate: function(totalDelta) {
    var rate = this.START_RATE - Math.pow(0.22*totalDelta, 2);
    return rate > this.MIN_RATE ? rate : this.MIN_RATE;
  }
};
<|endoftext|>


<commit_before>const AkairoModule = require('./AkairoModule');
const Argument = require('./Argument');
const { ArgumentMatches, ArgumentSplits } = require('../util/Constants');

/**
 * Options to use for command execution behavior.
 * @typedef {Object} CommandOptions
 * @prop {string[]} [aliases=[]] - Command names.
 * @prop {ArgumentOptions[]} [args=[]] - Arguments to parse.
 * @prop {ArgumentSplit} [split='plain'] - Method to split text into words.
 * @prop {string} [channelRestriction] - Restricts channel: 'guild' or 'dm'.
 * @prop {string} [category='default'] - Category ID for organization purposes.
 * @prop {boolean} [ownerOnly=false] - Whether or not to allow client owner(s) only.
 * @prop {boolean} [protected=false] - Whether or not this command cannot be disabled.
 * @prop {boolean} [typing=false] - Whether or not to type in channel during execution.
 * @prop {boolean} [editable=true] - Whether or not message edits will run this command.
 * On an edited message, the exec function edited param will be true.
 * @prop {number} [cooldown] - The command cooldown in milliseconds.
 * @prop {number} [ratelimit=1] - Amount of command uses allowed until cooldown.
 * @prop {string|string[]|Function} [prefix] - A prefix to overwrite the global one for this command.
 * Can be a function `(message => string|string[])`.
 * @prop {PermissionResolvable[]|Function} [userPermissions] - Permissions required by the user to run this command.
 * Can be either an array of permission name or a function returning `true or `false`.
 * @prop {PermissionResolvable[]|Function} [clientPermissions] - Permissions required by the client to run this command.
 * Can be either an array of permission name or a function returning `true or `false`.
 * @prop {RegExp|Function} [trigger] - A regex or function `(message => RegExp)` returning regex to match in messages that are NOT commands.
 * The exec function becomes `((message, match, groups, edited) => any)`.
 * @prop {Function} [condition] - A function `((message, edited) => {})` that returns true or false on messages that are NOT commands.
 * The exec function becomes `((message, edited) => any)`.
 * @prop {PromptOptions} [defaultPrompt={}] - The default prompt options.
 * @prop {Object} [options={}] - An object for custom options.
 * Accessible with `Command#options`.
 * @prop {string|string[]} [description=''] - Description of the command.
 */

/**
 * The method to split text into words.
 * - `plain` splits word separated by whitespace.
 * Extra whitespace between words are ignored.
 * - `split` splits word separated by whitespace.
 * Should not be used due to possible inconsistent whitespace.
 * - `quoted` is similar to plain, but counts text inside double quotes as one word.
 * - `sticky` is similar to quoted, but makes it so that quoted text must have a whitespace or another double quote before it to count as another word.
 * - `none` gives the entire content.
 *
 * A regex or a character can be used instead (for example, a comma) to split the message by that regex or character.
 * A function `((content, message) => string[])` can also be used, returning an array of strings.
 * @typedef {string|RegExp} ArgumentSplit
 */

/** @extends AkairoModule */
class Command extends AkairoModule {
    /**
     * Creates a new command.
     * @param {string} id - Command ID.
     * @param {Function} exec - Function `((message, args, edited) => any)` called when command is ran.
     * @param {CommandOptions} [options={}] - Options for the command.
     */
    constructor(id, exec, options) {
        if (!options && typeof exec === 'object') {
            options = exec;
            exec = null;
        }

        super(id, exec, options);

        /**
         * Command names.
         * @type {string[]}
         */
        this.aliases = options.aliases || [];

        /**
         * Arguments for the command.
         * @type {Argument[]}
         */
        this.args = (options.args || []).map(a => new Argument(this, a));

        /**
         * The command split method.
         * @type {ArgumentSplit}
         */
        this.split = options.split || this.split || ArgumentSplits.PLAIN;

        /**
         * Usable only in this channel type.
         * @type {string}
         */
        this.channelRestriction = options.channelRestriction;

        /**
         * Usable only by the client owner.
         * @type {boolean}
         */
        this.ownerOnly = !!options.ownerOnly;

        /**
         * Whether or not this command cannot be disabled.
         * @type {boolean}
         */
        this.protected = !!options.protected;

        /**
         * Whether or not this command can be ran by an edit.
         * @type {boolean}
         */
        this.editable = options.editable === undefined ? true : !!options.editable;

        /**
         * Whether or not to type during command execution.
         * @type {boolean}
         */
        this.typing = !!options.typing;

        /**
         * Cooldown in milliseconds.
         * @type {number}
         */
        this.cooldown = options.cooldown;

        /**
         * Uses allowed before cooldown.
         * @type {number}
         */
        this.ratelimit = options.ratelimit || 1;

        /**
         * Default prompt options.
         * @type {PromptOptions}
         */
        this.defaultPrompt = options.defaultPrompt || {};

        /**
         * Custom options for the command.
         * @type {Object}
         */
        this.options = options.custom || options.options || {};

        /**
         * Description of the command.
         * @type {string}
         */
        this.description = (Array.isArray(options.description) ? options.description.join('\n') : options.description) || '';

        /**
         * Command prefix overwrite.
         * @type {?string|string[]|Function}
         */
        this.prefix = options.prefix || this.prefix;

        /**
         * Permissions required to run command by the client.
         * @type {PermissionResolvable[]|Function}
         */
        this.clientPermissions = options.clientPermissions || this.clientPermissions;

        /**
         * Permissions required to run command by the user.
         * @type {PermissionResolvable[]|Function}
         */
        this.userPermissions = options.userPermissions || this.userPermissions;

        /**
         * Gets the regex trigger, if specified.
         * @method
         * @param {Message} message - Message being handled.
         * @returns {RegExp}
         */
        this.trigger = typeof options.trigger === 'function' ? options.trigger : this.trigger || (() => options.trigger);

        /**
         * Gets the condition trigger, if specified.
         * @method
         * @param {Message} message - Message being handled.
         * @returns {boolean}
         */
        this.condition = options.condition || this.condition || (() => false);

        /**
         * The ID of this command.
         * @name Command#id
         * @type {string}
         */

        /**
         * Executes the command.
         * @method
         * @name Command#exec
         * @returns {any}
         */

        /**
         * The command handler.
         * @readonly
         * @name Command#handler
         * @type {CommandHandler}
         */
    }

    /**
     * Parses text based on this command's args.
     * @param {string} content - String to parse.
     * @param {Message} [message] - Message to use.
     * @returns {Promise<Object>}
     */
    parse(content, message) {
        if (!this.args.length) return Promise.resolve({});

        const splitFuncs = {
            [ArgumentSplits.PLAIN]: c => c.match(/[^\s]+/g),
            [ArgumentSplits.SPLIT]: c => c.split(' '),
            [ArgumentSplits.QUOTED]: c => c.match(/".*?"|\s?[^\s"]+|"\s?/g),
            [ArgumentSplits.STICKY]: c => c.match(/[^\s"]*?".*?"|\s?[^\s"]+\s?|"/g),
            [ArgumentSplits.NONE]: c => [c]
        };

        const isQuoted = this.split === ArgumentSplits.QUOTED || this.split === ArgumentSplits.STICKY;

        const words = typeof this.split === 'function'
        ? this.split(content, message) || []
        : splitFuncs[this.split]
        ? splitFuncs[this.split](content) || []
        : content.split(this.split);

        const prefixes = this.args.reduce((arr, arg) => {
            if (arg.match === ArgumentMatches.PREFIX || arg.match === ArgumentMatches.FLAG) {
                if (Array.isArray(arg.prefix)) {
                    for (const p of arg.prefix) {
                        arr.push({
                            value: p.toLowerCase(),
                            flag: arg.match === ArgumentMatches.FLAG
                        });
                    }
                } else {
                    arr.push({
                        value: arg.prefix.toLowerCase(),
                        flag: arg.match === ArgumentMatches.FLAG
                    });
                }
            }

            return arr;
        }, []);

        const noPrefixWords = words.filter(w => {
            return !prefixes.some(p => {
                if (!p.flag) return w.toLowerCase().startsWith(p.value);
                return w.toLowerCase() === p.value;
            });
        });

        const parseFuncs = {
            [ArgumentMatches.WORD]: (arg, index) => {
                let word = noPrefixWords[arg.index != null ? arg.index : index] || '';
                if (isQuoted && /^".*"$/.test(word)) word = word.slice(1, -1);

                return arg.cast.bind(arg, word);
            },
            [ArgumentMatches.REST]: (arg, index) => {
                let word = noPrefixWords.slice(arg.index != null ? arg.index : index).join('') || '';
                if (isQuoted && /^".*"$/.test(word)) word = word.slice(1, -1);

                return arg.cast.bind(arg, word);
            },
            [ArgumentMatches.PREFIX]: arg => {
                let prefixUsed;
                let word;

                for (let i = words.length; i--;) {
                    const w = words[i];
                    if (Array.isArray(arg.prefix)) {
                        let found = false;

                        for (const prefix of arg.prefix) {
                            if (w.toLowerCase().startsWith(prefix.toLowerCase())) {
                                prefixUsed = prefix;
                                word = w;
                                found = true;
                                break;
                            }
                        }

                        if (found) break;
                    } else
                    if (w.toLowerCase().startsWith(arg.prefix.toLowerCase())) {
                        prefixUsed = arg.prefix;
                        word = w;
                        break;
                    }
                }

                if (word && prefixUsed) {
                    word = word.replace(prefixUsed, '');
                    if (isQuoted && /^".*"$/.test(word)) word = word.slice(1, -1);
                }

                return arg.cast.bind(arg, word || '');
            },
            [ArgumentMatches.FLAG]: arg => {
                let word;

                for (let i = words.length; i--;) {
                    const w = words[i];
                    if (Array.isArray(arg.prefix)) {
                        let found = false;

                        for (const prefix of arg.prefix) {
                            if (w.toLowerCase().startsWith(prefix.toLowerCase())) {
                                word = w;
                                found = true;
                                break;
                            }
                        }

                        if (found) break;
                    } else
                    if (w.toLowerCase().startsWith(arg.prefix.toLowerCase())) {
                        word = w;
                        break;
                    }
                }

                return () => Promise.resolve(arg.default() ? !word : !!word);
            },
            [ArgumentMatches.TEXT]: arg => {
                let word = arg.index ? noPrefixWords.join('') : noPrefixWords.slice(arg.index).join('');
                if (isQuoted && /^".*"$/.test(word)) word = word.slice(1, -1);

                return arg.cast.bind(arg, word);
            },
            [ArgumentMatches.CONTENT]: arg => {
                let word = arg.index ? content : content.split(' ').slice(arg.index).join(' ');
                if (isQuoted && /^".*"$/.test(word)) word = word.slice(1, -1);

                return arg.cast.bind(arg, word);
            },
            [ArgumentMatches.NONE]: arg => {
                return arg.cast.bind(arg, '');
            }
        };

        const processed = {};
        let wordIndex = 0;

        const process = i => {
            if (i === this.args.length) return processed;

            const arg = this.args[i];
            const matchType = typeof arg.match === 'function' ? arg.match.call(this, message, processed) : arg.match;
            const castFunc = parseFuncs[matchType](arg, wordIndex);

            if (matchType === ArgumentMatches.WORD || matchType === ArgumentMatches.REST) wordIndex++;

            return castFunc(message, processed).then(res => {
                processed[arg.id] = res;
                return process(i + 1);
            });
        };

        return process(0);
    }

    /**
     * Disables the command.
     * @returns {boolean}
     */
    disable() {
        if (this.protected) return false;
        return super.disable();
    }

    /**
     * Reloads the command.
     * @method
     * @name Command#reload
     * @returns {Command}
     */

    /**
     * Removes the command.
     * @method
     * @name Command#remove
     * @returns {Command}
     */

    /**
     * Enables the command.
     * @method
     * @name Command#enable
     * @returns {boolean}
     */
}

module.exports = Command;
<commit_msg>Revert removing quote for content match<commit_after>const AkairoModule = require('./AkairoModule');
const Argument = require('./Argument');
const { ArgumentMatches, ArgumentSplits } = require('../util/Constants');

/**
 * Options to use for command execution behavior.
 * @typedef {Object} CommandOptions
 * @prop {string[]} [aliases=[]] - Command names.
 * @prop {ArgumentOptions[]} [args=[]] - Arguments to parse.
 * @prop {ArgumentSplit} [split='plain'] - Method to split text into words.
 * @prop {string} [channelRestriction] - Restricts channel: 'guild' or 'dm'.
 * @prop {string} [category='default'] - Category ID for organization purposes.
 * @prop {boolean} [ownerOnly=false] - Whether or not to allow client owner(s) only.
 * @prop {boolean} [protected=false] - Whether or not this command cannot be disabled.
 * @prop {boolean} [typing=false] - Whether or not to type in channel during execution.
 * @prop {boolean} [editable=true] - Whether or not message edits will run this command.
 * On an edited message, the exec function edited param will be true.
 * @prop {number} [cooldown] - The command cooldown in milliseconds.
 * @prop {number} [ratelimit=1] - Amount of command uses allowed until cooldown.
 * @prop {string|string[]|Function} [prefix] - A prefix to overwrite the global one for this command.
 * Can be a function `(message => string|string[])`.
 * @prop {PermissionResolvable[]|Function} [userPermissions] - Permissions required by the user to run this command.
 * Can be either an array of permission name or a function returning `true or `false`.
 * @prop {PermissionResolvable[]|Function} [clientPermissions] - Permissions required by the client to run this command.
 * Can be either an array of permission name or a function returning `true or `false`.
 * @prop {RegExp|Function} [trigger] - A regex or function `(message => RegExp)` returning regex to match in messages that are NOT commands.
 * The exec function becomes `((message, match, groups, edited) => any)`.
 * @prop {Function} [condition] - A function `((message, edited) => {})` that returns true or false on messages that are NOT commands.
 * The exec function becomes `((message, edited) => any)`.
 * @prop {PromptOptions} [defaultPrompt={}] - The default prompt options.
 * @prop {Object} [options={}] - An object for custom options.
 * Accessible with `Command#options`.
 * @prop {string|string[]} [description=''] - Description of the command.
 */

/**
 * The method to split text into words.
 * - `plain` splits word separated by whitespace.
 * Extra whitespace between words are ignored.
 * - `split` splits word separated by whitespace.
 * Should not be used due to possible inconsistent whitespace.
 * - `quoted` is similar to plain, but counts text inside double quotes as one word.
 * - `sticky` is similar to quoted, but makes it so that quoted text must have a whitespace or another double quote before it to count as another word.
 * - `none` gives the entire content.
 *
 * A regex or a character can be used instead (for example, a comma) to split the message by that regex or character.
 * A function `((content, message) => string[])` can also be used, returning an array of strings.
 * @typedef {string|RegExp} ArgumentSplit
 */

/** @extends AkairoModule */
class Command extends AkairoModule {
    /**
     * Creates a new command.
     * @param {string} id - Command ID.
     * @param {Function} exec - Function `((message, args, edited) => any)` called when command is ran.
     * @param {CommandOptions} [options={}] - Options for the command.
     */
    constructor(id, exec, options) {
        if (!options && typeof exec === 'object') {
            options = exec;
            exec = null;
        }

        super(id, exec, options);

        /**
         * Command names.
         * @type {string[]}
         */
        this.aliases = options.aliases || [];

        /**
         * Arguments for the command.
         * @type {Argument[]}
         */
        this.args = (options.args || []).map(a => new Argument(this, a));

        /**
         * The command split method.
         * @type {ArgumentSplit}
         */
        this.split = options.split || this.split || ArgumentSplits.PLAIN;

        /**
         * Usable only in this channel type.
         * @type {string}
         */
        this.channelRestriction = options.channelRestriction;

        /**
         * Usable only by the client owner.
         * @type {boolean}
         */
        this.ownerOnly = !!options.ownerOnly;

        /**
         * Whether or not this command cannot be disabled.
         * @type {boolean}
         */
        this.protected = !!options.protected;

        /**
         * Whether or not this command can be ran by an edit.
         * @type {boolean}
         */
        this.editable = options.editable === undefined ? true : !!options.editable;

        /**
         * Whether or not to type during command execution.
         * @type {boolean}
         */
        this.typing = !!options.typing;

        /**
         * Cooldown in milliseconds.
         * @type {number}
         */
        this.cooldown = options.cooldown;

        /**
         * Uses allowed before cooldown.
         * @type {number}
         */
        this.ratelimit = options.ratelimit || 1;

        /**
         * Default prompt options.
         * @type {PromptOptions}
         */
        this.defaultPrompt = options.defaultPrompt || {};

        /**
         * Custom options for the command.
         * @type {Object}
         */
        this.options = options.custom || options.options || {};

        /**
         * Description of the command.
         * @type {string}
         */
        this.description = (Array.isArray(options.description) ? options.description.join('\n') : options.description) || '';

        /**
         * Command prefix overwrite.
         * @type {?string|string[]|Function}
         */
        this.prefix = options.prefix || this.prefix;

        /**
         * Permissions required to run command by the client.
         * @type {PermissionResolvable[]|Function}
         */
        this.clientPermissions = options.clientPermissions || this.clientPermissions;

        /**
         * Permissions required to run command by the user.
         * @type {PermissionResolvable[]|Function}
         */
        this.userPermissions = options.userPermissions || this.userPermissions;

        /**
         * Gets the regex trigger, if specified.
         * @method
         * @param {Message} message - Message being handled.
         * @returns {RegExp}
         */
        this.trigger = typeof options.trigger === 'function' ? options.trigger : this.trigger || (() => options.trigger);

        /**
         * Gets the condition trigger, if specified.
         * @method
         * @param {Message} message - Message being handled.
         * @returns {boolean}
         */
        this.condition = options.condition || this.condition || (() => false);

        /**
         * The ID of this command.
         * @name Command#id
         * @type {string}
         */

        /**
         * Executes the command.
         * @method
         * @name Command#exec
         * @returns {any}
         */

        /**
         * The command handler.
         * @readonly
         * @name Command#handler
         * @type {CommandHandler}
         */
    }

    /**
     * Parses text based on this command's args.
     * @param {string} content - String to parse.
     * @param {Message} [message] - Message to use.
     * @returns {Promise<Object>}
     */
    parse(content, message) {
        if (!this.args.length) return Promise.resolve({});

        const splitFuncs = {
            [ArgumentSplits.PLAIN]: c => c.match(/[^\s]+/g),
            [ArgumentSplits.SPLIT]: c => c.split(' '),
            [ArgumentSplits.QUOTED]: c => c.match(/".*?"|\s?[^\s"]+|"\s?/g),
            [ArgumentSplits.STICKY]: c => c.match(/[^\s"]*?".*?"|\s?[^\s"]+\s?|"/g),
            [ArgumentSplits.NONE]: c => [c]
        };

        const isQuoted = this.split === ArgumentSplits.QUOTED || this.split === ArgumentSplits.STICKY;

        const words = typeof this.split === 'function'
        ? this.split(content, message) || []
        : splitFuncs[this.split]
        ? splitFuncs[this.split](content) || []
        : content.split(this.split);

        const prefixes = this.args.reduce((arr, arg) => {
            if (arg.match === ArgumentMatches.PREFIX || arg.match === ArgumentMatches.FLAG) {
                if (Array.isArray(arg.prefix)) {
                    for (const p of arg.prefix) {
                        arr.push({
                            value: p.toLowerCase(),
                            flag: arg.match === ArgumentMatches.FLAG
                        });
                    }
                } else {
                    arr.push({
                        value: arg.prefix.toLowerCase(),
                        flag: arg.match === ArgumentMatches.FLAG
                    });
                }
            }

            return arr;
        }, []);

        const noPrefixWords = words.filter(w => {
            return !prefixes.some(p => {
                if (!p.flag) return w.toLowerCase().startsWith(p.value);
                return w.toLowerCase() === p.value;
            });
        });

        const parseFuncs = {
            [ArgumentMatches.WORD]: (arg, index) => {
                let word = noPrefixWords[arg.index != null ? arg.index : index] || '';
                if (isQuoted && /^".*"$/.test(word)) word = word.slice(1, -1);

                return arg.cast.bind(arg, word);
            },
            [ArgumentMatches.REST]: (arg, index) => {
                let word = noPrefixWords.slice(arg.index != null ? arg.index : index).join('') || '';
                if (isQuoted && /^".*"$/.test(word)) word = word.slice(1, -1);

                return arg.cast.bind(arg, word);
            },
            [ArgumentMatches.PREFIX]: arg => {
                let prefixUsed;
                let word;

                for (let i = words.length; i--;) {
                    const w = words[i];
                    if (Array.isArray(arg.prefix)) {
                        let found = false;

                        for (const prefix of arg.prefix) {
                            if (w.toLowerCase().startsWith(prefix.toLowerCase())) {
                                prefixUsed = prefix;
                                word = w;
                                found = true;
                                break;
                            }
                        }

                        if (found) break;
                    } else
                    if (w.toLowerCase().startsWith(arg.prefix.toLowerCase())) {
                        prefixUsed = arg.prefix;
                        word = w;
                        break;
                    }
                }

                if (word && prefixUsed) {
                    word = word.replace(prefixUsed, '');
                    if (isQuoted && /^".*"$/.test(word)) word = word.slice(1, -1);
                }

                return arg.cast.bind(arg, word || '');
            },
            [ArgumentMatches.FLAG]: arg => {
                let word;

                for (let i = words.length; i--;) {
                    const w = words[i];
                    if (Array.isArray(arg.prefix)) {
                        let found = false;

                        for (const prefix of arg.prefix) {
                            if (w.toLowerCase().startsWith(prefix.toLowerCase())) {
                                word = w;
                                found = true;
                                break;
                            }
                        }

                        if (found) break;
                    } else
                    if (w.toLowerCase().startsWith(arg.prefix.toLowerCase())) {
                        word = w;
                        break;
                    }
                }

                return () => Promise.resolve(arg.default() ? !word : !!word);
            },
            [ArgumentMatches.TEXT]: arg => {
                let word = arg.index ? noPrefixWords.join('') : noPrefixWords.slice(arg.index).join('');
                if (isQuoted && /^".*"$/.test(word)) word = word.slice(1, -1);

                return arg.cast.bind(arg, word);
            },
            [ArgumentMatches.CONTENT]: arg => {
                const word = arg.index ? content : content.split(' ').slice(arg.index).join(' ');
                return arg.cast.bind(arg, word);
            },
            [ArgumentMatches.NONE]: arg => {
                return arg.cast.bind(arg, '');
            }
        };

        const processed = {};
        let wordIndex = 0;

        const process = i => {
            if (i === this.args.length) return processed;

            const arg = this.args[i];
            const matchType = typeof arg.match === 'function' ? arg.match.call(this, message, processed) : arg.match;
            const castFunc = parseFuncs[matchType](arg, wordIndex);

            if (matchType === ArgumentMatches.WORD || matchType === ArgumentMatches.REST) wordIndex++;

            return castFunc(message, processed).then(res => {
                processed[arg.id] = res;
                return process(i + 1);
            });
        };

        return process(0);
    }

    /**
     * Disables the command.
     * @returns {boolean}
     */
    disable() {
        if (this.protected) return false;
        return super.disable();
    }

    /**
     * Reloads the command.
     * @method
     * @name Command#reload
     * @returns {Command}
     */

    /**
     * Removes the command.
     * @method
     * @name Command#remove
     * @returns {Command}
     */

    /**
     * Enables the command.
     * @method
     * @name Command#enable
     * @returns {boolean}
     */
}

module.exports = Command;
<|endoftext|>


<commit_before>'use strict'

let io = null

let connectionInfo

const MIN_WAIT = 100
let wait = MIN_WAIT

let states = []

function resetWait() {
    wait = MIN_WAIT
}

function incrementWait() {
    const MAX_WAIT = 2000
    if (wait < MAX_WAIT)
        wait *= 1.25
    else
        wait = MAX_WAIT
}

function connect(address) {
    io = new WebSocket(address)
    io.onopen = () => {
        resetWait()
        if (connectionInfo) {
            connectionInfo.visible = false
        }
    };

    io.onmessage = (msg) => {
        states.push(JSON.parse(msg.data, (key, value) => value === "" ? 0 : value))
        const state = states[states.length - 1]
        while (players.length < state.players.length) {
            spawnPlayer()
        }

        while (players.length > state.players.length) {
            removePlayer()
        }
    }

    io.onclose = () => {
        if (connectionInfo) {
            connectionInfo.text = 'Reconnecting...'
            connectionInfo.visible = true
        }
        io = null
        setTimeout(() => {
            incrementWait()
            connect(address);
        }, wait)
    };

    io.onerror = (err) => {
        console.error('Socket encountered error: ', err.message, 'Closing socket')
        io.close()
        io = null
    };
}

function send(data) {
    if (io && io.readyState === 1) {
        console.log('sending data: ', data)
        io.send(JSON.stringify(data))
    }
}

const Application = PIXI.Application,
    loader = PIXI.loader,
    resources = PIXI.loader.resources,
    Sprite = PIXI.Sprite

const app = new Application(
    1000, 1000, {
        backgroundColor: 0xFFFFFF,
        antialias: true,
    },
)

const addr = window.location.hostname === 'localhost' ? 'ws://localhost:8081' : 'wss://beta.jnferner.com/socket'
connect(addr)
document.body.appendChild(app.view)

loader
    .add([{
        name: 'pong',
        url: 'assets/pong.json'
    }, ])
    .on('progress', loadProgressHandler)
    .load(setup)

let validKeys = ["ArrowUp", "ArrowDown"]
let keyPressed = {
    ArrowLeft: false,
    ArrowRight: false
}
document.addEventListener("keydown", (event) => {
    sendKeyWithVal(event.key, true)
})
document.addEventListener("keyup", (event) => {
    sendKeyWithVal(event.key, false)
})

function sendKeyWithVal(key, val) {
    if (validKeys.indexOf(key) > -1) {
        sendIfNew(key, val)
    }
}

let msgId = 0

function sendIfNew(key, val) {
    if (keyPressed[key] !== val) {
        keyPressed[key] = val
        let msg = {
            id: msgId,
            key: key,
            pressed: val
        }
        msgId++
        send(msg)
    }
}

function loadProgressHandler(loader, resource) {
    console.log('loading: ' + resource.name + ' (' + resource.url + ')')
    console.log('progress: ' + loader.progress + '%')
    if (resource.error)
        console.error(resource.error)
}

let ball
let players = []

function setup() {
    const background = new Sprite(resources.pong.textures['fancy-court.png'])
    background.width = 1000
    background.height = 1000
    app.stage.addChild(background)

    connectionInfo = new PIXI.Text('')
    connectionInfo.style.fill = 0xe3e3ed
    connectionInfo.style.dropShadow = true
    connectionInfo.style.dropShadowAlpha = 0.7
    connectionInfo.y = 30
    connectionInfo.x = 40
    app.stage.addChild(connectionInfo)

    ball = new Sprite(resources.pong.textures['fancy-ball.png'])
    ball.anchor.set(0.5)
    app.stage.addChild(ball)

    app.ticker.add(gameLoop)
}

let state = connecting

function gameLoop(delta) {
    state()
}

function connecting() {
    connectionInfo.text = 'Connecting...'
    connectionInfo.visible = true

    const renderTime = getRenderTime()
    const index = getIndexOfRenderState(states, renderTime)
    if (index >= 0) {
        connectionInfo.visible = false
        state = play
    }
}

function play() {
    render(states)
}

function render(states) {
    const renderTime = getRenderTime()
    const index = getIndexOfRenderState(states, renderTime)
    if (index < 0)
        return
    states.splice(0, index)
    let interpolatedState = getInterpolatedState(states[0], states[1], renderTime)
    setWorld(interpolatedState)
}

function getRenderTime() {
    const now = new Date().getTime()
    const INTERPOLATION_DELTA = 200
    return now - INTERPOLATION_DELTA
}

function getIndexOfRenderState(states, renderTime) {
    const found = states.findIndex((state) => state.timestamp >= renderTime)
    return found - 1
}

function getInterpolatedState(from, to, renderTime) {
    const total = to.timestamp - from.timestamp
    const progress = renderTime - from.timestamp
    if (total === 0 || progress === 0)
        return from
    const fraction = progress / total
    let state = from

    state.ball.vel.x += (to.ball.vel.x - from.ball.vel.x) * fraction
    state.ball.vel.y += (to.ball.vel.y - from.ball.vel.y) * fraction
    state.ball.pos.x += (to.ball.pos.x - from.ball.pos.x) * fraction
    state.ball.pos.y += (to.ball.pos.y - from.ball.pos.y) * fraction

    for (let i = 0; i < players.length; i++) {
        if (!to.players[i])
            continue;
        state.players[i].acc.x += (to.players[i].acc.x - from.players[i].acc.x) * fraction
        state.players[i].acc.y += (to.players[i].acc.y - from.players[i].acc.y) * fraction
        state.players[i].vel.x += (to.players[i].vel.x - from.players[i].vel.x) * fraction
        state.players[i].vel.y += (to.players[i].vel.y - from.players[i].vel.y) * fraction
        state.players[i].pos.x += (to.players[i].pos.x - from.players[i].pos.x) * fraction
        state.players[i].pos.y += (to.players[i].pos.y - from.players[i].pos.y) * fraction
    }

    state.timestamp = renderTime
    return state
}

function setWorld(state) {
    ball.x = state.ball.pos.x
    ball.y = state.ball.pos.y

    for (let i = 0; i < players.length; i++) {
        if (!state.players[i])
            continue;
        players[i].x = state.players[i].pos.x
        players[i].y = state.players[i].pos.y
    }

    const maxVel = Math.max(state.ball.vel.x, state.ball.vel.y)
    const strength = Math.pow(Math.atan(Math.pow((maxVel / 10), 1.5)), 2) - 0.2
    if (strength > 0) {
        const blurFilter = new PIXI.filters.BlurFilter(strength, 1, 1)
        ball.filters = [blurFilter]
    }
}

function spawnPlayer() {
    const player = new Sprite(resources.pong.textures['fancy-paddle-green.png'])
    player.anchor.set(0.5)
    app.stage.addChild(player)
    players.push(player)
}

function removePlayer() {
    const player = players[players.length - 1]
    app.stage.removeChild(player)
    players.pop()
}<commit_msg>Client: Add motion blur to player<commit_after>'use strict'

let io = null

let connectionInfo

const MIN_WAIT = 100
let wait = MIN_WAIT

let states = []

function resetWait() {
    wait = MIN_WAIT
}

function incrementWait() {
    const MAX_WAIT = 2000
    if (wait < MAX_WAIT)
        wait *= 1.25
    else
        wait = MAX_WAIT
}

function connect(address) {
    io = new WebSocket(address)
    io.onopen = () => {
        resetWait()
        if (connectionInfo) {
            connectionInfo.visible = false
        }
    };

    io.onmessage = (msg) => {
        states.push(JSON.parse(msg.data, (key, value) => value === "" ? 0 : value))
        const state = states[states.length - 1]
        while (players.length < state.players.length) {
            spawnPlayer()
        }

        while (players.length > state.players.length) {
            removePlayer()
        }
    }

    io.onclose = () => {
        if (connectionInfo) {
            connectionInfo.text = 'Reconnecting...'
            connectionInfo.visible = true
        }
        io = null
        setTimeout(() => {
            incrementWait()
            connect(address);
        }, wait)
    };

    io.onerror = (err) => {
        console.error('Socket encountered error: ', err.message, 'Closing socket')
        io.close()
        io = null
    };
}

function send(data) {
    if (io && io.readyState === 1) {
        console.log('sending data: ', data)
        io.send(JSON.stringify(data))
    }
}

const Application = PIXI.Application,
    loader = PIXI.loader,
    resources = PIXI.loader.resources,
    Sprite = PIXI.Sprite

const app = new Application(
    1000, 1000, {
        backgroundColor: 0xFFFFFF,
        antialias: true,
    },
)

const addr = window.location.hostname === 'localhost' ? 'ws://localhost:8081' : 'wss://beta.jnferner.com/socket'
connect(addr)
document.body.appendChild(app.view)

loader
    .add([{
        name: 'pong',
        url: 'assets/pong.json'
    }, ])
    .on('progress', loadProgressHandler)
    .load(setup)

let validKeys = ["ArrowUp", "ArrowDown"]
let keyPressed = {
    ArrowLeft: false,
    ArrowRight: false
}
document.addEventListener("keydown", (event) => {
    sendKeyWithVal(event.key, true)
})
document.addEventListener("keyup", (event) => {
    sendKeyWithVal(event.key, false)
})

function sendKeyWithVal(key, val) {
    if (validKeys.indexOf(key) > -1) {
        sendIfNew(key, val)
    }
}

let msgId = 0

function sendIfNew(key, val) {
    if (keyPressed[key] !== val) {
        keyPressed[key] = val
        let msg = {
            id: msgId,
            key: key,
            pressed: val
        }
        msgId++
        send(msg)
    }
}

function loadProgressHandler(loader, resource) {
    console.log('loading: ' + resource.name + ' (' + resource.url + ')')
    console.log('progress: ' + loader.progress + '%')
    if (resource.error)
        console.error(resource.error)
}

let ball
let players = []

function setup() {
    const background = new Sprite(resources.pong.textures['fancy-court.png'])
    background.width = 1000
    background.height = 1000
    app.stage.addChild(background)

    connectionInfo = new PIXI.Text('')
    connectionInfo.style.fill = 0xe3e3ed
    connectionInfo.style.dropShadow = true
    connectionInfo.style.dropShadowAlpha = 0.7
    connectionInfo.y = 30
    connectionInfo.x = 40
    app.stage.addChild(connectionInfo)

    ball = new Sprite(resources.pong.textures['fancy-ball.png'])
    ball.anchor.set(0.5)
    app.stage.addChild(ball)

    app.ticker.add(gameLoop)
}

let state = connecting

function gameLoop(delta) {
    state()
}

function connecting() {
    connectionInfo.text = 'Connecting...'
    connectionInfo.visible = true

    const renderTime = getRenderTime()
    const index = getIndexOfRenderState(states, renderTime)
    if (index >= 0) {
        connectionInfo.visible = false
        state = play
    }
}

function play() {
    render(states)
}

function render(states) {
    const renderTime = getRenderTime()
    const index = getIndexOfRenderState(states, renderTime)
    if (index < 0)
        return
    states.splice(0, index)
    let interpolatedState = getInterpolatedState(states[0], states[1], renderTime)
    setWorld(interpolatedState)
}

function getRenderTime() {
    const now = new Date().getTime()
    const INTERPOLATION_DELTA = 200
    return now - INTERPOLATION_DELTA
}

function getIndexOfRenderState(states, renderTime) {
    const found = states.findIndex((state) => state.timestamp >= renderTime)
    return found - 1
}

function getInterpolatedState(from, to, renderTime) {
    const total = to.timestamp - from.timestamp
    const progress = renderTime - from.timestamp
    if (total === 0 || progress === 0)
        return from
    const fraction = progress / total
    let state = from

    state.ball.vel.x += (to.ball.vel.x - from.ball.vel.x) * fraction
    state.ball.vel.y += (to.ball.vel.y - from.ball.vel.y) * fraction
    state.ball.pos.x += (to.ball.pos.x - from.ball.pos.x) * fraction
    state.ball.pos.y += (to.ball.pos.y - from.ball.pos.y) * fraction

    for (let i = 0; i < players.length; i++) {
        if (!to.players[i])
            continue;
        state.players[i].acc.x += (to.players[i].acc.x - from.players[i].acc.x) * fraction
        state.players[i].acc.y += (to.players[i].acc.y - from.players[i].acc.y) * fraction
        state.players[i].vel.x += (to.players[i].vel.x - from.players[i].vel.x) * fraction
        state.players[i].vel.y += (to.players[i].vel.y - from.players[i].vel.y) * fraction
        state.players[i].pos.x += (to.players[i].pos.x - from.players[i].pos.x) * fraction
        state.players[i].pos.y += (to.players[i].pos.y - from.players[i].pos.y) * fraction
    }

    state.timestamp = renderTime
    return state
}

function setWorld(state) {
    ball.x = state.ball.pos.x
    ball.y = state.ball.pos.y

    addBlur(ball, state.ball.vel)
    for (let i = 0; i < players.length; i++) {
        if (!state.players[i])
            continue;
        players[i].x = state.players[i].pos.x
        players[i].y = state.players[i].pos.y
        addBlur(players[i], state.players[i].vel)
    }
}

function addBlur(obj, vel) {
    const maxVel = Math.max(vel.x, vel.y)
    const strength = Math.pow(Math.atan(Math.pow((maxVel / 10), 1.5)), 2) - 0.2
    if (strength > 0) {
        const blurFilter = new PIXI.filters.BlurFilter(strength, 1, 1)
        obj.filters = [blurFilter]
    }
}

function spawnPlayer() {
    const player = new Sprite(resources.pong.textures['fancy-paddle-green.png'])
    player.anchor.set(0.5)
    app.stage.addChild(player)
    players.push(player)
}

function removePlayer() {
    const player = players[players.length - 1]
    app.stage.removeChild(player)
    players.pop()
}<|endoftext|>


<commit_before>/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @author       Felipe Alfonso <@bitnenfer>
 * @author       Matthew Groves <@doormat>
 * @copyright    2022 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

/**
 * @namespace Phaser.Renderer.WebGL.Utils
 * @since 3.0.0
 */
module.exports = {

    /**
     * Packs four floats on a range from 0.0 to 1.0 into a single Uint32
     *
     * @function Phaser.Renderer.WebGL.Utils.getTintFromFloats
     * @since 3.0.0
     *
     * @param {number} r - Red component in a range from 0.0 to 1.0
     * @param {number} g - Green component in a range from 0.0 to 1.0
     * @param {number} b - Blue component in a range from 0.0 to 1.0
     * @param {number} a - Alpha component in a range from 0.0 to 1.0
     *
     * @return {number} The packed RGBA values as a Uint32.
     */
    getTintFromFloats: function (r, g, b, a)
    {
        var ur = ((r * 255) | 0) & 0xff;
        var ug = ((g * 255) | 0) & 0xff;
        var ub = ((b * 255) | 0) & 0xff;
        var ua = ((a * 255) | 0) & 0xff;

        return ((ua << 24) | (ur << 16) | (ug << 8) | ub) >>> 0;
    },

    /**
     * Packs a Uint24, representing RGB components, with a Float32, representing
     * the alpha component, with a range between 0.0 and 1.0 and return a Uint32
     *
     * @function Phaser.Renderer.WebGL.Utils.getTintAppendFloatAlpha
     * @since 3.0.0
     *
     * @param {number} rgb - Uint24 representing RGB components
     * @param {number} a - Float32 representing Alpha component
     *
     * @return {number} Packed RGBA as Uint32
     */
    getTintAppendFloatAlpha: function (rgb, a)
    {
        var ua = ((a * 255) | 0) & 0xff;

        return ((ua << 24) | rgb) >>> 0;
    },

    /**
     * Packs a Uint24, representing RGB components, with a Float32, representing
     * the alpha component, with a range between 0.0 and 1.0 and return a
     * swizzled Uint32
     *
     * @function Phaser.Renderer.WebGL.Utils.getTintAppendFloatAlphaAndSwap
     * @since 3.0.0
     *
     * @param {number} rgb - Uint24 representing RGB components
     * @param {number} a - Float32 representing Alpha component
     *
     * @return {number} Packed RGBA as Uint32
     */
    getTintAppendFloatAlphaAndSwap: function (rgb, a)
    {
        var ur = ((rgb >> 16) | 0) & 0xff;
        var ug = ((rgb >> 8) | 0) & 0xff;
        var ub = (rgb | 0) & 0xff;
        var ua = ((a * 255) | 0) & 0xff;

        return ((ua << 24) | (ub << 16) | (ug << 8) | ur) >>> 0;
    },

    /**
     * Unpacks a Uint24 RGB into an array of floats of ranges of 0.0 and 1.0
     *
     * @function Phaser.Renderer.WebGL.Utils.getFloatsFromUintRGB
     * @since 3.0.0
     *
     * @param {number} rgb - RGB packed as a Uint24
     *
     * @return {array} Array of floats representing each component as a float
     */
    getFloatsFromUintRGB: function (rgb)
    {
        var ur = ((rgb >> 16) | 0) & 0xff;
        var ug = ((rgb >> 8) | 0) & 0xff;
        var ub = (rgb | 0) & 0xff;

        return [ ur / 255, ug / 255, ub / 255 ];
    },

    /**
     * Check to see how many texture units the GPU supports in a fragment shader
     * and if the value specific in the game config is allowed.
     *
     * @function Phaser.Renderer.WebGL.Utils.checkShaderMax
     * @since 3.50.0
     *
     * @param {WebGLRenderingContext} gl - The WebGLContext used to create the shaders.
     * @param {number} maxTextures - The Game Config maxTextures value.
     *
     * @return {number} The number of texture units that is supported by this browser and GPU.
     */
    checkShaderMax: function (gl, maxTextures)
    {
        //  Note: This is the maximum number of TIUs that a _fragment_ shader supports
        //  https://www.khronos.org/opengl/wiki/Common_Mistakes#Texture_Unit

        //  Hard-clamp this to 16 to avoid run-away texture counts such as on Android
        var gpuMax = Math.min(16, gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS));

        if (!maxTextures || maxTextures === -1)
        {
            return gpuMax;
        }
        else
        {
            return Math.min(gpuMax, maxTextures);
        }
    },

    /**
     * Checks the given Fragment Shader Source for `%count%` and `%getSampler%` declarations and
     * replaces those with GLSL code for setting `texture = texture2D(uMainSampler[i], outTexCoord)`
     * and injecting the `getSampler` glsl function.
     *
     * @function Phaser.Renderer.WebGL.Utils.parseFragmentShaderMaxTextures
     * @since 3.50.0
     *
     * @param {string} fragmentShaderSource - The Fragment Shader source code to operate on.
     * @param {number} maxTextures - The number of maxTextures value.
     *
     * @return {string} The modified Fragment Shader source.
     */
    parseFragmentShaderMaxTextures: function (fragmentShaderSource, maxTextures)
    {
        if (!fragmentShaderSource)
        {
            return '';
        }

        /*
        var src = 'vec4 getSampler (int index, vec2 uv) {';

        for (var i = 0; i < maxTextures; i++)
        {
            if (i > 0 && i < maxTextures)
            {
                src += '\nelse ';
            }

            src += 'if (index == ' + i + ') { return texture2D(uMainSampler[' + i + '], uv); }';
        }

        src += '\nreturn vec4(0);\n}';

        fragmentShaderSource = fragmentShaderSource.replace(/%getSampler%/gi, src);
        */

        return fragmentShaderSource.replace(/%count%/gi, maxTextures.toString());
    }
};
<commit_msg>Update Utils.js<commit_after>/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @author       Felipe Alfonso <@bitnenfer>
 * @author       Matthew Groves <@doormat>
 * @copyright    2022 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

/**
 * @namespace Phaser.Renderer.WebGL.Utils
 * @since 3.0.0
 */
module.exports = {

    /**
     * Packs four floats on a range from 0.0 to 1.0 into a single Uint32
     *
     * @function Phaser.Renderer.WebGL.Utils.getTintFromFloats
     * @since 3.0.0
     *
     * @param {number} r - Red component in a range from 0.0 to 1.0
     * @param {number} g - Green component in a range from 0.0 to 1.0
     * @param {number} b - Blue component in a range from 0.0 to 1.0
     * @param {number} a - Alpha component in a range from 0.0 to 1.0
     *
     * @return {number} The packed RGBA values as a Uint32.
     */
    getTintFromFloats: function (r, g, b, a)
    {
        var ur = ((r * 255) | 0) & 0xff;
        var ug = ((g * 255) | 0) & 0xff;
        var ub = ((b * 255) | 0) & 0xff;
        var ua = ((a * 255) | 0) & 0xff;

        return ((ua << 24) | (ur << 16) | (ug << 8) | ub) >>> 0;
    },

    /**
     * Packs a Uint24, representing RGB components, with a Float32, representing
     * the alpha component, with a range between 0.0 and 1.0 and return a Uint32
     *
     * @function Phaser.Renderer.WebGL.Utils.getTintAppendFloatAlpha
     * @since 3.0.0
     *
     * @param {number} rgb - Uint24 representing RGB components
     * @param {number} a - Float32 representing Alpha component
     *
     * @return {number} Packed RGBA as Uint32
     */
    getTintAppendFloatAlpha: function (rgb, a)
    {
        var ua = ((a * 255) | 0) & 0xff;

        return ((ua << 24) | rgb) >>> 0;
    },

    /**
     * Packs a Uint24, representing RGB components, with a Float32, representing
     * the alpha component, with a range between 0.0 and 1.0 and return a
     * swizzled Uint32
     *
     * @function Phaser.Renderer.WebGL.Utils.getTintAppendFloatAlphaAndSwap
     * @since 3.0.0
     *
     * @param {number} rgb - Uint24 representing RGB components
     * @param {number} a - Float32 representing Alpha component
     *
     * @return {number} Packed RGBA as Uint32
     */
    getTintAppendFloatAlphaAndSwap: function (rgb, a)
    {
        var ur = ((rgb >> 16) | 0) & 0xff;
        var ug = ((rgb >> 8) | 0) & 0xff;
        var ub = (rgb | 0) & 0xff;
        var ua = ((a * 255) | 0) & 0xff;

        return ((ua << 24) | (ub << 16) | (ug << 8) | ur) >>> 0;
    },

    /**
     * Unpacks a Uint24 RGB into an array of floats of ranges of 0.0 and 1.0
     *
     * @function Phaser.Renderer.WebGL.Utils.getFloatsFromUintRGB
     * @since 3.0.0
     *
     * @param {number} rgb - RGB packed as a Uint24
     *
     * @return {array} Array of floats representing each component as a float
     */
    getFloatsFromUintRGB: function (rgb)
    {
        var ur = ((rgb >> 16) | 0) & 0xff;
        var ug = ((rgb >> 8) | 0) & 0xff;
        var ub = (rgb | 0) & 0xff;

        return [ ur / 255, ug / 255, ub / 255 ];
    },

    /**
     * Check to see how many texture units the GPU supports in a fragment shader
     * and if the value specific in the game config is allowed.
     *
     * This value is hard-clamped to 16 for performance reasons on Android devices.
     *
     * @function Phaser.Renderer.WebGL.Utils.checkShaderMax
     * @since 3.50.0
     *
     * @param {WebGLRenderingContext} gl - The WebGLContext used to create the shaders.
     * @param {number} maxTextures - The Game Config maxTextures value.
     *
     * @return {number} The number of texture units that is supported by this browser and GPU.
     */
    checkShaderMax: function (gl, maxTextures)
    {
        //  Note: This is the maximum number of TIUs that a _fragment_ shader supports
        //  https://www.khronos.org/opengl/wiki/Common_Mistakes#Texture_Unit

        //  Hard-clamp this to 16 to avoid run-away texture counts such as on Android
        var gpuMax = Math.min(16, gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS));

        if (!maxTextures || maxTextures === -1)
        {
            return gpuMax;
        }
        else
        {
            return Math.min(gpuMax, maxTextures);
        }
    },

    /**
     * Checks the given Fragment Shader Source for `%count%` and `%getSampler%` declarations and
     * replaces those with GLSL code for setting `texture = texture2D(uMainSampler[i], outTexCoord)`
     * and injecting the `getSampler` glsl function.
     *
     * @function Phaser.Renderer.WebGL.Utils.parseFragmentShaderMaxTextures
     * @since 3.50.0
     *
     * @param {string} fragmentShaderSource - The Fragment Shader source code to operate on.
     * @param {number} maxTextures - The number of maxTextures value.
     *
     * @return {string} The modified Fragment Shader source.
     */
    parseFragmentShaderMaxTextures: function (fragmentShaderSource, maxTextures)
    {
        if (!fragmentShaderSource)
        {
            return '';
        }

        /*
        var src = 'vec4 getSampler (int index, vec2 uv) {';

        for (var i = 0; i < maxTextures; i++)
        {
            if (i > 0 && i < maxTextures)
            {
                src += '\nelse ';
            }

            src += 'if (index == ' + i + ') { return texture2D(uMainSampler[' + i + '], uv); }';
        }

        src += '\nreturn vec4(0);\n}';

        fragmentShaderSource = fragmentShaderSource.replace(/%getSampler%/gi, src);
        */

        return fragmentShaderSource.replace(/%count%/gi, maxTextures.toString());
    }
};
<|endoftext|>