Added effect math tests

This commit is contained in:
Thaum
2015-03-24 12:11:04 +00:00
parent dc8a286a9a
commit 107a4150ff
4 changed files with 190 additions and 127 deletions

2
.codio
View File

@@ -3,7 +3,7 @@
// Run button configuration
"commands": {
"Run Meteor": "ROOT_URL=http://period-sheriff-3000.codio.io\ncd rpg-docs \n meteor run"
"Run Meteor": "cd rpg-docs \n ROOT_URL=http://period-sheriff.codio.io:3000 meteor"
},
// Preview button configuration

View File

@@ -257,87 +257,46 @@ Characters.helpers({
return value;
},
attributeBase: (function(){
//store a private array of attributes we've visited without returning
//if we try to visit the same attribute twice before resolving its value
//we are in a dependency loop and need to GTFO
var visitedAttributes = [];
return function(attributeName){
check(attributeName, String);
//we're still evaluating this attribute, must be in a loop
if(_.contains(visitedAttributes, attributeName)) {
console.log("dependency loop detected");
return NaN;
}
//push this attribute to the list of visited attributes
//we can't visit it again unless it returns first
visitedAttributes.push(attributeName);
try{
var charId = this._id;
//base value
var value = attributeBase(charId, attributeName);
}finally{
//this attribute returns or fails, pull it from the array, we may visit it again safely
visitedAttributes = _.without(visitedAttributes, attributeName);
}
return value;
}
})(),
attributeBase: preventLoop(function(attributeName){
var charId = this._id;
//base value
return attributeBase(charId, attributeName);
}),
skillMod: (function(){
//store a private array of skills we've visited without returning
//if we try to visit the same skill twice before resolving its value
//we are in a dependency loop and need to GTFO
var visitedSkills = [];
return function(skillName){
check(skillName, String);
//we're still evaluating this attribute, must be in a loop
if(_.contains(visitedSkills, skillName)) {
console.log("dependency loop detected");
return NaN;
}
//push this skill to the list of visited skills
//we can't visit it again unless it returns first
visitedSkills.push(skillName);
try{
var charId = this._id;
skill = this.getField(skillName);
//get the final value of the ability score
var ability = this.attributeValue(skill.ability);
skillMod: preventLoop(function(skillName){
var charId = this._id;
var skill = this.getField(skillName);
//get the final value of the ability score
var ability = this.attributeValue(skill.ability);
//base modifier
var mod = +getMod(ability)
//base modifier
var mod = +getMod(ability)
//multiply proficiency bonus by largest value in proficiency array
var prof = this.proficiency(skillName);
//multiply proficiency bonus by largest value in proficiency array
var prof = this.proficiency(skillName);
//add multiplied proficiency bonus to modifier
mod += prof * this.attributeValue("proficiencyBonus");
Effects.find({charId: charId, stat: skillName, enabled: true}).forEach(function(effect){
switch(effect.operation) {
case "add":
mod += evaluateEffect(charId, effect);
break;
case "mul":
mod *= evaluateEffect(charId, effect);
break;
case "min":
var min = evaluateEffect(charId, effect);
mod = mod > min? mod : min;
break;
case "max":
var max = evaluateEffect(charId, effect);
mod = mod < max? mod : max;
break;
}
});
} finally{
//this skill returns or fails, pull it from the array
visitedSkills = _.without(visitedSkills, skillName);
}
return signedString(mod);
}
})(),
//add multiplied proficiency bonus to modifier
mod += prof * this.attributeValue("proficiencyBonus");
//apply all effects
var rawEffects = Effects.find({charId: charId, stat: skillName, enabled: true}).fetch();
var effects = _.groupBy(rawEffects, "operation");
_.forEach(effects.add, function(effect){
mod += evaluateEffect(charId, effect);
});
_.forEach(effects.mul, function(effect){
mod *= evaluateEffect(charId, effect);
});
_.forEach(effects.min, function(effect){
var min = evaluateEffect(charId, effect);
mod = mod > min? mod : min;
});
_.forEach(effects.max, function(effect){
var max = evaluateEffect(charId, effect);
mod = mod < max? mod : max;
});
return signedString(mod);
}),
proficiency: function(skillName){
var charId = this._id;
@@ -418,7 +377,7 @@ Characters.helpers({
});
//clean up all data related to that character before removing it
Characters.before.remove(function (userId, character) {
Characters.after.remove(function (userId, character) {
if(Meteor.isServer){
Actions .remove({charId: character._id});
Attacks .remove({charId: character._id});

View File

@@ -0,0 +1,27 @@
preventLoop = function(inputFunction){
if(!_.isFunction(inputFunction)) throw new Meteor.Error("Not a function",
"preventLoop can only take a function as an argument");
//store a private array of arguments we have been given without returning
//if we try to visit the same argument twice before resolving its value
//we are in a dependency loop and need to GTFO
var visitedArgs = [];
return function(argument){
var value;
//we're still evaluating this attribute, must be in a loop
if(_.contains(visitedArgs, argument)) {
console.warn("dependency loop detected");
return NaN;
} else{
//push this skill to the list of visited skills
//we can't visit it again unless it returns first
visitedArgs.push(argument);
}
try{
value = inputFunction.call(this, argument);
} finally{
//this argument returns or fails, pull it from the array
visitedArgs = _.without(visitedArgs, argument);
}
return value;
}
};

View File

@@ -1,8 +1,26 @@
var getEffect = function(charId, op, value){
return getAttributeEffect(charId, "constitution", op, value);
};
var getAttributeEffect = function(charId, attribute, op, value){
return {
charId: charId,
type: "inate",
stat: "constitution",
stat: attribute,
operation: op,
value: value,
parent: {
id: charId,
collection: "Characters"
}
}
}
var getSkillEffect = function(charId, op, value){
return {
charId: charId,
type: "inate",
stat: "athletics",
operation: op,
value: value,
parent: {
@@ -15,81 +33,140 @@ var getEffect = function(charId, op, value){
if (!(typeof MochaWeb === 'undefined')){
MochaWeb.testOnly(function(){
describe("Character", function(){
Effects.remove({});
Characters.remove({});
var charId = Characters.insert({owner: "FWeGYyDY5jc4HuTh8"});
var char = Characters.findOne(charId);
var con = function(){return char.attributeValue("constitution")};
var charId, char, con, ath, strMod;
beforeEach(function(){
Effects.remove({});
Characters.remove({});
charId = Characters.insert({owner: "FWeGYyDY5jc4HuTh8"});
char = Characters.findOne(charId);
con = function(){return char.attributeValue("constitution")};
ath = function(){return char.skillMod("athletics")};
strMod = function(){return char.abilityMod("strength")};
});
describe("effects", function(){
describe("attributeValue", function(){
it("should be set to highest base", function(done){
Effects.insert(getEffect(charId, "base", 10), function(err, id){
if(err) done(err);
});
Effects.insert(getEffect(charId, "base", 6), function(err, id){
if(err) done(err);
});
beforeEach(function(){
Effects.remove({});
});
it("should be set to highest base", function(){
Effects.insert(getEffect(charId, "base", 10));
Effects.insert(getEffect(charId, "base", 6));
chai.assert.equal(10, con());
done();
});
it("should add", function(done){
Effects.insert(getEffect(charId, "add", 2), function(err, id){
if(err) done(err);
});
it("should add", function(){
Effects.insert(getEffect(charId, "add", 2));
Effects.insert(getEffect(charId, "base", 10));
chai.assert.equal(12, con());
done();
});
it("should multiply", function(done){
Effects.insert(getEffect(charId, "mul", 2), function(err, id){
if(err) done(err);
});
it("should multiply after adding", function(){
Effects.insert(getEffect(charId, "mul", 2));
Effects.insert(getEffect(charId, "base", 10));
Effects.insert(getEffect(charId, "add", 2));
chai.assert.equal(24, con());
done();
});
it("should be at least highest min", function(done){
Effects.insert(getEffect(charId, "min", 22), function(err, id){
if(err) done(err);
});
it("should be at least highest min", function(){
Effects.insert(getEffect(charId, "min", 22));
Effects.insert(getEffect(charId, "base", 10));
Effects.insert(getEffect(charId, "add", 2));
Effects.insert(getEffect(charId, "mul", 2));
chai.assert.equal(con(), 24);
Effects.insert(getEffect(charId, "min", 28), function(err, id){
if(err) done(err);
});
Effects.insert(getEffect(charId, "min", 28));
chai.assert.equal(28, con());
done();
});
it("should be at most lowest max", function(done){
Effects.insert(getEffect(charId, "max", 30), function(err, id){
if(err) done(err);
});
chai.assert.equal(28, con());
Effects.insert(getEffect(charId, "max", 5), function(err, id){
if(err) done(err);
});
it("should be at most lowest max after minning", function(){
Effects.insert(getEffect(charId, "max", 5));
Effects.insert(getEffect(charId, "min", 22));
Effects.insert(getEffect(charId, "base", 10));
Effects.insert(getEffect(charId, "add", 2));
Effects.insert(getEffect(charId, "mul", 2));
chai.assert.equal(5, con());
Effects.insert(getEffect(charId, "max", 6));
chai.assert.equal(5, con());
done();
});
it("should respect adjustment", function(done){
Characters.update(charId, {$set: {"constitution.adjustment": -2}}, function(err, num){
if(err) done(err);
})
it("should respect adjustment", function(){
Effects.insert(getEffect(charId, "base", 10));
Effects.insert(getEffect(charId, "add", 2));
Effects.insert(getEffect(charId, "mul", 2));
Effects.insert(getEffect(charId, "min", 28));
Effects.insert(getEffect(charId, "max", 5));
Characters.update(charId, {$set: {"constitution.adjustment": -2}})
chai.assert.equal(3, con());
var conBase = char.attributeBase("constitution");
chai.assert.equal(5, conBase)
done();
});
it("should be removed when the character is deleted", function(){
Effects.insert(getEffect(charId, "base", 10));
var count = Effects.find({charId: charId}).count();
chai.assert.equal(count, 1);
Characters.remove(charId);
var count = Effects.find({charId: charId}).count();
chai.assert.equal(count, 0);
});
});
describe("skillMod", function(){
beforeEach(function(){
Effects.remove({});
});
it("should get its base value from the ability mod", function(){
Effects.insert(getAttributeEffect(charId, "strength", "base", 16));
chai.assert.equal(3, strMod());
chai.assert.equal(3, ath());
});
it("should add a multiple of proficiency bonus", function(){
Effects.insert(getAttributeEffect(charId, "strength", "base", 16));
Effects.insert(getAttributeEffect(charId, "proficiencyBonus", "base", 7));
chai.assert.equal(7, char.attributeValue("proficiencyBonus"), "the proficiency bonus is calculated correctly");
Effects.insert(getSkillEffect(charId, "proficiency", 0.5));
Effects.insert(getSkillEffect(charId, "proficiency", 2));
chai.assert.equal(17, ath(), "3 strength + (7 x 2) proficiency bonus");
});
it("should add", function(){
Effects.insert(getAttributeEffect(charId, "strength", "base", 16));
Effects.insert(getSkillEffect(charId, "add", 2));
chai.assert.equal(5, ath());
});
it("should multiply", function(){
Effects.insert(getAttributeEffect(charId, "strength", "base", 16));
Effects.insert(getSkillEffect(charId, "mul", 2));
chai.assert.equal(6, ath());
});
it("should be at least highest min", function(){
Effects.insert(getAttributeEffect(charId, "strength", "base", 16));
Effects.insert(getSkillEffect(charId, "min", 1));
chai.assert.equal(3, ath());
Effects.insert(getSkillEffect(charId, "min", 5));
chai.assert.equal(5, ath());
});
it("should be at most lowest max", function(){
Effects.insert(getAttributeEffect(charId, "strength", "base", 16));
Effects.insert(getSkillEffect(charId, "max", 5));
chai.assert.equal(3, ath());
Effects.insert(getSkillEffect(charId, "max", 2));
chai.assert.equal(2, ath());
});
it("should be removed when the character is deleted", function(){
Characters.remove(charId);
var count = Effects.find({charId: charId}).count();
chai.assert.equal(count, 0);
})
});
});
});