Edit in JSFiddle

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