Skip to content

Instantly share code, notes, and snippets.

@STRd6 STRd6/TODO.md
Last active Dec 21, 2015

Embed
What would you like to do?
LD27 Trash Robots
@App =
width: 800
height: 450
%canvas(width=800 height=450)
@Blade = (I={}) ->
Object.defaults I,
color: "white"
damage: 100
duration: 0.05
radius: 10
self = GameObject(I)
self.include "Damager"
return self
(function() {
var _base;
this.HAMLjr || (this.HAMLjr = {});
(_base = this.HAMLjr).templates || (_base.templates = {});
this.HAMLjr.templates["arena"] = function(data) {
return (function() {
var __attribute, __each, __element, __filter, __on, __pop, __push, __render, __text, __with, _ref;
_ref = HAMLjr.Runtime(this), __push = _ref.__push, __pop = _ref.__pop, __attribute = _ref.__attribute, __filter = _ref.__filter, __text = _ref.__text, __on = _ref.__on, __each = _ref.__each, __with = _ref.__with, __render = _ref.__render;
__push(document.createDocumentFragment());
__element = document.createElement("canvas");
__push(__element);
__attribute(__element, "width", 800);
__attribute(__element, "height", 450);
__pop();
return __pop();
}).call(data);
};
}).call(this);
(function() {
var _base;
this.HAMLjr || (this.HAMLjr = {});
(_base = this.HAMLjr).templates || (_base.templates = {});
this.HAMLjr.templates["intro"] = function(data) {
return (function() {
var __attribute, __each, __element, __filter, __on, __pop, __push, __render, __text, __with, _ref;
_ref = HAMLjr.Runtime(this), __push = _ref.__push, __pop = _ref.__pop, __attribute = _ref.__attribute, __filter = _ref.__filter, __text = _ref.__text, __on = _ref.__on, __each = _ref.__each, __with = _ref.__with, __render = _ref.__render;
__push(document.createDocumentFragment());
__element = document.createElement("div");
__push(__element);
__attribute(__element, "class", "intro");
__attribute(__element, "style", "background-image: url(http://a2.pixiecdn.com/9756865efbcd9ea98cdc0c8636a1fa76343891d1)");
__element = document.createElement("div");
__push(__element);
__attribute(__element, "class", "content-wrap");
__element = document.createElement("div");
__push(__element);
__attribute(__element, "class", "content");
__element = document.createElement("h1");
__push(__element);
__element = document.createTextNode('');
__text(__element, "Trash Robots\n");
__push(__element);
__pop();
__pop();
__element = document.createElement("p");
__push(__element);
__element = document.createTextNode('');
__text(__element, "Build your robot dream team in 10 seconds from garbage.\n");
__push(__element);
__pop();
__pop();
__element = document.createElement("button");
__push(__element);
__element = document.createTextNode('');
__text(__element, "Start\n");
__push(__element);
__pop();
__on("click", function() {
console.log("clicked");
return goToSetup();
});
__pop();
__pop();
__pop();
__pop();
return __pop();
}).call(data);
};
}).call(this);
(function() {
var _base;
this.HAMLjr || (this.HAMLjr = {});
(_base = this.HAMLjr).templates || (_base.templates = {});
this.HAMLjr.templates["setup"] = function(data) {
return (function() {
var chooseItem, chooseRobot, __attribute, __each, __element, __filter, __on, __pop, __push, __render, __text, __with, _ref;
_ref = HAMLjr.Runtime(this), __push = _ref.__push, __pop = _ref.__pop, __attribute = _ref.__attribute, __filter = _ref.__filter, __text = _ref.__text, __on = _ref.__on, __each = _ref.__each, __with = _ref.__with, __render = _ref.__render;
__push(document.createDocumentFragment());
__element = document.createElement("div");
__push(__element);
__attribute(__element, "class", "setup");
__attribute(__element, "style", "background-image: url(http://a3.pixiecdn.com/f433296ce7b90ad0ccf61705b5eda754bb04af83)");
__element = document.createElement("div");
__push(__element);
__attribute(__element, "class", "content-wrap");
__element = document.createElement("div");
__push(__element);
__attribute(__element, "class", "content");
__element = document.createElement("div");
__push(__element);
__attribute(__element, "class", "time");
__element = document.createTextNode('');
__text(__element, "10.0\n");
__push(__element);
__pop();
__pop();
chooseRobot = this.chooseRobot, chooseItem = this.chooseItem;
__element = document.createElement("ul");
__push(__element);
__attribute(__element, "class", "robots");
__each(this.robots, function(robot) {
__element = document.createElement("li");
__push(__element);
__element = document.createElement("h3");
__push(__element);
__attribute(__element, "class", "name");
__element = document.createTextNode('');
__text(__element, robot.name);
__push(__element);
__pop();
__pop();
__element = document.createElement("div");
__push(__element);
__attribute(__element, "class", "weapons");
__element = document.createElement("h4");
__push(__element);
__element = document.createTextNode('');
__text(__element, "Weapons\n");
__push(__element);
__pop();
__pop();
__element = document.createTextNode('');
__text(__element, robot.oWeapons);
__push(__element);
__pop();
__pop();
__element = document.createElement("div");
__push(__element);
__attribute(__element, "class", "motors");
__element = document.createElement("h3");
__push(__element);
__element = document.createTextNode('');
__text(__element, "Motors\n");
__push(__element);
__pop();
__pop();
__element = document.createTextNode('');
__text(__element, robot.oMotors);
__push(__element);
__pop();
__pop();
__element = document.createElement("div");
__push(__element);
__attribute(__element, "class", "navigation");
__element = document.createElement("h3");
__push(__element);
__element = document.createTextNode('');
__text(__element, "Navigation\n");
__push(__element);
__pop();
__pop();
__element = document.createTextNode('');
__text(__element, robot.oNavigation);
__push(__element);
__pop();
__pop();
__on("click", function(e) {
$(".robots li").removeClass("selected");
$(e.currentTarget).addClass("selected");
return chooseRobot(robot);
});
return __pop();
});
__pop();
__element = document.createElement("ul");
__push(__element);
__attribute(__element, "class", "weapons");
__element = document.createElement("li");
__push(__element);
__element = document.createTextNode('');
__text(__element, "\n");
__push(__element);
__pop();
__element = document.createElement("h3");
__push(__element);
__element = document.createTextNode('');
__text(__element, "Weapons\n");
__push(__element);
__pop();
__pop();
__pop();
__each(this.weapons, function(item) {
__element = document.createElement("li");
__push(__element);
__element = document.createElement("button");
__push(__element);
__element = document.createTextNode('');
__text(__element, item.projectile.underscore().titleize());
__push(__element);
__pop();
__on("click", function() {
return chooseItem(item, "weapons");
});
__pop();
return __pop();
});
__pop();
__element = document.createElement("ul");
__push(__element);
__attribute(__element, "class", "motors");
__element = document.createElement("li");
__push(__element);
__element = document.createElement("h3");
__push(__element);
__element = document.createTextNode('');
__text(__element, "Motors\n");
__push(__element);
__pop();
__pop();
__pop();
__each(this.motors, function(item) {
__element = document.createElement("li");
__push(__element);
__element = document.createElement("button");
__push(__element);
__element = document.createTextNode('');
__text(__element, item.type.titleize());
__push(__element);
__pop();
__on("click", function() {
return chooseItem(item, "motors");
});
__pop();
return __pop();
});
__pop();
__element = document.createElement("ul");
__push(__element);
__attribute(__element, "class", "navigation");
__element = document.createElement("li");
__push(__element);
__element = document.createTextNode('');
__text(__element, "\n");
__push(__element);
__pop();
__element = document.createElement("h3");
__push(__element);
__element = document.createTextNode('');
__text(__element, "Navigation\n");
__push(__element);
__pop();
__pop();
__pop();
__each(this.navigation, function(item) {
__element = document.createElement("li");
__push(__element);
__element = document.createElement("button");
__push(__element);
__element = document.createTextNode('');
__text(__element, item.type);
__push(__element);
__pop();
__on("click", function() {
return chooseItem(item, "navigation");
});
__pop();
return __pop();
});
__pop();
__pop();
__pop();
__pop();
return __pop();
}).call(data);
};
}).call(this);
(function() {
this.App = {
width: 800,
height: 450
};
}).call(this);
(function() {
this.Blade = function(I) {
var self;
if (I == null) {
I = {};
}
Object.defaults(I, {
color: "white",
damage: 100,
duration: 0.05,
radius: 10
});
self = GameObject(I);
self.include("Damager");
return self;
};
}).call(this);
(function() {
this.Damager = function(I, self) {
if (I == null) {
I = {};
}
Object.defaults(I, {
selfDamage: false
});
return {
hit: function(target, dt) {
if ((target === I.source) && !I.selfDamage) {
return;
}
return target.I.health -= I.damage * dt;
}
};
};
}).call(this);
(function() {
this.Explosion = function(I) {
var self;
if (I == null) {
I = {};
}
Object.defaults(I, {
damage: 50,
duration: 0.3,
radiusMax: 100,
radius: 0
});
self = GameObject(I);
self.include("Damager");
self.unbind("draw");
self.on("draw", function(canvas) {
return canvas.drawCircle({
x: 0,
y: 0,
radius: I.radius,
color: "yellow"
});
});
self.on("update", function() {
return I.radius = I.radiusMax * I.age / I.duration;
});
return self;
};
}).call(this);
(function() {
var __slice = [].slice;
["log", "info", "error", "warn"].each(function(name) {
return window[name] = function() {
var args;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
if (typeof console !== "undefined" && console !== null) {
return console[name].apply(console, args);
}
};
});
}).call(this);
(function() {
this.Missile = function(I) {
var self;
if (I == null) {
I = {};
}
Object.defaults(I, {
speed: 250,
heading: 0,
radius: 10
});
self = GameObject(I);
self.on("destroy", function() {
return engine.add("Explosion", {
x: I.x,
y: I.y
});
});
self.on("update", function() {
I.velocity = Point.fromAngle(I.heading).scale(I.speed);
if (I.x <= 0 || I.x >= App.width || I.y <= 0 || I.y >= App.height) {
self.I.x = self.I.x.clamp(0, App.width);
self.I.y = self.I.y.clamp(0, App.height);
return self.destroy();
}
});
return self;
};
}).call(this);
(function() {
this.Motor = function(I) {
var self;
if (I == null) {
I = {};
}
Object.defaults(I, {
phaseOffset: 0,
type: "constant",
switchCooldown: 0,
switchDelay: 0.5,
lastPower: 0
});
self = GameObject(I).extend({
power: function() {
var lastPower;
if (I.switchCooldown) {
lastPower;
} else {
I.switchCooldown += I.switchDelay;
}
return lastPower = (function() {
switch (I.type) {
case "periodic":
return Math.sin((I.age + I.phaseOffset) * Math.TAU) + 0.5;
case "jittery":
return rand() * 0.5;
case "slow":
return 0.5;
default:
return 1;
}
})();
}
});
self.cooldown("switchCooldown");
return self;
};
this.Motor.random = function() {
return [
{
type: "slow"
}, {
type: "constant"
}, {
type: "jittery"
}, {
type: "periodic",
phaseOffset: rand(),
switchDelay: 0
}
].rand();
};
}).call(this);
(function() {
this.Navigation = function(I) {
var self;
if (I == null) {
I = {};
}
Object.defaults(I, {
type: "facing",
switchDelay: 2,
switchCooldown: 0
});
self = GameObject(I).extend({
navigate: function(source) {
if (I.switchCooldown) {
} else {
I.switchCooldown += I.switchDelay;
switch (I.type) {
case "random":
return rand() * Math.TAU;
case "spiral":
return 10 * Math.log(source.I.age / 10 + 1) + source.I.headingInitial;
case "strafe":
if (rand() > 0.5) {
return source.I.facing + 0.25.turns;
} else {
return source.I.facing - 0.25.turns;
}
break;
default:
return source.I.facing;
}
}
}
});
self.cooldown("switchCooldown");
return self;
};
this.Navigation.random = function() {
return [
{
type: "facing"
}, {
type: "spiral",
switchDelay: 0.25
}, {
type: "random"
}, {
type: "strafe"
}
].rand();
};
}).call(this);
(function() {
!function(){var __slice=[].slice;!function($){Number.prototype.clamp=function(min,max){return Math.min(Math.max(min,this),max)};Math.TAU=2*Math.PI;return $.fn.pixieCanvas=function(options){var $canvas,canvas,canvasAttrAccessor,context,contextAttrAccessor;if(options==null){options={}}canvas=this.get(0);context=void 0;$canvas=$.extend($(canvas),{withTransform:function(matrix,block){context.save();context.transform(matrix.a,matrix.b,matrix.c,matrix.d,matrix.tx,matrix.ty);try{block(this)}finally{context.restore()}return this},clear:function(_arg){var height,width,x,y,_ref;_ref=_arg!=null?_arg:{},x=_ref.x,y=_ref.y,width=_ref.width,height=_ref.height;if(x==null){x=0}if(y==null){y=0}if(width==null){width=canvas.width}if(height==null){height=canvas.height}context.clearRect(x,y,width,height);return this},fill:function(color){var bounds,height,width,x,y,_ref;if(color==null){color={}}if(!(typeof color==="string"||color.channels)){_ref=color,x=_ref.x,y=_ref.y,width=_ref.width,height=_ref.height,bounds=_ref.bounds,color=_ref.color}if(bounds){x=bounds.x,y=bounds.y,width=bounds.width,height=bounds.height}x||(x=0);y||(y=0);if(width==null){width=canvas.width}if(height==null){height=canvas.height}this.fillColor(color);context.fillRect(x,y,width,height);return this},drawImage:function(){var args;args=1<=arguments.length?__slice.call(arguments,0):[];context.drawImage.apply(context,args);return this},drawCircle:function(_arg){var circle,color,position,radius,stroke,x,y;x=_arg.x,y=_arg.y,radius=_arg.radius,position=_arg.position,color=_arg.color,stroke=_arg.stroke,circle=_arg.circle;if(circle){x=circle.x,y=circle.y,radius=circle.radius}if(position){x=position.x,y=position.y}radius=radius.clamp(0,Infinity);context.beginPath();context.arc(x,y,radius,0,Math.TAU,true);context.closePath();if(color){this.fillColor(color);context.fill()}if(stroke){this.strokeColor(stroke.color);this.lineWidth(stroke.width);context.stroke()}return this},drawRect:function(_arg){var bounds,color,height,position,stroke,width,x,y;x=_arg.x,y=_arg.y,width=_arg.width,height=_arg.height,position=_arg.position,bounds=_arg.bounds,color=_arg.color,stroke=_arg.stroke;if(bounds){x=bounds.x,y=bounds.y,width=bounds.width,height=bounds.height}if(position){x=position.x,y=position.y}if(color){this.fillColor(color);context.fillRect(x,y,width,height)}if(stroke){this.strokeColor(stroke.color);this.lineWidth(stroke.width);context.strokeRect(x,y,width,height)}return this},drawLine:function(_arg){var color,direction,end,length,start,width;start=_arg.start,end=_arg.end,width=_arg.width,color=_arg.color,direction=_arg.direction,length=_arg.length;width||(width=3);if(direction){end=direction.norm(length).add(start)}this.lineWidth(width);this.strokeColor(color);context.beginPath();context.moveTo(start.x,start.y);context.lineTo(end.x,end.y);context.closePath();context.stroke();return this},drawPoly:function(_arg){var color,points,stroke;points=_arg.points,color=_arg.color,stroke=_arg.stroke;context.beginPath();points.forEach(function(point,i){if(i===0){return context.moveTo(point.x,point.y)}else{return context.lineTo(point.x,point.y)}});context.lineTo(points[0].x,points[0].y);if(color){this.fillColor(color);context.fill()}if(stroke){this.strokeColor(stroke.color);this.lineWidth(stroke.width);context.stroke()}return this},drawRoundRect:function(_arg){var bounds,color,height,position,radius,stroke,width,x,y;x=_arg.x,y=_arg.y,width=_arg.width,height=_arg.height,radius=_arg.radius,position=_arg.position,bounds=_arg.bounds,color=_arg.color,stroke=_arg.stroke;if(radius==null){radius=5}if(bounds){x=bounds.x,y=bounds.y,width=bounds.width,height=bounds.height}if(position){x=position.x,y=position.y}context.beginPath();context.moveTo(x+radius,y);context.lineTo(x+width-radius,y);context.quadraticCurveTo(x+width,y,x+width,y+radius);context.lineTo(x+width,y+height-radius);context.quadraticCurveTo(x+width,y+height,x+width-radius,y+height);context.lineTo(x+radius,y+height);context.quadraticCurveTo(x,y+height,x,y+height-radius);context.lineTo(x,y+radius);context.quadraticCurveTo(x,y,x+radius,y);context.closePath();if(color){this.fillColor(color);context.fill()}if(stroke){this.lineWidth(stroke.width);this.strokeColor(stroke.color);context.stroke()}return this},drawText:function(_arg){var color,font,position,text,x,y;x=_arg.x,y=_arg.y,text=_arg.text,position=_arg.position,color=_arg.color,font=_arg.font;if(position){x=position.x,y=position.y}this.fillColor(color);if(font){this.font(font)}context.fillText(text,x,y);return this},centerText:function(_arg){var color,font,position,text,textWidth,x,y;text=_arg.text,x=_arg.x,y=_arg.y,position=_arg.position,color=_arg.color,font=_arg.font;if(position){x=position.x,y=position.y}if(x==null){x=canvas.width/2}textWidth=this.measureText(text);return this.drawText({text:text,color:color,font:font,x:x-textWidth/2,y:y})},fillColor:function(color){if(color){if(color.channels){context.fillStyle=color.toString()}else{context.fillStyle=color}return this}else{return context.fillStyle}},strokeColor:function(color){if(color){if(color.channels){context.strokeStyle=color.toString()}else{context.strokeStyle=color}return this}else{return context.strokeStyle}},measureText:function(text){return context.measureText(text).width},putImageData:function(){var args;args=1<=arguments.length?__slice.call(arguments,0):[];context.putImageData.apply(context,args);return this},context:function(){return context},element:function(){return canvas},createPattern:function(image,repitition){return context.createPattern(image,repitition)},clip:function(x,y,width,height){context.beginPath();context.rect(x,y,width,height);context.clip();return this}});contextAttrAccessor=function(){var attrs;attrs=1<=arguments.length?__slice.call(arguments,0):[];return attrs.forEach(function(attr){return $canvas[attr]=function(newVal){if(newVal!=null){context[attr]=newVal;return this}else{return context[attr]}}})};contextAttrAccessor("font","globalAlpha","globalCompositeOperation","lineWidth","textAlign");canvasAttrAccessor=function(){var attrs;attrs=1<=arguments.length?__slice.call(arguments,0):[];return attrs.forEach(function(attr){return $canvas[attr]=function(newVal){if(newVal!=null){canvas[attr]=newVal;return this}else{return canvas[attr]}}})};canvasAttrAccessor("height","width");if(canvas!=null?canvas.getContext:void 0){context=canvas.getContext("2d");if(options.init){options.init($canvas)}return $canvas}}}(typeof jQuery!=="undefined"&&jQuery!==null?jQuery:Zepto)}.call(this);;
}).call(this);
(function() {
this.PointDefense = function(I) {
var self;
if (I == null) {
I = {};
}
Object.defaults(I, {
color: "purple",
duration: 0.5,
range: 300
});
self = GameObject(I);
self.on("update", function() {
var closest;
closest = engine.find("Missile").select(function(missile) {
return missile.I.source.I.team !== I.source.I.team;
}).map(function(missile) {
return {
distance: Point.distance(self.position(), missile.position()),
object: missile
};
}).sort(function(_arg, _arg1) {
var a, b;
a = _arg.distance;
b = _arg1.distance;
return b - a;
}).first();
if (closest && closest.distance <= I.range) {
I.target = closest.object;
I.target.destroy();
return self.destroy();
}
});
self.on("destroy", function() {
return engine.add("Ray", {
start: I.source.position(),
end: I.target.position(),
color: I.color
});
});
return self;
};
}).call(this);
(function() {
this.Ray = function(I) {
var self;
if (I == null) {
I = {};
}
Object.defaults(I, {
duration: 0.25
});
self = GameObject(I);
self.on("update", function() {
return I.alpha = 1 - I.age / I.duration;
});
self.unbind("draw");
self.bind("draw", function(canvas) {
return canvas.drawLine({
start: I.start,
end: I.end,
color: I.color
});
});
return self;
};
}).call(this);
(function() {
this.Robot = function(I) {
var drawDebug, meter, motors, navigation, self, targetting, weapons;
if (I == null) {
I = {};
}
Object.defaults(I, {
color: "blue",
width: 50,
height: 50,
speed: 100,
name: "Robot",
heading: 0,
headingInitial: 0,
facing: 0,
facingInitial: 0,
health: 100,
healthMax: 100,
radius: 25,
team: 0,
motors: [Motor.random()],
navigation: Navigation.random(),
targetting: Targetting.random(),
weapons: [Weapon.random()]
});
weapons = I.weapons.map(Weapon);
motors = I.motors.map(Motor);
navigation = Navigation(I.navigation);
targetting = Targetting(I.targetting);
self = GameObject(I);
self.clampToBounds();
meter = function() {
var color, healthRatio;
healthRatio = I.health / I.healthMax;
if (healthRatio > 0.5) {
color = Color.mix(Color("green"), Color("yellow"), (healthRatio - 0.5) * 2);
} else {
color = Color.mix(Color("yellow"), Color("red"), healthRatio * 2);
}
return self.meter("health", {
color: color,
x: I.x - I.width / 2,
y: I.y - I.height / 2 - 20,
width: I.width
});
};
self.on("update", function(elapsedTime) {
if (I.health <= 0) {
self.destroy();
}
meter();
navigation.update(elapsedTime);
I.heading = navigation.navigate(self) || I.heading;
targetting.update(elapsedTime);
I.facing = targetting.target(self) || I.facing;
motors.invoke("update", elapsedTime);
I.power = motors.invoke("power").sum().clamp(0, 2);
weapons.invoke("update", elapsedTime);
weapons.invoke("fire", self, elapsedTime);
return I.velocity = Point.fromAngle(I.heading).scale(I.speed * I.power);
});
self.on("afterUpdate", function(elapsedTime) {
return motors.invoke("trigger", "afterUpdate", elapsedTime);
});
drawDebug = function(canvas) {
var end, p;
p = self.position();
end = p.add(Point.fromAngle(I.facing).scale(40));
canvas.drawLine({
start: p,
end: end,
color: "rgb(255, 0, 255)"
});
end = p.add(Point.fromAngle(I.heading).scale(40));
return canvas.drawLine({
start: p,
end: end,
color: "rgb(0, 255, 0)"
});
};
self.on("draw", function(canvas) {
canvas.font("bold 16px consolas, 'Courier New', 'andale mono', 'lucida console', monospace");
return canvas.centerText({
color: "white",
x: 0,
y: -I.height / 2,
text: I.name
});
});
self.on("overlay", drawDebug);
self.on("destroy", function() {
return engine.add("Explosion", {
x: I.x,
y: I.y
});
});
return self;
};
}).call(this);
(function() {
var defaultBot, defaultItem, randomName;
randomName = function() {
var name, prefix, suffix;
prefix = ["Mr. ", "Dr. ", "Ms. ", "Mrs. ", "The ", "", "", "", "", "El ", "Lord ", "Lady ", "Duke ", "Duchess "].rand();
name = ["Donuts", "BMO", "Bacon", "Duder", "Dude", "Hotdog", "Squirtle", "Killer", "Ronnie", "Joe", "Stalin", "Reginald", "Toast", "Margarine", "Juice", "Q", "DP", "Obama", "Hitler", "YOLO"].rand();
suffix = [" Jr", " II", " Sr", " III", " IV", " of Death", "", "", "", "", "", "", "", ""].rand();
return "" + prefix + name + suffix;
};
defaultBot = function() {
var bot;
bot = {
name: randomName(),
motors: [],
weapons: [],
targetting: null,
navigation: null
};
bot.oNavigation = Observable("");
bot.oWeapons = Observable("");
bot.oMotors = Observable("");
return bot;
};
defaultItem = function() {
return {
type: "default"
};
};
this.Setup = function(I) {
var activeRobot, add, removeFromArray, self;
if (I == null) {
I = {};
}
Object.defaults(I, {
robots: [defaultBot(), defaultBot(), defaultBot()],
motors: [Motor.random(), Motor.random(), Motor.random(), Motor.random()],
weapons: [Weapon.random(), Weapon.random(), Weapon.random(), Weapon.random()],
targetting: [Targetting.random(), Targetting.random()],
navigation: [Navigation.random(), Navigation.random(), Navigation.random()]
});
activeRobot = I.robots.first();
removeFromArray = function(item, slot) {
return I.robots.each(function(robot) {
return self[slot].remove(item);
});
};
add = function(robot, item, slot) {
if (slot.lastIndexOf('s') === slot.length - 1) {
if (robot[slot].length < 2) {
removeFromArray(item, slot);
robot[slot].push(item);
}
} else {
if (robot[slot] === null) {
removeFromArray(item, slot);
robot[slot] = item;
}
}
return I.robots.each(function(robot) {
var _ref;
robot.oMotors(robot.motors.map(function(m) {
return m.type.titleize();
}).join(","));
robot.oWeapons(robot.weapons.map(function(w) {
return w.projectile.underscore().titleize();
}).join(","));
return robot.oNavigation(((_ref = robot.navigation) != null ? _ref.type.titleize() : void 0) || "");
});
};
self = Model(I).extend({
chooseItem: function(item, slot) {
return add(activeRobot, item, slot);
},
chooseRobot: function(robot) {
return activeRobot = robot;
}
});
self.observeAll();
return self;
};
}).call(this);
(function() {
this.Targetting = function(I) {
var self, target;
if (I == null) {
I = {};
}
Object.defaults(I, {
type: "nearest",
switchDelay: 2,
switchCooldown: 0
});
target = null;
self = GameObject(I).extend({
target: function(source) {
var selector;
selector = "Robot.team=" + (+(!source.I.team));
if (target && I.switchCooldown) {
} else {
I.switchCooldown += I.switchDelay;
switch (I.type) {
case "random":
target = {
position: function() {
return Point(rand(App.width), rand(App.height));
}
};
break;
case "randomEnemy":
target = engine.find(selector).rand();
break;
default:
target = engine.closest(selector, source.position());
}
}
if (target) {
return Point.direction(source.position(), target.position());
}
}
});
self.cooldown("switchCooldown");
return self;
};
this.Targetting.random = function() {
return [
{
type: "random"
}, {
type: "randomEnemy"
}, {
type: "nearestEnemy"
}
].rand();
};
}).call(this);
(function() {
this.Weapon = function(I) {
var self;
if (I == null) {
I = {};
}
Object.defaults(I, {
delay: 1,
cooldown: 1,
projectile: "Missile",
spread: 0.05.turns
});
self = GameObject(I).extend({
fire: function(source, dt) {
var facing, p;
if (I.cooldown === 0) {
I.cooldown = I.delay;
facing = source.I.facing;
p = source.position().add(source.velocity().scale(dt)).add(Point.fromAngle(facing).scale(40));
return engine.add(I.projectile, {
x: p.x,
y: p.y,
heading: facing + rand() * I.spread - I.spread / 2,
source: source
});
}
}
});
self.cooldown("cooldown");
return self;
};
this.Weapon.random = function() {
return [
{
projectile: "Missile",
cooldown: rand() + 1
}, {
projectile: "Blade",
delay: 0.05,
cooldown: 0
}, {
projectile: "PointDefense"
}, {
projectile: "Teleporter",
delay: 4,
cooldown: 2 + 2 * rand()
}, {
projectile: "Flame",
delay: 0.25
}, {
projectile: "Laser",
delay: 0.6,
cooldown: 1 + rand()
}
].rand();
};
}).call(this);
(function() {
this.Teleporter = function(I) {
var self;
if (I == null) {
I = {};
}
Object.defaults(I, {
color: "lime"
});
self = GameObject(I);
self.on("update", function() {
if (I.age > 1) {
self.destroy();
I.source.I.x = rand(App.width);
return I.source.I.y = rand(App.height);
}
});
return self;
};
}).call(this);
(function() {
this.Flame = function(I) {
var self;
if (I == null) {
I = {};
}
Object.defaults(I, {
damage: 10,
duration: 3,
radiusMax: 50,
radius: 0,
speed: 150,
opacity: 0.5,
selfDamage: true
});
self = GameObject(I);
self.include("Damager");
self.unbind("draw");
self.on("draw", function(canvas) {
return canvas.drawCircle({
x: 0,
y: 0,
radius: I.radius,
color: "orange"
});
});
self.on("update", function() {
I.velocity = Point.fromAngle(I.heading).scale(I.speed);
return I.radius = I.radiusMax * I.age / I.duration;
});
return self;
};
}).call(this);
(function() {
this.Laser = function(I) {
var self;
if (I == null) {
I = {};
}
Object.defaults(I, {
color: "cyan",
duration: 0.5,
damage: 2
});
self = GameObject(I);
self.on("update", function() {
var closest;
closest = engine.closest("Robot.team=" + (+(!I.source.I.team)), self.position());
if (closest) {
I.target = closest;
I.target.I.health -= I.damage;
return self.destroy();
}
});
self.on("destroy", function() {
return engine.add("Ray", {
start: I.source.position(),
end: I.target.position(),
color: I.color
});
});
return self;
};
}).call(this);
(function() {
var $root, appRoot, canvas, countdownInterval, gist, objectCollision, setupTimeLimit, startGame, styleContent, _ref;
$root = ENV.$root, gist = ENV.gist;
appRoot = $("<div>", {
"class": "app"
});
$root.append(appRoot);
$root = appRoot;
Object.keys(gist.files).each(function(name) {
if (name.extension() === "js" && name.withoutExtension() !== "build") {
return Function(gist.files[name].content)();
}
});
if (styleContent = (_ref = gist.files["style.css"]) != null ? _ref.content : void 0) {
$root.append($("<style>", {
html: styleContent
}));
}
$root.append(HAMLjr.templates.arena());
$root.append(HAMLjr.templates.intro());
countdownInterval = null;
setupTimeLimit = 10;
this.goToSetup = function() {
var setupConfig, setupStartedAt;
$(".setup").remove();
setupConfig = Setup();
$root.append(HAMLjr.templates.setup(setupConfig));
$(".intro").hide();
setupStartedAt = +(new Date);
clearInterval(countdownInterval);
return countdownInterval = setInterval(function() {
var color, size, time;
time = (setupStartedAt + setupTimeLimit * 1000 - (+(new Date))) / 1000;
size = (time % 1) * 20 + 40;
color = "lime";
if (time < 6) {
color = "yellow";
}
if (time < 3) {
color = "red";
}
if (time < 0) {
clearInterval(countdownInterval);
return startGame(setupConfig);
} else {
return $(".setup .time").text(time.toFixed(2)).css({
fontSize: size,
color: color
});
}
}, 15);
};
canvas = $root.children("canvas").pixieCanvas();
canvas.fill("gray");
this.engine = Engine({
canvas: canvas,
FPS: 60
});
startGame = function(config) {
$(".setup").hide();
$(".intro").hide();
engine.start();
return config.robots.each(function(data, i) {
var y;
y = (i + 1) * App.height / 4;
engine.add("Robot", Object.extend(data, {
y: y
}));
return engine.add("Robot", {
color: "red",
x: App.width,
y: y,
headingInitial: 0.5.turns,
team: 1
});
});
};
engine.on("overlay", function(canvas) {
return canvas.drawText({
x: 10,
y: 20,
color: "white",
text: engine.objects().length
});
});
objectCollision = function(a, b) {
return Collision.circular(a.circle(), b.circle());
};
engine.on("update", function(dt) {
Collision.collide("Missile", "Robot", function(missile, robot) {
if (missile.I.source === robot) {
return;
}
return missile.destroy();
}, objectCollision);
Collision.collide("Explosion, Blade, Flame", "Robot", function(explosion, robot) {
return explosion.hit(robot, dt);
}, objectCollision);
Collision.collide("Explosion", "Missile", function(explosion, missile) {
return missile.destroy();
}, objectCollision);
return engine.find("Missile").eachPair(function(a, b) {
if (objectCollision(a, b)) {
a.destroy();
return b.destroy();
}
});
});
}).call(this);
@Damager = (I={}, self) ->
Object.defaults I,
selfDamage: false
hit: (target, dt) ->
return if (target is I.source) and !I.selfDamage
target.I.health -= I.damage * dt
@Explosion = (I={}) ->
Object.defaults I,
damage: 50 # Per second
duration: 0.3
radiusMax: 100
radius: 0
self = GameObject(I)
self.include "Damager"
self.unbind "draw"
self.on "draw", (canvas) ->
canvas.drawCircle
x: 0
y: 0
radius: I.radius
color: "yellow"
self.on "update", ->
I.radius = I.radiusMax * I.age / I.duration
return self
@Flame = (I={}) ->
Object.defaults I,
damage: 10 # Per second
duration: 3
radiusMax: 50
radius: 0
speed: 150
opacity: 0.5
selfDamage: true
self = GameObject(I)
self.include "Damager"
self.unbind "draw"
self.on "draw", (canvas) ->
canvas.drawCircle
x: 0
y: 0
radius: I.radius
color: "orange"
self.on "update", ->
I.velocity = Point.fromAngle(I.heading).scale(I.speed)
I.radius = I.radiusMax * I.age / I.duration
return self
// Generated by CoffeeScript 1.6.3
/**
The ActiveBounds module automatically destroys objects that
are outside of the specified bounds. The default bounds are
the dimensions of your game. Useful for bullet type objects.
bullet = GameObject
x: 10
y: 50
width: 20
height: 20
velocity: Point(120, 0)
bullet.include ActiveBounds
# => bullet will be removed when it
goes outside of the game bounds.
bullet2 = GameObject
x: 50
y: 50
width: 30
height: 20
activeBounds: Rectangle
x: 30
y: 20
width: 100
height 100
# => bullet2 will be removed unless 30 <= I.x <= 130
and 20 <= I.y <= 120
@name ActiveBounds
@module
@constructor
@param {Object} I Instance variables
@param {Core} self Reference to including object
*/
var CameraTarget,
__slice = [].slice;
this.ActiveBounds = function(I, self) {
if (I == null) {
I = {};
}
Object.reverseMerge(I, {
activeBounds: Rectangle({
x: 0,
y: 0,
width: App.width,
height: App.height
})
});
return self.bind('update', function() {
var _ref, _ref1;
if (!((I.activeBounds.left <= (_ref = I.x) && _ref <= I.activeBounds.right))) {
self.destroy();
}
if (!((I.activeBounds.top <= (_ref1 = I.y) && _ref1 <= I.activeBounds.bottom))) {
return self.destroy();
}
});
};
/**
The Ageable module handles keeping track of an object's age.
player = GameObject()
player.update(1)
#=> player.I.age == 1
@name Ageable
@module
@constructor
@param {Object} I Instance variables
@param {Core} self Reference to including object
*/
this.Ageable = function(I, self) {
if (I == null) {
I = {};
}
Object.reverseMerge(I, {
age: 0
});
self.bind('afterUpdate', function(dt) {
return I.age += dt;
});
return {};
};
/**
The Bounded module is used to provide basic data about the
location and dimensions of the including object. This module is included
by default in `GameObject`.
player = Core
x: 10
y: 50
width: 20
height: 20
other: "stuff"
more: "properties"
player.position()
# => Uncaught TypeError: Object has no method 'position'
player.include(Bounded)
# now player has all the methods provided by this module
player.position()
# => {x: 10, y: 50}
@see GameObject
Bounded module
@name Bounded
@module
@constructor
@param {Object} I Instance variables
@param {Core} self Reference to including object
*/
this.Bounded = function(I, self) {
if (I == null) {
I = {};
}
Object.reverseMerge(I, {
x: 0,
y: 0,
width: 8,
height: 8,
collisionMargin: Point(0, 0)
});
return {
/**
Get the object closest to this one.
@name closest
@methodOf Bounded#
@param {Object|Array|String} selector An object or set of objects to find the closest from.
*/
closest: function(selector) {
var position;
if (Object.isString(selector)) {
selector = engine.find(selector);
} else {
selector = [].concat(selector);
}
position = self.position();
return selector.sort(function(a, b) {
return Point.distanceSquared(position, a.position()) - Point.distanceSquared(position, b.position());
}).first();
},
/**
Distance between two objects. Proxies to Point.distance.
In order for this to work, `otherObj` must have a
position method.
player = GameObject
x: 50
y: 50
width: 10
height: 10
player.include Bounded
enemy = GameObject
x: 110
y: 120
width: 7
height: 20
player.distance(enemy)
# => 92.19544457292888
@name distance
@methodOf Bounded#
@see Point.distance
@returns {Number} Distance between the two objects
*/
distance: function(otherObj) {
return Point.distance(self.position(), otherObj.position());
},
/**
The position of this game object. By default it is the top left point.
Redefining the center method will change the relative position.
player = Core
x: 50
y: 40
player.include(Bounded)
player.position()
# => {x: 50, y: 40}
@name position
@methodOf Bounded#
@returns {Point} The position of this object
*/
position: function(newPosition) {
if (newPosition != null) {
I.x = newPosition.x;
return I.y = newPosition.y;
} else {
return Point(I.x, I.y);
}
},
changePosition: function(delta) {
I.x += delta.x;
I.y += delta.y;
return self;
},
/**
Does a check to see if this object is overlapping
with the bounds passed in.
player = Core
x: 4
y: 6
width: 20
height: 20
player.include(Bounded)
player.collides({x: 5, y: 7, width: 20, height: 20})
# => true
@name collides
@methodOf Bounded#
@returns {Point} The position of this object
*/
collides: function(bounds) {
return Collision.rectangular(self.bounds(), bounds);
},
/**
This returns a modified bounds based on the collision margin.
The area of the bounds is reduced if collision margin is positive
and increased if collision margin is negative.
player = Core
collisionMargin:
x: -2
y: -4
x: 50
y: 50
width: 20
height: 20
player.include(Bounded)
player.collisionBounds()
# => {x: 38, y: 36, height: 28, width: 24}
player.collisionBounds(10, 10)
# => {x: 48, y: 46, height: 28, width: 24}
@name collisionBounds
@methodOf Bounded#
@param {Number} xOffset the amount to shift the x position
@param {Number} yOffset the amount to shift the y position
@returns {Object} The collision bounds
*/
collisionBounds: function(xOffset, yOffset) {
var bounds;
bounds = self.bounds(xOffset, yOffset);
bounds.x += I.collisionMargin.x;
bounds.y += I.collisionMargin.y;
bounds.width -= 2 * I.collisionMargin.x;
bounds.height -= 2 * I.collisionMargin.y;
return bounds;
},
/**
Returns infomation about the location of the object and its dimensions with optional offsets.
player = Core
x: 3
y: 6
width: 2
height: 2
player.include(Bounded)
player.bounds()
# => {x: 3, y: 6, width: 2, height: 2}
player.bounds(7, 4)
# => {x: 10, y: 10, width: 2, height: 2}
@name bounds
@methodOf Bounded#
@param {Number} xOffset the amount to shift the x position
@param {Number} yOffset the amount to shift the y position
*/
bounds: function(xOffset, yOffset) {
var center;
center = self.center();
return {
x: center.x - I.width / 2 + (xOffset || 0),
y: center.y - I.height / 2 + (yOffset || 0),
width: I.width,
height: I.height
};
},
/**
The centeredBounds method returns infomation about the center
of the object along with the midpoint of the width and height.
player = Core
x: 3
y: 6
width: 2
height: 2
player.include(Bounded)
player.centeredBounds()
# => {x: 4, y: 7, xw: 1, yw: 1}
@name centeredBounds
@methodOf Bounded#
*/
centeredBounds: function() {
var center;
center = self.center();
return {
x: center.x,
y: center.y,
xw: I.width / 2,
yw: I.height / 2
};
},
/**
The center method returns the {@link Point} that is
the center of the object.
player = Core
x: 50
y: 40
width: 10
height: 30
player.include(Bounded)
player.center()
# => {x: 30, y: 35}
@name center
@methodOf Bounded#
@returns {Point} The middle of the calling object
*/
center: function(newCenter) {
return self.position(newCenter);
},
/**
Return the circular bounds of the object. The circle is
centered at the midpoint of the object.
player = Core
radius: 5
x: 50
y: 50
other: "stuff"
player.include(Bounded)
player.circle()
# => {radius: 5, x: 50, y: 50}
@name circle
@methodOf Bounded#
@returns {Object} An object with a position and a radius
*/
circle: function() {
var circle;
circle = self.center();
circle.radius = I.radius || I.width / 2 || I.height / 2;
return circle;
}
};
};
this.Camera = function(I) {
var currentObject, currentType, focusOn, followTypes, moduleName, objectFilters, self, transformFilters, _i, _len, _ref;
if (I == null) {
I = {};
}
Object.reverseMerge(I, {
cameraBounds: Rectangle({
x: 0,
y: 0,
width: App.width,
height: App.height
}),
screen: Rectangle({
x: 0,
y: 0,
width: App.width,
height: App.height
}),
deadzone: Point(0, 0),
zoom: 1,
transform: Matrix(),
x: App.width / 2,
y: App.height / 2,
velocity: Point.ZERO,
maxSpeed: 750,
t90: 2
});
currentType = "centered";
currentObject = null;
objectFilters = [];
transformFilters = [];
focusOn = function(object, elapsedTime) {
var c, dampingFactor, delta, force, objectCenter, target;
dampingFactor = 2;
c = elapsedTime * 3.75 / I.t90;
if (c >= 1) {
self.position(target);
return I.velocity = Point.ZERO;
} else {
objectCenter = object.center();
target = objectCenter;
delta = target.subtract(self.position());
force = delta.subtract(I.velocity.scale(dampingFactor));
self.changePosition(I.velocity.scale(c).clamp(I.maxSpeed));
return I.velocity = I.velocity.add(force.scale(c));
}
};
followTypes = {
centered: function(object, elapsedTime) {
I.deadzone = Point(0, 0);
return focusOn(object, elapsedTime);
},
topdown: function(object, elapsedTime) {
var helper;
helper = Math.max(I.screen.width, I.screen.height) / 4;
I.deadzone = Point(helper, helper);
return focusOn(object, elapsedTime);
},
platformer: function(object, elapsedTime) {
var height, width;
width = I.screen.width / 8;
height = I.screen.height / 3;
I.deadzone = Point(width, height);
return focusOn(object, elapsedTime);
}
};
self = Core(I).extend({
follow: function(object, type) {
if (type == null) {
type = "centered";
}
currentObject = object;
return currentType = type;
},
objectFilterChain: function(fn) {
return objectFilters.push(fn);
},
transformFilterChain: function(fn) {
return transformFilters.push(fn);
},
screenToWorld: function(point) {
return self.transform().inverse().transformPoint(point);
}
});
self.attrAccessor("transform");
self.bind("afterUpdate", function(elapsedTime) {
if (currentObject) {
followTypes[currentType](currentObject, elapsedTime);
}
I.x = I.x.clamp(I.cameraBounds.left + I.screen.width / 2, I.cameraBounds.right - I.screen.width / 2);
I.y = I.y.clamp(I.cameraBounds.top + I.screen.height / 2, I.cameraBounds.bottom - I.screen.height / 2);
return I.transform = Matrix.translate(I.screen.width / 2 - I.x.floor(), I.screen.height / 2 - I.y.floor());
});
self.bind("draw", function(canvas, objects) {
return canvas.withTransform(Matrix.translate(I.screen.x, I.screen.y), function(canvas) {
var transform;
canvas.clip(0, 0, I.screen.width, I.screen.height);
objects = objectFilters.pipeline(objects);
transform = transformFilters.pipeline(self.transform().copy());
canvas.withTransform(transform, function(canvas) {
self.trigger("beforeDraw", canvas);
return objects.invoke("draw", canvas);
});
return self.trigger('flash', canvas);
});
});
self.bind("overlay", function(canvas, objects) {
return canvas.withTransform(Matrix.translate(I.screen.x, I.screen.y), function(canvas) {
canvas.clip(0, 0, I.screen.width, I.screen.height);
objects = objectFilters.pipeline(objects);
return objects.invoke("trigger", "overlay", canvas);
});
});
self.include("Ageable", "Bounded");
_ref = Camera.defaultModules;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
moduleName = _ref[_i];
self.include("Camera." + moduleName);
}
return self;
};
Camera.defaultModules = ["ZSort", "Zoom", "Rotate", "Shake", "Flash", "Fade", "Transition"];
/**
The <code>Fade</code> module provides convenience methods for accessing common Engine.Flash presets.
@name Fade
@fieldOf Camera
@module
@param {Object} I Instance variables
@param {Object} self Reference to the engine
@see Camera.Flash
*/
Camera.Fade = function(I, self) {
var configureFade, fadeInDefaults, fadeOutDefaults;
fadeInDefaults = {
alpha: 0,
color: 'black',
duration: 30
};
fadeOutDefaults = {
alpha: 1,
color: 'transparent',
duration: 30
};
configureFade = function(duration, color, alpha) {
I.flashDuration = duration;
I.flashCooldown = duration;
I.flashColor = Color(color);
return I.flashTargetAlpha = alpha;
};
return {
/**
A convenient way to set the flash effect instance variables. This provides a shorthand for fading the screen in
from a given color over a specified duration.
engine.fadeIn()
# => Sets the effect variables to their default state. This will the screen to go from black to transparent over the next 30 frames.
engine.fadeIn('blue', 50)
# => This effect will start off blue and fade to transparent over 50 frames.
@name fadeIn
@methodOf Camera#
@param {Number} [duration=30] How long the effect lasts
@param {Color} [color="black"] The color to fade from
*/
fadeIn: function(options) {
var alpha, color, duration, _ref;
if (options == null) {
options = {};
}
_ref = Object.reverseMerge(options, fadeInDefaults), alpha = _ref.alpha, color = _ref.color, duration = _ref.duration;
return configureFade(duration, color, alpha);
},
/**
A convenient way to set the flash effect instance variables. This provides a shorthand for fading
the screen to a given color over a specified duration.
camera.fadeOut()
# => Sets the effect variables to their default state. This will the screen to fade from ransparent to black over the next 30 frames.
camera.fadeOut
color: blue
duration: 30
# => This effect will start off transparent and change to blue over 50 frames.
@name fadeOut
@methodOf Camera#
@param {Number} [duration=30] How long the effect lasts
@param {Color} [color="transparent"] The color to fade to
*/
fadeOut: function(options) {
var alpha, color, duration, _ref;
if (options == null) {
options = {};
}
_ref = Object.reverseMerge(options, fadeOutDefaults), alpha = _ref.alpha, color = _ref.color, duration = _ref.duration;
return configureFade(duration, color, alpha);
}
};
};
/**
The <code>Flash</code> module allows you to flash a color onscreen and then fade to transparent over a time period.
This is nice for lightning type effects or to accentuate major game events.
@name Flash
@fieldOf Camera
@module
@param {Object} I Instance variables
@param {Object} self Reference to the camera
*/
Camera.Flash = function(I, self) {
var defaultParams;
Object.reverseMerge(I, {
flashColor: Color(0, 0, 0, 0),
flashDuration: 12,
flashCooldown: 0,
flashTargetAlpha: 0
});
defaultParams = {
color: 'white',
duration: 12,
targetAlpha: 0
};
self.bind('afterUpdate', function() {
if (I.flashCooldown > 0) {
I.flashColor.a = I.flashColor.a.approach(I.flashTargetAlpha, 1 / I.flashDuration).clamp(0, 1);
if (I.flashColor.a < 0.00001) {
I.flashColor.a = 0;
}
if (I.flashColor.a > 0.9999) {
I.flashColor.a = 1;
}
return I.flashCooldown = I.flashCooldown.approach(0, 1);
}
});
self.bind('flash', function(canvas) {
return canvas.fill(I.flashColor);
});
return {
/**
A convenient way to set the flash effect instance variables. Alternatively, you can modify them by hand, but
using Camera#flash is the suggested approach.
camera.flash()
# => Sets the flash effect variables to their default state. This will cause a white flash that will turn transparent in the next 12 frames.
camera.flash
color: 'green'
duration: 30
# => This flash effect will start off green and fade to transparent over 30 frames.
camera.flash
color: Color(255, 0, 0, 0)
duration: 20
targetAlpha: 1
# => This flash effect will start off transparent and move toward red over 20 frames
@name flash
@methodOf Camera#
@param {Color} [color="white"] The flash color
@param {Number} [duration=12] How long the effect lasts
@param {Number} [targetAlpha=0] The alpha value to fade to. By default, this is set to 0, which fades the color to transparent.
*/
flash: function(options) {
var color, duration, targetAlpha;
if (options == null) {
options = {};
}
Object.reverseMerge(options, defaultParams);
color = options.color, duration = options.duration, targetAlpha = options.targetAlpha;
I.flashColor = Color(color);
I.flashTargetAlpha = targetAlpha;
I.flashCooldown = duration;
I.flashDuration = duration;
return self;
}
};
};
Camera.Rotate = function(I, self) {
Object.reverseMerge(I, {
rotation: 0
});
self.transformFilterChain(function(transform) {
return transform.rotate(I.rotation, self.position());
});
self.attrAccessor("rotation");
return {
rotate: function(amount) {
return self.rotation(I.rotation + amount);
}
};
};
Camera.Shake = function(I, self) {
var defaultParams;
Object.reverseMerge(I, {
shakeIntensity: 20,
shakeCooldown: 0
});
defaultParams = {
duration: 10,
intensity: 20
};
self.bind("afterUpdate", function() {
return I.shakeCooldown = I.shakeCooldown.approach(0, 1);
});
self.transformFilterChain(function(transform) {
if (I.shakeCooldown > 0) {
transform.tx += signedRand(I.shakeIntensity);
transform.ty += signedRand(I.shakeIntensity);
}
return transform;
});
return {
shake: function(options) {
var duration, intensity, _ref;
if (options == null) {
options = {};
}
_ref = Object.reverseMerge(options, defaultParams), duration = _ref.duration, intensity = _ref.intensity;
I.shakeCooldown = duration * I.zoom;
I.shakeIntensity = intensity * I.zoom;
return self;
}
};
};
CameraTarget = function(I) {
var self;
if (I == null) {
I = {};
}
Object.reverseMerge(I, {
target: "Player.controller=0",
lead: 1.25
});
self = GameObject(I).extend({
draw: function() {}
});
self.on("update", function(canvas) {
var position, target, targetPosition, targetVelocity;
if (target = engine.first(I.target)) {
targetPosition = target.position();
targetVelocity = target.velocity();
position = targetPosition.add(targetVelocity.scale(I.lead));
I.x = position.x;
return I.y = position.y;
}
});
return self;
};
Camera.Transition = function(I, self) {
var defaultOptions, transitionProgress, transitions;
Object.reverseMerge(I, {
transitionActive: null,
transitionStart: null,
transitionEnd: null
});
defaultOptions = {
color: "white"
};
transitionProgress = function() {
return ((I.age - I.transitionStart) / (I.transitionEnd - I.transitionStart)).clamp(0, 1);
};
transitions = {
angle: function(_arg) {
var canvas, color, p0, p1, p2, p3, p4, screenSize, t;
canvas = _arg.canvas, t = _arg.t, screenSize = _arg.screenSize, color = _arg.color;
p0 = Point(t * (screenSize.x * 2), screenSize.y / 2);
p1 = p0.subtract(Point(screenSize.x, screenSize.y / 2));
p2 = p1.subtract(Point(screenSize.x, 0));
p3 = p2.add(Point(0, screenSize.y));
p4 = p3.add(Point(screenSize.x, 0));
return canvas.drawPoly({
points: [p0, p1, p2, p3, p4],
color: color
});
},
square: function(_arg) {
var canvas, color, height, screenSize, t, width;
canvas = _arg.canvas, t = _arg.t, screenSize = _arg.screenSize, color = _arg.color;
width = 50;
height = 50;
return (screenSize.y / height).ceil().times(function(y) {
return (screenSize.x / width).ceil().times(function(x) {
var cellProgress;
cellProgress = (2 * t - (x + y).mod(2)).clamp(0, 1);
return canvas.drawRect({
x: x * width,
y: y * height,
width: width,
height: height * cellProgress,
color: color
});
});
});
},
line: function(_arg) {
var canvas, color, height, screenSize, t;
canvas = _arg.canvas, t = _arg.t, screenSize = _arg.screenSize, color = _arg.color;
height = 50;
return (screenSize.y / height).ceil().times(function(y) {
return canvas.drawRect({
x: 0,
y: y * height,
width: screenSize.x,
height: height * t,
color: color
});
});
}
};
self.on("overlay", function(canvas) {
var transitionName;
if (transitionName = I.transitionActive) {
return transitions[transitionName](Object.extend({
canvas: canvas,
screenSize: Point(I.screen.width, I.screen.height),
t: transitionProgress()
}, I.transitionOptions));
}
});
return {
transition: function(_arg) {
var duration, name, options, _ref;
_ref = _arg != null ? _arg : {}, name = _ref.name, duration = _ref.duration, options = _ref.options;
if (name == null) {
name = "angle";
}
if (duration == null) {
duration = 1;
}
I.transitionActive = name;
I.transitionStart = I.age;
I.transitionEnd = I.age + duration;
return I.transitionOptions = Object.extend({}, defaultOptions, options);
}
};
};
Camera.Zoom = function(I, self) {
var clampZoom;
Object.reverseMerge(I, {
maxZoom: 10,
minZoom: 0.1,
zoom: 1
});
self.transformFilterChain(function(transform) {
return transform.scale(I.zoom, I.zoom, self.position());
});
clampZoom = function(value) {
return value.clamp(I.minZoom, I.maxZoom);
};
return {
zoomIn: function(percentage) {
return self.zoom(clampZoom(I.zoom * (1 + percentage)));
},
zoomOut: function(percentage) {
return self.zoom(clampZoom(I.zoom * (1 - percentage)));
},
zoom: function(value) {
if (value != null) {
I.zoom = clampZoom(value);
return self;
} else {
return I.zoom;
}
}
};
};
Camera.ZSort = function(I, self) {
Object.reverseMerge(I, {
zSort: true
});
self.objectFilterChain(function(objects) {
if (I.zSort) {
objects.sort(function(a, b) {
return a.I.zIndex - b.I.zIndex;
});
}
return objects;
});
return {};
};
/**
The `Clampable` module provides helper methods to clamp object properties. This module is included by default in `GameObject`
player = GameObject
x: 40
y: 30
player.include Clampable
@name Clampable
@module
@constructor
@param {Object} I Instance variables
@param {Core} self Reference to including object
*/
this.Clampable = function(I, self) {
if (I == null) {
I = {};
}
Object.reverseMerge(I, {
clampData: {}
});
self.bind("afterUpdate", function() {
var data, property, _ref, _results;
_ref = I.clampData;
_results = [];
for (property in _ref) {
data = _ref[property];
_results.push(I[property] = I[property].clamp(data.min, data.max));
}
return _results;
});
return {
/**
Keep an objects attributes within a given range.
# Player's health will be within [0, 100] at the end of every update
player.clamp
health:
min: 0
max: 100
# Score can only be positive
player.clamp
score:
min: 0
@name clamp
@methodOf Clampable#
@param {Object} data
*/
clamp: function(data) {
return Object.extend(I.clampData, data);
},
/**
Helper to clamp the `x` and `y` properties of the object to be within a given bounds.
@name clampToBounds
@methodOf Clampable#
@param {Rectangle} [bounds] The bounds to clamp the object's position within. Defaults to the app size if none given.
*/
clampToBounds: function(bounds) {
bounds || (bounds = Rectangle({
x: 0,
y: 0,
width: App.width,
height: App.height
}));
return self.clamp({
x: {
min: bounds.x + I.width / 2,
max: bounds.width - I.width / 2
},
y: {
min: bounds.y + I.height / 2,
max: bounds.height - I.height / 2
}
});
}
};
};
(function() {
var ANY, CEILING, Collidable, DOWN, FLOOR, LEFT, NONE, RIGHT, UP, WALL, _ref, _ref1;
Collidable = function(I, self) {
Object.reverseMerge(I, {
allowCollisions: ANY,
immovable: false,
touching: NONE,
velocity: Point(0, 0),
mass: 1,
elasticity: 0
});
self.attrAccessor("immovable", "velocity", "mass", "elasticity");
return {
solid: function(newSolid) {
if (newSolid != null) {
if (newSolid) {
return I.allowCollisions = ANY;
} else {
return I.allowCollisions = NONE;
}
} else {
return I.allowCollisions;
}
}
};
};
(typeof exports !== "undefined" && exports !== null ? exports : this)["Collidable"] = Collidable;
_ref = Object.extend(Collidable, {
NONE: 0x0000,
LEFT: 0x0001,
RIGHT: 0x0010,
UP: 0x0100,
DOWN: 0x1000
}), NONE = _ref.NONE, LEFT = _ref.LEFT, RIGHT = _ref.RIGHT, UP = _ref.UP, DOWN = _ref.DOWN;
_ref1 = Object.extend(Collidable, {
FLOOR: DOWN,
WALL: LEFT | RIGHT,
CEILING: UP,
ANY: LEFT | RIGHT | UP | DOWN
}), ANY = _ref1.ANY, FLOOR = _ref1.FLOOR, WALL = _ref1.WALL, CEILING = _ref1.CEILING;
return Object.extend(Collidable, {
separate: function(a, b) {
var aBounds, aMass, aVelocity, average, bBounds, bMass, bVelocity, deltaVelocity, normal, overlap, pushA, pushB, relativeVelocity, totalMass;
if (a.immovable() && b.immovable()) {
return;
}
aBounds = a.bounds();
bBounds = b.bounds();
aVelocity = a.velocity();
bVelocity = b.velocity();
deltaVelocity = aVelocity.subtract(bVelocity);
overlap = Point(0, 0);
if (Collision.rectangular(aBounds, bBounds)) {
if (deltaVelocity.x > 0) {
overlap.x = aBounds.x + aBounds.width - bBounds.x;
if (!(a.I.allowCollisions & RIGHT) || !(b.I.allowCollisions & LEFT)) {
overlap.x = 0;
} else {
a.I.touching |= RIGHT;
b.I.touching |= LEFT;
}
} else if (deltaVelocity.x < 0) {
overlap.x = aBounds.x - bBounds.width - bBounds.x;
if (!(a.I.allowCollisions & LEFT) || !(b.I.allowCollisions & RIGHT)) {
overlap.x = 0;
} else {
a.I.touching |= LEFT;
b.I.touching |= RIGHT;
}
}
if (deltaVelocity.y > 0) {
overlap.y = aBounds.y + aBounds.height - bBounds.y;
if (!(a.I.allowCollisions & DOWN) || !(b.I.allowCollisions & UP)) {
overlap.y = 0;
} else {
a.I.touching |= DOWN;
b.I.touching |= UP;
}
} else if (deltaVelocity.y < 0) {
overlap.y = aBounds.y - bBounds.height - bBounds.y;
if (!(a.I.allowCollisions & UP) || !(b.I.allowCollisions & DOWN)) {
overlap.y = 0;
} else {
a.I.touching |= UP;
b.I.touching |= DOWN;
}
}
}
if (!overlap.equal(Point.ZERO)) {
if (!a.immovable() && !b.immovable()) {
a.changePosition(overlap.scale(-0.5));
b.changePosition(overlap.scale(+0.5));
relativeVelocity = aVelocity.subtract(bVelocity);
aMass = a.mass();
bMass = b.mass();
totalMass = bMass + aMass;
normal = overlap.norm();
pushA = normal.scale(-2 * (relativeVelocity.dot(normal) * (bMass / totalMass)));
pushB = normal.scale(+2 * (relativeVelocity.dot(normal) * (aMass / totalMass)));
average = pushA.add(pushB).scale(0.5);
pushA.subtract$(average).scale(a.elasticity());
pushB.subtract$(average).scale(b.elasticity());
a.I.velocity = average.add(pushA);
b.I.velocity = average.add(pushB);
} else if (!a.immovable()) {
a.changePosition(overlap.scale(-1));
a.I.velocity = bVelocity.subtract(aVelocity.scale(a.elasticity()));
} else if (!b.immovable()) {
b.changePosition(overlap);
b.I.velocity = aVelocity.subtract(bVelocity.scale(b.elasticity()));
}
return true;
}
}
});
})();
(function() {
var Collision, collides;
collides = function(a, b) {
return Collision.rectangular(a.bounds(), b.bounds());
};
/**
Collision holds many useful class methods for checking geometric overlap of various objects.
@name Collision
@namespace
*/
Collision = {
/**
Collision holds many useful class methods for checking geometric overlap of various objects.
player = engine.add
class: "Player"
x: 0
y: 0
width: 10
height: 10
enemy = engine.add
class: "Enemy"
x: 5
y: 5
width: 10
height: 10
enemy2 = engine.add
class: "Enemy"
x: -5
y: -5
width: 10
height: 10
Collision.collide(player, enemy, (p, e) -> ...)
# => callback is called once
Collision.collide(player, [enemy, enemy2], (p, e) -> ...)
# => callback is called twice
Collision.collide("Player", "Enemy", (p, e) -> ...)
# => callback is also called twice
@name collide
@methodOf Collision
@param {Object|Array|String} groupA An object or set of objects to check collisions with
@param {Object|Array|String} groupB An object or set of objects to check collisions with
@param {Function} callback The callback to call when an object of groupA collides
with an object of groupB: (a, b) ->
@param {Function} [detectionMethod] An optional detection method to determine when two
objects are colliding.
*/
collide: function(groupA, groupB, callback, detectionMethod) {
if (detectionMethod == null) {
detectionMethod = collides;
}
if (Object.isString(groupA)) {
groupA = engine.find(groupA);
} else {
groupA = [].concat(groupA);
}
if (Object.isString(groupB)) {
groupB = engine.find(groupB);
} else {
groupB = [].concat(groupB);
}
return groupA.each(function(a) {
return groupB.each(function(b) {
if (detectionMethod(a, b)) {
return callback(a, b);
}
});
});
},
/**
Takes two bounds objects and returns true if they collide (overlap), false otherwise.
Bounds objects have x, y, width and height properties.
player = GameObject
x: 0
y: 0
width: 10
height: 10
enemy = GameObject
x: 5
y: 5
width: 10
height: 10
Collision.rectangular(player, enemy)
# => true
Collision.rectangular(player, {x: 50, y: 40, width: 30, height: 30})
# => false
@name rectangular
@methodOf Collision
@param {Object} a The first rectangle
@param {Object} b The second rectangle
@returns {Boolean} true if the rectangles overlap, false otherwise
*/
rectangular: function(a, b) {
return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;
},
/**
Takes two circle objects and returns true if they collide (overlap), false otherwise.
Circle objects have x, y, and radius.
player = GameObject
x: 5
y: 5
radius: 10
enemy = GameObject
x: 10
y: 10
radius: 10
farEnemy = GameObject
x: 500
y: 500
radius: 30
Collision.circular(player, enemy)
# => true
Collision.circular(player, farEnemy)
# => false
@name circular
@methodOf Collision
@param {Object} a The first circle
@param {Object} b The second circle
@returns {Boolean} true is the circles overlap, false otherwise
*/
circular: function(a, b) {
var dx, dy, r;
r = a.radius + b.radius;
dx = b.x - a.x;
dy = b.y - a.y;
return r * r >= dx * dx + dy * dy;
},
/**
Detects whether a line intersects a circle.
circle = engine.add
class: "circle"
x: 50
y: 50
radius: 10
Collision.rayCircle(Point(0, 0), Point(1, 0), circle)
# => true
@name rayCircle
@methodOf Collision
@param {Point} source The starting position
@param {Point} direction A vector from the point
@param {Object} target The circle
@returns {Boolean} true if the line intersects the circle, false otherwise
*/
rayCircle: function(source, direction, target) {
var dt, hit, intersection, intersectionToTarget, intersectionToTargetLength, laserToTarget, projection, projectionLength, radius;
radius = target.radius();
target = target.position();
laserToTarget = target.subtract(source);
projectionLength = direction.dot(laserToTarget);
if (projectionLength < 0) {
return false;
}
projection = direction.scale(projectionLength);
intersection = source.add(projection);
intersectionToTarget = target.subtract(intersection);
intersectionToTargetLength = intersectionToTarget.length();
if (intersectionToTargetLength < radius) {
hit = true;
}
if (hit) {
dt = Math.sqrt(radius * radius - intersectionToTargetLength * intersectionToTargetLength);
return hit = direction.scale(projectionLength - dt).add(source);
}
},
/**
Detects whether a line intersects a rectangle.
rect = engine.add
class: "circle"
x: 50
y: 50
width: 20
height: 20
Collision.rayRectangle(Point(0, 0), Point(1, 0), rect)
# => true
@name rayRectangle
@methodOf Collision
@param {Point} source The starting position
@param {Point} direction A vector from the point
@param {Object} target The rectangle
@returns {Boolean} true if the line intersects the rectangle, false otherwise
*/
rayRectangle: function(source, direction, target) {
var areaPQ0, areaPQ1, hit, p0, p1, t, tX, tY, xval, xw, yval, yw, _ref, _ref1;
if (!((target.xw != null) && (target.yw != null))) {
if ((target.width != null) && (target.height != null)) {
xw = target.width / 2;
yw = target.height / 2;
return Collision.rayRectangle(source, direction, {
x: target.x + xw,
y: target.y + yw,
xw: xw,
yw: yw
});
} else {
error("Bounds object isn't a rectangle");
return;
}
}
xw = target.xw;
yw = target.yw;
if (source.x < target.x) {
xval = target.x - xw;
} else {
xval = target.x + xw;
}
if (source.y < target.y) {
yval = target.y - yw;
} else {
yval = target.y + yw;
}
if (direction.x === 0) {
p0 = Point(target.x - xw, yval);
p1 = Point(target.x + xw, yval);
t = (yval - source.y) / direction.y;
} else if (direction.y === 0) {
p0 = Point(xval, target.y - yw);
p1 = Point(xval, target.y + yw);
t = (xval - source.x) / direction.x;
} else {
tX = (xval - source.x) / direction.x;
tY = (yval - source.y) / direction.y;
if ((tX < tY || ((-xw < (_ref = source.x - target.x) && _ref < xw))) && !((-yw < (_ref1 = source.y - target.y) && _ref1 < yw))) {
p0 = Point(target.x - xw, yval);
p1 = Point(target.x + xw, yval);
t = tY;
} else {
p0 = Point(xval, target.y - yw);
p1 = Point(xval, target.y + yw);
t = tX;
}
}
if (t > 0) {
areaPQ0 = direction.cross(p0.subtract(source));
areaPQ1 = direction.cross(p1.subtract(source))