<commit_before>var WebSocket = require('ws');
var emitter   = require('./utils/emitter');
var moment    = require('moment');
var request   = require('request'); // github.com/mikeal/request

// Private
var Bots = {};

// Size of an object
Object.size = function(obj) {
    var size = 0, key;
    for (key in obj) {
        if (obj.hasOwnProperty(key)) size++;
    }
    return size;
};

Object.getKeyByValue = function( obj, value ) {
    for( var prop in obj ) {
        if( obj.hasOwnProperty( prop ) ) {
        	console.log("PROP " + prop)
             if( obj[ prop ] === value )
                 return prop;
        }
    }
};

// Public
var self = module.exports = {

	size: function size() {
		return Object.size(Bots);
	},

	onlineUsers: function onlineUsers(appid) {

		if(typeof Bots[appid] === 'undefined')
			return false;

		var application = Bots[appid].application;

		request.post('https://slack.com/api/users.list', { json: true, form: { token: application.slack_api_token }}, function (error, response, body) {
			if (!error && response.statusCode == 200 && typeof body.ok !== "undefined" && body.ok == true) {
				return body.members;
			} else {
				return error;
			}
		});
	},

	addChannel: function addChannel(appid, code, name) {
		if(typeof Bots[appid] === 'undefined')
			return false;

		Bots[appid].channels["#" + name] = code;

		return Bots[appid].channels;
	},

	getChannelCode: function getChannelCode(appid, name) {
		if(typeof Bots[appid] === 'undefined')
			return false;

		return Bots[appid].channels["#" + name];
	},

	isConnected: function isConnected(appid) {
		if(typeof Bots[appid] === 'undefined')
			return false;
		return Bots[appid].isConnected;
	},

	connect: function connect(application) {

		var appid = application.id;

		console.log("Connecting to " + appid);

		if(typeof Bots[appid] === 'undefined' || Bots[appid].isConnected == false) {

			request.post('https://slack.com/api/rtm.start', { json: true, form: { token: application.slack_api_token, t: Date.now() }}, function (error, response, connection) {
				if (!error && response.statusCode == 200 && typeof connection.ok !== "undefined" && connection.ok == true) {

					Bots[appid]             = emitter;
					Bots[appid].websocket   = new WebSocket(connection.url);
					Bots[appid].application = application;
					Bots[appid].isConnected = false;
					Bots[appid].msgCount    = 0;
					Bots[appid].nick        = connection.self.id;
					Bots[appid].team        = connection.team.id;
					Bots[appid].teamDomain  = connection.team.domain;
					Bots[appid].bootedAt    = moment().utc().unix();
					Bots[appid].channels    = [];
					Bots[appid].errors      = [];

					console.log("Undefined");
					console.log("T: " + Object.size(Bots));

					// Read Channels
					for (var channel in connection.channels) {
						self.addChannel(appid, connection.channels[channel].id, connection.channels[channel].name);
					}

					// Socket Listner
					Bots[appid].websocket.addListener('open', function () {
						console.log(appid + " is now open");
					});

					Bots[appid].addListener('hello', function () {
						console.log(appid + " said hello");
						Bots[appid].isConnected = true;
						//self.say(appid, self.getChannelCode(appid, "general"), "I'm online!");
					});

					Bots[appid].websocket.addListener('close', function () {
						Bots[appid].isConnected = false;
						Bots[appid].removeAllListeners();
						console.log("conn closed");
					});

					// Websocket Events
					Bots[appid].websocket.addListener('message', function (data) {
						var message = JSON.parse(data);

						// Gambiarra
						Bots[appid].msgCount++
						// Ignore the 2 message (last message)
						if(Bots[appid].msgCount == 2) return;

						if(message.type == 'message' && message.channel.indexOf("C") == 0 && typeof message.subtype === 'undefined') {

							console.log("Channel message %j", message);
							Bots[appid].emit('message', message);

						} else if(message.type == 'message' && message.channel.indexOf("C") == 0 && message.subtype === 'file_share') {

							console.log("File shared %j", message);
							var shared_message = {text: "Shared a file: " + message.file.url , channel: message.channel}
							Bots[appid].emit('message', shared_message);

						} else if(message.type == 'message' && message.channel.indexOf("D") == 0) {

							console.log("Direct message %j", message);
							Bots[appid].emit('direct_message', message);

						} else if(typeof message.type !== 'undefined' && message.type !== 'message') {

							console.log("Other message %s %j", message.type, message);
							Bots[appid].emit(message.type, message);

						} else {

							console.log("Undefined message %j", message);

						}
					});

					Bots[appid].addListener('error', function (e) {
						console.log(appid + " had an error");
					});

					// Direct message
					Bots[appid].addListener('direct_message', function (message) {

						// If command
						if(message.text.indexOf("!") == 0 && message.text.length > 1) {
							var command = message.text.substring(1, message.text.length);

							console.log("It's a command: " + command);

							if(command === "time") {
								self.say(appid, message.channel, "_It's now: *" + moment().utc().format() + "*._");
							} else if(command === "uptime") {
								var time = moment(Bots[appid].bootedAt, "X").utc();
								self.say(appid, message.channel, "_Uptime: *" + time.fromNow() + "* @ *" + time.format() + "*._");
							} else {
								// Command not valid!
								self.say(appid, message.channel, "_Sorry! Couldn't reconize the command: *" + command + "*._");
							}

						} else {
							// Reply
							self.say(appid, message.channel, "You said: _" + message.text + "_");
						}

					});

					return Bots[appid];
				}
			});

		} else {
			console.log("Already connected!");
			console.log("T: " + Object.size(Bots));

			return Bots[appid];
		}

	},

	say: function say(appid, channel, message) {
		if(typeof Bots[appid] === 'undefined') {
			return true;
		}

		var data = {
			"type":    "message",
			"channel": channel, //D... or C....
			"user":    Bots[appid].nick,
			"text":    message,
			"ts":      Date.now() + ".000000",
			"team":    Bots[appid].team
		};

		Bots[appid].websocket.send(JSON.stringify(data), function(error) {
			console.log("Error sending data to socket!");
		});
	},

	// Disconect one websocket
	disconnect: function disconnect(appid) {

		if(typeof Bots[appid] === 'undefined') {
			return true;
		}

		if(Bots[appid].isConnected == true) {
			self.say(appid, self.getChannelCode(appid, "general"), "I'm going offline!");
			Bots[appid].websocket.close();
		}

		return true;
	},

	// Disconect all webockets
	disconnectAll: function disconnectAll() {

		for (var appid in Bots) {
			this.disconnect(appid);
		}

		return true;
	}
};
<commit_msg>Error sending data to socket<commit_after>var WebSocket = require('ws');
var emitter   = require('./utils/emitter');
var moment    = require('moment');
var request   = require('request'); // github.com/mikeal/request

// Private
var Bots = {};

// Size of an object
Object.size = function(obj) {
    var size = 0, key;
    for (key in obj) {
        if (obj.hasOwnProperty(key)) size++;
    }
    return size;
};

Object.getKeyByValue = function( obj, value ) {
    for( var prop in obj ) {
        if( obj.hasOwnProperty( prop ) ) {
        	console.log("PROP " + prop)
             if( obj[ prop ] === value )
                 return prop;
        }
    }
};

// Public
var self = module.exports = {

	size: function size() {
		return Object.size(Bots);
	},

	onlineUsers: function onlineUsers(appid) {

		if(typeof Bots[appid] === 'undefined')
			return false;

		var application = Bots[appid].application;

		request.post('https://slack.com/api/users.list', { json: true, form: { token: application.slack_api_token }}, function (error, response, body) {
			if (!error && response.statusCode == 200 && typeof body.ok !== "undefined" && body.ok == true) {
				return body.members;
			} else {
				return error;
			}
		});
	},

	addChannel: function addChannel(appid, code, name) {
		if(typeof Bots[appid] === 'undefined')
			return false;

		Bots[appid].channels["#" + name] = code;

		return Bots[appid].channels;
	},

	getChannelCode: function getChannelCode(appid, name) {
		if(typeof Bots[appid] === 'undefined')
			return false;

		return Bots[appid].channels["#" + name];
	},

	isConnected: function isConnected(appid) {
		if(typeof Bots[appid] === 'undefined')
			return false;
		return Bots[appid].isConnected;
	},

	connect: function connect(application) {

		var appid = application.id;

		console.log("Connecting to " + appid);

		if(typeof Bots[appid] === 'undefined' || Bots[appid].isConnected == false) {

			request.post('https://slack.com/api/rtm.start', { json: true, form: { token: application.slack_api_token, t: Date.now() }}, function (error, response, connection) {
				if (!error && response.statusCode == 200 && typeof connection.ok !== "undefined" && connection.ok == true) {

					Bots[appid]             = emitter;
					Bots[appid].websocket   = new WebSocket(connection.url);
					Bots[appid].application = application;
					Bots[appid].isConnected = false;
					Bots[appid].msgCount    = 0;
					Bots[appid].nick        = connection.self.id;
					Bots[appid].team        = connection.team.id;
					Bots[appid].teamDomain  = connection.team.domain;
					Bots[appid].bootedAt    = moment().utc().unix();
					Bots[appid].channels    = [];
					Bots[appid].errors      = [];

					console.log("Undefined");
					console.log("T: " + Object.size(Bots));

					// Read Channels
					for (var channel in connection.channels) {
						self.addChannel(appid, connection.channels[channel].id, connection.channels[channel].name);
					}

					// Socket Listner
					Bots[appid].websocket.addListener('open', function () {
						console.log(appid + " is now open");
					});

					Bots[appid].addListener('hello', function () {
						console.log(appid + " said hello");
						Bots[appid].isConnected = true;
						//self.say(appid, self.getChannelCode(appid, "general"), "I'm online!");
					});

					Bots[appid].websocket.addListener('close', function () {
						Bots[appid].isConnected = false;
						Bots[appid].removeAllListeners();
						console.log("conn closed");
					});

					// Websocket Events
					Bots[appid].websocket.addListener('message', function (data) {
						var message = JSON.parse(data);

						// Gambiarra
						Bots[appid].msgCount++
						// Ignore the 2 message (last message)
						if(Bots[appid].msgCount == 2) return;

						if(message.type == 'message' && message.channel.indexOf("C") == 0 && typeof message.subtype === 'undefined') {

							console.log("Channel message %j", message);
							Bots[appid].emit('message', message);

						} else if(message.type == 'message' && message.channel.indexOf("C") == 0 && message.subtype === 'file_share') {

							console.log("File shared %j", message);
							var shared_message = {text: "Shared a file: " + message.file.url , channel: message.channel}
							Bots[appid].emit('message', shared_message);

						} else if(message.type == 'message' && message.channel.indexOf("D") == 0) {

							console.log("Direct message %j", message);
							Bots[appid].emit('direct_message', message);

						} else if(typeof message.type !== 'undefined' && message.type !== 'message') {

							console.log("Other message %s %j", message.type, message);
							Bots[appid].emit(message.type, message);

						} else {

							console.log("Undefined message %j", message);

						}
					});

					Bots[appid].addListener('error', function (e) {
						console.log(appid + " had an error");
					});

					// Direct message
					Bots[appid].addListener('direct_message', function (message) {

						// If command
						if(message.text.indexOf("!") == 0 && message.text.length > 1) {
							var command = message.text.substring(1, message.text.length);

							console.log("It's a command: " + command);

							if(command === "time") {
								self.say(appid, message.channel, "_It's now: *" + moment().utc().format() + "*._");
							} else if(command === "uptime") {
								var time = moment(Bots[appid].bootedAt, "X").utc();
								self.say(appid, message.channel, "_Uptime: *" + time.fromNow() + "* @ *" + time.format() + "*._");
							} else {
								// Command not valid!
								self.say(appid, message.channel, "_Sorry! Couldn't reconize the command: *" + command + "*._");
							}

						} else {
							// Reply
							self.say(appid, message.channel, "You said: _" + message.text + "_");
						}

					});

					return Bots[appid];
				}
			});

		} else {
			console.log("Already connected!");
			console.log("T: " + Object.size(Bots));

			return Bots[appid];
		}

	},

	say: function say(appid, channel, message) {
		if(typeof Bots[appid] === 'undefined') {
			return true;
		}

		var data = {
			"type":    "message",
			"channel": channel, //D... or C....
			"user":    Bots[appid].nick,
			"text":    message,
			"ts":      Date.now() + ".000000",
			"team":    Bots[appid].team
		};

		Bots[appid].websocket.send(JSON.stringify(data), function(error) {
			console.log("Error sending data to socket!");
			console.log(error);
			console.log(data);
		});
	},

	// Disconect one websocket
	disconnect: function disconnect(appid) {

		if(typeof Bots[appid] === 'undefined') {
			return true;
		}

		if(Bots[appid].isConnected == true) {
			self.say(appid, self.getChannelCode(appid, "general"), "I'm going offline!");
			Bots[appid].websocket.close();
		}

		return true;
	},

	// Disconect all webockets
	disconnectAll: function disconnectAll() {

		for (var appid in Bots) {
			this.disconnect(appid);
		}

		return true;
	}
};
<|endoftext|>


