From 1ceecc417dfcfbed8ecc2c03fc4c8965e4e133de Mon Sep 17 00:00:00 2001 From: Is Isilon Date: Fri, 26 Feb 2016 14:21:01 +0800 Subject: [PATCH] Added rules and tests for them --- css/style.css | 35 +++++- index.html | 19 ++- js/app.js | 25 ++-- js/detector/detector.js | 69 ++++------- js/detector/event.js | 70 ----------- js/game.js | 255 +++++++++++++++++++++++++++------------- js/gameobjects.js | 102 +++++++--------- js/rules.js | 248 ++++++++++++++++++++++++++++++++++++++ js/ui.js | 5 + json/elements.json | 118 +++++++++---------- json/runes.json | 83 +++++++++++++ test/karma.conf.js | 10 +- test/unit/ruleSpec.js | 102 ++++++++++++++++ 13 files changed, 801 insertions(+), 340 deletions(-) delete mode 100644 js/detector/event.js create mode 100644 js/rules.js create mode 100644 json/runes.json create mode 100644 test/unit/ruleSpec.js diff --git a/css/style.css b/css/style.css index 8a413fc..1860cfa 100644 --- a/css/style.css +++ b/css/style.css @@ -303,17 +303,20 @@ h1 br { padding-top: 50px; } .test-tube { - border-left: 5px solid #949494; + /*border-left: 5px solid #949494; border-right: 5px solid #949494; border-bottom: 5px solid #949494; border-bottom-left-radius: 10px; - border-bottom-right-radius: 10px; + border-bottom-right-radius: 10px;*/ + border: 5px solid #949494; + border-radius: 10px; } /* science-click custom css */ + .panel-body.large { - min-height: 500px; + min-height: 600px; } .ui-draggable { @@ -338,13 +341,19 @@ h1 br { .white-badge { color: black; - background-color: transparent; + background-color: white; + border-radius: 2px; + padding: 2px; + z-index: 100; position: relative; - font-weight: bold; + border: 1px black solid; + /*font-weight: bold;*/ top: -1em; - left: 2em; + left: 2.5em; } + + /* https://github.com/mbostock/d3/blob/master/lib/colorbrewer/colorbrewer.css */ .Paired .q0-12{fill:rgb(166,206,227)} .Paired .q1-12{fill:rgb(31,120,180)} @@ -395,3 +404,17 @@ h1 br { padding-left: 5px; padding-right: 5px; } + +.ui-grid-filter-select { + background: white; + font-size: 22px; +} +#card-deck{ + font-size: 75px; +} +.ui-grid-cell-contents{ + font-size: 22px; +} +.ui-grid-viewport .ui-grid-cell-contents{ + font-size: 22px; +} diff --git a/index.html b/index.html index 3c30343..f43573d 100644 --- a/index.html +++ b/index.html @@ -96,7 +96,7 @@ id="{{r.key}}" class="{{r.key}} element element-store {{ r.state.interesting ? 'blink' : '' }} {{r.color}} {{r.state.amount>0 ? ' ui-draggable': 'empty'}}" data-element="{{r.key}}" - data-drag="true" + data-drag="rc.isAvailable(r)" jqyoui-draggable="{containment:'offset'}" data-jqyoui-options="{{rc.dragOptions}}" data-hashkey={{r.$$hashKey}} @@ -150,9 +150,10 @@ > Your detector. Click on it to generate events. - + 🂠 + @@ -346,8 +347,14 @@ + + + + + + @@ -359,11 +366,11 @@ + - - - + diff --git a/js/app.js b/js/app.js index 6b64358..db57dff 100644 --- a/js/app.js +++ b/js/app.js @@ -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' diff --git a/js/detector/detector.js b/js/detector/detector.js index 97282ba..6ab4c45 100644 --- a/js/detector/detector.js +++ b/js/detector/detector.js @@ -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=$(''); - // $(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(); }, }; diff --git a/js/detector/event.js b/js/detector/event.js deleted file mode 100644 index fdd4916..0000000 --- a/js/detector/event.js +++ /dev/null @@ -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; - } -}; diff --git a/js/game.js b/js/game.js index 912b27a..1a916d8 100644 --- a/js/game.js +++ b/js/game.js @@ -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)); diff --git a/js/gameobjects.js b/js/gameobjects.js index 1f7f04d..f6968db 100644 --- a/js/gameobjects.js +++ b/js/gameobjects.js @@ -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 }; }()); diff --git a/js/rules.js b/js/rules.js new file mode 100644 index 0000000..915aab3 --- /dev/null +++ b/js/rules.js @@ -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, + }; + + +})(_); diff --git a/js/ui.js b/js/ui.js index b7b2ca2..475ae0c 100644 --- a/js/ui.js +++ b/js/ui.js @@ -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'); diff --git a/json/elements.json b/json/elements.json index b2655f8..0d567c5 100644 --- a/json/elements.json +++ b/json/elements.json @@ -1,59 +1,59 @@ -[{"key": "🂡","name":"Ace","value":"1","suite":"Spades","color":"Black","royal":"false","face":"false","number":"false"}, -{"key": "🂢","name":"Two","value":"2","suite":"Spades","color":"Black","royal":"false","face":"false","number":"true"}, -{"key": "🂣","name":"Three","value":"3","suite":"Spades","color":"Black","royal":"false","face":"false","number":"true"}, -{"key": "🂤","name":"Four","value":"4","suite":"Spades","color":"Black","royal":"false","face":"false","number":"true"}, -{"key": "🂥","name":"Five","value":"5","suite":"Spades","color":"Black","royal":"false","face":"false","number":"true"}, -{"key": "🂦","name":"Six","value":"6","suite":"Spades","color":"Black","royal":"false","face":"false","number":"true"}, -{"key": "🂧","name":"Seven","value":"7","suite":"Spades","color":"Black","royal":"false","face":"false","number":"true"}, -{"key": "🂨","name":"Eight","value":"8","suite":"Spades","color":"Black","royal":"false","face":"false","number":"true"}, -{"key": "🂩","name":"Nine","value":"9","suite":"Spades","color":"Black","royal":"false","face":"false","number":"true"}, -{"key": "🂪","name":"Ten","value":"10","suite":"Spades","color":"Black","royal":"false","face":"true","number":"true"}, -{"key": "🂫","name":"Jack","value":"11","suite":"Spades","color":"Black","royal":"true","face":"true","number":"false"}, -{"key": "🂬","name":"Knight","value":"12","suite":"Spades","color":"Black","royal":"true","face":"true","number":"false"}, -{"key": "🂭","name":"Queen","value":"13","suite":"Spades","color":"Black","royal":"true","face":"true","number":"false"}, -{"key": "🂮","name":"King","value":"14","suite":"Spades","color":"Black","royal":"true","face":"true","number":"false"}, -{"key": "🂱","name":"Ace","value":"1","suite":"Hearts","color":"Red","royal":"false","face":"false","number":"false"}, -{"key": "🂲","name":"Two","value":"2","suite":"Hearts","color":"Red","royal":"false","face":"false","number":"true"}, -{"key": "🂳","name":"Three","value":"3","suite":"Hearts","color":"Red","royal":"false","face":"false","number":"true"}, -{"key": "🂴","name":"Four","value":"4","suite":"Hearts","color":"Red","royal":"false","face":"false","number":"true"}, -{"key": "🂵","name":"Five","value":"5","suite":"Hearts","color":"Red","royal":"false","face":"false","number":"true"}, -{"key": "🂶","name":"Six","value":"6","suite":"Hearts","color":"Red","royal":"false","face":"false","number":"true"}, -{"key": "🂷","name":"Seven","value":"7","suite":"Hearts","color":"Red","royal":"false","face":"false","number":"true"}, -{"key": "🂸","name":"Eight","value":"8","suite":"Hearts","color":"Red","royal":"false","face":"false","number":"true"}, -{"key": "🂹","name":"Nine","value":"9","suite":"Hearts","color":"Red","royal":"false","face":"false","number":"true"}, -{"key": "🂺","name":"Ten","value":"10","suite":"Hearts","color":"Red","royal":"false","face":"true","number":"true"}, -{"key": "🂻","name":"Jack","value":"11","suite":"Hearts","color":"Red","royal":"true","face":"true","number":"false"}, -{"key": "🂼","name":"Knight","value":"12","suite":"Hearts","color":"Red","royal":"true","face":"true","number":"false"}, -{"key": "🂽","name":"Queen","value":"13","suite":"Hearts","color":"Red","royal":"true","face":"true","number":"false"}, -{"key": "🂾","name":"King","value":"14","suite":"Hearts","color":"Red","royal":"true","face":"true","number":"false"}, -{"key": "🃁","name":"Ace","value":"1","suite":"Diamonds","color":"Red","royal":"false","face":"false","number":"false"}, -{"key": "🃂","name":"Two","value":"2","suite":"Diamonds","color":"Red","royal":"false","face":"false","number":"true"}, -{"key": "🃃","name":"Three","value":"3","suite":"Diamonds","color":"Red","royal":"false","face":"false","number":"true"}, -{"key": "🃄","name":"Four","value":"4","suite":"Diamonds","color":"Red","royal":"false","face":"false","number":"true"}, -{"key": "🃅","name":"Five","value":"5","suite":"Diamonds","color":"Red","royal":"false","face":"false","number":"true"}, -{"key": "🃆","name":"Six","value":"6","suite":"Diamonds","color":"Red","royal":"false","face":"false","number":"true"}, -{"key": "🃇","name":"Seven","value":"7","suite":"Diamonds","color":"Red","royal":"false","face":"false","number":"true"}, -{"key": "🃈","name":"Eight","value":"8","suite":"Diamonds","color":"Red","royal":"false","face":"false","number":"true"}, -{"key": "🃉","name":"Nine","value":"9","suite":"Diamonds","color":"Red","royal":"false","face":"false","number":"true"}, -{"key": "🃊","name":"Ten","value":"10","suite":"Diamonds","color":"Red","royal":"false","face":"true","number":"true"}, -{"key": "🃋","name":"Jack","value":"11","suite":"Diamonds","color":"Red","royal":"true","face":"true","number":"false"}, -{"key": "🃌","name":"Knight","value":"12","suite":"Diamonds","color":"Red","royal":"true","face":"true","number":"false"}, -{"key": "🃍","name":"Queen","value":"13","suite":"Diamonds","color":"Red","royal":"true","face":"true","number":"false"}, -{"key": "🃎","name":"King","value":"14","suite":"Diamonds","color":"Red","royal":"true","face":"true","number":"false"}, -{"key": "🃑","name":"Ace","value":"1","suite":"Clubs","color":"Black","royal":"false","face":"false","number":"false"}, -{"key": "🃒","name":"Two","value":"2","suite":"Clubs","color":"Black","royal":"false","face":"false","number":"true"}, -{"key": "🃓","name":"Three","value":"3","suite":"Clubs","color":"Black","royal":"false","face":"false","number":"true"}, -{"key": "🃔","name":"Four","value":"4","suite":"Clubs","color":"Black","royal":"false","face":"false","number":"true"}, -{"key": "🃕","name":"Five","value":"5","suite":"Clubs","color":"Black","royal":"false","face":"false","number":"true"}, -{"key": "🃖","name":"Six","value":"6","suite":"Clubs","color":"Black","royal":"false","face":"false","number":"true"}, -{"key": "🃗","name":"Seven","value":"7","suite":"Clubs","color":"Black","royal":"false","face":"false","number":"true"}, -{"key": "🃘","name":"Eight","value":"8","suite":"Clubs","color":"Black","royal":"false","face":"false","number":"true"}, -{"key": "🃙","name":"Nine","value":"9","suite":"Clubs","color":"Black","royal":"false","face":"false","number":"true"}, -{"key": "🃚","name":"Ten","value":"10","suite":"Clubs","color":"Black","royal":"false","face":"true","number":"true"}, -{"key": "🃛","name":"Jack","value":"11","suite":"Clubs","color":"Black","royal":"true","face":"true","number":"false"}, -{"key": "🃜","name":"Knight","value":"12","suite":"Clubs","color":"Black","royal":"true","face":"true","number":"false"}, -{"key": "🃝","name":"Queen","value":"13","suite":"Clubs","color":"Black","royal":"true","face":"true","number":"false"}, -{"key": "🃞","name":"King","value":"14","suite":"Clubs","color":"Black","royal":"true","face":"true","number":"false"}, -{"key": "🂠","name":"Playing Card","value":"15","suite":"Black","color":"Black","royal":"false","face":"false","number":"false"}, -{"key": "🃏","name":"Joker","value":"16","suite":"Black","color":"Black","royal":"false","face":"true","number":"false"}, -{"key": "🃟","name":"Joker","value":"16","suite":"Red","color":"Red","royal":"false","face":"true","number":"false"}] +[{"key": "🂡","name":"Ace","value":1,"suit":"Spades","color":"Black","royal":false,"face":false,"number":false}, +{"key": "🂢","name":"Two","value":2,"suit":"Spades","color":"Black","royal":false,"face":false,"number":true}, +{"key": "🂣","name":"Three","value":3,"suit":"Spades","color":"Black","royal":false,"face":false,"number":true}, +{"key": "🂤","name":"Four","value":4,"suit":"Spades","color":"Black","royal":false,"face":false,"number":true}, +{"key": "🂥","name":"Five","value":5,"suit":"Spades","color":"Black","royal":false,"face":false,"number":true}, +{"key": "🂦","name":"Six","value":6,"suit":"Spades","color":"Black","royal":false,"face":false,"number":true}, +{"key": "🂧","name":"Seven","value":7,"suit":"Spades","color":"Black","royal":false,"face":false,"number":true}, +{"key": "🂨","name":"Eight","value":8,"suit":"Spades","color":"Black","royal":false,"face":false,"number":true}, +{"key": "🂩","name":"Nine","value":9,"suit":"Spades","color":"Black","royal":false,"face":false,"number":true}, +{"key": "🂪","name":"Ten","value":10,"suit":"Spades","color":"Black","royal":false,"face":true,"number":true}, +{"key": "🂫","name":"Jack","value":11,"suit":"Spades","color":"Black","royal":true,"face":true,"number":false}, +{"key": "🂬","name":"Knight","value":12,"suit":"Spades","color":"Black","royal":true,"face":true,"number":false}, +{"key": "🂭","name":"Queen","value":13,"suit":"Spades","color":"Black","royal":true,"face":true,"number":false}, +{"key": "🂮","name":"King","value":14,"suit":"Spades","color":"Black","royal":true,"face":true,"number":false}, +{"key": "🂱","name":"Ace","value":1,"suit":"Hearts","color":"Red","royal":false,"face":false,"number":false}, +{"key": "🂲","name":"Two","value":2,"suit":"Hearts","color":"Red","royal":false,"face":false,"number":true}, +{"key": "🂳","name":"Three","value":3,"suit":"Hearts","color":"Red","royal":false,"face":false,"number":true}, +{"key": "🂴","name":"Four","value":4,"suit":"Hearts","color":"Red","royal":false,"face":false,"number":true}, +{"key": "🂵","name":"Five","value":5,"suit":"Hearts","color":"Red","royal":false,"face":false,"number":true}, +{"key": "🂶","name":"Six","value":6,"suit":"Hearts","color":"Red","royal":false,"face":false,"number":true}, +{"key": "🂷","name":"Seven","value":7,"suit":"Hearts","color":"Red","royal":false,"face":false,"number":true}, +{"key": "🂸","name":"Eight","value":8,"suit":"Hearts","color":"Red","royal":false,"face":false,"number":true}, +{"key": "🂹","name":"Nine","value":9,"suit":"Hearts","color":"Red","royal":false,"face":false,"number":true}, +{"key": "🂺","name":"Ten","value":10,"suit":"Hearts","color":"Red","royal":false,"face":true,"number":true}, +{"key": "🂻","name":"Jack","value":11,"suit":"Hearts","color":"Red","royal":true,"face":true,"number":false}, +{"key": "🂼","name":"Knight","value":12,"suit":"Hearts","color":"Red","royal":true,"face":true,"number":false}, +{"key": "🂽","name":"Queen","value":13,"suit":"Hearts","color":"Red","royal":true,"face":true,"number":false}, +{"key": "🂾","name":"King","value":14,"suit":"Hearts","color":"Red","royal":true,"face":true,"number":false}, +{"key": "🃁","name":"Ace","value":1,"suit":"Diamonds","color":"Red","royal":false,"face":false,"number":false}, +{"key": "🃂","name":"Two","value":2,"suit":"Diamonds","color":"Red","royal":false,"face":false,"number":true}, +{"key": "🃃","name":"Three","value":3,"suit":"Diamonds","color":"Red","royal":false,"face":false,"number":true}, +{"key": "🃄","name":"Four","value":4,"suit":"Diamonds","color":"Red","royal":false,"face":false,"number":true}, +{"key": "🃅","name":"Five","value":5,"suit":"Diamonds","color":"Red","royal":false,"face":false,"number":true}, +{"key": "🃆","name":"Six","value":6,"suit":"Diamonds","color":"Red","royal":false,"face":false,"number":true}, +{"key": "🃇","name":"Seven","value":7,"suit":"Diamonds","color":"Red","royal":false,"face":false,"number":true}, +{"key": "🃈","name":"Eight","value":8,"suit":"Diamonds","color":"Red","royal":false,"face":false,"number":true}, +{"key": "🃉","name":"Nine","value":9,"suit":"Diamonds","color":"Red","royal":false,"face":false,"number":true}, +{"key": "🃊","name":"Ten","value":10,"suit":"Diamonds","color":"Red","royal":false,"face":true,"number":true}, +{"key": "🃋","name":"Jack","value":11,"suit":"Diamonds","color":"Red","royal":true,"face":true,"number":false}, +{"key": "🃌","name":"Knight","value":12,"suit":"Diamonds","color":"Red","royal":true,"face":true,"number":false}, +{"key": "🃍","name":"Queen","value":13,"suit":"Diamonds","color":"Red","royal":true,"face":true,"number":false}, +{"key": "🃎","name":"King","value":14,"suit":"Diamonds","color":"Red","royal":true,"face":true,"number":false}, +{"key": "🃑","name":"Ace","value":1,"suit":"Clubs","color":"Black","royal":false,"face":false,"number":false}, +{"key": "🃒","name":"Two","value":2,"suit":"Clubs","color":"Black","royal":false,"face":false,"number":true}, +{"key": "🃓","name":"Three","value":3,"suit":"Clubs","color":"Black","royal":false,"face":false,"number":true}, +{"key": "🃔","name":"Four","value":4,"suit":"Clubs","color":"Black","royal":false,"face":false,"number":true}, +{"key": "🃕","name":"Five","value":5,"suit":"Clubs","color":"Black","royal":false,"face":false,"number":true}, +{"key": "🃖","name":"Six","value":6,"suit":"Clubs","color":"Black","royal":false,"face":false,"number":true}, +{"key": "🃗","name":"Seven","value":7,"suit":"Clubs","color":"Black","royal":false,"face":false,"number":true}, +{"key": "🃘","name":"Eight","value":8,"suit":"Clubs","color":"Black","royal":false,"face":false,"number":true}, +{"key": "🃙","name":"Nine","value":9,"suit":"Clubs","color":"Black","royal":false,"face":false,"number":true}, +{"key": "🃚","name":"Ten","value":10,"suit":"Clubs","color":"Black","royal":false,"face":true,"number":true}, +{"key": "🃛","name":"Jack","value":11,"suit":"Clubs","color":"Black","royal":true,"face":true,"number":false}, +{"key": "🃜","name":"Knight","value":12,"suit":"Clubs","color":"Black","royal":true,"face":true,"number":false}, +{"key": "🃝","name":"Queen","value":13,"suit":"Clubs","color":"Black","royal":true,"face":true,"number":false}, +{"key": "🃞","name":"King","value":14,"suit":"Clubs","color":"Black","royal":true,"face":true,"number":false}, +{"key": "🂠","name":"Playing Card","value":15,"suit":"Black","color":"Black","royal":false,"face":false,"number":false}, +{"key": "🃏","name":"Joker","value":16,"suit":"Black","color":"Black","royal":false,"face":true,"number":false}, +{"key": "🃟","name":"Joker","value":16,"suit":"Red","color":"Red","royal":false,"face":true,"number":false}] diff --git a/json/runes.json b/json/runes.json new file mode 100644 index 0000000..bc51132 --- /dev/null +++ b/json/runes.json @@ -0,0 +1,83 @@ +[ + {"key":"ᚠ"}, + {"key":"ᚡ"}, + {"key":"ᚢ"}, + {"key":"ᚣ"}, + {"key":"ᚤ"}, + {"key":"ᚥ"}, + {"key":"ᚦ"}, + {"key":"ᚧ"}, + {"key":"ᚨ"}, + {"key":"ᚩ"}, + {"key":"ᚪ"}, + {"key":"ᚫ"}, + {"key":"ᚬ"}, + {"key":"ᚭ"}, + {"key":"ᚮ"}, + {"key":"ᚯ"}, + {"key":"ᚰ"}, + {"key":"ᚱ"}, + {"key":"ᚲ"}, + {"key":"ᚳ"}, + {"key":"ᚴ"}, + {"key":"ᚵ"}, + {"key":"ᚶ"}, + {"key":"ᚷ"}, + {"key":"ᚸ"}, + {"key":"ᚹ"}, + {"key":"ᚺ"}, + {"key":"ᚻ"}, + {"key":"ᚼ"}, + {"key":"ᚽ"}, + {"key":"ᚾ"}, + {"key":"ᚿ"}, + {"key":"ᛀ"}, + {"key":"ᛁ"}, + {"key":"ᛂ"}, + {"key":"ᛃ"}, + {"key":"ᛄ"}, + {"key":"ᛅ"}, + {"key":"ᛆ"}, + {"key":"ᛇ"}, + {"key":"ᛈ"}, + {"key":"ᛉ"}, + {"key":"ᛊ"}, + {"key":"ᛋ"}, + {"key":"ᛌ"}, + {"key":"ᛍ"}, + {"key":"ᛎ"}, + {"key":"ᛏ"}, + {"key":"ᛐ"}, + {"key":"ᛑ"}, + {"key":"ᛒ"}, + {"key":"ᛓ"}, + {"key":"ᛔ"}, + {"key":"ᛕ"}, + {"key":"ᛖ"}, + {"key":"ᛗ"}, + {"key":"ᛘ"}, + {"key":"ᛙ"}, + {"key":"ᛚ"}, + {"key":"ᛛ"}, + {"key":"ᛜ"}, + {"key":"ᛝ"}, + {"key":"ᛞ"}, + {"key":"ᛟ"}, + {"key":"ᛠ"}, + {"key":"ᛡ"}, + {"key":"ᛢ"}, + {"key":"ᛣ"}, + {"key":"ᛤ"}, + {"key":"ᛥ"}, + {"key":"ᛦ"}, + {"key":"ᛧ"}, + {"key":"ᛨ"}, + {"key":"ᛩ"}, + {"key":"ᛪ"}, + {"key":"᛫"}, + {"key":"᛬"}, + {"key":"᛭"}, + {"key":"ᛮ"}, + {"key":"ᛯ"}, + {"key":"ᛰ"} +] diff --git a/test/karma.conf.js b/test/karma.conf.js index c0189b6..a3dc26e 100644 --- a/test/karma.conf.js +++ b/test/karma.conf.js @@ -53,7 +53,10 @@ module.exports = function (config) { // dependencies 'bower_components/jquery/dist/jquery.js', 'bower_components/jquery-ui/jquery-ui.js', + + 'bower_components/bootstrap/dist/js/bootstrap.js', + 'bower_components/angular/angular.js', 'bower_components/angular-route/angular-route.js', 'bower_components/angular-resource/angular-resource.js', @@ -61,7 +64,10 @@ module.exports = function (config) { 'bower_components/angular-mocks/angular-mocks.js', 'bower_components/angular-dragdrop/src/angular-dragdrop.js', 'bower_components/angular-ui-grid/ui-grid.js', + 'bower_components/lodash/dist/lodash.js', + 'bower_components/chai/chai.js', + 'js/external/*.js', // fixtures @@ -88,9 +94,7 @@ module.exports = function (config) { 'js/helpers.js', 'js/analytics.js', 'js/gameobjects.js', - 'js/detector/flame.js', - 'js/detector/bubblr.js', - 'js/detector/event.js', + 'js/rules.js', 'js/detector/detector.js', 'js/ui.js', 'js/game.js', diff --git a/test/unit/ruleSpec.js b/test/unit/ruleSpec.js new file mode 100644 index 0000000..f1a3733 --- /dev/null +++ b/test/unit/ruleSpec.js @@ -0,0 +1,102 @@ +'use strict'; + +/* jasmine specs for filters go here */ +var allCards; +beforeEach(function(){ + allCards = Helpers.loadFile('json/elements.json'); +}); + +describe('Rules', function () { + var card, lastCards; + + beforeEach( + module('Rules') + ); + + beforeEach(function(){ + lastCards = _.sampleSize(allCards,4); + card = _.sample(allCards); + }); + + describe('Rule', function () { + it('should test false on false', function () { + var rule = new Rules.Rule('1',function(){return false;},{},{},[]); + var res = rule.test(card,lastCards,allCards); + expect(res).toBe(false); + }); + it('should test false on assertion error', function () { + var rule = new Rules.Rule('2',function(){return chai.expect(1).to.equal(0);},{},{},[]); + var res = rule.test(card,lastCards,allCards); + expect(res).toBe(false); + }); + it('should test true on assertion', function () { + var rule = new Rules.Rule('3',function(){return chai.expect(1).to.equal(1);},{},{},[]); + var res = rule.test(card,lastCards,allCards); + expect(res).toBe(true); + }); + it('should test true on true', function () { + var rule = new Rules.Rule('4',function(){return true;},{},{},[]); + var res = rule.test(card,lastCards,allCards); + expect(res).toBe(true); + }); + it('should throw test on error', function () { + var rule = new Rules.Rule('5',function(){throw new Error('test');return true;},{},{},[]); + expect(function(){ + rule.test(card,lastCards,allCards); + }).toThrow(); + }); + }); + Rules.rules.forEach(function(rule){ + describe('rule', function () { + it('should test', function () { + var res = rule.test(card,lastCards,allCards); + Boolean(res); + }); + it('should describe itself', function () { + var desc = rule.describe(); + expect(typeof desc).toBe('string'); + expect(desc).not.toContain(/[{}]+/); + }); + it('should describeOptions', function () { + var desc = rule.describeOptions(); + expect(typeof desc).toBe('string'); + expect(desc).not.toContain(/[{}]+/); + }); + it('should describeVariations', function () { + var desc = rule.describeVariations(); + expect(typeof desc).toBe('string'); + expect(desc).not.toContain(/[{}]+/); + }); + it('should genHints', function () { + var hints = rule.genHints(); + expect(hints instanceof Array).toBe(true); + expect(hints.length).toBeGreaterThan(0); + expect(hints.join('')).not.toContain(/[{}]+/); + }); + it('should set options', function () { + var opts1 = rule.options; + var opts = rule.setOptions({a24tgsdgtg43t5fd:1}); + expect(typeof opts).toBe('object'); + expect(opts).not.toEqual(opts1); + }); + + // now check each rule permutation + var options=Object.keys(rule.optionDesc); + options.forEach(function(option){ + var vals = rule.optionDesc[option].possibleVals; + for (var i = 0; i < vals.length; i++) { + it('should test with option: '+option+' = '+vals[i],function(){ + var options={}; + options[option]=vals[i]; + rule.setOptions(options); + var res = rule.test(card,lastCards,allCards); + Boolean(res); + }); + } + + }); + + + }); + }); +});