mirror of
https://github.com/wassname/cardsforscience.git
synced 2026-08-01 12:20:04 +08:00
Added rules and tests for them
This commit is contained in:
@@ -14,12 +14,12 @@ var app = (function () {
|
||||
// var elements = Helpers.loadFile('json/elements.json');
|
||||
// elements = elements.map(
|
||||
// function (r) {
|
||||
// return new GameObjects.ElementStore(r);
|
||||
// return new GameObjects.Card(r);
|
||||
// });
|
||||
// // put in extended array with helper methods
|
||||
// elementStore = new GameObjects.ElementStores();
|
||||
// elementStore.push.apply(elementStore,elements);
|
||||
// return elementStore;
|
||||
// Card = new GameObjects.Cards();
|
||||
// Card.push.apply(Card,elements);
|
||||
// return Card;
|
||||
// });
|
||||
|
||||
|
||||
@@ -107,10 +107,10 @@ var app = (function () {
|
||||
var draggable = angular.element(ui.draggable);
|
||||
var key = draggable.data('element');
|
||||
if (!draggable.hasClass('element-store')) {
|
||||
var elementStore = vm.elements.get(key);
|
||||
var Card = vm.elements.get(key);
|
||||
var i = _.findIndex(vm.elements,{$$hashKey:draggable.data('hashkey')});
|
||||
detector.elements.splice(i, 1);
|
||||
elementStore.state.amount += 1;
|
||||
Card.state.amount += 1;
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -150,8 +150,8 @@ var app = (function () {
|
||||
return false;
|
||||
};
|
||||
vm.toggleFlameFuel = function () {
|
||||
console.log('toggleFlameFuel');
|
||||
detector.flamer.toggleFuel();
|
||||
// console.log('toggleFlameFuel');
|
||||
// detector.flamer.toggleFuel();
|
||||
};
|
||||
vm.clearAll = function () {
|
||||
detector.clearAll(game);
|
||||
@@ -200,13 +200,20 @@ var app = (function () {
|
||||
enableFiltering: true,
|
||||
columnDefs: [{
|
||||
field: 'inputs',
|
||||
filter: {},
|
||||
filter: {
|
||||
type: 'select',
|
||||
selectOptions: _(game.elements).filter({state:{discovered:true}}).map(function(e){return {value:e.key,label:e.key};}).value(),
|
||||
},
|
||||
visible: true
|
||||
}, {
|
||||
field: 'reactants',
|
||||
visible: false
|
||||
}, {
|
||||
field: 'results',
|
||||
filter: {
|
||||
type: 'select',
|
||||
selectOptions: _(game.elements).filter({state:{discovered:true}}).map(function(e){return {value:e.key,label:e.key};}).value(),
|
||||
},
|
||||
visible: true,
|
||||
sort: {
|
||||
direction: 'asc'
|
||||
|
||||
+22
-47
@@ -4,26 +4,7 @@ var Detector = function(){
|
||||
|
||||
|
||||
return {
|
||||
core:
|
||||
{
|
||||
// canvas: null,
|
||||
// ctx: null
|
||||
},
|
||||
//
|
||||
// events:
|
||||
// {
|
||||
// canvas: null,
|
||||
// ctx: null,
|
||||
// list: [],
|
||||
// },
|
||||
|
||||
flame:
|
||||
{
|
||||
// canvas: null,
|
||||
// ctx: null
|
||||
},
|
||||
|
||||
elements: new GameObjects.ElementStores(),
|
||||
elements: new GameObjects.Cards(),
|
||||
|
||||
visible: true,
|
||||
|
||||
@@ -36,21 +17,10 @@ var Detector = function(){
|
||||
|
||||
bubblr: undefined,
|
||||
|
||||
init: function(baseSize,element)
|
||||
init: function()
|
||||
{
|
||||
// get canvas
|
||||
// this.core.canvas = document.getElementById('detector-core');
|
||||
// if (!this.core.canvas) {
|
||||
// this.core.canvas=$('<canvas id="detector-core"></canvas>');
|
||||
// $(element).append(this.core.canvas);
|
||||
// }
|
||||
// this.core.ctx = this.core.canvas.getContext('2d');
|
||||
//
|
||||
// this.flame.canvas = document.getElementById('detector-flame');
|
||||
// this.flame.ctx = this.flame.canvas.getContext('2d');
|
||||
|
||||
this.initBubbles();
|
||||
this.initFlame();
|
||||
// this.initBubbles();
|
||||
// this.initFlame();
|
||||
},
|
||||
|
||||
initBubbles: function(){
|
||||
@@ -72,10 +42,10 @@ var Detector = function(){
|
||||
this.bubblr = bubblrElem.data('plugin_bubblr');
|
||||
},
|
||||
|
||||
initFlame: function(){
|
||||
var flameElem = $('#detector-flame').flame();
|
||||
this.flamer = flameElem.data('plugin_flame')
|
||||
},
|
||||
// initFlame: function(){
|
||||
// var flameElem = $('#detector-flame').flame();
|
||||
// this.flamer = flameElem.data('plugin_flame')
|
||||
// },
|
||||
|
||||
bindOnResize: function(){
|
||||
// HACK
|
||||
@@ -114,19 +84,19 @@ var Detector = function(){
|
||||
/** When a user clicks the detector **/
|
||||
addEvent: function()
|
||||
{
|
||||
this.bubblr.bubble(); // bubble for 500ms, TODO make one bubble
|
||||
// this.bubblr.bubble(); // bubble for 500ms, TODO make one bubble
|
||||
},
|
||||
|
||||
/** When a worker clicks the detector **/
|
||||
addEventExternal: function(numWorkers)
|
||||
{
|
||||
this.bubblr.genBubbles(numWorkers);
|
||||
// this.bubblr.genBubbles(numWorkers);
|
||||
},
|
||||
|
||||
/** Draw current events **/
|
||||
draw: function(duration)
|
||||
{
|
||||
this.bubblr.bubble();
|
||||
// this.bubblr.bubble();
|
||||
},
|
||||
|
||||
/** Clear an element back to element Store **/
|
||||
@@ -196,9 +166,14 @@ var Detector = function(){
|
||||
if (newElement && intersectingElements.indexOf(newElement)===-1) intersectingElements.push(newElement);
|
||||
|
||||
|
||||
var observation = this.experiment({inputs:intersectingElements,location:$draggable.offset()},game);
|
||||
console.log('intersectingElements', intersectingElements.length, observation);
|
||||
return observation;
|
||||
var uniqueElems = _(intersectingElements).map('key').uniq().value().length;
|
||||
if (intersectingElements.length==2 && uniqueElems>1){
|
||||
var observation = this.experiment({inputs:intersectingElements,location:$draggable.offset()},game);
|
||||
console.log('intersectingElements', intersectingElements.length, observation);
|
||||
return observation;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
@@ -209,7 +184,7 @@ var Detector = function(){
|
||||
var inputKeys = inputs.map(function(e){return e.key;});
|
||||
inputKeys.sort(); // this makes reaction be independant of order
|
||||
|
||||
var result = game.rules[inputKeys];
|
||||
var result = game.testRules(inputKeys);
|
||||
if (result) {
|
||||
this.reaction(inputs,result.reactants,result.results, options.location, game);
|
||||
} else {
|
||||
@@ -232,7 +207,7 @@ var Detector = function(){
|
||||
// get the uuid from inputs
|
||||
var ingredient = inputs.filter(function(e){return e.key===reactants[i];})[0];
|
||||
var j = _.findIndex(this.elements,{uuid:ingredient.uuid});
|
||||
var removed = this.elements.slice(j,1);
|
||||
var removed = this.elements.splice(j,1);
|
||||
}
|
||||
|
||||
// TODO use angular effects to remove in puff of fade
|
||||
@@ -259,7 +234,7 @@ var Detector = function(){
|
||||
}
|
||||
|
||||
// effects
|
||||
this.bubblr.bubble();
|
||||
// this.bubblr.bubble();
|
||||
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
/** Define and draw to canvas particle events, used in detector **/
|
||||
|
||||
/**
|
||||
* Define and draw to canvas particle events, used in detector
|
||||
* @param {Object} type Object with name 'electron'||'jet'||'muon'
|
||||
* @param {Number} count Number of particles in this event
|
||||
* @param {Boolean} external Wether even was caused by player or worker
|
||||
*/
|
||||
function ParticleEvent(type, count, external)
|
||||
{
|
||||
this.work = typeof external !== 'undefined' ? external : false;
|
||||
this.type = type;
|
||||
this.length = 0;
|
||||
this.radius = 0;
|
||||
this.direction = 0;
|
||||
this.sign = (Math.random() - 0.5 >= 0) ? 1 : -1;
|
||||
this.alpha = this.work ? 0.5 : 1;
|
||||
this.count = count;
|
||||
|
||||
switch (this.type.name)
|
||||
{
|
||||
case 'electron':
|
||||
this.length = detector.radius.siliconSpace * detector.ratio + Math.round((detector.radius.ecal * detector.ratio + 10 - detector.radius.siliconSpace * detector.ratio) * Math.random());
|
||||
this.direction = Math.random() * Math.PI * 2;
|
||||
this.radius = 20 + Math.round((100 - 20) * Math.random());
|
||||
break;
|
||||
case 'jet':
|
||||
this.length = detector.radius.ecal * detector.ratio + Math.round((detector.radius.mucal * detector.ratio - detector.radius.ecal * detector.ratio) * Math.random());
|
||||
this.direction = Math.random() * Math.PI * 2;
|
||||
this.radius = 40 + Math.round((200 - 40) * Math.random());
|
||||
break;
|
||||
case 'muon':
|
||||
this.length = detector.radius.mucal * detector.ratio + 3 * detector.radius.mucalDark * detector.ratio + Math.round((4 * detector.radius.mucalLight * detector.ratio + 2 * detector.radius.mucalDark * detector.ratio) * Math.random());
|
||||
this.direction = Math.random() * Math.PI * 2;
|
||||
this.radius = 200 + Math.round((600 - 200) * Math.random());
|
||||
break;
|
||||
}
|
||||
|
||||
this.draw(16, true);
|
||||
};
|
||||
|
||||
ParticleEvent.prototype.draw = function(duration, init)
|
||||
{
|
||||
init = typeof init !== 'undefined' ? init : false;
|
||||
|
||||
var ctx = detector.events.ctx;
|
||||
var cx = detector.width / 2;
|
||||
var cy = detector.height / 2;
|
||||
|
||||
ctx.save();
|
||||
|
||||
ctx.globalAlpha = this.alpha;
|
||||
ctx.strokeStyle = this.type.color;
|
||||
ctx.fillStyle = this.type.color;
|
||||
ctx.lineWidth = 2;
|
||||
|
||||
ctx.translate(cx, cy);
|
||||
ctx.rotate(this.direction);
|
||||
ctx.translate(-cx, -cy);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx + this.length / 2, cy + this.sign * Math.round(Math.sqrt(Math.abs(this.radius * this.radius - this.length * this.length / 4))), this.radius, - this.sign * Math.PI / 2 - Math.asin(this.length / (2 * this.radius)), - this.sign * Math.PI / 2 + Math.asin(this.length / (2 * this.radius)), false);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.restore();
|
||||
|
||||
if (!init) {
|
||||
this.alpha -= 0.03 / 16 * duration;
|
||||
}
|
||||
};
|
||||
+175
-80
@@ -76,103 +76,198 @@ var Game = (function (Helpers, GameObjects, ObjectStorage) {
|
||||
// })
|
||||
// ).then(function () {
|
||||
|
||||
// Turn JSON files into actual game objects and fill map of all objects
|
||||
var makeGameObject = function (type, object) {
|
||||
// It's okay to define this function here since load is only called
|
||||
// once anyway...
|
||||
var o = new type(object);
|
||||
self.allObjects[o.key] = o;
|
||||
return o;
|
||||
};
|
||||
self.elements = self.elements.slice(0, 20).map(
|
||||
function (r) {
|
||||
return makeGameObject(GameObjects.ElementStore, r);
|
||||
});
|
||||
self.workers = self.workers.map(
|
||||
function (w) {
|
||||
return makeGameObject(GameObjects.Worker, w);
|
||||
});
|
||||
self.upgrades = self.upgrades.map(
|
||||
function (u) {
|
||||
return makeGameObject(GameObjects.Upgrade, u);
|
||||
});
|
||||
self.achievements = self.achievements.map(
|
||||
function (a) {
|
||||
return makeGameObject(GameObjects.Achievement, a);
|
||||
});
|
||||
// Load states from local store
|
||||
for (var key in self.allObjects) {
|
||||
var o = self.allObjects[key];
|
||||
o.loadState(ObjectStorage.load(key));
|
||||
}
|
||||
// Turn JSON files into actual game objects and fill map of all objects
|
||||
var makeGameObject = function (type, object) {
|
||||
// It's okay to define this function here since load is only called
|
||||
// once anyway...
|
||||
var o = new type(object);
|
||||
self.allObjects[o.key] = o;
|
||||
return o;
|
||||
};
|
||||
self.elements = self.elements.map(
|
||||
function (r) {
|
||||
return makeGameObject(GameObjects.Card, r);
|
||||
});
|
||||
self.workers = self.workers.map(
|
||||
function (w) {
|
||||
return makeGameObject(GameObjects.Worker, w);
|
||||
});
|
||||
self.upgrades = self.upgrades.map(
|
||||
function (u) {
|
||||
return makeGameObject(GameObjects.Upgrade, u);
|
||||
});
|
||||
self.achievements = self.achievements.map(
|
||||
function (a) {
|
||||
return makeGameObject(GameObjects.Achievement, a);
|
||||
});
|
||||
// Load states from local store
|
||||
for (var key in self.allObjects) {
|
||||
var o = self.allObjects[key];
|
||||
o.loadState(ObjectStorage.load(key));
|
||||
}
|
||||
|
||||
// put elements in extended array with utility methods
|
||||
self.elementStore = new GameObjects.ElementStores();
|
||||
self.elementStore.push.apply(self.elementStore, self.elements);
|
||||
self.elements = self.elementStore;
|
||||
// put elements in extended array with utility methods
|
||||
self.Card = new GameObjects.Cards();
|
||||
self.Card.push.apply(self.Card, self.elements);
|
||||
self.elements = self.Card;
|
||||
// var totalElements = _(self.elements).map('state.amount').sum();
|
||||
// if (totalElements<1) self.initialElements();
|
||||
|
||||
self.rules = self.generateRules();
|
||||
|
||||
self.loaded = true;
|
||||
return self;
|
||||
self.rules = self.generateRules();
|
||||
|
||||
self.loaded = true;
|
||||
return self;
|
||||
// });
|
||||
};
|
||||
|
||||
Game.prototype.initialElements = function () {
|
||||
// if we are making new rules we will also reset element stores
|
||||
this.elements.map(function (element) {
|
||||
element.state.amount = 0;
|
||||
element.state.discovered = false;
|
||||
element.state.interesting = false;
|
||||
});
|
||||
// but give us a random 4 number cards of one suite
|
||||
var startSuit = _.sample(["Spades", "Hearts", "Diamonds", "Clubs"]);
|
||||
|
||||
this.elements.map(function (element) {
|
||||
if (element.number && element.suit === startSuit) {
|
||||
element.state.amount = 5;
|
||||
element.state.discovered = true;
|
||||
}
|
||||
});
|
||||
console.log('Set initial cards');
|
||||
return startSuit;
|
||||
}
|
||||
|
||||
/** Generate rules between runes **/
|
||||
Game.prototype.generateRules = function () {
|
||||
var rules = ObjectStorage['rules'];
|
||||
if (!rules) {
|
||||
var elements = this.elements;
|
||||
// generate rules and store them with a hash of inredients
|
||||
// the rule will be an object with reactants, catalysts, conditions, results
|
||||
// todo make a strcture that's more like a tree with progression etc
|
||||
// todo add duds, misleading ones, explosions wildcards
|
||||
// todo make this theory based?
|
||||
// todo simulation
|
||||
var rules = {};
|
||||
for (var k = 0; k < this.elements.length * 20; k++) {
|
||||
// make a rules
|
||||
var rule = {
|
||||
reactants: [],
|
||||
catalysts: [],
|
||||
conditions: [],
|
||||
results: [],
|
||||
inputs: []
|
||||
}
|
||||
var numOfIngredients = 2 + Math.round(Math.random() * 2);
|
||||
for (var i = 0; i < numOfIngredients; i++) {
|
||||
var j = Math.round(Math.random() * (elements.length - 1));
|
||||
rule.reactants.push(elements[j].key);
|
||||
}
|
||||
|
||||
if (Math.random() < 0.1) {
|
||||
var j = Math.round(Math.random() * (elements.length - 1));
|
||||
rule.catalysts.push(elements[j].key);
|
||||
/**
|
||||
* Make the values sequential for given card names
|
||||
* @param {Array} names - String names e.g. ['King','Queen']
|
||||
* @param {Object} rules - Rules
|
||||
* @return {Object} - modified Rules
|
||||
*/
|
||||
function orderValues(names, rules, reverse) {
|
||||
var namedCards = _.filter(elements, function (c) {
|
||||
return names.indexOf(c.name) > -1;
|
||||
});
|
||||
var uNamedCardVals = _.uniq(_.map(namedCards, 'value'));
|
||||
var maxVal = _.max(uNamedCardVals);
|
||||
var initialValue = rules.value[uNamedCardVals[0]];
|
||||
for (var i = 0; i < uNamedCardVals.length; i++) {
|
||||
var v = uNamedCardVals[i];
|
||||
if (reverse)
|
||||
rules.value[v] = initialValue + maxVal - v;
|
||||
else
|
||||
rules.value[v] = initialValue + v;
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
|
||||
var rules = ObjectStorage.load('rules');
|
||||
if (!rules) {
|
||||
|
||||
var startSuit = this.initialElements();
|
||||
|
||||
var elements = this.elements;
|
||||
var attrs = ["key", "name", "value", "suit", "color", "royal", "face", "number"];
|
||||
var rules = {};
|
||||
|
||||
// first lets apply random values to each attribute
|
||||
for (var i = 0; i < attrs.length; i++) {
|
||||
var attr = attrs[i];
|
||||
rules[attr] = {};
|
||||
var uniqVals = _.uniq(_.map(elements, attr));
|
||||
for (var j = 0; j < uniqVals.length; j++) {
|
||||
var v = uniqVals[j];
|
||||
// E.g. rules.suit.black=4, rules.name.Queen=10 etc
|
||||
rules[attr][v] = Math.round(Math.random() * 104);
|
||||
}
|
||||
var numOfresults = Math.round(Math.random() * 3);
|
||||
for (var i = 0; i < numOfresults; i++) {
|
||||
var j = Math.round(Math.random() * (elements.length - 1));
|
||||
rule.results.push(elements[j].key);
|
||||
}
|
||||
rule.inputs = [].concat(rule.reactants, rule.catalysts)
|
||||
rule.reactants.sort()
|
||||
rule.results.sort()
|
||||
rule.catalysts.sort()
|
||||
rule.inputs.sort();
|
||||
// index byhash of sorted array of reactants
|
||||
rules[rule.inputs] = rule
|
||||
}
|
||||
|
||||
// now lets overide some attrbutes with a bit more order
|
||||
|
||||
// number cards should be in sequence as should face cards
|
||||
// _.uniq(_.map(_.filter(cards,{face:'true'}),'value'))
|
||||
var royals = ["Jack", "Knight", "Queen", "King"];
|
||||
var numbers = ["Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"];
|
||||
// lets randomly add the ace to the top or bottom
|
||||
if (Math.random() > 0.5) {
|
||||
royals.push("Ace");
|
||||
} else {
|
||||
numbers.push("Ace");
|
||||
}
|
||||
orderValues(royals, rules);
|
||||
orderValues(numbers, rules);
|
||||
|
||||
// and make the startingSuit 0 for a simpler set of initial rules
|
||||
rules.suit[startSuit] = 0;
|
||||
|
||||
// and finally get the joker a change for a negative
|
||||
rules.value[16] = Math.random(Math.random() * 20 - 10);
|
||||
|
||||
|
||||
|
||||
console.log('Reset and made new rules');
|
||||
|
||||
}
|
||||
ObjectStorage['rules'] = rules;
|
||||
ObjectStorage.save('rules', rules);
|
||||
return rules;
|
||||
},
|
||||
Game.prototype.save = function () {
|
||||
// Save every object's state to local storage
|
||||
for (var key in this.allObjects) {
|
||||
ObjectStorage.save(key, this.allObjects[key].state);
|
||||
/**
|
||||
* Test the rules
|
||||
* @param {array} inputKeys - e.g ["🂤", "🂤"]
|
||||
* @return {Object} rule with {inputs,reactants,results}
|
||||
*/
|
||||
Game.prototype.testRules = function (inputKeys) {
|
||||
var self = this;
|
||||
var attrs = ["value", "suit"]; // attrs we use, possible expand later
|
||||
|
||||
|
||||
// work out total for this combo
|
||||
var total = 0;
|
||||
for (var i = 0; i < inputKeys.length; i++) {
|
||||
var key = inputKeys[i];
|
||||
var element = _.find(self.elements, {
|
||||
key: key
|
||||
});
|
||||
for (var j = 0; j < attrs.length; j++) {
|
||||
var attr = attrs[j];
|
||||
// add appropriate value for this card's attribute
|
||||
// e.g. total+=rules.suit["spade]
|
||||
total += self.rules[attr][element[attr]];
|
||||
}
|
||||
}
|
||||
|
||||
// you keep you results and thus get more cards unless the total is
|
||||
// <5
|
||||
var maxValue = 14; //=_(self.elements).map('value').map(function(v){return parseInt(v);}).max()
|
||||
var divisor = Math.round(maxValue * 3); // ~66% chance of dud if they don't understand rules
|
||||
var value = total % maxValue;
|
||||
var resultElem = _.find(self.elements, {
|
||||
value: value
|
||||
});
|
||||
var reactants = resultElem ? inputKeys : [];
|
||||
var results = resultElem ? [resultElem.key] : [];
|
||||
// TODO add penalty for duds? like more cards
|
||||
|
||||
return {
|
||||
results: results,
|
||||
inputs: inputKeys,
|
||||
conditions: [],
|
||||
catalysts: [],
|
||||
reactants: reactants
|
||||
};
|
||||
};
|
||||
Game.prototype.save = function () {
|
||||
// Save every object's state to local storage
|
||||
for (var key in this.allObjects) {
|
||||
ObjectStorage.save(key, this.allObjects[key].state);
|
||||
}
|
||||
};
|
||||
|
||||
return Game;
|
||||
}(Helpers, GameObjects, ObjectStorage));
|
||||
|
||||
+42
-60
@@ -98,7 +98,13 @@ var GameObjects = (function () {
|
||||
obsText[k] = observation[k].sort().join('');
|
||||
}
|
||||
}
|
||||
this.state.observations.push(obsText);
|
||||
obsText.amount=1;
|
||||
// check if an obs with all the attributes matching (extra attribs are ok)
|
||||
var index = _.findIndex(this.state.observations,obsText);
|
||||
if (index>-1)
|
||||
this.state.observations[index].amount+=1;
|
||||
else
|
||||
this.state.observations.push(obsText);
|
||||
};
|
||||
|
||||
|
||||
@@ -111,50 +117,50 @@ var GameObjects = (function () {
|
||||
return false;
|
||||
};
|
||||
|
||||
var ElementStores = function (obj) {
|
||||
var Cards = function (obj) {
|
||||
this.push.apply(this, obj);
|
||||
};
|
||||
|
||||
ElementStores.prototype = Object.create(Array.prototype);
|
||||
Cards.prototype = Object.create(Array.prototype);
|
||||
|
||||
ElementStores.prototype.constructor = Array.constructor;
|
||||
Cards.prototype.constructor = Array.constructor;
|
||||
|
||||
ElementStores.prototype.pushAll = function (items) {
|
||||
Cards.prototype.pushAll = function (items) {
|
||||
this.push.apply(this, items);
|
||||
};
|
||||
|
||||
/** Add a random element or specify it's key **/
|
||||
ElementStores.prototype.addToStore = function (element) {
|
||||
Cards.prototype.addToStore = function (element) {
|
||||
if (element) this.get(element);
|
||||
if (!element) element = this.select();
|
||||
return element.state.amount += 1;
|
||||
};
|
||||
|
||||
/** Add a random discovered element or specify it's key **/
|
||||
ElementStores.prototype.addKnownToStore = function (element) {
|
||||
Cards.prototype.addKnownToStore = function (element) {
|
||||
var discovered = this.filter(function (e) {
|
||||
return e.state.discovered;
|
||||
});
|
||||
discovered = new GameObjects.ElementStores(discovered);
|
||||
discovered = new GameObjects.Cards(discovered);
|
||||
if (element) discovered.get(element);
|
||||
if (!element) element = discovered.select();
|
||||
return element.state.amount += 1;
|
||||
};
|
||||
|
||||
/** Select random element from store **/
|
||||
ElementStores.prototype.select = function () {
|
||||
Cards.prototype.select = function () {
|
||||
var i = Math.round((this.length - 1) * Math.random());
|
||||
return this[i];
|
||||
};
|
||||
/** Get element by key **/
|
||||
ElementStores.prototype.get = function (key) {
|
||||
Cards.prototype.get = function (key) {
|
||||
return this.filter(function (e) {
|
||||
return e.key === key;
|
||||
})[0];
|
||||
};
|
||||
|
||||
/** Get element by hashid **/
|
||||
ElementStores.prototype.getByHashKey = function (hashKey) {
|
||||
Cards.prototype.getByHashKey = function (hashKey) {
|
||||
if (hashKey === undefined) {
|
||||
console.warn('GetByHashKey given an undefined hashkey', hashKey)
|
||||
return;
|
||||
@@ -172,66 +178,42 @@ var GameObjects = (function () {
|
||||
}
|
||||
};
|
||||
|
||||
/** filter by e.g. {uuid:'34hgyh454'} **/
|
||||
// ElementStores.prototype.filterBy = function (filter) {
|
||||
// return this.filter(function (e) {
|
||||
// var match = true;
|
||||
// for (var k in filter) {
|
||||
// if (filter.hasOwnProperty(k)) {
|
||||
// match = match && (e[k] === filter[k]);
|
||||
// };
|
||||
// }
|
||||
// return match;
|
||||
// });
|
||||
// };
|
||||
//
|
||||
// /** Get indexes matching filter e.g. {uuid:'34hgyh454'} **/
|
||||
// ElementStores.prototype.findIndex = function (filter) {
|
||||
// var self = this;
|
||||
// return this.filterBy(function (e) {
|
||||
// var match = true;
|
||||
// for (var k in filter) {
|
||||
// if (filter.hasOwnProperty(k)) {
|
||||
// match = match && (e[k] === filter[k]);
|
||||
// };
|
||||
// }
|
||||
// return match;
|
||||
// })
|
||||
// .map(function (e) {
|
||||
// return self.indexOf(e);
|
||||
// });
|
||||
// };
|
||||
|
||||
/** @class ElementStore
|
||||
/** @class Card
|
||||
*/
|
||||
var ElementStore = function (obj) {
|
||||
var Card = function (obj) {
|
||||
// load from localStorage by obj.key
|
||||
GameObject.apply(this, [obj]);
|
||||
this.state.amount = Math.round(Math.random() * 2);
|
||||
this.state.discovered = Math.random() < 0.1;
|
||||
this.state.interesting = Math.random() < 0.1;
|
||||
this.state.color = Math.round(Math.random() * 11);
|
||||
this.uuid = this.guid();
|
||||
|
||||
// apply defaults to undefined values
|
||||
this.state = _.defaults(this.state,{
|
||||
amount: 0,
|
||||
discovered: false,
|
||||
interesting: false,
|
||||
});
|
||||
|
||||
// generate uuid
|
||||
this.uuid = this.uuid || this.guid();
|
||||
};
|
||||
|
||||
ElementStore.prototype = Object.create(GameObject.prototype);
|
||||
Card.prototype = Object.create(GameObject.prototype);
|
||||
|
||||
ElementStore.prototype.constructor = ElementStore;
|
||||
Card.prototype.constructor = Card;
|
||||
|
||||
ElementStore.prototype.isVisible = function (lab) {
|
||||
Card.prototype.isVisible = function (lab) {
|
||||
if (!lab) {
|
||||
return false;
|
||||
}
|
||||
return this.state.discovered;
|
||||
};
|
||||
|
||||
ElementStore.prototype.isAvailable = function (lab) {
|
||||
Card.prototype.isAvailable = function (lab) {
|
||||
if (!lab) {
|
||||
return false;
|
||||
}
|
||||
return this.state.amount > 0;
|
||||
};
|
||||
|
||||
ElementStore.prototype.research = function (lab) {
|
||||
Card.prototype.research = function (lab) {
|
||||
if (lab && lab.research(this.state.cost, this.state.reputation)) {
|
||||
this.state.level++;
|
||||
if (this.state.info_levels.length > 0 &&
|
||||
@@ -246,7 +228,7 @@ var GameObjects = (function () {
|
||||
return -1;
|
||||
};
|
||||
|
||||
ElementStore.prototype.getInfo = function () {
|
||||
Card.prototype.getInfo = function () {
|
||||
if (!this._info) {
|
||||
this._info = Helpers.loadFile(this.info);
|
||||
}
|
||||
@@ -254,16 +236,16 @@ var GameObjects = (function () {
|
||||
return this._info;
|
||||
};
|
||||
|
||||
/** Create a new element for the test tube from this ElementStore **/
|
||||
ElementStore.prototype.spawn = function () {
|
||||
/** Create a new element for the test tube from this Card **/
|
||||
Card.prototype.spawn = function () {
|
||||
var element = angular.copy(this);
|
||||
element.uuid = element.guid();
|
||||
element.state = undefined;
|
||||
this.state.amount -= 1;
|
||||
// this.state.amount -= 1;
|
||||
return element;
|
||||
};
|
||||
|
||||
ElementStore.prototype.decreaseStore = function () {
|
||||
Card.prototype.decreaseStore = function () {
|
||||
return this.state.amount -= 1;
|
||||
};
|
||||
|
||||
@@ -406,10 +388,10 @@ var GameObjects = (function () {
|
||||
// Expose classes in module.
|
||||
return {
|
||||
Lab: Lab,
|
||||
ElementStore: ElementStore,
|
||||
Card: Card,
|
||||
Worker: Worker,
|
||||
Upgrade: Upgrade,
|
||||
Achievement: Achievement,
|
||||
ElementStores: ElementStores
|
||||
Cards: Cards
|
||||
};
|
||||
}());
|
||||
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
/**
|
||||
* 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,
|
||||
};
|
||||
|
||||
|
||||
})(_);
|
||||
@@ -9,6 +9,11 @@ var UI = (function () {
|
||||
FastClick.attach(document.body);
|
||||
});
|
||||
|
||||
// $('.prevent-select').on('mousedown', function(e) {
|
||||
// e.preventDefault();
|
||||
// });
|
||||
|
||||
|
||||
/** Show a bootstrap modal with dynamic content e.g. background info **/
|
||||
var showModal = function(title, text, level) {
|
||||
var $modal = $('#infoBox');
|
||||
|
||||
Reference in New Issue
Block a user