<commit_before>"use strict";

var reAnsi        = require("ansi-regex")
  , stringifiable = require("es5-ext/object/validate-stringifiable-value")
  , length        = require("./get-stripped-length")
  , sgr           = require("./lib/sgr")

  , max = Math.max;

var Token = function Token(token) {
	this.token = token;
};

var tokenize = function (str) {
	var match = reAnsi().exec(str);

	if (!match) {
		return [str];
	}

	var index = match.index
	  , head, prehead, tail;

	if (index === 0) {
		head = match[0];
		tail = str.slice(head.length);

		return [new Token(head)].concat(tokenize(tail));
	}

	prehead = str.slice(0, index);
	head = match[0];
	tail = str.slice(index + head.length);

	return [prehead, new Token(head)].concat(tokenize(tail));
};

var isChunkInSlice = function (chunk, index, begin, end) {
	var endIndex = chunk.length + index;

	if (begin > endIndex) return false;
	if (end < index) return false;
	return true;
};

var sliceSeq = function (seq, begin, end) {
	var sliced = seq.reduce(function (state, chunk) {
		var index = state.index;

		if (!(chunk instanceof Token)) {
			var nextChunk = "";

			if (isChunkInSlice(chunk, index, begin, end)) {
				var relBegin = Math.max(begin - index, 0)
				  , relEnd   = Math.min(end - index, chunk.length);

				nextChunk = chunk.slice(relBegin, relEnd);
			}

			state.seq.push(nextChunk);
			state.index = index + chunk.length;
		} else {
			var code = sgr.extractCode(chunk.token);

			if (index <= begin) {
				if (code in sgr.openers) {
					sgr.openStyle(state.preOpeners, code);
				}
				if (code in sgr.closers) {
					sgr.closeStyle(state.preOpeners, code);
				}
			} else if (index < end) {
				if (code in sgr.openers) {
					sgr.openStyle(state.inOpeners, code);
					state.seq.push(chunk);
				} else if (code in sgr.closers) {
					state.inClosers.push(code);
					state.seq.push(chunk);
				}
			}
		}

		return state;
	}, {
		index: 0,
		seq: [],

		// preOpeners -> [ mod ]
		// preOpeners must be prepended to the slice if they wasn't closed til the end of it
		// preOpeners must be closed if they wasn't closed til the end of the slice
		preOpeners: [],

		// inOpeners  -> [ mod ]
		// inOpeners already in the slice and must not be prepended to the slice
		// inOpeners must be closed if they wasn't closed til the end of the slice
		inOpeners: [], // opener CSI inside slice

		// inClosers -> [ code ]
		// closer CSIs for determining which pre/in-Openers must be closed
		inClosers: []
	});

	sliced.seq = [].concat(
		sgr.prepend(sliced.preOpeners),
		sliced.seq,
		sgr.complete([].concat(sliced.preOpeners, sliced.inOpeners), sliced.inClosers)
	);

	return sliced.seq;
};

module.exports = function (str/*, begin, end*/) {
	var seq, begin = Number(arguments[1]), end = Number(arguments[2]), len;

	str = stringifiable(str);
	len = length(str);

	if (isNaN(begin)) {
		begin = 0;
	}
	if (isNaN(end)) {
		end = len;
	}
	if (begin < 0) {
		begin = max(len + begin, 0);
	}
	if (end < 0) {
		end = max(len + end, 0);
	}

	seq = tokenize(str);
	seq = sliceSeq(seq, begin, end);
	return seq.map(function (chunk) {
		if (chunk instanceof Token) {
			return chunk.token;
		}

		return chunk;
	}).join("");
};
<commit_msg>style: prettier<commit_after>"use strict";

var reAnsi        = require("ansi-regex")
  , stringifiable = require("es5-ext/object/validate-stringifiable-value")
  , length        = require("./get-stripped-length")
  , sgr           = require("./lib/sgr")
  , max           = Math.max;

var Token = function Token(token) { this.token = token; };

var tokenize = function (str) {
	var match = reAnsi().exec(str);

	if (!match) {
		return [str];
	}

	var index = match.index, head, prehead, tail;

	if (index === 0) {
		head = match[0];
		tail = str.slice(head.length);

		return [new Token(head)].concat(tokenize(tail));
	}

	prehead = str.slice(0, index);
	head = match[0];
	tail = str.slice(index + head.length);

	return [prehead, new Token(head)].concat(tokenize(tail));
};

var isChunkInSlice = function (chunk, index, begin, end) {
	var endIndex = chunk.length + index;

	if (begin > endIndex) return false;
	if (end < index) return false;
	return true;
};

var sliceSeq = function (seq, begin, end) {
	var sliced = seq.reduce(
		function (state, chunk) {
			var index = state.index;

			if (!(chunk instanceof Token)) {
				var nextChunk = "";

				if (isChunkInSlice(chunk, index, begin, end)) {
					var relBegin = Math.max(begin - index, 0)
					  , relEnd = Math.min(end - index, chunk.length);

					nextChunk = chunk.slice(relBegin, relEnd);
				}

				state.seq.push(nextChunk);
				state.index = index + chunk.length;
			} else {
				var code = sgr.extractCode(chunk.token);

				if (index <= begin) {
					if (code in sgr.openers) {
						sgr.openStyle(state.preOpeners, code);
					}
					if (code in sgr.closers) {
						sgr.closeStyle(state.preOpeners, code);
					}
				} else if (index < end) {
					if (code in sgr.openers) {
						sgr.openStyle(state.inOpeners, code);
						state.seq.push(chunk);
					} else if (code in sgr.closers) {
						state.inClosers.push(code);
						state.seq.push(chunk);
					}
				}
			}

			return state;
		},
		{
			index: 0,
			seq: [],

			// preOpeners -> [ mod ]
			// preOpeners must be prepended to the slice if they wasn't closed til the end of it
			// preOpeners must be closed if they wasn't closed til the end of the slice
			preOpeners: [],

			// inOpeners  -> [ mod ]
			// inOpeners already in the slice and must not be prepended to the slice
			// inOpeners must be closed if they wasn't closed til the end of the slice
			inOpeners: [], // opener CSI inside slice

			// inClosers -> [ code ]
			// closer CSIs for determining which pre/in-Openers must be closed
			inClosers: []
		}
	);

	sliced.seq = [].concat(
		sgr.prepend(sliced.preOpeners), sliced.seq,
		sgr.complete([].concat(sliced.preOpeners, sliced.inOpeners), sliced.inClosers)
	);

	return sliced.seq;
};

module.exports = function (str/*, begin, end*/) {
	var seq, begin = Number(arguments[1]), end = Number(arguments[2]), len;

	str = stringifiable(str);
	len = length(str);

	if (isNaN(begin)) {
		begin = 0;
	}
	if (isNaN(end)) {
		end = len;
	}
	if (begin < 0) {
		begin = max(len + begin, 0);
	}
	if (end < 0) {
		end = max(len + end, 0);
	}

	seq = tokenize(str);
	seq = sliceSeq(seq, begin, end);
	return seq
		.map(function (chunk) {
			if (chunk instanceof Token) {
				return chunk.token;
			}

			return chunk;
		})
		.join("");
};
<|endoftext|>


<commit_before>/*
    This is the javascript needed by Slyde itself.

    Do not include your own javascript. Use user.js for that instead.
 */

 (function () {
var slides = document.querySelectorAll("body > section"),
    numberOfSlides = slides.length;

for (var i = 0; i < numberOfSlides; i++) {
    var slide = slides[i];
    slide.id = "slide" + (i + 1);

    var nav = document.createElement("nav");

    if (i > 0) {
        var prev = document.createElement("a");
        prev.appendChild(document.createTextNode("<"));
        prev.href = "#slide" + i;
        nav.appendChild(prev);
    }

    var current = document.createElement("span");
    current.appendChild(document.createTextNode(" " + (i + 1) + " "));
    nav.appendChild(current);

    if (i < numberOfSlides - 1) {
        var next = document.createElement("a");
        next.appendChild(document.createTextNode(">"));
        next.href = "#slide" + (i + 2);
        nav.appendChild(next);
    }

    slide.appendChild(nav);
}

function getHiddenIncrementElements () {
    return [].slice.call(document.querySelectorAll(location.hash + " .increment:not(.show)"));
}

function getShownIncrementElements () {
    return [].slice.call(document.querySelectorAll(location.hash + " .increment.show"));
}

function addClass (element, className) {
    if (element.classList) {
        element.classList.add(className);
    }
    else {
        element.className = element.className + " " + className;
    }
}

function removeClass (element, className) {
    if (element.classList) {
        element.classList.remove(className);
    }
    else {
        element.className = element.className.replace(className, "").trim();
    }
}

function forward() {
    var hidden, nextSlide;

    hidden = getHiddenIncrementElements();

    if (hidden.length) {
        addClass(hidden[0], "show");
    }
    else {
        nextSlide = +location.hash.substring(6) + 1;
        if (nextSlide <= numberOfSlides) {
            location.hash = "#slide" + (nextSlide);
        }
    }
}

function fastForward() {
    var hidden = getHiddenIncrementElements();

    if (hidden.length) {
        hidden.forEach(function (inc) {
            addClass(inc, "show");
        });
    }
    else if (+location.hash.substring(6) < numberOfSlides) {
        location.hash = "#slide" + numberOfSlides;
    }
}

function rewind() {
    var shown, previousSlide;

    shown = getShownIncrementElements();

    if (shown.length) {
        removeClass(shown.pop(), "show");
    }
    else {
        previousSlide = +location.hash.substring(6) - 1;
        if (previousSlide > 0) {
            location.hash = "#slide" + previousSlide;
        }
    }
}

function fastRewind() {
    var shown = getShownIncrementElements();

    if (shown.length) {
        shown.forEach(function (inc) {
            removeClass(inc, "show");
        });
    }
    else if (location.hash !== "#slide1") {
        location.hash = "#slide1";
    }
}

document.addEventListener("keyup", function (event) {
    var key = event.keyCode;

    // Home
    if (key === 36) {
        fastRewind();
    }
    // End
    else if (key === 35) {
        fastForward();
    }
    // left arrow or page up
    else if (key === 37 || key === 33) {
        rewind();
    }
    // right arrow or page down
    else if (key === 39 || key === 34) {
        forward();
    }
}, false);

if (location.hash.indexOf("#slide") !== 0) {
    location.hash = "#slide1";
}
})();<commit_msg>script: modernized js<commit_after>/*
    This is the javascript needed by Slyde itself.

    Do not include your own javascript. Use user.js for that instead.
 */

(function() {
    var slides = document.querySelectorAll("body > section"),
        numberOfSlides = slides.length;

    for (var i = 0; i < numberOfSlides; i++) {
        var slide = slides[i];
        slide.id = "slide" + (i + 1);

        var nav = document.createElement("nav");

        if (i > 0) {
            var prev = document.createElement("a");
            prev.appendChild(document.createTextNode("<"));
            prev.href = "#slide" + i;
            nav.appendChild(prev);
        }

        var current = document.createElement("span");
        current.appendChild(document.createTextNode(" " + (i + 1) + " "));
        nav.appendChild(current);

        if (i < numberOfSlides - 1) {
            var next = document.createElement("a");
            next.appendChild(document.createTextNode(">"));
            next.href = "#slide" + (i + 2);
            nav.appendChild(next);
        }

        slide.appendChild(nav);
    }

    function getHiddenIncrementElements(onCurrentSlideOnly) {
        var root = onCurrentSlideOnly ? document.querySelector(location.hash) : document.body;

        return Array.apply(null, root.querySelectorAll(".increment:not(.show)"));
    }

    function getShownIncrementElements(onCurrentSlideOnly) {
        var root = onCurrentSlideOnly ? document.querySelector(location.hash) : document.body;

        return Array.apply(null, root.querySelectorAll(".increment.show"));
    }

    function addClass(element, className) {
        if (element.classList) {
            element.classList.add(className);
        } else {
            element.className = element.className + " " + className;
        }
    }

    function removeClass(element, className) {
        if (element.classList) {
            element.classList.remove(className);
        } else {
            element.className = element.className.replace(className, "").trim();
        }
    }

    function forward() {
        var hidden, nextSlide;

        hidden = getHiddenIncrementElements(true);

        if (hidden.length) {
            hidden[0].classList.add("show");
        } else {
            nextSlide = +location.hash.substring(6) + 1;

            if (nextSlide <= numberOfSlides) {
                location.hash = "#slide" + (nextSlide);
            }
        }
    }

    function fastForward() {
        var hidden = getHiddenIncrementElements();

        if (hidden.length) {
            hidden.forEach(function(inc) {
                inc.classList.add("show");
            });
        }

        if (+location.hash.substring(6) < numberOfSlides) {
            location.hash = "#slide" + numberOfSlides;
        }
    }

    function rewind() {
        var shown, previousSlide;

        shown = getShownIncrementElements(true);

        if (shown.length) {
            shown.pop().classList.remove("show");
        } else {
            previousSlide = +location.hash.substring(6) - 1;

            if (previousSlide > 0) {
                location.hash = "#slide" + previousSlide;
            }
        }
    }

    function fastRewind() {
        var shown = getShownIncrementElements();

        if (shown.length) {
            shown.forEach(function(inc) {
                inc.classList.remove("show");
            });
        }

        if (location.hash !== "#slide1") {
            location.hash = "#slide1";
        }
    }

    document.addEventListener("keyup", function(event) {
        if (/input|textarea/i.test(event.target.nodeName) && !(event.ctrlKey && event.shiftKey)) {
            return;
        }

        var key = event.keyCode;

        // Home
        if (key === 36) {
            fastRewind();
        }
        // End
        else if (key === 35) {
            fastForward();
        }
        // left arrow or page up
        else if (key === 37 || key === 33) {
            rewind();
        }
        // right arrow or page down
        else if (key === 39 || key === 34) {
            forward();
        }
    }, false);

    if (location.hash.indexOf("#slide") !== 0) {
        location.hash = "#slide1";
    }
})();
<|endoftext|>


<commit_before>var gameState = {
                board: [[]],
                food: [[]],
                game_id: "1",
                height: 35,
                width: 35,
                snakes: [
                {
                  taunt: "",
                  name: "Red Snake",
                  id: 0,
                  health_points: 100,
                  coords: [[]]
                },
                {
                  taunt: "",
                  name: "Blue Snake",
                  id: 1,
                  health_points: 100,
                  coords: [[]]
                }
                ],
                turn: 0,
            };
/**
 * A lightweight game wrapper
 *
 * @constructor
 */
function Game(canvas, options) {
    this.canvas = canvas;
    this.context = canvas.getContext('2d');
    this.snakes = {};

    this.key = [];
    this.entities = [];

    this.options = {
        fps: 5
    };

    if (options) {
        for (var i in options) this.options[i] = options[i];
    }

    this.scale();
}

/**
 * Start the game loop
 * and initialize the keybindings
 */
Game.prototype.start = function () {
    // TODO: Add more snakes
    // this.keyBindings();
    this.gameLoop();
};

/**
 * Stop the game loop
 */
Game.prototype.stop = function() {
    alert("GAME OVER");
    this.pause = true;
};

/**
 * Scale the canvas element
 * in accordance with the correct ratio
 */
Game.prototype.scale = function () {
    this.ratio = innerWidth < innerHeight ? innerWidth : innerHeight;
    this.tile = Math.round(this.ratio / 20);
    this.grid = Math.round(this.ratio / this.tile);

    this.canvas.width = this.canvas.height = this.ratio;
};

/**
 * Adds an entity to the game
 *
 * @param {Function} entity
 */
Game.prototype.addEntity = function (entity) {
    this.entities.push(entity);
};

/**
 * Determines if an entity collides with another
 *
 * @param {Object} a
 * @param {Object} b
 */
Game.prototype.collide = function(a, b){
    return a.x === b.x && a.y === b.y;
};

/**
 * Tracks the pressed keys
 */
Game.prototype.keyBindings = function () {
    var that = this;

    // define some keys
    var keys = {
        a: 65,
        left: 37,
        d: 68,
        right: 39,
        w: 87,
        up: 38,
        s: 83,
        down: 40
    };

    /**
     * Attach keyboard arrows to snake direction
     */
    document.onkeydown = function (e) {
        switch ((e.which || e.keyCode) | 0) {
            case keys.a:
            case keys.left:
                if (that.key !== 'east') that.key = 'west';
                break;

            case keys.d:
            case keys.right:
                if (that.key !== 'west') that.key = 'east';
                break;

            case keys.w:
            case keys.up:
                if (that.key !== 'south') that.key = 'north';
                break;

            case keys.s:
            case keys.down:
                if (that.key !== 'north') that.key = 'south';
        }
    };
};

/**
 * The gameloop - and entity (update/draw) calls
 * Use of `setTimeout` instead of animationFrame
 * in order to keep it simple as possible
 */
Game.prototype.gameLoop = function () {
    if(this.pause) return;

    var self = this,
        ctx = this.context;

    // clear the view area
    ctx.fillStyle = "#123";

    // add some blur
    ctx.globalAlpha = 0.5;
    ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);

    // disable blur
    ctx.globalAlpha = 1;

    var i = this.entities.length;

    while(i--) {
        var entity = this.entities[i];
        if(entity.update) entity.update();
        if(entity.draw) entity.draw(ctx);
    }

    gameState.turn++;
    var redSnake = $.extend({}, gameState);
    redSnake.you = 0;

    var blueSnake = $.extend({}, gameState);
    blueSnake.you = 1;

    Promise.delay(1000/this.options.fps).then(() => {
      return [
        $.ajax({
            type: "post",
            url: "http://localhost:5000/move",
            data: JSON.stringify(redSnake),
            dataType: "json",
            contentType: "application/json"
          }),
        $.ajax({
            type: "post",
            url: "http://localhost:5000/move",
            data: JSON.stringify(blueSnake),
            dataType: "json",
            contentType: "application/json"
          })];
        }).spread((move1, move2) => {
            console.log(move1);
            console.log(move2);
            self.key = [move1.move, move2.move];
            self.gameLoop();
        });
};

/**
 * The whole snake things
 *
 * @constructor
 */
function Snake(game, food, name, options){
    var tile = game.tile;
    var grid = game.grid;
    var collide = game.collide;

    this.x = Math.round(Math.random()*tile-3);
    this.y = Math.round(Math.random()*tile-3);

    this.snakeId = game.snakes[name];

    this.segments = [
      { x: this.x,   y: this.y },
      { x: this.x-1, y: this.y },
      { x: this.x-2, y: this.y }];
    gameState.snakes[this.snakeId].coords[0] = [this.x, this.y];
    gameState.snakes[this.snakeId].coords[1] = [this.x-1, this.y];
    gameState.snakes[this.snakeId].coords[2] = [this.x-2, this.y];

    this.update = function() {
        gameState.snakes[this.snakeId].health_points--;

        var key = game.key[this.snakeId];

        if(key === 'west') this.x--;
        if(key === 'east') this.x++;
        if(key === 'south') this.y--;
        if(key === 'north') this.y++;

        // boundaries
        if (this.x>tile || this.x < 0 || this.y > tile || this.y <0) game.stop();
        /**
         * check snake-food collision
         */
        if (game.collide(this, food)) {

            // randomize point position
            food.spawnFood(game);
            gameState.food =[[food.x, food.y]];
            gameState.snakes[this.snakeId].health_points = 100;

        } else {
            // remove last segment if snake
            // didn't got a point in this turn
            if (this.segments.length) this.segments.pop();
        }

        // push next x and y to the beginning of segments
        this.segments.unshift({x:this.x, y:this.y});

        for (var i = 0; i < this.segments.length; i++) {
            gameState.snakes[this.snakeId].coords[i] = [this.segments[i].x,
                                             this.segments[i].y];
        }

        /**
         * check collision with snake itself - skipping the head (`--i` instead of `i--`)
         */
        var j = this.segments.length;
        while (--j) {
            if(game.collide(this, this.segments[j])) {
                // break the loop and slice the worm in point of intersection
                // here's in reality gameover...
                game.stop();
            }
        }

    };

    this.draw = function(ctx) {
        // draw rectangle for each segment
        // head gets another color
        var i = this.segments.length;
        while (i--) {
            var segment = this.segments[i];
            ctx.fillStyle = !i ? options.headColor : options.bodyColor;
            ctx.fillRect(
            segment.x * grid,
            segment.y * grid,
            grid, grid);
        }
    };
}

function getRandomPoint(bound) {
  return Math.round(Math.random()*(bound-1));
}
/**
 * The whole things to eat
 *
 * @constructor
 */
function Food(game){
    var grid = game.grid;

    this.x = getRandomPoint(game.tile);
    this.y = getRandomPoint(game.tile);
    gameState.food = [[this.x, this.y]];

    this.draw = function(ctx){
        ctx.fillStyle = "#f05";
        ctx.fillRect(this.x * grid, this.y * grid, grid, grid);
    };

    this.spawnFood = function(game) {
      var self = this;
      var gameSnakes = game.entities.filter((e) => {
        return e.segments;
      });

      self.x = getRandomPoint(game.tile);
      self.y = getRandomPoint(game.tile);
      while (true) {
          var match = gameSnakes.filter((s) => {
              return (s.segments.filter((e) => {
                  return game.collide(self,e);
                })).length;
              });
            if (match.length){
              self.x = getRandomPoint(game.tile);
              self.y = getRandomPoint(game.tile);
            }
            else {
              break;
          }
        }
    };
}

window.onload = function() {
    var canvas = document.createElement("canvas");
    document.body.appendChild(canvas);

    var game = new Game(canvas);
    var food = new Food(game);
    game.snakes = {
      'red' : 0,
      'blue': 1
    };
    game.key = ['east', 'north'];

    var redSnake = new Snake(game, food, "red", {
      headColor: "green",
      bodyColor: "#009688"
    });

    var blueSnake = new Snake(game, food, "blue", {
      headColor: "blue",
      bodyColor: "#0cf"
    });

    game.addEntity(food);
    game.addEntity(redSnake);
    game.addEntity(blueSnake);

    gameState.height = gameState.width = game.tile;

    Promise.all([
      $.ajax({
          type: "post",
          url: "http://localhost:5000/start",
          data: JSON.stringify({game_id: 1}),
          dataType: "json",
          contentType: "application/json"
        }),
      $.ajax({
          type: "post",
          url: "http://localhost:5000/start",
          data: JSON.stringify({game_id: 1}),
          dataType: "json",
          contentType: "application/json"
      })
    ]).spread(function() {
        game.start();
    });
};
<commit_msg>Fix snake overflow<commit_after>var gameState = {
                board: [[]],
                food: [[]],
                game_id: "1",
                height: 35,
                width: 35,
                snakes: [
                {
                  taunt: "",
                  name: "Red Snake",
                  id: 0,
                  health_points: 100,
                  coords: [[]]
                },
                {
                  taunt: "",
                  name: "Blue Snake",
                  id: 1,
                  health_points: 100,
                  coords: [[]]
                }
                ],
                turn: 0,
            };
/**
 * A lightweight game wrapper
 *
 * @constructor
 */
function Game(canvas, options) {
    this.canvas = canvas;
    this.context = canvas.getContext('2d');
    this.snakes = {};

    this.key = [];
    this.entities = [];

    this.options = {
        fps: 15
    };

    if (options) {
        for (var i in options) this.options[i] = options[i];
    }

    this.scale();
}

/**
 * Start the game loop
 * and initialize the keybindings
 */
Game.prototype.start = function () {
    // TODO: Add more snakes
    // this.keyBindings();
    this.gameLoop();
};

/**
 * Stop the game loop
 */
Game.prototype.stop = function() {
    alert("GAME OVER");
    this.pause = true;
};

/**
 * Scale the canvas element
 * in accordance with the correct ratio
 */
Game.prototype.scale = function () {
    this.ratio = innerWidth < innerHeight ? innerWidth : innerHeight;
    this.tile = Math.round(this.ratio / 20);
    this.grid = Math.round(this.ratio / this.tile);

    this.canvas.width = this.canvas.height = this.ratio;
};

/**
 * Adds an entity to the game
 *
 * @param {Function} entity
 */
Game.prototype.addEntity = function (entity) {
    this.entities.push(entity);
};

/**
 * Determines if an entity collides with another
 *
 * @param {Object} a
 * @param {Object} b
 */
Game.prototype.collide = function(a, b){
    return a.x === b.x && a.y === b.y;
};

/**
 * Tracks the pressed keys
 */
Game.prototype.keyBindings = function () {
    var that = this;

    // define some keys
    var keys = {
        a: 65,
        left: 37,
        d: 68,
        right: 39,
        w: 87,
        up: 38,
        s: 83,
        down: 40
    };

    /**
     * Attach keyboard arrows to snake direction
     */
    document.onkeydown = function (e) {
        switch ((e.which || e.keyCode) | 0) {
            case keys.a:
            case keys.left:
                if (that.key !== 'east') that.key = 'west';
                break;

            case keys.d:
            case keys.right:
                if (that.key !== 'west') that.key = 'east';
                break;

            case keys.w:
            case keys.up:
                if (that.key !== 'south') that.key = 'north';
                break;

            case keys.s:
            case keys.down:
                if (that.key !== 'north') that.key = 'south';
        }
    };
};

/**
 * The gameloop - and entity (update/draw) calls
 * Use of `setTimeout` instead of animationFrame
 * in order to keep it simple as possible
 */
Game.prototype.gameLoop = function () {
    if(this.pause) return;

    var self = this,
        ctx = this.context;

    // clear the view area
    ctx.fillStyle = "#123";

    // add some blur
    ctx.globalAlpha = 0.5;
    ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);

    // disable blur
    ctx.globalAlpha = 1;

    var i = this.entities.length;

    while(i--) {
        var entity = this.entities[i];
        if(entity.update) entity.update();
        if(entity.draw) entity.draw(ctx);
    }

    gameState.turn++;
    var redSnake = $.extend({}, gameState);
    redSnake.you = 0;

    var blueSnake = $.extend({}, gameState);
    blueSnake.you = 1;

    Promise.delay(1000/this.options.fps).then(() => {
      return [
        $.ajax({
            type: "post",
            url: "http://localhost:5000/move",
            data: JSON.stringify(redSnake),
            dataType: "json",
            contentType: "application/json"
          }),
        $.ajax({
            type: "post",
            url: "http://localhost:5000/move",
            data: JSON.stringify(blueSnake),
            dataType: "json",
            contentType: "application/json"
          })];
        }).spread((move1, move2) => {
            console.log(move1);
            console.log(move2);
            self.key = [move1.move, move2.move];
            self.gameLoop();
        });
};

/**
 * The whole snake things
 *
 * @constructor
 */
function Snake(game, food, name, options){
    var tile = game.tile;
    var grid = game.grid;
    var collide = game.collide;

    this.x = Math.round(Math.random()*(tile-3));
    this.y = Math.round(Math.random()*(tile-3));

    this.snakeId = game.snakes[name];

    this.segments = [
      { x: this.x,   y: this.y },
      { x: this.x-1, y: this.y },
      { x: this.x-2, y: this.y }];
    gameState.snakes[this.snakeId].coords[0] = [this.x, this.y];
    gameState.snakes[this.snakeId].coords[1] = [this.x-1, this.y];
    gameState.snakes[this.snakeId].coords[2] = [this.x-2, this.y];

    this.update = function() {
        gameState.snakes[this.snakeId].health_points--;

        var key = game.key[this.snakeId];

        if(key === 'west') this.x--;
        if(key === 'east') this.x++;
        if(key === 'south') this.y--;
        if(key === 'north') this.y++;

        // boundaries
        if (this.x>tile || this.x < 0 || this.y > tile || this.y <0) game.stop();
        /**
         * check snake-food collision
         */
        if (game.collide(this, food)) {

            // randomize point position
            food.spawnFood(game);
            gameState.food =[[food.x, food.y]];
            gameState.snakes[this.snakeId].health_points = 100;

        } else {
            // remove last segment if snake
            // didn't got a point in this turn
            if (this.segments.length) this.segments.pop();
        }

        // push next x and y to the beginning of segments
        this.segments.unshift({x:this.x, y:this.y});

        for (var i = 0; i < this.segments.length; i++) {
            gameState.snakes[this.snakeId].coords[i] = [this.segments[i].x,
                                             this.segments[i].y];
        }

        /**
         * check collision with snake itself - skipping the head (`--i` instead of `i--`)
         */
        var j = this.segments.length;
        while (--j) {
            if(game.collide(this, this.segments[j])) {
                // break the loop and slice the worm in point of intersection
                // here's in reality gameover...
                game.stop();
            }
        }

    };

    this.draw = function(ctx) {
        // draw rectangle for each segment
        // head gets another color
        var i = this.segments.length;
        while (i--) {
            var segment = this.segments[i];
            ctx.fillStyle = !i ? options.headColor : options.bodyColor;
            ctx.fillRect(
            segment.x * grid,
            segment.y * grid,
            grid, grid);
        }
    };
}

