Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false
trim_trailing_whitespace = false

[*.yaml]
indent_style = spaces
indent_size = 2
163 changes: 163 additions & 0 deletions app/api/game.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
swagger: '2.0'
info:
version: 0.1.0
title: 'Torwolf Games API'
paths:
/games:
get:
description: Finds games
parameters:
- name: offset
in: query
description: The offset at which the result set should begin.
required: false
type: integer
default: 0
- name: limit
in: query
description: Result will contain at most limit items
required: false
type: integer
default: 20
- name: phase
in: query
description: Games in the result set should be in the provided list of comma separated phases.
required: false
type: string
enum: ["FORMING", "STARTED", "COMPLETED"]
- name: name
in: query
description: Games should match the provided text search string
required: false
type: string
responses:
200:
description: Successful response
schema:
$ref: '#/definitions/ResultSet'
# TODO: error middleware
default:
description: Unexpected error
schema:
$ref: '#/definitions/Error'
post:
description: Creates a game
parameters:
- name: game
in: body
description: Game to create
required: true
schema:
$ref: '#/definitions/Game'
responses:
200:
description: Successful response
schema:
$ref: '#/definitions/Game'
default:
description: Unexpected error
schema:
$ref: '#/definitions/Error'
/games/{id}:
get:
description: Gets a game
parameters:
- name: id
in: path
description: Id of the game to get
required: true
type: number
format: i32
responses:
200:
description: Successful response
schema:
$ref: '#/definitions/Game'
# TODO: error middleware
404:
description: Game was not found
schema:
$ref: '#/definitions/Error'
default:
description: Unexpected error
schema:
$ref: '#/definitions/Error'

definitions:
Game:
required:
- name
- description
properties:
name:
type: string
description: The name of this game.
description:
type: string
description: A description of what to expect in this game.
victor:
type: string
description: Which side won the game.
enum: ['GOVERNMENT', 'REBELLION']
phase:
type: string
description: Which phase the game is currently in.
enum: ['FORMING', 'STARTED', 'COMPLETED']
startedAt:
type: string
format: date-time
description: When this game entered the 'STARTED' phase.
completedAt:
type: string
format: date-time
description: When this game entered the 'COMPLETED' phase.
createdAt:
type: string
format: date-time
description: When this game was created.
updatedAt:
type: string
format: date-time
description: When this game was last updated.
id:
type: integer
description: A unique identifier for this game.
example:
name: Example Game
description: A really good example
victor: REBELLION
phase: COMPLETED
startedAt: 2015-10-04 05:05:09.877 +00:00
completedAt: 2015-10-04 05:05:09.877 +00:00
createdAt: 2015-10-04 05:05:09.877 +00:00
updatedAt: 2015-10-04 05:05:09.877 +00:00
id: 39

ResultSet:
type: object
required:
- start
- totalCount
- results
properties:
start:
type: integer
format: int32
totalCount:
type: integer
format: i32
results:
type: array
items:
$ref: '#/definitions/Game'

Error:
required:
- code
- message
properties:
code:
type: integer
format: int32
message:
type: string
10 changes: 10 additions & 0 deletions app/classes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
var uuid = require('uuid');

exports.Interaction = function() {
this.id = uuid.v4();
this.isSsl = false;
this.isTor = false;
this.message = null; // The message that initiated this interaction
this.responses = []; // The messages that have been sent as a response to this interaction
this.socket = null; // The socket that initiated this interaction
};
65 changes: 65 additions & 0 deletions app/controllers/game.server.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
var gameRepository = require('../repositories/game');
var async = require('async');

/**
* Module dependencies.
*/

exports.create = function(req, res, next) {
'use strict';
gameRepository.create(req.body, function(err, game) {
if (err) {
return next(err);
}
return res.json(game);
});
};

