Skip to content

Instantly share code, notes, and snippets.

@STRd6
Last active December 21, 2015 15:28
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save STRd6/6326545 to your computer and use it in GitHub Desktop.
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));
if (areaPQ0 * areaPQ1 < 0) {
return hit = direction.scale(t).add(source);
}
}
}
};
return (typeof exports !== "undefined" && exports !== null ? exports : this)["Collision"] = Collision;
})();
this.CollisionResponse = function(I, self) {
if (I == null) {
I = {};
}
self.unbind(".Movable");
self.bind('update.Movable', function(elapsedTime) {
var t, unit;
t = (elapsedTime * I.velocity.x).abs();
unit = I.velocity.x.sign();
t.times(function() {
if (self.collide(unit, 0, ".solid")) {
return I.velocity.x = 0;
} else {
return I.x += unit;
}
});
t = (elapsedTime * I.velocity.y).abs();
unit = I.velocity.y.sign();
return t.times(function() {
if (self.collide(0, unit, ".solid")) {
return I.velocity.y = 0;
} else {
return I.y += unit;
}
});
});
return self.extend({
collide: function(xOffset, yOffset, className) {
return engine.find(className).inject(false, function(hit, block) {
return hit || Collision.rectangular(self.bounds(xOffset, yOffset), block.bounds());
});
}
});
};
(function() {
var Color, channelize, hslParser, hslToRgb, hsvToRgb, parseHSL, parseRGB, rgbParser;
rgbParser = /^rgba?\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3}),?\s*(\d?\.?\d*)?\)$/;
hslParser = /^hsla?\((\d{1,3}),\s*(\d?\.?\d*),\s*(\d?\.?\d*),?\s*(\d?\.?\d*)?\)$/;
parseRGB = function(colorString) {
var channel, channels, parsedColor;
if (!(channels = rgbParser.exec(colorString))) {
return void 0;
}
parsedColor = (function() {
var _i, _len, _ref, _results;
_ref = channels.slice(1, 5);
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
channel = _ref[_i];
_results.push(parseFloat(channel));
}
return _results;
})();
if (isNaN(parsedColor[3])) {
parsedColor[3] = 1;
}
return parsedColor;
};
parseHSL = function(colorString) {
var channel, channels, parsedColor;
if (!(channels = hslParser.exec(colorString))) {
return void 0;
}
parsedColor = (function() {
var _i, _len, _ref, _results;
_ref = channels.slice(1, 5);
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
channel = _ref[_i];
_results.push(parseFloat(channel));
}
return _results;
})();
if (isNaN(parsedColor[3])) {
parsedColor[3] = 1;
}
return hslToRgb(parsedColor);
};
hsvToRgb = function(hsv) {
var a, b, f, g, h, i, p, q, r, rgb, s, t, v;
r = g = b = null;
h = hsv[0], s = hsv[1], v = hsv[2], a = hsv[3];
if (a == null) {
a = 1;
}
i = (h / 60).floor();
f = h / 60 - i;
p = v * (1 - s);
q = v * (1 - f * s);
t = v * (1 - (1 - f) * s);
switch (i % 6) {
case 0:
r = v;
g = t;
b = p;
break;
case 1:
r = q;
g = v;
b = p;
break;
case 2:
r = p;
g = v;
b = t;
break;
case 3:
r = p;
g = q;
b = v;
break;
case 4:
r = t;
g = p;
b = v;
break;
case 5:
r = v;
g = p;
b = q;
}
rgb = [(r * 255).round(), (g * 255).round(), (b * 255).round()];
return rgb.concat(a);
};
hslToRgb = function(hsl) {
var a, b, channel, g, h, hueToRgb, l, p, q, r, rgbMap, s;
h = hsl[0], s = hsl[1], l = hsl[2], a = hsl[3];
h = h % 360;
if (a == null) {
a = 1;
}
r = g = b = null;
hueToRgb = function(p, q, hue) {
hue = hue.mod(360);
if (hue < 60) {
return p + (q - p) * (hue / 60);
}
if (hue < 180) {
return q;
}
if (hue < 240) {
return p + (q - p) * ((240 - hue) / 60);
}
return p;
};
if (s === 0) {
r = g = b = l;
} else {
q = (l < 0.5 ? l * (1 + s) : l + s - l * s);
p = 2 * l - q;
r = hueToRgb(p, q, h + 120);
g = hueToRgb(p, q, h);
b = hueToRgb(p, q, h - 120);
}
rgbMap = (function() {
var _i, _len, _ref, _results;
_ref = [r, g, b];
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
channel = _ref[_i];
_results.push((channel * 255).round());
}
return _results;
})();
return rgbMap.concat(a);
};
channelize = function(color, alpha) {
var channel, result;
if (color.channels != null) {
return color.channels();
}
if (Object.isArray(color)) {
if (alpha != null) {
alpha = parseFloat(alpha);
} else if (color[3] != null) {
alpha = parseFloat(color[3]);
} else {
alpha = 1;
}
result = ((function() {
var _i, _len, _ref, _results;
_ref = color.slice(0, 3);
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
channel = _ref[_i];
_results.push(parseFloat(channel));
}
return _results;
})()).concat(alpha);
} else {
result = (typeof Color.lookup === "function" ? Color.lookup(color) : void 0) || color.parseHex() || parseRGB(color) || parseHSL(color);
if (alpha != null) {
result[3] = parseFloat(alpha);
}
}
return result;
};
/**
Create a new color. The constructor is very flexible. It accepts individual r, g, b, a values,
arrays of r, g, b values, hex strings, rgb strings, hsl strings, other Color objects,
and even the named colors from the xkcd survey: http://blog.xkcd.com/2010/05/03/color-survey-results/.
If no arguments are given, defaults to transparent.
individualRgb = Color(23, 56, 49, 0.4)
arrayRgb = Color([59, 100, 230])
hex = Color('#ff0000')
rgb = Color('rgb(0, 255, 0)')
hsl = Color('hsl(180, 1, 0.5)')
anotherColor = Color('blue')
Color(anotherColor)
# => a new color with the same r, g, b, and alpha values as `anotherColor`
# You have access to all sorts of weird colors.
# We give you all the named colors the browser recognizes
# and the ones from this survey
# http://blog.xkcd.com/2010/05/03/color-survey-results/
namedBrown = Color('Fuzzy Wuzzy Brown')
# Uutput color in Hex format
namedBrown.toHex()
# => '#c45655'
# Default behavior
transparent = Color()
transparent.toString()
# => 'rgba(0, 0, 0, 0)'
# let's print out the colors on a canvas to see what they look like
canvas.font('14px Helvetica')
for color, index in ['individualRgb', 'arrayRgb', 'hex', 'rgb', 'hsl', 'anotherColor', 'namedBrown']
canvas.centerText
color: eval(color)
text: color
y: 20 * (index + 1)
@name Color
@param {Array|Number|String|Color} args... An Array, r, g, b values,
a sequence of numbers defining r, g, b values, a hex or hsl string, another Color object, or a named color
@constructor
*/
Color = function() {
var args, parsedColor;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
parsedColor = (function() {
switch (args.length) {
case 0:
return [0, 0, 0, 0];
case 1:
return channelize(args.first());
case 2:
return channelize(args.first(), args.last());
default:
return channelize(args);
}
})();
if (!parsedColor) {
throw "" + (args.join(',')) + " is an unknown color";
}
return {
__proto__: Color.prototype,
r: parsedColor[0].round(),
g: parsedColor[1].round(),
b: parsedColor[2].round(),
a: parsedColor[3]
};
};
Color.prototype = {
/**
Returns the rgba color channels in an array.
transparent = Color()
transparent.channels()
# => [0, 0, 0, 0]
red = Color("#FF0000")
red.channels()
# => [255, 0, 0, 1]
rgb = Color(200, 34, 2)
rgb.channels()
# => [200, 34, 2, 1]
@name channels
@methodOf Color#
@returns {Array} Array of r, g, b, and alpha values of the color
*/
channels: function() {
return [this.r, this.g, this.b, this.a];
},
/**
A copy of the calling color that is its complementary color on the color wheel.
red = Color(255, 0, 0)
cyan = red.complement()
# to see what they look like
for color, index in [red, cyan]
canvas.drawRect
color: color
x: 20 + (60 * index)
y: 20 + (60 * index)
width: 60
height: 60
@name complement
@methodOf Color#
@returns {Color} new color that is a copy of the calling color with its hue shifted by 180 degrees on the color wheel
*/
complement: function() {
return this.copy().complement$();
},
/**
Modifies the calling color to make it the complement of its previous value.
red = Color(255, 0, 0)
# modifies red in place to make it into cyan
red.complement$()
red.toString()
# => 'rgba(0, 255, 255, 1)'
@name complement$
@methodOf Color#
@returns {Color} the color hue shifted by 180 degrees on the color wheel. Modifies the existing color.
*/
complement$: function() {
return this.shiftHue$(180);
},
/**
A copy of the calling color.
color = Color(0, 100, 200)
copy = color.copy()
color == copy
# => false
color.equal(copy)
# => true
@name copy
@methodOf Color#
@returns {Color} A new color. A copy of the calling color
*/
copy: function() {
return Color(this.r, this.g, this.b, this.a);
},
/**
Returns a copy of the calling color darkened by `amount` (Lightness of the color ranges from 0 to 1).
green = Color(0, 255, 0)
darkGreen = green.darken(0.3)
# to see what they look like
for color, index in [green, darkGreen]
canvas.drawRect
color: color
x: 20 + (60 * index)
y: 20 + (60 * index)
width: 60
height: 60
@name darken
@methodOf Color#
@param {Number} amount Amount to darken color by (between 0 - 1)
@returns {Color} A new color. The lightness value is reduced by `amount` from the original.
*/
darken: function(amount) {
return this.copy().darken$(amount);
},
/**
Modifies the color so that it is darkened by `amount` (Lightness of the color ranges from 0 to 1).
green = Color(0, 255, 0)
# Modifies green to be darkGreen
green.darken$(0.3)
green.toString()
# => 'rgba(0, 102, 0, 1)'
@name darken$
@methodOf Color#
@param {Number} amount Amount to darken color by (between 0 - 1)
@returns {Color} the color with the lightness value reduced by `amount`
*/
darken$: function(amount) {
var hsl, _ref;
hsl = this.toHsl();
hsl[2] -= amount;
_ref = hslToRgb(hsl), this.r = _ref[0], this.g = _ref[1], this.b = _ref[2], this.a = _ref[3];
return this;
},
/**
A copy of the calling color with its saturation reduced by `amount`.
blue = Color(0, 0, 255)
desaturatedBlue = blue.desaturate(0.4)
# to see what they look like
for color, index in [blue, desaturatedBlue]
canvas.drawRect
color: color
x: 20 + (60 * index)
y: 20 + (60 * index)
width: 60
height: 60
@name desaturate
@methodOf Color#
@param {Number} amount Amount to reduce color saturation by (between 0 and 1)
@returns {Color} A copy of the color with the saturation value reduced by `amount`
*/
desaturate: function(amount) {
return this.copy().desaturate$(amount);
},
/**
The modified color with its saturation reduced by `amount`.
blue = Color(0, 0, 255)
# modifies blue to be desaturatedBlue
blue.desaturate$(0.4)
blue.toString()
# => 'rgba(38, 38, 217, 1)'
@name desaturate$
@methodOf Color#
@param {Number} amount Amount to reduce color saturation by (between 0 and 1)
@returns {Color} the color with the saturation value reduced by `amount`
*/
desaturate$: function(amount) {
var hsl, _ref;
hsl = this.toHsl();
hsl[1] -= amount;
_ref = hslToRgb(hsl), this.r = _ref[0], this.g = _ref[1], this.b = _ref[2], this.a = _ref[3];
return this;
},
/**
Determine whether two colors are equal. Compares their r, g, b, and alpha values.
hex = Color('#ffff00')
rgb = Color(255, 255, 0)
hex == rgb
# => false
hex.equal(rgb)
# => true
@name equal
@methodOf Color#
@param {Color} other the color to compare to the calling color
@returns {Boolean} true if the r, g, b, a values of the colors agree, false otherwise
*/
equal: function(other) {
return other.r === this.r && other.g === this.g && other.b === this.b && other.a === this.a;
},
/**
A copy of the calling color converted to grayscale.
yellow = Color(255, 255, 0)
gray = yellow.grayscale()
# to see what they look like
for color, index in [yellow, gray]
canvas.drawRect
color: color
x: 20 + (60 * index)
y: 20 + (60 * index)
width: 60
height: 60
@name grayscale
@methodOf Color#
@returns {Color} A copy of the calling color converted to grayscale.
*/
grayscale: function() {
return this.copy().grayscale$();
},
/**
The calling color converted to grayscale.
color = Color(255, 255, 0)
# modifies color into gray
color.grayscale$()
color.toString()
# => 'rgba(128, 128, 128, 1)'
@name grayscale$
@methodOf Color#
@returns {Color} The calling color converted to grayscale.
*/
grayscale$: function() {
var g, hsl;
hsl = this.toHsl();
g = (hsl[2] * 255).round();
this.r = this.g = this.b = g;
return this;
},
/**
A getter / setter for the hue value of the color. Passing no argument returns the
current hue value. Passing a value will set the hue to that value and return the color.
magenta = Color(255, 0, 255)
magenta.hue()
# => 300
# modifies the color to be yellow
magenta.hue(60)
# to see what it looks like
canvas.drawRect
color: magenta
x: 50
y: 30
width: 80
height: 80
@name hue
@methodOf Color#
@param {Number} [newVal] the new hue value
@returns {Color|Number} returns the color object if you pass a new hue value and returns the hue otherwise
*/
hue: function(newVal) {
var hsl, _ref;
hsl = this.toHsl();
if (newVal != null) {
hsl[0] = newVal;
_ref = hslToRgb(hsl), this.r = _ref[0], this.g = _ref[1], this.b = _ref[2], this.a = _ref[3];
return this;
} else {
return hsl[0];
}
},
/**
A getter / setter for the lightness value of the color. Passing no argument returns the
current lightness value. Passing a value will set the lightness to that value and return the color.
magenta = Color(255, 0, 255)
magenta.lightness()
# => 0.9
# modifies magenta in place to be lighter
magenta.lightness(0.75)
# to see what it looks like
canvas.drawRect
color: magenta
x: 50
y: 30
width: 80
height: 80
@name lightness
@methodOf Color#
@param {Number} [newVal] the new lightness value
@returns {Color|Number} returns the color object if you pass a new lightness value and returns the lightness otherwise
*/
lightness: function(newVal) {
var hsl, _ref;
hsl = this.toHsl();
if (newVal != null) {
hsl[2] = newVal;
_ref = hslToRgb(hsl), this.r = _ref[0], this.g = _ref[1], this.b = _ref[2], this.a = _ref[3];
return this;
} else {
return hsl[2];
}
},
value: function(newVal) {
var hsv, _ref;
hsv = this.toHsv();
if (newVal != null) {
hsv[2] = newVal;
_ref = hsvToRgb(hsv), this.r = _ref[0], this.g = _ref[1], this.b = _ref[2], this.a = _ref[3];
return this;
} else {
return hsv[2];
}
},
/**
A copy of the calling color with its hue shifted by `degrees`. This differs from the hue setter in that it adds to the existing hue value and will wrap around 0 and 360.
magenta = Color(255, 0, 255)
magenta.hue()
# => 300
yellow = magenta.shiftHue(120)
# since magenta's hue is 300 we have wrapped
# around 360 to end up at 60
yellow.hue()
# => 60
# to see what they look like
for color, index in [magenta, yellow]
canvas.drawRect
color: color
x: 20 + (60 * index)
y: 20 + (60 * index)
width: 60
height: 60
@name shiftHue
@methodOf Color#
@param {Number} degrees number of degrees to shift the hue on the color wheel.
@returns {Color} A copy of the color with its hue shifted by `degrees`
*/
shiftHue: function(degrees) {
return this.copy().shiftHue$(degrees);
},
/**
The calling color with its hue shifted by `degrees`. This differs from the hue setter in that it adds to the existing hue value and will wrap around 0 and 360.
magenta = Color(255, 0, 255)
magenta.hue()
# => 300
magenta.shiftHue$(120)
# since magenta's hue is 300 we have wrapped
# around 360 to end up at 60. Also we have
# modified magenta in place to become yellow
magenta.hue()
# => 60
magenta.toString()
# => 'rgba(255, 255, 0, 1)'
@name shiftHue$
@methodOf Color#
@param {Number} degrees number of degrees to shift the hue on the color wheel.
@returns {Color} The color with its hue shifted by `degrees`
*/
shiftHue$: function(degrees) {
var hsl, _ref;
hsl = this.toHsl();
hsl[0] = (hsl[0] + degrees.round()).mod(360);
_ref = hslToRgb(hsl), this.r = _ref[0], this.g = _ref[1], this.b = _ref[2], this.a = _ref[3];
return this;
},
/**
Returns a copy of the calling color lightened by `amount` (Lightness of the color ranges from 0 to 1).
green = Color(0, 255, 0)
lightGreen = green.lighten(0.3)
# to see what they look like
for color, index in [green, lightGreen]
canvas.drawRect
color: color
x: 20 + (60 * index)
y: 20 + (60 * index)
width: 60
height: 60
@name lighten
@methodOf Color#
@param {Number} amount Amount to lighten color by (between 0 to 1)
@returns {Color} A new color. The lightness value is increased by `amount` from the original.
*/
lighten: function(amount) {
return this.copy().lighten$(amount);
},
/**
The calling color lightened by `amount` (Lightness of the color ranges from 0 to 1).
green = Color(0, 255, 0)
green.lighten$(0.2)
# we have modified green in place
# to become lightGreen
green.toString()
# => 'rgba(102, 255, 102, 1)'
@name lighten$
@methodOf Color#
@param {Number} amount Amount to lighten color by (between 0 - 1)
@returns {Color} The calling color with its lightness value increased by `amount`.
*/
lighten$: function(amount) {
var hsl, _ref;
hsl = this.toHsl();
hsl[2] += amount;
_ref = hslToRgb(hsl), this.r = _ref[0], this.g = _ref[1], this.b = _ref[2], this.a = _ref[3];
return this;
},
/**
A copy of the calling color mixed with `other` using `amount` as the
mixing ratio. If amount is not passed, then the colors are mixed evenly.
red = Color(255, 0, 0)
yellow = Color(255, 255, 0)
# With no amount argument the colors are mixed evenly
orange = red.mixWith(yellow)
# With an amount of 0.3 we are mixing the color 30% red and 70% yellow
somethingCloseToOrange = red.mixWith(yellow, 0.3)
# to see what they look like
for color, index in [red, yellow, orange, somethingCloseToOrange]
canvas.drawRect
color: color
x: 20 + (60 * (index % 2))
y: 20 + (60 * (if index > 1 then 1 else 0))
width: 60
height: 60
@name mixWith
@methodOf Color#
@param {Color} other the other color to mix
@param {Number} [amount] the mixing ratio of the calling color to `other`
@returns {Color} A new color that is a mix of the calling color and `other`
*/
mixWith: function(other, amount) {
return this.copy().mixWith$(other, amount);
},
/**
A copy of the calling color mixed with `other` using `amount` as the
mixing ratio. If amount is not passed, then the colors are mixed evenly.
red = Color(255, 0, 0)
yellow = Color(255, 255, 0)
anotherRed = Color(255, 0, 0)
# With no amount argument the colors are mixed evenly
red.mixWith$(yellow)
# We have modified red in place to be orange
red.toString()
# => 'rgba(255, 128, 0, 1)'
# With an amount of 0.3 we are mixing the color 30% red and 70% yellow
anotherRed.mixWith$(yellow, 0.3)
# We have modified `anotherRed` in place to be somethingCloseToOrange
anotherRed.toString()
# => rgba(255, 179, 0, 1)
@name mixWith$
@methodOf Color#
@param {Color} other the other color to mix
@param {Number} [amount] the mixing ratio of the calling color to `other`
@returns {Color} The modified calling color after mixing it with `other`
*/
mixWith$: function(other, amount) {
var _ref, _ref1;
amount || (amount = 0.5);
_ref = [this.r, this.g, this.b, this.a].zip([other.r, other.g, other.b, other.a]).map(function(array) {
return (array[0] * amount) + (array[1] * (1 - amount));
}), this.r = _ref[0], this.g = _ref[1], this.b = _ref[2], this.a = _ref[3];
_ref1 = [this.r, this.g, this.b].map(function(color) {
return color.round();
}), this.r = _ref1[0], this.g = _ref1[1], this.b = _ref1[2];
return this;
},
/**
A copy of the calling color with its saturation increased by `amount`.
color = Color(50, 50, 200)
color.saturation()
# => 0.6
saturatedColor = color.saturate(0.2)
saturatedColor.saturation()
# => 0.8
# to see what they look like
for color, index in [color, saturatedColor]
canvas.drawRect
color: color
x: 20 + (60 * index)
y: 20 + (60 * index)
width: 60
height: 60
@name saturate
@methodOf Color#
@param {Number} amount the amount to increase saturation by
@returns {Color} A copy of the calling color with its saturation increased by `amount`
*/
saturate: function(amount) {
return this.copy().saturate$(amount);
},
/**
The calling color with its saturation increased by `amount`.
color = Color(50, 50, 200)
color.saturation()
# => 0.6
color.saturate$(0.2)
# We have modified color in place and increased its saturation to 0.8
color.saturation()
# => 0.8
color.toString()
# => rgba(25, 25, 225, 1)
@name saturate$
@methodOf Color#
@param {Number} amount the amount to increase saturation by
@returns {Color} The calling color with its saturation increased by `amount`
*/
saturate$: function(amount) {
var hsl, _ref;
hsl = this.toHsl();
hsl[1] += amount;
_ref = hslToRgb(hsl), this.r = _ref[0], this.g = _ref[1], this.b = _ref[2], this.a = _ref[3];
return this;
},
/**
A getter / setter for the saturation value of the color. Passing no argument returns the
current saturation value. Passing a value will set the saturation to that value and return the color.
yellow = Color('hsl(60, 0.5, 0.5)')
yellow.saturation()
# => 0.5
yellow.saturation(0.8)
# to see what it looks like
canvas.drawRect
color: yellow
x: 50
y: 30
width: 80
height: 80
@name saturation
@methodOf Color#
@param {Number} [newVal] the new saturation value
@returns {Color|Number} returns the color object if you pass a new saturation value and returns the saturation otherwise
*/
saturation: function(newVal, mode) {
var hsl, hsv, _ref, _ref1;
if (mode === 'hsv') {
hsv = this.toHsv();
if (newVal != null) {
hsv[1] = newVal;
_ref = hsvToRgb(hsv), this.r = _ref[0], this.g = _ref[1], this.b = _ref[2], this.a = _ref[3];
return this;
} else {
return hsv[1];
}
} else {
hsl = this.toHsl();
if (newVal != null) {
hsl[1] = newVal;
_ref1 = hslToRgb(hsl), this.r = _ref1[0], this.g = _ref1[1], this.b = _ref1[2], this.a = _ref1[3];
return this;
} else {
return hsl[1];
}
}
},
/**
returns the Hex representation of the color. Exclude the leading `#` by passing false.
color = Color('hsl(60, 1, 0.5)')
# passing nothing will leave the `#` intact
color.toHex()
# => '#ffff00'
# passing false will remove the `#`
color.toHex(false)
# => 'ffff00'
@name toHex
@methodOf Color#
@param {Boolean} [leadingHash] if passed as false excludes the leading `#` from the string
@returns {String} returns the Hex representation of the color
*/
toHex: function(leadingHash) {
var hexFromNumber, padString;
padString = function(hexString) {
var pad;
if (hexString.length === 1) {
pad = "0";
} else {
pad = "";
}
return pad + hexString;
};
hexFromNumber = function(number) {
return padString(number.toString(16));
};
if (leadingHash === false) {
return "" + (hexFromNumber(this.r)) + (hexFromNumber(this.g)) + (hexFromNumber(this.b));
} else {
return "#" + (hexFromNumber(this.r)) + (hexFromNumber(this.g)) + (hexFromNumber(this.b));
}
},
/**
returns an array of the hue, saturation, lightness, and alpha values of the color.
magenta = Color(255, 0, 255)
magenta.toHsl()
# => [300, 1, 0.5, 1]
@name toHsl
@methodOf Color#
@returns {Array} An array of the hue, saturation, lightness, and alpha values of the color.
*/
toHsl: function() {
var b, channel, chroma, g, hue, lightness, max, min, r, saturation, _ref, _ref1;
_ref = (function() {
var _i, _len, _ref, _results;
_ref = [this.r, this.g, this.b];
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
channel = _ref[_i];
_results.push(channel / 255);
}
return _results;
}).call(this), r = _ref[0], g = _ref[1], b = _ref[2];
_ref1 = [r, g, b].extremes(), min = _ref1.min, max = _ref1.max;
hue = saturation = lightness = (max + min) / 2;
chroma = max - min;
if (chroma.abs() < 0.00001) {
hue = saturation = 0;
} else {
saturation = lightness > 0.5 ? chroma / (1 - lightness) : chroma / lightness;
saturation /= 2;
switch (max) {
case r:
hue = ((g - b) / chroma) + 0;
break;
case g:
hue = ((b - r) / chroma) + 2;
break;
case b:
hue = ((r - g) / chroma) + 4;
}
hue = (hue * 60).mod(360);
}
return [hue, saturation, lightness, this.a];
},
toHsv: function() {
var b, d, g, h, max, min, r, s, v, _ref;
r = this.r / 255;
g = this.g / 255;
b = this.b / 255;
_ref = [r, g, b].extremes(), min = _ref.min, max = _ref.max;
h = s = v = max;
d = max - min;
s = (max === 0 ? 0 : d / max);
if (max === min) {
h = 0;
} else {
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0);
break;
case g:
h = (b - r) / d + 2;
break;
case b:
h = (r - g) / d + 4;
}
h *= 60;
}
return [h, s, v];
},
/**
returns string rgba representation of the color.
red = Color('#ff0000')
red.toString()
# => 'rgba(255, 0, 0, 1)'
@name toString
@methodOf Color#
@returns {String} The rgba string representation of the color
*/
toString: function() {
return "rgba(" + this.r + ", " + this.g + ", " + this.b + ", " + this.a + ")";
},
/**
A copy of the calling color with its alpha reduced by `amount`.
color = Color(0, 0, 0, 1)
color.a
# => 1
transparentColor = color.transparentize(0.5)
transparentColor.a
# => 0.5
# to see what they look like
for color, index in [color, transparentColor]
canvas.drawRect
color: color
x: 20 + (60 * index)
y: 20 + (60 * index)
width: 60
height: 60
@name transparentize
@methodOf Color#
@returns {Color} A copy of the calling color with its alpha reduced by `amount`
*/
transparentize: function(amount) {
return this.copy().transparentize$(amount);
},
/**
The calling color with its alpha reduced by `amount`.
color = Color(0, 0, 0, 1)
color.a
# => 1
# We modify color in place
color.transparentize$(0.5)
color.a
# => 0.5
@name transparentize$
@methodOf Color#
@returns {Color} The calling color with its alpha reduced by `amount`
*/
transparentize$: function(amount) {
this.a = (this.a - amount).clamp(0, 1);
return this;
},
/**
A copy of the calling color with its alpha increased by `amount`.
color = Color(0, 0, 0, 0.25)
color.a
# => 0.25
opaqueColor = color.opacify(0.5)
opaqueColor.a
# => 0.75
# to see what they look like
for color, index in [color, opaqueColor]
canvas.drawRect
color: color
x: 20 + (60 * index)
y: 20 + (60 * index)
width: 60
height: 60
@name opacify
@methodOf Color#
@returns {Color} A copy of the calling color with its alpha increased by `amount`
*/
opacify: function(amount) {
return this.copy().opacify$(amount);
},
/**
The calling color with its alpha increased by `amount`.
color = Color(0, 0, 0, 0)
color.a
# => 0
# We modify color in place
color.opacify$(0.25)
color.a
# => 0.25
@name opacify$
@methodOf Color#
@returns {Color} The calling color with its alpha increased by `amount`
*/
opacify$: function(amount) {
this.a = (this.a + amount).clamp(0, 1);
return this;
}
};
/**
returns a random color.
Color.random().toString()
# => 'rgba(213, 144, 202, 1)'
Color.random().toString()
# => 'rgba(1, 211, 24, 1)'
@name random
@methodOf Color
@returns {Color} A random color.
*/
Color.random = function() {
return Color(rand(256), rand(256), rand(256));
};
/**
Mix two colors. Behaves just like `#mixWith` except that you are passing two colors.
red = Color(255, 0, 0)
yellow = Color(255, 255, 0)
# With no amount argument the colors are mixed evenly
orange = Color.mix(red, yellow)
orange.toString()
# => 'rgba(255, 128, 0, 1)'
# With an amount of 0.3 we are mixing the color 30% red and 70% yellow
somethingCloseToOrange = Color.mix(red, yellow, 0.3)
somethingCloseToOrange.toString()
# => rgba(255, 179, 0, 1)
@name mix
@methodOf Color
@see Color#mixWith
@param {Color} color1 the first color to mix
@param {Color} color2 the second color to mix
@param {Number} amount the ratio to mix the colors
@returns {Color} A new color that is the two colors mixed at the ratio defined by `amount`
*/
Color.mix = function(color1, color2, amount) {
var newColors;
amount || (amount = 0.5);
newColors = [color1.r, color1.g, color1.b, color1.a].zip([color2.r, color2.g, color2.b, color2.a]).map(function(array) {
return (array[0] * amount) + (array[1] * (1 - amount));
});
return Color(newColors);
};
return (typeof exports !== "undefined" && exports !== null ? exports : this)["Color"] = Color;
})();
(function() {
var lookup, names, normalizeKey;
names = [["000000", "Black"], ["000080", "Navy Blue"], ["0000C8", "Dark Blue"], ["0000FF", "Blue"], ["000741", "Stratos"], ["001B1C", "Swamp"], ["002387", "Resolution Blue"], ["002900", "Deep Fir"], ["002E20", "Burnham"], ["002FA7", "International Klein Blue"], ["003153", "Prussian Blue"], ["003366", "Midnight Blue"], ["003399", "Smalt"], ["003532", "Deep Teal"], ["003E40", "Cyprus"], ["004620", "Kaitoke Green"], ["0047AB", "Cobalt"], ["004816", "Crusoe"], ["004950", "Sherpa Blue"], ["0056A7", "Endeavour"], ["00581A", "Camarone"], ["0066CC", "Science Blue"], ["0066FF", "Blue Ribbon"], ["00755E", "Tropical Rain Forest"], ["0076A3", "Allports"], ["007BA7", "Deep Cerulean"], ["007EC7", "Lochmara"], ["007FFF", "Azure Radiance"], ["008080", "Teal"], ["0095B6", "Bondi Blue"], ["009DC4", "Pacific Blue"], ["00A693", "Persian Green"], ["00A86B", "Jade"], ["00CC99", "Caribbean Green"], ["00CCCC", "Robin's Egg Blue"], ["00FF00", "Green"], ["00FF7F", "Spring Green"], ["00FFFF", "Cyan / Aqua"], ["010D1A", "Blue Charcoal"], ["011635", "Midnight"], ["011D13", "Holly"], ["012731", "Daintree"], ["01361C", "Cardin Green"], ["01371A", "County Green"], ["013E62", "Astronaut Blue"], ["013F6A", "Regal Blue"], ["014B43", "Aqua Deep"], ["015E85", "Orient"], ["016162", "Blue Stone"], ["016D39", "Fun Green"], ["01796F", "Pine Green"], ["017987", "Blue Lagoon"], ["01826B", "Deep Sea"], ["01A368", "Green Haze"], ["022D15", "English Holly"], ["02402C", "Sherwood Green"], ["02478E", "Congress Blue"], ["024E46", "Evening Sea"], ["026395", "Bahama Blue"], ["02866F", "Observatory"], ["02A4D3", "Cerulean"], ["03163C", "Tangaroa"], ["032B52", "Green Vogue"], ["036A6E", "Mosque"], ["041004", "Midnight Moss"], ["041322", "Black Pearl"], ["042E4C", "Blue Whale"], ["044022", "Zuccini"], ["044259", "Teal Blue"], ["051040", "Deep Cove"], ["051657", "Gulf Blue"], ["055989", "Venice Blue"], ["056F57", "Watercourse"], ["062A78", "Catalina Blue"], ["063537", "Tiber"], ["069B81", "Gossamer"], ["06A189", "Niagara"], ["073A50", "Tarawera"], ["080110", "Jaguar"], ["081910", "Black Bean"], ["082567", "Deep Sapphire"], ["088370", "Elf Green"], ["08E8DE", "Bright Turquoise"], ["092256", "Downriver"], ["09230F", "Palm Green"], ["09255D", "Madison"], ["093624", "Bottle Green"], ["095859", "Deep Sea Green"], ["097F4B", "Salem"], ["0A001C", "Black Russian"], ["0A480D", "Dark Fern"], ["0A6906", "Japanese Laurel"], ["0A6F75", "Atoll"], ["0B0B0B", "Cod Gray"], ["0B0F08", "Marshland"], ["0B1107", "Gordons Green"], ["0B1304", "Black Forest"], ["0B6207", "San Felix"], ["0BDA51", "Malachite"], ["0C0B1D", "Ebony"], ["0C0D0F", "Woodsmoke"], ["0C1911", "Racing Green"], ["0C7A79", "Surfie Green"], ["0C8990", "Blue Chill"], ["0D0332", "Black Rock"], ["0D1117", "Bunker"], ["0D1C19", "Aztec"], ["0D2E1C", "Bush"], ["0E0E18", "Cinder"], ["0E2A30", "Firefly"], ["0F2D9E", "Torea Bay"], ["10121D", "Vulcan"], ["101405", "Green Waterloo"], ["105852", "Eden"], ["110C6C", "Arapawa"], ["120A8F", "Ultramarine"], ["123447", "Elephant"], ["126B40", "Jewel"], ["130000", "Diesel"], ["130A06", "Asphalt"], ["13264D", "Blue Zodiac"], ["134F19", "Parsley"], ["140600", "Nero"], ["1450AA", "Tory Blue"], ["151F4C", "Bunting"], ["1560BD", "Denim"], ["15736B", "Genoa"], ["161928", "Mirage"], ["161D10", "Hunter Green"], ["162A40", "Big Stone"], ["163222", "Celtic"], ["16322C", "Timber Green"], ["163531", "Gable Green"], ["171F04", "Pine Tree"], ["175579", "Chathams Blue"], ["182D09", "Deep Forest Green"], ["18587A", "Blumine"], ["19330E", "Palm Leaf"], ["193751", "Nile Blue"], ["1959A8", "Fun Blue"], ["1A1A68", "Lucky Point"], ["1AB385", "Mountain Meadow"], ["1B0245", "Tolopea"], ["1B1035", "Haiti"], ["1B127B", "Deep Koamaru"], ["1B1404", "Acadia"], ["1B2F11", "Seaweed"], ["1B3162", "Biscay"], ["1B659D", "Matisse"], ["1C1208", "Crowshead"], ["1C1E13", "Rangoon Green"], ["1C39BB", "Persian Blue"], ["1C402E", "Everglade"], ["1C7C7D", "Elm"], ["1D6142", "Green Pea"], ["1E0F04", "Creole"], ["1E1609", "Karaka"], ["1E1708", "El Paso"], ["1E385B", "Cello"], ["1E433C", "Te Papa Green"], ["1E90FF", "Dodger Blue"], ["1E9AB0", "Eastern Blue"], ["1F120F", "Night Rider"], ["1FC2C2", "Java"], ["20208D", "Jacksons Purple"], ["202E54", "Cloud Burst"], ["204852", "Blue Dianne"], ["211A0E", "Eternity"], ["220878", "Deep Blue"], ["228B22", "Forest Green"], ["233418", "Mallard"], ["240A40", "Violet"], ["240C02", "Kilamanjaro"], ["242A1D", "Log Cabin"], ["242E16", "Black Olive"], ["24500F", "Green House"], ["251607", "Graphite"], ["251706", "Cannon Black"], ["251F4F", "Port Gore"], ["25272C", "Shark"], ["25311C", "Green Kelp"], ["2596D1", "Curious Blue"], ["260368", "Paua"], ["26056A", "Paris M"], ["261105", "Wood Bark"], ["261414", "Gondola"], ["262335", "Steel Gray"], ["26283B", "Ebony Clay"], ["273A81", "Bay of Many"], ["27504B", "Plantation"], ["278A5B", "Eucalyptus"], ["281E15", "Oil"], ["283A77", "Astronaut"], ["286ACD", "Mariner"], ["290C5E", "Violent Violet"], ["292130", "Bastille"], ["292319", "Zeus"], ["292937", "Charade"], ["297B9A", "Jelly Bean"], ["29AB87", "Jungle Green"], ["2A0359", "Cherry Pie"], ["2A140E", "Coffee Bean"], ["2A2630", "Baltic Sea"], ["2A380B", "Turtle Green"], ["2A52BE", "Cerulean Blue"], ["2B0202", "Sepia Black"], ["2B194F", "Valhalla"], ["2B3228", "Heavy Metal"], ["2C0E8C", "Blue Gem"], ["2C1632", "Revolver"], ["2C2133", "Bleached Cedar"], ["2C8C84", "Lochinvar"], ["2D2510", "Mikado"], ["2D383A", "Outer Space"], ["2D569B", "St Tropaz"], ["2E0329", "Jacaranda"], ["2E1905", "Jacko Bean"], ["2E3222", "Rangitoto"], ["2E3F62", "Rhino"], ["2E8B57", "Sea Green"], ["2EBFD4", "Scooter"], ["2F270E", "Onion"], ["2F3CB3", "Governor Bay"], ["2F519E", "Sapphire"], ["2F5A57", "Spectra"], ["2F6168", "Casal"], ["300529", "Melanzane"], ["301F1E", "Cocoa Brown"], ["302A0F", "Woodrush"], ["304B6A", "San Juan"], ["30D5C8", "Turquoise"], ["311C17", "Eclipse"], ["314459", "Pickled Bluewood"], ["315BA1", "Azure"], ["31728D", "Calypso"], ["317D82", "Paradiso"], ["32127A", "Persian Indigo"], ["32293A", "Blackcurrant"], ["323232", "Mine Shaft"], ["325D52", "Stromboli"], ["327C14", "Bilbao"], ["327DA0", "Astral"], ["33036B", "Christalle"], ["33292F", "Thunder"], ["33CC99", "Shamrock"], ["341515", "Tamarind"], ["350036", "Mardi Gras"], ["350E42", "Valentino"], ["350E57", "Jagger"], ["353542", "Tuna"], ["354E8C", "Chambray"], ["363050", "Martinique"], ["363534", "Tuatara"], ["363C0D", "Waiouru"], ["36747D", "Ming"], ["368716", "La Palma"], ["370202", "Chocolate"], ["371D09", "Clinker"], ["37290E", "Brown Tumbleweed"], ["373021", "Birch"], ["377475", "Oracle"], ["380474", "Blue Diamond"], ["381A51", "Grape"], ["383533", "Dune"], ["384555", "Oxford Blue"], ["384910", "Clover"], ["394851", "Limed Spruce"], ["396413", "Dell"], ["3A0020", "Toledo"], ["3A2010", "Sambuca"], ["3A2A6A", "Jacarta"], ["3A686C", "William"], ["3A6A47", "Killarney"], ["3AB09E", "Keppel"], ["3B000B", "Temptress"], ["3B0910", "Aubergine"], ["3B1F1F", "Jon"], ["3B2820", "Treehouse"], ["3B7A57", "Amazon"], ["3B91B4", "Boston Blue"], ["3C0878", "Windsor"], ["3C1206", "Rebel"], ["3C1F76", "Meteorite"], ["3C2005", "Dark Ebony"], ["3C3910", "Camouflage"], ["3C4151", "Bright Gray"], ["3C4443", "Cape Cod"], ["3C493A", "Lunar Green"], ["3D0C02", "Bean "], ["3D2B1F", "Bistre"], ["3D7D52", "Goblin"], ["3E0480", "Kingfisher Daisy"], ["3E1C14", "Cedar"], ["3E2B23", "English Walnut"], ["3E2C1C", "Black Marlin"], ["3E3A44", "Ship Gray"], ["3EABBF", "Pelorous"], ["3F2109", "Bronze"], ["3F2500", "Cola"], ["3F3002", "Madras"], ["3F307F", "Minsk"], ["3F4C3A", "Cabbage Pont"], ["3F583B", "Tom Thumb"], ["3F5D53", "Mineral Green"], ["3FC1AA", "Puerto Rico"], ["3FFF00", "Harlequin"], ["401801", "Brown Pod"], ["40291D", "Cork"], ["403B38", "Masala"], ["403D19", "Thatch Green"], ["405169", "Fiord"], ["40826D", "Viridian"], ["40A860", "Chateau Green"], ["410056", "Ripe Plum"], ["411F10", "Paco"], ["412010", "Deep Oak"], ["413C37", "Merlin"], ["414257", "Gun Powder"], ["414C7D", "East Bay"], ["4169E1", "Royal Blue"], ["41AA78", "Ocean Green"], ["420303", "Burnt Maroon"], ["423921", "Lisbon Brown"], ["427977", "Faded Jade"], ["431560", "Scarlet Gum"], ["433120", "Iroko"], ["433E37", "Armadillo"], ["434C59", "River Bed"], ["436A0D", "Green Leaf"], ["44012D", "Barossa"], ["441D00", "Morocco Brown"], ["444954", "Mako"], ["454936", "Kelp"], ["456CAC", "San Marino"], ["45B1E8", "Picton Blue"], ["460B41", "Loulou"], ["462425", "Crater Brown"], ["465945", "Gray Asparagus"], ["4682B4", "Steel Blue"], ["480404", "Rustic Red"], ["480607", "Bulgarian Rose"], ["480656", "Clairvoyant"], ["481C1C", "Cocoa Bean"], ["483131", "Woody Brown"], ["483C32", "Taupe"], ["49170C", "Van Cleef"], ["492615", "Brown Derby"], ["49371B", "Metallic Bronze"], ["495400", "Verdun Green"], ["496679", "Blue Bayoux"], ["497183", "Bismark"], ["4A2A04", "Bracken"], ["4A3004", "Deep Bronze"], ["4A3C30", "Mondo"], ["4A4244", "Tundora"], ["4A444B", "Gravel"], ["4A4E5A", "Trout"], ["4B0082", "Pigment Indigo"], ["4B5D52", "Nandor"], ["4C3024", "Saddle"], ["4C4F56", "Abbey"], ["4D0135", "Blackberry"], ["4D0A18", "Cab Sav"], ["4D1E01", "Indian Tan"], ["4D282D", "Cowboy"], ["4D282E", "Livid Brown"], ["4D3833", "Rock"], ["4D3D14", "Punga"], ["4D400F", "Bronzetone"], ["4D5328", "Woodland"], ["4E0606", "Mahogany"], ["4E2A5A", "Bossanova"], ["4E3B41", "Matterhorn"], ["4E420C", "Bronze Olive"], ["4E4562", "Mulled Wine"], ["4E6649", "Axolotl"], ["4E7F9E", "Wedgewood"], ["4EABD1", "Shakespeare"], ["4F1C70", "Honey Flower"], ["4F2398", "Daisy Bush"], ["4F69C6", "Indigo"], ["4F7942", "Fern Green"], ["4F9D5D", "Fruit Salad"], ["4FA83D", "Apple"], ["504351", "Mortar"], ["507096", "Kashmir Blue"], ["507672", "Cutty Sark"], ["50C878", "Emerald"], ["514649", "Emperor"], ["516E3D", "Chalet Green"], ["517C66", "Como"], ["51808F", "Smalt Blue"], ["52001F", "Castro"], ["520C17", "Maroon Oak"], ["523C94", "Gigas"], ["533455", "Voodoo"], ["534491", "Victoria"], ["53824B", "Hippie Green"], ["541012", "Heath"], ["544333", "Judge Gray"], ["54534D", "Fuscous Gray"], ["549019", "Vida Loca"], ["55280C", "Cioccolato"], ["555B10", "Saratoga"], ["556D56", "Finlandia"], ["5590D9", "Havelock Blue"], ["56B4BE", "Fountain Blue"], ["578363", "Spring Leaves"], ["583401", "Saddle Brown"], ["585562", "Scarpa Flow"], ["587156", "Cactus"], ["589AAF", "Hippie Blue"], ["591D35", "Wine Berry"], ["592804", "Brown Bramble"], ["593737", "Congo Brown"], ["594433", "Millbrook"], ["5A6E9C", "Waikawa Gray"], ["5A87A0", "Horizon"], ["5B3013", "Jambalaya"], ["5C0120", "Bordeaux"], ["5C0536", "Mulberry Wood"], ["5C2E01", "Carnaby Tan"], ["5C5D75", "Comet"], ["5D1E0F", "Redwood"], ["5D4C51", "Don Juan"], ["5D5C58", "Chicago"], ["5D5E37", "Verdigris"], ["5D7747", "Dingley"], ["5DA19F", "Breaker Bay"], ["5E483E", "Kabul"], ["5E5D3B", "Hemlock"], ["5F3D26", "Irish Coffee"], ["5F5F6E", "Mid Gray"], ["5F6672", "Shuttle Gray"], ["5FA777", "Aqua Forest"], ["5FB3AC", "Tradewind"], ["604913", "Horses Neck"], ["605B73", "Smoky"], ["606E68", "Corduroy"], ["6093D1", "Danube"], ["612718", "Espresso"], ["614051", "Eggplant"], ["615D30", "Costa Del Sol"], ["61845F", "Glade Green"], ["622F30", "Buccaneer"], ["623F2D", "Quincy"], ["624E9A", "Butterfly Bush"], ["625119", "West Coast"], ["626649", "Finch"], ["639A8F", "Patina"], ["63B76C", "Fern"], ["6456B7", "Blue Violet"], ["646077", "Dolphin"], ["646463", "Storm Dust"], ["646A54", "Siam"], ["646E75", "Nevada"], ["6495ED", "Cornflower Blue"], ["64CCDB", "Viking"], ["65000B", "Rosewood"], ["651A14", "Cherrywood"], ["652DC1", "Purple Heart"], ["657220", "Fern Frond"], ["65745D", "Willow Grove"], ["65869F", "Hoki"], ["660045", "Pompadour"], ["660099", "Purple"], ["66023C", "Tyrian Purple"], ["661010", "Dark Tan"], ["66B58F", "Silver Tree"], ["66FF00", "Bright Green"], ["66FF66", "Screamin' Green"], ["67032D", "Black Rose"], ["675FA6", "Scampi"], ["676662", "Ironside Gray"], ["678975", "Viridian Green"], ["67A712", "Christi"], ["683600", "Nutmeg Wood Finish"], ["685558", "Zambezi"], ["685E6E", "Salt Box"], ["692545", "Tawny Port"], ["692D54", "Finn"], ["695F62", "Scorpion"], ["697E9A", "Lynch"], ["6A442E", "Spice"], ["6A5D1B", "Himalaya"], ["6A6051", "Soya Bean"], ["6B2A14", "Hairy Heath"], ["6B3FA0", "Royal Purple"], ["6B4E31", "Shingle Fawn"], ["6B5755", "Dorado"], ["6B8BA2", "Bermuda Gray"], ["6B8E23", "Olive Drab"], ["6C3082", "Eminence"], ["6CDAE7", "Turquoise Blue"], ["6D0101", "Lonestar"], ["6D5E54", "Pine Cone"], ["6D6C6C", "Dove Gray"], ["6D9292", "Juniper"], ["6D92A1", "Gothic"], ["6E0902", "Red Oxide"], ["6E1D14", "Moccaccino"], ["6E4826", "Pickled Bean"], ["6E4B26", "Dallas"], ["6E6D57", "Kokoda"], ["6E7783", "Pale Sky"], ["6F440C", "Cafe Royale"], ["6F6A61", "Flint"], ["6F8E63", "Highland"], ["6F9D02", "Limeade"], ["6FD0C5", "Downy"], ["701C1C", "Persian Plum"], ["704214", "Sepia"], ["704A07", "Antique Bronze"], ["704F50", "Ferra"], ["706555", "Coffee"], ["708090", "Slate Gray"], ["711A00", "Cedar Wood Finish"], ["71291D", "Metallic Copper"], ["714693", "Affair"], ["714AB2", "Studio"], ["715D47", "Tobacco Brown"], ["716338", "Yellow Metal"], ["716B56", "Peat"], ["716E10", "Olivetone"], ["717486", "Storm Gray"], ["718080", "Sirocco"], ["71D9E2", "Aquamarine Blue"], ["72010F", "Venetian Red"], ["724A2F", "Old Copper"], ["726D4E", "Go Ben"], ["727B89", "Raven"], ["731E8F", "Seance"], ["734A12", "Raw Umber"], ["736C9F", "Kimberly"], ["736D58", "Crocodile"], ["737829", "Crete"], ["738678", "Xanadu"], ["74640D", "Spicy Mustard"], ["747D63", "Limed Ash"], ["747D83", "Rolling Stone"], ["748881", "Blue Smoke"], ["749378", "Laurel"], ["74C365", "Mantis"], ["755A57", "Russett"], ["7563A8", "Deluge"], ["76395D", "Cosmic"], ["7666C6", "Blue Marguerite"], ["76BD17", "Lima"], ["76D7EA", "Sky Blue"], ["770F05", "Dark Burgundy"], ["771F1F", "Crown of Thorns"], ["773F1A", "Walnut"], ["776F61", "Pablo"], ["778120", "Pacifika"], ["779E86", "Oxley"], ["77DD77", "Pastel Green"], ["780109", "Japanese Maple"], ["782D19", "Mocha"], ["782F16", "Peanut"], ["78866B", "Camouflage Green"], ["788A25", "Wasabi"], ["788BBA", "Ship Cove"], ["78A39C", "Sea Nymph"], ["795D4C", "Roman Coffee"], ["796878", "Old Lavender"], ["796989", "Rum"], ["796A78", "Fedora"], ["796D62", "Sandstone"], ["79DEEC", "Spray"], ["7A013A", "Siren"], ["7A58C1", "Fuchsia Blue"], ["7A7A7A", "Boulder"], ["7A89B8", "Wild Blue Yonder"], ["7AC488", "De York"], ["7B3801", "Red Beech"], ["7B3F00", "Cinnamon"], ["7B6608", "Yukon Gold"], ["7B7874", "Tapa"], ["7B7C94", "Waterloo "], ["7B8265", "Flax Smoke"], ["7B9F80", "Amulet"], ["7BA05B", "Asparagus"], ["7C1C05", "Kenyan Copper"], ["7C7631", "Pesto"], ["7C778A", "Topaz"], ["7C7B7A", "Concord"], ["7C7B82", "Jumbo"], ["7C881A", "Trendy Green"], ["7CA1A6", "Gumbo"], ["7CB0A1", "Acapulco"], ["7CB7BB", "Neptune"], ["7D2C14", "Pueblo"], ["7DA98D", "Bay Leaf"], ["7DC8F7", "Malibu"], ["7DD8C6", "Bermuda"], ["7E3A15", "Copper Canyon"], ["7F1734", "Claret"], ["7F3A02", "Peru Tan"], ["7F626D", "Falcon"], ["7F7589", "Mobster"], ["7F76D3", "Moody Blue"], ["7FFF00", "Chartreuse"], ["7FFFD4", "Aquamarine"], ["800000", "Maroon"], ["800B47", "Rose Bud Cherry"], ["801818", "Falu Red"], ["80341F", "Red Robin"], ["803790", "Vivid Violet"], ["80461B", "Russet"], ["807E79", "Friar Gray"], ["808000", "Olive"], ["808080", "Gray"], ["80B3AE", "Gulf Stream"], ["80B3C4", "Glacier"], ["80CCEA", "Seagull"], ["81422C", "Nutmeg"], ["816E71", "Spicy Pink"], ["817377", "Empress"], ["819885", "Spanish Green"], ["826F65", "Sand Dune"], ["828685", "Gunsmoke"], ["828F72", "Battleship Gray"], ["831923", "Merlot"], ["837050", "Shadow"], ["83AA5D", "Chelsea Cucumber"], ["83D0C6", "Monte Carlo"], ["843179", "Plum"], ["84A0A0", "Granny Smith"], ["8581D9", "Chetwode Blue"], ["858470", "Bandicoot"], ["859FAF", "Bali Hai"], ["85C4CC", "Half Baked"], ["860111", "Red Devil"], ["863C3C", "Lotus"], ["86483C", "Ironstone"], ["864D1E", "Bull Shot"], ["86560A", "Rusty Nail"], ["868974", "Bitter"], ["86949F", "Regent Gray"], ["871550", "Disco"], ["87756E", "Americano"], ["877C7B", "Hurricane"], ["878D91", "Oslo Gray"], ["87AB39", "Sushi"], ["885342", "Spicy Mix"], ["886221", "Kumera"], ["888387", "Suva Gray"], ["888D65", "Avocado"], ["893456", "Camelot"], ["893843", "Solid Pink"], ["894367", "Cannon Pink"], ["897D6D", "Makara"], ["8A3324", "Burnt Umber"], ["8A73D6", "True V"], ["8A8360", "Clay Creek"], ["8A8389", "Monsoon"], ["8A8F8A", "Stack"], ["8AB9F1", "Jordy Blue"], ["8B00FF", "Electric Violet"], ["8B0723", "Monarch"], ["8B6B0B", "Corn Harvest"], ["8B8470", "Olive Haze"], ["8B847E", "Schooner"], ["8B8680", "Natural Gray"], ["8B9C90", "Mantle"], ["8B9FEE", "Portage"], ["8BA690", "Envy"], ["8BA9A5", "Cascade"], ["8BE6D8", "Riptide"], ["8C055E", "Cardinal Pink"], ["8C472F", "Mule Fawn"], ["8C5738", "Potters Clay"], ["8C6495", "Trendy Pink"], ["8D0226", "Paprika"], ["8D3D38", "Sanguine Brown"], ["8D3F3F", "Tosca"], ["8D7662", "Cement"], ["8D8974", "Granite Green"], ["8D90A1", "Manatee"], ["8DA8CC", "Polo Blue"], ["8E0000", "Red Berry"], ["8E4D1E", "Rope"], ["8E6F70", "Opium"], ["8E775E", "Domino"], ["8E8190", "Mamba"], ["8EABC1", "Nepal"], ["8F021C", "Pohutukawa"], ["8F3E33", "El Salva"], ["8F4B0E", "Korma"], ["8F8176", "Squirrel"], ["8FD6B4", "Vista Blue"], ["900020", "Burgundy"], ["901E1E", "Old Brick"], ["907874", "Hemp"], ["907B71", "Almond Frost"], ["908D39", "Sycamore"], ["92000A", "Sangria"], ["924321", "Cumin"], ["926F5B", "Beaver"], ["928573", "Stonewall"], ["928590", "Venus"], ["9370DB", "Medium Purple"], ["93CCEA", "Cornflower"], ["93DFB8", "Algae Green"], ["944747", "Copper Rust"], ["948771", "Arrowtown"], ["950015", "Scarlett"], ["956387", "Strikemaster"], ["959396", "Mountain Mist"], ["960018", "Carmine"], ["964B00", "Brown"], ["967059", "Leather"], ["9678B6", "Purple Mountain's Majesty"], ["967BB6", "Lavender Purple"], ["96A8A1", "Pewter"], ["96BBAB", "Summer Green"], ["97605D", "Au Chico"], ["9771B5", "Wisteria"], ["97CD2D", "Atlantis"], ["983D61", "Vin Rouge"], ["9874D3", "Lilac Bush"], ["98777B", "Bazaar"], ["98811B", "Hacienda"], ["988D77", "Pale Oyster"], ["98FF98", "Mint Green"], ["990066", "Fresh Eggplant"], ["991199", "Violet Eggplant"], ["991613", "Tamarillo"], ["991B07", "Totem Pole"], ["996666", "Copper Rose"], ["9966CC", "Amethyst"], ["997A8D", "Mountbatten Pink"], ["9999CC", "Blue Bell"], ["9A3820", "Prairie Sand"], ["9A6E61", "Toast"], ["9A9577", "Gurkha"], ["9AB973", "Olivine"], ["9AC2B8", "Shadow Green"], ["9B4703", "Oregon"], ["9B9E8F", "Lemon Grass"], ["9C3336", "Stiletto"], ["9D5616", "Hawaiian Tan"], ["9DACB7", "Gull Gray"], ["9DC209", "Pistachio"], ["9DE093", "Granny Smith Apple"], ["9DE5FF", "Anakiwa"], ["9E5302", "Chelsea Gem"], ["9E5B40", "Sepia Skin"], ["9EA587", "Sage"], ["9EA91F", "Citron"], ["9EB1CD", "Rock Blue"], ["9EDEE0", "Morning Glory"], ["9F381D", "Cognac"], ["9F821C", "Reef Gold"], ["9F9F9C", "Star Dust"], ["9FA0B1", "Santas Gray"], ["9FD7D3", "Sinbad"], ["9FDD8C", "Feijoa"], ["A02712", "Tabasco"], ["A1750D", "Buttered Rum"], ["A1ADB5", "Hit Gray"], ["A1C50A", "Citrus"], ["A1DAD7", "Aqua Island"], ["A1E9DE", "Water Leaf"], ["A2006D", "Flirt"], ["A23B6C", "Rouge"], ["A26645", "Cape Palliser"], ["A2AAB3", "Gray Chateau"], ["A2AEAB", "Edward"], ["A3807B", "Pharlap"], ["A397B4", "Amethyst Smoke"], ["A3E3ED", "Blizzard Blue"], ["A4A49D", "Delta"], ["A4A6D3", "Wistful"], ["A4AF6E", "Green Smoke"], ["A50B5E", "Jazzberry Jam"], ["A59B91", "Zorba"], ["A5CB0C", "Bahia"], ["A62F20", "Roof Terracotta"], ["A65529", "Paarl"], ["A68B5B", "Barley Corn"], ["A69279", "Donkey Brown"], ["A6A29A", "Dawn"], ["A72525", "Mexican Red"], ["A7882C", "Luxor Gold"], ["A85307", "Rich Gold"], ["A86515", "Reno Sand"], ["A86B6B", "Coral Tree"], ["A8989B", "Dusty Gray"], ["A899E6", "Dull Lavender"], ["A8A589", "Tallow"], ["A8AE9C", "Bud"], ["A8AF8E", "Locust"], ["A8BD9F", "Norway"], ["A8E3BD", "Chinook"], ["A9A491", "Gray Olive"], ["A9ACB6", "Aluminium"], ["A9B2C3", "Cadet Blue"], ["A9B497", "Schist"], ["A9BDBF", "Tower Gray"], ["A9BEF2", "Perano"], ["A9C6C2", "Opal"], ["AA375A", "Night Shadz"], ["AA4203", "Fire"], ["AA8B5B", "Muesli"], ["AA8D6F", "Sandal"], ["AAA5A9", "Shady Lady"], ["AAA9CD", "Logan"], ["AAABB7", "Spun Pearl"], ["AAD6E6", "Regent St Blue"], ["AAF0D1", "Magic Mint"], ["AB0563", "Lipstick"], ["AB3472", "Royal Heath"], ["AB917A", "Sandrift"], ["ABA0D9", "Cold Purple"], ["ABA196", "Bronco"], ["AC8A56", "Limed Oak"], ["AC91CE", "East Side"], ["AC9E22", "Lemon Ginger"], ["ACA494", "Napa"], ["ACA586", "Hillary"], ["ACA59F", "Cloudy"], ["ACACAC", "Silver Chalice"], ["ACB78E", "Swamp Green"], ["ACCBB1", "Spring Rain"], ["ACDD4D", "Conifer"], ["ACE1AF", "Celadon"], ["AD781B", "Mandalay"], ["ADBED1", "Casper"], ["ADDFAD", "Moss Green"], ["ADE6C4", "Padua"], ["ADFF2F", "Green Yellow"], ["AE4560", "Hippie Pink"], ["AE6020", "Desert"], ["AE809E", "Bouquet"], ["AF4035", "Medium Carmine"], ["AF4D43", "Apple Blossom"], ["AF593E", "Brown Rust"], ["AF8751", "Driftwood"], ["AF8F2C", "Alpine"], ["AF9F1C", "Lucky"], ["AFA09E", "Martini"], ["AFB1B8", "Bombay"], ["AFBDD9", "Pigeon Post"], ["B04C6A", "Cadillac"], ["B05D54", "Matrix"], ["B05E81", "Tapestry"], ["B06608", "Mai Tai"], ["B09A95", "Del Rio"], ["B0E0E6", "Powder Blue"], ["B0E313", "Inch Worm"], ["B10000", "Bright Red"], ["B14A0B", "Vesuvius"], ["B1610B", "Pumpkin Skin"], ["B16D52", "Santa Fe"], ["B19461", "Teak"], ["B1E2C1", "Fringy Flower"], ["B1F4E7", "Ice Cold"], ["B20931", "Shiraz"], ["B2A1EA", "Biloba Flower"], ["B32D29", "Tall Poppy"], ["B35213", "Fiery Orange"], ["B38007", "Hot Toddy"], ["B3AF95", "Taupe Gray"], ["B3C110", "La Rioja"], ["B43332", "Well Read"], ["B44668", "Blush"], ["B4CFD3", "Jungle Mist"], ["B57281", "Turkish Rose"], ["B57EDC", "Lavender"], ["B5A27F", "Mongoose"], ["B5B35C", "Olive Green"], ["B5D2CE", "Jet Stream"], ["B5ECDF", "Cruise"], ["B6316C", "Hibiscus"], ["B69D98", "Thatch"], ["B6B095", "Heathered Gray"], ["B6BAA4", "Eagle"], ["B6D1EA", "Spindle"], ["B6D3BF", "Gum Leaf"], ["B7410E", "Rust"], ["B78E5C", "Muddy Waters"], ["B7A214", "Sahara"], ["B7A458", "Husk"], ["B7B1B1", "Nobel"], ["B7C3D0", "Heather"], ["B7F0BE", "Madang"], ["B81104", "Milano Red"], ["B87333", "Copper"], ["B8B56A", "Gimblet"], ["B8C1B1", "Green Spring"], ["B8C25D", "Celery"], ["B8E0F9", "Sail"], ["B94E48", "Chestnut"], ["B95140", "Crail"], ["B98D28", "Marigold"], ["B9C46A", "Wild Willow"], ["B9C8AC", "Rainee"], ["BA0101", "Guardsman Red"], ["BA450C", "Rock Spray"], ["BA6F1E", "Bourbon"], ["BA7F03", "Pirate Gold"], ["BAB1A2", "Nomad"], ["BAC7C9", "Submarine"], ["BAEEF9", "Charlotte"], ["BB3385", "Medium Red Violet"], ["BB8983", "Brandy Rose"], ["BBD009", "Rio Grande"], ["BBD7C1", "Surf"], ["BCC9C2", "Powder Ash"], ["BD5E2E", "Tuscany"], ["BD978E", "Quicksand"], ["BDB1A8", "Silk"], ["BDB2A1", "Malta"], ["BDB3C7", "Chatelle"], ["BDBBD7", "Lavender Gray"], ["BDBDC6", "French Gray"], ["BDC8B3", "Clay Ash"], ["BDC9CE", "Loblolly"], ["BDEDFD", "French Pass"], ["BEA6C3", "London Hue"], ["BEB5B7", "Pink Swan"], ["BEDE0D", "Fuego"], ["BF5500", "Rose of Sharon"], ["BFB8B0", "Tide"], ["BFBED8", "Blue Haze"], ["BFC1C2", "Silver Sand"], ["BFC921", "Key Lime Pie"], ["BFDBE2", "Ziggurat"], ["BFFF00", "Lime"], ["C02B18", "Thunderbird"], ["C04737", "Mojo"], ["C08081", "Old Rose"], ["C0C0C0", "Silver"], ["C0D3B9", "Pale Leaf"], ["C0D8B6", "Pixie Green"], ["C1440E", "Tia Maria"], ["C154C1", "Fuchsia Pink"], ["C1A004", "Buddha Gold"], ["C1B7A4", "Bison Hide"], ["C1BAB0", "Tea"], ["C1BECD", "Gray Suit"], ["C1D7B0", "Sprout"], ["C1F07C", "Sulu"], ["C26B03", "Indochine"], ["C2955D", "Twine"], ["C2BDB6", "Cotton Seed"], ["C2CAC4", "Pumice"], ["C2E8E5", "Jagged Ice"], ["C32148", "Maroon Flush"], ["C3B091", "Indian Khaki"], ["C3BFC1", "Pale Slate"], ["C3C3BD", "Gray Nickel"], ["C3CDE6", "Periwinkle Gray"], ["C3D1D1", "Tiara"], ["C3DDF9", "Tropical Blue"], ["C41E3A", "Cardinal"], ["C45655", "Fuzzy Wuzzy Brown"], ["C45719", "Orange Roughy"], ["C4C4BC", "Mist Gray"], ["C4D0B0", "Coriander"], ["C4F4EB", "Mint Tulip"], ["C54B8C", "Mulberry"], ["C59922", "Nugget"], ["C5994B", "Tussock"], ["C5DBCA", "Sea Mist"], ["C5E17A", "Yellow Green"], ["C62D42", "Brick Red"], ["C6726B", "Contessa"], ["C69191", "Oriental Pink"], ["C6A84B", "Roti"], ["C6C3B5", "Ash"], ["C6C8BD", "Kangaroo"], ["C6E610", "Las Palmas"], ["C7031E", "Monza"], ["C71585", "Red Violet"], ["C7BCA2", "Coral Reef"], ["C7C1FF", "Melrose"], ["C7C4BF", "Cloud"], ["C7C9D5", "Ghost"], ["C7CD90", "Pine Glade"], ["C7DDE5", "Botticelli"], ["C88A65", "Antique Brass"], ["C8A2C8", "Lilac"], ["C8A528", "Hokey Pokey"], ["C8AABF", "Lily"], ["C8B568", "Laser"], ["C8E3D7", "Edgewater"], ["C96323", "Piper"], ["C99415", "Pizza"], ["C9A0DC", "Light Wisteria"], ["C9B29B", "Rodeo Dust"], ["C9B35B", "Sundance"], ["C9B93B", "Earls Green"], ["C9C0BB", "Silver Rust"], ["C9D9D2", "Conch"], ["C9FFA2", "Reef"], ["C9FFE5", "Aero Blue"], ["CA3435", "Flush Mahogany"], ["CABB48", "Turmeric"], ["CADCD4", "Paris White"], ["CAE00D", "Bitter Lemon"], ["CAE6DA", "Skeptic"], ["CB8FA9", "Viola"], ["CBCAB6", "Foggy Gray"], ["CBD3B0", "Green Mist"], ["CBDBD6", "Nebula"], ["CC3333", "Persian Red"], ["CC5500", "Burnt Orange"], ["CC7722", "Ochre"], ["CC8899", "Puce"], ["CCCAA8", "Thistle Green"], ["CCCCFF", "Periwinkle"], ["CCFF00", "Electric Lime"], ["CD5700", "Tenn"], ["CD5C5C", "Chestnut Rose"], ["CD8429", "Brandy Punch"], ["CDF4FF", "Onahau"], ["CEB98F", "Sorrell Brown"], ["CEBABA", "Cold Turkey"], ["CEC291", "Yuma"], ["CEC7A7", "Chino"], ["CFA39D", "Eunry"], ["CFB53B", "Old Gold"], ["CFDCCF", "Tasman"], ["CFE5D2", "Surf Crest"], ["CFF9F3", "Humming Bird"], ["CFFAF4", "Scandal"], ["D05F04", "Red Stage"], ["D06DA1", "Hopbush"], ["D07D12", "Meteor"], ["D0BEF8", "Perfume"], ["D0C0E5", "Prelude"], ["D0F0C0", "Tea Green"], ["D18F1B", "Geebung"], ["D1BEA8", "Vanilla"], ["D1C6B4", "Soft Amber"], ["D1D2CA", "Celeste"], ["D1D2DD", "Mischka"], ["D1E231", "Pear"], ["D2691E", "Hot Cinnamon"], ["D27D46", "Raw Sienna"], ["D29EAA", "Careys Pink"], ["D2B48C", "Tan"], ["D2DA97", "Deco"], ["D2F6DE", "Blue Romance"], ["D2F8B0", "Gossip"], ["D3CBBA", "Sisal"], ["D3CDC5", "Swirl"], ["D47494", "Charm"], ["D4B6AF", "Clam Shell"], ["D4BF8D", "Straw"], ["D4C4A8", "Akaroa"], ["D4CD16", "Bird Flower"], ["D4D7D9", "Iron"], ["D4DFE2", "Geyser"], ["D4E2FC", "Hawkes Blue"], ["D54600", "Grenadier"], ["D591A4", "Can Can"], ["D59A6F", "Whiskey"], ["D5D195", "Winter Hazel"], ["D5F6E3", "Granny Apple"], ["D69188", "My Pink"], ["D6C562", "Tacha"], ["D6CEF6", "Moon Raker"], ["D6D6D1", "Quill Gray"], ["D6FFDB", "Snowy Mint"], ["D7837F", "New York Pink"], ["D7C498", "Pavlova"], ["D7D0FF", "Fog"], ["D84437", "Valencia"], ["D87C63", "Japonica"], ["D8BFD8", "Thistle"], ["D8C2D5", "Maverick"], ["D8FCFA", "Foam"], ["D94972", "Cabaret"], ["D99376", "Burning Sand"], ["D9B99B", "Cameo"], ["D9D6CF", "Timberwolf"], ["D9DCC1", "Tana"], ["D9E4F5", "Link Water"], ["D9F7FF", "Mabel"], ["DA3287", "Cerise"], ["DA5B38", "Flame Pea"], ["DA6304", "Bamboo"], ["DA6A41", "Red Damask"], ["DA70D6", "Orchid"], ["DA8A67", "Copperfield"], ["DAA520", "Golden Grass"], ["DAECD6", "Zanah"], ["DAF4F0", "Iceberg"], ["DAFAFF", "Oyster Bay"], ["DB5079", "Cranberry"], ["DB9690", "Petite Orchid"], ["DB995E", "Di Serria"], ["DBDBDB", "Alto"], ["DBFFF8", "Frosted Mint"], ["DC143C", "Crimson"], ["DC4333", "Punch"], ["DCB20C", "Galliano"], ["DCB4BC", "Blossom"], ["DCD747", "Wattle"], ["DCD9D2", "Westar"], ["DCDDCC", "Moon Mist"], ["DCEDB4", "Caper"], ["DCF0EA", "Swans Down"], ["DDD6D5", "Swiss Coffee"], ["DDF9F1", "White Ice"], ["DE3163", "Cerise Red"], ["DE6360", "Roman"], ["DEA681", "Tumbleweed"], ["DEBA13", "Gold Tips"], ["DEC196", "Brandy"], ["DECBC6", "Wafer"], ["DED4A4", "Sapling"], ["DED717", "Barberry"], ["DEE5C0", "Beryl Green"], ["DEF5FF", "Pattens Blue"], ["DF73FF", "Heliotrope"], ["DFBE6F", "Apache"], ["DFCD6F", "Chenin"], ["DFCFDB", "Lola"], ["DFECDA", "Willow Brook"], ["DFFF00", "Chartreuse Yellow"], ["E0B0FF", "Mauve"], ["E0B646", "Anzac"], ["E0B974", "Harvest Gold"], ["E0C095", "Calico"], ["E0FFFF", "Baby Blue"], ["E16865", "Sunglo"], ["E1BC64", "Equator"], ["E1C0C8", "Pink Flare"], ["E1E6D6", "Periglacial Blue"], ["E1EAD4", "Kidnapper"], ["E1F6E8", "Tara"], ["E25465", "Mandy"], ["E2725B", "Terracotta"], ["E28913", "Golden Bell"], ["E292C0", "Shocking"], ["E29418", "Dixie"], ["E29CD2", "Light Orchid"], ["E2D8ED", "Snuff"], ["E2EBED", "Mystic"], ["E2F3EC", "Apple Green"], ["E30B5C", "Razzmatazz"], ["E32636", "Alizarin Crimson"], ["E34234", "Cinnabar"], ["E3BEBE", "Cavern Pink"], ["E3F5E1", "Peppermint"], ["E3F988", "Mindaro"], ["E47698", "Deep Blush"], ["E49B0F", "Gamboge"], ["E4C2D5", "Melanie"], ["E4CFDE", "Twilight"], ["E4D1C0", "Bone"], ["E4D422", "Sunflower"], ["E4D5B7", "Grain Brown"], ["E4D69B", "Zombie"], ["E4F6E7", "Frostee"], ["E4FFD1", "Snow Flurry"], ["E52B50", "Amaranth"], ["E5841B", "Zest"], ["E5CCC9", "Dust Storm"], ["E5D7BD", "Stark White"], ["E5D8AF", "Hampton"], ["E5E0E1", "Bon Jour"], ["E5E5E5", "Mercury"], ["E5F9F6", "Polar"], ["E64E03", "Trinidad"], ["E6BE8A", "Gold Sand"], ["E6BEA5", "Cashmere"], ["E6D7B9", "Double Spanish White"], ["E6E4D4", "Satin Linen"], ["E6F2EA", "Harp"], ["E6F8F3", "Off Green"], ["E6FFE9", "Hint of Green"], ["E6FFFF", "Tranquil"], ["E77200", "Mango Tango"], ["E7730A", "Christine"], ["E79F8C", "Tonys Pink"], ["E79FC4", "Kobi"], ["E7BCB4", "Rose Fog"], ["E7BF05", "Corn"], ["E7CD8C", "Putty"], ["E7ECE6", "Gray Nurse"], ["E7F8FF", "Lily White"], ["E7FEFF", "Bubbles"], ["E89928", "Fire Bush"], ["E8B9B3", "Shilo"], ["E8E0D5", "Pearl Bush"], ["E8EBE0", "Green White"], ["E8F1D4", "Chrome White"], ["E8F2EB", "Gin"], ["E8F5F2", "Aqua Squeeze"], ["E96E00", "Clementine"], ["E97451", "Burnt Sienna"], ["E97C07", "Tahiti Gold"], ["E9CECD", "Oyster Pink"], ["E9D75A", "Confetti"], ["E9E3E3", "Ebb"], ["E9F8ED", "Ottoman"], ["E9FFFD", "Clear Day"], ["EA88A8", "Carissma"], ["EAAE69", "Porsche"], ["EAB33B", "Tulip Tree"], ["EAC674", "Rob Roy"], ["EADAB8", "Raffia"], ["EAE8D4", "White Rock"], ["EAF6EE", "Panache"], ["EAF6FF", "Solitude"], ["EAF9F5", "Aqua Spring"], ["EAFFFE", "Dew"], ["EB9373", "Apricot"], ["EBC2AF", "Zinnwaldite"], ["ECA927", "Fuel Yellow"], ["ECC54E", "Ronchi"], ["ECC7EE", "French Lilac"], ["ECCDB9", "Just Right"], ["ECE090", "Wild Rice"], ["ECEBBD", "Fall Green"], ["ECEBCE", "Aths Special"], ["ECF245", "Starship"], ["ED0A3F", "Red Ribbon"], ["ED7A1C", "Tango"], ["ED9121", "Carrot Orange"], ["ED989E", "Sea Pink"], ["EDB381", "Tacao"], ["EDC9AF", "Desert Sand"], ["EDCDAB", "Pancho"], ["EDDCB1", "Chamois"], ["EDEA99", "Primrose"], ["EDF5DD", "Frost"], ["EDF5F5", "Aqua Haze"], ["EDF6FF", "Zumthor"], ["EDF9F1", "Narvik"], ["EDFC84", "Honeysuckle"], ["EE82EE", "Lavender Magenta"], ["EEC1BE", "Beauty Bush"], ["EED794", "Chalky"], ["EED9C4", "Almond"], ["EEDC82", "Flax"], ["EEDEDA", "Bizarre"], ["EEE3AD", "Double Colonial White"], ["EEEEE8", "Cararra"], ["EEEF78", "Manz"], ["EEF0C8", "Tahuna Sands"], ["EEF0F3", "Athens Gray"], ["EEF3C3", "Tusk"], ["EEF4DE", "Loafer"], ["EEF6F7", "Catskill White"], ["EEFDFF", "Twilight Blue"], ["EEFF9A", "Jonquil"], ["EEFFE2", "Rice Flower"], ["EF863F", "Jaffa"], ["EFEFEF", "Gallery"], ["EFF2F3", "Porcelain"], ["F091A9", "Mauvelous"], ["F0D52D", "Golden Dream"], ["F0DB7D", "Golden Sand"], ["F0DC82", "Buff"], ["F0E2EC", "Prim"], ["F0E68C", "Khaki"], ["F0EEFD", "Selago"], ["F0EEFF", "Titan White"], ["F0F8FF", "Alice Blue"], ["F0FCEA", "Feta"], ["F18200", "Gold Drop"], ["F19BAB", "Wewak"], ["F1E788", "Sahara Sand"], ["F1E9D2", "Parchment"], ["F1E9FF", "Blue Chalk"], ["F1EEC1", "Mint Julep"], ["F1F1F1", "Seashell"], ["F1F7F2", "Saltpan"], ["F1FFAD", "Tidal"], ["F1FFC8", "Chiffon"], ["F2552A", "Flamingo"], ["F28500", "Tangerine"], ["F2C3B2", "Mandys Pink"], ["F2F2F2", "Concrete"], ["F2FAFA", "Black Squeeze"], ["F34723", "Pomegranate"], ["F3AD16", "Buttercup"], ["F3D69D", "New Orleans"], ["F3D9DF", "Vanilla Ice"], ["F3E7BB", "Sidecar"], ["F3E9E5", "Dawn Pink"], ["F3EDCF", "Wheatfield"], ["F3FB62", "Canary"], ["F3FBD4", "Orinoco"], ["F3FFD8", "Carla"], ["F400A1", "Hollywood Cerise"], ["F4A460", "Sandy brown"], ["F4C430", "Saffron"], ["F4D81C", "Ripe Lemon"], ["F4EBD3", "Janna"], ["F4F2EE", "Pampas"], ["F4F4F4", "Wild Sand"], ["F4F8FF", "Zircon"], ["F57584", "Froly"], ["F5C85C", "Cream Can"], ["F5C999", "Manhattan"], ["F5D5A0", "Maize"], ["F5DEB3", "Wheat"], ["F5E7A2", "Sandwisp"], ["F5E7E2", "Pot Pourri"], ["F5E9D3", "Albescent White"], ["F5EDEF", "Soft Peach"], ["F5F3E5", "Ecru White"], ["F5F5DC", "Beige"], ["F5FB3D", "Golden Fizz"], ["F5FFBE", "Australian Mint"], ["F64A8A", "French Rose"], ["F653A6", "Brilliant Rose"], ["F6A4C9", "Illusion"], ["F6F0E6", "Merino"], ["F6F7F7", "Black Haze"], ["F6FFDC", "Spring Sun"], ["F7468A", "Violet Red"], ["F77703", "Chilean Fire"], ["F77FBE", "Persian Pink"], ["F7B668", "Rajah"], ["F7C8DA", "Azalea"], ["F7DBE6", "We Peep"], ["F7F2E1", "Quarter Spanish White"], ["F7F5FA", "Whisper"], ["F7FAF7", "Snow Drift"], ["F8B853", "Casablanca"], ["F8C3DF", "Chantilly"], ["F8D9E9", "Cherub"], ["F8DB9D", "Marzipan"], ["F8DD5C", "Energy Yellow"], ["F8E4BF", "Givry"], ["F8F0E8", "White Linen"], ["F8F4FF", "Magnolia"], ["F8F6F1", "Spring Wood"], ["F8F7DC", "Coconut Cream"], ["F8F7FC", "White Lilac"], ["F8F8F7", "Desert Storm"], ["F8F99C", "Texas"], ["F8FACD", "Corn Field"], ["F8FDD3", "Mimosa"], ["F95A61", "Carnation"], ["F9BF58", "Saffron Mango"], ["F9E0ED", "Carousel Pink"], ["F9E4BC", "Dairy Cream"], ["F9E663", "Portica"], ["F9E6F4", "Underage Pink"], ["F9EAF3", "Amour"], ["F9F8E4", "Rum Swizzle"], ["F9FF8B", "Dolly"], ["F9FFF6", "Sugar Cane"], ["FA7814", "Ecstasy"], ["FA9D5A", "Tan Hide"], ["FAD3A2", "Corvette"], ["FADFAD", "Peach Yellow"], ["FAE600", "Turbo"], ["FAEAB9", "Astra"], ["FAECCC", "Champagne"], ["FAF0E6", "Linen"], ["FAF3F0", "Fantasy"], ["FAF7D6", "Citrine White"], ["FAFAFA", "Alabaster"], ["FAFDE4", "Hint of Yellow"], ["FAFFA4", "Milan"], ["FB607F", "Brink Pink"], ["FB8989", "Geraldine"], ["FBA0E3", "Lavender Rose"], ["FBA129", "Sea Buckthorn"], ["FBAC13", "Sun"], ["FBAED2", "Lavender Pink"], ["FBB2A3", "Rose Bud"], ["FBBEDA", "Cupid"], ["FBCCE7", "Classic Rose"], ["FBCEB1", "Apricot Peach"], ["FBE7B2", "Banana Mania"], ["FBE870", "Marigold Yellow"], ["FBE96C", "Festival"], ["FBEA8C", "Sweet Corn"], ["FBEC5D", "Candy Corn"], ["FBF9F9", "Hint of Red"], ["FBFFBA", "Shalimar"], ["FC0FC0", "Shocking Pink"], ["FC80A5", "Tickle Me Pink"], ["FC9C1D", "Tree Poppy"], ["FCC01E", "Lightning Yellow"], ["FCD667", "Goldenrod"], ["FCD917", "Candlelight"], ["FCDA98", "Cherokee"], ["FCF4D0", "Double Pearl Lusta"], ["FCF4DC", "Pearl Lusta"], ["FCF8F7", "Vista White"], ["FCFBF3", "Bianca"], ["FCFEDA", "Moon Glow"], ["FCFFE7", "China Ivory"], ["FCFFF9", "Ceramic"], ["FD0E35", "Torch Red"], ["FD5B78", "Wild Watermelon"], ["FD7B33", "Crusta"], ["FD7C07", "Sorbus"], ["FD9FA2", "Sweet Pink"], ["FDD5B1", "Light Apricot"], ["FDD7E4", "Pig Pink"], ["FDE1DC", "Cinderella"], ["FDE295", "Golden Glow"], ["FDE910", "Lemon"], ["FDF5E6", "Old Lace"], ["FDF6D3", "Half Colonial White"], ["FDF7AD", "Drover"], ["FDFEB8", "Pale Prim"], ["FDFFD5", "Cumulus"], ["FE28A2", "Persian Rose"], ["FE4C40", "Sunset Orange"], ["FE6F5E", "Bittersweet"], ["FE9D04", "California"], ["FEA904", "Yellow Sea"], ["FEBAAD", "Melon"], ["FED33C", "Bright Sun"], ["FED85D", "Dandelion"], ["FEDB8D", "Salomie"], ["FEE5AC", "Cape Honey"], ["FEEBF3", "Remy"], ["FEEFCE", "Oasis"], ["FEF0EC", "Bridesmaid"], ["FEF2C7", "Beeswax"], ["FEF3D8", "Bleach White"], ["FEF4CC", "Pipi"], ["FEF4DB", "Half Spanish White"], ["FEF4F8", "Wisp Pink"], ["FEF5F1", "Provincial Pink"], ["FEF7DE", "Half Dutch White"], ["FEF8E2", "Solitaire"], ["FEF8FF", "White Pointer"], ["FEF9E3", "Off Yellow"], ["FEFCED", "Orange White"], ["FF0000", "Red"], ["FF007F", "Rose"], ["FF00CC", "Purple Pizzazz"], ["FF00FF", "Magenta / Fuchsia"], ["FF2400", "Scarlet"], ["FF3399", "Wild Strawberry"], ["FF33CC", "Razzle Dazzle Rose"], ["FF355E", "Radical Red"], ["FF3F34", "Red Orange"], ["FF4040", "Coral Red"], ["FF4D00", "Vermilion"], ["FF4F00", "International Orange"], ["FF6037", "Outrageous Orange"], ["FF6600", "Blaze Orange"], ["FF66FF", "Pink Flamingo"], ["FF681F", "Orange"], ["FF69B4", "Hot Pink"], ["FF6B53", "Persimmon"], ["FF6FFF", "Blush Pink"], ["FF7034", "Burning Orange"], ["FF7518", "Pumpkin"], ["FF7D07", "Flamenco"], ["FF7F00", "Flush Orange"], ["FF7F50", "Coral"], ["FF8C69", "Salmon"], ["FF9000", "Pizazz"], ["FF910F", "West Side"], ["FF91A4", "Pink Salmon"], ["FF9933", "Neon Carrot"], ["FF9966", "Atomic Tangerine"], ["FF9980", "Vivid Tangerine"], ["FF9E2C", "Sunshade"], ["FFA000", "Orange Peel"], ["FFA194", "Mona Lisa"], ["FFA500", "Web Orange"], ["FFA6C9", "Carnation Pink"], ["FFAB81", "Hit Pink"], ["FFAE42", "Yellow Orange"], ["FFB0AC", "Cornflower Lilac"], ["FFB1B3", "Sundown"], ["FFB31F", "My Sin"], ["FFB555", "Texas Rose"], ["FFB7D5", "Cotton Candy"], ["FFB97B", "Macaroni and Cheese"], ["FFBA00", "Selective Yellow"], ["FFBD5F", "Koromiko"], ["FFBF00", "Amber"], ["FFC0A8", "Wax Flower"], ["FFC0CB", "Pink"], ["FFC3C0", "Your Pink"], ["FFC901", "Supernova"], ["FFCBA4", "Flesh"], ["FFCC33", "Sunglow"], ["FFCC5C", "Golden Tainoi"], ["FFCC99", "Peach Orange"], ["FFCD8C", "Chardonnay"], ["FFD1DC", "Pastel Pink"], ["FFD2B7", "Romantic"], ["FFD38C", "Grandis"], ["FFD700", "Gold"], ["FFD800", "School bus Yellow"], ["FFD8D9", "Cosmos"], ["FFDB58", "Mustard"], ["FFDCD6", "Peach Schnapps"], ["FFDDAF", "Caramel"], ["FFDDCD", "Tuft Bush"], ["FFDDCF", "Watusi"], ["FFDDF4", "Pink Lace"], ["FFDEAD", "Navajo White"], ["FFDEB3", "Frangipani"], ["FFE1DF", "Pippin"], ["FFE1F2", "Pale Rose"], ["FFE2C5", "Negroni"], ["FFE5A0", "Cream Brulee"], ["FFE5B4", "Peach"], ["FFE6C7", "Tequila"], ["FFE772", "Kournikova"], ["FFEAC8", "Sandy Beach"], ["FFEAD4", "Karry"], ["FFEC13", "Broom"], ["FFEDBC", "Colonial White"], ["FFEED8", "Derby"], ["FFEFA1", "Vis Vis"], ["FFEFC1", "Egg White"], ["FFEFD5", "Papaya Whip"], ["FFEFEC", "Fair Pink"], ["FFF0DB", "Peach Cream"], ["FFF0F5", "Lavender blush"], ["FFF14F", "Gorse"], ["FFF1B5", "Buttermilk"], ["FFF1D8", "Pink Lady"], ["FFF1EE", "Forget Me Not"], ["FFF1F9", "Tutu"], ["FFF39D", "Picasso"], ["FFF3F1", "Chardon"], ["FFF46E", "Paris Daisy"], ["FFF4CE", "Barley White"], ["FFF4DD", "Egg Sour"], ["FFF4E0", "Sazerac"], ["FFF4E8", "Serenade"], ["FFF4F3", "Chablis"], ["FFF5EE", "Seashell Peach"], ["FFF5F3", "Sauvignon"], ["FFF6D4", "Milk Punch"], ["FFF6DF", "Varden"], ["FFF6F5", "Rose White"], ["FFF8D1", "Baja White"], ["FFF9E2", "Gin Fizz"], ["FFF9E6", "Early Dawn"], ["FFFACD", "Lemon Chiffon"], ["FFFAF4", "Bridal Heath"], ["FFFBDC", "Scotch Mist"], ["FFFBF9", "Soapstone"], ["FFFC99", "Witch Haze"], ["FFFCEA", "Buttery White"], ["FFFCEE", "Island Spice"], ["FFFDD0", "Cream"], ["FFFDE6", "Chilean Heath"], ["FFFDE8", "Travertine"], ["FFFDF3", "Orchid White"], ["FFFDF4", "Quarter Pearl Lusta"], ["FFFEE1", "Half and Half"], ["FFFEEC", "Apricot White"], ["FFFEF0", "Rice Cake"], ["FFFEF6", "Black White"], ["FFFEFD", "Romance"], ["FFFF00", "Yellow"], ["FFFF66", "Laser Lemon"], ["FFFF99", "Pale Canary"], ["FFFFB4", "Portafino"], ["FFFFF0", "Ivory"], ["FFFFFF", "White"], ["acc2d9", "cloudy blue"], ["56ae57", "dark pastel green"], ["b2996e", "dust"], ["a8ff04", "electric lime"], ["69d84f", "fresh green"], ["894585", "light eggplant"], ["70b23f", "nasty green"], ["d4ffff", "really light blue"], ["65ab7c", "tea"], ["952e8f", "warm purple"], ["fcfc81", "yellowish tan"], ["a5a391", "cement"], ["388004", "dark grass green"], ["4c9085", "dusty teal"], ["5e9b8a", "grey teal"], ["efb435", "macaroni and cheese"], ["d99b82", "pinkish tan"], ["0a5f38", "spruce"], ["0c06f7", "strong blue"], ["61de2a", "toxic green"], ["3778bf", "windows blue"], ["2242c7", "blue blue"], ["533cc6", "blue with a hint of purple"], ["9bb53c", "booger"], ["05ffa6", "bright sea green"], ["1f6357", "dark green blue"], ["017374", "deep turquoise"], ["0cb577", "green teal"], ["ff0789", "strong pink"], ["afa88b", "bland"], ["08787f", "deep aqua"], ["dd85d7", "lavender pink"], ["a6c875", "light moss green"], ["a7ffb5", "light seafoam green"], ["c2b709", "olive yellow"], ["e78ea5", "pig pink"], ["966ebd", "deep lilac"], ["ccad60", "desert"], ["ac86a8", "dusty lavender"], ["947e94", "purpley grey"], ["983fb2", "purply"], ["ff63e9", "candy pink"], ["b2fba5", "light pastel green"], ["63b365", "boring green"], ["8ee53f", "kiwi green"], ["b7e1a1", "light grey green"], ["ff6f52", "orange pink"], ["bdf8a3", "tea green"], ["d3b683", "very light brown"], ["fffcc4", "egg shell"], ["430541", "eggplant purple"], ["ffb2d0", "powder pink"], ["997570", "reddish grey"], ["ad900d", "baby shit brown"], ["c48efd", "liliac"], ["507b9c", "stormy blue"], ["7d7103", "ugly brown"], ["fffd78", "custard"], ["da467d", "darkish pink"], ["410200", "deep brown"], ["c9d179", "greenish beige"], ["fffa86", "manilla"], ["5684ae", "off blue"], ["6b7c85", "battleship grey"], ["6f6c0a", "browny green"], ["7e4071", "bruise"], ["009337", "kelley green"], ["d0e429", "sickly yellow"], ["fff917", "sunny yellow"], ["1d5dec", "azul"], ["054907", "darkgreen"], ["b5ce08", "green/yellow"], ["8fb67b", "lichen"], ["c8ffb0", "light light green"], ["fdde6c", "pale gold"], ["ffdf22", "sun yellow"], ["a9be70", "tan green"], ["6832e3", "burple"], ["fdb147", "butterscotch"], ["c7ac7d", "toupe"], ["fff39a", "dark cream"], ["850e04", "indian red"], ["efc0fe", "light lavendar"], ["40fd14", "poison green"], ["b6c406", "baby puke green"], ["9dff00", "bright yellow green"], ["3c4142", "charcoal grey"], ["f2ab15", "squash"], ["ac4f06", "cinnamon"], ["c4fe82", "light pea green"], ["2cfa1f", "radioactive green"], ["9a6200", "raw sienna"], ["ca9bf7", "baby purple"], ["875f42", "cocoa"], ["3a2efe", "light royal blue"], ["fd8d49", "orangeish"], ["8b3103", "rust brown"], ["cba560", "sand brown"], ["698339", "swamp"], ["0cdc73", "tealish green"], ["b75203", "burnt siena"], ["7f8f4e", "camo"], ["26538d", "dusk blue"], ["63a950", "fern"], ["c87f89", "old rose"], ["b1fc99", "pale light green"], ["ff9a8a", "peachy pink"], ["f6688e", "rosy pink"], ["76fda8", "light bluish green"], ["53fe5c", "light bright green"], ["4efd54", "light neon green"], ["a0febf", "light seafoam"], ["7bf2da", "tiffany blue"], ["bcf5a6", "washed out green"], ["ca6b02", "browny orange"], ["107ab0", "nice blue"], ["2138ab", "sapphire"], ["719f91", "greyish teal"], ["fdb915", "orangey yellow"], ["fefcaf", "parchment"], ["fcf679", "straw"], ["1d0200", "very dark brown"], ["cb6843", "terracota"], ["31668a", "ugly blue"], ["247afd", "clear blue"], ["ffffb6", "creme"], ["90fda9", "foam green"], ["86a17d", "grey/green"], ["fddc5c", "light gold"], ["78d1b6", "seafoam blue"], ["13bbaf", "topaz"], ["fb5ffc", "violet pink"], ["20f986", "wintergreen"], ["ffe36e", "yellow tan"], ["9d0759", "dark fuchsia"], ["3a18b1", "indigo blue"], ["c2ff89", "light yellowish green"], ["d767ad", "pale magenta"], ["720058", "rich purple"], ["ffda03", "sunflower yellow"], ["01c08d", "green/blue"], ["ac7434", "leather"], ["014600", "racing green"], ["9900fa", "vivid purple"], ["02066f", "dark royal blue"], ["8e7618", "hazel"], ["d1768f", "muted pink"], ["96b403", "booger green"], ["fdff63", "canary"], ["95a3a6", "cool grey"], ["7f684e", "dark taupe"], ["751973", "darkish purple"], ["089404", "true green"], ["ff6163", "coral pink"], ["598556", "dark sage"], ["214761", "dark slate blue"], ["3c73a8", "flat blue"], ["ba9e88", "mushroom"], ["021bf9", "rich blue"], ["734a65", "dirty purple"], ["23c48b", "greenblue"], ["8fae22", "icky green"], ["e6f2a2", "light khaki"], ["4b57db", "warm blue"], ["d90166", "dark hot pink"], ["015482", "deep sea blue"], ["9d0216", "carmine"], ["728f02", "dark yellow green"], ["ffe5ad", "pale peach"], ["4e0550", "plum purple"], ["f9bc08", "golden rod"], ["ff073a", "neon red"], ["c77986", "old pink"], ["d6fffe", "very pale blue"], ["fe4b03", "blood orange"], ["fd5956", "grapefruit"], ["fce166", "sand yellow"], ["b2713d", "clay brown"], ["1f3b4d", "dark blue grey"], ["699d4c", "flat green"], ["56fca2", "light green blue"], ["fb5581", "warm pink"], ["3e82fc", "dodger blue"], ["a0bf16", "gross green"], ["d6fffa", "ice"], ["4f738e", "metallic blue"], ["ffb19a", "pale salmon"], ["5c8b15", "sap green"], ["54ac68", "algae"], ["89a0b0", "bluey grey"], ["7ea07a", "greeny grey"], ["1bfc06", "highlighter green"], ["cafffb", "light light blue"], ["b6ffbb", "light mint"], ["a75e09", "raw umber"], ["152eff", "vivid blue"], ["8d5eb7", "deep lavender"], ["5f9e8f", "dull teal"], ["63f7b4", "light greenish blue"], ["606602", "mud green"], ["fc86aa", "pinky"], ["8c0034", "red wine"], ["758000", "shit green"], ["ab7e4c", "tan brown"], ["030764", "darkblue"], ["fe86a4", "rosa"], ["d5174e", "lipstick"], ["fed0fc", "pale mauve"], ["680018", "claret"], ["fedf08", "dandelion"], ["fe420f", "orangered"], ["6f7c00", "poop green"], ["ca0147", "ruby"], ["1b2431", "dark"], ["00fbb0", "greenish turquoise"], ["db5856", "pastel red"], ["ddd618", "piss yellow"], ["41fdfe", "bright cyan"], ["cf524e", "dark coral"], ["21c36f", "algae green"], ["a90308", "darkish red"], ["6e1005", "reddy brown"], ["fe828c", "blush pink"], ["4b6113", "camouflage green"], ["4da409", "lawn green"], ["beae8a", "putty"], ["0339f8", "vibrant blue"], ["a88f59", "dark sand"], ["5d21d0", "purple/blue"], ["feb209", "saffron"], ["4e518b", "twilight"], ["964e02", "warm brown"], ["85a3b2", "bluegrey"], ["ff69af", "bubble gum pink"], ["c3fbf4", "duck egg blue"], ["2afeb7", "greenish cyan"], ["005f6a", "petrol"], ["0c1793", "royal"], ["ffff81", "butter"], ["f0833a", "dusty orange"], ["f1f33f", "off yellow"], ["b1d27b", "pale olive green"], ["fc824a", "orangish"], ["71aa34", "leaf"], ["b7c9e2", "light blue grey"], ["4b0101", "dried blood"], ["a552e6", "lightish purple"], ["af2f0d", "rusty red"], ["8b88f8", "lavender blue"], ["9af764", "light grass green"], ["a6fbb2", "light mint green"], ["ffc512", "sunflower"], ["750851", "velvet"], ["c14a09", "brick orange"], ["fe2f4a", "lightish red"], ["0203e2", "pure blue"], ["0a437a", "twilight blue"], ["a50055", "violet red"], ["ae8b0c", "yellowy brown"], ["fd798f", "carnation"], ["bfac05", "muddy yellow"], ["3eaf76", "dark seafoam green"], ["c74767", "deep rose"], ["b9484e", "dusty red"], ["647d8e", "grey/blue"], ["bffe28", "lemon lime"], ["d725de", "purple/pink"], ["b29705", "brown yellow"], ["673a3f", "purple brown"], ["a87dc2", "wisteria"], ["fafe4b", "banana yellow"], ["c0022f", "lipstick red"], ["0e87cc", "water blue"], ["8d8468", "brown grey"], ["ad03de", "vibrant purple"], ["8cff9e", "baby green"], ["94ac02", "barf green"], ["c4fff7", "eggshell blue"], ["fdee73", "sandy yellow"], ["33b864", "cool green"], ["fff9d0", "pale"], ["758da3", "blue/grey"], ["f504c9", "hot magenta"], ["77a1b5", "greyblue"], ["8756e4", "purpley"], ["889717", "baby shit green"], ["c27e79", "brownish pink"], ["017371", "dark aquamarine"], ["9f8303", "diarrhea"], ["f7d560", "light mustard"], ["bdf6fe", "pale sky blue"], ["75b84f", "turtle green"], ["9cbb04", "bright olive"], ["29465b", "dark grey blue"], ["696006", "greeny brown"], ["adf802", "lemon green"], ["c1c6fc", "light periwinkle"], ["35ad6b", "seaweed green"], ["fffd37", "sunshine yellow"], ["a442a0", "ugly purple"], ["f36196", "medium pink"], ["947706", "puke brown"], ["fff4f2", "very light pink"], ["1e9167", "viridian"], ["b5c306", "bile"], ["feff7f", "faded yellow"], ["cffdbc", "very pale green"], ["0add08", "vibrant green"], ["87fd05", "bright lime"], ["1ef876", "spearmint"], ["7bfdc7", "light aquamarine"], ["bcecac", "light sage"], ["bbf90f", "yellowgreen"], ["ab9004", "baby poo"], ["1fb57a", "dark seafoam"], ["00555a", "deep teal"], ["a484ac", "heather"], ["c45508", "rust orange"], ["3f829d", "dirty blue"], ["548d44", "fern green"], ["c95efb", "bright lilac"], ["3ae57f", "weird green"], ["016795", "peacock blue"], ["87a922", "avocado green"], ["f0944d", "faded orange"], ["5d1451", "grape purple"], ["25ff29", "hot green"], ["d0fe1d", "lime yellow"], ["ffa62b", "mango"], ["01b44c", "shamrock"], ["ff6cb5", "bubblegum"], ["6b4247", "purplish brown"], ["c7c10c", "vomit yellow"], ["b7fffa", "pale cyan"], ["aeff6e", "key lime"], ["ec2d01", "tomato red"], ["76ff7b", "lightgreen"], ["730039", "merlot"], ["040348", "night blue"], ["df4ec8", "purpleish pink"], ["6ecb3c", "apple"], ["8f9805", "baby poop green"], ["5edc1f", "green apple"], ["d94ff5", "heliotrope"], ["c8fd3d", "yellow/green"], ["070d0d", "almost black"], ["4984b8", "cool blue"], ["51b73b", "leafy green"], ["ac7e04", "mustard brown"], ["4e5481", "dusk"], ["876e4b", "dull brown"], ["58bc08", "frog green"], ["2fef10", "vivid green"], ["2dfe54", "bright light green"], ["0aff02", "fluro green"], ["9cef43", "kiwi"], ["18d17b", "seaweed"], ["35530a", "navy green"], ["1805db", "ultramarine blue"], ["6258c4", "iris"], ["ff964f", "pastel orange"], ["ffab0f", "yellowish orange"], ["8f8ce7", "perrywinkle"], ["24bca8", "tealish"], ["3f012c", "dark plum"], ["cbf85f", "pear"], ["ff724c", "pinkish orange"], ["280137", "midnight purple"], ["b36ff6", "light urple"], ["48c072", "dark mint"], ["bccb7a", "greenish tan"], ["a8415b", "light burgundy"], ["06b1c4", "turquoise blue"], ["cd7584", "ugly pink"], ["f1da7a", "sandy"], ["ff0490", "electric pink"], ["805b87", "muted purple"], ["50a747", "mid green"], ["a8a495", "greyish"], ["cfff04", "neon yellow"], ["ffff7e", "banana"], ["ff7fa7", "carnation pink"], ["ef4026", "tomato"], ["3c9992", "sea"], ["886806", "muddy brown"], ["04f489", "turquoise green"], ["fef69e", "buff"], ["cfaf7b", "fawn"], ["3b719f", "muted blue"], ["fdc1c5", "pale rose"], ["20c073", "dark mint green"], ["9b5fc0", "amethyst"], ["0f9b8e", "blue/green"], ["742802", "chestnut"], ["9db92c", "sick green"], ["a4bf20", "pea"], ["cd5909", "rusty orange"], ["ada587", "stone"], ["be013c", "rose red"], ["b8ffeb", "pale aqua"], ["dc4d01", "deep orange"], ["a2653e", "earth"], ["638b27", "mossy green"], ["419c03", "grassy green"], ["b1ff65", "pale lime green"], ["9dbcd4", "light grey blue"], ["fdfdfe", "pale grey"], ["77ab56", "asparagus"], ["464196", "blueberry"], ["990147", "purple red"], ["befd73", "pale lime"], ["32bf84", "greenish teal"], ["af6f09", "caramel"], ["a0025c", "deep magenta"], ["ffd8b1", "light peach"], ["7f4e1e", "milk chocolate"], ["bf9b0c", "ocher"], ["6ba353", "off green"], ["f075e6", "purply pink"], ["7bc8f6", "lightblue"], ["475f94", "dusky blue"], ["f5bf03", "golden"], ["fffeb6", "light beige"], ["fffd74", "butter yellow"], ["895b7b", "dusky purple"], ["436bad", "french blue"], ["d0c101", "ugly yellow"], ["c6f808", "greeny yellow"], ["f43605", "orangish red"], ["02c14d", "shamrock green"], ["b25f03", "orangish brown"], ["2a7e19", "tree green"], ["490648", "deep violet"], ["536267", "gunmetal"], ["5a06ef", "blue/purple"], ["cf0234", "cherry"], ["c4a661", "sandy brown"], ["978a84", "warm grey"], ["1f0954", "dark indigo"], ["03012d", "midnight"], ["2bb179", "bluey green"], ["c3909b", "grey pink"], ["a66fb5", "soft purple"], ["770001", "blood"], ["922b05", "brown red"], ["7d7f7c", "medium grey"], ["990f4b", "berry"], ["8f7303", "poo"], ["c83cb9", "purpley pink"], ["fea993", "light salmon"], ["acbb0d", "snot"], ["c071fe", "easter purple"], ["ccfd7f", "light yellow green"], ["00022e", "dark navy blue"], ["828344", "drab"], ["ffc5cb", "light rose"], ["ab1239", "rouge"], ["b0054b", "purplish red"], ["99cc04", "slime green"], ["937c00", "baby poop"], ["019529", "irish green"], ["ef1de7", "pink/purple"], ["000435", "dark navy"], ["42b395", "greeny blue"], ["9d5783", "light plum"], ["c8aca9", "pinkish grey"], ["c87606", "dirty orange"], ["aa2704", "rust red"], ["e4cbff", "pale lilac"], ["fa4224", "orangey red"], ["0804f9", "primary blue"], ["5cb200", "kermit green"], ["76424e", "brownish purple"], ["6c7a0e", "murky green"], ["fbdd7e", "wheat"], ["2a0134", "very dark purple"], ["044a05", "bottle green"], ["fd4659", "watermelon"], ["0d75f8", "deep sky blue"], ["fe0002", "fire engine red"], ["cb9d06", "yellow ochre"], ["fb7d07", "pumpkin orange"], ["b9cc81", "pale olive"], ["edc8ff", "light lilac"], ["61e160", "lightish green"], ["8ab8fe", "carolina blue"], ["920a4e", "mulberry"], ["fe02a2", "shocking pink"], ["9a3001", "auburn"], ["65fe08", "bright lime green"], ["befdb7", "celadon"], ["b17261", "pinkish brown"], ["885f01", "poo brown"], ["02ccfe", "bright sky blue"], ["c1fd95", "celery"], ["836539", "dirt brown"], ["fb2943", "strawberry"], ["84b701", "dark lime"], ["b66325", "copper"], ["7f5112", "medium brown"], ["5fa052", "muted green"], ["6dedfd", "robin's egg"], ["0bf9ea", "bright aqua"], ["c760ff", "bright lavender"], ["ffffcb", "ivory"], ["f6cefc", "very light purple"], ["155084", "light navy"], ["f5054f", "pink red"], ["645403", "olive brown"], ["7a5901", "poop brown"], ["a8b504", "mustard green"], ["3d9973", "ocean green"], ["000133", "very dark blue"], ["76a973", "dusty green"], ["2e5a88", "light navy blue"], ["0bf77d", "minty green"], ["bd6c48", "adobe"], ["ac1db8", "barney"], ["2baf6a", "jade green"], ["26f7fd", "bright light blue"], ["aefd6c", "light lime"], ["9b8f55", "dark khaki"], ["ffad01", "orange yellow"], ["c69c04", "ocre"], ["f4d054", "maize"], ["de9dac", "faded pink"], ["05480d", "british racing green"], ["c9ae74", "sandstone"], ["60460f", "mud brown"], ["98f6b0", "light sea green"], ["8af1fe", "robin egg blue"], ["2ee8bb", "aqua marine"], ["11875d", "dark sea green"], ["fdb0c0", "soft pink"], ["b16002", "orangey brown"], ["f7022a", "cherry red"], ["d5ab09", "burnt yellow"], ["86775f", "brownish grey"], ["c69f59", "camel"], ["7a687f", "purplish grey"], ["042e60", "marine"], ["c88d94", "greyish pink"], ["a5fbd5", "pale turquoise"], ["fffe71", "pastel yellow"], ["6241c7", "bluey purple"], ["fffe40", "canary yellow"], ["d3494e", "faded red"], ["985e2b", "sepia"], ["a6814c", "coffee"], ["ff08e8", "bright magenta"], ["9d7651", "mocha"], ["feffca", "ecru"], ["98568d", "purpleish"], ["9e003a", "cranberry"], ["287c37", "darkish green"], ["b96902", "brown orange"], ["ba6873", "dusky rose"], ["ff7855", "melon"], ["94b21c", "sickly green"], ["c5c9c7", "silver"], ["661aee", "purply blue"], ["6140ef", "purpleish blue"], ["9be5aa", "hospital green"], ["7b5804", "shit brown"], ["276ab3", "mid blue"], ["feb308", "amber"], ["8cfd7e", "easter green"], ["6488ea", "soft blue"], ["056eee", "cerulean blue"], ["b27a01", "golden brown"], ["0ffef9", "bright turquoise"], ["fa2a55", "red pink"], ["820747", "red purple"], ["7a6a4f", "greyish brown"], ["f4320c", "vermillion"], ["a13905", "russet"], ["6f828a", "steel grey"], ["a55af4", "lighter purple"], ["ad0afd", "bright violet"], ["004577", "prussian blue"], ["658d6d", "slate green"], ["ca7b80", "dirty pink"], ["005249", "dark blue green"], ["2b5d34", "pine"], ["bff128", "yellowy green"], ["b59410", "dark gold"], ["2976bb", "bluish"], ["014182", "darkish blue"], ["bb3f3f", "dull red"], ["fc2647", "pinky red"], ["a87900", "bronze"], ["82cbb2", "pale teal"], ["667c3e", "military green"], ["fe46a5", "barbie pink"], ["fe83cc", "bubblegum pink"], ["94a617", "pea soup green"], ["a88905", "dark mustard"], ["7f5f00", "shit"], ["9e43a2", "medium purple"], ["062e03", "very dark green"], ["8a6e45", "dirt"], ["cc7a8b", "dusky pink"], ["9e0168", "red violet"], ["fdff38", "lemon yellow"], ["c0fa8b", "pistachio"], ["eedc5b", "dull yellow"], ["7ebd01", "dark lime green"], ["3b5b92", "denim blue"], ["01889f", "teal blue"], ["3d7afd", "lightish blue"], ["5f34e7", "purpley blue"], ["6d5acf", "light indigo"], ["748500", "swamp green"], ["706c11", "brown green"], ["3c0008", "dark maroon"], ["cb00f5", "hot purple"], ["002d04", "dark forest green"], ["658cbb", "faded blue"], ["749551", "drab green"], ["b9ff66", "light lime green"], ["9dc100", "snot green"], ["faee66", "yellowish"], ["7efbb3", "light blue green"], ["7b002c", "bordeaux"], ["c292a1", "light mauve"], ["017b92", "ocean"], ["fcc006", "marigold"], ["657432", "muddy green"], ["d8863b", "dull orange"], ["738595", "steel"], ["aa23ff", "electric purple"], ["08ff08", "fluorescent green"], ["9b7a01", "yellowish brown"], ["f29e8e", "blush"], ["6fc276", "soft green"], ["ff5b00", "bright orange"], ["fdff52", "lemon"], ["866f85", "purple grey"], ["8ffe09", "acid green"], ["eecffe", "pale lavender"], ["510ac9", "violet blue"], ["4f9153", "light forest green"], ["9f2305", "burnt red"], ["728639", "khaki green"], ["de0c62", "cerise"], ["916e99", "faded purple"], ["ffb16d", "apricot"], ["3c4d03", "dark olive green"], ["7f7053", "grey brown"], ["77926f", "green grey"], ["010fcc", "true blue"], ["ceaefa", "pale violet"], ["8f99fb", "periwinkle blue"], ["c6fcff", "light sky blue"], ["5539cc", "blurple"], ["544e03", "green brown"], ["017a79", "bluegreen"], ["01f9c6", "bright teal"], ["c9b003", "brownish yellow"], ["929901", "pea soup"], ["0b5509", "forest"], ["a00498", "barney purple"], ["2000b1", "ultramarine"], ["94568c", "purplish"], ["c2be0e", "puke yellow"], ["748b97", "bluish grey"], ["665fd1", "dark periwinkle"], ["9c6da5", "dark lilac"], ["c44240", "reddish"], ["a24857", "light maroon"], ["825f87", "dusty purple"], ["c9643b", "terra cotta"], ["90b134", "avocado"], ["01386a", "marine blue"], ["25a36f", "teal green"], ["59656d", "slate grey"], ["75fd63", "lighter green"], ["21fc0d", "electric green"], ["5a86ad", "dusty blue"], ["fec615", "golden yellow"], ["fffd01", "bright yellow"], ["dfc5fe", "light lavender"], ["b26400", "umber"], ["7f5e00", "poop"], ["de7e5d", "dark peach"], ["048243", "jungle green"], ["ffffd4", "eggshell"], ["3b638c", "denim"], ["b79400", "yellow brown"], ["84597e", "dull purple"], ["411900", "chocolate brown"], ["7b0323", "wine red"], ["04d9ff", "neon blue"], ["667e2c", "dirty green"], ["fbeeac", "light tan"], ["d7fffe", "ice blue"], ["4e7496", "cadet blue"], ["874c62", "dark mauve"], ["d5ffff", "very light blue"], ["826d8c", "grey purple"], ["ffbacd", "pastel pink"], ["d1ffbd", "very light green"], ["448ee4", "dark sky blue"], ["05472a", "evergreen"], ["d5869d", "dull pink"], ["3d0734", "aubergine"], ["4a0100", "mahogany"], ["f8481c", "reddish orange"], ["02590f", "deep green"], ["89a203", "vomit green"], ["e03fd8", "purple pink"], ["d58a94", "dusty pink"], ["7bb274", "faded green"], ["526525", "camo green"], ["c94cbe", "pinky purple"], ["db4bda", "pink purple"], ["9e3623", "brownish red"], ["b5485d", "dark rose"], ["735c12", "mud"], ["9c6d57", "brownish"], ["028f1e", "emerald green"], ["b1916e", "pale brown"], ["49759c", "dull blue"], ["a0450e", "burnt umber"], ["39ad48", "medium green"], ["b66a50", "clay"], ["8cffdb", "light aqua"], ["a4be5c", "light olive green"], ["cb7723", "brownish orange"], ["05696b", "dark aqua"], ["ce5dae", "purplish pink"], ["c85a53", "dark salmon"], ["96ae8d", "greenish grey"], ["1fa774", "jade"], ["7a9703", "ugly green"], ["ac9362", "dark beige"], ["01a049", "emerald"], ["d9544d", "pale red"], ["fa5ff7", "light magenta"], ["82cafc", "sky"], ["acfffc", "light cyan"], ["fcb001", "yellow orange"], ["910951", "reddish purple"], ["fe2c54", "reddish pink"], ["c875c4", "orchid"], ["cdc50a", "dirty yellow"], ["fd411e", "orange red"], ["9a0200", "deep red"], ["be6400", "orange brown"], ["030aa7", "cobalt blue"], ["fe019a", "neon pink"], ["f7879a", "rose pink"], ["887191", "greyish purple"], ["b00149", "raspberry"], ["12e193", "aqua green"], ["fe7b7c", "salmon pink"], ["ff9408", "tangerine"], ["6a6e09", "brownish green"], ["8b2e16", "red brown"], ["696112", "greenish brown"], ["e17701", "pumpkin"], ["0a481e", "pine green"], ["343837", "charcoal"], ["ffb7ce", "baby pink"], ["6a79f7", "cornflower"], ["5d06e9", "blue violet"], ["3d1c02", "chocolate"], ["82a67d", "greyish green"], ["be0119", "scarlet"], ["c9ff27", "green yellow"], ["373e02", "dark olive"], ["a9561e", "sienna"], ["caa0ff", "pastel purple"], ["ca6641", "terracotta"], ["02d8e9", "aqua blue"], ["88b378", "sage green"], ["980002", "blood red"], ["cb0162", "deep pink"], ["5cac2d", "grass"], ["769958", "moss"], ["a2bffe", "pastel blue"], ["10a674", "bluish green"], ["06b48b", "green blue"], ["af884a", "dark tan"], ["0b8b87", "greenish blue"], ["ffa756", "pale orange"], ["a2a415", "vomit"], ["154406", "forrest green"], ["856798", "dark lavender"], ["34013f", "dark violet"], ["632de9", "purple blue"], ["0a888a", "dark cyan"], ["6f7632", "olive drab"], ["d46a7e", "pinkish"], ["1e488f", "cobalt"], ["bc13fe", "neon purple"], ["7ef4cc", "light turquoise"], ["76cd26", "apple green"], ["74a662", "dull green"], ["80013f", "wine"], ["b1d1fc", "powder blue"], ["ffffe4", "off white"], ["0652ff", "electric blue"], ["045c5a", "dark turquoise"], ["5729ce", "blue purple"], ["069af3", "azure"], ["ff000d", "bright red"], ["f10c45", "pinkish red"], ["5170d7", "cornflower blue"], ["acbf69", "light olive"], ["6c3461", "grape"], ["5e819d", "greyish blue"], ["601ef9", "purplish blue"], ["b0dd16", "yellowish green"], ["cdfd02", "greenish yellow"], ["2c6fbb", "medium blue"], ["c0737a", "dusty rose"], ["d6b4fc", "light violet"], ["020035", "midnight blue"], ["703be7", "bluish purple"], ["fd3c06", "red orange"], ["960056", "dark magenta"], ["40a368", "greenish"], ["03719c", "ocean blue"], ["fc5a50", "coral"], ["ffffc2", "cream"], ["7f2b0a", "reddish brown"], ["b04e0f", "burnt sienna"], ["a03623", "brick"], ["87ae73", "sage"], ["789b73", "grey green"], ["ffffff", "white"], ["98eff9", "robin's egg blue"], ["658b38", "moss green"], ["5a7d9a", "steel blue"], ["380835", "eggplant"], ["fffe7a", "light yellow"], ["5ca904", "leaf green"], ["d8dcd6", "light grey"], ["a5a502", "puke"], ["d648d7", "pinkish purple"], ["047495", "sea blue"], ["b790d4", "pale purple"], ["5b7c99", "slate blue"], ["607c8e", "blue grey"], ["0b4008", "hunter green"], ["ed0dd9", "fuchsia"], ["8c000f", "crimson"], ["ffff84", "pale yellow"], ["bf9005", "ochre"], ["d2bd0a", "mustard yellow"], ["ff474c", "light red"], ["0485d1", "cerulean"], ["ffcfdc", "pale pink"], ["040273", "deep blue"], ["a83c09", "rust"], ["90e4c1", "light teal"], ["516572", "slate"], ["fac205", "goldenrod"], ["d5b60a", "dark yellow"], ["363737", "dark grey"], ["4b5d16", "army green"], ["6b8ba4", "grey blue"], ["80f9ad", "seafoam"], ["a57e52", "puce"], ["a9f971", "spring green"], ["c65102", "dark orange"], ["e2ca76", "sand"], ["b0ff9d", "pastel green"], ["9ffeb0", "mint"], ["fdaa48", "light orange"], ["fe01b1", "bright pink"], ["c1f80a", "chartreuse"], ["36013f", "deep purple"], ["341c02", "dark brown"], ["b9a281", "taupe"], ["8eab12", "pea green"], ["9aae07", "puke green"], ["02ab2e", "kelly green"], ["7af9ab", "seafoam green"], ["137e6d", "blue green"], ["aaa662", "khaki"], ["610023", "burgundy"], ["014d4e", "dark teal"], ["8f1402", "brick red"], ["4b006e", "royal purple"], ["580f41", "plum"], ["8fff9f", "mint green"], ["dbb40c", "gold"], ["a2cffe", "baby blue"], ["c0fb2d", "yellow green"], ["be03fd", "bright purple"], ["840000", "dark red"], ["d0fefe", "pale blue"], ["3f9b0b", "grass green"], ["01153e", "navy"], ["04d8b2", "aquamarine"], ["c04e01", "burnt orange"], ["0cff0c", "neon green"], ["0165fc", "bright blue"], ["cf6275", "rose"], ["ffd1df", "light pink"], ["ceb301", "mustard"], ["380282", "indigo"], ["aaff32", "lime"], ["53fca1", "sea green"], ["8e82fe", "periwinkle"], ["cb416b", "dark pink"], ["677a04", "olive green"], ["ffb07c", "peach"], ["c7fdb5", "pale green"], ["ad8150", "light brown"], ["ff028d", "hot pink"], ["000000", "black"], ["cea2fd", "lilac"], ["001146", "navy blue"], ["0504aa", "royal blue"], ["e6daa6", "beige"], ["ff796c", "salmon"], ["6e750e", "olive"], ["650021", "maroon"], ["01ff07", "bright green"], ["35063e", "dark purple"], ["ae7181", "mauve"], ["06470c", "forest green"], ["13eac9", "aqua"], ["00ffff", "cyan"], ["d1b26f", "tan"], ["00035b", "dark blue"], ["c79fef", "lavender"], ["06c2ac", "turquoise"], ["033500", "dark green"], ["9a0eea", "violet"], ["bf77f6", "light purple"], ["89fe05", "lime green"], ["929591", "grey"], ["75bbfd", "sky blue"], ["ffff14", "yellow"], ["c20078", "magenta"], ["96f97b", "light green"], ["f97306", "orange"], ["029386", "teal"], ["95d0fc", "light blue"], ["e50000", "red"], ["653700", "brown"], ["ff81c0", "pink"], ["0343df", "blue"], ["15b01a", "green"], ["7e1e9c", "purple"], ["FF5E99", "paul irish pink"], ["87b84a", "peridot"], ["00000000", "transparent"]];
lookup = {};
normalizeKey = function(key) {
return key.toString().toLowerCase().split(' ').join('');
};
names.each(function(element) {
return lookup[normalizeKey(element[1])] = element[0].parseHex();
});
return Color.lookup = function(color) {
return lookup[normalizeKey(color)];
};
})();
/**
The Controllable module adds simple movement
when up, down, left, or right are held.
# create a player and include Controllable
player = GameObject
width: 5
height: 17
x: 15
y: 30
speed: 2
player.include Controllable
# hold the left arrow key, then
# update the player
player.update()
# the player is moved left according to his speed
player.I.x
# => 13
# We keep track of the direction the object is
# facing in case you need that (eg. for attack direction)
player.I.facing
# => player.I.facing
# => Point(-1, 0)
@name Controllable
@module
@constructor
@param {Object} I Instance variables
@param {Core} self Reference to including object
*/
this.Controllable = function(I, self) {
if (I == null) {
I = {};
}
Object.reverseMerge(I, {
facing: Point(1, 0),
speed: 1,
velocity: Point(0, 0)
});
self.bind("update", function() {
return self.movement();
});
return {
movement: function() {
I.velocity.x = 0;
I.velocity.y = 0;
if (keydown.left) {
I.velocity.x = -1;
}
if (keydown.right) {
I.velocity.x = 1;
}
if (keydown.up) {
I.velocity.y = -1;
}
if (keydown.down) {
I.velocity.y = 1;
}
I.velocity = I.velocity.norm();
if (!I.velocity.equal(Point.ZERO)) {
I.facing = I.velocity;
}
return I.velocity = I.velocity.scale(I.speed);
}
};
};
/**
The Cooldown module provides a declarative way to manage cooldowns on
GameObject's properties.
# Health regeneration
player = GameObject
health: 50
# health will approach
# 100 by 1 every second
player.cooldown "health",
target: 100
elapsedTime = 1
player.update(elapsedTime)
player.I.health
# => 51
# Shoot Timeout
player = GameObject()
# by default the cooldown
# approaches 0 by 1 each second
player.cooldown "shootTimer"
player.I.shootTimer = 10 # => Pew! Pew!
player.update(elapsedTime)
player.I.shootTimer # => 9
# Turbo Cooldown
player = GameObject()
# turboTimer starts at 1000
# and approaches 12 by 5 each second
player.cooldown "turboTimer",
approachBy: 5
value: 1000
target: 12
player.I.turboTimer = 1000
player.update(elapsedTime)
player.I.turboTimer # => 995
@name Cooldown
@module
@constructor
@param {Object} I Instance variables
@param {Core} self Reference to including object
*/
this.Cooldown = function(I, self) {
Object.reverseMerge(I, {
cooldowns: {}
});
self.bind("update", function(dt) {
var approachBy, cooldownOptions, name, target, _ref, _results;
_ref = I.cooldowns;
_results = [];
for (name in _ref) {
cooldownOptions = _ref[name];
approachBy = cooldownOptions.approachBy, target = cooldownOptions.target;
_results.push(I[name] = I[name].approach(target, approachBy * dt));
}
return _results;
});
return {
cooldown: function(name, options) {
var approachBy, target, value;
if (options == null) {
options = {};
}
target = options.target, approachBy = options.approachBy, value = options.value;
target || (target = 0);
if (approachBy == null) {
approachBy = 1;
}
I.cooldowns[name] = {
target: target,
approachBy: approachBy
};
if (value != null) {
return I[name] = options.value;
} else {
if (!I[name]) {
return I[name] = 0;
}
}
}
};
};
/**
The Debuggable Module provides a simple API to easily display
an object's properties onscreen. This mixin comes with predefined
attribute filters so that you can exclude irrelevant data.
player = GameObject
x: 40
y: 14
spriteName: null
numericErrorProperty: NaN
player.include Debuggable
# sets up debug output for all player's properties
# at the starting position (0, 0)
player.debug
filter: 'all'
@name Debuggable
@module
@constructor
@param {Object} I Instance variables
@param {Core} self Reference to including object
*/
this.Debuggable = function(I, self) {
var COL_HEIGHT, FONT_SIZE, ROW_HEIGHT, debugBounds, debugVelocity, debugX, debugY, drawDebugLine, filterProperties, getPropertyRow, initialI, nan, processValue, sortedKeys;
if (I == null) {
I = {};
}
COL_HEIGHT = 175;
ROW_HEIGHT = 9;
FONT_SIZE = 9;
debugX = 0;
debugY = 0;
Object.reverseMerge(I, {
debug: {
enabled: false,
color: 'black',
filter: 'all',
bounds: true,
velocity: true,
position: {
x: 0,
y: 0
}
}
});
initialI = Object.extend({}, I);
debugBounds = function(canvas) {
return canvas.drawRect({
color: 'rgba(255, 0, 255, 0.4)',
bounds: self.bounds()
});
};
debugVelocity = function(canvas) {
if (I.velocity != null) {
return canvas.withTransform(Matrix.translation(I.x, I.y), function(canvas) {
var color, thickness;
thickness = 4;
color = 'rgba(255, 0, 0, 0.5)';
canvas.drawRect({
x: 0,
y: -thickness / 2,
width: I.velocity.x,
height: thickness,
color: color
});
return canvas.drawRect({
x: -thickness / 2,
y: 0,
width: thickness,
height: I.velocity.y,
color: color
});
});
}
};
filterProperties = function(properties) {
var key, results, value;
results = {};
switch (I.debug.filter) {
case 'all':
results = properties;
break;
case 'undefined':
for (key in properties) {
value = properties[key];
if ((value == null) || nan(value)) {
results[key] = value;
}
}
break;
case 'changed':
for (key in properties) {
value = properties[key];
if (initialI[key] !== value) {
results[key] = value;
}
}
}
return results;
};
sortedKeys = function() {
var key, keys, value, _ref;
keys = [];
_ref = filterProperties(I);
for (key in _ref) {
value = _ref[key];
keys.push(key);
}
return keys.sort();
};
nan = function(value) {
return typeof value === 'number' && isNaN(value);
};
drawDebugLine = function(text, canvas, x, y) {
canvas.drawText({
color: I.debug.color,
x: x + I.debug.position.x,
y: y + I.debug.position.y,
text: text
});
return debugY += ROW_HEIGHT;
};
getPropertyRow = function(key, value, canvas) {
var k, toStringArray, v;
if (typeof value === 'function') {
} else if (Object.isObject(value)) {
drawDebugLine(key, canvas, debugX, debugY);
debugX += 8;
for (k in value) {
v = value[k];
getPropertyRow(k, v, canvas);
}
return debugX -= 8;
} else if (Object.isArray(value)) {
toStringArray = (function() {
var _i, _len, _results;
_results = [];
for (_i = 0, _len = value.length; _i < _len; _i++) {
v = value[_i];
if (Object.isObject(v)) {
_results.push(v.I["class"] || v.toString());
} else {
_results.push(v);
}
}
return _results;
})();
return drawDebugLine("" + key + "(" + value.length + "): " + toStringArray, canvas, debugX, debugY);
} else {
value = processValue(value);
return drawDebugLine("" + key + ": " + value, canvas, debugX, debugY);
}
};
processValue = function(value) {
var output, parsedNumber;
output = value;
try {
parsedNumber = parseFloat(value);
} catch (_error) {}
if (parsedNumber) {
if (typeof value !== 'string' && parsedNumber !== parseInt(value)) {
output = value.toFixed(3);
}
}
return output;
};
self.bind("update", function() {
if (justPressed['0']) {
return self.toggleDebug();
}
});
self.bind("overlay", function(canvas) {
var key, _i, _len, _ref;
if (I.debug.enabled) {
canvas.font("" + FONT_SIZE + "px Monaco");
debugX = 0;
debugY = ROW_HEIGHT;
_ref = sortedKeys();
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
key = _ref[_i];
getPropertyRow(key, I[key], canvas);
}
debugX += COL_HEIGHT;
debugY = ROW_HEIGHT;
if (I.debug.bounds) {
debugBounds(canvas);
}
if (I.debug.velocity) {
return debugVelocity(canvas);
}
}
});
return {
/**
Enable debugging display for the calling GameObject.
player = GameObject
x: 40
y: 14
spriteName: null
numericErrorProperty: NaN
player.include Debuggable
# sets up debug output for all player's properties
# at the starting position (0, 0)
player.debug
filter: 'all'
player.I.y = 45
# sets up debug output for only properties that have
# changed since initialization. In this case only y
# would be displayed.
player.debug
filter: 'changed'
# sets up debug output for properties that are <code>undefined</code>,
# <code>null</code>, or <code>NaN</code>. In this case spriteName and
# numericErrorProperty would be displayed.
player.debug
filter: 'undefined'
# sets up debug output using all possible configuration options
player.debug
bounds: true # set this to false to disable visual debugging of the object's bounding box
color: 'red' # color of debug text
filter: 'all'
x: 30 # x position to start printing debug information
y: 50 # y position to start printing debug information
velocity: true # set this to false to disable visual debugging of the object's velocity
@name debug
@methodOf Debuggable#
@param {Object} Options to configure debug output
@param {Boolean} bounds Whether or not to visually debug the object's bounds
@param {Color|String} color The color of the debug text
@param {String} filter Choices include 'all', 'changed', and 'undefined'
@param {Number} x The x position to start drawing the debug information
@param {Number} y The y position to start drawing the debug information
@param {Boolean} velocity Whether or not to visually debug the object's velocity
*/
debug: function(options) {
var x, y;
if (options == null) {
options = {};
}
x = options.x, y = options.y;
if (x != null) {
I.debug.position.x = x;
}
if (y != null) {
I.debug.position.y = y;
}
Object.extend(I.debug, options);
return I.debug.enabled = true;
},
/**
Toggle display of debug information.
player = GameObject()
player.include Debuggable
# enables debug display
player.debug()
# disables debug display
player.toggleDisable()
# if false is passed to toggleDisplay, then debugging is disabled.
player.toggleDisplay(false)
# if true is passed to toggleDisplay, then debugging is enabled.
player.toggleDisplay(true)
@name toggleDebug
@methodOf Debuggable#
@param {Boolean} newVal If true is passed then debugging is enabled, if false is passed then debugging is disabled, if nothing is passed, then debug state is toggled.
*/
toggleDebug: function(newVal) {
if (newVal != null) {
return I.debug.enabled = newVal;
} else {
return I.debug.enabled = !I.debug.enabled;
}
}
};
};
/**
The Drawable module is used to provide a simple draw method to the including
object.
Binds a default draw listener to draw a rectangle or a sprite, if one exists.
Binds a step listener to update the transform of the object.
Autoloads the sprite specified in I.sprite, if any.
player = Core
x: 15
y: 30
width: 5
height: 5
sprite: "my_cool_sprite"
engine.bind 'draw', (canvas) ->
player.draw(canvas)
# => Uncaught TypeError: Object has no method 'draw'
player.include(Drawable)
engine.bind 'draw', (canvas) ->
player.draw(canvas)
# => if you have a sprite named "my_cool_sprite" in your images folder
# then it will be drawn. Otherwise, a rectangle positioned at x: 15 and
# y: 30 with width and height 5 will be drawn.
@name Drawable
@module
@constructor
@param {Object} I Instance variables
@param {Core} self Reference to including object
*/
/**
Triggered every time the object should be drawn. A canvas is passed as
the first argument.
player = Core
x: 0
y: 10
width: 5
height: 5
player.bind "draw", (canvas) ->
# Text will be drawn positioned relatively to the object.
canvas.drawText
text: "Hey, drawing stuff is pretty easy."
color: "white"
x: 5
y: 5
@name draw
@methodOf Drawable#
@event
@param {PowerCanvas} canvas A reference to the canvas to draw on.
*/
/**
Triggered before the object should be drawn. A canvas is passed as
the first argument. This does not apply the current transform.
@name beforeTransform
@methodOf Drawable#
@event
@param {PowerCanvas} canvas A reference to the canvas to draw on.
*/
/**
Triggered after the object should be drawn. A canvas is passed as
the first argument. This applies the current transform.
@name afterTransform
@methodOf Drawable#
@event
@param {PowerCanvas} canvas A reference to the canvas to draw on.
*/
this.Drawable = function(I, self) {
var cachedSprite;
if (I == null) {
I = {};
}
Object.reverseMerge(I, {
alpha: 1,
color: "#196",
scale: 1,
scaleX: 1,
scaleY: 1,
zIndex: 0
});
cachedSprite = null;
self.unbind(".Drawable");
self.bind('draw.Drawable', function(canvas) {
var previousAlpha, sprite;
if ((I.alpha != null) && I.alpha !== 1) {
previousAlpha = canvas.context().globalAlpha;
canvas.context().globalAlpha = I.alpha;
}
if (sprite = self.sprite()) {
if (sprite.draw != null) {
sprite.draw(canvas, -sprite.width / 2, -sprite.height / 2);
} else {
if (typeof warn === "function") {
warn("Sprite has no draw method!");
}
}
} else {
if (I.radius != null) {
canvas.drawCircle({
x: 0,
y: 0,
radius: I.radius,
color: I.color
});
} else {
canvas.drawRect({
x: -I.width / 2,
y: -I.height / 2,
width: I.width,
height: I.height,
color: I.color
});
}
}
if ((I.alpha != null) && I.alpha !== 1) {
return canvas.context().globalAlpha = previousAlpha;
}
});
return {
/**
Draw does not actually do any drawing itself, instead it triggers all of the draw events.
Listeners on the events do the actual drawing.
@name draw
@methodOf Drawable#
@returns self
*/
draw: function(canvas) {
self.trigger('beforeTransform', canvas);
canvas.withTransform(self.transform(), function(canvas) {
self.trigger('beforeDraw', canvas);
self.trigger('draw', canvas);
return self.trigger('afterDraw', canvas);
});
self.trigger('afterTransform', canvas);
return self;
},
sprite: function(newSprite) {
if (newSprite != null) {
return cachedSprite = newSprite;
} else {
if (I.sprite) {
return Sprite.loadByName(I.sprite);
}
}
},
/**
Returns the current transform, with translation, rotation, and flipping applied.
@name transform
@methodOf Drawable#
@returns {Matrix} The current transform
*/
transform: function() {
var center, transform;
center = self.center();
transform = Matrix.translation(center.x.floor(), center.y.floor());
transform = transform.concat(Matrix.scale(I.scale * I.scaleX, I.scale * I.scaleY));
if (I.rotation) {
transform = transform.concat(Matrix.rotation(I.rotation));
}
if (I.spriteOffset) {
transform = transform.concat(Matrix.translation(I.spriteOffset.x, I.spriteOffset.y));
}
return transform;
}
};
};
this.DustParticle = function(I, self) {
if (I == null) {
I = {};
}
Object.extend(I, {
color: 'rgb(100, 100, 100)',
duration: 0.2,
fadeOut: true,
maxSpeed: 90
});
I.velocity = [Point(-60, -30), Point(40, -15), Point(-20, -7), Point(60, -30), Point(40, -15), Point(20, -7)].rand();
I.acceleration = Point(0, 60);
return {};
};
this.DustEmitter = function(I) {
var self;
if (I == null) {
I = {};
}
Object.reverseMerge(I, {
duration: 3,
particleCount: 20,
batchSize: 5,
x: 0,
y: 0,
zIndex: 50,
generator: {
includedModules: ["DustParticle"],
radius: function(n) {
return [2, 3, 1].wrap(n);
}
}
});
self = Emitter(I);
return self;
};
(function() {
var Easing, polynomialEasings;
Easing = {
sinusoidal: function(begin, end) {
var change;
change = end - begin;
return function(t) {
return begin + change * (1 - Math.cos(t * Math.TAU / 4));
};
},
sinusoidalOut: function(begin, end) {
var change;
change = end - begin;
return function(t) {
return begin + change * (0 + Math.sin(t * Math.TAU / 4));
};
}
};
polynomialEasings = ["linear", "quadratic", "cubic", "quartic", "quintic"];
polynomialEasings.each(function(easing, i) {
var exponent, sign;
exponent = i + 1;
sign = exponent % 2 ? 1 : -1;
Easing[easing] = function(begin, end) {
var change;
change = end - begin;
return function(t) {
return begin + change * Math.pow(t, exponent);
};
};
return Easing["" + easing + "Out"] = function(begin, end) {
var change;
change = end - begin;
return function(t) {
return begin + change * (1 + sign * Math.pow(t - 1, exponent));
};
};
});
["sinusoidal"].concat(polynomialEasings).each(function(easing) {
return Easing["" + easing + "InOut"] = function(begin, end) {
var easeIn, easeOut, midpoint;
midpoint = (begin + end) / 2;
easeIn = Easing[easing](begin, midpoint);
easeOut = Easing["" + easing + "Out"](midpoint, end);
return function(t) {
if (t < 0.5) {
return easeIn(2 * t);
} else {
return easeOut(2 * t - 1);
}
};
};
});
return (typeof exports !== "undefined" && exports !== null ? exports : this)["Easing"] = Easing;
})();
this.Emitterable = function(I, self) {
var n;
if (I == null) {
I = {};
}
Object.reverseMerge(I, {
batchSize: 1,
emissionRate: 1,
width: 0,
height: 0,
sprite: Sprite.EMPTY,
generator: {},
particles: [],
particleCount: Infinity,
x: I.x,
y: I.y,
particleData: {
acceleration: Point(0, 0.1),
age: 0,
color: "blue",
duration: 1.5,
height: 2,
maxSpeed: 120,
offset: Point(0, 0),
sprite: false,
spriteName: false,
velocity: Point(-0.25, 1),
width: 2,
x: 0,
y: 0
}
});
n = 0;
self.bind('draw', function(canvas) {
return I.particles.invoke("draw", canvas);
});
self.bind('overlay', function(canvas) {
return I.particles.invoke("trigger", "overlay", canvas);
});
self.bind('beforeUpdate', function(dt) {
return I.particles.invoke("trigger", "beforeUpdate", dt);
});
self.bind('afterUpdate', function(dt) {
return I.particles.invoke("trigger", "afterUpdate", dt);
});
self.bind('update', function(dt) {
I.batchSize.times(function() {
var key, particleProperties, value, _ref;
if (n < I.particleCount && rand() < I.emissionRate) {
particleProperties = Object.extend({}, I.particleData);
_ref = I.generator;
for (key in _ref) {
value = _ref[key];
if (I.generator[key].call) {
particleProperties[key] = I.generator[key](n, I);
} else {
particleProperties[key] = I.generator[key];
}
}
particleProperties.x += particleProperties.offset.x;
particleProperties.y += particleProperties.offset.y;
I.particles.push(GameObject(particleProperties));
return n += 1;
}
});
I.particles = I.particles.select(function(particle) {
return particle.update(dt);
});
if (n === I.particleCount && !I.particles.length) {
return I.active = false;
}
});
return {};
};
this.Emitter = function(I) {
var self;
if (I == null) {
I = {};
}
self = GameObject(I);
self.include("Emitterable");
return self;
};
(function() {
/**
The Engine controls the game world and manages game state. Once you
set it up and let it run it pretty much takes care of itself.
You can use the engine to add or remove objects from the game world.
There are several modules that can include to add additional capabilities
to the engine.
The engine fires events that you may bind listeners to. Event listeners
may be bound with <code>engine.bind(eventName, callback)</code>
@name Engine
@constructor
@param {Object} I Instance variables of the engine
*/
/**
Observe or modify the
entity data before it is added to the engine.
@name beforeAdd
@methodOf Engine#
@event
@param {Object} entityData
*/
/**
Observe or configure a <code>gameObject</code> that has been added
to the engine.
@name afterAdd
@methodOf Engine#
@event
@param {GameObject} object The object that has just been added to the
engine.
*/
/**
Called when the engine updates all the game objects.
@name update
@methodOf Engine#
@event
@param {Number} elapsedTime The time in seconds that has elapsed since the last update.
*/
/**
Called after the engine completes an update. Here it is
safe to modify the game objects array.
@name afterUpdate
@methodOf Engine#
@event
*/
/**
Called before the engine draws the game objects on the canvas. The current camera transform is applied.
@name beforeDraw
@methodOf Engine#
@event
@params {PixieCanvas} canvas A reference to the canvas to draw on.
*/
/**
Called after the engine draws on the canvas. The current camera transform is applied.
engine.bind "draw", (canvas) ->
# print some directions for the player
canvas.drawText
text: "Go this way =>"
x: 200
y: 200
@name draw
@methodOf Engine#
@event
@params {PixieCanvas} canvas A reference to the canvas to draw on.
*/
/**
Called after the engine draws.
The current camera transform is not applied. This is great for
adding overlays.
engine.bind "overlay", (canvas) ->
# print the player's health. This will be
# positioned absolutely according to the viewport.
canvas.drawText
text: "HEALTH:"
position: Point(20, 20)
canvas.drawText
text: player.health()
position: Point(50, 20)
@name overlay
@methodOf Engine#
@event
@params {PixieCanvas} canvas A reference to the canvas to draw on.
*/
var Engine;
Engine = function(I) {
var animLoop, draw, frameAdvance, lastStepTime, running, self, startTime, step, update;
if (I == null) {
I = {};
}
Object.reverseMerge(I, {
FPS: 60,
paused: false
});
frameAdvance = false;
running = false;
startTime = +new Date();
lastStepTime = -Infinity;
animLoop = function(timestamp) {
var delta, msPerFrame, remainder;
timestamp || (timestamp = +new Date());
msPerFrame = 1000 / I.FPS;
delta = timestamp - lastStepTime;
remainder = delta - msPerFrame;
if (remainder > 0) {
lastStepTime = timestamp - Math.min(remainder, msPerFrame);
step();
}
if (running) {
return window.requestAnimationFrame(animLoop);
}
};
update = function(elapsedTime) {
self.trigger("beforeUpdate", elapsedTime);
self.trigger("update", elapsedTime);
return self.trigger("afterUpdate", elapsedTime);
};
draw = function() {
var canvas;
if (!(canvas = I.canvas)) {
return;
}
self.trigger("beforeDraw", canvas);
self.trigger("draw", canvas);
return self.trigger("overlay", canvas);
};
step = function() {
var elapsedTime;
if (!I.paused || frameAdvance) {
elapsedTime = 1 / I.FPS;
update(elapsedTime);
}
return draw();
};
self = Core(I).extend({
/**
Start the game simulation.
engine.start()
@methodOf Engine#
@name start
*/
start: function() {
if (!running) {
running = true;
return window.requestAnimationFrame(animLoop);
}
},
/**
Stop the simulation.
engine.stop()
@methodOf Engine#
@name stop
*/
stop: function() {
return running = false;
},
/**
Pause the game and step through 1 update of the engine.
engine.frameAdvance()
@methodOf Engine#
@name frameAdvance
*/
frameAdvance: function() {
I.paused = true;
frameAdvance = true;
step();
return frameAdvance = false;
},
/**
Resume the game.
engine.play()
@methodOf Engine#
@name play
*/
play: function() {
return I.paused = false;
},
/**
Toggle the paused state of the simulation.
engine.pause()
@methodOf Engine#
@name pause
@param {Boolean} [setTo] Force to pause by passing true or unpause by passing false.
*/
pause: function(setTo) {
if (setTo != null) {
return I.paused = setTo;
} else {
return I.paused = !I.paused;
}
},
/**
Query the engine to see if it is paused.
engine.pause()
engine.paused()
# true
engine.play()
engine.paused()
# false
@methodOf Engine#
@name paused
*/
paused: function() {
return I.paused;
},
/**
Change the framerate of the game. The default framerate is 30 fps.
engine.setFramerate(60)
@methodOf Engine#
@name setFramerate
*/
setFramerate: function(newFPS) {
I.FPS = newFPS;
self.stop();
return self.start();
},
update: update,
draw: draw
});
self.include(Ageable);
Engine.defaultModules.each(function(moduleName) {
var fullModuleName;
fullModuleName = "Engine." + moduleName;
if (!Engine[moduleName]) {
throw "#" + fullModuleName + " is not a valid engine module";
}
return self.include(fullModuleName);
});
self.trigger("init");
return self;
};
Engine.defaultModules = ["Data", "Keyboard", "Mouse", "Background", "Delay", "GameState", "Selector", "Collision", "Tilemap", "Levels"];
return (typeof exports !== "undefined" && exports !== null ? exports : this)["Engine"] = Engine;
})();
/**
The <code>Collision</code> module provides some simple collision detection methods to engine.
@name Collision
@fieldOf Engine
@module
@param {Object} I Instance variables
@param {Object} self Reference to the engine
*/
Engine.Collision = function(I, self) {
return {
/**
Detects collisions between a bounds and the game objects.
@name collides
@methodOf Engine#
@param bounds The bounds to check collisions with.
@param [sourceObject] An object to exclude from the results.
@returns {Boolean} true if the bounds object collides with any of the game objects, false otherwise.
*/
collides: function(bounds, sourceObject, selector) {
if (selector == null) {
selector = ".solid";
}
return self.find(selector).inject(false, function(collided, object) {
return collided || (object !== sourceObject) && object.collides(bounds) && object;
});
},
/**
Detects collisions between a bounds and the game objects.
Returns an array of objects colliding with the bounds provided.
@name collidesWith
@methodOf Engine#
@param bounds The bounds to check collisions with.
@param [sourceObject] An object to exclude from the results.
@returns {Array} An array of objects that collide with the given bounds.
*/
collidesWith: function(bounds, sourceObject, selector) {
if (selector == null) {
selector = ".solid";
}
return self.find(selector).select(function(object) {
return object !== sourceObject && object.collides(bounds);
});
},
/**
Detects collisions between a ray and the game objects.
@name rayCollides
@methodOf Engine#
@param source The origin point
@param direction A point representing the direction of the ray
@param [sourceObject] An object to exclude from the results.
@param [selector] A selector to choos which objects in the engine to collide with
*/
rayCollides: function(_arg) {
var direction, hits, nearestDistance, nearestHit, selector, source, sourceObject;
source = _arg.source, direction = _arg.direction, sourceObject = _arg.sourceObject, selector = _arg.selector;
if (selector == null) {
selector = "";
}
hits = self.find(selector).map(function(object) {
var hit;
hit = (object !== sourceObject) && Collision.rayRectangle(source, direction, object.centeredBounds());
if (hit) {
hit.object = object;
}
return hit;
});
nearestDistance = Infinity;
nearestHit = null;
hits.each(function(hit) {
var d;
if (hit && (d = hit.distance(source)) < nearestDistance) {
nearestDistance = d;
return nearestHit = hit;
}
});
return nearestHit;
},
objectsUnderPoint: function(point, selector) {
var bounds;
if (selector == null) {
selector = "";
}
bounds = {
x: point.x,
y: point.y,
width: 0,
height: 0
};
return self.find(selector).select(function(object) {
return object.collides(bounds);
});
}
};
};
/**
The <code>Data</code> module provides methods to store global and persistent data in the engine.
engine.data.score = 0
engine.data.score += 10
engine.data.score # => 10
@name Data
@fieldOf Engine
@module
@param {Object} I Instance variables
@param {Object} self Reference to the engine
*/
Engine.Data = function(I, self) {
if (I == null) {
I = {};
}
Object.reverseMerge(I, {
data: {}
});
Object.defineProperty(self, 'data', {
get: function() {
return I.data;
}
});
return {};
};
/**
The <code>Delay</code> module provides methods to trigger events after a number of steps have passed.
@name Delay
@fieldOf Engine
@module
@param {Object} I Instance variables
@param {Object} self Reference to the engine
*/
Engine.Delay = function(I, self) {
var delayedEvents;
delayedEvents = [];
self.bind('afterUpdate', function(elapsedTime) {
var firingEvents, _ref;
_ref = delayedEvents.partition(function(event) {
return (event.delay -= elapsedTime) >= 0;
}), delayedEvents = _ref[0], firingEvents = _ref[1];
firingEvents.each(function(event) {
return event.callback();
});
});
return {
/**
Execute a callback after a number of seconds have passed.
engine.delay 5, ->
engine.add
class: "Ghost"
@name delay
@methodOf Engine#
@param {Number} seconds The number of steps to wait before executing the callback
@param {Function} callback The callback to be executed.
@returns {Engine} self
*/
delay: function(seconds, callback) {
delayedEvents.push({
delay: seconds,
callback: callback
});
return self;
}
};
};
/**
The <code>FPSCounter</code> module tracks and displays the framerate.
window.engine = Engine
...
includedModules: ["FPSCounter"]
FPSColor: "#080"
@name FPSCounter
@fieldOf Engine
@module
@param {Object} I Instance variables
@param {Object} self Reference to the engine
*/
Engine.FPSCounter = function(I, self) {
var framerate;
Object.reverseMerge(I, {
showFPS: true,
FPSColor: "#FFF"
});
framerate = Framerate();
return self.bind("overlay", function(canvas) {
if (I.showFPS) {
canvas.font("bold 9pt consolas, 'Courier New', 'andale mono', 'lucida console', monospace");
canvas.drawText({
color: I.FPSColor,
position: Point(6, 18),
text: "fps: " + framerate.fps
});
}
return framerate.rendered();
});
};
Engine.GameState = function(I, self) {
var requestedState;
Object.reverseMerge(I, {
currentState: GameState()
});
requestedState = null;
self.bind("update", function(elapsedTime) {
I.currentState.trigger("beforeUpdate", elapsedTime);
I.currentState.trigger("update", elapsedTime);
return I.currentState.trigger("afterUpdate", elapsedTime);
});
self.bind("afterUpdate", function() {
var previousState;
if (requestedState != null) {
I.currentState.trigger("exit", requestedState);
self.trigger('stateExited', I.currentState);
previousState = I.currentState;
I.currentState = requestedState;
I.currentState.trigger("enter", previousState);
self.trigger('stateEntered', I.currentState);
return requestedState = null;
}
});
self.bind("draw", function(canvas) {
I.currentState.trigger("beforeDraw", canvas);
I.currentState.trigger("draw", canvas);
return I.currentState.trigger("overlay", canvas);
});
return {
add: function(classNameOrEntityData, entityData) {
var object;
if (entityData == null) {
entityData = {};
}
if (typeof classNameOrEntityData.isString === "function" ? classNameOrEntityData.isString() : void 0) {
entityData["class"] = classNameOrEntityData;
} else {
entityData = classNameOrEntityData;
}
self.trigger("beforeAdd", entityData);
object = I.currentState.add(entityData);
self.trigger("afterAdd", object);
return object;
},
camera: function(n) {
if (n == null) {
n = 0;
}
return self.cameras()[n];
},
cameras: function(newCameras) {
if (newCameras != null) {
I.currentState.cameras(newCameras);
return self;
} else {
return I.currentState.cameras();
}
},
fadeIn: function(options) {
if (options == null) {
options = {};
}
return self.cameras().invoke('fadeIn', options);
},
fadeOut: function(options) {
if (options == null) {
options = {};
}
return self.cameras().invoke('fadeOut', options);
},
flash: function(options) {
if (options == null) {
options = {};
}
return self.camera(options.camera).flash(options);
},
objects: function() {
return I.currentState.objects();
},
setState: function(newState) {
return requestedState = newState;
},
shake: function(options) {
if (options == null) {
options = {};
}
return self.camera(options.camera).shake(options);
},
saveState: function() {
return I.currentState.saveState();
},
loadState: function(newState) {
return I.currentState.loadState(newState);
},
reload: function() {
return I.currentState.reload();
}
};
};
/**
The <code>Joysticks</code> module gives the engine access to joysticks.
# First you need to add the joysticks module to the engine
window.engine = Engine
...
includedModules: ["Joysticks"]
# Then you need to get a controller reference
# id = 0 for player 1, etc.
controller = engine.controller(id)
# Point indicating direction primary axis is held
direction = controller.position()
# Check if buttons are held
controller.actionDown("A")
controller.actionDown("B")
controller.actionDown("X")
controller.actionDown("Y")
@name Joysticks
@fieldOf Engine
@module
@param {Object} I Instance variables
@param {Object} self Reference to the engine
*/
Engine.Joysticks = function(I, self) {
Joysticks.init();
self.bind("update", function() {
Joysticks.init();
return Joysticks.update();
});
return {
/**
Get a controller for a given joystick id.
@name controller
@methodOf Engine.Joysticks#
@param {Number} i The joystick id to get the controller of.
*/
controller: function(i) {
return Joysticks.getController(i);
}
};
};
/**
This module sets up the keyboard inputs for each engine update.
@name Keyboard
@fieldOf Engine
@module
@param {Object} I Instance variables
@param {Object} self Reference to the engine
*/
Engine.Keyboard = function(I, self) {
self.bind("beforeUpdate", function() {
return typeof updateKeys === "function" ? updateKeys() : void 0;
});
return {};
};
/**
This module provides methods for transitioning between levels.
@name Levels
@fieldOf Engine
@module
@param {Object} I Instance variables
@param {Object} self Reference to the engine
*/
Engine.Levels = function(I, self) {
var loadLevel;
Object.reverseMerge(I, {
levels: [],
currentLevel: -1,
currentLevelName: null
});
I.transitioning = false;
loadLevel = function(level) {
var levelState;
if (!I.transitioning) {
I.transitioning = true;
levelState = LevelState({
level: level
});
I.currentLevelName = level;
return engine.setState(levelState);
}
};
return {
/**
Load map for the next level.
engine.nextLevel()
@name nextLevel
@methodOf Engine#
*/
nextLevel: function() {
var level;
if (!I.transitioning) {
I.currentLevel += 1;
if (level = I.levels[I.currentLevel]) {
return loadLevel(level);
} else {
return engine.setState(GameOver());
}
}
},
/**
Load map named <code>level</code>
engine.goToLevel 'bossFight'
@name goToLevel
@methodOf Engine#
*/
goToLevel: function(level) {
return loadLevel(level);
},
/**
Reload the current level. Useful for retrying after a player dies.
engine.reloadLevel()
@name reloadLevel
@methodOf Engine#
*/
restartLevel: function() {
return loadLevel(I.currentLevelName);
},
reloadLevel: function() {
return self.restartLevel();
}
};
};
/**
This module clears or fills the canvas before drawing the scene.
@name Clear
@fieldOf Engine
@module
@param {Object} I Instance variables
@param {Object} self Reference to the engine
*/
Engine.Background = function(I, self) {
Object.reverseMerge(I, {
background: null,
backgroundColor: "#00010D",
clear: false
});
self.attrAccessor("clear", "backgroundColor");
self.bind("init", function() {
var _ref;
if ((_ref = I.background) != null ? typeof _ref.isString === "function" ? _ref.isString() : void 0 : void 0) {
return I.background = Sprite.loadByName(I.background);
}
});
self.bind("beforeDraw", function() {
if (I.clear) {
return I.canvas.clear();
} else if (I.background) {
return I.background.fill(I.canvas, 0, 0, App.width, App.height);
} else if (I.backgroundColor) {
return I.canvas.fill(I.backgroundColor);
}
});
return {};
};
/**
This module sets up the mouse inputs for each engine update.
@name Mouse
@fieldOf Engine
@module
@param {Object} I Instance variables
@param {Object} self Reference to the engine
*/
Engine.Mouse = function(I, self) {
self.bind("beforeUpdate", function() {
return typeof updateMouse === "function" ? updateMouse() : void 0;
});
return {};
};
/**
The <code>Selector</code> module provides methods to query the engine to find game objects.
@name Selector
@fieldOf Engine
@module
@param {Object} I Instance variables
@param {Object} self Reference to the engine
*/
Engine.Selector = function(I, self) {
var instanceMethods;
instanceMethods = {
set: function(attr, value) {
return this.each(function(item) {
return item.I[attr] = value;
});
}
};
return {
/**
Get the game object matching the given selector that is closest to the given position.
player = engine.add
x: 0
y: 0
enemy1 = engine.add
enemy: true
x: 10
y: 0
enemy2 = engine.add
enemy: true
x: 0
y: 15
player2 = engine.add
x: 0
y: 10
equals engine.closest(".enemy", player.position()), enemy1
equals engine.closest(".enemy", player2.position()), enemy2
@name closest
@methodOf Engine.selector
@param {String} selector
@param {Point} position
*/
closest: function(selector, position) {
return self.find(selector).sort(function(a, b) {
return Point.distanceSquared(position, a.position()) - Point.distanceSquared(position, b.position());
}).first();
},
/**
Get a selection of GameObjects that match the specified selector criteria. The selector language
can select objects by id, class, or attributes. Note that this method always returns an Array,
so if you are trying to find only one object you will need something like <code>engine.find("Enemy").first()</code>.
player = engine.add
class: "Player"
enemy = engine.add
class: "Enemy"
speed: 5
x: 0
distantEnemy = engine.add
class "Enemy"
x: 500
boss = engine.add
class: "Enemy"
id: "Boss"
x: 0
# to select an object by id use "#anId"
engine.find "#Boss"
# => [boss]
# to select an object by class use "MyClass"
engine.find "Enemy"
# => [enemy, distantEnemy, boss]
# to select an object by properties use ".someProperty" or ".someProperty=someValue"
engine.find ".speed=5"
# => [enemy]
# You may mix and match selectors.
engine.find "Enemy.x=0"
# => [enemy, boss] # doesn't return distantEnemy
@name find
@methodOf Engine#
@param {String} selector
@returns {Array} An array of the objects found
*/
each: function(selector, fn) {
return self.find(selector).each(function(obj, index) {
return fn(obj, index);
});
},
/**
Find all game objects that match the given selector.
@name find
@methodOf Engine.selector
@param {String} selector
*/
find: function(selector) {
var matcher, results;
results = [];
matcher = Engine.Selector.generate(selector);
self.objects().each(function(object) {
if (matcher.match(object)) {
return results.push(object);
}
});
return Object.extend(results, instanceMethods);
},
/**
Find the first game object that matches the given selector.
@name find
@methodOf Engine.selector
@param {String} selector
*/
first: function(selector) {
return self.find(selector).first();
}
};
};
Object.extend(Engine.Selector, {
parse: function(selector) {
return selector.split(",").invoke("trim");
},
process: function(item) {
var result;
result = /^(\w+)?#?([\w\-]+)?\.?([\w\-]+)?=?([\w\-]+)?/.exec(item);
if (result) {
if (result[4]) {
result[4] = result[4].parse();
}
return result.splice(1);
} else {
return [];
}
},
generate: function(selector) {
var ATTR, ATTR_VALUE, ID, TYPE, components;
components = Engine.Selector.parse(selector).map(function(piece) {
return Engine.Selector.process(piece);
});
TYPE = 0;
ID = 1;
ATTR = 2;
ATTR_VALUE = 3;
return {
match: function(object) {
var attr, attrMatch, component, idMatch, typeMatch, value, _i, _len;
for (_i = 0, _len = components.length; _i < _len; _i++) {
component = components[_i];
idMatch = (component[ID] === object.I.id) || !component[ID];
typeMatch = (component[TYPE] === object.I["class"]) || !component[TYPE];
if (attr = component[ATTR]) {
if ((value = component[ATTR_VALUE]) != null) {
attrMatch = object.I[attr] === value;
} else {
attrMatch = object.I[attr];
}
} else {
attrMatch = true;
}
if (idMatch && typeMatch && attrMatch) {
return true;
}
}
return false;
}
};
}
});
/**
The <code>Stats</code> module provides methods to query the engine to find game objects.
@name Stats
@fieldOf Engine
@module
@param {Object} I Instance variables
@param {Object} self Reference to the engine
*/
Engine.Stats = function(I, self) {
return {
measure: function(objects, field, frequency) {
if (frequency == null) {
frequency = 30;
}
},
gatherData: function() {
return self.find();
}
};
};
/**
The <code>Tilemap</code> module provides a way to load tilemaps in the engine.
@name Tilemap
@fieldOf Engine
@module
@param {Object} I Instance variables
@param {Object} self Reference to the engine
*/
Engine.Tilemap = function(I, self) {
var clearObjects, updating;
Object.extend(I, {
map: null
});
updating = false;
clearObjects = false;
self.bind("update", function() {
return updating = true;
});
self.bind("afterUpdate", function() {
updating = false;
if (clearObjects) {
self.objects().clear();
return clearObjects = false;
}
});
return {
/**
Loads a new may and unloads any existing map or entities.
@name loadMap
@methodOf Engine#
*/
loadMap: function(name, complete) {
clearObjects = updating;
return I.map = Tilemap.load({
name: name,
complete: complete,
entity: self.add
});
}
};
};
/**
The Expirable module deactivates a <code>GameObject</code> after a specified duration.
If a duration is specified the object will update that many times. If -1 is
specified the object will have an unlimited duration.
This module is included by default in <code>GameObjects</code>
enemy = GameObject
x: 50
y: 30
duration: 5
enemy.include Expirable
enemy.I.active
# => true
5.times ->
enemy.update(1)
enemy.I.active
# => false
@name Expirable
@module
@constructor
@param {Object} I Instance variables
@param {Core} self Reference to including object
*/
this.Expirable = function(I, self) {
var startingAlpha;
if (I == null) {
I = {};
}
Object.reverseMerge(I, {
duration: -1,
alpha: 1,
fadeOut: false
});
startingAlpha = I.alpha;
self.bind("update", function(dt) {
if (I.fadeOut) {
I.alpha = startingAlpha * (1 - (I.age / I.duration));
}
if (I.duration !== -1 && I.age >= I.duration) {
I.active = false;
}
return I.alpha = I.alpha.clamp(0, 1);
});
return {};
};
/**
The `Flickerable` module provides a method to flicker a sprite between its current opacity (alpha) and a given opacity.
player = GameObject
alpha: 0.9
player.include 'Flickerable'
# called with no arguments, flicker will toggle the player's alpha
# value between 0.9 (value provided above) and 0.5 (flickerable default)
# every 0.1 second, for a total of 2 seconds
player.flicker()
@name Flickerable
@module
@constructor
@param {Object} I Instance variables
@param {Core} self Reference to including object
*/
this.Flickerable = function(I, self) {
var frequencyLength, originalAlpha;
if (I == null) {
I = {};
}
Object.reverseMerge(I, {
flickerAlpha: 0.5,
flickerDuration: 0,
flickerFrequency: 0.1
});
originalAlpha = I.alpha;
frequencyLength = 0;
self.bind('update', function(elapsedTime) {
I.flickerDuration = I.flickerDuration.approach(0, elapsedTime);
frequencyLength += elapsedTime;
if (I.flickerDuration > 0) {
if (frequencyLength >= I.flickerFrequency) {
frequencyLength = 0;
if (I.alpha === I.flickerAlpha) {
return I.alpha = originalAlpha;
} else {
return I.alpha = I.flickerAlpha;
}
}
} else {
return I.alpha = originalAlpha;
}
});
return {
/**
A convenient way to set the flicker instance variables on a sprite. You can modify the
instance variables by hand but the suggested way to do it is through this method.
player = GameObject()
player.include(Flickerable)
player.flicker
duration: 5
frequency: 0.2
alpha: 0.3
# => This causes the sprite to flicker between full opacity
# => and 30% opacity every 0.2 seconds for 5 seconds
@name flicker
@methodOf Flickerable#
@param {Number} [duration=2] How long the effect lasts in seconds
@param {Number} [frequency=0.1] Number of seconds in between opacity changes
@param {Number} [alpha=0.5] Alpha value to toggle between
@returns {GameObject} The calling object
*/
flicker: function(options) {
if (options == null) {
options = {};
}
Object.reverseMerge(options, {
duration: 2,
frequency: 0.1,
alpha: 0.5
});
I.flickerDuration = options.duration;
I.flickerFrequency = options.frequency;
I.flickerAlpha = options.alpha;
return self;
}
};
};
/**
The Follow module provides a simple method to set an object's
direction so that it is pointed at another object.
The calculated direction is based on the center point of
each object.
This method relies on both objects having <code>position</code> methods.
Include this module by calling <code>self.include Follow</code>
player = GameObject
x: 50
y: 50
width: 10
height: 10
enemy = GameObject
x: 100
y: 50
width: 10
height: 10
velocity: Point(0, 0)
speed: 2
enemy.include Follow
# Make an enemy follow the player
enemy.follow(player)
# now the enemy's direction will point toward the player
enemy.I.direction
# => Point(-1, 0)
# you can use this direction to set a velocity for your object.
enemy.I.velocity = enemy.I.direction.scale(I.speed)
@name Follow
@module
@constructor
@param {Object} I Instance variables
@param {Core} self Reference to including object
*/
this.Follow = function(I, self) {
if (I == null) {
I = {};
}
Object.reverseMerge(I, {
velocity: Point(0, 0),
speed: 1
});
return {
/**
Set your velocity to follow another object.
enemy.follow(player)
# => The enemy now has it's velocity attribute set in
# the direction of the player, with magnitude equal to
# the enemy's speed
@name follow
@methodOf Follow#
@param {GameObject} obj The object you want to follow
*/
follow: function(obj) {
if (obj) {
return I.velocity = obj.position().subtract(self.position()).norm(I.speed);
}
}
};
};
/**
This object keeps track of framerate and displays it by creating and appending an
html element to the DOM.
Once created you call snapshot at the end of every rendering cycle.
@name Framerate
@constructor
*/
this.Framerate = function(options) {
var framerateUpdateInterval, framerates, numFramerates, renderTime, self, updateFramerate;
if (options == null) {
options = {};
}
numFramerates = 15;
framerateUpdateInterval = 250;
renderTime = -1;
framerates = [];
updateFramerate = function() {
return self.fps = framerates.average().round();
};
setInterval(updateFramerate, framerateUpdateInterval);
/**
Call this method everytime you render.
@name rendered
@methodOf Framerate#
*/
return self = {
rendered: function() {
var framerate, newTime, t;
if (renderTime < 0) {
return renderTime = new Date().getTime();
} else {
newTime = new Date().getTime();
t = newTime - renderTime;
framerate = 1000 / t;
framerates.push(framerate);
while (framerates.length > numFramerates) {
framerates.shift();
}
return renderTime = newTime;
}
}
};
};
/**
The default base class for all objects you can add to the engine.
GameObjects fire events that you may bind listeners to. Event listeners
may be bound with <code>object.bind(eventName, callback)</code>
@name GameObject
@extends Core
@constructor
@instanceVariables age, active, created, destroyed, solid, includedModules, excludedModules
*/
/**
Triggered when the object is created.
enemyCount = 0
enemy = engine.add
class: "Enemy"
enemy.bind 'create', ->
enemyCount++
@name create
@methodOf GameObject#
@event
*/
/**
Triggered when object is destroyed. Use
the destroy event to add particle effects, play sounds, etc.
bomb = GameObject()
bomb.bind 'destroy', ->
bomb.explode()
Sound.play "Kaboom"
@name destroy
@methodOf GameObject#
@event
*/
/**
Triggered during every update step.
player = GameObject()
player.bind 'step', ->
# check to see if keys are being pressed and
# change the player's velocity
if keydown.left
player.velocity(Point(-1, 0))
else if keydown.right
player.velocity(Point(1, 0))
else
player.velocity(Point(0, 0))
@name step
@methodOf GameObject#
@event
*/
/**
Triggered every update after the <code>step</code> event is triggered.
player = GameObject()
# we can really use the update and
# step events almost interchangebly
player.bind 'update', ->
# check to see if keys are being pressed and
# change the player's velocity
if keydown.left
player.velocity(Point(-1, 0))
else if keydown.right
player.velocity(Point(1, 0))
else
player.velocity(Point(0, 0))
@name update
@methodOf GameObject#
@event
*/
/**
Triggered when the object is removed from
the engine. Use the remove event to handle any clean up.
boss = GameObject()
boss.bind 'remove', ->
unlockDoorToLevel2()
@name remove
@methodOf GameObject#
@event
*/
this.GameObject = function(I) {
var self;
I || (I = {});
/**
@name {Object} I Instance variables
@memberOf GameObject#
*/
Object.reverseMerge(I, {
active: true,
created: false,
destroyed: false
});
self = Core(I).extend({
/**
Update the game object. This is generally called by the engine.
@name update
@methodOf GameObject#
*/
update: function(elapsedTime) {
if (I.active) {
self.trigger('update', elapsedTime);
}
return I.active;
},
/**
Triggers the create event if the object has not already been created.
@name create
@methodOf GameObject#
*/
create: function() {
if (!I.created) {
self.trigger('create');
}
return I.created = true;
},
/**
Destroys the object and triggers the destroyed event.
@name destroy
@methodOf GameObject#
*/
destroy: function() {
if (!I.destroyed) {
self.trigger('destroy');
}
I.destroyed = true;
return I.active = false;
}
});
GameObject.defaultModules.each(function(moduleName) {
return self.include(moduleName);
});
return self;
};
GameObject.defaultModules = ["Ageable", "Bounded", "Clampable", "Cooldown", "Drawable", "Expirable", "Follow", "GameObject.Meter", "Movable", "Rotatable", "TimedEvents", "Tween", "GameObject.Effect"];
/**
Construct an object instance from the given entity data.
@name construct
@memberOf GameObject
@param {Object} entityData
*/
GameObject.construct = function(entityData) {
if (entityData["class"]) {
return entityData["class"].constantize()(entityData);
} else {
return GameObject(entityData);
}
};
/**
A collection of effects to make your game juicy.
@name Effect
@fieldOf GameObject
@module
@constructor
@param {Object} I Instance variables
@param {Core} self Reference to including object
*/
GameObject.Effect = function(I, self) {
if (I == null) {
I = {};
}
return {
/**
A convenient way to fade out this object over time.
player = GameObject()
# Fade the player object out over the next 2 seconds.
player.fadeOut 2
# Fade out and then destroy
player.fadeOut, 0.25, ->
self.destroy()
@name fadeOut
@methodOf GameObject#
@param {Number} [duration=1] Time to fade out in seconds
@param {Function} [complete=null] The function to execute when fade out completes.
*/
fadeOut: function(duration, complete) {
if (duration == null) {
duration = 1;
}
return self.tween(duration, {
alpha: 0,
complete: complete
});
}
};
};
/**
The Metered module provides a simple drop-in
meter ui to track arbitrary numeric attributes.
player = GameObject
health: 100
heathMax: 100
enemy = GameObject
health: 500
someOtherObject = GameObject
player.meter 'health'
# => Sets up a health meter that will be drawn during the player overlay event
enemy.meter 'health'
# => Sets up a health meter that will be drawn during the enemy overlay event.
# Since healthMax wasn't provided, it is set to the value of I.health (500)
someOtherObject.meter 'turbo'
# => Sets up a turbo meter that will be drawn during the someOtherObject overlay event.
# Since neither turbo or turboMax were provided, they are both set to 100.
Metered module
@name Metered
@module
@constructor
@param {Object} I Instance variables
@param {GameObject} self Reference to including object
*/
GameObject.Meter = function(I, self) {
if (I == null) {
I = {};
}
Object.reverseMerge(I, {
meters: {}
});
self.bind('overlay', function(canvas) {
var backgroundColor, borderColor, borderRadius, borderWidth, color, height, meterData, name, ratio, show, width, x, y, _ref, _ref1, _ref2;
_ref = I.meters;
for (name in _ref) {
meterData = _ref[name];
backgroundColor = meterData.backgroundColor, (_ref1 = meterData.border, borderColor = _ref1.color, borderRadius = _ref1.radius, borderWidth = _ref1.width), color = meterData.color, height = meterData.height, show = meterData.show, width = meterData.width, x = meterData.x, y = meterData.y;
if (meterData.position != null) {
_ref2 = meterData.position, x = _ref2.x, y = _ref2.y;
}
if (!show) {
return;
}
ratio = (I[name] / I["" + name + "Max"]).clamp(0, 1);
canvas.drawRoundRect({
color: backgroundColor,
radius: borderRadius,
x: x,
y: y,
width: width,
height: height
});
canvas.drawRoundRect({
color: color,
x: x,
y: y,
radius: borderRadius,
width: width * ratio,
height: height
});
canvas.drawRoundRect({
x: x,
y: y,
width: width,
height: height,
radius: borderRadius,
stroke: {
color: borderColor,
width: borderWidth
}
});
}
});
return {
/**
Configures a meter to be drawn each overlay event.
player = GameObject
player.meter 'health',
border
color: 'brown'
radius: 3
color: 'pink'
height: 20
x: 5
y: 5
show: true
width: 150
# => Sets up a health meter, using all the configuration options
@name meter
@methodOf Metered#
@param {String} name The name of the property to meter
@param {Object} options The meter configuration options
@param {String} border: color Color of the meter's border
@param {Number} border: width Width of the meter's border
@param {String} color Color of the meter's inner rectangle
@param {Number} height Height of the meter
@param {Object} position An x, y object representing the position of the meter
@param {Number} x x position of the meter
@param {Number} y y position of the meter
@param {Number} border: radius Border radius of the meter
@param {Boolean} show Boolean to toggle whether of not to display the meter
@param {Number} width How wide the meter is
*/
meter: function(name, options) {
if (options == null) {
options = {};
}
Object.reverseMerge(options, {
backgroundColor: 'black',
border: {
color: 'white',
radius: 2,
width: 1.5
},
color: 'green',
height: 10,
x: 0,
y: 0,
show: true,
width: 100
});
if (I[name] == null) {
I[name] = 100;
}
if (!I["" + name + "Max"]) {
if (I[name]) {
I["" + name + "Max"] = I[name];
} else {
I["" + name + "Max"] = 100;
}
}
return I.meters[name] = options;
},
/**
Shows the named meter
player = GameObject
# creates a health meter but disables visibility
player.meter 'health'
show: false
# enables visibility for the meter named 'health'
player.showMeter 'health'
@name showMeter
@methodOf Metered#
@param {String} name The name of the meter to show
*/
showMeter: function(name) {
return I.meters[name].show = true;
},
/**
Hides the named meter
player = GameObject
# creates a health meter
player.meter 'health'
# disables visibility for the meter named 'health'
player.hideMeter 'health'
@name hideMeter
@methodOf Metered#
@param {String} name The name of the meter to hide
*/
hideMeter: function(name) {
return I.meters[name].show = false;
},
/**
Toggles visibility of the named meter
player = GameObject
# creates a health meter
player.meter 'health'
# toggles visibility for the meter named 'health'
player.toggleMeter 'health'
@name toggleMeter
@methodOf Metered#
@param {String} name The name of the meter to toggle
*/
toggleMeter: function(name) {
return I.meters[name].show = !I.meters[name].show;
}
};
};
/**
The Game Over class sets up a simple game state with restart instructions.
@see TextScreen
@name GameOver
@constructor
*/
/**
Transitions to the title state on user input.
@name update
@methodOf GameOver#
@event
*/
/**
Draws Game Over screen and reset instructions.
@name overlay
@methodOf GameOver#
@param {PixieCanvas} canvas
@event
*/
this.GameOver = function(I) {
var self;
if (I == null) {
I = {};
}
self = TextScreen(I);
self.bind('update', function() {
if (justPressed.any) {
return engine.delay(15, function() {
return engine.setState(TitleScreen());
});
}
});
self.bind("overlay", function(canvas) {
self.centerText(canvas, "Game Over");
return self.centerText(canvas, "Press any key to restart", {
size: 12,
y: App.height / 2 + 30
});
});
return self;
};
this.GameState = function(I) {
var queuedObjects, self;
if (I == null) {
I = {};
}
Object.reverseMerge(I, {
objects: []
});
queuedObjects = [];
self = Core(I).extend({
/**
The add method creates and adds an object to the game world. Two
other events are triggered around this one: beforeAdd and afterAdd.
# you can add arbitrary entityData and
# the engine will make it into a GameObject
engine.add
x: 50
y: 30
color: "red"
player = engine.add
class: "Player"
@name add
@methodOf Engine#
@param {Object} entityData The data used to create the game object.
@returns {GameObject}
*/
add: function(entityData) {
var object;
self.trigger("beforeAdd", entityData);
object = GameObject.construct(entityData);
object.create();
self.trigger("afterAdd", object);
if (I.updating) {
queuedObjects.push(object);
} else {
I.objects.push(object);
}
return object;
},
objects: function() {
return I.objects.copy();
}
});
self.bind("update", function(elapsedTime) {
var toKeep, toRemove, _ref;
I.updating = true;
I.objects.invoke("trigger", "beforeUpdate", elapsedTime);
_ref = I.objects.partition(function(object) {
return object.update(elapsedTime);
}), toKeep = _ref[0], toRemove = _ref[1];
I.objects.invoke("trigger", "afterUpdate", elapsedTime);
toRemove.invoke("trigger", "remove");
I.objects = toKeep.concat(queuedObjects);
queuedObjects = [];
return I.updating = false;
});
self.include("GameState.Cameras");
self.include("GameState.SaveState");
return self;
};
GameState.Cameras = function(I, self) {
var cameras;
cameras = [Camera()];
self.bind('update', function(elapsedTime) {
return self.cameras().invoke('trigger', 'update', elapsedTime);
});
self.bind('afterUpdate', function(elapsedTime) {
return self.cameras().invoke('trigger', 'afterUpdate', elapsedTime);
});
self.bind('draw', function(canvas) {
return self.cameras().invoke('trigger', 'draw', canvas, self.objects());
});
self.bind('overlay', function(canvas) {
return self.cameras().invoke('trigger', 'overlay', canvas, self.objects());
});
return {
addCamera: function(data) {
return cameras.push(Camera(data));
},
/**
Returns the array of camera objects.
@name cameras
@methodOf Engine#
@returns {Array}
*/
cameras: function(newCameras) {
if (newCameras) {
cameras = newCameras;
return self;
} else {
return cameras;
}
}
};
};
/**
The <code>SaveState</code> module provides methods to save and restore the current game state.
@name SaveState
@fieldOf GameState
@module
@param {Object} I Instance variables
@param {Object} self Reference to the game state
*/
GameState.SaveState = function(I, self) {
var savedState;
savedState = null;
return {
/**
Save the current game state and returns a JSON object representing that state.
engine.bind 'update', ->
if justPressed.s
engine.saveState()
@name saveState
@methodOf GameState#
@returns {Array} An array of the instance data of all objects in the game state
*/
saveState: function() {
return savedState = I.objects.map(function(object) {
return Object.extend({}, object.I);
});
},
/**
Loads the game state passed in, or the last saved state, if any.
engine.bind 'update', ->
if justPressed.l
# loads the last saved state
engine.loadState()
if justPressed.o
# removes all game objects, then reinstantiates
# them with the entityData passed in
engine.loadState([{x: 40, y: 50, class: "Player"}, {x: 0, y: 0, class: "Enemy"}, {x: 500, y: 400, class: "Boss"}])
@name loadState
@methodOf GameState#
@param [newState] An arraf of object instance data to load.
*/
loadState: function(newState) {
if (newState || (newState = savedState)) {
I.objects.invoke("trigger", "remove");
I.objects = [];
return newState.each(function(objectData) {
return self.add(Object.extend({}, objectData));
});
}
},
/**
Reloads the current game state, useful for hotswapping code.
engine.I.objects.each (object) ->
# bring all objects to (0, 0) for some reason
object.I.x = 0
object.I.y = 0
# reload all objects to make sure
# they are at (0, 0)
engine.reload()
@name reload
@methodOf GameState#
*/
reload: function() {
var oldObjects;
oldObjects = I.objects;
I.objects = [];
return oldObjects.each(function(object) {
object.trigger("remove");
return self.add(object.I);
});
}
};
};
/**
The <code>SingleCamera</code> module provides provides a single camera view of the game.
Its transform can be adjusted to view different areas and provide various camera effects.
@name SingleCamera
@fieldOf GameState
@module
@param {Object} I Instance variables
@param {Object} self Reference to the game state
*/
GameState.SingleCamera = function(I, self) {
Object.reverseMerge(I, {
cameraTransform: Matrix.IDENTITY,
zSort: true
});
self.attrAccessor("cameraTransform");
self.bind("draw", function(canvas) {
return canvas.withTransform(I.cameraTransform, function(canvas) {
var drawObjects;
drawObjects = self.objects();
if (I.zSort) {
drawObjects.sort(function(a, b) {
return a.I.zIndex - b.I.zIndex;
});
}
return drawObjects.invoke("draw", canvas);
});
});
return {};
};
/**
A Game State that loads the map for a given level and transitions into the level.
@see GameState
@name LevelState
@constructor
@param {Number} duration Amount of time in frames it takes to fade into the level
@param {String} level name of the map to load
*/
/**
Fades in the current level and loads the map.
@name enter
@methodOf LevelState#
@event
*/
this.LevelState = function(I) {
var self;
if (I == null) {
I = {};
}
Object.reverseMerge(I, {
duration: 10,
level: 'level1'
});
self = GameState(I);
self.bind("enter", function() {
engine.fadeIn({
duration: I.duration
});
return engine.loadMap(I.level, function() {
return engine.I.transitioning = false;
});
});
return self;
};
/**
The Movable module automatically updates the position and velocity of
GameObjects based on the velocity and acceleration. It does not check
collisions so is probably best suited to particle effect like things.
player = GameObject
x: 0
y: 0
velocity: Point(0, 0)
acceleration: Point(1, 0)
maxSpeed: 2
player.include(Movable)
# => `velocity is {x: 0, y: 0} and position is {x: 0, y: 0}`
player.update(1)
# => `velocity is {x: 1, y: 0} and position is {x: 1, y: 0}`
player.update(1)
# => `velocity is {x: 2, y: 0} and position is {x: 3, y: 0}`
# we've hit our maxSpeed so our velocity won't increase
player.update(1)
# => `velocity is {x: 2, y: 0} and position is {x: 5, y: 0}`
@name Movable
@module
@constructor
@param {Object} I Instance variables
@param {Core} self Reference to including object
*/
this.Movable = function(I, self) {
if (I == null) {
I = {};
}
Object.reverseMerge(I, {
acceleration: Point(0, 0),
velocity: Point(0, 0)
});
I.acceleration = Point(I.acceleration.x, I.acceleration.y);
I.velocity = Point(I.velocity.x, I.velocity.y);
self.attrReader("velocity", "acceleration");
self.unbind(".Movable");
return self.bind('update.Movable', function(dt) {
var currentSpeed;
I.velocity = I.velocity.add(I.acceleration.scale(dt));
if (I.maxSpeed != null) {
currentSpeed = I.velocity.magnitude();
if (currentSpeed > I.maxSpeed) {
I.velocity = I.velocity.scale(I.maxSpeed / currentSpeed);
}
}
I.x += I.velocity.x * dt;
return I.y += I.velocity.y * dt;
});
};
/**
Creates an oscillator function with the given parameters.
@name Oscillator
@constructor
@param {Number} amplitude How much to scale the oscillator function value
@param {Number} period How fast the osciallator function repeats
@param {Number} offset How much to offset the created oscillator function. Useful for translating between sin and cosine functions.
*/
this.Oscillator = function(options) {
var amplitude, offset, period;
if (options == null) {
options = {};
}
amplitude = options.amplitude, period = options.period, offset = options.offset;
if (amplitude == null) {
amplitude = 1;
}
if (period == null) {
period = 1;
}
if (offset == null) {
offset = 0;
}
return function(t) {
return amplitude * Math.cos(Math.TAU * t / period + offset);
};
};
/**
@name ResourceLoader
@namespace
Helps access the assets in your game.
*/
(function() {
var ResourceLoader, typeTable;
typeTable = {
images: "png",
data: "json",
tilemaps: "tilemap"
};
ResourceLoader = {
/**
Return the url for a particular asset.
ResourceLoader.urlFor("images", "player")
# => This returns the url for the file "player.png" in your images directory.
@name urlFor
@methodOf ResourceLoader#
@param {String} directory The directory your file is in.
@param {String} name The name of the file.
@returns {String} The full url of your asset
*/
urlFor: function(directory, name) {
var type, _ref;
directory = (typeof App !== "undefined" && App !== null ? (_ref = App.directories) != null ? _ref[directory] : void 0 : void 0) || directory;
type = typeTable[directory];
return "" + BASE_URL + "/" + directory + "/" + name + "." + type + "?" + MTIME;
}
};
return (typeof exports !== "undefined" && exports !== null ? exports : this)["ResourceLoader"] = ResourceLoader;
})();
/**
The Rotatable module rotates the object
based on its rotational velocity.
player = GameObject
x: 0
y: 0
rotationalVelocity: Math.PI / 64
player.I.rotation
# => 0
player.update(1)
player.I.rotation
# => 0.04908738521234052 # Math.PI / 64
player.update(1)
player.I.rotation
# => 0.09817477042468103 # 2 * (Math.PI / 64)
@name Rotatable
@module
@constructor
@param {Object} I Instance variables
@param {Core} self Reference to including object
*/
this.Rotatable = function(I, self) {
if (I == null) {
I = {};
}
Object.reverseMerge(I, {
rotation: 0,
rotationalVelocity: 0
});
self.bind('update', function(dt) {
return I.rotation += I.rotationalVelocity * dt;
});
return {};
};
/**
The Sprite class provides a way to load images for use in games.
By default, images are loaded asynchronously. A proxy object is
returned immediately. Even though it has a draw method it will not
draw anything to the screen until the image has been loaded.
@name Sprite
@constructor
*/
(function() {
var LoaderProxy, Sprite, spriteCache;
LoaderProxy = function() {
return {
draw: function() {},
fill: function() {},
frame: function() {},
update: function() {},
width: null,
height: null,
image: null
};
};
spriteCache = {};
Sprite = function(image, sourceX, sourceY, width, height) {
sourceX || (sourceX = 0);
sourceY || (sourceY = 0);
width || (width = image.width);
height || (height = image.height);
return {
/**
Draw this sprite on the given canvas at the given position.
@name draw
@methodOf Sprite#
@param {PowerCanvas} canvas Reference to the canvas to draw the sprite on
@param {Number} x Position on the x axis to draw the sprite
@param {Number} y Position on the y axis to draw the sprite
*/
draw: function(canvas, x, y) {
return canvas.drawImage(image, sourceX, sourceY, width, height, x, y, width, height);
},
/**
Draw this sprite on the given canvas tiled to the x, y,
width, and height dimensions specified.
@name fill
@methodOf Sprite#
@param {PowerCanvas} canvas Reference to the canvas to draw the sprite on
@param {Number} x Position on the x axis to draw the sprite
@param {Number} y Position on the y axis to draw the sprite
@param {Number} width How far to tile the sprite on the x-axis
@param {Number} height How far to tile the sprite on the y-axis
@param {String} repeat Repeat options. Can be `repeat-x`, `repeat-y`, `no-repeat`, or `repeat`. Defaults to `repeat`
*/
fill: function(canvas, x, y, width, height, repeat) {
var pattern;
if (repeat == null) {
repeat = "repeat";
}
pattern = canvas.createPattern(image, repeat);
return canvas.drawRect({
x: x,
y: y,
width: width,
height: height,
color: pattern
});
},
width: width,
height: height,
image: image
};
};
/**
Loads all sprites from a sprite sheet found in
your images directory, specified by the name passed in.
@name loadSheet
@methodOf Sprite
@param {String} name Name of the spriteSheet image in your images directory
@param {Number} tileWidth Width of each sprite in the sheet
@param {Number} tileHeight Height of each sprite in the sheet
@returns {Array} An array of sprite objects
*/
Sprite.loadSheet = function(name, tileWidth, tileHeight) {
var image, sprites, url;
url = ResourceLoader.urlFor("images", name);
sprites = [];
image = new Image();
image.onload = function() {
var imgElement;
imgElement = this;
return (image.height / tileHeight).times(function(row) {
return (image.width / tileWidth).times(function(col) {
return sprites.push(Sprite(imgElement, col * tileWidth, row * tileHeight, tileWidth, tileHeight));
});
});
};
image.src = url;
return sprites;
};
/**
Loads a sprite from a given url.
@name load
@methodOf Sprite
@param {String} url
@param {Function} [loadedCallback]
@returns {Sprite} A sprite object
*/
Sprite.load = function(url, loadedCallback) {
var img, proxy, sprite;
if (sprite = spriteCache[url]) {
if (loadedCallback != null) {
loadedCallback.defer(sprite);
}
return sprite;
}
img = new Image();
proxy = LoaderProxy();
img.onload = function() {
spriteCache[url] = Object.extend(proxy, Sprite(this));
return typeof loadedCallback === "function" ? loadedCallback(proxy) : void 0;
};
img.src = url;
return proxy;
};
/**
Loads a sprite with the given pixie id.
@name fromPixieId
@methodOf Sprite
@param {Number} id Pixie Id of the sprite to load
@param {Function} [callback] Function to execute once the image is loaded. The sprite proxy data is passed to this as a parameter.
@returns {Sprite}
*/
Sprite.fromPixieId = function(id, callback) {
return Sprite.load("http://pixieengine.com/s3/sprites/" + id + "/original.png", callback);
};
/**
A sprite that draws nothing.
@name EMPTY
@fieldOf Sprite
@constant
@returns {Sprite}
*/
/**
A sprite that draws nothing.
@name NONE
@fieldOf Sprite
@constant
@returns {Sprite}
*/
Sprite.EMPTY = Sprite.NONE = LoaderProxy();
/**
Loads a sprite from a given url.
@name fromURL
@methodOf Sprite
@param {String} url The url where the image to load is located
@param {Function} [callback] Function to execute once the image is loaded. The sprite proxy data is passed to this as a parameter.
@returns {Sprite}
*/
Sprite.fromURL = Sprite.load;
/**
Loads a sprite with the given name.
@name loadByName
@methodOf Sprite
@param {String} name The name of the image in your images directory
@param {Function} [callback] Function to execute once the image is loaded. The sprite proxy data is passed to this as a parameter.
@returns {Sprite}
*/
Sprite.loadByName = function(name, callback) {
return Sprite.load(ResourceLoader.urlFor("images", name), callback);
};
return (typeof exports !== "undefined" && exports !== null ? exports : this)["Sprite"] = Sprite;
})();
/**
The Text Effect class provides a method to display moving text onscreen, fading out the text over the effect duration.
# adds a TextEffect to the engine at (60, 100)
engine.add 'TextEffect'
x: 60
y: 100
@name TextEffect
@constructor
*/
/**
Updates the position of the text based on the effect velocity. Updates the
alpha based on the elapsed time since the effect creation.
@name update
@methodOf TextEffect#
@event
*/
/**
Draws text from `I.textShadow` `I.text`.
@name draw
@methodOf TextEffect#
@param {PixieCanvas} canvas
@event
*/
this.TextEffect = function(I) {
var self;
if (I == null) {
I = {};
}
Object.reverseMerge(I, {
color: Color('green'),
duration: -1,
font: '20px Helvetica',
text: '100',
textShadow: Color('black'),
alpha: 1,
rotation: 0,
velocity: Point(0, 0)
});
self = GameObject(I);
self.bind("update", function() {
if (I.rotationalVelocity != null) {
I.rotation += I.rotationalVelocity;
}
return I.alpha = (1 - (I.age / I.duration)).clamp(0, 1);
});
self.unbind("draw");
self.bind("draw", function(canvas) {
if (!I.color.channels) {
I.color = Color(I.color);
}
if (!I.textShadow.channels) {
I.textShadow = Color(I.textShadow);
}
I.color.a = I.alpha;
I.textShadow.a = I.alpha;
I.width = canvas.measureText(I.text);
canvas.font(I.font);
canvas.drawText({
color: I.textShadow,
x: 1 - I.width,
y: 1,
text: I.text
});
return canvas.drawText({
color: I.color,
x: 0 - I.width,
y: 0,
text: I.text
});
});
return self;
};
/**
`TextEffect.Floating` is a simple subclass of `TextEffect`. It provides some defaults
to move the text upward and fade it out over 0.5 seconds.
# adds a FloatingTextEffect to the engine
# at (50, 50). This effect will float upward
# at 90 pixels/sec and will fadeOut over 0.5 seconds
engine.add 'TextEffect.Floating'
x: 50
y: 50
@see TextEffect
@name Floating
@fieldOf TextEffect
@constructor
*/
TextEffect.Floating = function(I) {
if (I == null) {
I = {};
}
Object.reverseMerge(I, {
duration: 0.5,
velocity: Point(0, -90)
});
return TextEffect(I);
};
/**
The Text Screen class is a GameState that provides convenience methods for drawing text to screen.
@name TextScreen
@constructor
*/
this.TextScreen = function(I) {
var self;
if (I == null) {
I = {};
}
Object.reverseMerge(I, {
font: 'Helvetica',
fontSize: 24,
fontColor: 'white',
yPosition: App.height / 2
});
return self = GameState(I).extend({
/**
Draw center aligned text at the given y position.
screen = TextScreen()
screen.centerText canvas, 'Centering text is easy'
@name centerText
@methodOf TextScreen#
@param {PixieCanvas} canvas The canvas to draw on
@param {String} text The text to draw
@param {Object} options These include font, size, color, and yPosition
*/
centerText: function(canvas, text, options) {
var color, font, size, yPosition;
if (options == null) {
options = {};
}
font = options.font || I.font;
size = options.size || I.fontSize;
color = options.color || I.fontColor;
yPosition = options.y || I.yPosition;
canvas.font("" + size + "px " + font);
return canvas.centerText({
y: yPosition,
text: text,
color: color
});
}
});
};
(function() {
var Map, Tilemap, loadByName;
Map = function(data, entityCallback) {
var entity, loadEntities, spriteLookup, tileHeight, tileWidth, uuid, _ref;
tileHeight = data.tileHeight;
tileWidth = data.tileWidth;
spriteLookup = {};
_ref = App.entities;
for (uuid in _ref) {
entity = _ref[uuid];
spriteLookup[uuid] = Sprite.fromURL(entity.tileSrc);
}
loadEntities = function() {
if (!entityCallback) {
return;
}
return data.layers.each(function(layer, layerIndex) {
var instance, instanceData, instances, x, y, _i, _len, _results;
if (instances = layer.instances) {
_results = [];
for (_i = 0, _len = instances.length; _i < _len; _i++) {
instance = instances[_i];
x = instance.x, y = instance.y, uuid = instance.uuid;
instanceData = Object.extend({
layer: layerIndex,
sprite: spriteLookup[uuid],
x: x + tileWidth / 2,
y: y + tileHeight / 2
}, App.entities[uuid], instance.properties);
_results.push(entityCallback(instanceData));
}
return _results;
}
});
};
loadEntities();
return data;
};
Tilemap = function(name, callback, entityCallback) {
return fromPixieId(App.Tilemaps[name], callback, entityCallback);
};
loadByName = function(name, callback, entityCallback) {
var proxy, url;
url = ResourceLoader.urlFor("tilemaps", name);
proxy = {};
$.getJSON(url, function(data) {
Object.extend(proxy, Map(data, entityCallback));
return typeof callback === "function" ? callback(proxy) : void 0;
});
return proxy;
};
Tilemap.load = function(options) {
if (options.pixieId) {
return fromPixieId(options.pixieId, options.complete, options.entity);
} else if (options.name) {
return loadByName(options.name, options.complete, options.entity);
}
};
return (typeof exports !== "undefined" && exports !== null ? exports : this)["Tilemap"] = Tilemap;
})();
/**
The TimedEvents module allows arbitrary code to be executed at set intervals. <code>GameObject</code> includes this module by default
TimedEvents module
@name TimedEvents
@module
@constructor
@param {Object} I Instance variables
*/
this.TimedEvents = function(I, self) {
if (I == null) {
I = {};
}
Object.reverseMerge(I, {
everyEvents: [],
delayEvents: []
});
self.bind("update", function(elapsedTime) {
var event, firingEvents, fn, period, _i, _len, _ref, _ref1;
_ref = I.everyEvents;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
event = _ref[_i];
fn = event.fn, period = event.period;
while (event.lastFired < I.age + elapsedTime) {
self.sendOrApply(fn);
event.lastFired += period;
}
}
_ref1 = I.delayEvents.partition(function(event) {
return (event.delay -= elapsedTime) >= 0;
}), I.delayEvents = _ref1[0], firingEvents = _ref1[1];
return firingEvents.each(function(event) {
return self.sendOrApply(event.fn);
});
});
return {
/**
Execute <code>fn</code> every <code>n</code> frames.
player = GameObject()
player.include TimedEvents
# doSomething is called every 4 seconds
player.every 4, ->
doSomething()
@name every
@methodOf TimedEvents#
@param {Number} n Number of frames to wait before executing the callback
@param {Function} fn Code to execute after <code>n</code> frames has passed
*/
every: function(period, fn) {
if (!(period > 0)) {
return;
}
return I.everyEvents.push({
fn: fn,
period: period,
lastFired: I.age
});
/**
Execute a callback after a number of seconds have passed.
self.delay 5, ->
engine.add
class: "Ghost"
@name delay
@methodOf TimedEvents#
@param {Number} steps The number of steps to wait before executing the callback
@param {Function} callback The callback to be executed.
@returns {Engine} self
*/
},
delay: function(seconds, fn) {
I.delayEvents.push({
delay: seconds,
fn: fn
});
return self;
},
sendOrApply: function() {
var args, fn;
fn = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
if (typeof fn === "function") {
return fn.apply(self, args);
} else {
return self.send.apply(self, [fn].concat(__slice.call(args)));
}
}
};
};
/**
The Title Screen class sets up a simple game title screen using <code>App.name</code>
@see TextScreen
@name TitleScreen
@constructor
*/
/**
Goes to the next level on any user input.
@name update
@methodOf TitleScreen#
@event
*/
/**
Overlays the title text in the middle of the screen. Uses <code>App.name</code>
@name overlay
@methodOf TitleScreen#
@param {PixieCanvas} canvas
@event
*/
this.TitleScreen = function(I) {
var self;
if (I == null) {
I = {};
}
self = TextScreen(I);
self.bind('update', function() {
if (justPressed.any) {
return engine.nextLevel();
}
});
self.bind("overlay", function(canvas) {
self.centerText(canvas, App.name);
return self.centerText(canvas, "Press any key to start", {
size: 12,
y: App.height / 2 + 30
});
});
return self;
};
/**
The <code>Tween</code> module provides a method to tween object properties.
@name Tween
@module
@constructor
@param {Object} I Instance variables
@param {Core} self Reference to including object
*/
this.Tween = function(I, self) {
if (I == null) {
I = {};
}
Object.reverseMerge(I, {
activeTweens: {}
});
self.bind("update", function(elapsedTime) {
var data, easingFunction, property, t, _base, _base1, _ref, _results;
t = I.age + elapsedTime;
_ref = I.activeTweens;
_results = [];
for (property in _ref) {
data = _ref[property];
if (t >= data.endTime) {
I[property] = data.end;
if (typeof (_base = I.activeTweens[property]).complete === "function") {
_base.complete();
}
_results.push(delete I.activeTweens[property]);
} else {
if (typeof (_base1 = data.easing).isString === "function" ? _base1.isString() : void 0) {
easingFunction = Easing[data.easing](data.start, data.end);
} else {
easingFunction = data.easing;
}
_results.push(I[property] = easingFunction((t - data.startTime) / data.duration));
}
}
return _results;
});
return {
/**
Modify the object's properties over time.
player = GameObject()
player.tween 30,
x: 50
y: 50
easing: "quadratic"
player = GameObject()
player.tween 30,
x: 150
y: 150
complete: ->
player.dance()
@name tween
@methodOf Tween#
@param {Number} duration How long (in frames) until the object's properties reach their final values.
@param {Object} properties Which properties to tween. Set the `easing` property to specify the easing function.
*/
tween: function(duration, properties) {
var complete, easing, property, target, _results;
properties = Object.extend({}, properties);
easing = properties.easing || "linear";
complete = properties.complete;
delete properties.easing;
delete properties.complete;
_results = [];
for (property in properties) {
target = properties[property];
_results.push(I.activeTweens[property] = {
complete: complete,
end: target,
start: I[property],
easing: easing,
duration: duration,
startTime: I.age,
endTime: I.age + duration
});
}
return _results;
}
};
};
.intro(style="background-image: url(http://a2.pixiecdn.com/9756865efbcd9ea98cdc0c8636a1fa76343891d1)")
.content-wrap
.content
%h1 Trash Robots
%p Build your robot dream team in 10 seconds from garbage.
%button Start
- on "click", ->
- console.log "clicked"
- goToSetup()
@Laser = (I={}) ->
Object.defaults I,
color: "cyan"
duration: 0.5
damage: 2
self = GameObject(I)
self.on "update", ->
closest = engine.closest("Robot.team=#{+!I.source.I.team}", self.position())
if closest
I.target = closest
I.target.I.health -= I.damage
self.destroy()
self.on "destroy", ->
engine.add "Ray",
start: I.source.position()
end: I.target.position()
color: I.color
return self
["log", "info", "error", "warn"].each (name) ->
window[name] = (args...)->
console[name](args...) if console?
{$root, gist} = ENV
appRoot = $ "<div>",
class: "app"
$root.append(appRoot)
$root = appRoot
# HACK: Load JS Libs
Object.keys(gist.files).each (name) ->
# Load all js files except build
if name.extension() is "js" and name.withoutExtension() != "build"
Function(gist.files[name].content)()
# Apply our styles
if styleContent = gist.files["style.css"]?.content
$root.append $("<style>",
html: styleContent
)
$root.append HAMLjr.templates.arena()
$root.append HAMLjr.templates.intro()
countdownInterval = null
setupTimeLimit = 10
@goToSetup = ->
$(".setup").remove()
setupConfig = Setup()
$root.append HAMLjr.templates.setup(setupConfig)
$(".intro").hide()
# TODO: Stop game if playing
setupStartedAt = +new Date
clearInterval countdownInterval
countdownInterval = setInterval ->
time = (setupStartedAt + setupTimeLimit * 1000 - (+new Date)) / 1000
size = (time % 1) * 20 + 40
color = "lime"
color = "yellow" if time < 6
color = "red" if time < 3
if time < 0
clearInterval countdownInterval
startGame(setupConfig)
else
$(".setup .time")
.text(time.toFixed(2))
.css
fontSize: size
color: color
, 15
canvas = $root.children("canvas").pixieCanvas()
canvas.fill("gray")
@engine = Engine
canvas: canvas
FPS: 60
startGame = (config) ->
$(".setup").hide()
$(".intro").hide()
engine.start()
config.robots.each (data, i) ->
y = (i + 1) * App.height/4
engine.add "Robot", Object.extend(data,
y: y
)
engine.add "Robot",
color: "red"
x: App.width
y: y
headingInitial: 0.5.turns
team: 1
engine.on "overlay", (canvas) ->
canvas.drawText
x: 10
y: 20
color: "white"
text: engine.objects().length
objectCollision = (a, b) ->
Collision.circular(a.circle(), b.circle())
engine.on "update", (dt) ->
Collision.collide "Missile", "Robot", (missile, robot) ->
# Can't hit ourselves
return if missile.I.source is robot
missile.destroy()
, objectCollision
Collision.collide "Explosion, Blade, Flame", "Robot", (explosion, robot) ->
explosion.hit(robot, dt)
, objectCollision
Collision.collide "Explosion", "Missile", (explosion, missile) ->
missile.destroy()
, objectCollision
engine.find("Missile").eachPair (a, b) ->
if objectCollision(a, b)
a.destroy()
b.destroy()
@Missile = (I={}) ->
Object.defaults I,
speed: 250
heading: 0
radius: 10
self = GameObject(I)
self.on "destroy", ->
engine.add "Explosion",
x: I.x
y: I.y
self.on "update", ->
I.velocity = Point.fromAngle(I.heading).scale(I.speed)
# Explode if outside arena
if I.x <= 0 or I.x >= App.width or I.y <= 0 or I.y >= App.height
self.I.x = self.I.x.clamp(0, App.width)
self.I.y = self.I.y.clamp(0, App.height)
self.destroy()
return self
@Motor = (I={}) ->
Object.defaults I,
phaseOffset: 0 # Seconds
type: "constant"
switchCooldown: 0
switchDelay: 0.5
lastPower: 0
self = GameObject(I).extend
power: ->
if I.switchCooldown
# Don't switch
lastPower
else
I.switchCooldown += I.switchDelay
lastPower = switch I.type
when "periodic"
Math.sin((I.age + I.phaseOffset) * Math.TAU) + 0.5
when "jittery"
rand() * 0.5
when "slow"
0.5
else # constant
1
self.cooldown "switchCooldown"
return self
@Motor.random = ->
[
{type: "slow"}
{type: "constant"}
{type: "jittery"}
{type: "periodic", phaseOffset: rand(), switchDelay: 0}
].rand()
@Navigation = (I={}) ->
Object.defaults I,
type: "facing"
switchDelay: 2
switchCooldown: 0
self = GameObject(I).extend
navigate: (source) ->
if I.switchCooldown
# Don't switch
else
I.switchCooldown += I.switchDelay
switch I.type
when "random"
rand() * Math.TAU
when "spiral"
10 * Math.log(source.I.age/10 + 1) + source.I.headingInitial
when "strafe"
if rand() > 0.5
source.I.facing + 0.25.turns
else
source.I.facing - 0.25.turns
else # facing
source.I.facing
self.cooldown "switchCooldown"
return self
@Navigation.random = ->
[
{type: "facing"}
{type: "spiral", switchDelay: 0.25}
{type: "random"}
{type: "strafe"}
].rand()
{
"width": 800,
"height": 450
}
`!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);`
@PointDefense = (I={}) ->
Object.defaults I,
color: "purple"
duration: 0.5
range: 300
self = GameObject(I)
self.on "update", ->
closest = engine.find("Missile").select (missile) ->
missile.I.source.I.team != I.source.I.team
.map (missile) ->
{
distance: Point.distance(self.position(), missile.position())
object: missile
}
.sort ({distance:a}, {distance:b}) ->
b - a
.first()
if closest and closest.distance <= I.range
I.target = closest.object
I.target.destroy()
self.destroy()
self.on "destroy", ->
engine.add "Ray",
start: I.source.position()
end: I.target.position()
color: I.color
return self
@Ray = (I={}) ->
Object.defaults I,
duration: 0.25
self = GameObject(I)
self.on "update", ->
I.alpha = 1 - I.age / I.duration
self.unbind "draw"
self.bind "draw", (canvas) ->
canvas.drawLine
start: I.start
end: I.end
color: I.color
return self
@Robot = (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 # For explosion collisions
team: 0
motors: [
Motor.random()
]
navigation: Navigation.random()
targetting: Targetting.random()
weapons: [
Weapon.random()
]
# Serializability
weapons = I.weapons.map Weapon
motors = I.motors.map Motor
navigation = Navigation I.navigation
targetting = Targetting I.targetting
self = GameObject(I)
self.clampToBounds()
meter = ->
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)
self.meter "health",
color: color
x: I.x - I.width/2
y: I.y - I.height/2 - 20
width: I.width
self.on "update", (elapsedTime) ->
if I.health <= 0
self.destroy()
# Health Meter
meter()
# Navigation
navigation.update(elapsedTime)
I.heading = navigation.navigate(self) or I.heading
# Targetting
targetting.update(elapsedTime)
I.facing = targetting.target(self) or I.facing
# Motor
motors.invoke("update", elapsedTime)
I.power = motors.invoke("power").sum().clamp(0, 2)
# Weapons
# Apply cooldowns and fire
weapons.invoke("update", elapsedTime)
weapons.invoke("fire", self, elapsedTime) # TODO: Pass additional info?
# Apply Movement
I.velocity = Point.fromAngle(I.heading).scale(I.speed * I.power)
self.on "afterUpdate", (elapsedTime) ->
motors.invoke("trigger", "afterUpdate", elapsedTime)
drawDebug = (canvas) ->
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))
canvas.drawLine
start: p
end: end
color: "rgb(0, 255, 0)"
self.on "draw", (canvas) ->
canvas.font("bold 16px consolas, 'Courier New', 'andale mono', 'lucida console', monospace")
canvas.centerText
color: "white"
x: 0
y: -I.height/2
text: I.name
self.on "overlay", drawDebug
self.on "destroy", ->
engine.add "Explosion",
x: I.x
y: I.y
return self
randomName = ->
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()
"#{prefix}#{name}#{suffix}"
defaultBot = ->
bot = {name: randomName(), motors: [], weapons: [], targetting: null, navigation: null}
# TODO: OMG this blows
bot.oNavigation = Observable("")
bot.oWeapons = Observable("")
bot.oMotors = Observable("")
return bot
defaultItem = ->
{type: "default"}
@Setup = (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 = (item, slot) ->
I.robots.each (robot) ->
self[slot].remove(item)
add = (robot, item, slot) ->
if slot.lastIndexOf('s') is slot.length - 1
if robot[slot].length < 2
removeFromArray(item, slot)
robot[slot].push item
else
if robot[slot] is null
removeFromArray(item, slot)
robot[slot] = item
# Update stuff
I.robots.each (robot) ->
robot.oMotors(robot.motors.map((m)->m.type.titleize()).join(","))
robot.oWeapons(robot.weapons.map((w)->w.projectile.underscore().titleize()).join(","))
robot.oNavigation(robot.navigation?.type.titleize() or "")
self = Model(I).extend
chooseItem: (item, slot) ->
add(activeRobot, item, slot)
chooseRobot: (robot) ->
activeRobot = robot
self.observeAll()
return self
.setup(style="background-image: url(http://a3.pixiecdn.com/f433296ce7b90ad0ccf61705b5eda754bb04af83)")
.content-wrap
.content
.time 10.0
- {chooseRobot, chooseItem} = this
%ul.robots
- each @robots, (robot) ->
%li
%h3.name= robot.name
.weapons
%h4 Weapons
= robot.oWeapons
.motors
%h3 Motors
= robot.oMotors
.navigation
%h3 Navigation
= robot.oNavigation
- on "click", (e) ->
- $(".robots li").removeClass("selected")
- $(e.currentTarget).addClass("selected")
- chooseRobot(robot)
%ul.weapons
%li
%h3 Weapons
- each @weapons, (item) ->
%li
%button= item.projectile.underscore().titleize()
- on "click", ->
- chooseItem(item, "weapons")
%ul.motors
%li
%h3 Motors
- each @motors, (item) ->
%li
%button= item.type.titleize()
- on "click", ->
- chooseItem(item, "motors")
%ul.navigation
%li
%h3 Navigation
- each @navigation, (item) ->
%li
%button= item.type
- on "click", ->
- chooseItem(item, "navigation")
// Generated by CoffeeScript 1.6.3
/**
The <code>Gamepads</code> module gives the engine access to gamepads.
# First you need to add the `Gamepads` module to the engine
Engine.defaultModules.push "Gamepads"
window.engine = Engine
...
# Then you need to get a controller reference
# id = 0 for player 1, etc.
controller = engine.controller(id)
# Point indicating direction primary axis is held
direction = controller.position()
# Check if buttons are held
controller.actionDown("A")
controller.actionDown("B")
controller.actionDown("X")
controller.actionDown("Y")
@name Gamepads
@fieldOf Engine
@module
@param {Object} I Instance variables
@param {Object} self Reference to the engine
*/
(function() {
var Gamepads,
__slice = [].slice;
Engine.Gamepads = function(I, self) {
var gamepads;
gamepads = Gamepads();
self.bind("beforeUpdate", function() {
return gamepads.update();
});
return {
/**
Get a controller for a given id.
@name controller
@methodOf Engine.Gamepads#
@param {Number} index The index to get a controller for.
*/
controller: function(index) {
return gamepads.controller(index);
}
};
};
/**
This error handler captures any runtime errors and reports them to the IDE
if present.
*/
window.onerror = function(message, url, lineNumber) {
var errorContext;
errorContext = $('script').last().text().split('\n').slice(lineNumber - 5, +(lineNumber + 4) + 1 || 9e9);
errorContext[4] = "<b style='font-weight: bold; text-decoration: underline;'>" + errorContext[4] + "</b>";
return typeof displayRuntimeError === "function" ? displayRuntimeError("<code>" + message + "</code> <br /><br />(Sometimes this context may be wrong.)<br /><code><pre>" + (errorContext.join('\n')) + "</pre></code>") : void 0;
};
Gamepads = function(I) {
var controllers, snapshot, state;
if (I == null) {
I = {};
}
state = {};
controllers = [];
snapshot = function() {
return Array.prototype.map.call(navigator.webkitGamepads || (typeof navigator.webkitGetGamepads === "function" ? navigator.webkitGetGamepads() : void 0) || [], function(x) {
return {
axes: x.axes,
buttons: x.buttons
};
});
};
return {
controller: function(index) {
var controller, gamepad, gamepadIndex, keyboardController;
if (index == null) {
index = 0;
}
if (controller = controllers[index]) {
return controller;
}
gamepadIndex = index;
gamepad = Gamepads.Controller({
index: gamepadIndex,
state: state
});
if (index === 0) {
keyboardController = Gamepads.KeyboardController();
return controllers[index] || (controllers[index] = Gamepads.CombinedController(gamepad, keyboardController));
} else {
return controllers[index] || (controllers[index] = gamepad);
}
},
update: function() {
state.previous = state.current;
state.current = snapshot();
return controllers.each(function(controller) {
return controller != null ? controller.update() : void 0;
});
}
};
};
Gamepads.CombinedController = function() {
var self, sources;
sources = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
return self = Core().extend({
buttonDown: function() {
var buttons;
buttons = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
return sources.inject(false, function(memo, source) {
return memo || source.buttonDown.apply(source, buttons);
});
},
buttonPressed: function(button) {
return sources.inject(false, function(memo, source) {
return memo || source.buttonPressed(button);
});
},
buttonReleased: function(button) {
return sources.inject(false, function(memo, source) {
return memo || source.buttonPressed(button);
});
},
position: function(stick) {
var raw;
if (stick == null) {
stick = 0;
}
raw = sources.inject(Point(0, 0), function(point, source) {
return point.add(source.position(stick));
});
if (raw.length() > 1) {
return raw.norm();
} else {
return raw;
}
},
tap: function() {
var raw;
raw = sources.inject(Point(0, 0), function(point, source) {
return point.add(source.tap());
});
return Point(raw.x.sign(), raw.y.sign());
},
update: function() {
return sources.invoke("update");
},
drawDebug: function(canvas) {
return sources.invoke("drawDebug", canvas);
}
});
};
Gamepads.Controller = function(I) {
var AXIS_MAX, BUTTON_THRESHOLD, DEAD_ZONE, MAX_BUFFER, TRIP_HIGH, TRIP_LOW, axisTrips, buttonMapping, currentState, previousState, processTaps, self, tap;
if (I == null) {
I = {};
}
Object.reverseMerge(I, {
debugColor: "#000"
});
MAX_BUFFER = 0.03;
AXIS_MAX = 1 - MAX_BUFFER;
DEAD_ZONE = AXIS_MAX * 0.25;
TRIP_HIGH = AXIS_MAX * 0.75;
TRIP_LOW = AXIS_MAX * 0.5;
BUTTON_THRESHOLD = 0.5;
buttonMapping = {
"A": 0,
"B": 1,
"X": 2,
"Y": 3,
"LB": 4,
"RB": 5,
"LT": 6,
"RT": 7,
"SELECT": 8,
"BACK": 8,
"START": 9,
"TL": 10,
"TR": 11,
"HOME": 16
};
currentState = function() {
var _ref;
return (_ref = I.state.current) != null ? _ref[I.index] : void 0;
};
previousState = function() {
var _ref;
return (_ref = I.state.previous) != null ? _ref[I.index] : void 0;
};
axisTrips = [];
tap = Point(0, 0);
processTaps = function() {
var x, y, _ref;
_ref = [0, 1].map(function(n) {
if (!axisTrips[n] && self.axis(n).abs() > TRIP_HIGH) {
axisTrips[n] = true;
return self.axis(n).sign();
}
if (axisTrips[n] && self.axis(n).abs() < TRIP_LOW) {
axisTrips[n] = false;
}
return 0;
}), x = _ref[0], y = _ref[1];
return tap = Point(x, y);
};
return self = Core().include(Bindable).extend({
buttonDown: function() {
var buttons, state;
buttons = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
if (state = currentState()) {
return buttons.inject(false, function(down, button) {
return down || (button === "ANY" ? state.buttons.inject(false, function(down, button) {
return down || (button > BUTTON_THRESHOLD);
}) : state.buttons[buttonMapping[button]] > BUTTON_THRESHOLD);
});
} else {
return false;
}
},
buttonPressed: function(button) {
var buttonId, _ref;
buttonId = buttonMapping[button];
return (self.buttons()[buttonId] > BUTTON_THRESHOLD) && !(((_ref = previousState()) != null ? _ref.buttons[buttonId] : void 0) > BUTTON_THRESHOLD);
},
buttonReleased: function(button) {
var buttonId, _ref;
buttonId = buttonMapping[button];
return !(self.buttons()[buttonId] > BUTTON_THRESHOLD) && (((_ref = previousState()) != null ? _ref.buttons[buttonId] : void 0) > BUTTON_THRESHOLD);
},
position: function(stick) {
var magnitude, p, ratio, state;
if (stick == null) {
stick = 0;
}
if (state = currentState()) {
p = Point(self.axis(2 * stick), self.axis(2 * stick + 1));
magnitude = p.magnitude();
if (magnitude > AXIS_MAX) {
return p.norm();
} else if (magnitude < DEAD_ZONE) {
return Point(0, 0);
} else {
ratio = magnitude / AXIS_MAX;
return p.scale(ratio / AXIS_MAX);
}
} else {
return Point(0, 0);
}
},
axis: function(n) {
return self.axes()[n] || 0;
},
axes: function() {
var state;
if (state = currentState()) {
return state.axes;
} else {
return [];
}
},
buttons: function() {
var state;
if (state = currentState()) {
return state.buttons;
} else {
return [];
}
},
tap: function() {
return tap;
},
update: function() {
return processTaps();
},
drawDebug: function(canvas) {
var lineHeight;
lineHeight = 18;
self.axes().each(function(axis, i) {
return canvas.drawText({
color: I.debugColor,
text: axis,
x: 0,
y: (i + 1) * lineHeight
});
});
return self.buttons().each(function(button, i) {
canvas.drawText({
color: I.debugColor,
text: "" + i + ":",
x: 230,
y: (i + 1) * lineHeight
});
return canvas.drawText({
color: I.debugColor,
text: button,
x: 250,
y: (i + 1) * lineHeight
});
});
}
});
};
Gamepads.KeyboardController = function(I) {
var buttonKeys, buttonValues, processTaps, self, tap;
if (I == null) {
I = {};
}
Object.reverseMerge(I, {
axisMapping: [["left", "right"], ["up", "down"]],
buttonMapping: {
"A": 'z',
"B": 'x',
"C": 'c',
"D": 'v',
"X": 'c',
"Y": 'v',
"SELECT": "shift",
"START": "return"
},
debugColor: "#000"
});
tap = Point(0, 0);
buttonKeys = Object.keys(I.buttonMapping);
buttonValues = buttonKeys.map(function(key) {
return I.buttonMapping[key];
});
I.axisMapping.each(function(axis) {
return axis.each(function(key) {
return keydown[key] = false;
});
});
buttonKeys.each(function(key) {
return keydown[key] = false;
});
processTaps = function() {
var x, y, _ref;
_ref = I.axisMapping.map(function(_arg) {
var negative, positive;
negative = _arg[0], positive = _arg[1];
return justPressed[positive] - justPressed[negative];
}), x = _ref[0], y = _ref[1];
return tap = Point(x, y);
};
return self = Core().include(Bindable).extend({
buttonDown: function() {
var buttons;
buttons = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
return buttons.inject(false, function(down, button) {
return down || (button === "ANY" ? buttonValues.inject(false, function(down, button) {
return down || keydown[button];
}) : keydown[I.buttonMapping[button]]);
});
},
buttonPressed: function(button) {
var keyname;
keyname = I.buttonMapping[button];
return justPressed[keyname];
},
buttonReleased: function(button) {
var keyname;
keyname = I.buttonMapping[button];
return justReleased[keyname];
},
position: function(stick) {
var x, y, _ref;
if (stick == null) {
stick = 0;
}
_ref = I.axisMapping.map(function(_arg) {
var negative, positive;
negative = _arg[0], positive = _arg[1];
return keydown[positive] - keydown[negative];
}), x = _ref[0], y = _ref[1];
return tap = Point(x, y);
},
tap: function() {
return tap;
},
update: function() {
return processTaps();
},
drawDebug: function(canvas) {
var lineHeight, p;
lineHeight = 18;
p = self.position();
["x", "y"].each(function(key, i) {
return canvas.drawText({
color: I.debugColor,
text: p[key],
x: 0,
y: i * lineHeight
});
});
return buttonKeys.each(function(button, i) {
return canvas.drawText({
color: I.debugColor,
text: self.buttonDown(button),
x: 250,
y: i * lineHeight
});
});
}
});
};
document.oncontextmenu = function() {
return false;
};
$(document).bind("keydown", function(event) {
if (!$(event.target).is("input")) {
return event.preventDefault();
}
});
/**
jQuery Hotkeys Plugin
Copyright 2010, John Resig
Dual licensed under the MIT or GPL Version 2 licenses.
Based upon the plugin by Tzury Bar Yochay:
http://github.com/tzuryby/hotkeys
Original idea by:
Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
*/
(function(jQuery) {
var isFunctionKey, isTextAcceptingInput, keyHandler;
isTextAcceptingInput = function(element) {
return /textarea|select/i.test(element.nodeName) || element.type === "text" || element.type === "password";
};
isFunctionKey = function(event) {
var _ref;
return (event.type !== "keypress") && ((112 <= (_ref = event.which) && _ref <= 123));
};
jQuery.hotkeys = {
version: "0.8",
specialKeys: {
8: "backspace",
9: "tab",
13: "return",
16: "shift",
17: "ctrl",
18: "alt",
19: "pause",
20: "capslock",
27: "esc",
32: "space",
33: "pageup",
34: "pagedown",
35: "end",
36: "home",
37: "left",
38: "up",
39: "right",
40: "down",
45: "insert",
46: "del",
96: "0",
97: "1",
98: "2",
99: "3",
100: "4",
101: "5",
102: "6",
103: "7",
104: "8",
105: "9",
106: "*",
107: "+",
109: "-",
110: ".",
111: "/",
112: "f1",
113: "f2",
114: "f3",
115: "f4",
116: "f5",
117: "f6",
118: "f7",
119: "f8",
120: "f9",
121: "f10",
122: "f11",
123: "f12",
144: "numlock",
145: "scroll",
186: ";",
187: "=",
188: ",",
189: "-",
190: ".",
191: "/",
219: "[",
220: "\\",
221: "]",
222: "'",
224: "meta"
},
shiftNums: {
"`": "~",
"1": "!",
"2": "@",
"3": "#",
"4": "$",
"5": "%",
"6": "^",
"7": "&",
"8": "*",
"9": "(",
"0": ")",
"-": "_",
"=": "+",
";": ":",
"'": "\"",
",": "<",
".": ">",
"/": "?",
"\\": "|"
}
};
keyHandler = function(handleObj) {
var keys, origHandler;
if (typeof handleObj.data !== "string") {
return;
}
origHandler = handleObj.handler;
keys = handleObj.data.toLowerCase().split(" ");
return handleObj.handler = function(event) {
var character, key, modif, possible, special, target, _i, _len;
special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[event.which];
character = String.fromCharCode(event.which).toLowerCase();
modif = "";
possible = {};
target = event.target;
if (event.altKey && special !== "alt") {
modif += "alt+";
}
if (event.ctrlKey && special !== "ctrl") {
modif += "ctrl+";
}
if (event.metaKey && !event.ctrlKey && special !== "meta") {
modif += "meta+";
}
if (this !== target) {
if (isTextAcceptingInput(target) && !modif && !isFunctionKey(event)) {
return;
}
}
if (event.shiftKey && special !== "shift") {
modif += "shift+";
}
if (special) {
possible[modif + special] = true;
} else {
possible[modif + character] = true;
possible[modif + jQuery.hotkeys.shiftNums[character]] = true;
if (modif === "shift+") {
possible[jQuery.hotkeys.shiftNums[character]] = true;
}
}
for (_i = 0, _len = keys.length; _i < _len; _i++) {
key = keys[_i];
if (possible[key]) {
return origHandler.apply(this, arguments);
}
}
};
};
return jQuery.each(["keydown", "keyup", "keypress"], function() {
return jQuery.event.special[this] = {
add: keyHandler
};
});
})(jQuery);
$(function() {
/**
The global keydown property lets your query the status of keys.
<code><pre>
if keydown.left
moveLeft()
if keydown.a or keydown.space
attack()
if keydown.return
confirm()
if keydown.esc
cancel()
</pre></code>
@name keydown
@namespace
*/
/**
The global justPressed property lets your query the status of keys. However,
unlike keydown it will only trigger once for each time the key is pressed.
<code><pre>
if justPressed.left
moveLeft()
if justPressed.a or justPressed.space
attack()
if justPressed.return
confirm()
if justPressed.esc
cancel()
</pre></code>
@name justPressed
@namespace
*/
var keyName, prevKeysDown;
window.keydown = {};
window.justPressed = {};
window.justReleased = {};
prevKeysDown = {};
keyName = function(event) {
return jQuery.hotkeys.specialKeys[event.which] || String.fromCharCode(event.which).toLowerCase();
};
$(document).bind("keydown", function(event) {
var key;
key = keyName(event);
return keydown[key] = true;
});
$(document).bind("keyup", function(event) {
var key;
key = keyName(event);
return keydown[key] = false;
});
return window.updateKeys = function() {
var key, value, _results;
window.justPressed = {};
window.justReleased = {};
keydown.any = false;
for (key in keydown) {
value = keydown[key];
justPressed[key] = value && !prevKeysDown[key];
justReleased[key] = !value && prevKeysDown[key];
if (justPressed[key] || mousePressed.left || mousePressed.right) {
justPressed.any = true;
}
if (value || mouseDown.left || mouseDown.right) {
keydown.any = true;
}
}
prevKeysDown = {};
_results = [];
for (key in keydown) {
value = keydown[key];
_results.push(prevKeysDown[key] = value);
}
return _results;
};
});
$(function() {
/**
The global mouseDown property lets your query the status of mouse buttons.
<code><pre>
if mouseDown.left
moveLeft()
if mouseDown.right
attack()
</pre></code>
@name mouseDown
@namespace
*/
/**
The global mousePressed property lets your query the status of mouse buttons.
However, unlike mouseDown it will only trigger the first time the button
pressed.
<code><pre>
if mousePressed.left
moveLeft()
if mousePressed.right
attack()
</pre></code>
@name mousePressed
@namespace
*/
var buttonName, buttonNames, prevButtonsDown;
window.mouseDown = {};
window.mousePressed = {};
window.mouseReleased = {};
window.mousePosition = Point(0, 0);
prevButtonsDown = {};
buttonNames = {
1: "left",
2: "middle",
3: "right"
};
buttonName = function(event) {
return buttonNames[event.which];
};
$(document).bind("mousemove", function(event) {
var offset;
offset = $("canvas").offset();
mousePosition.x = event.pageX - offset.left;
return mousePosition.y = event.pageY - offset.top;
});
$(document).bind("mousedown", function(event) {
return mouseDown[buttonName(event)] = true;
});
$(document).bind("mouseup", function(event) {
return mouseDown[buttonName(event)] = false;
});
return window.updateMouse = function() {
var button, value, _results;
window.mousePressed = {};
window.mouseReleased = {};
for (button in mouseDown) {
value = mouseDown[button];
if (!prevButtonsDown[button]) {
mousePressed[button] = value;
}
}
for (button in mouseDown) {
value = mouseDown[button];
if (prevButtonsDown[button]) {
mouseReleased[button] = !value;
}
}
prevButtonsDown = {};
_results = [];
for (button in mouseDown) {
value = mouseDown[button];
_results.push(prevButtonsDown[button] = value);
}
return _results;
};
});
(function($) {
return $.fn.pixieCanvas = function(options) {
var $canvas, canvas, canvasAttrAccessor, context, contextAttrAccessor;
if (options == null) {
options = {};
}
canvas = this.get(0);
context = void 0;
/**
PixieCanvas provides a convenient wrapper for working with Context2d.
Methods try to be as flexible as possible as to what arguments they take.
Non-getter methods return `this` for method chaining.
@name PixieCanvas
@constructor
*/
$canvas = $(canvas).extend({
/**
Passes this canvas to the block with the given matrix transformation
applied. All drawing methods called within the block will draw
into the canvas with the transformation applied. The transformation
is removed at the end of the block, even if the block throws an error.
@name withTransform
@methodOf PixieCanvas#
@param {Matrix} matrix
@param {Function} block
@returns {PixieCanvas} this
*/
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 the canvas (or a portion of it).
Clear the entire canvas
<code><pre>
canvas.clear()
</pre></code>
Clear a portion of the canvas
<code class="run"><pre>
# Set up: Fill canvas with blue
canvas.fill("blue")
# Clear a portion of the canvas
canvas.clear
x: 50
y: 50
width: 50
height: 50
</pre></code>
You can also clear the canvas by passing x, y, width height as
unnamed parameters:
<code><pre>
canvas.clear(25, 25, 50, 50)
</pre></code>
@name clear
@methodOf PixieCanvas#
@param {Number} [x] where to start clearing on the x axis
@param {Number} [y] where to start clearing on the y axis
@param {Number} [width] width of area to clear
@param {Number} [height] height of area to clear
@returns {PixieCanvas} this
*/
clear: function(x, y, width, height) {
var _ref;
if (x == null) {
x = {};
}
if (y == null) {
_ref = x, x = _ref.x, y = _ref.y, width = _ref.width, height = _ref.height;
}
x || (x = 0);
y || (y = 0);
if (width == null) {
width = canvas.width;
}
if (height == null) {
height = canvas.height;
}
context.clearRect(x, y, width, height);
return this;
},
/**
Fills the entire canvas (or a specified section of it) with
the given color.
<code class="run"><pre>
# Paint the town (entire canvas) red
canvas.fill "red"
# Fill a section of the canvas white (#FFF)
canvas.fill
x: 50
y: 50
width: 50
height: 50
color: "#FFF"
</pre></code>
@name fill
@methodOf PixieCanvas#
@param {Number} [x=0] Optional x position to fill from
@param {Number} [y=0] Optional y position to fill from
@param {Number} [width=canvas.width] Optional width of area to fill
@param {Number} [height=canvas.height] Optional height of area to fill
@param {Bounds} [bounds] bounds object to fill
@param {String|Color} [color] color of area to fill
@returns {PixieCanvas} this
*/
fill: function(color) {
var bounds, height, width, x, y, _ref;
if (color == null) {
color = {};
}
if (!((typeof color.isString === "function" ? color.isString() : void 0) || 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;
},
/**
A direct map to the Context2d draw image. `GameObject`s
that implement drawable will have this wrapped up nicely,
so there is a good chance that you will not have to deal with
it directly.
@name drawImage
@methodOf PixieCanvas#
@param image
@param {Number} sx
@param {Number} sy
@param {Number} sWidth
@param {Number} sHeight
@param {Number} dx
@param {Number} dy
@param {Number} dWidth
@param {Number} dHeight
@returns {PixieCanvas} this
*/
drawImage: function(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) {
context.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
return this;
},
/**
Draws a circle at the specified position with the specified
radius and color.
<code class="run"><pre>
# Draw a large orange circle
canvas.drawCircle
radius: 30
position: Point(100, 75)
color: "orange"
# Draw a blue circle with radius 10 at (25, 50)
# and a red stroke
canvas.drawCircle
x: 25
y: 50
radius: 10
color: "blue"
stroke:
color: "red"
width: 1
# Create a circle object to set up the next examples
circle =
radius: 20
x: 50
y: 50
# Draw a given circle in yellow
canvas.drawCircle
circle: circle
color: "yellow"
# Draw the circle in green at a different position
canvas.drawCircle
circle: circle
position: Point(25, 75)
color: "green"
# Draw an outline circle in purple.
canvas.drawCircle
x: 50
y: 75
radius: 10
stroke:
color: "purple"
width: 2
</pre></code>
@name drawCircle
@methodOf PixieCanvas#
@param {Number} [x] location on the x axis to start drawing
@param {Number} [y] location on the y axis to start drawing
@param {Point} [position] position object of location to start drawing. This will override x and y values passed
@param {Number} [radius] length of the radius of the circle
@param {Color|String} [color] color of the circle
@param {Circle} [circle] circle object that contains position and radius. Overrides x, y, and radius if passed
@param {Stroke} [stroke] stroke object that specifies stroke color and stroke width
@returns {PixieCanvas} 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;
},
/**
Draws a rectangle at the specified position with given
width and height. Optionally takes a position, bounds
and color argument.
<code class="run"><pre>
# Draw a red rectangle using x, y, width and height
canvas.drawRect
x: 50
y: 50
width: 50
height: 50
color: "#F00"
# Draw a blue rectangle using position, width and height
# and throw in a stroke for good measure
canvas.drawRect
position: Point(0, 0)
width: 50
height: 50
color: "blue"
stroke:
color: "orange"
width: 3
# Set up a bounds object for the next examples
bounds =
x: 100
y: 0
width: 100
height: 100
# Draw a purple rectangle using bounds
canvas.drawRect
bounds: bounds
color: "green"
# Draw the outline of the same bounds, but at a different position
canvas.drawRect
bounds: bounds
position: Point(0, 50)
stroke:
color: "purple"
width: 2
</pre></code>
@name drawRect
@methodOf PixieCanvas#
@param {Number} [x] location on the x axis to start drawing
@param {Number} [y] location on the y axis to start drawing
@param {Number} [width] width of rectangle to draw
@param {Number} [height] height of rectangle to draw
@param {Point} [position] position to start drawing. Overrides x and y if passed
@param {Color|String} [color] color of rectangle
@param {Bounds} [bounds] bounds of rectangle. Overrides x, y, width, height if passed
@param {Stroke} [stroke] stroke object that specifies stroke color and stroke width
@returns {PixieCanvas} 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;
},
/**
Draw a line from `start` to `end`.
<code class="run"><pre>
# Draw a sweet diagonal
canvas.drawLine
start: Point(0, 0)
end: Point(200, 200)
color: "purple"
# Draw another sweet diagonal
canvas.drawLine
start: Point(200, 0)
end: Point(0, 200)
color: "red"
width: 6
# Now draw a sweet horizontal with a direction and a length
canvas.drawLine
start: Point(0, 100)
length: 200
direction: Point(1, 0)
color: "orange"
</pre></code>
@name drawLine
@methodOf PixieCanvas#
@param {Point} start position to start drawing from
@param {Point} [end] position to stop drawing
@param {Number} [width] width of the line
@param {String|Color} [color] color of the line
@returns {PixieCanvas} 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;
},
/**
Draw a polygon.
<code class="run"><pre>
# Draw a sweet rhombus
canvas.drawPoly
points: [
Point(50, 25)
Point(75, 50)
Point(50, 75)
Point(25, 50)
]
color: "purple"
stroke:
color: "red"
width: 2
</pre></code>
@name drawPoly
@methodOf PixieCanvas#
@param {Point[]} [points] collection of points that define the vertices of the polygon
@param {String|Color} [color] color of the polygon
@param {Stroke} [stroke] stroke object that specifies stroke color and stroke width
@returns {PixieCanvas} this
*/
drawPoly: function(_arg) {
var color, points, stroke;
points = _arg.points, color = _arg.color, stroke = _arg.stroke;
context.beginPath();
points.each(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;
},
/**
Draw a rounded rectangle.
Adapted from http://js-bits.blogspot.com/2010/07/canvas-rounded-corner-rectangles.html
<code class="run"><pre>
# Draw a purple rounded rectangle with a red outline
canvas.drawRoundRect
position: Point(25, 25)
radius: 10
width: 150
height: 100
color: "purple"
stroke:
color: "red"
width: 2
</pre></code>
@name drawRoundRect
@methodOf PixieCanvas#
@param {Number} [x] location on the x axis to start drawing
@param {Number} [y] location on the y axis to start drawing
@param {Number} [width] width of the rounded rectangle
@param {Number} [height] height of the rounded rectangle
@param {Number} [radius=5] radius to round the rectangle corners
@param {Point} [position] position to start drawing. Overrides x and y if passed
@param {Color|String} [color] color of the rounded rectangle
@param {Bounds} [bounds] bounds of the rounded rectangle. Overrides x, y, width, and height if passed
@param {Stroke} [stroke] stroke object that specifies stroke color and stroke width
@returns {PixieCanvas} 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;
},
/**
Draws text on the canvas at the given position, in the given color.
If no color is given then the previous fill color is used.
<code class="run"><pre>
# Fill canvas to indicate bounds
canvas.fill
color: '#eee'
# A line to indicate the baseline
canvas.drawLine
start: Point(25, 50)
end: Point(125, 50)
color: "#333"
width: 1
# Draw some text, note the position of the baseline
canvas.drawText
position: Point(25, 50)
color: "red"
text: "It's dangerous to go alone"
</pre></code>
@name drawText
@methodOf PixieCanvas#
@param {Number} [x] location on x axis to start printing
@param {Number} [y] location on y axis to start printing
@param {String} text text to print
@param {Point} [position] position to start printing. Overrides x and y if passed
@param {String|Color} [color] color of text to start printing
@param {String} [font] font of text to print
@returns {PixieCanvas} 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;
},
/**
Centers the given text on the canvas at the given y position. An x position
or point position can also be given in which case the text is centered at the
x, y or position value specified.
<code class="run"><pre>
# Fill canvas to indicate bounds
canvas.fill
color: "#eee"
# A line to indicate the baseline
canvas.drawLine
start: Point(25, 25)
end: Point(125, 25)
color: "#333"
width: 1
# Center text on the screen at y value 25
canvas.centerText
y: 25
color: "red"
text: "It's dangerous to go alone"
# Center text at point (75, 75)
canvas.centerText
position: Point(75, 75)
color: "green"
text: "take this"
</pre></code>
@name centerText
@methodOf PixieCanvas#
@param {String} text Text to print
@param {Number} [y] location on the y axis to start printing
@param {Number} [x] location on the x axis to start printing. Overrides the default centering behavior if passed
@param {Point} [position] position to start printing. Overrides x and y if passed
@param {String|Color} [color] color of text to print
@param {String} [font] font of text to print
@returns {PixieCanvas} 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
});
},
/**
A getter / setter method to set the canvas fillColor.
<code><pre>
# Set the fill color
canvas.fillColor('#FF0000')
# Passing no arguments returns the fillColor
canvas.fillColor()
# => '#FF0000'
# You can also pass a Color object
canvas.fillColor(Color('sky blue'))
</pre></code>
@name fillColor
@methodOf PixieCanvas#
@param {String|Color} [color] color to make the canvas fillColor
@returns {PixieCanvas} this
*/
fillColor: function(color) {
if (color) {
if (color.channels) {
context.fillStyle = color.toString();
} else {
context.fillStyle = color;
}
return this;
} else {
return context.fillStyle;
}
},
/**
A getter / setter method to set the canvas strokeColor.
<code><pre>
# Set the stroke color
canvas.strokeColor('#FF0000')
# Passing no arguments returns the strokeColor
canvas.strokeColor()
# => '#FF0000'
# You can also pass a Color object
canvas.strokeColor(Color('sky blue'))
</pre></code>
@name strokeColor
@methodOf PixieCanvas#
@param {String|Color} [color] color to make the canvas strokeColor
@returns {PixieCanvas} this
*/
strokeColor: function(color) {
if (color) {
if (color.channels) {
context.strokeStyle = color.toString();
} else {
context.strokeStyle = color;
}
return this;
} else {
return context.strokeStyle;
}
},
/**
Determine how wide some text is.
<code><pre>
canvas.measureText('Hello World!')
# => 55
</pre></code>
@name measureText
@methodOf PixieCanvas#
@param {String} [text] the text to measure
@returns {PixieCanvas} this
*/
measureText: function(text) {
return context.measureText(text).width;
},
putImageData: function(imageData, x, y) {
context.putImageData(imageData, x, y);
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.each(function(attr) {
return $canvas[attr] = function(newVal) {
if (newVal != null) {
context[attr] = newVal;
return this;
} else {
return context[attr];
}
};
});
};
canvasAttrAccessor = function() {
var attrs;
attrs = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
return attrs.each(function(attr) {
return $canvas[attr] = function(newVal) {
if (newVal != null) {
canvas[attr] = newVal;
return this;
} else {
return canvas[attr];
}
};
});
};
contextAttrAccessor("font", "globalAlpha", "globalCompositeOperation", "lineWidth", "textAlign");
canvasAttrAccessor("height", "width");
if (canvas != null ? canvas.getContext : void 0) {
context = canvas.getContext('2d');
if (options.init) {
options.init($canvas);
}
return $canvas;
}
};
})(jQuery);
/**
A browser polyfill so you can consistently
call requestAnimationFrame. Using
requestAnimationFrame is preferred to
setInterval for main game loops.
http://paulirish.com/2011/requestanimationframe-for-smart-animating/
@name requestAnimationFrame
@namespace
*/
window.requestAnimationFrame || (window.requestAnimationFrame = window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback, element) {
return window.setTimeout(function() {
return callback(+new Date());
}, 1000 / 60);
});
(function() {
/**
A wrapper on the Local Storage API
@name Local
@namespace
*/
/**
Store an object in local storage.
<code><pre>
# you can store strings
Local.set('name', 'Matt')
# and numbers
Local.set('age', 26)
# and even objects
Local.set('person', {name: 'Matt', age: 26})
</pre></code>
@name set
@methodOf Local
@param {String} key string used to identify the object you are storing
@param {Object} value value of the object you are storing
@returns {Object} value
*/
var retrieve, store;
store = function(key, value) {
localStorage[key] = JSON.stringify(value);
return value;
};
/**
Retrieve an object from local storage.
<code><pre>
Local.get('name')
# => 'Matt'
Local.get('age')
# => 26
Local.get('person')
# => { age: 26, name: 'Matt' }
</pre></code>
@name get
@methodOf Local
@param {String} key string that identifies the stored object
@returns {Object} The object that was stored or undefined if no object was stored.
*/
retrieve = function(key) {
var value;
value = localStorage[key];
if (value != null) {
return JSON.parse(value);
}
};
return (typeof exports !== "undefined" && exports !== null ? exports : this)["Local"] = {
get: retrieve,
set: store,
put: store,
/**
Access an instance of Local with a specified prefix.
@name new
@methodOf Local
@param {String} prefix
@returns {Local} An interface to local storage with the given prefix applied.
*/
"new": function(prefix) {
prefix || (prefix = "");
return {
get: function(key) {
return retrieve("" + prefix + "_" + key);
},
set: function(key, value) {
return store("" + prefix + "_" + key, value);
},
put: function(key, value) {
return store("" + prefix + "_" + key, value);
}
};
}
};
})();
}).call(this);
body {
background-color: black;
margin: 0;
overflow: hidden;
font-family: helvetica;
font-size: 16px;
}
ul {
list-style-type: none;
}
button {
cursor: pointer;
}
.app {
width: 800px;
height: 450px;
margin: auto;
position: relative;
}
.setup,
.intro {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
padding: 2em 4em;
-ms-box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.intro button {
display: block;
width: 200px;
height: 40px;
position: absolute;
bottom: 1em;
left: 0;
right: 0;
font-size: 30px;
font-weight: bold;
margin: auto;
}
.content-wrap {
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.75);
color: white;
padding: 1em;
position: relative;
-ms-box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
h1 {
margin-top: 0;
}
.time {
position: absolute;
bottom: 0;
left: 0;
right: 0;
margin: auto;
text-align: center;
font-weight: bold;
font-family: "Lucida Console", Monaco, monospace;
height: 80px;
font-size: 40px;
}
h3,
h4 {
margin: 0;
}
ul {
display: inline-block;
width: 33%;
margin: 0;
padding: 0;
vertical-align: top;
-ms-box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
ul.robots li:nth-child(1) {
background-image: url(http://a2.pixiecdn.com/82fcca0104779a4cb2a6b3da022bbcc5e5519c83);
}
ul.robots li:nth-child(2) {
background-image: url(http://a1.pixiecdn.com/500ba6b2a40368ca4786fce1a81c3870c7c471dc);
}
ul.robots li:nth-child(3) {
background-image: url(http://a2.pixiecdn.com/383b39e44e31aed2cfadf37c37aabd5462a99590);
}
ul.robots li {
cursor: pointer;
padding-left: 88px;
vertical-align: top;
width: 33%;
height: 140px;
display: inline-block;
background-position: top left;
background-repeat: no-repeat;
-ms-box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
ul.robots {
width: 100%;
display: block;
clear: both;
overflow: auto;
}
li.selected {
color: lime;
background-color: rgba(255, 255, 255, 0.25);
}
@media all and (-webkit-min-device-pixel-ratio: 1.5) {
ul.robots li:nth-child(1) {
background-image: url("http:/a2.pixiecdn.com/82fcca0104779a4cb2a6b3da022bbcc5e5519c83@2x");
background-size: contain;
}
}
@media all and (-webkit-min-device-pixel-ratio: 1.5) {
ul.robots li:nth-child(2) {
background-image: url("http:/a1.pixiecdn.com/500ba6b2a40368ca4786fce1a81c3870c7c471dc@2x");
background-size: contain;
}
}
@media all and (-webkit-min-device-pixel-ratio: 1.5) {
ul.robots li:nth-child(3) {
background-image: url("http:/a2.pixiecdn.com/383b39e44e31aed2cfadf37c37aabd5462a99590@2x");
background-size: contain;
}
}
body
background-color: black
margin: 0
overflow: hidden
font-family: helvetica
font-size: 16px
ul
list-style-type: none
button
cursor: pointer
.app
width: 800px
height: 450px
margin: auto
position: relative
.setup, .intro
box-sizing: border-box
width: 100%
height: 100%
position: absolute
top: 0
left: 0
padding: 2em 4em
.intro button
display: block
width: 200px
height: 40px
position: absolute
bottom: 1em
left: 0
right: 0
font-size: 30px
font-weight: bold
margin: auto
.content-wrap
box-sizing: border-box
width: 100%
height: 100%
background-color: rgba(0, 0, 0, 0.75)
color: white
padding: 1em
position: relative
h1
margin-top: 0
.time
position: absolute
bottom: 0
left: 0
right: 0
margin: auto
text-align: center
font-weight: bold
font-family: "Lucida Console", Monaco, monospace
height: 80px
font-size: 40px
h3, h4
margin: 0
ul
display: inline-block
width: 33%
box-sizing: border-box
margin: 0
padding: 0
vertical-align: top
&.robots
width: 100%
display: block
clear: both
overflow: auto
li
cursor: pointer
box-sizing: border-box
padding-left: 88px
vertical-align: top
width: 33%
height: 140px
display: inline-block
background-position: top left
background-repeat: no-repeat
&:nth-child(1)
background-image: url(http://a2.pixiecdn.com/82fcca0104779a4cb2a6b3da022bbcc5e5519c83)
&:nth-child(2)
background-image: url(http://a1.pixiecdn.com/500ba6b2a40368ca4786fce1a81c3870c7c471dc)
&:nth-child(3)
background-image: url(http://a2.pixiecdn.com/383b39e44e31aed2cfadf37c37aabd5462a99590)
li.selected
color: lime
background-color: rgba(255, 255, 255, 0.25)
@Targetting = (I={}) ->
Object.defaults I,
type: "nearest"
switchDelay: 2
switchCooldown: 0
target = null
self = GameObject(I).extend
target: (source) ->
# NOTE: Only works for two teams
selector = "Robot.team=#{+!source.I.team}"
if target and I.switchCooldown
# Don't switch target
else
I.switchCooldown += I.switchDelay
switch I.type
when "random"
target =
position: ->
Point(rand(App.width), rand(App.height))
when "randomEnemy"
target = engine.find(selector).rand()
else # Nearest Enemy
target = engine.closest(selector, source.position())
if target
return Point.direction(source.position(), target.position())
self.cooldown "switchCooldown"
return self
@Targetting.random = ->
[
{type: "random"}
{type: "randomEnemy"}
{type: "nearestEnemy"}
].rand()
@Teleporter = (I={}) ->
Object.defaults I,
color: "lime"
self = GameObject(I)
self.on "update", ->
if I.age > 1
self.destroy()
I.source.I.x = rand(App.width)
I.source.I.y = rand(App.height)
return self

Screens

Intro

Config View

Arena View

Robot Behavior

Movement

Constant Square Wave Sine Wave

Navigation

Target Enemy Random Shifts

Weapons

Teleporter Gun Saw

@Weapon = (I={}) ->
Object.defaults I,
delay: 1
cooldown: 1
projectile: "Missile"
spread: 0.05.turns
self = GameObject(I).extend
fire: (source, dt) ->
if I.cooldown is 0
I.cooldown = I.delay
facing = source.I.facing
p = source.position()
.add(source.velocity().scale(dt))
.add(Point.fromAngle(facing).scale(40))
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
@Weapon.random = ->
[
{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()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment