;(function(e,t,n){function i(n,s){if(!t[n]){if(!e[n]){var o=typeof require=="function"&&require;if(!s&&o)return o(n,!0);if(r)return r(n,!0);throw new Error("Cannot find module '"+n+"'")}var u=t[n]={exports:{}};e[n][0].call(u.exports,function(t){var r=e[n][1][t];return i(r?r:t)},u,u.exports)}return t[n].exports}var r=typeof require=="function"&&require;for(var s=0;s= 0; --i) { amplitude *= persistence; totalAmplitude += amplitude; for (var j = 0; j < perlinNoise.length; ++j) { perlinNoise[j] = perlinNoise[j] || 0; perlinNoise[j] += smoothNoiseList[i][j] * amplitude; } } // normalization for (i = 0; i < perlinNoise.length; ++i) { perlinNoise[i] /= totalAmplitude; } return perlinNoise; function generateSmoothNoise(octave) { var noise = new Array(width * height); var samplePeriod = Math.pow(2, octave); var sampleFrequency = 1 / samplePeriod; var noiseIndex = 0; for (var y = 0; y < height; ++y) { var sampleY0 = Math.floor(y / samplePeriod) * samplePeriod; var sampleY1 = (sampleY0 + samplePeriod) % height; var vertBlend = (y - sampleY0) * sampleFrequency; for (var x = 0; x < width; ++x) { var sampleX0 = Math.floor(x / samplePeriod) * samplePeriod; var sampleX1 = (sampleX0 + samplePeriod) % width; var horizBlend = (x - sampleX0) * sampleFrequency; // blend top two corners var top = interpolate(whiteNoise[sampleY0 * width + sampleX0], whiteNoise[sampleY1 * width + sampleX0], vertBlend); // blend bottom two corners var bottom = interpolate(whiteNoise[sampleY0 * width + sampleX1], whiteNoise[sampleY1 * width + sampleX1], vertBlend); // final blend noise[noiseIndex] = interpolate(top, bottom, horizBlend); noiseIndex += 1; } } return noise; } function generateWhiteNoise() { var noise = new Array(width * height); for (var i = 0; i < noise.length; ++i) { noise[i] = Math.random(); } return noise; } function interpolate(x0, x1, alpha) { return x0 * (1 - alpha) + alpha * x1; } } },{}],2:[function(require,module,exports){ var chem = require("chem"); var Vec2d = chem.vec2d.Vec2d; var perlin = require('./perlin'); var Color = require('./color'); var commaIt = require('comma-it').commaIt; var STREAMER_SPEED = 0.40; var STREAMER_ARRIVE_THRESHOLD = 1; var STREAMER_RADIUS = 12; var STREAMER_MAX_PEOPLE = 1000; var STREAMER_MIN_RESPAWN_TIME = 1; // min time between streamer events var streamer_time_counter = 0; var MAX_CONCURRENT_STREAMERS = 15; var MAX_CELL_POPULATION = 10000; var PLAGUE_KILL_RATE = 0.0025; var PLAGUE_KILL_CONSTANT = 0.005; var STREAMER_SCHEDULE_PROBABILITY = 0.00025; var INFECT_CONSTANT = 0.000004; var NUM_INITIAL_INFECTIONS = 4; var GUN_RADIUS = 6; var GUN_INFECT_KILL_RATE = 0.20; var GUN_INFECT_KILL_CONSTANT = 20; var GUN_HEALTHY_KILL_RATE = 0.04; var GUN_HEALTHY_KILL_CONSTANT = 4; var populationCenters = []; var worldSize = new Vec2d(480, 480); var worldPos = new Vec2d(240, 0); var uiWeapons = [ { name: "gun", crosshair: "crosshair", radius: GUN_RADIUS, }, { name: "bomb", crosshair: "bomb-crosshair", radius: 30, }, { name: "wall", crosshair: null, }, { name: "disinfecticide", crosshair: 'ch-disinfecticide', radius: 30, }, { name: "curebomb", crosshair: null, }, ]; var selectedWeapon = null; var stepCounter = 0; var stepThreshold = 10; var colorUninhabited = new Color("#ffffff"); var colorHealthyAlive = new Color("#ff83e9"); var colorInfectedAlive = new Color("#e13e3a"); var colorInfectedDead = new Color("#008817"); var colorHealthyDead = new Color("#585858"); var colorCasualties = new Color("#ECBD1C"); var PIE_STAT_HEALTHY = 0; var PIE_STAT_INFECTED = 1; var PIE_STAT_DEAD = 2; var PIE_STAT_CASUALTIES = 3; var pie = [ { name: "Healthy", color: colorHealthyAlive.toString(), stat: 0, }, { name: "Infected", color: colorInfectedAlive.toString(), stat: 0, }, { name: "Dead", color: colorHealthyDead.toString(), stat: 0, }, { name: "Casualties", color: colorCasualties.toString(), stat: 0, }, ]; chem.onReady(function () { var canvas = document.getElementById("game"); var engine = new chem.Engine(canvas); var batch = new chem.Batch(); var streamers = []; var selectionSprite = new chem.Sprite('selection', {batch: batch, zOrder: 1}); var screamingSound = new chem.Sound('sfx/screaming.ogg'); var gunSound = new chem.Sound('sfx/gun.ogg'); var explosionSound = new chem.Sound('sfx/boom.ogg'); var imageData = engine.context.getImageData(worldPos.x, worldPos.x, worldSize.x, worldSize.y); var cells = initializeCells(); initializeInfections(); var currentCrosshair = null; var pieMargin = 10; var pieRadius = (worldPos.x - pieMargin * 2) / 2; var pieLoc = new Vec2d(pieMargin + pieRadius, engine.size.y - pieRadius - pieMargin); renderAllCells(); setUpUi(); selectWeapon(uiWeapons[0]); engine.on('mousemove', function() { var showCursor = engine.mousePos.x < worldPos.x || currentCrosshair == null; canvas.style.cursor = showCursor ? "default" : "none"; if (currentCrosshair != null) { currentCrosshair.pos = engine.mousePos; currentCrosshair.setVisible(!showCursor); } }); engine.on('update', function (dt, dx) { if (engine.buttonJustPressed(chem.button.MouseLeft)) { handleMouseLeft(); } streamer_time_counter += dt; streamers.forEach(function(streamer) { if (streamer.deleted) return; streamer.pos.add(streamer.dir.scaled(STREAMER_SPEED)); streamer.sprite.pos = streamer.pos.plus(worldPos); if (streamer.pos.distance(streamer.dest) < STREAMER_ARRIVE_THRESHOLD) { streamer.sprite.setAnimationName('biohazard'); streamer.sprite.setFrameIndex(0); //streamer.dest.x, streamer.dest.y var destCell = cellAt(streamer.dest.x, streamer.dest.y); if (destCell.canInfect()) { destCell.addInfected(streamer.populationInfectedAlive); destCell.populationHealthyAlive += streamer.populationHealthyAlive; renderCell(destCell.index); } streamer.deleted = true; streamer.sprite.on('animationend', function() { streamer.sprite.delete(); }); } }); stepCounter += 1; if (stepCounter > stepThreshold) { computePlagueSpread(); updateCells(); cullDeletedStreamers(); stepCounter -= stepThreshold; } }); engine.on('draw', function (context) { // clear the part that isn't covered by putImageData context.fillStyle = "#e6e6e6"; context.fillRect(0, 0, worldPos.x, engine.size.y); context.putImageData(imageData, worldPos.x, worldPos.y); // draw sprites engine.draw(batch); drawStatsPieChart(context, pieLoc.x, pieLoc.y, pieRadius); var spotInfoSize = new Vec2d(pieRadius * 2, 90); var spotInfoLoc = pieLoc.offset(-pieRadius, -pieRadius - pieMargin - spotInfoSize.y); drawSpotInfo(context, spotInfoLoc, spotInfoSize); // draw a little fps counter in the corner context.fillStyle = '#000000' engine.drawFps(); }); engine.start(); canvas.focus(); function handleMouseLeft() { // selecting a weapon for (var i = 0; i < uiWeapons.length; ++i) { var uiWeapon = uiWeapons[i]; if (uiWeapon.sprite.hitTest(engine.mousePos)) { selectWeapon(uiWeapon); return; } } if (selectedWeapon === 'gun') { shootGun(); } else if (selectedWeapon === 'bomb') { shootBomb(); } } function shootBomb() { var targetPos = engine.mousePos.minus(worldPos); var sprite = new chem.Sprite('drop-bomb', { batch: batch, pos: engine.mousePos.clone(), }); sprite.once('animationend', function() { sprite.setAnimationName('hbombexplode'); sprite.setFrameIndex(0); sprite.once('animationend', function() { sprite.delete(); }); }); } function shootGun() { var targetPos = engine.mousePos.minus(worldPos); var casualties = 0; rasterCircle(targetPos.x, targetPos.y, GUN_RADIUS, function(x, y) { if (!inBounds(new Vec2d(x, y))) return; var cell = cellAt(x, y); var infectedKillAmt = Math.min(cell.populationInfectedAlive, cell.populationInfectedAlive * GUN_INFECT_KILL_RATE + GUN_INFECT_KILL_CONSTANT); cell.populationInfectedAlive -= infectedKillAmt; cell.populationHealthyDead += infectedKillAmt; pie[PIE_STAT_CASUALTIES].stat += infectedKillAmt; casualties += infectedKillAmt; var healthyKillAmt = Math.min(cell.populationHealthyAlive, cell.populationHealthyAlive * GUN_HEALTHY_KILL_RATE + GUN_HEALTHY_KILL_CONSTANT); cell.populationHealthyAlive -= healthyKillAmt; cell.populationHealthyDead += healthyKillAmt; pie[PIE_STAT_CASUALTIES].stat += healthyKillAmt; casualties += healthyKillAmt; renderCell(cellIndex(x, y)); }); // check if we killed any streamers var streamerKillCount = 0; streamers.forEach(function(streamer) { if (streamer.deleted) return; if (targetPos.distance(streamer.pos) < STREAMER_RADIUS) { streamerKillCount += 1; streamer.deleted = true; streamer.sprite.setAnimationName('explosion'); streamer.sprite.setFrameIndex(0); streamer.sprite.on('animationend', function() { streamer.sprite.delete(); }); var streamerCell = cellAt(Math.floor(streamer.pos.x), Math.floor(streamer.pos.y)); streamerCell.populationHealthyDead += (streamer.populationHealthyAlive + streamer.populationInfectedAlive); renderCell(streamerCell.index); } pie[PIE_STAT_CASUALTIES].stat += (streamer.populationHealthyAlive + streamer.populationInfectedAlive); }); gunSound.play(); if (casualties >= 1) screamingSound.play(); } function cullDeletedStreamers() { for (var i = 0; i < streamers.length; ++i) { if (streamers[i].deleted) { streamers.splice(i, 1); i -= 1; } } } function drawSpotInfo(context, pos, size) { var items = []; if (engine.mousePos.distance(pieLoc) < pieRadius) { pie.forEach(function(pieItem) { if (pieItem.stat >= 1) { items.push({ color: pieItem.color, caption: pieItem.name + ": " + displayNumber(pieItem.stat), }); } }); } else { var relMousePos = engine.mousePos.minus(worldPos); if (! inBounds(relMousePos)) return; var cell = cellAt(relMousePos.x, relMousePos.y); if (cell.totalPopulation() === 0) { items.push({ color: colorUninhabited.toString(), caption: "Uninhabited", }); } if (cell.populationHealthyAlive >= 1) { items.push({ color: colorHealthyAlive.toString(), caption: "Healthy: " + displayNumber(cell.populationHealthyAlive), }); } if (cell.populationInfectedAlive >= 1) { items.push({ color: colorInfectedAlive.toString(), caption: "Infected: " + displayNumber(cell.populationInfectedAlive), }); } if (cell.populationHealthyDead >= 1) { items.push({ color: colorHealthyDead.toString(), caption: "Dead: " + displayNumber(cell.populationHealthyDead), }); } if (cell.populationInfectedDead >= 1) { items.push({ color: colorInfectedDead.toString(), caption: "Rotting Corpses: " + displayNumber(cell.populationInfectedDead), }); } } if (items.length === 0) return; var margin = 4; var boxSize = 16; var y = pos.y + margin; items.forEach(function(item) { context.beginPath(); context.rect(pos.x + margin, y, boxSize, boxSize); context.closePath(); context.fillStyle = item.color; context.fill(); context.strokeStyle = "#000000"; context.lineWidth = 1; context.stroke(); context.font = "13pt Arial"; context.textAlign = "left"; context.fillStyle = "#000000"; context.fillText(item.caption, pos.x + margin + boxSize + margin, y + boxSize); y += boxSize + margin; }); context.beginPath(); context.rect(pos.x, pos.y, size.x, size.y); context.closePath(); context.strokeStyle = "#000000"; context.lineWidth = 2; context.stroke(); } function drawStatsPieChart(context, x, y, radius) { var total = 0; var i; for (i = 0; i < pie.length; ++i) { total += pie[i].stat; } var r = 0; for (i = 0; i < pie.length; ++i) { var amt = pie[i].stat / total; var newR = r + amt * Math.PI * 2; context.beginPath(); context.moveTo(x, y); context.lineTo(x + Math.cos(r) * radius, y + Math.sin(r) * radius); context.arc(x, y, radius, r, newR); context.lineTo(x, y); context.closePath(); context.fillStyle = pie[i].color; context.fill(); context.strokeStyle = "#000000"; context.lineWidth = 1; context.stroke(); r = newR; } // outline context.beginPath(); context.arc(x, y, radius, 0, 2 * Math.PI, false); context.closePath(); context.strokeStyle = "#000000"; context.lineWidth = 2; context.stroke(); } function initializeCells() { var noise = perlin.generatePerlinNoise(worldSize.x, worldSize.y, { octaveCount: 6, amplitude: 0.2, persistence: 0.24, }); var cells = new Array(worldSize.x * worldSize.y); for (var i = 0; i < cells.length; ++i) { cells[i] = new Cell(i); var n = noise[i]; if (n > 0.70) { cells[i].addHealthyPopulation( ((n-0.7)/0.3)*MAX_CELL_POPULATION ); // staticly initialize targets for streamers to try and go to if (n > 0.80) { populationCenters.push(i); } } } return cells; } function renderAllCells() { for (var i = 0; i < cells.length; ++i) { renderCell(i); } } function initializeInfections() { centers = findHealthyPopulationCenters(10,10); // choose random centers to infect; var infectedIdx = new Array(NUM_INITIAL_INFECTIONS); for (var i=0; i maxHealthy && c.populationInfectedAlive <= 0) { maxHealthy = c.populationHealthyAlive; maxHealthyIdx = c.index; } } } if (maxHealthyIdx > 0) { populationCenters.push(maxHealthyIdx); } } } return populationCenters; } function renderCell(i) { var cell = cells[i]; var index = i * 4; var density = 255 - (cell.density() * 255); var blendConstant = 0.2; if (cell.populationInfectedAlive >= 1) { imageData.data[index + 0] = Math.floor(density*blendConstant + colorInfectedAlive.red *(1-blendConstant)); imageData.data[index + 1] = Math.floor(density*blendConstant + colorInfectedAlive.green*(1-blendConstant)); imageData.data[index + 2] = Math.floor(density*blendConstant + colorInfectedAlive.blue *(1-blendConstant)); } else if (cell.populationInfectedDead >= 1) { imageData.data[index + 0] = Math.floor(density*blendConstant + colorInfectedDead.red*(1-blendConstant)); imageData.data[index + 1] = Math.floor(density*blendConstant + colorInfectedDead.green*(1-blendConstant)); imageData.data[index + 2] = Math.floor(density*blendConstant + colorInfectedDead.blue*(1-blendConstant)); } else if (cell.populationHealthyAlive >= 1) { imageData.data[index + 0] = Math.floor(density*blendConstant + colorHealthyAlive.red *(1-blendConstant)); imageData.data[index + 1] = Math.floor(density*blendConstant + colorHealthyAlive.green*(1-blendConstant)); imageData.data[index + 2] = Math.floor(density*blendConstant + colorHealthyAlive.blue *(1-blendConstant)); } else if (cell.populationHealthyDead >= 1) { imageData.data[index + 0] = Math.floor(density*blendConstant + colorHealthyDead.red *(1-blendConstant)); imageData.data[index + 1] = Math.floor(density*blendConstant + colorHealthyDead.green*(1-blendConstant)); imageData.data[index + 2] = Math.floor(density*blendConstant + colorHealthyDead.blue *(1-blendConstant)); } else { imageData.data[index + 0] = density; // red imageData.data[index + 1] = density; // green imageData.data[index + 2] = density; // blue } imageData.data[index + 3] = 255; // alpha } function cellAt(x, y) { return cells[cellIndex(x, y)]; } function cellIndex(x, y) { return y * worldSize.x + x; } function inBounds(v) { return v.x >= 0 && v.y >= 0 && v.x < worldSize.x && v.y < worldSize.y; } function setUpUi() { var margin = 10; var pos = new Vec2d(margin, margin); var right = worldPos.x - 2 * margin; for (var i = 0; i < uiWeapons.length; ++i) { var uiWeapon = uiWeapons[i]; uiWeapon.sprite = new chem.Sprite(uiWeapon.name, { batch: batch, pos: pos.clone(), }); pos.x += uiWeapon.sprite.size.x; if (pos.x + uiWeapon.sprite.size.x >= right) { pos.x = margin; pos.y += uiWeapon.sprite.size.y; } if (uiWeapon.crosshair) { uiWeapon.crosshairSprite = new chem.Sprite(uiWeapon.crosshair, { batch: batch, zOrder: 1, visible: false, }); } } } function selectWeapon(target) { uiWeapons.forEach(function(uiWeapon) { uiWeapon.selected = false; }); target.selected = true; selectedWeapon = target.name; selectionSprite.pos = target.sprite.pos; selectionSprite.setFrameIndex(0); if (currentCrosshair != null) { currentCrosshair.setVisible(false); } currentCrosshair = target.crosshairSprite; if (currentCrosshair != null) { currentCrosshair.scale.x = (target.radius * 2) / currentCrosshair.size.x; currentCrosshair.scale.y = (target.radius * 2) / currentCrosshair.size.y; } } function updateCells() { var i = 0; for (var y = 0; y < worldSize.y; ++y) { for (var x = 0; x < worldSize.x; ++x) { cells[i].justInfected = false; if (cells[i].computeUpdate()) renderCell(i); // Infected streamers if (streamer_time_counter > STREAMER_MIN_RESPAWN_TIME && cells[i].populationInfectedAlive > 0.3 * cells[i].totalPopulation() && Math.random() < STREAMER_SCHEDULE_PROBABILITY && streamers.length < MAX_CONCURRENT_STREAMERS) { var sprite = new chem.Sprite("infected-car", { batch: batch }); var healthyCenters = findHealthyPopulationCenters(50,50); var destIdx; if (healthyCenters.length > 0) destIdx = healthyCenters[ Math.floor( Math.random() * healthyCenters.length ) ]; else { // no more healthy places? go to places that used to be... var populationCenterIdx = Math.floor( Math.random() * populationCenters.length ); destIdx = populationCenters[populationCenterIdx] } var numInfected = Math.min( cells[i].populationInfectedAlive, STREAMER_MAX_PEOPLE ); cells[i].populationInfectedAlive -= numInfected; renderCell(i); var destLoc = new Vec2d(destIdx%worldSize.x, Math.floor(destIdx/worldSize.x)); var srcLoc = new Vec2d(x, y); streamers.push(new Streamer(srcLoc, destLoc, sprite, 0, numInfected)); streamer_time_counter = 0; } i += 1; } } } function numHealthyInStreamers() { var totalHealthy = 0; for (var i=0; i 0; } Cell.prototype.density = function() { return this.totalPopulation() / MAX_CELL_POPULATION; }; Cell.prototype.totalPopulation = function() { return this.populationHealthyAlive + this.populationInfectedAlive + this.populationHealthyDead + this.populationInfectedDead; }; Cell.prototype.addHealthyPopulation = function(population) { this.populationHealthyAlive += population; if (this.totalPopulation() > MAX_CELL_POPULATION) { this.populationHealthyAlive -= this.totalPopulation() - MAX_CELL_POPULATION; } } Cell.prototype.canInfect = function() { return this.populationHealthyAlive > 0; } Cell.prototype.isInfected = function() { return this.populationInfectedAlive > 0; } Cell.prototype.addInfected = function(amount) { this.populationInfectedAlive += amount; this.justInfected = true; } Cell.prototype.infect = function(amount) { var trueAmount = Math.min(amount, this.populationHealthyAlive); this.populationHealthyAlive -= trueAmount; this.populationInfectedAlive += trueAmount; this.justInfected = true; } Cell.prototype.setPopulation = function(healthy_ppl) { this.populationHealthyAlive = healthy_ppl; this.populationInfectedAlive = 0; } function rasterCircle(x0, y0, radius, cb) { var x = radius; var y = 0; var xChange = 1 - (radius * 2); var yChange = 0; var radiusError = 0; var i; while(x >= y) { for (i = x0 - x; i <= x0 + x; ++i) { cb(i, y + y0); cb(i, -y + y0); } for (i = x0 - y; i <= x0 + y; ++i) { cb(i, x + y0); cb(i, -x + y0); } y++; radiusError += yChange; yChange += 2; if (radiusError * 2 + xChange > 0) { x--; radiusError += xChange; xChange += 2; } } } function Streamer(pos, dest, sprite, num_healthy, num_infected) { this.pos = pos; this.dest = dest; this.dir = this.dest.minus(this.pos).normalize(); this.sprite = sprite; this.sprite.pos = pos.plus(worldPos); this.sprite.rotation = this.dir.angle(); this.deleted = false; this.populationHealthyAlive = num_healthy; this.populationInfectedAlive = num_infected; } function displayNumber(n) { return commaIt(Math.floor(n), {thousandSeperator: ','}); } },{"./color":3,"./perlin":1,"chem":4,"comma-it":5}],5:[function(require,module,exports){ // Simplest possible solution to turn numbers into nicely seperated amounts: // 1234 => 1 234 // When addPrecision is set to false (or default is used = false), precision is stripped function commaIt(number, options) { //Set up default seperators addPrecision = (options && options['addPrecision'] || false); thousandSeperator = (options && options['thousandSeperator'] || ' '); decimalSeperator = (options && options['decimalSeperator'] || ','); var replacmentRegex = '$1' + thousandSeperator; //Conversion to string and default return managment number = number.toString(); if(number.length === 0) return '0' + decimalSeperator + '00'; //Actual parsing of two side of the number var amount = number.split(decimalSeperator)[0]; var floats = addPrecision ? (decimalSeperator + ((number.split(decimalSeperator)[1] || '') + '00').substr(0, 2)) : ''; var numberified = amount.split('').reverse().join('').replace(/(\d{3}(?!$))/g, replacmentRegex).split('').reverse().join(''); return numberified + floats; } exports.commaIt = commaIt; },{}],3:[function(require,module,exports){ var zfill = require('zfill'); module.exports = Color; function Color(nameOrRed, green, blue) { if (green != null) { this.red = nameOrRed; this.green = green; this.blue = blue; } else { if (nameOrRed[0] === "#") nameOrRed = nameOrRed.substring(1); this.red = parseInt(nameOrRed.substring(0, 2), 16); this.green = parseInt(nameOrRed.substring(2, 4), 16); this.blue = parseInt(nameOrRed.substring(4, 6), 16); } } Color.prototype.toString = function() { return "#" + zfill(this.red.toString(16), 2) + zfill(this.green.toString(16), 2) + zfill(this.blue.toString(16), 2); }; },{"zfill":6}],7:[function(require,module,exports){ var KEY_OFFSET = 0; var MOUSE_OFFSET = 256; exports.KEY_OFFSET = KEY_OFFSET; exports.MOUSE_OFFSET = MOUSE_OFFSET; var Key = { Backspace: 8, Tab: 9, Enter: 13, Shift: 16, Ctrl: 17, Alt: 18, Pause: 19, Break: 19, CapsLock: 20, Escape: 27, Space: 32, PageUp: 33, PageDown: 34, End: 35, Home: 36, Left: 37, Up: 38, Right: 39, Down: 40, Insert: 45, Delete: 46, 0: 48, 1: 49, 2: 50, 3: 51, 4: 52, A: 65, B: 66, C: 67, D: 68, E: 69, F: 70, G: 71, H: 72, I: 73, J: 74, K: 75, L: 76, M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82, S: 83, T: 84, U: 85, V: 86, W: 87, X: 88, Y: 89, Z: 90, MetaLeft: 91, MetaRight: 92, Select: 93, Numpad0: 96, Numpad1: 97, Numpad2: 98, Numpad3: 99, Numpad4: 100, Numpad5: 101, Numpad6: 102, Numpad7: 103, Numpad8: 104, Numpad9: 105, Multiply: 106, Add: 107, Subtract: 109, Decimal: 110, Divide: 111, F1: 112, F2: 113, F3: 114, F4: 115, F5: 116, F6: 117, F7: 118, F8: 119, F9: 120, F10: 121, F11: 122, F12: 123, NumLock: 144, ScrollLock: 145, Semicolon: 186, EqualSign: 187, Comma: 188, Dash: 189, Period: 190, SlashForward: 191, Grave: 192, BracketOpen: 219, SlashBack: 220, BracketClose: 221, Quote: 222 }; var Mouse = { Left: 1, Middle: 2, Right: 3 }; // map both Key and Mouse into Button var btnName, val; for (btnName in Key) { val = Key[btnName]; exports["Key" + btnName] = KEY_OFFSET + val; } for (btnName in Mouse) { val = Mouse[btnName]; exports["Mouse" + btnName] = MOUSE_OFFSET + val; } },{}],8:[function(require,module,exports){ module.exports = Sound; function Sound(url) { this.audioPool = [new Audio(url)]; } function audioReadyToPlay(audio) { return audio.currentTime === 0 || audio.currentTime === audio.duration; } Sound.prototype.play = function() { for (var i = 0; i < this.audioPool.length; ++i) { var audio = this.audioPool[i]; if (audioReadyToPlay(audio)) { audio.play(); return audio; } } var newAudio = new Audio(this.audioPool[0].currentSrc); newAudio.play(); this.audioPool.push(newAudio); }; },{}],9:[function(require,module,exports){ module.exports = Batch; function Batch() { // indexed by zOrder this.sprites = []; } Batch.prototype.add = function(sprite) { if (sprite.batch) sprite.batch.remove(sprite); sprite.batch = this; if (sprite.visible) { var o = this.sprites[sprite.zOrder]; if (! o) o = this.sprites[sprite.zOrder] = {}; o[sprite.id] = sprite; } }; Batch.prototype.remove = function(sprite) { var o = this.sprites[sprite.zOrder]; if (o) delete o[sprite.id]; }; },{}],6:[function(require,module,exports){ module.exports = zfill; function zfill(number, size) { number = number.toString(); while (number.length < size) number = "0" + number; return number; } },{}],4:[function(require,module,exports){ var resources = require('./lib/resources'); module.exports = { vec2d: require('vec2d'), Engine: require('./lib/engine'), button: require('./lib/button'), Sound: require('./lib/sound'), Sprite: require('./lib/sprite'), Batch: require('./lib/batch'), resources: resources, onReady: resources.onReady, }; resources.bootstrap(); },{"./lib/batch":9,"./lib/button":7,"./lib/engine":11,"./lib/resources":10,"./lib/sound":8,"./lib/sprite":12,"vec2d":13}],14:[function(require,module,exports){ // shim for using process in browser var process = module.exports = {}; process.nextTick = (function () { var canSetImmediate = typeof window !== 'undefined' && window.setImmediate; var canPost = typeof window !== 'undefined' && window.postMessage && window.addEventListener ; if (canSetImmediate) { return function (f) { return window.setImmediate(f) }; } if (canPost) { var queue = []; window.addEventListener('message', function (ev) { if (ev.source === window && ev.data === 'process-tick') { ev.stopPropagation(); if (queue.length > 0) { var fn = queue.shift(); fn(); } } }, true); return function nextTick(fn) { queue.push(fn); window.postMessage('process-tick', '*'); }; } return function nextTick(fn) { setTimeout(fn, 0); }; })(); process.title = 'browser'; process.browser = true; process.env = {}; process.argv = []; process.binding = function (name) { throw new Error('process.binding is not supported'); } // TODO(shtylman) process.cwd = function () { return '/' }; process.chdir = function (dir) { throw new Error('process.chdir is not supported'); }; },{}],15:[function(require,module,exports){ (function(process){if (!process.EventEmitter) process.EventEmitter = function () {}; var EventEmitter = exports.EventEmitter = process.EventEmitter; var isArray = typeof Array.isArray === 'function' ? Array.isArray : function (xs) { return Object.prototype.toString.call(xs) === '[object Array]' } ; function indexOf (xs, x) { if (xs.indexOf) return xs.indexOf(x); for (var i = 0; i < xs.length; i++) { if (x === xs[i]) return i; } return -1; } // By default EventEmitters will print a warning if more than // 10 listeners are added to it. This is a useful default which // helps finding memory leaks. // // Obviously not all Emitters should be limited to 10. This function allows // that to be increased. Set to zero for unlimited. var defaultMaxListeners = 10; EventEmitter.prototype.setMaxListeners = function(n) { if (!this._events) this._events = {}; this._events.maxListeners = n; }; EventEmitter.prototype.emit = function(type) { // If there is no 'error' event listener then throw. if (type === 'error') { if (!this._events || !this._events.error || (isArray(this._events.error) && !this._events.error.length)) { if (arguments[1] instanceof Error) { throw arguments[1]; // Unhandled 'error' event } else { throw new Error("Uncaught, unspecified 'error' event."); } return false; } } if (!this._events) return false; var handler = this._events[type]; if (!handler) return false; if (typeof handler == 'function') { switch (arguments.length) { // fast cases case 1: handler.call(this); break; case 2: handler.call(this, arguments[1]); break; case 3: handler.call(this, arguments[1], arguments[2]); break; // slower default: var args = Array.prototype.slice.call(arguments, 1); handler.apply(this, args); } return true; } else if (isArray(handler)) { var args = Array.prototype.slice.call(arguments, 1); var listeners = handler.slice(); for (var i = 0, l = listeners.length; i < l; i++) { listeners[i].apply(this, args); } return true; } else { return false; } }; // EventEmitter is defined in src/node_events.cc // EventEmitter.prototype.emit() is also defined there. EventEmitter.prototype.addListener = function(type, listener) { if ('function' !== typeof listener) { throw new Error('addListener only takes instances of Function'); } if (!this._events) this._events = {}; // To avoid recursion in the case that type == "newListeners"! Before // adding it to the listeners, first emit "newListeners". this.emit('newListener', type, listener); if (!this._events[type]) { // Optimize the case of one listener. Don't need the extra array object. this._events[type] = listener; } else if (isArray(this._events[type])) { // Check for listener leak if (!this._events[type].warned) { var m; if (this._events.maxListeners !== undefined) { m = this._events.maxListeners; } else { m = defaultMaxListeners; } if (m && m > 0 && this._events[type].length > m) { this._events[type].warned = true; console.error('(node) warning: possible EventEmitter memory ' + 'leak detected. %d listeners added. ' + 'Use emitter.setMaxListeners() to increase limit.', this._events[type].length); console.trace(); } } // If we've already got an array, just append. this._events[type].push(listener); } else { // Adding the second element, need to change to array. this._events[type] = [this._events[type], listener]; } return this; }; EventEmitter.prototype.on = EventEmitter.prototype.addListener; EventEmitter.prototype.once = function(type, listener) { var self = this; self.on(type, function g() { self.removeListener(type, g); listener.apply(this, arguments); }); return this; }; EventEmitter.prototype.removeListener = function(type, listener) { if ('function' !== typeof listener) { throw new Error('removeListener only takes instances of Function'); } // does not use listeners(), so no side effect of creating _events[type] if (!this._events || !this._events[type]) return this; var list = this._events[type]; if (isArray(list)) { var i = indexOf(list, listener); if (i < 0) return this; list.splice(i, 1); if (list.length == 0) delete this._events[type]; } else if (this._events[type] === listener) { delete this._events[type]; } return this; }; EventEmitter.prototype.removeAllListeners = function(type) { if (arguments.length === 0) { this._events = {}; return this; } // does not use listeners(), so no side effect of creating _events[type] if (type && this._events && this._events[type]) this._events[type] = null; return this; }; EventEmitter.prototype.listeners = function(type) { if (!this._events) this._events = {}; if (!this._events[type]) this._events[type] = []; if (!isArray(this._events[type])) { this._events[type] = [this._events[type]]; } return this._events[type]; }; })(require("__browserify_process")) },{"__browserify_process":14}],16:[function(require,module,exports){ var events = require('events'); exports.isArray = isArray; exports.isDate = function(obj){return Object.prototype.toString.call(obj) === '[object Date]'}; exports.isRegExp = function(obj){return Object.prototype.toString.call(obj) === '[object RegExp]'}; exports.print = function () {}; exports.puts = function () {}; exports.debug = function() {}; exports.inspect = function(obj, showHidden, depth, colors) { var seen = []; var stylize = function(str, styleType) { // http://en.wikipedia.org/wiki/ANSI_escape_code#graphics var styles = { 'bold' : [1, 22], 'italic' : [3, 23], 'underline' : [4, 24], 'inverse' : [7, 27], 'white' : [37, 39], 'grey' : [90, 39], 'black' : [30, 39], 'blue' : [34, 39], 'cyan' : [36, 39], 'green' : [32, 39], 'magenta' : [35, 39], 'red' : [31, 39], 'yellow' : [33, 39] }; var style = { 'special': 'cyan', 'number': 'blue', 'boolean': 'yellow', 'undefined': 'grey', 'null': 'bold', 'string': 'green', 'date': 'magenta', // "name": intentionally not styling 'regexp': 'red' }[styleType]; if (style) { return '\033[' + styles[style][0] + 'm' + str + '\033[' + styles[style][1] + 'm'; } else { return str; } }; if (! colors) { stylize = function(str, styleType) { return str; }; } function format(value, recurseTimes) { // Provide a hook for user-specified inspect functions. // Check that value is an object with an inspect function on it if (value && typeof value.inspect === 'function' && // Filter out the util module, it's inspect function is special value !== exports && // Also filter out any prototype objects using the circular check. !(value.constructor && value.constructor.prototype === value)) { return value.inspect(recurseTimes); } // Primitive types cannot have properties switch (typeof value) { case 'undefined': return stylize('undefined', 'undefined'); case 'string': var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') .replace(/'/g, "\\'") .replace(/\\"/g, '"') + '\''; return stylize(simple, 'string'); case 'number': return stylize('' + value, 'number'); case 'boolean': return stylize('' + value, 'boolean'); } // For some reason typeof null is "object", so special case here. if (value === null) { return stylize('null', 'null'); } // Look up the keys of the object. var visible_keys = Object_keys(value); var keys = showHidden ? Object_getOwnPropertyNames(value) : visible_keys; // Functions without properties can be shortcutted. if (typeof value === 'function' && keys.length === 0) { if (isRegExp(value)) { return stylize('' + value, 'regexp'); } else { var name = value.name ? ': ' + value.name : ''; return stylize('[Function' + name + ']', 'special'); } } // Dates without properties can be shortcutted if (isDate(value) && keys.length === 0) { return stylize(value.toUTCString(), 'date'); } var base, type, braces; // Determine the object type if (isArray(value)) { type = 'Array'; braces = ['[', ']']; } else { type = 'Object'; braces = ['{', '}']; } // Make functions say that they are functions if (typeof value === 'function') { var n = value.name ? ': ' + value.name : ''; base = (isRegExp(value)) ? ' ' + value : ' [Function' + n + ']'; } else { base = ''; } // Make dates with properties first say the date if (isDate(value)) { base = ' ' + value.toUTCString(); } if (keys.length === 0) { return braces[0] + base + braces[1]; } if (recurseTimes < 0) { if (isRegExp(value)) { return stylize('' + value, 'regexp'); } else { return stylize('[Object]', 'special'); } } seen.push(value); var output = keys.map(function(key) { var name, str; if (value.__lookupGetter__) { if (value.__lookupGetter__(key)) { if (value.__lookupSetter__(key)) { str = stylize('[Getter/Setter]', 'special'); } else { str = stylize('[Getter]', 'special'); } } else { if (value.__lookupSetter__(key)) { str = stylize('[Setter]', 'special'); } } } if (visible_keys.indexOf(key) < 0) { name = '[' + key + ']'; } if (!str) { if (seen.indexOf(value[key]) < 0) { if (recurseTimes === null) { str = format(value[key]); } else { str = format(value[key], recurseTimes - 1); } if (str.indexOf('\n') > -1) { if (isArray(value)) { str = str.split('\n').map(function(line) { return ' ' + line; }).join('\n').substr(2); } else { str = '\n' + str.split('\n').map(function(line) { return ' ' + line; }).join('\n'); } } } else { str = stylize('[Circular]', 'special'); } } if (typeof name === 'undefined') { if (type === 'Array' && key.match(/^\d+$/)) { return str; } name = JSON.stringify('' + key); if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { name = name.substr(1, name.length - 2); name = stylize(name, 'name'); } else { name = name.replace(/'/g, "\\'") .replace(/\\"/g, '"') .replace(/(^"|"$)/g, "'"); name = stylize(name, 'string'); } } return name + ': ' + str; }); seen.pop(); var numLinesEst = 0; var length = output.reduce(function(prev, cur) { numLinesEst++; if (cur.indexOf('\n') >= 0) numLinesEst++; return prev + cur.length + 1; }, 0); if (length > 50) { output = braces[0] + (base === '' ? '' : base + '\n ') + ' ' + output.join(',\n ') + ' ' + braces[1]; } else { output = braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; } return output; } return format(obj, (typeof depth === 'undefined' ? 2 : depth)); }; function isArray(ar) { return ar instanceof Array || Array.isArray(ar) || (ar && ar !== Object.prototype && isArray(ar.__proto__)); } function isRegExp(re) { return re instanceof RegExp || (typeof re === 'object' && Object.prototype.toString.call(re) === '[object RegExp]'); } function isDate(d) { if (d instanceof Date) return true; if (typeof d !== 'object') return false; var properties = Date.prototype && Object_getOwnPropertyNames(Date.prototype); var proto = d.__proto__ && Object_getOwnPropertyNames(d.__proto__); return JSON.stringify(proto) === JSON.stringify(properties); } function pad(n) { return n < 10 ? '0' + n.toString(10) : n.toString(10); } var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; // 26 Feb 16:19:34 function timestamp() { var d = new Date(); var time = [pad(d.getHours()), pad(d.getMinutes()), pad(d.getSeconds())].join(':'); return [d.getDate(), months[d.getMonth()], time].join(' '); } exports.log = function (msg) {}; exports.pump = null; var Object_keys = Object.keys || function (obj) { var res = []; for (var key in obj) res.push(key); return res; }; var Object_getOwnPropertyNames = Object.getOwnPropertyNames || function (obj) { var res = []; for (var key in obj) { if (Object.hasOwnProperty.call(obj, key)) res.push(key); } return res; }; var Object_create = Object.create || function (prototype, properties) { // from es5-shim var object; if (prototype === null) { object = { '__proto__' : null }; } else { if (typeof prototype !== 'object') { throw new TypeError( 'typeof prototype[' + (typeof prototype) + '] != \'object\'' ); } var Type = function () {}; Type.prototype = prototype; object = new Type(); object.__proto__ = prototype; } if (typeof properties !== 'undefined' && Object.defineProperties) { Object.defineProperties(object, properties); } return object; }; exports.inherits = function(ctor, superCtor) { ctor.super_ = superCtor; ctor.prototype = Object_create(superCtor.prototype, { constructor: { value: ctor, enumerable: false, writable: true, configurable: true } }); }; var formatRegExp = /%[sdj%]/g; exports.format = function(f) { if (typeof f !== 'string') { var objects = []; for (var i = 0; i < arguments.length; i++) { objects.push(exports.inspect(arguments[i])); } return objects.join(' '); } var i = 1; var args = arguments; var len = args.length; var str = String(f).replace(formatRegExp, function(x) { if (x === '%%') return '%'; if (i >= len) return x; switch (x) { case '%s': return String(args[i++]); case '%d': return Number(args[i++]); case '%j': return JSON.stringify(args[i++]); default: return x; } }); for(var x = args[i]; i < len; x = args[++i]){ if (x === null || typeof x !== 'object') { str += ' ' + x; } else { str += ' ' + exports.inspect(x); } } return str; }; },{"events":15}],13:[function(require,module,exports){ module.exports = v; v.Vec2d = Vec2d; var re = /\((-?[.\d]+), (-?[.\d]+)\)/; function Vec2d(x, y) { this.x = x; this.y = y; } function v(xOrPair, y){ if (xOrPair == null) { return new Vec2d(0, 0, 0); } else if (Array.isArray(xOrPair)) { return new Vec2d(parseFloat(xOrPair[0], 10), parseFloat(xOrPair[1], 10)); } else if (typeof xOrPair === 'object') { return new Vec2d(parseFloat(xOrPair.x, 10), parseFloat(xOrPair.y, 10)); } else if (typeof xOrPair === 'string' && y == null) { var match = xOrPair.match(re); if (match) { return new Vec2d(parseFloat(match[1], 10), parseFloat(match[2], 10)); } else { throw new Error("Vec2d: cannot parse: " + xOrPair); } } else { return new Vec2d(parseFloat(xOrPair, 10), parseFloat(y, 10)); } } Vec2d.prototype.offset = function(dx, dy){ return new Vec2d(this.x + dx, this.y + dy); }; Vec2d.prototype.add = function(other){ this.x += other.x; this.y += other.y; return this; }; Vec2d.prototype.sub = function(other){ this.x -= other.x; this.y -= other.y; return this; }; Vec2d.prototype.plus = function(other){ return this.clone().add(other); }; Vec2d.prototype.minus = function(other){ return this.clone().sub(other); }; Vec2d.prototype.neg = function(){ this.x = -this.x; this.y = -this.y; return this; }; Vec2d.prototype.mult = function(other){ this.x *= other.x; this.y *= other.y; return this; }; Vec2d.prototype.times = function(other){ return this.clone().mult(other); }; Vec2d.prototype.div = function(other){ this.x /= other.x; this.y /= other.y; return this; }; Vec2d.prototype.divBy = function(other){ return this.clone().div(other); }; Vec2d.prototype.scale = function(scalar){ this.x *= scalar; this.y *= scalar; return this; }; Vec2d.prototype.scaled = function(scalar){ return this.clone().scale(scalar); }; Vec2d.prototype.clone = function(){ return new Vec2d(this.x, this.y); }; Vec2d.prototype.apply = function(func){ this.x = func(this.x); this.y = func(this.y); return this; }; Vec2d.prototype.applied = function(func){ return this.clone().apply(func); }; Vec2d.prototype.distanceSqrd = function(other){ var dx = other.x - this.x; var dy = other.y - this.y; return dx * dx + dy * dy; }; Vec2d.prototype.distance = function(other){ return Math.sqrt(this.distanceSqrd(other)); }; Vec2d.prototype.equals = function(other){ return this.x === other.x && this.y === other.y; }; Vec2d.prototype.toString = function(){ return "(" + this.x + ", " + this.y + ")"; }; Vec2d.prototype.lengthSqrd = function(){ return this.x * this.x + this.y * this.y; }; Vec2d.prototype.length = function(){ return Math.sqrt(this.lengthSqrd()); }; Vec2d.prototype.angle = function(){ if (this.lengthSqrd() === 0) { return 0; } else { return Math.atan2(this.y, this.x); } }; Vec2d.prototype.normalize = function(){ var length; length = this.length(); if (length === 0) { return this; } else { return this.scale(1 / length); } }; Vec2d.prototype.normalized = function(){ return this.clone().normalize(); }; Vec2d.prototype.boundMin = function(other){ if (this.x < other.x) { this.x = other.x; } if (this.y < other.y) { return this.y = other.y; } }; Vec2d.prototype.boundMax = function(other){ if (this.x > other.x) { this.x = other.x; } if (this.y > other.y) { return this.y = other.y; } }; Vec2d.prototype.floor = function(){ return this.apply(Math.floor); }; Vec2d.prototype.floored = function(){ return this.applied(Math.floor); }; Vec2d.prototype.ceil = function(){ return this.apply(Math.ceil); }; Vec2d.prototype.ceiled = function(){ return this.applied(Math.ceil); }; Vec2d.prototype.project = function(other){ this.scale(this.dot(other) / other.lengthSqrd()); return this; }; Vec2d.prototype.dot = function(other){ return this.x * other.x + this.y * other.y; }; Vec2d.prototype.rotate = function(other){ this.x = this.x * other.x - this.y * other.y; this.y = this.x * other.y + this.y * other.x; return this; }; },{}],10:[function(require,module,exports){ var vec2d = require('vec2d'); var onReadyQueue = []; // set assetsLoaded after all assets are done loading var assetsLoaded = false; var spritesheetDone = false; var animationsJsonDone = false; var alreadyStarted = false; exports.bootstrap = bootstrap; exports.onReady = onReady; exports.animations = null; exports.spritesheet = null; exports.useSpritesheet = true; exports.getImage = getImage; function onReady(cb) { if (assetsLoaded) { cb(); } else { onReadyQueue.push(cb); } } function bootstrap(){ // don't bootstrap twice if (alreadyStarted) return; alreadyStarted = true; // give the app a chance to skip spritesheet loading setTimeout(loadSpritesheet); function checkDoneLoading(){ if (spritesheetDone && animationsJsonDone) { assetsLoaded = true; onReadyQueue.forEach(function(cb) { cb(); }); } } function loadSpritesheet() { if (!exports.useSpritesheet) { spritesheetDone = true; animationsJsonDone = true; return checkDoneLoading(); } // get the spritesheet exports.spritesheet = new Image(); exports.spritesheet.src = "spritesheet.png"; exports.spritesheet.onload = function(){ spritesheetDone = true; checkDoneLoading(); }; // get the animations.json file var request = new XMLHttpRequest(); request.onreadystatechange = function(){ if (!(request.readyState === 4 && request.status === 200)) { return; } exports.animations = JSON.parse(request.responseText); // cache some values so don't have to compute them all the time for (var name in exports.animations) { var anim = exports.animations[name]; anim.duration = anim.delay * anim.frames.length; anim.name = name; anim.anchor = vec2d(anim.anchor); for (var i = 0; i < anim.frames.length; ++i) { var frame = anim.frames[i]; frame.pos = vec2d(frame.pos); frame.size = vec2d(frame.size); } } animationsJsonDone = true; checkDoneLoading(); }; request.open("GET", "animations.json", true); request.send(); } } function getImage(name, frameIndex){ if (frameIndex == null) frameIndex = 0; var anim = exports.animations[name]; var buffer = document.createElement('canvas'); var frame = anim.frames[frameIndex]; buffer.width = frame.size.x; buffer.height = frame.size.y; var context = buffer.getContext('2d'); context.drawImage(exports.spritesheet, frame.pos.x, frame.pos.y, frame.size.x, frame.size.y, 0, 0, frame.size.x, frame.size.y); return buffer; } },{"vec2d":13}],12:[function(require,module,exports){ var Vec2d = require('vec2d').Vec2d; var util = require('util'); var EventEmitter = require('events').EventEmitter; var resources = require('./resources'); module.exports = Sprite; Sprite.idCount = 0; util.inherits(Sprite, EventEmitter); function Sprite(animationName, params) { EventEmitter.call(this); params = params || {}; // defaults this.pos = params.pos == null ? new Vec2d(0, 0) : params.pos; this.scale = params.scale == null ? new Vec2d(1, 1) : params.scale; this.zOrder = params.zOrder == null ? 0 : params.zOrder; this.batch = params.batch; this.rotation = params.rotation == null ? 0 : params.rotation; this.alpha = params.alpha == null ? 1 : params.alpha; this.id = Sprite.idCount++; this.setAnimationName(animationName); this.setLoop(params.loop); this.setVisible(params.visible == null ? true : params.visible); this.setFrameIndex(params.frameIndex == null ? 0 : params.frameIndex); if (resources.animations == null) { throw new Error("You may not create Sprites until the onReady event has been fired from Chem."); } } Sprite.prototype.setAnimationName = function(animationName){ var anim = resources.animations[animationName]; if (anim == null) { throw new Error("name not found in animation list: " + animationName); } this.setAnimation(resources.animations[animationName]); }; Sprite.prototype.setAnimation = function(animation){ this.animation = animation; this.animationName = this.animation.name; this._loop = this.loop == null ? this.animation.loop : this.loop; // size of first frame, which does not take scale into account this.size = this.animation.frames[0].size; }; // takes scale and current frame into account Sprite.prototype.getSize = function(){ return this.animation.frames[this.getFrameIndex()].size.times(this.scale.applied(Math.abs)); }; // convenience Sprite.prototype.getAnchor = function(){ return this.animation.anchor.times(this.scale.applied(Math.abs)); }; Sprite.prototype.getTopLeft = function(){ return this.pos.minus(this.getAnchor()); }; Sprite.prototype.getBottomRight = function(){ return this.getTopLeft().plus(this.getSize()); }; Sprite.prototype.getTop = function(){ return this.getTopLeft().y; }; Sprite.prototype.getLeft = function(){ return this.getTopLeft().x; }; Sprite.prototype.getBottom = function(){ return this.getBottomRight().y; }; Sprite.prototype.getRight = function(){ return this.getBottomRight().x; }; Sprite.prototype.setLeft = function(x){ this.pos.x = x + this.animation.anchor.x; }; Sprite.prototype.setRight = function(x){ this.pos.x = x - this.animation.anchor.x; }; Sprite.prototype.setTop = function(y){ this.pos.y = y + this.animation.anchor.y; }; Sprite.prototype.setBottom = function(y){ this.pos.y = y - this.animation.anchor.y; }; Sprite.prototype.isTouching = function(sprite){ var a_tl = this.getTopLeft(); var a_br = this.getBottomRight(); var b_tl = sprite.getTopLeft(); var b_br = sprite.getBottomRight(); var notTouching = a_tl.x >= b_br.x || a_br.x <= b_tl.x || a_tl.y >= b_br.y || a_br.y <= b_tl.y; return !notTouching; }; Sprite.prototype.hitTest = function(pt) { var tl = this.getTopLeft(); var br = this.getBottomRight(); return pt.x >= tl.x && pt.y >= tl.y && pt.x < br.x && pt.y < br.y; }; Sprite.prototype.setVisible = function(visible){ this.visible = visible; if (this.batch == null) { return; } if (this.visible) { this.batch.add(this); } else { this.batch.remove(this); } }; Sprite.prototype.setZOrder = function(zOrder){ if (this.batch != null) { this.batch.remove(this); this.zOrder = zOrder; this.batch.add(this); } else { this.zOrder = zOrder; } }; Sprite.prototype.setFrameIndex = function(frameIndex){ var secondsPassed = frameIndex * this.animation.delay; var date = new Date(); date.setMilliseconds(date.getMilliseconds() - secondsPassed * 1000); this.setAnimationStartDate(date); }; Sprite.prototype.setLoop = function(loop){ this.loop = loop; // this is the actual value we'll use to check if we're going to loop. this._loop = this.loop == null ? this.animation.loop : this.loop; this.setUpInterval(); }; Sprite.prototype.setAnimationStartDate = function(animationStartDate){ this.animationStartDate = animationStartDate; this.setUpInterval(); }; Sprite.prototype.getFrameIndex = function(){ var ref$, ref1$; var now = new Date(); var totalTime = (now - this.animationStartDate) / 1000; if (this._loop) { return Math.floor((totalTime % this.animation.duration) / this.animation.delay); } else { return (ref$ = Math.floor(totalTime / this.animation.delay)) < (ref1$ = this.animation.frames.length - 1) ? ref$ : ref1$; } }; Sprite.prototype['delete'] = function(){ if (this.batch) this.batch.remove(this); this.batch = null; }; // private Sprite.prototype.setUpInterval = function(){ var self = this; if (this.interval) this.interval.cancel(); var _schedule = this._loop ? schedule : wait; var now = new Date(); var timeSinceStart = (now - this.animationStartDate) / 1000; var duration = this.animation.duration - timeSinceStart; this.interval = _schedule(duration, function(){ return self.emit('animationend'); }); }; function wait(sec, cb) { var interval = setTimeout(cb, sec * 1000); return { cancel: function(){ if (interval != null) { clearTimeout(interval); interval = null; } } }; } function schedule(sec, cb) { var interval = setInterval(cb, sec * 1000); return { cancel: function(){ if (interval != null) { clearInterval(interval); interval = null; } } }; } },{"./resources":10,"events":15,"util":16,"vec2d":13}],11:[function(require,module,exports){ var vec2d = require('vec2d'); var resources = require('./resources'); var util = require('util'); var EventEmitter = require('events').EventEmitter; var button = require('./button'); var MOUSE_OFFSET = button.MOUSE_OFFSET; var KEY_OFFSET = button.KEY_OFFSET; var EPSILON = 0.00000001; var MAX_DISPLAY_FPS = 90000; module.exports = Engine; var targetFps = 60; var targetSpf = 1 / targetFps; var fpsSmoothness = 0.9; var fpsOneFrameWeight = 1.0 - fpsSmoothness; var requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || fallbackRequestAnimationFrame; util.inherits(Engine, EventEmitter); function Engine(canvas) { EventEmitter.call(this); this.canvas = canvas; this.listeners = []; // add tabindex property to canvas so that it can receive keyboard input this.canvas.tabIndex = 0; this.context = this.canvas.getContext("2d"); this.size = vec2d(this.canvas.width, this.canvas.height); this.fps = targetFps; this.setMinFps(20); this.buttonCaptureExceptions = {}; } Engine.prototype.setSize = function(size) { this.size = size; this.canvas.width = this.size.x; this.canvas.height = this.size.y; }; Engine.prototype.setMinFps = function(it){ this.maxSpf = 1 / it; }; Engine.prototype.start = function(){ this.attachListeners(); this.startMainLoop(); }; Engine.prototype.stop = function(){ this.stopMainLoop(); this.removeListeners(); }; Engine.prototype.buttonState = function(button){ return !!this.buttonStates[button]; }; Engine.prototype.buttonJustPressed = function(button){ return !!this.btnJustPressed[button]; }; Engine.prototype.buttonJustReleased = function(button){ return !!this.btnJustReleased[button]; }; Engine.prototype.draw = function(batch){ for (var i = 0; i < batch.sprites.length; ++i) { var sprites = batch.sprites[i]; for (var id in sprites) { var sprite = sprites[id]; var frame = sprite.animation.frames[sprite.getFrameIndex()]; this.context.save(); this.context.translate(sprite.pos.x, sprite.pos.y); this.context.scale(sprite.scale.x, sprite.scale.y); this.context.rotate(sprite.rotation); this.context.globalAlpha = sprite.alpha; this.context.drawImage(resources.spritesheet, frame.pos.x, frame.pos.y, frame.size.x, frame.size.y, -sprite.animation.anchor.x, -sprite.animation.anchor.y, frame.size.x, frame.size.y); this.context.restore(); } } }; Engine.prototype.drawFps = function(){ this.context.textAlign = 'left'; this.context.fillText(Math.round(this.fps) + " fps", 0, this.size.y); }; // private Engine.prototype.startMainLoop = function(){ var self = this; var previousUpdate = new Date(); this.mainLoopOn = true; requestAnimationFrame(mainLoop, this.canvas); function mainLoop(){ var now = new Date(); var delta = (now - previousUpdate) / 1000; previousUpdate = now; // make sure dt is never zero // if FPS is too low, lag instead of causing physics glitches var dt = delta; if (dt < EPSILON) dt = EPSILON; if (dt > self.maxSpf) dt = self.maxSpf; var multiplier = dt / targetSpf; self.emit('update', dt, multiplier); self.btnJustPressed = {}; self.btnJustReleased = {}; self.emit('draw', self.context); var fps = 1 / delta; fps = fps < MAX_DISPLAY_FPS ? fps : MAX_DISPLAY_FPS; self.fps = self.fps * fpsSmoothness + fps * fpsOneFrameWeight; if (self.mainLoopOn) { requestAnimationFrame(mainLoop, self.canvas); } } }; Engine.prototype.attachListeners = function(){ var self = this; this.buttonStates = {}; this.btnJustPressed = {}; this.btnJustReleased = {}; // disable right click context menu addListener(this.canvas, 'contextmenu', function(event){ if (self.buttonCaptureExceptions[button.MouseRight]) return true; event.preventDefault(); }); // mouse input this.mousePos = vec2d(); addListener(this.canvas, 'mousemove', function(event){ self.mousePos = vec2d( (event.offsetX) != null ? event.offsetX : event.pageX - event.target.offsetLeft, (event.offsetY) != null ? event.offsetY : event.pageY - event.target.offsetTop); self.emit('mousemove', self.mousePos, MOUSE_OFFSET + event.which); }); addListener(this.canvas, 'mousedown', function(event){ var buttonId = MOUSE_OFFSET + event.which; self.buttonStates[buttonId] = true; self.btnJustPressed[buttonId] = true; self.emit('buttondown', buttonId); return self.bubbleEvent(event); }); addListener(this.canvas, 'mouseup', function(event){ var buttonId; buttonId = MOUSE_OFFSET + event.which; self.buttonStates[buttonId] = false; self.btnJustReleased[buttonId] = true; self.emit('buttonup', buttonId); return self.bubbleEvent(event); }); // keyboard input addListener(this.canvas, 'keydown', function(event){ var buttonId = KEY_OFFSET + event.which; self.btnJustPressed[buttonId] = !self.buttonStates[buttonId]; self.buttonStates[buttonId] = true; self.emit('buttondown', buttonId); return self.bubbleEvent(event); }); addListener(this.canvas, 'keyup', function(event){ var buttonId = KEY_OFFSET + event.which; self.btnJustReleased[buttonId] = self.buttonStates[buttonId]; self.buttonStates[buttonId] = false; self.emit('buttonup', buttonId); return self.bubbleEvent(event); }); function addListener(element, eventName, listener){ self.listeners.push([element, eventName, listener]); element.addEventListener(eventName, listener, false); } }; Engine.prototype.bubbleEvent = function(event) { // we need to figure out whether to bubble this key event up. // if the button is an exception, bubble it up. // also if any other exceptions are pressed, bubble it up. // this allows ctrl+(anything) to work. var buttonId = KEY_OFFSET + event.which; if (this.buttonCaptureExceptions[buttonId] || (event.ctrlKey && this.buttonCaptureExceptions[button.KeyCtrl]) || (event.altKey && this.buttonCaptureExceptions[button.KeyAlt]) || (event.shiftKey && this.buttonCaptureExceptions[button.KeyShift])) { return true; } else { event.preventDefault(); return false; } } Engine.prototype.removeListeners = function(){ this.listeners.forEach(function(listener) { var element = listener[0]; var eventName = listener[1]; var fn = listener[2]; element.removeEventListener(eventName, fn, false); }); this.listeners = []; }; Engine.prototype.stopMainLoop = function(){ this.mainLoopOn = false; }; function fallbackRequestAnimationFrame(cb){ window.setTimeout(cb, targetSpf * 1000); } },{"./button":7,"./resources":10,"events":15,"util":16,"vec2d":13}]},{},[2]) ;