Permalink
Browse files

simplified state transmission and authoritative server

  • Loading branch information...
1 parent 6baf536 commit f9eeb93ecf779c9e58af30f206642270f899993f @masylum committed Dec 20, 2011
Showing with 415 additions and 94 deletions.
  1. +19 −19 app.js
  2. +29 −14 game.js
  3. +2 −1 package.json
  4. +67 −31 public/js/client.js
  5. +37 −24 public/js/tile.js
  6. +255 −0 public/js/vendor/uuid.js
  7. +6 −5 views/welcome.jade
View
38 app.js
@@ -194,24 +194,6 @@ http.post('/room', authorize, function (req, res, next) {
}
});
-http.get('/game/single', authorize, function (req, res, next) {
- var js = ['tile', 'client', 'mouse', 'confirm']
- , options = { user: req.user
- , room: null
- , css: asereje.css()
- , js: asereje.js(js)
- };
-
- require('./game').spawn({
- io: io
- , single: true
- , user: req.user
- , db: db
- });
-
- res.render('room', options);
-});
-
http.get('/scores', authorize, function (req, res, next) {
var funk = require('funk')('parallel');
@@ -292,9 +274,27 @@ http.get('/scores', authorize, function (req, res, next) {
}, next);
});
+http.get('/game/single', authorize, function (req, res, next) {
+ var js = ['vendor/uuid', 'tile', 'client', 'mouse', 'confirm']
+ , options = { user: req.user
+ , room: null
+ , css: asereje.css()
+ , js: asereje.js(js)
+ };
+
+ require('./game').spawn({
+ io: io
+ , single: true
+ , user: req.user
+ , db: db
+ });
+
+ res.render('room', options);
+});
+
http.get('/game/:room_id', authorize, function (req, res, next) {
var query = {_id: require('mongojs').ObjectId(req.param('room_id'))}
- , js = ['tile', 'client', 'mouse', 'confirm'];
+ , js = ['vendor/uuid', 'tile', 'client', 'mouse', 'confirm'];
db.rooms.findOne(query, function (error, room) {
if (error) return next(error);
View
43 game.js
@@ -8,6 +8,7 @@ module.exports.spawn = function (options) {
function _defaultState() {
return { tiles: []
+ , events: []
, time: 0
, points: 250
, players: {}
@@ -53,6 +54,7 @@ module.exports.spawn = function (options) {
function _cleanState() {
clearInterval(STATE.interval);
+ clearInterval(STATE.sync_interval);
STATE = _defaultState();
room.removeAllListeners();
}
@@ -70,6 +72,14 @@ module.exports.spawn = function (options) {
room_socket.emit('tick', {time: STATE.time, points: STATE.points});
}
+ function _sync() {
+ console.log('sync', STATE.events);
+ room_socket.emit('sync', STATE.events);
+
+ // reset the event stack
+ STATE.events = [];
+ }
+
// for each game
function _initState() {
STATE.tiles = _shuffle(_getDeck());
@@ -83,23 +93,29 @@ module.exports.spawn = function (options) {
STATE.num_pairs = Tile.getNumPairs(STATE.tiles);
STATE.remaining_tiles = Tile.TOTAL_TILES;
STATE.interval = setInterval(_eachSecond, 1000);
+ STATE.sync_interval = setInterval(_sync, 1000);
STATE.started = true;
_.each(STATE.players, function (player) {
player.selected_tile = null;
player.num_pairs = 0;
});
}
- // when a tile is being clicked
- socket.on('tile.clicked', function (data) {
- var tile = data.tile
- , player_id = data.player_id;
+ // when two tiles are matched
+ socket.on('tile.matched', function (data) {
+ var tiles = data.tiles
+ , uuid = data.uuid
+ , player_id = data.player_id
+ , points;
+
+ if (STATE.tiles[tiles[0].i].is_deleted || STATE.tiles[tiles[1].i].is_deleted) {
+ STATE.events.push({type: 'error', uuid: uuid});
+
+ // are matching
+ } else if (Tile.areMatching(tiles[0], tiles[1])) {
+
+ _.each(tiles, Tile['delete']);
- Tile.onClicked(
- function (tile) {
- socket.broadcast.emit('tiles.updated', STATE.tiles);
- }
- , function secondSelection(tile, selected_tile, points) {
STATE.remaining_tiles -= 2;
STATE.num_pairs = Tile.getNumPairs(STATE.tiles);
points = (Tile.POINTS_PER_SECOND * 3) + Math.ceil(
@@ -113,11 +129,14 @@ module.exports.spawn = function (options) {
, num_pairs: STATE.num_pairs
, player_num_pairs: STATE.players[player_id].num_pairs
});
- socket.broadcast.emit('tiles.updated', STATE.tiles);
+
+ STATE.events.push(data);
if (!STATE.num_pairs || !STATE.remaining_tiles) {
STATE.finished = true;
clearInterval(STATE.interval);
+ clearInterval(STATE.sync_interval);
+ _sync();
// loose
if (!STATE.remaining_tiles) {
@@ -138,10 +157,6 @@ module.exports.spawn = function (options) {
}
}
}
- , function noMatching(tile, selected_tile) {
- socket.broadcast.emit('tiles.updated', STATE.tiles);
- }
- )(tile, player_id);
});
// mouse.js
View
@@ -12,6 +12,7 @@
"jade": "0.19.0",
"mongojs": "0.2.6",
"asereje": "0.1.0",
- "moment": "1.2.0"
+ "moment": "1.2.0",
+ "node-uuid": "1.3.1"
}
}
View
@@ -1,4 +1,4 @@
-/*global Raphael, io, Tile*/
+/*global Raphael, io, Tile, uuid*/
var socket;
@@ -13,6 +13,7 @@ $(function () {
, ALPHA_HIDE = 0.25
, CANVAS_WIDTH = (ROWS * TILE_WIDTH) + (2 * SIDE_SIZE)
, CANVAS_HEIGHT = (COLUMNS * TILE_HEIGHT) + (2 * SIDE_SIZE)
+ , UUID = uuid.noConflict()
, paper = Raphael($('#canvas').get(0), CANVAS_WIDTH, CANVAS_HEIGHT)
, room_host_id = $('#room_host_id').val()
, namespace_id = $('#namespace_id').val()
@@ -25,14 +26,14 @@ $(function () {
socket = io.connect('/' + namespace_id);
emit = _.bind(socket.emit, socket);
- //if (document.location.hostname === 'localhost') {
- // emit = function (ev) {
- // var args = arguments;
- // setTimeout(function () {
- // socket.emit.apply(socket, args);
- // }, 5000);
- // };
- //}
+ if (document.location.hostname === 'localhost') {
+ emit = function (ev) {
+ var args = arguments;
+ setTimeout(function () {
+ socket.emit.apply(socket, args);
+ }, 5000);
+ };
+ }
/**
* Adds the new player to the room view
@@ -174,7 +175,7 @@ $(function () {
*/
function initGame(STATE) {
var TILE = Tile(STATE)
- , svgs = [], images = [], shapes = [], shadows = [];
+ , events = [], svgs = [], images = [], shapes = [], shadows = [];
function setShadows(tile) {
var shadow = shadows[tile.i]
@@ -280,7 +281,10 @@ $(function () {
function paintAsSelected(tile, color) {
if (!shapes[tile.i].removed) {
- shapes[tile.i].attr({fill: color || STATE.players[tile.player_ids[0]].color, 'fill-opacity': 0.5});
+ shapes[tile.i].attr({
+ fill: color || STATE.players[tile.player_ids[tile.player_ids.length - 1]].color
+ , 'fill-opacity': 0.5
+ });
}
}
@@ -332,7 +336,9 @@ $(function () {
}
shadowing(tile);
- svgs[tile.i].remove();
+ svgs[tile.i].attr({opacity: tile.is_deleted ? 0 : 1});
+ // svgs[tile.i].remove();
+ // delete svgs[tile.i];
makeBetterVisibility(tile, false);
}
@@ -410,16 +416,22 @@ $(function () {
function firstSelection(tile) {
document.getElementById('s_click').play();
paintAsSelected(tile, STATE.players[user_data._id].color);
- emit('tile.clicked', {tile: tile, player_id: user_data._id});
}
, function onMatching(tile, selected_tile, points) {
+ var event = { uuid: UUID.v4()
+ , tiles: [_.clone(tile), _.clone(selected_tile)]
+ , player_id: user_data._id
+ };
+
document.getElementById('s_gling').play();
_.each([tile, selected_tile], function (tile) {
svgs[tile.i].animate({opacity: 0}, 100, '>', function () {
onDelete(tile);
});
});
- emit('tile.clicked', {tile: tile, player_id: user_data._id});
+
+ events.push(event);
+ emit('tile.matched', event);
}
, function notMatching(tile, selected_tile) {
document.getElementById('s_grunt').play();
@@ -428,18 +440,20 @@ $(function () {
onUnselected(selected_tile);
}
onUnselected(tile);
- emit('tile.clicked', {tile: tile, player_id: user_data._id});
}
+ , function onError() {}
)(tile, user_data._id);
});
shape.hover(function () {
- if (!STATE.tiles[tile.i].selected && TILE.isFree(STATE.tiles[tile.i]) && !STATE.tiles[tile.i].is_deleted) {
+ if (!TILE.isSelected(STATE.tiles[tile.i])
+ && TILE.isFree(STATE.tiles[tile.i])
+ && !STATE.tiles[tile.i].is_deleted) {
this.attr({'fill-opacity': 0.3});
}
makeBetterVisibility(STATE.tiles[tile.i], true);
}, function () {
- if (!STATE.tiles[tile.i].selected && !STATE.tiles[tile.i].is_deleted) {
+ if (!TILE.isSelected(STATE.tiles[tile.i]) && !STATE.tiles[tile.i].is_deleted) {
this.attr({'fill-opacity': 0});
}
makeBetterVisibility(STATE.tiles[tile.i], false);
@@ -453,16 +467,12 @@ $(function () {
*/
function drawTile(tiles, i) {
if (tiles[i].is_deleted) {
- if (svgs[i]) {
- onDelete(tiles[i]);
- }
+ onDelete(tiles[i]);
} else {
- if (!svgs[i]) {
- renderTile(tiles[i]);
- setShadows(tiles[i]);
- }
+ renderTile(tiles[i]);
+ setShadows(tiles[i]);
- if (tiles[i].selected) {
+ if (TILE.isSelected(tiles[i])) {
paintAsSelected(tiles[i]);
} else {
paintAsUnselected(tiles[i]);
@@ -505,12 +515,38 @@ $(function () {
$('.sidebar .player_' + data.player_id + ' .points').html(data.player_num_pairs);
});
- socket.removeAllListeners('tiles.updated');
- socket.on('tiles.updated', function (tiles) {
- _.each(tiles, function (tile) {
- if (tile && !_.isEqual(tile, STATE.tiles[tile.i])) {
- STATE.tiles[tile.i] = tile;
- drawTile(STATE.tiles, tile.i);
+ socket.removeAllListeners('sync');
+ socket.on('sync', function (server_events) {
+ console.log('sync', Object.keys(server_events).length, _.clone(server_events), _.clone(events));
+
+ _.each(server_events, function (server_event) {
+ var event = _.detect(events, function (e) {
+ return e.uuid === server_event.uuid;
+ });
+
+ if (event) {
+ // rollback
+ if (server_event.type === 'error') {
+ _.each(server_event.tiles, function (tile) {
+ if (!tile.is_deleted) {
+ STATE.tiles[tile.i].is_deleted = false;
+ onDelete(tile);
+ }
+ });
+ }
+ events = _.without(events, event);
+ } else {
+ if (server_event.type !== 'error') {
+ _.each(server_event.tiles, function (tile) {
+ if (STATE.players[user_data._id].selected_tile.i === tile.i) {
+ STATE.players[user_data._id].selected_tile = null;
+ }
+ TILE['delete'](STATE.tiles[tile.i]);
+ svgs[tile.i].animate({opacity: 0}, 100, '>', function () {
+ onDelete(tile);
+ });
+ });
+ }
}
});
});
Oops, something went wrong.

0 comments on commit f9eeb93

Please sign in to comment.