exports.find = function(req, res, next) {
'use strict';
var options = {
offset: req.query.offset || 0,
limit: req.query.limit || 20,
where: { }
};
if (req.query.name) {
var queryString = '%' + req.query.name + '%';
options.where.name = {
$like: queryString
};
}
if (req.query.phase) {
var phases = req.query.phase.split(',');
options.where.phase = {
$in: phases
};
}

async.parallel([
function(cb) {
gameRepository.find(options, cb);
},
function(cb) {
gameRepository.count(options, cb);
}
], function(err, results) {
if (err) {
return next(err);
}
return res.json({
start: parseInt(options.offset),
totalCount: results[1],
results: results[0]
});
});
};

exports.get = function(req, res, next) {
'use strict';
gameRepository.get(req.params.id, function(err, game) {
if (err) {
return next(err);
}
return res.json(game);
});
};
11 changes: 11 additions & 0 deletions app/handlers/error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
var payloads = require('../../payloads');
var messageTypes = require('../../message-types');

// Functions
exports.error = function (message, socket) {
var error = new payloads.ErrorPayload(message);
socket.emit('error', {
payload: error,
type: messageTypes.GENERAL_ERROR
});
};
34 changes: 34 additions & 0 deletions app/handlers/heartbeat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
var constants = require('../../constants'),
payloads = require('../../payloads'),
locales = require('../../locales'),
error = require('./error'),
messageSender = require('./messageSender'),
gameRepository = require('../repositories/game'),
messageTypes = require('../../message-types');

exports.handle = function(data, interaction) {
var socket = interaction.socket;
var count = data.count;
var game = gameRepository.get(data.game.id, function (err, game) {
if (err) {
throw err;
}

// Did the game end?
if (game.phase === 'COMPLETED') {
return;
}

// Start the next heartbeat
return setTimeout(function() {
var payload = new payloads.StorytellerHeartbeatOutPayload(++count);
messageSender.send(
payload,
messageTypes.STORYTELLER_HEARTBEATPING,
socket,
interaction);
}, constants.TICK_HEARTBEAT);
});

};

23 changes: 23 additions & 0 deletions app/handlers/messageSender.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
exports.send = function(payload, type, socket, interaction) {
var message = {
payload: payload,
type: type
};

// Add to the interaction
if(interaction) {
interaction.responses.push(message);
message.interactionId = interaction.id;
}

// TODO: Snoop the interaction
if(interaction) {
// var interceptIn = new payloads.SnooperInterceptInPayload(interaction);
// exports.routeMessage(
// constants.COMMUNICATION_TARGET_SNOOPER,
// interceptIn.getPayload(),
// constants.COMMUNICATION_SOCKET_SERVER);
}

socket.emit('message', message);
};
30 changes: 30 additions & 0 deletions app/handlers/router.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
var classes = require('../classes'),
constants = require('../../constants'),
payloads = require('../../payloads'),
gameState = require('../lib/gameState'),
routingTable = require('./routingTable'),
logger = require('../lib/logger').logger;

exports.receiveMessage = function(message, socket) {
if(!message.payload || !message.type) {
logger.info("Invalid payload received " + JSON.stringify(message));
return; // Invalid payload
}

// Set up metadata about the message
if(gameState.getInteractionById(message.interactionId)) {
logger.info("Duplicate interaction received " + message.interactionId);
return; // Duplicate interaction
}
// Build the interaction
var interaction = new classes.Interaction();
interaction.id = message.interactionId?message.interactionId:interaction.id;
interaction.message = message;
interaction.isTor = message.isTor?true:false;
interaction.isSsl = message.isSsl?true:false;
interaction.socket = socket;
gameState.storeInteraction(interaction);
message.interactionId = interaction.id;

routingTable[message.type].handle(message.payload, interaction);
};
8 changes: 8 additions & 0 deletions app/handlers/routingTable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
var messageTypes = require('../../message-types');

var heartbeat = require('./heartbeat');

var table = {};
table[messageTypes.STORYTELLER_HEARTBEATPONG] = heartbeat;

module.exports = table;
9 changes: 9 additions & 0 deletions app/lib/gameState.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
var interactions = {};

exports.getInteractionById = function(interactionId) {
return interactions[interactionId];
};

exports.storeInteraction = function(interaction) {
interactions[interaction.id] = interaction;
};
Loading