function getRandomPoint(bound) {
  return Math.round(Math.random()*(bound-1));
}
/**
 * The whole things to eat
 *
 * @constructor
 */
function Food(game){
    var grid = game.grid;

    this.x = getRandomPoint(game.tile);
    this.y = getRandomPoint(game.tile);
    gameState.food = [[this.x, this.y]];

    this.draw = function(ctx){
        ctx.fillStyle = "#f05";
        ctx.fillRect(this.x * grid, this.y * grid, grid, grid);
    };

    this.spawnFood = function(game) {
      var self = this;
      var gameSnakes = game.entities.filter((e) => {
        return e.segments;
      });

      self.x = getRandomPoint(game.tile);
      self.y = getRandomPoint(game.tile);
      while (true) {
          var match = gameSnakes.filter((s) => {
              return (s.segments.filter((e) => {
                  return game.collide(self,e);
                })).length;
              });
            if (match.length){
              self.x = getRandomPoint(game.tile);
              self.y = getRandomPoint(game.tile);
            }
            else {
              break;
          }
        }
    };
}

window.onload = function() {
    var canvas = document.createElement("canvas");
    document.body.appendChild(canvas);

    var game = new Game(canvas);
    var food = new Food(game);
    game.snakes = {
      'red' : 0,
      'blue': 1
    };
    game.key = ['east', 'north'];

    var redSnake = new Snake(game, food, "red", {
      headColor: "green",
      bodyColor: "#009688"
    });

    var blueSnake = new Snake(game, food, "blue", {
      headColor: "blue",
      bodyColor: "#0cf"
    });

    game.addEntity(food);
    game.addEntity(redSnake);
    game.addEntity(blueSnake);

    gameState.height = gameState.width = game.tile;

    Promise.all([
      $.ajax({
          type: "post",
          url: "http://localhost:5000/start",
          data: JSON.stringify({game_id: 1}),
          dataType: "json",
          contentType: "application/json"
        }),
      $.ajax({
          type: "post",
          url: "http://localhost:5000/start",
          data: JSON.stringify({game_id: 1}),
          dataType: "json",
          contentType: "application/json"
      })
    ]).spread(function() {
        game.start();
    });
};
<|endoftext|>


<commit_before>var Game = {
	rows: 32,
	cols: 17,
	tileSize: 20,
	borderSize: 5,
	backgroundColor: "#aaaaaa",
}

Game.offset = Game.tileSize + Game.borderSize;
Game.w = Game.rows * Game.offset + Game.borderSize;
Game.h = (Game.cols + 1) * Game.offset + Game.borderSize;

var Utils = {
	randInt: function(n){
		return Math.floor(Math.random() * n);
	},
	randomChoice: function(seq) {
		return seq[Utils.randInt(seq.length)];
	},
}

Crafty.c("Cell", {
	tileSize: Game.tileSize,
	borderSize: Game.borderSize,
	offset: Game.tileSize + Game.borderSize,
	init: function() {
		this.requires("2D, Canvas, Color, Tween, Persist");
		this.attr({w: this.tileSize, h: this.tileSize});
	},
	at: function(x, y) {
		this.attr({
			x: this.borderSize + this.offset * x,
			y: this.borderSize + this.offset * y,
		});
		return this;
	},
	tweenColor: function(color) {
		Crafty.trigger("CellFlip");
		var _this = this;
		var delta = 400;
		this.tween({h: 0, alpha: 0}, delta)
		.one("TweenEnd", function(){
			this.color(color);
			this.tween({h: this.tileSize, alpha: 1}, delta);
		});
		return this;
	},
});

Crafty.c("Grid", {
	backgroundColor: Game.backgroundColor,
	init: function() {
		this.requires("Delay");
		this.bind("SceneChange", this.clearGrid);
	},
	grid: function(rows, cols) {
		this.rows = rows;
		this.cols = cols;
		this.cells = {};
		this._createCells();
		return this;
	},
	at: function(x, y) {
		var key = this._key(x, y);
		if (y === undefined) {
			key = x;
		};
		return this.cells[key];
	},
	clearGrid: function() {
		Crafty.trigger("StopFlipping");
		var maxDelay = 0;
		for (var x = 0; x < this.rows; x++) {
			for (var y = 0; y < this.cols; y++) {
				var delay = this._delayedTweenColor(x, y);
				maxDelay = Math.max(maxDelay, delay);
			};
		};
		var self = this;
		this.delay(function(){
			Crafty.trigger("GridReady");
		}, maxDelay + 800, 0);
	},
	getKeys: function() {
		return Object.keys(this.cells);
	},
	colorAt: function(x, y, color, obj) {
		this.at(x, y).tweenColor(color).obj = obj;
	},
	resetColorAt: function(x, y) {
		this.at(x, y).tweenColor(this.backgroundColor).obj = undefined;
	},
	_createCells: function() {
		for (var x = 0; x < this.rows; x++) {
			for (var y = 0; y < this.cols; y++) {
				this.cells[this._key(x, y)] = Crafty.e("Cell").at(x, y);
			};
		};
	},
	_key: function(x, y) {
		return "" + x + ", " + y;
	},
	_delayedTweenColor: function(x, y) {
		var delay = 100*x + 100*y;
		var self = this;
		this.delay(function() {
			self.resetColorAt(x, y);
		}, delay, 0);
		return delay;
	},
});

Crafty.c("RandomFlipper", {
	init: function() {
		this.requires("Delay");
	},
	grid: function(grid) {
		if (grid === undefined) {
			return this._grid;
		};
		this._grid = grid;
		this.bind("GridReady", this.startFlipping);
		return this;
	},
	flipRandomCell: function() {
		var x = Utils.randInt(this._grid.rows);
		var y = Utils.randInt(this._grid.cols);
		var red = Utils.randInt(256);
		var green = Utils.randInt(256);
		var blue = Utils.randInt(256);
		var rgb = "rgb(" + red + "," + green + "," + blue + ")";
		this._grid.colorAt(x, y, rgb);
	},
	startFlipping: function() {
		this.delay(this.flipRandomCell, 50, -1);
	},
	stopFlipping: function() {
		this.cancelDelay(this.flipRandomCell);
	},
	remove: function() {
		this.stopFlipping();
	},
});

Crafty.c("PointItem", {
	color: "#cccc00",
	pointItem: function(grid) {
		this._grid = grid;
		this.bind("GridReady", this.randomPlacement);
		this.bind("PointItemEaten", this.randomPlacement)
	},
	randomPlacement: function() {
		if (this._key != undefined) {
			this._grid.resetColorAt(this._key);
		};
		var self = this;
		var validPositions = this._grid.getKeys().filter(function(key){
			return self._grid.at(key).obj === undefined;
		});
		this._key = Utils.randomChoice(validPositions);
		this._grid.colorAt(this._key, undefined, this.color, this);
	},
});

Crafty.c("Snake", {
	init: function() {
		this.requires("Delay");
		this._segments = [];
	},
	snake: function(grid, x, y, dir, maxLen, color) {
		this._grid = grid;
		this._segments[0] = {x: x, y: y};
		this._dir = dir;
		this._lastMoveDirection = this._dir;
		this._maxLen = maxLen;
		this._speedFactor = 2000;
		this.color = color;
		this.bind("GridReady", this.startMoving);
		this.bind("OutOfBounds", this.stopMoving);
		this.bind("SelfHit", this.stopMoving);
		return this;
	},
	_recalculateMoveDelay: function() {
		this.cancelDelay(this.move);
		var delta = this._speedFactor / this._maxLen;
		Crafty.trigger("SpeedChanged", 1000 / delta);
		this.delay(this.move, delta, -1);
	},
	startMoving: function() {
		var head = this.head();
		this._grid.colorAt(head.x, head.y, this.color, this);
		this._recalculateMoveDelay();
	},
	stopMoving: function() {
		this.cancelDelay(this.move);
		Crafty.trigger("GameOver");
	},
	move: function() {
		var new_segment = this._newSegment();
		var cell = this._grid.at(new_segment.x, new_segment.y);
		if (cell === undefined) {
			this.trigger("OutOfBounds");
			return;
		};
		if (cell.obj != undefined) {
			if (cell.obj.has("PointItem")) {
				this._maxLen += 1;
				this._recalculateMoveDelay();
				Crafty.trigger("PointItemEaten");
			} else if (cell.obj.has("Snake")) {
				this.trigger("SelfHit");
			};
		};
		this._grid.colorAt(new_segment.x, new_segment.y, this.color, this);
		this._segments.push(new_segment);
		if (this._segments.length > this._maxLen) {
			var old = this._segments.shift();
			this._grid.resetColorAt(old.x, old.y);
		};
		Crafty.trigger("SnakeMoved");
	},
	head: function() {
		return this._segments[this._segments.length - 1];
	},
	_directions: {
		"right": {x: 1, y: 0},
		"left": {x: -1, y: 0},
		"up": {x: 0, y: -1},
		"down": {x: 0, y: 1},
	},
	_resetDirectionIfItPointsBackwards: function() {
		var opposites = {
			"left": "right", "right": "left",
			"up": "down", "down": "up",
		};
		if (opposites[this._dir] === this._lastMoveDirection) {
			this._dir = this._lastMoveDirection;
		};
		this._lastMoveDirection = this._dir;
	},
	_newSegment: function() {
		this._resetDirectionIfItPointsBackwards();
		var head = this.head();
		var delta = this._directions[this._dir];
		return {x: head.x + delta.x, y: head.y + delta.y};
	},
	remove: function() {
		this.stopMoving();
	},
});

Crafty.c("Controls", {
	init: function() {
		this.requires("Keyboard");
		this.keymap = {};
		this.bind("KeyDown", this.handleKeyPress);
	},
	handleKeyPress: function() {
		for (var key in this.keymap) {
			if (this.isDown(key)) {
				this.keymap[key]();
			};
		};
	},
	direction: function(dir) {
		this._dir = dir;
	},
});

Crafty.c("Player1Controls", {
	init: function() {
		this.requires("Controls, Snake");
		this.keymap = {
			"UP_ARROW": this.direction.bind(this, "up"),
			"DOWN_ARROW": this.direction.bind(this, "down"),
			"LEFT_ARROW": this.direction.bind(this, "left"),
			"RIGHT_ARROW": this.direction.bind(this, "right"),
		};
	},
});

Crafty.c("SceneSelectControls", {
	init: function(){
		this.requires("Controls");
		this.keymap = {
			"SPACE": Crafty.scene.bind(Crafty, "SnakeGame"),
			"ESC": Crafty.scene.bind(Crafty, "MainMenu"),
		};
	},
});

