Skip to content

Instantly share code, notes, and snippets.

@linzhp
Created September 17, 2013 17:43
Show Gist options
  • Save linzhp/6597849 to your computer and use it in GitHub Desktop.
Save linzhp/6597849 to your computer and use it in GitHub Desktop.
Code for communicating with RA
var exec = require('child_process').exec;
var mongoose = require('mongoose');
desc('Runs the mocha unit tests');
task('test', function(params) {
var proc = exec('mocha --recursive --colors');
proc.stdout.pipe(process.stdout, { end: false });
proc.stderr.pipe(process.stderr, { end: false });
proc.on('exit', process.exit);
});
function modelClassForName(name) {
name = name.toLowerCase();
return require('./app/models/' + name);
}
function connect(env, done) {
var database = require('./config/database');
mongoose.connect(database[env].uri);
var db = mongoose.connection;
db.on('error', done);
db.on('open', done);
}
function deleteAllLevels(env, done) {
var levelDeleted = false, idNullified = false;
function next() {
if (levelDeleted && idNullified) {
console.log('Done!');
done();
}
}
// Delete all levels from RA
var config = require('./config/resource_allocator')[env];
var http = require('http');
var options = {
host: config.host,
port: config.port,
path: '/ra/games/1/report'
};
var getReq = http.get(options, function(res1) {
var body = '';
res1.on('data', function(data) {
body += data.toString();
});
res1.on('end', function() {
var report = JSON.parse(body);
var numLevels = report.activeLevelAgents.length;
console.log('Found ' + numLevels + ' active levels on RA.');
if (numLevels === 0) {
levelDeleted = true;
next();
} else {
console.log('Deleting...');
}
var success = 0, failure = 0;
report.activeLevelAgents.forEach(function(level) {
options.path = '/ra/games/1/levels/' + level;
options.method = 'DELETE';
var deleteReq = http.get(options, function(res2) {
res2.on('data', function(data) {
if (JSON.parse(data).success) {
success++;
} else {
failure++;
}
if (success + failure == numLevels) {
console.log('Success :' + success + '\nFailure: ' + failure);
levelDeleted = true;
next();
}
});
});
deleteReq.end();
});
});
});
getReq.end();
// Nullify all levelIds
var Problem = require('./app/models/problem');
Problem.find({}, function(err, problems) {
if (err) {
console.log('Error in finding problems: ' + err);
next();
} else {
var problemSaved = 0;
problems.forEach(function(problem) {
problem.instances.forEach(function(instance) {
delete instance.levelId;
});
problem.update({instances: problem.instances}, function(err) {
if (err) {
console.error('Error in saving problem ' + problem.id);
}
problemSaved++;
if (problemSaved >= problems.length) {
idNullified = true;
next();
}
});
});
}
});
}
desc('Delete all data of a model. The model name needs to be singular')
task('delete', {async:true}, function(env, modelName) {
env = env || process.env.NODE_ENV || 'development';
connect(env, function(error) {
if (error) {
console.error('Failed to connect to MongoDB: ' + error);
complete();
return;
}
if (modelName.toLowerCase() == 'level') {
// delete all levels from RA, and nullify all level ids
deleteAllLevels(env, complete);
} else {
// delete the database table underlying the model
var modelsPath = require('path').resolve(__dirname, 'app', 'models');
var Model = modelClassForName(modelName);
Model.remove({}, function(err) {
if (err) {
console.error(err);
} else {
console.log('Done!');
}
complete();
});
}
});
});
desc('Load data of a model into the database')
task('load', {async:true}, function(env, modelName) {
env = env || process.env.NODE_ENV || 'development';
var Model = modelClassForName(modelName);
var modelData = require('./db/' + Model.collection.name);
if(modelData) {
connect(env, function(error) {
if (error) {
console.error('Failed to connect to MongoDB: ' + error);
complete();
return;
}
Model.create(modelData, function(err) {
if (err) {
console.error('Error in saving ' + Model.collection.name + ': ' + err);
} else {
console.log('Done!');
complete();
}
});
});
} else {
console.error('No data file found');
complete();
}
});
/*
This is NOT a mongoose model. It is an abstraction of a level in the resource
allocator
*/
var http = require('http');
function Level(instance) {
var self = this;
self.id = instance.levelId;
self.arrayCount = 0; // number of array variables
self.varCount = 0; // number of all variables
self.maxValue = -Infinity; // max value in this level
var minValue = Infinity;
var lengthSum = 0;
instance.variables.forEach(function(seq, index) {
self.varCount++;
var isArray = false;
seq.forEach(function(slide) {
if (slide instanceof Array) {
// array variable
isArray = true;
slide.forEach(function(value) {
if (typeof value == 'string') {
value = parseFloat(value);
}
if (value > self.maxValue) {
self.maxValue = value;
}
if (value < minValue) {
minValue = value;
}
});
} else {
if (typeof slide === 'string') {
slide = parseFloat(slide);
}
if (slide > self.maxValue) {
self.maxValue = slide;
}
if (slide < minValue) {
minValue = slide;
}
}
});
if (isArray) {
self.arrayCount++;
lengthSum += seq[0].length;
}
});
self.valueRange = self.maxValue - minValue; // value range in this level
if (self.arrayCount > 0) {
self.avgArrayLength = lengthSum / self.arrayCount; // average array length
}
}
Level.getHTTPOptions = function() {
var config = require('../../config/resource_allocator');
return {
host: config[this.env].host,
port: config[this.env].port,
headers: {}
};
};
Level.prototype.createInRA = function(done) {
var self = this;
var options = Level.getHTTPOptions();
if (self.id) {
options.path = '/ra/games/1/levels/' + self.id + '/new';
} else {
options.path = '/ra/games/1/levels/new';
}
options.method = 'POST';
var req = http.request(options, function(res) {
res.on('data', function(data) {
if (res.statusCode == 200) {
var obj = JSON.parse(data);
if (obj.hasOwnProperty('id')) {
self.id = obj.id;
done();
} else {
done(data);
}
} else {
console.error('Creating empty level returns a status code: ' + res.statusCode);
done(data);
}
});
});
req.on('error', function(e) {
console.log('problem with request: ' + e.message);
});
req.end();
};
Level.activateAll = function(done) {
var options = Level.getHTTPOptions();
options.path = '/ra/games/1/levels/all/activate';
options.method = 'PUT';
var req = http.request(options, function(res) {
var body = '';
res.on('data', function(data) {
if (res.statusCode == 200) {
body += data.toString();
} else {
console.error('Activating levels returns a status code: ' + res.statusCode);
done(data);
}
});
res.on('end', function() {
var obj = JSON.parse(body);
if (obj.success) {
done();
} else {
done(body);
}
});
});
req.on('error', function(e) {
console.log('problem with request: ' + e.message);
});
req.end();
};
Level.prototype.setMetadata = function(done) {
var options = Level.getHTTPOptions();
options.path = '/ra/games/1/levels/metadata';
options.method = 'POST';
options.headers['Content-Type'] = 'application/json';
/*
options.path = 'http://' + options.host + ':' + options.port + options.path;
options.host = '127.0.0.1';
options.port = 8888;
*/
var log10 = Math.log(10);
var parameters = [
{name: 'array_count', value: this.arrayCount},
{name: 'var_count', value: this.varCount},
{name: 'log_value_range', value: Math.log(this.valueRange)/log10},
{name: 'log_max_value', value: Math.log(this.maxValue)/log10}
];
if (this.arrayCount > 0) {
parameters.push({name: 'avg_array_length', value: this.avgArrayLength});
}
var properties = [
{name: 'index', value: this.index}
];
var labels = [
{name: this.problemId, value: true},
{name: this.regionId, value: true}
];
var body = JSON.stringify({
ids: [this.id],
metadata: {
parameters: parameters,
priority: 50,
labels: labels,
properties: properties
}
});
options.headers['Content-Length'] = body.length;
var req = http.request(options, function(res) {
res.on('data', function(data) {
if (res.statusCode == 200) {
var obj = JSON.parse(data);
if (obj.success) {
done();
} else {
done(data);
}
} else {
console.error('Setting level metadata returns a status code: ' + res.statusCode);
done(data);
}
});
});
req.on('error', function(e) {
console.log('problem with request: ' + e.message);
});
req.end(body);
};
// for debugging purpose
Level.prototype.getMetadata = function(callback) {
var options = Level.getHTTPOptions();
options.path = '/ra/games/1/levels/' + this.id + '/metadata';
var req = http.get(options, function(res) {
var body = '';
res.on('data', function(data) {
body += data.toString();
});
res.on('end', function() {
callback(body);
});
});
req.end();
};
Level.prototype.existsInRA = function(done) {
var options = Level.getHTTPOptions();
options.path = '/ra/games/1/levels/' + this.id + '/exists';
var req = http.request(options, function(res1) {
res1.on('data', function(data) {
if (res1.statusCode == 200) {
var obj = JSON.parse(data);
if (obj.success) {
done(null, obj.existsInRepo);
} else {
done(data);
}
} else {
done(data);
}
});
});
req.on('error', function(e) {
console.log('problem with request: ' + e.message);
});
req.end();
};
module.exports = Level;
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var Slice = require('./slice');
var Solution = require('./solution');
var http = require('http');
var raConfig = require('../../config/resource_allocator');
var playerSchema = new Schema({
_id: String,
username: String,
firstName: String,
lastName: String,
profileUpdatedAt: Date,
accessToken: String,
nextSlice: {type: Number, default: 0}, // the number of solutions needed for next story slice
currentSolutionsSubmitted: {type: Number, default: 0} // the number of solutions completed towards the next story slice
});
playerSchema.statics.findOrCreateByAccessToken = function(accessToken, done) {
console.log('Finding user with access token: ' + accessToken);
var Player = this;
this.findOne({accessToken: accessToken}, function(err, doc) {
if (err || doc) {
// either error occurred or found a player in the database
done(err, doc);
} else {
// try to validate the accessToken and load the player from TopCoder
console.log('Validating access token from OAuth server');
var options = {
host: 'oauth.verigames.com',
//port: 8888,
path: '/oauth2/validate',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
};
/*
* to make the request go through Fiddler
options.path = 'http://' + options.host + ':' + options.port + options.path;
options.host = '127.0.0.1';
options.port = 8888;
*/
var req = http.request(options, function(res) {
res.on('data', function(chunk) {
console.log('OAuth server responded: ' + chunk);
if (res.statusCode == 200) {
// valid access token
var userId = JSON.parse(chunk).userId;
// save the player to the database
Player.findByIdAndUpdate(userId, {accessToken: accessToken}, {upsert: true}, done);
} else {
// access token invalid
done(chunk, null);
}
});
});
req.on('error', function(e) {
console.log('problem with request: ' + e.message);
});
req.end('token=' + accessToken);
}
});
};
playerSchema.methods.canGetStorySlice = function() {
if (this.nextSlice <= this.currentSolutionsSubmitted) {
return true;
} else {
return false;
}
};
playerSchema.methods.resetSliceAwardingState = function() {
this.nextSlice = 3 + Math.floor(Math.random() * 2);
this.currentSolutionsSubmitted = 0;
};
playerSchema.methods.getAward = function(callback) {
var self = this;
if (self.canGetStorySlice()) {
Slice.assign(self._id, function(err, slice) {
if (err) {
callback('Assigning a story slice resulted in an error: ' + err);
} else {
self.resetSliceAwardingState();
callback(null, slice);
}
});
} else {
// TODO generate traits
callback(null, 'Poisonous');
}
};
playerSchema.methods.updateProfile = function(callback) {
var http = require('http');
var self = this;
var options = {
host: 'api.xylem.verigames.com',
path: '/api/users/' + self._id,
method: 'GET'
};
var req = http.request(options, function(response) {
response.on('data', function(data) {
if (response.statusCode == 200) {
var profile = JSON.parse(data);
self.username = profile.username;
if (exist(profile.firstName)) {
self.firstName = profile.firstName;
}
if (exist(profile.lastName)) {
self.lastName = profile.lastName;
}
callback(null, self);
} else {
var msg = 'Fetching player profile from verigames.com resulted in an error: '
+ data + ' with status code: ' + response.statusCode;
callback(msg);
}
function exist(something) {
return something && something != "null" && something != "undefined" && something.length > 0;
}
});
});
req.on('error', function(e) {
console.log('Problem with request: ' + e.message);
});
req.end();
};
playerSchema.methods.getSlices = function(callback) {
Solution.find({solverId: this.id, sliceId: {$exists: true}}, function(error, solutions) {
if (error) {
var msg = 'Error in finding solutions submitted by the player: ' + error;
callback(msg, null);
} else {
var sliceIds = solutions.map(function(s) {return s.sliceId});
Slice.find({_id: {$in: sliceIds}}, callback);
}
});
};
playerSchema.statics.sendSimpleRequest = function(method, path, done, returnField) {
var config = raConfig[this.env];
var options = {
host: config.host,
port: config.port,
method: method,
path: '/ra/games/1/players/' + path
};
var req = http.request(options, function(res1) {
res1.on('data', function(data) {
if (res1.statusCode == 200) {
var obj = JSON.parse(data);
if (obj.success) {
if (returnField) {
done(null, obj[returnField]);
} else {
done();
}
} else {
done(data);
}
} else {
done(data);
}
});
});
req.on('error', function(e) {
console.log('problem with request: ' + e.message);
});
req.end();
};
playerSchema.methods.isActiveInRA = function(done) {
this.constructor.sendSimpleRequest('GET', this.id + '/active', done,
'active');
};
playerSchema.methods.existsInRA = function(done) {
this.constructor.sendSimpleRequest('GET', this.id + '/exists', done,
'existsInRepo');
};
playerSchema.methods.deleteFromRA = function(done) {
this.constructor.sendSimpleRequest('DELETE', this.id, done);
};
playerSchema.methods.createInRA = function(done) {
this.constructor.sendSimpleRequest('POST', this.id + '/new', done);
};
playerSchema.methods.activateInRA = function(done) {
this.constructor.sendSimpleRequest('PUT', this.id + '/activate', done);
};
module.exports = mongoose.model('Player', playerSchema);
package network
{
import localStorage.Debug;
import localStorage.PlayerData;
/**
* Gateway to the resource allocator
*/
public class RAGateway
{
private static const RA_ADDRESS:String = "http://api.xylem.verigames.com";
private static const CONTENT_TYPE:String = "application/json";
private var http:HTTPClient;
public function RAGateway()
{
http = new HTTPClient(Debug.instance.ra_address || RA_ADDRESS);
}
/**
* Get recommended levels from RA.
* callback signature:
* function(instances:Array):void
*
* Array items are all InstanceSequence
*/
public function getLevels(count:int, region:String, excludeProblems:Array, callback):void {
var playerId:String = PlayerData.local.player.id;
if (!playerId) {
callback([]);
return;
}
var labelConstraints:Array = [{name: region, isRequired: true}];
for each (var problemId:String in excludeProblems) {
labelConstraints.push({name: problemId, isRequired: false});
}
var constraints:String = JSON.stringify({
label: labelConstraints
});
http.doPost("/ra/games/1/players/" + playerId + "/count/" + count + "/match",
constraints, CONTENT_TYPE, function(status:int, data:*):void {
if (status == 200) {
var body:Object = JSON.parse(data);
if (body.success) {
var levels:Array = [];
for each(var match:Object in body.matches) {
levels.push(match.levelId);
}
callback(levels);
return;
}
}
ScreenManager.setStatus("Failed to get personalized levels");
trace("Error in getting levels from RA: " + status + "\n" + data);
callback([]);
});
}
public function startLevel(levelId:String):void {
var playerId:String = PlayerData.local.player.id;
if (!playerId) {
return;
}
http.doPost("/ra/put/games/1/players/" + playerId + "/levels/" + levelId + "/started", null, null,
function(status:int, data:*):void {
trace("After reporting level started to RA :" + status + "\n" + data);
});
}
public function stopLevel(levelId:String):void {
var playerId:String = PlayerData.local.player.id;
if (!playerId) {
return;
}
http.doPost("/ra/put/games/1/players/" + playerId + "/stopped", null, null,
function(status:int, data:*):void {
trace("After reporting level stopped to RA :" + status + "\n" + data);
});
}
public function reportPerformance(levelId:String, score:int):void {
var playerId:String = PlayerData.local.player.id;
if (!playerId) {
return;
}
http.doPost("/ra/games/1/players/" + playerId + "/levels/" + levelId + "/performance/" + score + "/report",
null, null, function(status:int, data:*):void {
trace("After reporting player performance to RA :" + status + "\n" + data);
});
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment