2018-03-27 17:39:08 +00:00
|
|
|
/******************************
|
|
|
|
|
|
|
|
An interactive game in the BACKGROUND of the Slideshow...
|
|
|
|
(if fullscreen, origin is top-left of slideshow)
|
|
|
|
(if not, allow MULTIPLE canvasses & games.)
|
|
|
|
|
|
|
|
******************************/
|
|
|
|
|
|
|
|
function Simulations(){
|
|
|
|
|
|
|
|
var self = this;
|
|
|
|
self.dom = $("#simulations");
|
|
|
|
|
|
|
|
self.sims = [];
|
|
|
|
|
2018-03-27 19:20:22 +00:00
|
|
|
// Clear All Sims
|
2018-03-27 17:39:08 +00:00
|
|
|
self.clear = function(){
|
2018-04-03 17:32:26 +00:00
|
|
|
|
|
|
|
Simulations.IS_RUNNING = false;
|
|
|
|
$("#container").removeAttribute("sim_is_running");
|
|
|
|
|
2018-03-27 17:39:08 +00:00
|
|
|
self.sims.forEach(function(sim){
|
|
|
|
self.dom.removeChild(sim.canvas);
|
|
|
|
sim.kill();
|
|
|
|
});
|
2018-03-28 17:12:05 +00:00
|
|
|
self.sims = [];
|
2018-04-03 17:32:26 +00:00
|
|
|
|
2018-03-27 17:39:08 +00:00
|
|
|
};
|
2018-03-27 19:20:22 +00:00
|
|
|
|
2018-03-28 17:12:05 +00:00
|
|
|
// Add Sims
|
|
|
|
self.add = function(config){
|
|
|
|
config = cloneObject(config);
|
|
|
|
config.container = self;
|
|
|
|
var sim = new Sim(config);
|
|
|
|
self.dom.appendChild(sim.canvas);
|
|
|
|
self.sims.push(sim);
|
2018-03-27 17:39:08 +00:00
|
|
|
};
|
|
|
|
|
2018-03-27 19:20:22 +00:00
|
|
|
// Update
|
|
|
|
self.update = function(){
|
2018-04-18 15:25:41 +00:00
|
|
|
|
|
|
|
// Running sims... the CLOCK!
|
|
|
|
if(Simulations.IS_RUNNING){
|
|
|
|
if(self.CLOCK==0){
|
|
|
|
|
|
|
|
// Step all sims!
|
|
|
|
self.sims.forEach(function(sim){
|
|
|
|
sim.nextStep();
|
|
|
|
});
|
|
|
|
self.CLOCK = 30; //25;
|
|
|
|
|
|
|
|
}
|
|
|
|
self.CLOCK--;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update all sims
|
2018-03-27 19:20:22 +00:00
|
|
|
self.sims.forEach(function(sim){
|
|
|
|
sim.update();
|
|
|
|
});
|
2018-04-18 15:25:41 +00:00
|
|
|
|
2018-03-27 19:20:22 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// Draw
|
|
|
|
self.draw = function(){
|
|
|
|
self.sims.forEach(function(sim){
|
|
|
|
sim.draw();
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2018-04-03 15:25:13 +00:00
|
|
|
////////////////////////
|
|
|
|
// SIMULATION RUNNING //
|
|
|
|
////////////////////////
|
|
|
|
|
2018-04-18 15:25:41 +00:00
|
|
|
self.CLOCK = -1;
|
2018-04-03 15:25:13 +00:00
|
|
|
subscribe("sim/start", function(){
|
2018-04-03 17:32:26 +00:00
|
|
|
|
2018-04-03 15:25:13 +00:00
|
|
|
Simulations.IS_RUNNING = true;
|
2018-04-03 17:32:26 +00:00
|
|
|
$("#container").setAttribute("sim_is_running",true);
|
|
|
|
|
2018-04-18 15:25:41 +00:00
|
|
|
self.CLOCK = 0;
|
|
|
|
// save for later resetting
|
2018-04-03 15:25:13 +00:00
|
|
|
self.sims.forEach(function(sim){
|
2018-04-18 15:25:41 +00:00
|
|
|
sim.save();
|
2018-04-03 15:25:13 +00:00
|
|
|
});
|
2018-04-03 17:32:26 +00:00
|
|
|
|
2018-04-03 15:25:13 +00:00
|
|
|
});
|
2018-04-18 15:25:41 +00:00
|
|
|
subscribe("sim/stop", function(){
|
2018-04-03 17:32:26 +00:00
|
|
|
|
2018-04-03 15:25:13 +00:00
|
|
|
Simulations.IS_RUNNING = false;
|
2018-04-03 17:32:26 +00:00
|
|
|
$("#container").removeAttribute("sim_is_running");
|
2018-04-18 15:25:41 +00:00
|
|
|
|
|
|
|
// reload the network pre-sim
|
2018-04-03 15:25:13 +00:00
|
|
|
self.sims.forEach(function(sim){
|
2018-04-18 15:25:41 +00:00
|
|
|
sim.reload();
|
2018-04-03 15:25:13 +00:00
|
|
|
});
|
2018-04-03 17:32:26 +00:00
|
|
|
|
2018-04-03 15:25:13 +00:00
|
|
|
});
|
|
|
|
|
2018-04-01 16:34:52 +00:00
|
|
|
///////////////////////
|
|
|
|
// HELPERS AND STUFF //
|
|
|
|
///////////////////////
|
|
|
|
|
|
|
|
// Get Child!
|
|
|
|
self.getChildByID = function(id){
|
|
|
|
return self.sims.find(function(sim){
|
|
|
|
return sim.id==id;
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2018-03-27 17:39:08 +00:00
|
|
|
}
|
|
|
|
|
2018-04-24 18:12:38 +00:00
|
|
|
// On resize, adjust the fullscreen sim (if any).
|
|
|
|
window.addEventListener("resize", function(){
|
|
|
|
if(slideshow.simulations.sims.length>0){
|
|
|
|
slideshow.simulations.sims[0].resize();
|
|
|
|
}
|
|
|
|
}, false);
|
|
|
|
|
2018-03-27 17:39:08 +00:00
|
|
|
function Sim(config){
|
|
|
|
|
|
|
|
var self = this;
|
|
|
|
self.config = config;
|
2018-04-03 15:25:13 +00:00
|
|
|
self.networkConfig = cloneObject(config.network);
|
2018-03-28 17:12:05 +00:00
|
|
|
self.container = config.container;
|
2018-04-01 16:34:52 +00:00
|
|
|
self.options = config.options || {};
|
|
|
|
|
|
|
|
self.id = config.id;
|
2018-03-27 17:39:08 +00:00
|
|
|
|
2018-04-20 18:47:58 +00:00
|
|
|
// CONTAGION SOUND
|
2018-04-20 20:10:18 +00:00
|
|
|
//var _CONTAGION_SOUND = 0;
|
2018-04-20 18:47:58 +00:00
|
|
|
var _PLAY_CONTAGION_SOUND = function(){
|
2018-04-20 20:10:18 +00:00
|
|
|
//_CONTAGION_SOUND = (_CONTAGION_SOUND+1)%3;
|
|
|
|
//SOUNDS["contagion"+_CONTAGION_SOUND].play();
|
|
|
|
SOUNDS.contagion.volume(0.75);
|
|
|
|
SOUNDS.contagion.play();
|
2018-04-20 18:47:58 +00:00
|
|
|
};
|
|
|
|
|
2018-03-27 17:39:08 +00:00
|
|
|
// Canvas
|
2018-03-28 14:57:19 +00:00
|
|
|
if(config.fullscreen){
|
|
|
|
var container = $("#simulations_container");
|
|
|
|
self.canvas = createCanvas(container.clientWidth, container.clientHeight);
|
|
|
|
}else{
|
2018-04-24 18:12:38 +00:00
|
|
|
alert("this code should not run. if it does, something bad has happened.");
|
|
|
|
/*(self.canvas = createCanvas(config.width||500, config.height||500);
|
2018-03-28 14:57:19 +00:00
|
|
|
self.canvas.style.left = config.x || 0;
|
2018-04-24 18:12:38 +00:00
|
|
|
self.canvas.style.top = config.y || 0;*/
|
2018-03-28 14:57:19 +00:00
|
|
|
}
|
|
|
|
//self.canvas.style.border = "1px solid #ccc";
|
2018-03-27 17:39:08 +00:00
|
|
|
self.ctx = self.canvas.getContext('2d');
|
|
|
|
|
2018-03-27 19:20:22 +00:00
|
|
|
// Mouse, offset!
|
|
|
|
self.mouse = {x:0, y:0};
|
|
|
|
|
|
|
|
// Connector-Cutter
|
|
|
|
self.connectorCutter = new ConnectorCutter({sim:self});
|
|
|
|
|
2018-04-24 18:12:38 +00:00
|
|
|
// Resize
|
|
|
|
var simOffset;
|
|
|
|
self.resize = function(){
|
|
|
|
|
|
|
|
var container = $("#simulations_container");
|
|
|
|
simOffset = _getBoundingClientRect(self.container.dom);
|
2018-04-25 18:46:26 +00:00
|
|
|
self.canvas.style.left = (-simOffset.x) + "px";
|
|
|
|
self.canvas.style.top = (-simOffset.y) + "px";
|
2018-04-24 18:12:38 +00:00
|
|
|
|
|
|
|
// Set difference in width & height
|
|
|
|
var width = container.clientWidth;
|
|
|
|
var height = container.clientHeight;
|
|
|
|
self.canvas.width = width*2;
|
|
|
|
self.canvas.height = height*2;
|
2018-04-25 18:46:26 +00:00
|
|
|
self.canvas.style.width = (width) + "px";
|
|
|
|
self.canvas.style.height = (height) + "px";
|
2018-04-24 18:12:38 +00:00
|
|
|
|
|
|
|
};
|
|
|
|
self.resize();
|
|
|
|
|
2018-03-27 17:39:08 +00:00
|
|
|
// Networks... clear/init
|
|
|
|
self.clear = function(){
|
|
|
|
self.peeps = [];
|
|
|
|
self.connections = [];
|
|
|
|
self.contagion = 0;
|
|
|
|
};
|
|
|
|
self.init = function(){
|
|
|
|
|
|
|
|
// Clear!
|
|
|
|
self.clear();
|
|
|
|
|
|
|
|
// Peeps
|
|
|
|
self.networkConfig.peeps.forEach(function(p){
|
|
|
|
var x = p[0],
|
|
|
|
y = p[1],
|
|
|
|
infected = p[2];
|
|
|
|
self.addPeep(x, y, infected);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Connections
|
|
|
|
self.networkConfig.connections.forEach(function(c){
|
2018-03-27 19:20:22 +00:00
|
|
|
var from = self.peeps[c[0]],
|
2018-04-03 17:32:26 +00:00
|
|
|
to = self.peeps[c[1]],
|
|
|
|
uncuttable = c[2]||false
|
|
|
|
self.addConnection(from, to, uncuttable);
|
2018-03-27 17:39:08 +00:00
|
|
|
});
|
|
|
|
|
2018-03-27 19:20:22 +00:00
|
|
|
// Contagion
|
|
|
|
self.contagion = self.networkConfig.contagion;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
// Update
|
2018-03-28 14:57:19 +00:00
|
|
|
self.onupdate = config.onupdate || function(){};
|
2018-03-27 19:20:22 +00:00
|
|
|
self.update = function(){
|
|
|
|
|
|
|
|
// "Mouse", offset!
|
2018-04-24 14:22:06 +00:00
|
|
|
var canvasBounds = _getBoundingClientRect(self.canvas);
|
2018-03-27 19:20:22 +00:00
|
|
|
self.mouse = cloneObject(Mouse);
|
|
|
|
self.mouse.x -= canvasBounds.x;
|
|
|
|
self.mouse.y -= canvasBounds.y;
|
|
|
|
self.mouse.lastX -= canvasBounds.x;
|
|
|
|
self.mouse.lastY -= canvasBounds.y;
|
2018-03-28 14:57:19 +00:00
|
|
|
if(config.fullscreen){
|
2018-04-01 16:34:52 +00:00
|
|
|
var fullscreenOffsetX = config.x + simOffset.x;
|
|
|
|
var fullscreenOffsetY = config.y + simOffset.y;
|
|
|
|
self.mouse.x -= fullscreenOffsetX;
|
|
|
|
self.mouse.y -= fullscreenOffsetY;
|
|
|
|
self.mouse.lastX -= fullscreenOffsetX;
|
|
|
|
self.mouse.lastY -= fullscreenOffsetY;
|
2018-03-28 14:57:19 +00:00
|
|
|
}
|
2018-03-27 19:20:22 +00:00
|
|
|
|
|
|
|
// Connector-Cutter
|
|
|
|
self.connectorCutter.update();
|
|
|
|
|
|
|
|
// Connections & Peeps
|
|
|
|
self.connections.forEach(function(connection){
|
|
|
|
connection.update();
|
|
|
|
});
|
|
|
|
self.peeps.forEach(function(peep){
|
|
|
|
peep.update();
|
|
|
|
});
|
|
|
|
|
2018-03-28 14:57:19 +00:00
|
|
|
// secret editor...
|
|
|
|
// drag Peep
|
|
|
|
if(_draggingPeep){
|
|
|
|
_draggingPeep.x = self.mouse.x+_draggingOffset.x;
|
|
|
|
_draggingPeep.y = self.mouse.y+_draggingOffset.y;
|
2018-04-10 17:00:22 +00:00
|
|
|
_draggingPeep.velocity.x = 0;
|
|
|
|
_draggingPeep.velocity.y = 0;
|
2018-03-28 14:57:19 +00:00
|
|
|
}
|
|
|
|
|
2018-04-02 17:43:20 +00:00
|
|
|
// update confetti & winword...
|
|
|
|
self.confetti.forEach(function(confetti){
|
|
|
|
|
|
|
|
confetti.x += confetti.vx;
|
|
|
|
confetti.y += confetti.vy;
|
|
|
|
confetti.spin += confetti.spinSpeed;
|
|
|
|
|
|
|
|
confetti.vy += confetti.g;
|
|
|
|
|
|
|
|
confetti.vx *= 0.95;
|
|
|
|
confetti.vy *= 0.95;
|
|
|
|
|
|
|
|
});
|
|
|
|
if(self.winWord.ticker>=0){
|
|
|
|
self.winWord.ticker += 1/60;
|
2018-04-18 14:51:44 +00:00
|
|
|
if(self.winWord.ticker>3){
|
2018-04-02 17:43:20 +00:00
|
|
|
self.winWord.ticker = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-28 14:57:19 +00:00
|
|
|
// On update! (for arbitrary sim-specific logic)
|
|
|
|
self.onupdate(self);
|
|
|
|
|
2018-03-27 17:39:08 +00:00
|
|
|
};
|
|
|
|
|
2018-03-27 19:20:22 +00:00
|
|
|
// Draw
|
|
|
|
self.draw = function(){
|
|
|
|
|
|
|
|
// Retina
|
|
|
|
var ctx = self.ctx;
|
|
|
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
2018-03-28 14:57:19 +00:00
|
|
|
// todo: smarter redraw coz, wow, retina.
|
2018-03-27 19:20:22 +00:00
|
|
|
ctx.save();
|
|
|
|
ctx.scale(2,2);
|
2018-03-28 14:57:19 +00:00
|
|
|
if(config.fullscreen){
|
2018-04-01 16:34:52 +00:00
|
|
|
var fullscreenOffsetX = config.x + simOffset.x;
|
|
|
|
var fullscreenOffsetY = config.y + simOffset.y;
|
|
|
|
ctx.translate(fullscreenOffsetX, fullscreenOffsetY);
|
2018-03-28 14:57:19 +00:00
|
|
|
}
|
2018-03-27 19:20:22 +00:00
|
|
|
|
|
|
|
// Draw all of it!
|
|
|
|
self.connectorCutter.draw(ctx);
|
|
|
|
self.connections.forEach(function(connection){
|
|
|
|
connection.draw(ctx);
|
|
|
|
});
|
|
|
|
self.peeps.forEach(function(peep){
|
|
|
|
peep.draw(ctx);
|
|
|
|
});
|
|
|
|
|
|
|
|
ctx.restore();
|
|
|
|
|
2018-04-02 17:43:20 +00:00
|
|
|
// Draw confetti - NOT AFFECTED BY TRANSFORMS.
|
|
|
|
self.confetti.forEach(function(confetti){
|
|
|
|
ctx.save();
|
2018-04-18 18:02:27 +00:00
|
|
|
var offsetX = -Math.sin(confetti.spin)*9;
|
|
|
|
ctx.translate(confetti.x+offsetX, confetti.y);
|
|
|
|
ctx.rotate(Math.sin(confetti.spin)*0.2);
|
|
|
|
if(confetti.flip) ctx.scale(-1,1);
|
|
|
|
self.confettiSprite.gotoFrame(confetti.frame);
|
|
|
|
self.confettiSprite.draw(ctx);
|
2018-04-02 17:43:20 +00:00
|
|
|
ctx.restore();
|
|
|
|
});
|
|
|
|
|
|
|
|
// Draw WIN WORD
|
|
|
|
if(self.winWord.ticker>=0){
|
|
|
|
|
|
|
|
ctx.save();
|
|
|
|
ctx.translate(self.winWord.x, self.winWord.y);
|
|
|
|
ctx.scale(2,2); // retina
|
|
|
|
|
|
|
|
// expand
|
|
|
|
if(self.winWord.ticker<0.2){
|
|
|
|
var scale = self.winWord.ticker/0.2;
|
|
|
|
ctx.scale(scale,scale);
|
|
|
|
}
|
|
|
|
|
|
|
|
// fade away
|
2018-04-18 14:51:44 +00:00
|
|
|
if(self.winWord.ticker>2){
|
|
|
|
var alpha = -(self.winWord.ticker-3);
|
2018-04-02 17:43:20 +00:00
|
|
|
ctx.globalAlpha = alpha;
|
|
|
|
}
|
|
|
|
|
2018-04-15 21:24:22 +00:00
|
|
|
ctx.font = '100px PatrickHand';
|
2018-04-02 17:43:20 +00:00
|
|
|
ctx.fillStyle = "#000";
|
|
|
|
ctx.textBaseline = "middle";
|
|
|
|
ctx.fontWeight = "bold";
|
|
|
|
ctx.textAlign = "center";
|
|
|
|
var label = getWords("WIN");
|
|
|
|
ctx.fillText(label, 0, 0);
|
|
|
|
|
|
|
|
ctx.restore();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2018-03-27 19:20:22 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// Kill
|
|
|
|
self.kill = function(){
|
2018-04-06 16:06:55 +00:00
|
|
|
|
2018-03-27 19:20:22 +00:00
|
|
|
self.clear();
|
2018-04-06 16:06:55 +00:00
|
|
|
|
|
|
|
// key handlers, too
|
|
|
|
_keyHandlers.forEach(function(_handler){
|
|
|
|
unsubscribe(_handler);
|
|
|
|
});
|
|
|
|
|
2018-03-27 19:20:22 +00:00
|
|
|
};
|
|
|
|
|
2018-04-02 17:43:20 +00:00
|
|
|
///////////////////
|
|
|
|
// WINNER WINNER //
|
|
|
|
///////////////////
|
|
|
|
|
|
|
|
self.wonBefore = false;
|
|
|
|
self.confetti = [];
|
|
|
|
self.winWord = {x:0, y:0, ticker:-1};
|
|
|
|
|
2018-04-18 18:02:27 +00:00
|
|
|
// Confetti Sprite
|
|
|
|
self.confettiSprite = new Sprite({
|
2018-04-24 18:12:38 +00:00
|
|
|
img: "confetti",
|
2018-04-18 18:02:27 +00:00
|
|
|
frames:3, sw:100, sh:50,
|
|
|
|
});
|
|
|
|
self.confettiSprite.pivotX = 50;
|
|
|
|
self.confettiSprite.pivotY = 50;
|
|
|
|
self.confettiSprite.scale = 0.5;
|
|
|
|
|
2018-04-13 20:58:04 +00:00
|
|
|
self.win = function(bounds){
|
2018-04-02 17:43:20 +00:00
|
|
|
|
|
|
|
// ONLY ONCE
|
|
|
|
if(self.wonBefore) return;
|
|
|
|
self.wonBefore = true;
|
|
|
|
|
2018-04-20 18:47:58 +00:00
|
|
|
// SOUND!
|
|
|
|
if(bounds && bounds.small){
|
|
|
|
SOUNDS.party_short.play();
|
|
|
|
}else{
|
|
|
|
SOUNDS.party.play();
|
|
|
|
}
|
|
|
|
|
|
|
|
// AMOUNT OF CONFETTI
|
|
|
|
var AMOUNT_OF_CONFETTI = 100;
|
|
|
|
if(bounds && bounds.small){
|
|
|
|
AMOUNT_OF_CONFETTI = 50;
|
|
|
|
}
|
|
|
|
|
2018-04-02 17:43:20 +00:00
|
|
|
// Get center of peeps
|
|
|
|
var fullscreenOffsetX = config.x + simOffset.x;
|
|
|
|
var fullscreenOffsetY = config.y + simOffset.y;
|
2018-04-20 18:47:58 +00:00
|
|
|
if(!bounds || !bounds.x) bounds = getBoundsOfPoints(self.peeps); // OPTIONAL BOUNDS
|
2018-04-02 17:43:20 +00:00
|
|
|
var cx = bounds.x + bounds.width/2;
|
|
|
|
var cy = bounds.y + bounds.height/2;
|
|
|
|
cx += fullscreenOffsetX;
|
|
|
|
cy += fullscreenOffsetY;
|
|
|
|
cx *= 2; // retina
|
|
|
|
cy *= 2; // retina
|
|
|
|
|
|
|
|
// Place Win Word
|
|
|
|
self.winWord.x = cx;
|
|
|
|
self.winWord.y = cy;
|
|
|
|
self.winWord.ticker = 0;
|
|
|
|
|
|
|
|
// Place confetti
|
2018-04-20 18:47:58 +00:00
|
|
|
for(var i=0; i<AMOUNT_OF_CONFETTI; i++){
|
2018-04-02 17:43:20 +00:00
|
|
|
var angle = Math.random()*Math.TAU;
|
|
|
|
var burst = bounds.width/15;
|
|
|
|
var frame = Math.floor(Math.random()*5);
|
|
|
|
var spinSpeed = 0.03+Math.random()*0.03;
|
|
|
|
var confetti = {
|
|
|
|
x: cx,
|
|
|
|
y: cy,
|
|
|
|
vx: Math.cos(angle)*Math.random()*burst,
|
|
|
|
vy: Math.sin(angle)*Math.random()*burst - burst*0.25,
|
|
|
|
frame: frame,
|
|
|
|
spinSpeed: spinSpeed,
|
|
|
|
spin: Math.random()*Math.TAU,
|
2018-04-18 18:02:27 +00:00
|
|
|
g: 0.10+Math.random()*0.10,
|
|
|
|
flip: (Math.random()<0.5)
|
2018-04-02 17:43:20 +00:00
|
|
|
};
|
|
|
|
self.confetti.push(confetti);
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
2018-04-03 15:25:13 +00:00
|
|
|
////////////////////////
|
|
|
|
// SIMULATION RUNNING //
|
|
|
|
////////////////////////
|
|
|
|
|
2018-04-03 17:32:26 +00:00
|
|
|
self.STEP = 0;
|
|
|
|
|
2018-04-03 15:25:13 +00:00
|
|
|
self.save = function(){
|
2018-04-03 17:32:26 +00:00
|
|
|
self.STEP = 0;
|
2018-04-03 15:25:13 +00:00
|
|
|
self.networkConfig = self.getCurrentNetwork();
|
|
|
|
};
|
|
|
|
|
2018-04-20 18:47:58 +00:00
|
|
|
self._canPlayBonkSound = true;
|
|
|
|
|
2018-04-03 15:25:13 +00:00
|
|
|
self.reload = function(){
|
2018-04-06 16:06:55 +00:00
|
|
|
var contagionLevel = self.contagion; // hack for sandbox: keep contagion the same
|
2018-04-03 17:32:26 +00:00
|
|
|
self.STEP = 0;
|
2018-04-20 18:47:58 +00:00
|
|
|
self._canPlayBonkSound = true;
|
2018-04-03 15:25:13 +00:00
|
|
|
self.init();
|
2018-04-06 16:06:55 +00:00
|
|
|
self.contagion = contagionLevel;
|
2018-04-03 15:25:13 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
self.nextStep = function(){
|
|
|
|
|
2018-04-20 18:47:58 +00:00
|
|
|
// SOUND! If anyone can be infected, play Contagion sound.
|
|
|
|
// Otherwise play Bonk sound ONCE
|
|
|
|
var canBeInfected = self.peeps.filter(function(peep){
|
|
|
|
return !peep.infected && peep.isPastThreshold;
|
|
|
|
}).length;
|
|
|
|
var isEveryoneInfected = true;
|
|
|
|
self.peeps.forEach(function(peep){
|
|
|
|
if(!peep.infected) isEveryoneInfected=false;
|
|
|
|
});
|
|
|
|
if(canBeInfected>0){
|
|
|
|
_PLAY_CONTAGION_SOUND();
|
|
|
|
}else if(self._canPlayBonkSound && !isEveryoneInfected){
|
|
|
|
self._canPlayBonkSound = false;
|
|
|
|
|
2018-04-24 14:22:06 +00:00
|
|
|
if(!self.options.NO_BONK){
|
2018-04-20 18:47:58 +00:00
|
|
|
SOUNDS.bonk.play();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2018-04-03 15:25:13 +00:00
|
|
|
// "Infect" the peeps who need to get infected
|
2018-04-18 20:28:41 +00:00
|
|
|
setTimeout(function(){
|
|
|
|
self.STEP++;
|
|
|
|
},400);
|
2018-04-15 21:24:22 +00:00
|
|
|
|
|
|
|
// CONNECTIONS: IF one is INFECTED and the other is PAST THRESHOLD, then ANIMATE
|
|
|
|
self.connections.forEach(function(c){
|
|
|
|
c.animate();
|
|
|
|
});
|
|
|
|
|
|
|
|
// PEEPS: If not already infected & past threshold, infect
|
|
|
|
self.peeps.forEach(function(peep){
|
|
|
|
if(!peep.infected && peep.isPastThreshold){
|
|
|
|
// timeout for animation
|
|
|
|
setTimeout(function(){
|
|
|
|
peep.infect();
|
|
|
|
},333);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// PEEPS: If NOT infected, NOT past threshold, and a friend IS INFECTED, then SHAKE
|
|
|
|
self.peeps.forEach(function(peep){
|
|
|
|
if(!peep.infected && !peep.isPastThreshold){
|
|
|
|
var friends = self.getFriendsOf(peep);
|
|
|
|
var infectedFriends = friends.filter(function(f){
|
|
|
|
return f.infected;
|
|
|
|
});
|
|
|
|
if(infectedFriends.length>0){
|
|
|
|
peep.shake();
|
|
|
|
}
|
|
|
|
}
|
2018-04-03 15:25:13 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
};
|
|
|
|
|
2018-03-28 14:57:19 +00:00
|
|
|
///////////////////////////////
|
|
|
|
// secret keyboard interface //
|
|
|
|
///////////////////////////////
|
|
|
|
|
|
|
|
// todo: active only when mouse is over MY CANVAS.
|
|
|
|
|
|
|
|
var _draggingPeep = null;
|
|
|
|
var _draggingOffset = {x:0,y:0};
|
2018-04-06 16:06:55 +00:00
|
|
|
var _keyHandlers = [];
|
2018-04-06 17:13:42 +00:00
|
|
|
var _resetConnectorCutter = function(){
|
|
|
|
self.connectorCutter.sandbox_state = 0;
|
|
|
|
};
|
2018-04-06 16:06:55 +00:00
|
|
|
_keyHandlers.push(subscribe("key/down/space",function(){
|
2018-04-06 17:13:42 +00:00
|
|
|
_resetConnectorCutter();
|
|
|
|
self._startMove();
|
|
|
|
}));
|
|
|
|
self._startMove = function(){
|
2018-03-28 14:57:19 +00:00
|
|
|
if(!_draggingPeep){ // prevent double-activation
|
|
|
|
var hoveredPeep = self.getHoveredPeep(0);
|
|
|
|
if(hoveredPeep){
|
|
|
|
_draggingPeep = hoveredPeep;
|
|
|
|
_draggingOffset.x = _draggingPeep.x-self.mouse.x;
|
|
|
|
_draggingOffset.y = _draggingPeep.y-self.mouse.y;
|
2018-04-20 20:10:18 +00:00
|
|
|
|
|
|
|
// Sound!
|
|
|
|
SOUNDS.squeak_down.volume(0.6);
|
|
|
|
SOUNDS.squeak_down.play();
|
|
|
|
|
2018-03-28 14:57:19 +00:00
|
|
|
}
|
|
|
|
}
|
2018-04-06 17:13:42 +00:00
|
|
|
};
|
2018-04-06 16:06:55 +00:00
|
|
|
_keyHandlers.push(subscribe("key/up/space",function(){
|
2018-04-06 17:13:42 +00:00
|
|
|
self._stopMove();
|
2018-04-06 16:06:55 +00:00
|
|
|
}));
|
2018-04-06 17:13:42 +00:00
|
|
|
self._stopMove = function(){
|
2018-04-20 20:10:18 +00:00
|
|
|
|
|
|
|
// Sound!
|
|
|
|
SOUNDS.squeak_up.volume(0.6);
|
|
|
|
SOUNDS.squeak_up.play();
|
|
|
|
|
2018-04-06 17:13:42 +00:00
|
|
|
_draggingPeep = null;
|
2018-04-20 20:10:18 +00:00
|
|
|
|
2018-04-06 17:13:42 +00:00
|
|
|
};
|
2018-04-06 16:06:55 +00:00
|
|
|
_keyHandlers.push(subscribe("key/down/1",function(){
|
2018-04-06 17:13:42 +00:00
|
|
|
_resetConnectorCutter();
|
|
|
|
self._addPeepAtMouse(false);
|
2018-04-06 16:06:55 +00:00
|
|
|
}));
|
|
|
|
_keyHandlers.push(subscribe("key/down/2",function(){
|
2018-04-06 17:13:42 +00:00
|
|
|
_resetConnectorCutter();
|
|
|
|
self._addPeepAtMouse(true);
|
2018-04-06 16:06:55 +00:00
|
|
|
}));
|
2018-04-06 17:13:42 +00:00
|
|
|
self._addPeepAtMouse = function(infected){
|
2018-04-20 20:10:18 +00:00
|
|
|
|
|
|
|
// SOUND
|
|
|
|
SOUNDS.pop.play();
|
|
|
|
|
2018-04-18 14:51:44 +00:00
|
|
|
self.addPeep(self.mouse.x, self.mouse.y, infected);
|
2018-04-20 20:10:18 +00:00
|
|
|
|
2018-03-28 14:57:19 +00:00
|
|
|
};
|
2018-04-06 16:06:55 +00:00
|
|
|
_keyHandlers.push(subscribe("key/down/delete",function(){
|
2018-04-06 17:13:42 +00:00
|
|
|
_resetConnectorCutter();
|
|
|
|
self._deletePeep();
|
|
|
|
}));
|
|
|
|
self._deletePeep = function(){
|
2018-04-20 20:10:18 +00:00
|
|
|
|
|
|
|
// SOUND
|
|
|
|
SOUNDS.trash.play();
|
|
|
|
|
2018-03-28 14:57:19 +00:00
|
|
|
var toDeletePeep = self.getHoveredPeep(0);
|
|
|
|
if(toDeletePeep) self.removePeep(toDeletePeep);
|
2018-04-20 20:10:18 +00:00
|
|
|
|
2018-04-06 17:13:42 +00:00
|
|
|
};
|
2018-03-28 14:57:19 +00:00
|
|
|
|
2018-04-03 15:25:13 +00:00
|
|
|
self.getCurrentNetwork = function(){
|
2018-03-28 14:57:19 +00:00
|
|
|
var savedNetwork = {
|
|
|
|
contagion: self.contagion,
|
|
|
|
peeps: [],
|
|
|
|
connections: []
|
|
|
|
};
|
|
|
|
self.peeps.forEach(function(peep){
|
|
|
|
savedNetwork.peeps.push([Math.round(peep.x), Math.round(peep.y), peep.infected?1:0]);
|
|
|
|
});
|
|
|
|
self.connections.forEach(function(c){
|
|
|
|
var fromIndex = self.peeps.indexOf(c.from);
|
|
|
|
var toIndex = self.peeps.indexOf(c.to);
|
2018-04-03 17:32:26 +00:00
|
|
|
var uncuttable = c.uncuttable ? 1 : 0;
|
|
|
|
savedNetwork.connections.push([fromIndex, toIndex, uncuttable]);
|
2018-03-28 14:57:19 +00:00
|
|
|
});
|
2018-04-03 15:25:13 +00:00
|
|
|
return savedNetwork;
|
|
|
|
};
|
|
|
|
self.serialize = function(){
|
|
|
|
var savedNetwork = self.getCurrentNetwork();
|
2018-04-01 16:34:52 +00:00
|
|
|
return '{\n'+
|
|
|
|
'\t"contagion":'+savedNetwork.contagion+",\n"+
|
|
|
|
'\t"peeps":'+JSON.stringify(savedNetwork.peeps)+",\n"+
|
2018-04-02 17:43:20 +00:00
|
|
|
'\t"connections":'+JSON.stringify(savedNetwork.connections)+"\n"+
|
2018-04-01 16:34:52 +00:00
|
|
|
'}';
|
2018-03-28 14:57:19 +00:00
|
|
|
};
|
|
|
|
|
2018-03-27 19:20:22 +00:00
|
|
|
////////////////
|
|
|
|
// HELPERS... //
|
|
|
|
////////////////
|
|
|
|
|
2018-03-27 17:39:08 +00:00
|
|
|
// Add Peeps/Connections
|
|
|
|
self.addPeep = function(x, y, infected){
|
|
|
|
var peep = new Peep({ x:x, y:y, infected:infected, sim:self });
|
|
|
|
self.peeps.push(peep);
|
|
|
|
return peep;
|
|
|
|
};
|
2018-03-28 14:57:19 +00:00
|
|
|
self.removePeep = function(peep){
|
|
|
|
self.removeAllConnectedTo(peep); // delete all connections
|
2018-04-01 16:34:52 +00:00
|
|
|
removeFromArray(self.peeps, peep); // BYE peep
|
2018-03-28 14:57:19 +00:00
|
|
|
};
|
2018-03-27 17:39:08 +00:00
|
|
|
self.addConnection = function(from, to, uncuttable){
|
|
|
|
|
|
|
|
// Don't allow connecting to self...
|
|
|
|
if(from==to) return;
|
|
|
|
|
|
|
|
// ...or if already exists, in either direction
|
|
|
|
for(var i=0; i<self.connections.length; i++){
|
|
|
|
var c = self.connections[i];
|
|
|
|
if(c.from==from && c.to==to) return;
|
|
|
|
if(c.from==to && c.to==from) return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, go ahead and add it!
|
|
|
|
var connection = new Connection({ from:from, to:to, uncuttable:uncuttable, sim:self });
|
|
|
|
self.connections.push(connection);
|
|
|
|
return connection;
|
|
|
|
|
|
|
|
};
|
2018-03-27 19:20:22 +00:00
|
|
|
self.getFriendsOf = function(peep){
|
|
|
|
var friends = [];
|
|
|
|
for(var i=0; i<self.connections.length; i++){ // in either direction
|
|
|
|
var c = self.connections[i];
|
|
|
|
if(c.from==peep) friends.push(c.to);
|
|
|
|
if(c.to==peep) friends.push(c.from);
|
|
|
|
}
|
|
|
|
return friends;
|
|
|
|
};
|
|
|
|
self.getHoveredPeep = function(mouseBuffer){
|
2018-03-28 14:57:19 +00:00
|
|
|
mouseBuffer = mouseBuffer || 0;
|
2018-03-27 19:20:22 +00:00
|
|
|
return self.peeps.find(function(peep){
|
|
|
|
return peep.hitTest(self.mouse.x, self.mouse.y, mouseBuffer);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
self.tryCuttingConnections = function(line){
|
2018-04-20 18:47:58 +00:00
|
|
|
var wasLineCut = 0;
|
2018-03-27 19:20:22 +00:00
|
|
|
for(var i=self.connections.length-1; i>=0; i--){ // going BACKWARDS coz killing connections
|
|
|
|
var c = self.connections[i];
|
2018-04-20 18:47:58 +00:00
|
|
|
if(c.hitTest(line)){
|
|
|
|
if(c.uncuttable){ // can't cut uncuttables!
|
|
|
|
wasLineCut = -1;
|
|
|
|
c.shake();
|
|
|
|
}else{
|
|
|
|
wasLineCut = 1;
|
|
|
|
self.connections.splice(i,1);
|
|
|
|
}
|
|
|
|
}
|
2018-03-27 19:20:22 +00:00
|
|
|
}
|
2018-04-20 18:47:58 +00:00
|
|
|
return wasLineCut;
|
2018-03-27 17:39:08 +00:00
|
|
|
};
|
2018-03-28 14:57:19 +00:00
|
|
|
self.removeAllConnectedTo = function(peep){
|
|
|
|
for(var i=self.connections.length-1; i>=0; i--){ // backwards index coz we're deleting
|
|
|
|
var c = self.connections[i];
|
|
|
|
if(c.from==peep || c.to==peep){ // in either direction
|
|
|
|
self.connections.splice(i,1); // remove!
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2018-03-27 17:39:08 +00:00
|
|
|
|
2018-03-27 19:20:22 +00:00
|
|
|
|
|
|
|
//////////////
|
|
|
|
// INIT NOW //
|
|
|
|
//////////////
|
|
|
|
|
2018-04-03 17:32:26 +00:00
|
|
|
// Start Uncuttable?
|
|
|
|
if(self.options.startUncuttable){
|
|
|
|
self.networkConfig.connections.forEach(function(c){
|
|
|
|
c[2] = 1;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-03-27 17:39:08 +00:00
|
|
|
self.init();
|
|
|
|
|
2018-04-10 17:00:22 +00:00
|
|
|
// Start randomize positions?
|
|
|
|
if(self.options.randomStart){
|
|
|
|
var r = {
|
|
|
|
x:self.options.randomStart,
|
|
|
|
y:0
|
|
|
|
};
|
|
|
|
self.peeps.forEach(function(peep){
|
|
|
|
var randomPush = rotateVector(r, Math.random()*Math.TAU);
|
|
|
|
peep.x += randomPush.x;
|
|
|
|
peep.y += randomPush.y;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-03-28 14:57:19 +00:00
|
|
|
}
|