Crafty.c("DestroyNoPersist", {
	init: function() {
		this.bind("SceneDestroy", function() {
			Crafty("NoPersist").each(function() {
				this.destroy();
			});
		});
	},
});

Crafty.c("Score", {
	init: function() {
		this._score = 0;
		this._speed = NaN;
		this._gameIsOver = false;
		var _dimensions = {
			x: Game.borderSize,
			y: Game.h - Game.offset,
			w: Game.w - Game.borderSize * 2,
			h: Game.tileSize,
		};
		this._cell = Crafty.e("2D, DOM, Color, Persist")
		.color(Game.backgroundColor)
		.attr(_dimensions);
		this.requires("2D, DOM, Text, Persist")
		.textFont({
			size: "" + Game.tileSize - 4 + "px",
			family: "Orbitron",
		})
		.textColor("#ffffff")
		.attr(_dimensions)
		.attr({x: this.x + 3, y: this.y + 3, h: this.h - 3})
		.reset();
		this.bind("PointItemEaten", this.increment);
		this.bind("ScoreChanged", this.updateText);
		this.bind("SpeedChanged", this.updateSpeed);
		this.bind("GridReady", this.reset);
		this.bind("GameOver", this.gameOver);
	},
	reset: function() {
		this._gameIsOver = false;
		this._score = 0;
		this.trigger("ScoreChanged");
	},
	gameOver: function() {
		this._gameIsOver = true;
		this.trigger("ScoreChanged");
	},
	updateSpeed: function(new_speed) {
		this._speed = new_speed;
		this.trigger("ScoreChanged");
	},
	updateText: function() {
		var prefix = " Speed: ";
		var postfix = "";
		if (this._gameIsOver) {
			postfix += " - Game Over"
		};
		this.text(prefix + this._speed.toFixed(2) + postfix);
		return this;
	},
	increment: function(number) {
		if (number === undefined) {
			number = 1;
		};
		this._score += number;
		this.trigger("ScoreChanged");
		return this;
	},
});

Crafty.scene("SetUp", function() {
	console.log("setup");
	Game.grid = Crafty.e("Grid").grid(Game.rows, Game.cols);
	Game.grid.addComponent("SceneSelectControls");
	Game.grid.addComponent("DestroyNoPersist");
	Game.pointItem = Crafty.e("PointItem").pointItem(Game.grid);
	Game.score = Crafty.e("Score");

	Crafty.bind("GameOver", function(){Crafty.audio.play("bloop");});
	Crafty.bind("SnakeMoved", function(){Crafty.audio.play("blip");});
	Crafty.bind("PointItemEaten", function(){Crafty.audio.play("tik")});
	Crafty.scene("SnakeGame");
});

Crafty.scene("MainMenu", function() {
	console.log("main menu");
	var rf = Crafty.e("RandomFlipper, NoPersist").grid(Game.grid);
});

Crafty.scene("SnakeGame", function() {
	console.log("snake game");
	var snake = Crafty.e("Snake, Player1Controls, NoPersist")
	.snake(Game.grid, 3, 4, "right", 5, "#00cc00");
});

window.onload = function() {
	console.log("Starting Snake Beat...");
	Crafty.init(Game.w, Game.h);
	Crafty.background("#000000");
	Crafty.paths({"audio": "assets/", "images": "assets/"});
	Crafty.load({
		"audio": {
			"blip": ["blip.wav"],
			"tik": ["tik.wav"],
			"bloop": ["bloop.wav"],
		},
	}, function() {
		Crafty.scene("SetUp");
	});
};
<commit_msg>Position text better<commit_after>var Game = {
	rows: 32,
	cols: 17,
	tileSize: 20,
	borderSize: 5,
	backgroundColor: "#aaaaaa",
}

Game.offset = Game.tileSize + Game.borderSize;
Game.w = Game.rows * Game.offset + Game.borderSize;
Game.h = (Game.cols + 1) * Game.offset + Game.borderSize;

var Utils = {
	randInt: function(n){
		return Math.floor(Math.random() * n);
	},
	randomChoice: function(seq) {
		return seq[Utils.randInt(seq.length)];
	},
}

Crafty.c("Cell", {
	tileSize: Game.tileSize,
	borderSize: Game.borderSize,
	offset: Game.tileSize + Game.borderSize,
	init: function() {
		this.requires("2D, Canvas, Color, Tween, Persist");
		this.attr({w: this.tileSize, h: this.tileSize});
	},
	at: function(x, y) {
		this.attr({
			x: this.borderSize + this.offset * x,
			y: this.borderSize + this.offset * y,
		});
		return this;
	},
	tweenColor: function(color) {
		Crafty.trigger("CellFlip");
		var _this = this;
		var delta = 400;
		this.tween({h: 0, alpha: 0}, delta)
		.one("TweenEnd", function(){
			this.color(color);
			this.tween({h: this.tileSize, alpha: 1}, delta);
		});
		return this;
	},
});

Crafty.c("Grid", {
	backgroundColor: Game.backgroundColor,
	init: function() {
		this.requires("Delay");
		this.bind("SceneChange", this.clearGrid);
	},
	grid: function(rows, cols) {
		this.rows = rows;
		this.cols = cols;
		this.cells = {};
		this._createCells();
		return this;
	},
	at: function(x, y) {
		var key = this._key(x, y);
		if (y === undefined) {
			key = x;
		};
		return this.cells[key];
	},
	clearGrid: function() {
		Crafty.trigger("StopFlipping");
		var maxDelay = 0;
		for (var x = 0; x < this.rows; x++) {
			for (var y = 0; y < this.cols; y++) {
				var delay = this._delayedTweenColor(x, y);
				maxDelay = Math.max(maxDelay, delay);
			};
		};
		var self = this;
		this.delay(function(){
			Crafty.trigger("GridReady");
		}, maxDelay + 800, 0);
	},
	getKeys: function() {
		return Object.keys(this.cells);
	},
	colorAt: function(x, y, color, obj) {
		this.at(x, y).tweenColor(color).obj = obj;
	},
	resetColorAt: function(x, y) {
		this.at(x, y).tweenColor(this.backgroundColor).obj = undefined;
	},
	_createCells: function() {
		for (var x = 0; x < this.rows; x++) {
			for (var y = 0; y < this.cols; y++) {
				this.cells[this._key(x, y)] = Crafty.e("Cell").at(x, y);
			};
		};
	},
	_key: function(x, y) {
		return "" + x + ", " + y;
	},
	_delayedTweenColor: function(x, y) {
		var delay = 100*x + 100*y;
		var self = this;
		this.delay(function() {
			self.resetColorAt(x, y);
		}, delay, 0);
		return delay;
	},
});

Crafty.c("RandomFlipper", {
	init: function() {
		this.requires("Delay");
	},
	grid: function(grid) {
		if (grid === undefined) {
			return this._grid;
		};
		this._grid = grid;
		this.bind("GridReady", this.startFlipping);
		return this;
	},
	flipRandomCell: function() {
		var x = Utils.randInt(this._grid.rows);
		var y = Utils.randInt(this._grid.cols);
		var red = Utils.randInt(256);
		var green = Utils.randInt(256);
		var blue = Utils.randInt(256);
		var rgb = "rgb(" + red + "," + green + "," + blue + ")";
		this._grid.colorAt(x, y, rgb);
	},
	startFlipping: function() {
		this.delay(this.flipRandomCell, 50, -1);
	},
	stopFlipping: function() {
		this.cancelDelay(this.flipRandomCell);
	},
	remove: function() {
		this.stopFlipping();
	},
});

Crafty.c("PointItem", {
	color: "#cccc00",
	pointItem: function(grid) {
		this._grid = grid;
		this.bind("GridReady", this.randomPlacement);
		this.bind("PointItemEaten", this.randomPlacement)
	},
	randomPlacement: function() {
		if (this._key != undefined) {
			this._grid.resetColorAt(this._key);
		};
		var self = this;
		var validPositions = this._grid.getKeys().filter(function(key){
			return self._grid.at(key).obj === undefined;
		});
		this._key = Utils.randomChoice(validPositions);
		this._grid.colorAt(this._key, undefined, this.color, this);
	},
});

Crafty.c("Snake", {
	init: function() {
		this.requires("Delay");
		this._segments = [];
	},
	snake: function(grid, x, y, dir, maxLen, color) {
		this._grid = grid;
		this._segments[0] = {x: x, y: y};
		this._dir = dir;
		this._lastMoveDirection = this._dir;
		this._maxLen = maxLen;
		this._speedFactor = 2000;
		this.color = color;
		this.bind("GridReady", this.startMoving);
		this.bind("OutOfBounds", this.stopMoving);
		this.bind("SelfHit", this.stopMoving);
		return this;
	},
	_recalculateMoveDelay: function() {
		this.cancelDelay(this.move);
		var delta = this._speedFactor / this._maxLen;
		Crafty.trigger("SpeedChanged", 1000 / delta);
		this.delay(this.move, delta, -1);
	},
	startMoving: function() {
		var head = this.head();
		this._grid.colorAt(head.x, head.y, this.color, this);
		this._recalculateMoveDelay();
	},
	stopMoving: function() {
		this.cancelDelay(this.move);
		Crafty.trigger("GameOver");
	},
	move: function() {
		var new_segment = this._newSegment();
		var cell = this._grid.at(new_segment.x, new_segment.y);
		if (cell === undefined) {
			this.trigger("OutOfBounds");
			return;
		};
		if (cell.obj != undefined) {
			if (cell.obj.has("PointItem")) {
				this._maxLen += 1;
				this._recalculateMoveDelay();
				Crafty.trigger("PointItemEaten");
			} else if (cell.obj.has("Snake")) {
				this.trigger("SelfHit");
			};
		};
		this._grid.colorAt(new_segment.x, new_segment.y, this.color, this);
		this._segments.push(new_segment);
		if (this._segments.length > this._maxLen) {
			var old = this._segments.shift();
			this._grid.resetColorAt(old.x, old.y);
		};
		Crafty.trigger("SnakeMoved");
	},
	head: function() {
		return this._segments[this._segments.length - 1];
	},
	_directions: {
		"right": {x: 1, y: 0},
		"left": {x: -1, y: 0},
		"up": {x: 0, y: -1},
		"down": {x: 0, y: 1},
	},
	_resetDirectionIfItPointsBackwards: function() {
		var opposites = {
			"left": "right", "right": "left",
			"up": "down", "down": "up",
		};
		if (opposites[this._dir] === this._lastMoveDirection) {
			this._dir = this._lastMoveDirection;
		};
		this._lastMoveDirection = this._dir;
	},
	_newSegment: function() {
		this._resetDirectionIfItPointsBackwards();
		var head = this.head();
		var delta = this._directions[this._dir];
		return {x: head.x + delta.x, y: head.y + delta.y};
	},
	remove: function() {
		this.stopMoving();
	},
});

