Created
September 17, 2013 17:43
-
-
Save linzhp/6597849 to your computer and use it in GitHub Desktop.
Code for communicating with RA
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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