mirror of
https://github.com/wassname/cardsforscience.git
synced 2026-06-27 18:23:07 +08:00
249 lines
9.7 KiB
JavaScript
249 lines
9.7 KiB
JavaScript
/**
|
|
* Rules objects for game
|
|
* @param {[type]} function functionName( [description]
|
|
* @return {[type]} [description]
|
|
*/
|
|
var Rules = (function functionName(_) {
|
|
|
|
/**
|
|
* Rule prototype
|
|
* @param {String} description - Description of the rule using angular templating from options
|
|
* @param {Function} ruleFunc - A function returning a true or a failure
|
|
* message or a error
|
|
* @param {Object} options - Options for the rule will be given on test and description rendering
|
|
*/
|
|
|
|
/**
|
|
* Rule prototype
|
|
* @param {String} description Description of the rule using angular templating from options
|
|
* @param {Function} ruleFunc A function returning a true or a failure message or a error
|
|
* @param {Object} optionDefaults Default options for the rule e.g. {n:1, parameter:'color'}.
|
|
* @param {Object} optionDesc Object with object for each option
|
|
* @param {Array} optionDesc[].possibleVals Array of possible values or empty if they are to many to be enumerated
|
|
* @param {String} optionDesc[].description Description of this option e.g. 'Property to compare to last card'
|
|
* @param {String} optionDesc[].type Type for this option e.g. 'String'
|
|
* @param {Array} hintTmpls Array of hint templates. Each should only contain one option e.g.
|
|
* `The rule uses {{parameter}}` and for every parameter you can
|
|
* use `The rule doesn't use {{parameterUnused}}` to have each unused param substituted in.
|
|
*/
|
|
var Rule = function (description, ruleFunc, optionDefaults, optionDesc,hintTmpls) {
|
|
if (!(typeof(description)==='string'||description instanceof String)|| !description.length)
|
|
throw new TypeError('description should be a non empty string');
|
|
this.description = description;
|
|
if (!(ruleFunc instanceof Function))
|
|
throw new TypeError('ruleFunc should be a function');
|
|
|
|
this.ruleFunc = ruleFunc;
|
|
this.optionDesc = optionDesc||{};
|
|
this.options = this.optionDefaults = optionDefaults||{};
|
|
this.hintTmpls=hintTmpls||[];
|
|
|
|
// use angular and mustache templating
|
|
_.templateSettings.interpolate = /{{([\s\S]+?)}}/g;
|
|
|
|
// init
|
|
this.hintsUsed = 0;
|
|
this.hints=this.genHints();
|
|
};
|
|
Rule.prototype.setOptions = function (options) {
|
|
this.options = _.defaults(options, this.optionDefaults);
|
|
this.hints=this.genHints();
|
|
return this.options;
|
|
};
|
|
/** Tests the rule and returns true, false, or an AssertionError **/
|
|
Rule.prototype.testAndTell = function (card, lastCards, allCards) {
|
|
chai.expect(card).to.be.a('object');
|
|
chai.expect(lastCards).to.be.a('array');
|
|
chai.expect(allCards).to.be.a('array');
|
|
|
|
var result;
|
|
var reason;
|
|
try {
|
|
result = this.ruleFunc(card, lastCards, allCards, this.options);
|
|
if (result instanceof chai.Assertion){
|
|
reason=result;
|
|
result=true;
|
|
}
|
|
} catch (e) {
|
|
if (e instanceof chai.AssertionError){
|
|
result = false;
|
|
reason=e;
|
|
} else {throw(e);}
|
|
}
|
|
return {result:result,reason:reason};
|
|
};
|
|
Rule.prototype.test = function (card, lastCards, allCards) {
|
|
var res = this.testAndTell(card,lastCards,allCards);
|
|
return res.result;
|
|
};
|
|
/** Describe rule using set options **/
|
|
Rule.prototype.describe = function () {
|
|
return this.describeVariation(this.options);
|
|
};
|
|
/** Compile description using options to express rule**/
|
|
Rule.prototype.describeVariation = function (options) {
|
|
var compiled = _.template(this.description);
|
|
return compiled(_.defaults(options,this.options));
|
|
};
|
|
/** Return options string for humans **/
|
|
Rule.prototype.describeOptions = function () {
|
|
var s = "";
|
|
// explicit python style
|
|
var template = '{{option}} \n\tDescription: {{description}}\n\tType {{type}} \n\tValues: current:{{current}}, default:{{defaultVal}}, all:[{{possibleVals}}]\n ';
|
|
// jsdoc style I think
|
|
// var template = '{{type}}\t[{{option}}={{defaultVal}}]\t{{description}}. ({{current}})[{{possibleVals}}]\n';
|
|
for (var option in this.optionDesc) {
|
|
if (this.optionDesc.hasOwnProperty(option)) {
|
|
var tmplParams = _.defaults({option:option,defaultVal:this.optionDefaults[option],current:this.options[option]},this.optionDesc[option]);
|
|
s+=_.template(template)(tmplParams);
|
|
}
|
|
}
|
|
return s;
|
|
};
|
|
/** Describe all variations on the default options **/
|
|
Rule.prototype.describeVariations = function () {
|
|
var s="";
|
|
for (var option in this.optionDesc) {
|
|
if (this.optionDesc.hasOwnProperty(option)) {
|
|
var vals = this.optionDesc[option].possibleVals;
|
|
for (var i = 0; i < vals.length; i++) {
|
|
var options={};
|
|
options[option]=vals[i];
|
|
s+=this.describeVariation(options)+'\n';
|
|
}
|
|
|
|
}
|
|
}
|
|
return s;
|
|
};
|
|
/** Get next hint **/
|
|
Rule.prototype.nextHint = function () {
|
|
var hint = this.hints[this.hintsUsed];
|
|
this.hintsUsed++;
|
|
return hint||"";
|
|
|
|
};
|
|
/** Generate an automatic hint from params **/
|
|
Rule.prototype.genHints = function () {
|
|
// first manual hints
|
|
this.hintsUsed=0;
|
|
var hints = [];
|
|
|
|
// compile hints for each unused property
|
|
// this onl handles one unused property per hint
|
|
|
|
// get all unused options
|
|
var unusedOptions = {};
|
|
for (var option in this.optionDesc) {
|
|
if (this.optionDesc.hasOwnProperty(option)) {
|
|
// for each option find any unused options
|
|
var posVals = this.optionDesc[option].possibleVals;
|
|
if (posVals){
|
|
var optionUnused = _.difference(posVals,[this.options[option]]);
|
|
unusedOptions[option+'Unused']=optionUnused;
|
|
}
|
|
}
|
|
}
|
|
|
|
// copy options
|
|
var tmplParams = _.extend({},this.options);
|
|
|
|
|
|
// make hint from unused options if there are hint templates for them
|
|
for (var optionUnused in unusedOptions) {
|
|
if (unusedOptions.hasOwnProperty(optionUnused)) {
|
|
|
|
// find hint templats that take this unused option
|
|
for (var i = 0; i < this.hintTmpls.length; i++) {
|
|
var tmpl = this.hintTmpls[i];
|
|
if (tmpl.indexOf(optionUnused)>=0){
|
|
var tmplCmp = _.template(tmpl);
|
|
var unused = unusedOptions[optionUnused];
|
|
// generate a hint for each unsed options
|
|
for (var j = 0; j < unused.length; j++) {
|
|
tmplParams[optionUnused]=unused[j];
|
|
var hint = tmplCmp(tmplParams);
|
|
hints.push(hint);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// now make other hints for used params
|
|
for (var i = 0; i < this.hintTmpls.length; i++) {
|
|
var hint = _.template(this.hintTmpls[i])(tmplParams);
|
|
hints.push(hint);
|
|
}
|
|
|
|
// on on a last hint
|
|
hints.push("That's all the hints for this rule. But don't forget to check for hints for the overall game. This is a game of inductive logic to try to disprove rules.");
|
|
hints.push("You cheeky blighter. Feel free to restart the game to get a new rule if this one is no fun (it will happen).");
|
|
|
|
// and finally remove duplicate hints
|
|
hints=_.uniq(hints);
|
|
|
|
return hints;
|
|
};
|
|
|
|
/** How many combination of this rule **/
|
|
Rule.prototype.combinations = function (arguments) {
|
|
var pos = _.map(this.optionDesc,'possibleVals');
|
|
var c=0;
|
|
for (var i = 0; i < pos.length; i++) {
|
|
c*pos[i].length;
|
|
}
|
|
return c;
|
|
};
|
|
// TODO estimate hardness perhaps through simulation, combinatorics or hints
|
|
|
|
|
|
// Now defined actual rules
|
|
var rules = [];
|
|
|
|
rules.push(
|
|
// here is a example rule where the card must have differen't color etc from the last card
|
|
// but it's abstracted to allow variations
|
|
new Rule(
|
|
"Next card must not have the same {{property}} as the last {{n}}'th card",
|
|
function (card, lastCards, allCards, options) {
|
|
var lastNCard = lastCards[this.options.n-1];
|
|
var property = this.options.property;
|
|
return chai.expect(card)
|
|
.to.have.property(property)
|
|
.not.equal(lastNCard[property]);
|
|
},
|
|
{
|
|
property: 'color',
|
|
n: 1
|
|
},
|
|
{
|
|
property: {
|
|
description: 'The property to compare in last and current card',
|
|
possibleVals: ['color','face','number','value','suit'],
|
|
type: 'String'
|
|
},
|
|
n: {
|
|
description: 'Number for how many cards back. Start counting at 1',
|
|
possibleVals: [1,2,3],
|
|
type: 'Number'
|
|
}
|
|
},
|
|
[
|
|
'This rule does not involve {{propertyUnused}}',
|
|
// 'This rule does not involve the {{nUnused}}th last card',
|
|
'This rule does involve {{property}}',
|
|
'This rule does involve the {{n}}th last card',
|
|
]
|
|
)
|
|
);
|
|
|
|
|
|
return {
|
|
Rule: Rule,
|
|
rules: rules,
|
|
};
|
|
|
|
|
|
})(_);
|