Crafty.c("Controls", {
	init: function() {
		this.requires("Keyboard");
		this.keymap = {};
		this.bind("KeyDown", this.handleKeyPress);
	},
	handleKeyPress: function() {
		for (var key in this.keymap) {
			if (this.isDown(key)) {
				this.keymap[key]();
			};
		};
	},
	direction: function(dir) {
		this._dir = dir;
	},
});

Crafty.c("Player1Controls", {
	init: function() {
		this.requires("Controls, Snake");
		this.keymap = {
			"UP_ARROW": this.direction.bind(this, "up"),
			"DOWN_ARROW": this.direction.bind(this, "down"),
			"LEFT_ARROW": this.direction.bind(this, "left"),
			"RIGHT_ARROW": this.direction.bind(this, "right"),
		};
	},
});

Crafty.c("SceneSelectControls", {
	init: function(){
		this.requires("Controls");
		this.keymap = {
			"SPACE": Crafty.scene.bind(Crafty, "SnakeGame"),
			"ESC": Crafty.scene.bind(Crafty, "MainMenu"),
		};
	},
});

Crafty.c("DestroyNoPersist", {
	init: function() {
		this.bind("SceneDestroy", function() {
			Crafty("NoPersist").each(function() {
				this.destroy();
			});
		});
	},
});

Crafty.c("Score", {
	init: function() {
		this._score = 0;
		this._speed = NaN;
		this._gameIsOver = false;
		var _dimensions = {
			x: Game.borderSize,
			y: Game.h - Game.offset,
			w: Game.w - Game.borderSize * 2,
			h: Game.tileSize,
		};
		this._cell = Crafty.e("2D, DOM, Color, Persist")
		.color(Game.backgroundColor)
		.attr(_dimensions);
		this.requires("2D, DOM, Text, Persist")
		.textFont({
			size: "" + Game.tileSize - 4 + "px",
			family: "Orbitron",
		})
		.textColor("#ffffff")
		.attr(_dimensions)
		.attr({x: this.x + 2, y: this.y + 2, h: this.h - 3})
		.reset();
		this.bind("PointItemEaten", this.increment);
		this.bind("ScoreChanged", this.updateText);
		this.bind("SpeedChanged", this.updateSpeed);
		this.bind("GridReady", this.reset);
		this.bind("GameOver", this.gameOver);
	},
	reset: function() {
		this._gameIsOver = false;
		this._score = 0;
		this.trigger("ScoreChanged");
	},
	gameOver: function() {
		this._gameIsOver = true;
		this.trigger("ScoreChanged");
	},
	updateSpeed: function(new_speed) {
		this._speed = new_speed;
		this.trigger("ScoreChanged");
	},
	updateText: function() {
		var prefix = " Speed: ";
		var postfix = "";
		if (this._gameIsOver) {
			postfix += " - Game Over"
		};
		this.text(prefix + this._speed.toFixed(2) + postfix);
		return this;
	},
	increment: function(number) {
		if (number === undefined) {
			number = 1;
		};
		this._score += number;
		this.trigger("ScoreChanged");
		return this;
	},
});

Crafty.scene("SetUp", function() {
	console.log("setup");
	Game.grid = Crafty.e("Grid").grid(Game.rows, Game.cols);
	Game.grid.addComponent("SceneSelectControls");
	Game.grid.addComponent("DestroyNoPersist");
	Game.pointItem = Crafty.e("PointItem").pointItem(Game.grid);
	Game.score = Crafty.e("Score");

	Crafty.bind("GameOver", function(){Crafty.audio.play("bloop");});
	Crafty.bind("SnakeMoved", function(){Crafty.audio.play("blip");});
	Crafty.bind("PointItemEaten", function(){Crafty.audio.play("tik")});
	Crafty.scene("SnakeGame");
});

Crafty.scene("MainMenu", function() {
	console.log("main menu");
	var rf = Crafty.e("RandomFlipper, NoPersist").grid(Game.grid);
});

Crafty.scene("SnakeGame", function() {
	console.log("snake game");
	var snake = Crafty.e("Snake, Player1Controls, NoPersist")
	.snake(Game.grid, 3, 4, "right", 5, "#00cc00");
});

window.onload = function() {
	console.log("Starting Snake Beat...");
	Crafty.init(Game.w, Game.h);
	Crafty.background("#000000");
	Crafty.paths({"audio": "assets/", "images": "assets/"});
	Crafty.load({
		"audio": {
			"blip": ["blip.wav"],
			"tik": ["tik.wav"],
			"bloop": ["bloop.wav"],
		},
	}, function() {
		Crafty.scene("SetUp");
	});
};
<|endoftext|>


<commit_before>var Game = {
	rows: 16,
	cols: 9,
	tileSize: 20,
	borderSize: 5,
}

Game.offset = Game.tileSize + Game.borderSize;
Game.w = Game.rows * Game.offset + Game.borderSize;
Game.h = Game.cols * Game.offset + Game.borderSize;

var Utils = {
	randInt: function(n){
		return Math.floor(Math.random() * n);
	},
}

Crafty.c("Cell", {
	tileSize: Game.tileSize,
	borderSize: Game.borderSize,
	offset: Game.tileSize + Game.borderSize,
	init: function() {
		this.requires("2D, DOM, Color, Tween, Persist");
		this.attr({w: this.tileSize, h: this.tileSize});
	},
	at: function(x, y) {
		this.attr({
			x: this.borderSize + this.offset * x,
			y: this.borderSize + this.offset * y,
		});
		return this;
	},
	tweenColor: function(color) {
		var _this = this;
		var delta = 400;
		this.tween({h: 0, alpha: 0}, delta)
		.one("TweenEnd", function(){
			this.color(color);
			this.tween({h: this.tileSize, alpha: 1}, delta);
		});
		return this;
	},
});

Crafty.c("Grid", {
	backgroundColor: "#aaaaaa",
	init: function() {
		this.requires("Delay");
		this.bind("SceneChange", this.clearGrid);
	},
	grid: function(rows, cols) {
		this.rows = rows;
		this.cols = cols;
		this.cells = {};
		this._createCells();
		return this;
	},
	at: function(x, y) {
		return this.cells[this._key(x, y)];
	},
	clearGrid: function() {
		Crafty.trigger("StopFlipping");
		var maxDelay = 0;
		for (var x = 0; x < this.rows; x++) {
			for (var y = 0; y < this.cols; y++) {
				var delay = this._delayedTweenColor(x, y);
				maxDelay = Math.max(maxDelay, delay);
			};
		};
		var self = this;
		this.delay(function(){
			Crafty.trigger("GridReady");
		}, maxDelay + 800, 0);
	},
	colorAt: function(x, y, color, obj) {
		this.at(x, y).tweenColor(color).obj = obj;
	},
	resetColorAt: function(x, y) {
		this.at(x, y).tweenColor(this.backgroundColor).obj = undefined;
	},
	_createCells: function() {
		for (var x = 0; x < this.rows; x++) {
			for (var y = 0; y < this.cols; y++) {
				this.cells[this._key(x, y)] = Crafty.e("Cell").at(x, y);
			};
		};
	},
	_key: function(x, y) {
		return "" + x + ", " + y;
	},
	_delayedTweenColor: function(x, y) {
		var delay = 100*x + 100*y;
		var self = this;
		this.delay(function() {
			self.resetColorAt(x, y);
		}, delay, 0);
		return delay;
	},
});

Crafty.c("RandomFlipper", {
	init: function() {
		this.requires("Delay");
	},
	grid: function(grid) {
		if (grid === undefined) {
			return this._grid;
		};
		this._grid = grid;
		this.bind("GridReady", this.startFlipping);
		return this;
	},
	flipRandomCell: function() {
		var x = Utils.randInt(this._grid.rows);
		var y = Utils.randInt(this._grid.cols);
		var red = Utils.randInt(256);
		var green = Utils.randInt(256);
		var blue = Utils.randInt(256);
		var rgb = "rgb(" + red + "," + green + "," + blue + ")";
		this._grid.colorAt(x, y, rgb);
	},
	startFlipping: function() {
		this.delay(this.flipRandomCell, 50, -1);
	},
	stopFlipping: function() {
		this.cancelDelay(this.flipRandomCell);
	},
	remove: function() {
		this.stopFlipping();
	},
});

Crafty.c("PointItem", {
	color: "#cccc00",
	pointItem: function(grid) {
		this._grid = grid;
		this.bind("GridReady", this.randomPlacement);
		this.bind("PointItemEaten", this.randomPlacement)
	},
	randomPlacement: function() {
		if (this.x != undefined && this.y != undefined) {
			this._grid.resetColorAt(this.x, this.y);
		};
		this.x = Utils.randInt(this._grid.rows);
		this.y = Utils.randInt(this._grid.cols);
		this._grid.colorAt(this.x, this.y, this.color, this);
	},
});

Crafty.c("Snake", {
	init: function() {
		this.requires("Delay");
		this._segments = [];
	},
	snake: function(grid, x, y, dir, maxLen, color) {
		this._grid = grid;
		this._segments[0] = {x: x, y: y};
		this._dir = dir;
		this._maxLen = maxLen;
		this.color = color;
		this.bind("GridReady", this.startMoving);
		this.bind("OutOfBounds", this.stopMoving);
		return this;
	},
	startMoving: function() {
		var head = this.head();
		this._grid.colorAt(head.x, head.y, this.color, this);
		this.delay(this.move, 400, -1);
	},
	stopMoving: function() {
		this.cancelDelay(this.move);
	},
	move: function() {
		var new_segment = this._newSegment();
		var cell = this._grid.at(new_segment.x, new_segment.y);
		if (cell === undefined) {
			this.trigger("OutOfBounds");
			return;
		};
		if (cell.obj != undefined && cell.obj.has("PointItem")) {
			this._maxLen += 1;
			Crafty.trigger("PointItemEaten");
		};
		this._grid.colorAt(new_segment.x, new_segment.y, this.color);
		this._segments.push(new_segment);
		if (this._segments.length > this._maxLen) {
			var old = this._segments.shift();
			this._grid.resetColorAt(old.x, old.y);
		};
	},
	head: function() {
		return this._segments[this._segments.length - 1];
	},
	_directions: {
		"right": {x: 1, y: 0},
		"left": {x: -1, y: 0},
		"up": {x: 0, y: -1},
		"down": {x: 0, y: 1},
	},
	_newSegment: function() {
		var head = this.head();
		var delta = this._directions[this._dir];
		return {x: head.x + delta.x, y: head.y + delta.y};
	},
	remove: function() {
		this.stopMoving();
	},
});

