Edit in JSFiddle


              
<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, "&amp;")
		.replace(/</g, "&lt;")
		.replace(/>/g, "&gt;")
		.replace(/"/g, "&quot;")
		.replace(/'/g, "&#039;")
		.replace(/\//g, "&#x2F;");
}

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;
   }
}