<head>
<title>Hungry Kittens — Shiny Demos</title>
<meta charset='utf8'>
<meta name="viewport" id="viewport" content="width=500">
<link rel="stylesheet" href="styles/style.css">
<link rel="stylesheet" href="/styles/panel.css">
<link rel='stylesheet' href='http://fonts.googleapis.com/css?family=Special+Elite'>
</head>
<body>
<div id="demo">
<h1>Hungry Kittens (Room #<span id="room"></span>)</h1>
<div id="info">
<p id="fps"></p>
</div>
<div id="container"></div>
<div id="toolbar">
<span class="leftside">
<span class="button meow" id="button-meow"></span>
<span class="button jump"id="button-up"></span>
</span>
<span class="rightside">
<span class="button left" id="button-left"></span>
<span class="button right" id="button-right"></span>
</span>
</div>
<script src="/scripts/modernizr.js"></script>
<script src="/scripts/panel.js"></script>
<script src="scripts/options.js"></script>
<script src="scripts/sprite.js"></script>
<script src="scripts/game.js"></script>
</div>
<script>
var Game = function() {
var messageBox = document.getElementById("messages");
var LEFT = 0, JUMP = 1, RIGHT = 2, MEOW = 3;
//buttons = [left, right, jump, meow]
var buttons = [false, false, false, false];
var cats = {};
var me, socket;
// creates a scene using sprite.js
var SCENE_WIDTH = 500;
var SCENE_HEIGHT = 490;
var container = document.getElementById('container');
var scene = sjs.Scene({ parent:container, w: SCENE_WIDTH, h: SCENE_HEIGHT, autoPause: false });
var ticker;
// plays meow sound, if this cat meows also broadcasts to server
var meow = function (broadcast) {
var a = document.createElement("audio");
a.src = "http://media.shinydemos.com/hungry-kittens/meow" + (Math.round(Math.random()*10) % 5) + ".wav";
a.addEventListener("ended", function () { a.parentNode.removeChild(a); }, false);
document.body.appendChild(a);
a.play();
if (broadcast){
socket.send(JSON.stringify({ type: "meow", data: me.catId }));
}
};
// create Cat as a subclass of Sprite
var Cat = function (scene, data) {
var w = 32; // side of the cat's sprite frame, in px
this.catId = data.id;
this.name = escapeString(data.name);
this.race = this.catId % 4; // assigning one of four races in the sprite sheet (we're an equal opportunity app)
this.isJumping = false;
this.jumpSpeed = 15; // initial jumping speed
this.ySpeed = 0;
this.frame = 0;
this.looking = "left";
//Sprite by WidgetWorx @ http://www.widgetworx.com/widgetworx/portfolio/spritelib.html
sjs.Sprite.call(this, scene, "../images/sprite.gif", {
size: [w, w],
x: data.x,
y: 0,
xoffset: this.race * 3 * w,
yoffset: 5 * w,
layer: scene.layers["default"]
});
// adding a tag with the kitten's name
var tag = document.createElement("span");
tag.className = "nametag";
tag.innerHTML = this.name;
this.dom.appendChild(tag);
};
Cat.prototype = Object.create(sjs.Sprite.prototype, {
turnHead: { value: function (direction) {
if (direction == "left")
this.lookLeft();
else if (direction == "right")
this.lookRight();
}},
lookLeft: { value: function () {
this.looking = "left";
this.setYOffset(5*this.w);
}},
lookRight: { value: function () {
this.looking = "right";
this.setYOffset(6*this.w);
}},
walk: { value: function () {
this.frame = ++this.frame % 3;
this.setXOffset((this.frame + this.race * 3) * this.w);
}},
jump: { value: function() {
if (!this.isJumping) {
this.isJumping = true;
this.ySpeed = this.jumpSpeed;
}
}},
move: { value: function (x, y, boundingX) {
sjs.Sprite.prototype.move.call(this, x, y);
if (this.x > boundingX)
this.setX(-this.w);
if (this.x < -this.w)
this.setX(boundingX);
return this;
}},
update: { value: function () {
if (this.isJumping) {
this.setY(this.y - this.ySpeed);
this.ySpeed--;
if (this.ySpeed < -this.jumpSpeed){
this.isJumping = false;
}
}
return sjs.Sprite.prototype.update.call(this);
}}
});
var sendMove = function () {
socket.send(JSON.stringify({
type: "move",
data: {
id: me.catId,
x: me.x,
y: me.y,
looking: me.looking
}
}));
};
// update the cats' positions
var paint = function() {
//handle user input
var x = 0, y = 0, step = 5;
var left = buttons[LEFT];
var right = buttons[RIGHT];
var jump = buttons[JUMP];
if (left) {
me.walk();
me.lookLeft();
x -= step;
}
if (right) {
me.walk();
me.lookRight();
x += step;
}
if (jump){
me.jump();
}
if (buttons[MEOW]) {
meow(true);
buttons[MEOW] = false;
}
// update cats' positions
for (var id in cats) {
var cat = cats[id];
// if the cat has left the game, skip it
if (!cat){
continue;
}
// update this cat
if(cat == me && (x || y || cat.isJumping)) {
me.move(x, y, SCENE_HEIGHT).update();
sendMove();
} else {
//update the other cats
cat.update();
}
}
if (ticker.currentTick % 20 === 0)
document.getElementById("fps").innerHTML = ticker.fps + "fps";
};
// start game
var start = function() {
var defaultName = ["Agatha", "Cyrus", "Oswald", "Roscoe",
"Holden", "Jasper", "Wren", "Clementine", "Florence", "Reginald"]
[Math.floor(Math.random() * 10)];
var name = prompt("Please name your kitten", defaultName) || defaultName;
//connect to server
socket = new WebSocket('ws://' + location.host + '/?name=' + name.slice(0, 10));
var handlers = {
// server sends data about peers in the room when connection is established
"connected": function (data) {
//add a kitten to the scene for each peer
for (var id in data.cats) {
cats[data.cats[id].id] = new Cat(scene, data.cats[id]);
}
// new peer gets id assigned by the server, and appears in random position in the scene
me = cats[data.id];
me.position(Math.round(Math.random()*SCENE_WIDTH), SCENE_HEIGHT - me.h);
sendMove();
//adding event listeners to buttons (for touch version)
var dirs = {left:LEFT, up:JUMP, right:RIGHT};
for (var dir in dirs) {
var button = document.querySelector("#button-" + dir);
(function (dir) {
button.addEventListener('touchstart', function (e) {
e.preventDefault();
e.stopPropagation();
buttons[dir] = true;
}, false);
button.addEventListener('touchend', function (e) {
e.preventDefault();
e.stopPropagation();
buttons[dir] = false;
}, false);
button.addEventListener('mousedown', function (e) {
e.preventDefault();
e.stopPropagation();
buttons[dir] = true;
}, false);
button.addEventListener('mouseup', function (e) {
e.preventDefault();
e.stopPropagation();
buttons[dir] = false;
}, false);
})(dirs[dir]);
}
var buttonMeow = document.querySelector("#button-meow");
buttonMeow.addEventListener('touchstart', function (e) {
e.preventDefault();
e.stopPropagation();
buttons[MEOW] = true;
}, false);
buttonMeow.addEventListener('mouseup', function (e) {
e.preventDefault();
e.stopPropagation();
buttons[MEOW] = true;
}, false);
var processKeyDown = function (e) {
if (e.keyCode >= 37 && e.keyCode <= 40){
buttons[e.keyCode - 37] = true;
} else if (e.keyCode == 32){
meow(true);
}
};
var processKeyUp = function (e) {
if (e.keyCode >= 37 && e.keyCode <= 40){
buttons[e.keyCode - 37] = false;
}
};
// listen to keydown/keyup events: arrows = [left, up, right, down] keyCodes 37 to 40, and space = 32
window.addEventListener('keydown', processKeyDown, false);
window.addEventListener('keyup', processKeyUp, false);
document.getElementById("room").innerHTML = data.roomId + 1;
},
// if a new peer connects, the server broadcasts the new cat
"new-cat": function(cat) {
if (cat.id != me.catId){
cats[cat.id] = new Cat(scene, cat);
}
},
// move all the cats! \o/
"moved": function(data) {
for (var id in cats) {
var cat = cats[id];
if (cat.catId != me.catId) {
cat.setX(+data[cat.catId].x);
cat.setY(+data[cat.catId].y);
cat.turnHead(data[cat.catId].looking);
}
}
},
// when a peer closes the window, remove its cat
"unload": function (id) {
cats[id].remove();
delete cats[id];
},
// echo peers' meows
"meow": function (id) {
if (id != me.catId){
meow();
}
}
};
/* handle messages:
{ type: "unload", data: cat.id } -> remove a cat, it provides the cat's id
{ type: "new-cat", data: cat } -> add a new cat, it gives you the cat
{ type: "moved", data: rooms[cat.roomId] } -> move the cats, gives you the whole room where the cats are
{ type: "meow", data: id} -> echo meow, provides id of the meowing cat
*/
socket.onmessage = function (e) {
var o;
try { o = JSON.parse(e.data); } catch (ex) { return; }
if (!(o.type in handlers)){
return;
} else {
handlers[o.type](o.data);
}
};
socket.onclose = function () {};
// when the page closes, send socket.close to server to remove cat tied to this page
window.addEventListener("unload", function () {
socket.close();
}, false);
// use sprite.js ticker to animate the scene: call paint in each tick
ticker = scene.Ticker(paint, { useAnimationFrame: true });
// start the ticker
ticker.run();
};
// Public API
return {
start: start
};
};
function escapeString(str) {
return String(str)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'")
.replace(/\//g, "/");
}
window.onload = function () {
new Game().start();
};
</script>
html, body {
margin: 0;
padding: 0;
color: #eee;
overflow: hidden;
background: black;
}
#demo {
margin:none;
padding:none;
}
#fps {
color: #F00;
display: none;
}
.sjs {
margin: auto;
border: 0px solid #000;
}
h1 {
margin: auto;
padding: 10px;
text-align: center;
font-family: 'Special Elite', Courier;
}
#container{
clear: both;
width: 500px;
height:500px;
margin: auto;
background-image: url(../images/backgnd.jpg);
/* Background image Ⓒ A is for Angie http://www.flickr.com/photos/aisforangie/9526485/in/photostream */
background-repeat: no-repeat;
background-position: 0 0;
}
span.nametag {
color: #00F;
font-family: sans-serif;
font-size: 10px;
font-weight: bold;
text-shadow: 0 0 1px #FFF;
display: block;
user-select: none;
-o-user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
}
/*BUTTONS START*/
#toolbar {
font-size: 0; /* AVOIDING SPACING CONFLICTS */
width: 500px;
margin: auto;
text-align: center;
margin-top: 10px;
}
/* NEED CLEAR IN ORDER TO FLOAT THE CONTROLS */
#toolbar:after {
content: "";
display: block;
clear: both;
}
.button {
padding: 50px;
background-size: 60% 60%;
border-radius: 100px;
background-position: 50% 50%;
background-repeat: no-repeat;
display: inline-block;
}
.button:active {
-webkit-transform: translateY(2px);
-o-transform: translateY(2px);
transform: translateY(2px);
}
.button.left {background-image: url('../images/left.png')}
.button.right {background-image: url('../images/right.png')}
.button.jump {background-image: url('../images/jump.png')}
.button.meow {background-image: url('../images/meow.png')}
.leftside, .rightside {
background-color: rgba(128,128,128,0.4);
box-shadow: inset 0 0 0 1px rgba(128,128,128,0.1);
border-radius: 100px;
padding: 8px 12px 12px 12px;
}
.leftside {
float: left;
padding-left: 0px;
margin-left: 5px;
}
.rightside {
float: right;
padding-right: 0px;
margin-right: 5px;
}
.leftside .button {
margin-left: 10px;
background-color: #E1C131;
box-shadow: 0 3px #666,
inset 0 0 0 1px rgba(0,0,0,0.2),
inset 0 -2px 10px 1px rgba(0,0,0,0.2),
inset 0 0 0 2px rgba(255,255,255,0.2);
}
.leftside .button:active, .leftside .button.active {
box-shadow: 0 1px #666,
inset 0 0 0 1px rgba(0,0,0,0.2),
inset 0 -2px 10px 1px rgba(0,0,0,0.2),
inset 0 0 0 2px rgba(255,255,255,0.2);
}
.rightside .button {
margin-right: 10px;
background-color: #fff;
box-shadow: 0 3px #999,
inset 0 0 0 1px rgba(0,0,0,0.2),
inset 0 -2px 10px 1px rgba(0,0,0,0.2),
inset 0 0 0 2px rgba(255,255,255,0.2);
}
.rightside .button:active, .rightside .button.active {
box-shadow: 0 1px #999,
inset 0 0 0 1px rgba(0,0,0,0.2),
inset 0 -2px 10px 1px rgba(0,0,0,0.2),
inset 0 0 0 2px rgba(255,255,255,0.2);
}
/*BUTTONS END*/
@media (orientation: landscape) and (max-height: 600px) {
#demo {
-o-transform: scale(.45);
-o-transform-origin: 50% 0;
-moz-transform: scale(.45);
-moz-transform-origin: 50% 0;
-webkit-transform: scale(.45);
-webkit-transform-origin: 50% 0;
transform: scale(.45);
transform-origin: 50% 0;
}
}
@media (orientation: portrait), (orientation: landscape) and (min-height: 600px) {
#demo {
-o-transform: scale(1);
-o-transform-origin: 50% 0;
-moz-transform: scale(1);
-moz-transform-origin: 50% 0;
-webkit-transform: scale(1);
-webkit-transform-origin: 50% 0;
transform: scale(1);
transform-origin: 50% 0;
}
}