Crafty.c("Controls", {
	init: function() {
		this.requires("Keyboard");
		this.keymap = {};
		this.bind("KeyDown", this.handleKeyPress);
	},
	handleKeyPress: function() {
		for (var key in this.keymap) {
			if (this.isDown(key)) {
				this.keymap[key]();
			};
		};
	},
	direction: function(dir) {
		this._dir = dir;
	},
});

Crafty.c("Player1Controls", {
	init: function() {
		this.requires("Controls, Snake");
		this.keymap = {
			"UP_ARROW": this.direction.bind(this, "up"),
			"DOWN_ARROW": this.direction.bind(this, "down"),
			"LEFT_ARROW": this.direction.bind(this, "left"),
			"RIGHT_ARROW": this.direction.bind(this, "right"),
		};
	},
});

Crafty.c("SceneSelectControls", {
	init: function(){
		this.requires("Controls");
		this.keymap = {
			"SPACE": Crafty.scene.bind(Crafty, "SnakeGame"),
			"ESC": Crafty.scene.bind(Crafty, "MainMenu"),
		};
	},
});

Crafty.c("DestroyNoPersist", {
	init: function() {
		this.bind("SceneDestroy", function() {
			Crafty("NoPersist").each(function() {
				this.destroy();
			});
		});
	},
});

Crafty.scene("MainMenu", function() {
	console.log("main menu");
	var rf = Crafty.e("RandomFlipper, NoPersist").grid(Game.grid);
});

Crafty.scene("SnakeGame", function() {
	console.log("snake game");
	var snake = Crafty.e("Snake, Player1Controls, NoPersist")
	.snake(Game.grid, 3, 4, "right", 5, "#00cc00");
	var pointItem = Crafty.e("PointItem").pointItem(Game.grid);
});

window.onload = function() {
	console.log("Starting Snake Beat...");
	Crafty.init(Game.w, Game.h);
	Crafty.background("#000000");
	Game.grid = Crafty.e("Grid").grid(Game.rows, Game.cols);
	Game.grid.addComponent("SceneSelectControls");
	Game.grid.addComponent("DestroyNoPersist");
	Crafty.scene("SnakeGame");
};
<commit_msg>Register snake hitting itself but do nothing about it<commit_after>var Game = {
	rows: 16,
	cols: 9,
	tileSize: 20,
	borderSize: 5,
}

Game.offset = Game.tileSize + Game.borderSize;
Game.w = Game.rows * Game.offset + Game.borderSize;
Game.h = Game.cols * Game.offset + Game.borderSize;

var Utils = {
	randInt: function(n){
		return Math.floor(Math.random() * n);
	},
}

Crafty.c("Cell", {
	tileSize: Game.tileSize,
	borderSize: Game.borderSize,
	offset: Game.tileSize + Game.borderSize,
	init: function() {
		this.requires("2D, DOM, Color, Tween, Persist");
		this.attr({w: this.tileSize, h: this.tileSize});
	},
	at: function(x, y) {
		this.attr({
			x: this.borderSize + this.offset * x,
			y: this.borderSize + this.offset * y,
		});
		return this;
	},
	tweenColor: function(color) {
		var _this = this;
		var delta = 400;
		this.tween({h: 0, alpha: 0}, delta)
		.one("TweenEnd", function(){
			this.color(color);
			this.tween({h: this.tileSize, alpha: 1}, delta);
		});
		return this;
	},
});

Crafty.c("Grid", {
	backgroundColor: "#aaaaaa",
	init: function() {
		this.requires("Delay");
		this.bind("SceneChange", this.clearGrid);
	},
	grid: function(rows, cols) {
		this.rows = rows;
		this.cols = cols;
		this.cells = {};
		this._createCells();
		return this;
	},
	at: function(x, y) {
		return this.cells[this._key(x, y)];
	},
	clearGrid: function() {
		Crafty.trigger("StopFlipping");
		var maxDelay = 0;
		for (var x = 0; x < this.rows; x++) {
			for (var y = 0; y < this.cols; y++) {
				var delay = this._delayedTweenColor(x, y);
				maxDelay = Math.max(maxDelay, delay);
			};
		};
		var self = this;
		this.delay(function(){
			Crafty.trigger("GridReady");
		}, maxDelay + 800, 0);
	},
	colorAt: function(x, y, color, obj) {
		this.at(x, y).tweenColor(color).obj = obj;
	},
	resetColorAt: function(x, y) {
		this.at(x, y).tweenColor(this.backgroundColor).obj = undefined;
	},
	_createCells: function() {
		for (var x = 0; x < this.rows; x++) {
			for (var y = 0; y < this.cols; y++) {
				this.cells[this._key(x, y)] = Crafty.e("Cell").at(x, y);
			};
		};
	},
	_key: function(x, y) {
		return "" + x + ", " + y;
	},
	_delayedTweenColor: function(x, y) {
		var delay = 100*x + 100*y;
		var self = this;
		this.delay(function() {
			self.resetColorAt(x, y);
		}, delay, 0);
		return delay;
	},
});

Crafty.c("RandomFlipper", {
	init: function() {
		this.requires("Delay");
	},
	grid: function(grid) {
		if (grid === undefined) {
			return this._grid;
		};
		this._grid = grid;
		this.bind("GridReady", this.startFlipping);
		return this;
	},
	flipRandomCell: function() {
		var x = Utils.randInt(this._grid.rows);
		var y = Utils.randInt(this._grid.cols);
		var red = Utils.randInt(256);
		var green = Utils.randInt(256);
		var blue = Utils.randInt(256);
		var rgb = "rgb(" + red + "," + green + "," + blue + ")";
		this._grid.colorAt(x, y, rgb);
	},
	startFlipping: function() {
		this.delay(this.flipRandomCell, 50, -1);
	},
	stopFlipping: function() {
		this.cancelDelay(this.flipRandomCell);
	},
	remove: function() {
		this.stopFlipping();
	},
});

Crafty.c("PointItem", {
	color: "#cccc00",
	pointItem: function(grid) {
		this._grid = grid;
		this.bind("GridReady", this.randomPlacement);
		this.bind("PointItemEaten", this.randomPlacement)
	},
	randomPlacement: function() {
		if (this.x != undefined && this.y != undefined) {
			this._grid.resetColorAt(this.x, this.y);
		};
		this.x = Utils.randInt(this._grid.rows);
		this.y = Utils.randInt(this._grid.cols);
		this._grid.colorAt(this.x, this.y, this.color, this);
	},
});

Crafty.c("Snake", {
	init: function() {
		this.requires("Delay");
		this._segments = [];
	},
	snake: function(grid, x, y, dir, maxLen, color) {
		this._grid = grid;
		this._segments[0] = {x: x, y: y};
		this._dir = dir;
		this._maxLen = maxLen;
		this.color = color;
		this.bind("GridReady", this.startMoving);
		this.bind("OutOfBounds", this.stopMoving);
		return this;
	},
	startMoving: function() {
		var head = this.head();
		this._grid.colorAt(head.x, head.y, this.color, this);
		this.delay(this.move, 400, -1);
	},
	stopMoving: function() {
		this.cancelDelay(this.move);
	},
	move: function() {
		var new_segment = this._newSegment();
		var cell = this._grid.at(new_segment.x, new_segment.y);
		if (cell === undefined) {
			this.trigger("OutOfBounds");
			return;
		};
		if (cell.obj != undefined) {
			if (cell.obj.has("PointItem")) {
				this._maxLen += 1;
				Crafty.trigger("PointItemEaten");
			} else if (cell.obj.has("Snake")) {
				console.log("self hit");
				this.trigger("SelfHit");
			};
		};
		this._grid.colorAt(new_segment.x, new_segment.y, this.color, this);
		this._segments.push(new_segment);
		if (this._segments.length > this._maxLen) {
			var old = this._segments.shift();
			this._grid.resetColorAt(old.x, old.y);
		};
	},
	head: function() {
		return this._segments[this._segments.length - 1];
	},
	_directions: {
		"right": {x: 1, y: 0},
		"left": {x: -1, y: 0},
		"up": {x: 0, y: -1},
		"down": {x: 0, y: 1},
	},
	_newSegment: function() {
		var head = this.head();
		var delta = this._directions[this._dir];
		return {x: head.x + delta.x, y: head.y + delta.y};
	},
	remove: function() {
		this.stopMoving();
	},
});

Crafty.c("Controls", {
	init: function() {
		this.requires("Keyboard");
		this.keymap = {};
		this.bind("KeyDown", this.handleKeyPress);
	},
	handleKeyPress: function() {
		for (var key in this.keymap) {
			if (this.isDown(key)) {
				this.keymap[key]();
			};
		};
	},
	direction: function(dir) {
		this._dir = dir;
	},
});

Crafty.c("Player1Controls", {
	init: function() {
		this.requires("Controls, Snake");
		this.keymap = {
			"UP_ARROW": this.direction.bind(this, "up"),
			"DOWN_ARROW": this.direction.bind(this, "down"),
			"LEFT_ARROW": this.direction.bind(this, "left"),
			"RIGHT_ARROW": this.direction.bind(this, "right"),
		};
	},
});

Crafty.c("SceneSelectControls", {
	init: function(){
		this.requires("Controls");
		this.keymap = {
			"SPACE": Crafty.scene.bind(Crafty, "SnakeGame"),
			"ESC": Crafty.scene.bind(Crafty, "MainMenu"),
		};
	},
});

Crafty.c("DestroyNoPersist", {
	init: function() {
		this.bind("SceneDestroy", function() {
			Crafty("NoPersist").each(function() {
				this.destroy();
			});
		});
	},
});

Crafty.scene("MainMenu", function() {
	console.log("main menu");
	var rf = Crafty.e("RandomFlipper, NoPersist").grid(Game.grid);
});

Crafty.scene("SnakeGame", function() {
	console.log("snake game");
	var snake = Crafty.e("Snake, Player1Controls, NoPersist")
	.snake(Game.grid, 3, 4, "right", 5, "#00cc00");
	var pointItem = Crafty.e("PointItem").pointItem(Game.grid);
});

window.onload = function() {
	console.log("Starting Snake Beat...");
	Crafty.init(Game.w, Game.h);
	Crafty.background("#000000");
	Game.grid = Crafty.e("Grid").grid(Game.rows, Game.cols);
	Game.grid.addComponent("SceneSelectControls");
	Game.grid.addComponent("DestroyNoPersist");
	Crafty.scene("SnakeGame");
};
<|endoftext|>