<strong>Move around with the arrow keys, and click replay after you're done.</strong> <div class="rpg"></div> <script type="text/mustache" id="avatar"> <div class="map"> <div class="character {{#isMoving}}moving{{/isMoving}} {{ player.position.direction }}" style="left: {{ player.position.left }}px; top: {{ player.position.top}}px"></div> <div class="fire-pit"></div> </div> <button class="replay" {{#player.hasQueuedRequests}}disabled="disabled"{{/player.hasQueuedRequests}}>Replay!</button> <span>{{#player.hasQueuedRequests}}Saving!{{/player.hasQueuedRequests}}</span> </script>
$(document).ready(function(){ /* SERVER EMULATION */ var getRandomInt = function(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } var positions = []; can.fixture('POST /players', function(req){ var attrs = req.data || {}; positions.push(attrs.position); // Save current position attrs.id = 1; return attrs; }) can.fixture('PUT /players/{id}', function(req, respondWith){ var attrs = req.data || {}, position = attrs.position; if(position.top > 80 && position.top < 100 && position.left > 60 && position.left < 100){ // you stepped on the fire, you die respondWith(406); } else { positions.push(attrs.position); // save current position setTimeout(function(){ respondWith(attrs); }, getRandomInt(10, 50)) // change delay to simulate slow and spotty connection } }) can.fixture('GET /game', function(req){ return positions; }) var directions = { 37 : 'LEFT', 38 : 'UP', 39 : 'RIGHT', 40 : 'DOWN' }, max = function(val, max){ return val > max ? max : val; }, min = function(val, min){ return val < min ? min : val; }; var Player = can.Model({ create : "POST /players", update : "PUT /players/{id}" }, {}); var Replay = can.Model({ findAll : "GET /game" }, {}) var RPG = can.Control({ init : function(){ this.player = new Player({ position: { top : 0, left : 0, direction: "down" } }); this.isMoving = can.compute(false); this.element.html(can.view('avatar', { player : this.player, isMoving : this.isMoving })); // save starting position this.savePosition(); }, move : function(direction){ var position, direction; if(direction){ this.isMoving(true); this.player.attr('position.direction', direction.toLowerCase()); // move the avatar in the right direction if(direction === 'UP'){ this.player.attr('position.top', min(this.player.attr('position.top') - 5, 0)); } else if(direction === 'DOWN'){ this.player.attr('position.top', max(this.player.attr('position.top') + 5, 300 - 32)); } else if(direction === 'RIGHT'){ this.player.attr('position.left', max(this.player.attr('position.left') + 5, 300 - 24)); } else if(direction === 'LEFT'){ this.player.attr('position.left', min(this.player.attr('position.left') - 5, 0)); } // after we move the avatar save the position again this.savePosition(); } }, ".replay click" : function(){ Replay.findAll({}, $.proxy(this.replay, this)); }, replay : function(positions){ var currentPosition = positions.shift(), self = this; if(currentPosition){ this.isMoving(true); this.player.attr('position').attr(currentPosition.attr()); setTimeout(function(){ self.replay(positions); }, 50) } else { this.isMoving(false); } }, // On each keydown event move the avatar "{document} keydown" : function(el, ev){ this.move(directions[ev.which]); ev.preventDefault(); }, // When user stops pressing arrow keys remove moving class "{document} keyup" : function(el, ev){ this.isMoving(false); }, savePosition : function(){ // save position to the server // we don't need a success callback since live binding takes care of it this.player.save(undefined, $.proxy(this.loadBackup, this)); }, loadBackup : function(){ alert('You walked in to the fire. Resurrecting you to the last good position') this.player.restore(true); this.isMoving(false); } }) new RPG('.rpg'); })
body { font-family: Arial, sans-serif; } .character { width: 24px; height: 32px; position: absolute; z-index: 1000; /* created with http://charas-project.net/charas2/index.php */ background-image: url('http://s21.postimage.org/byyd7owj7/static.gif'); } .fire-pit { width: 24px; height: 24px; background: url('http://s21.postimage.org/krfbvdho3/fire.gif'); position: absolute; top: 100px; left: 80px; } .up { background-position: 0 0; } .down { background-position: 0 -64px; } .right { background-position: 0 -32px; } .left { background-position: 0 -96px; } .moving { background-image: url('http://s21.postimage.org/b85n1wu5v/animated.gif'); } .map { width: 300px; height: 300px; position: relative; margin: 10px 0; /* Texture from http://zooboingreview.blogspot.com/2009/11/seamless-texture-more-nice-grass.html */ background-image: url('http://s21.postimage.org/fxvkqil6f/363_tile_Grass.jpg'); }