Made into next card game

This commit is contained in:
2016-02-27 14:50:06 +08:00
parent 1ceecc417d
commit 67eaa637c6
14 changed files with 2025 additions and 1118 deletions
+6 -3
View File
@@ -10,8 +10,7 @@
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
"test"
],
"dependencies": {
"angular": "1.4.x",
@@ -22,6 +21,10 @@
"angular-resource": "1.4.x",
"angular-animate": "1.4.x",
"angular-dragdrop": "~1.0.13",
"lodash": "~4.5.1"
"lodash": "~4.5.1",
"chai": "~3.5.0",
"angular-ui-grid": "~3.1.1",
"jquery-ui": "~1.11.4",
"font-awesome": "~4.5.0"
}
}
+48 -20
View File
@@ -30,20 +30,6 @@ h1 br {
position: relative;
}
#detector-flame{
position: relative;
/* So it looks like the flame it touching the test-tube */
top: -60px;
}
#detector-core {
z-index: 0;
}
#detector-events {
z-index: 1;
}
#detector-info {
z-index: 10;
position: absolute;
@@ -140,9 +126,9 @@ h1 br {
width: 99%;
}
.panel {
/*.panel {
margin-bottom: 0;
}
}*/
.media-list-no-margin {
margin-top: -15px;
@@ -209,8 +195,8 @@ h1 br {
@media screen and (min-width: 768px) {
#detector-holder {
margin-left: -15px;
margin-right: -15px;
/*margin-left: -15px;
margin-right: -15px;*/
}
.status {
@@ -250,11 +236,11 @@ h1 br {
@media screen and (min-width: 992px) and (max-width: 1199px){
.col-md-3s {
width:20%;
/*width:20%;*/
}
.col-md-5s {
width:40%;
/*width:40%;*/
}
}
@@ -418,3 +404,45 @@ h1 br {
.ui-grid-viewport .ui-grid-cell-contents{
font-size: 22px;
}
.card-panel {
min-height: 130px;
min-width: 100px;
/*border: 1px solid black;*/
/*border-radius: 4px;*/
/*padding: 5px;*/
/*margin: 15px;*/
/*font-size: 20px;*/
}
.short-lines {
line-height: 3em;
}
.main-line{
border: 3px green solid;
border-radius: 5px;
/*margin: 10px;*/
}
.side-line {
/*padding-top: 10px;*/
/*margin: 10px;*/
border:2px red solid;
border-radius: 5px;
min-height: 200px;
}
.card-line{
margin-bottom: 5px;
}
.left-list ul{
padding-left: 5px;
}
.hypotheses select{
height:20px;
padding:1px;
}
.guess-correct{
}
.guess-wrong{
}
+135 -115
View File
@@ -11,11 +11,10 @@
<meta property="og:image" content="https://avatars2.githubusercontent.com/u/8345868">
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css">
<link rel="stylesheet" href="bower_components/font-awesome/css/font-awesome.css">
<link rel="stylesheet" type="text/css" href="bower_components/angular-ui-grid/ui-grid.min.css">
<link rel="stylesheet" href="css/ui-grid.css">
<link rel="stylesheet" href="css/style.css">
<link rel="icon" type="image/png" href="assets/favicon.png" />
@@ -23,14 +22,7 @@
<link rel="apple-touch-icon" sizes="72x72" href="assets/mobile/icon.png">
<meta name="apple-mobile-web-app-capable" content="yes">
<!-- Google Analytics -->
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
</script>
<!-- End Google Analytics -->
</head>
<body>
<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
@@ -74,27 +66,26 @@
</nav>
<div id="main-content" class="container-fluid col-no-padding Paired">
<div class="row">
<div class="col-md-3 col-md-3s col-lg-2 col-no-padding visible-md-block visible-lg-block">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<i class="fa fa-cogs"></i> Elements
<i class="fa fa-cogs"></i> Hand
</h4>
</div>
<div class="panel-body large" id="elementLarge"
data-drop="true"
jqyoui-droppable="{onDrop: 'rc.onDrop'}"
data-jqyoui-options="{{rc.dropOptions}}"
ng-controller="ElementController as rc"
>
<div id="elementContent">
<div class="row" ng-cloak>
<div class="col-md-4" ng-repeat="r in rc.elements">
<span ng-show="rc.isVisible(r)"
<div class="col-md-4" ng-repeat="r in rc.elements" ng-show="rc.isAvailable(r)">
<span ng-show="rc.isAvailable(r)"
id="{{r.key}}"
class="{{r.key}} element element-store {{ r.state.interesting ? 'blink' : '' }} {{r.color}} {{r.state.amount>0 ? ' ui-draggable': 'empty'}}"
class="{{r.key}} element element-store {{ r.state.interesting ? 'blink' : '' }} {{r.color}} {{rc.isAvailable(r) ? ' ui-draggable': 'empty'}}"
ng-click="rc.onClick(r)"
data-element="{{r.key}}"
data-drag="rc.isAvailable(r)"
jqyoui-draggable="{containment:'offset'}"
@@ -103,7 +94,7 @@
>
{{r.key}}
</span>
<span ng-show="rc.isVisible(r)" ng-show="r.state.amount > 0" class="{{r.state.amount>0 ? '': 'empty'}} white-badge ng-binding">{{r.state.amount}}</span>
<span ng-show="r.state.amount > 1" class="{{rc.isAvailable(r) ? '': 'empty'}} white-badge ng-binding">{{r.state.amount}}</span>
</div>
</div>
</div>
@@ -112,111 +103,142 @@
</div>
</div>
<div id="column-lab" class="col-xs-6 col-md-5 col-md-5s col-lg-6 col-no-padding-xs" ng-controller="LabController as lc">
<input class="hidden-xs" id="labname" value="{{ lc.lab.state.name }}" ng-model="lc.lab.state.name" ng-cloak>
<div id="column-lab" class="col-xs-6 col-md-6 col-md-6s col-lg-7 col-no-padding-xs" ng-controller="LabController as lc">
<!-- <input class="hidden-xs" id="labname" value="{{ lc.lab.state.name }}" ng-model="lc.lab.state.name" ng-cloak> -->
<div class="row status" ng-cloak>
<div class="col-xs-12 text-center col-no-padding-xs">
<strong>Score</strong><br>
{{ lc.lab.state.score | niceNumber }}
<div class="update-value" id="update-data"></div>
</div>
</div>
<hr class="hidden-xs">
<div id="detector-holder">
<div id="detector" ng-controller="DetectorController as dc">
<div id="detector-element-container" ng-cloak>
<div ng-repeat="r in dc.elements">
<span
class="{{r.key}} element {{r.color}} ui-draggable"
data-element="{{r.key}}"
data-drag="true"
jqyoui-draggable="{containment:'offset'}"
data-jqyoui-options="{{rc.dragOptions}}"
aria-disabled="false"
style="position: fixed; z-index: 9999; left: {{r.state.left}}px; top: {{r.state.top}}px;"
data-hashkey={{r.$$hashKey}}
>
{{r.key}}
</span>
</div>
</div>
<canvas
ng-click="dc.click()"
id="detector-core"
width="400"
height="400"
class="test-tube prevent-select center-block"
data-drop="true"
<div id="detector-element-container"
ng-cloak
jqyoui-droppable="{onDrop: 'dc.onDrop'}"
data-jqyoui-options="{{dc.dropOptions}}"
data-drop="true"
>
Your detector. Click on it to generate events.
</canvas>
<span id="card-deck" title="deal more cards" ng-click="dc.click()" class="prevent-select">🂠</span>
<!-- <canvas id="detector-flame" width="300" height="300" class="prevent-select center-block " ng-click="dc.toggleFlameFuel()">
Your bunsen burner. Click it to turn on and off.
</canvas> -->
<button id="clearall" class="btn btn-default" ng-click="dc.clearAll()"><i class="fa fa-refresh"></i></button>
<div class="main-line card-line" id="lastCards">
<div class="row" >
<div class="col-md-1" ng-repeat="r in dc.lastCards | limitTo: -10" ng-cloak>
<span
class="{{r.key}} element {{r.color}}"
data-element="{{r.key}}"
data-hashkey={{r.$$hashKey}}
>
{{r.key}}
</span>
</div>
</div>
</div>
<div class="side-line card-line short-lines" id="incorrectCards">
<div class="row">
<div ng-repeat="rr in dc.incorrectCards | limitTo: -10" class="col-md-1" ng-cloak>
<div ng-repeat="r in rr| limitTo: -10" class="row">
<div class="col-md-12">
<span
class="{{r.key}} element {{r.color}}"
data-element="{{r.key}}"
data-hashkey={{r.$$hashKey}}
>
{{r.key}}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row" id="hints">
<div ng-click="dc.revealHint()" class="col-md-6">
<div class="panel panel-default left-list card-panel">
<div class="panel-heading">
<h3 class="panel-title">Hint</h3>
<div ng-cloak> (-{{dc.hintCost}} score)</div>
</div>
<div class="panel-body">
<ul>
<li ng-repeat="h in dc.hints" ng-cloak><i>{{h}}</i></li>
</ul>
</div>
</div>
</div>
<div id="rule" ng-click="dc.revealRule()" class="col-md-6">
<div class="panel panel-default left-list card-panel">
<div class="panel-heading">
<h3 class="panel-title">Rule</h3>
<div ng-cloak>(-{{dc.ruleCost}} score)</div>
</div>
<div class="panel-body">
<i ng-cloak>{{dc.rule}}</i>
</div>
</div>
</div>
</div>
<!-- <span id="card-deck" title="deal more cards" ng-click="dc.click()" class="prevent-select">🂠</span> -->
<!-- <button id="clearall" class="btn btn-default" ng-click="dc.clearAll()"><i class="fa fa-refresh"></i></button> -->
</div>
</div>
<button id="detector-info" class="btn btn-info" ng-click="lc.showDetectorInfo()"><i class="fa fa-info"></i></button>
<button id="detector-info" type="button" class="btn btn-info" ng-click="lc.showDetectorInfo()"><i class="fa fa-info"></i></button>
<div class="row status" ng-cloak>
<!-- <div class="row status" ng-cloak>
<div class="col-xs-4 text-center col-no-padding-xs">
<strong>Data</strong><br>
{{ lc.lab.state.data | niceNumber }}
<strong>Score</strong><br>
{{ lc.lab.state.score | niceNumber }}
<div class="update-value" id="update-data"></div>
</div>
<div class="col-xs-4 text-center col-no-padding-xs">
<strong>Reputation</strong><br>
{{ lc.lab.state.reputation | niceNumber }}
<strong>Rank</strong><br>
{{ lc.lab.state.rank | niceNumber }}
<div class="update-value" id="update-reputation"></div>
</div>
<div class="col-xs-4 text-center col-no-padding-xs">
<strong>Funding</strong><br>
{{ lc.lab.state.money | currency }}
<strong>High Score</strong><br>
{{ lc.lab.state.highScore | niceNumber }}
<div class="update-value" id="update-funding"></div>
</div>
</div>
</div>
</div> -->
<div class="col-md-3 col-md-3s col-lg-2 col-no-padding visible-md-block visible-lg-block">
<div class="panel panel-default panel-stick">
<div class="panel-heading">
<h4 class="panel-title">
<i class="fa fa-users"></i> Observations
</h4>
</div>
<div class="panel-body scrollable large obervations-panel-body" id="hrLarge">
<div id="hrContent" ng-controller="ObservationsController as oc">
<div id="observations-grid" ui-grid="oc.gridOptions" class="observations-grid"></div>
</div>
</div>
</div>
</div>
<div class="col-md-3 col-md-3s col-lg-2 col-no-padding visible-md-block visible-lg-block">
<div class="panel panel-default hidden-xs hidden-sm">
<div class="panel-heading">
<h4 class="panel-title">
<i class="fa fa-wrench"></i> Upgrades
<i class="fa fa-wrench"></i> Guess the rule
</h4>
</div>
<div class="panel-body scrollable large" id="upgradesLarge">
<div id="upgradesContent" ng-controller="UpgradesController as uc">
<ul class="media-list media-list-no-margin" ng-cloak>
<li class="media" ng-show="false"></li>
<li class="media" ng-repeat="u in uc.upgrades" ng-show="uc.isVisible(u)">
<div class="media-body">
<h4 class="media-heading"><i class="fa {{ u.icon }} media-object"></i> {{ u.name }}</h4>
<p>{{ u.description }}</p>
<p class="small">{{ u.effect }}</p>
<button class="btn btn-primary" ng-disabled="!uc.isAvailable(u)" ng-click="uc.upgrade(u)"><i class="fa fa-money"></i> <small>{{ u.cost | currency }}</small></button>
</div>
<div id="upgradesContent" class="left-list" ng-controller="UpgradesController as uc">
<!-- <label>Search: <input ng-model="searchText"></label> -->
<!-- <ul class="form-inline hypotheses" ng-cloak>
<li ng-repeat="rule in uc.rules|filter:searchText">
<div cfs-rule="rule" index="$index" ng-model="uc.rules[$index]"></div>
<button ng-click="uc.guess($event,rule)" class="btn btn-sm btn-default" type="submit">Guess</button>
</li>
</ul>
</ul> -->
<form class="form-inline hypotheses" ng-submit="uc.guess($event,uc.rules2[uc.guessedIndex])">
<div class="radio" ng-repeat="rule in uc.rules2">
<label class="{{uc.isGuessed(rule)}}" ng-cloak>
<input type="radio" ng-model="uc.guessedIndex" name="ruleRadios" id="ruleRadios-{{$index}}" value="{{$index}}" class="btn btn-sm btn-default">
</input>
{{rule.describe()}}
</label>
</div>
<button class="btn btn-sm btn-default" type="submit">Guess</button>
</form>
</div>
</div>
</div>
@@ -241,6 +263,7 @@
</div>
</div>
</div>
</div>
<div class="modal fade" id="infoBox" tabindex="-1" role="dialog" aria-labelledby="infoBoxLabel" aria-hidden="true">
<div class="modal-dialog">
@@ -339,40 +362,37 @@
</div>
</div>
</div>
<script type="text/javascript" src="bower_components/jquery/dist/jquery.js"></script>
<script type="text/javascript" src="bower_components/jquery-ui/jquery-ui.js"></script>
<script type="text/javascript" src="js/external/jquery.ui.touch-punch.min.js"></script>
<script type="text/javascript" src="js/external/jquery.cookie-1.4.1.min.js"></script>
<script type="text/javascript" src="bower_components/bootstrap/dist/js/bootstrap.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1/jquery-ui.js"></script>
<script src="js/external/jquery.ui.touch-punch.min.js"></script>
<script src="js/external/jquery.cookie-1.4.1.min.js"></script>
<script type="text/javascript" src="bower_components/angular/angular.js"></script>
<script type="text/javascript" src="bower_components/angular-dragdrop/src/angular-dragdrop.js"></script>
<script type="text/javascript" src="bower_components/angular-animate/angular-animate.js"></script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.js"></script>
<script type="text/javascript" src="js/external/retina.js"></script>
<script type="text/javascript" src="js/external/fastclick.js"></script>
<script src="bower_components/chai/chai.js"></script>
<script src="bower_components/should/should.js"></script>
<script src="bower_components/expect/index.js"></script>
<script src="bower_components/jasmine-core/lib/jasmine-core/jasmine.js"></script>
<script type="text/javascript" src="bower_components/chai/chai.js"></script>
<script type="text/javascript" src="bower_components/lodash/dist/lodash.js"></script>
<script type="text/javascript" src="bower_components/angular-ui-grid/ui-grid.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.17/angular.js"></script>
<script src="bower_components/angular-dragdrop/src/angular-dragdrop.js"></script>
<script src="bower_components/angular-animate/angular-animate.js"></script>
<script src="js/external/retina.js"></script>
<script src="js/external/fastclick.js"></script>
<script src="bower_components/lodash/dist/lodash.js"></script>
<script src="bower_components/angular-ui-grid/ui-grid.js"></script>
<!-- Google Analytics -->
<!-- <script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
</script> -->
<!-- End Google Analytics -->
<script src="js/storage.js"></script>
<script src="js/helpers.js"></script>
<script src="js/analytics.js"></script>
<script src="js/gameobjects.js"></script>
<script src="js/rules.js"></script>
<!-- <script src="js/detector/flame.js"></script>
<script src="js/detector/bubblr.js"></script> -->
<script src="js/detector/detector.js"></script>
<script src="js/ui.js"></script>
<script src="js/game.js"></script>
<script src="js/app.js"></script>
+175 -112
View File
@@ -8,6 +8,80 @@ var app = (function () {
var app = angular.module('scienceAlchemy', ['ngDragDrop', 'ui.grid']);
// directives
/**
* Provides an easy way to toggle a checkboxes indeterminate property
*
* @example <input type="checkbox" ui-indeterminate="isUnkown">
*/
// app.directive('uiIndeterminate', [
// function () {
//
// return {
// compile: function (tElm, tAttrs) {
// if (!tAttrs.type || tAttrs.type.toLowerCase() !== 'checkbox') {
// return angular.noop;
// }
//
// return function ($scope, elm, attrs) {
// $scope.$watch(attrs.uiIndeterminate, function (newVal) {
// elm[0].indeterminate = !!newVal;
// });
// };
// }
// };
// }
// ]);
/**
* Directive to render a rule and bind it's option with select boxes
* This expects ng-model="rule" as an attribute
*/
function cfsRule($compile) {
return {
link: function (scope, element, attrs) {
scope.$eval("$index");
var rule = scope.$eval(attrs.ngModel);
// first generate a select box for each option (using lodash templating)
_.templateSettings.interpolate = /<%=([\s\S]+?)%>/g;
var optionTmpl = '' +
'<select \n' +
' name="<%=option%>" \n' +
' convert-to-number="{{rule.optionDesc.<%=option%>.type===\'Number\'}}" \n' +
' ng-model="rule.options.<%=option%>" \n' +
' class="form-control input-sm" \n' +
' ng-options="v for v in rule.optionDesc.<%=option%>.possibleVals track by v" \n' +
'>\n' +
'</select>\n';
var tmplParams = _.defaults({},rule.options,rule.otherOptions);
for (var option in rule.optionDesc) {
if (rule.optionDesc.hasOwnProperty(option)) {
var vals = rule.optionDesc[option].possibleVals;
if (vals) {
tmplParams[option] = _.template(optionTmpl)({
option: option
});
} else {
// if there are no options replace '{{color}}' with 'color'
tmplParams[option] = option;
}
}
}
// now put each select box into description
// replace '{{color}}' with '<select name="color"...'
var template = _.template(rule.description)(tmplParams);
// now compile with angular
element.html(template).show();
$compile(element.contents())(scope);
}
};
};
cfsRule.$inject = ['$compile'];
app.directive('cfsRule', cfsRule);
// factories to provide services. They serve shared game objects
// app.factory('elements', function () {
@@ -25,12 +99,14 @@ var app = (function () {
function game($http, $q, lab) {
var game = new Game();
game.lab=lab;
game.lab = lab;
game.allObjects.lab = lab;
var promise = game.load($http, $q);
game.init();
// return promise;
return game;
};
game.$inject = ['$http', '$q'];
game.$inject = ['$http', '$q', 'lab'];
app.factory('game', game);
function lab() {
@@ -39,21 +115,21 @@ var app = (function () {
};
app.factory('lab', lab);
function detector() {
var detector = new Detector();
detector.init(400);
return detector;
}
app.factory('detector', detector);
// add helpers as filters
function niceNumber($filter) {
return Helpers.formatNumberPostfix;
};
niceNumber.$inject = ['$filter'];
app.filter('niceNumber', niceNumber);
function transpose($filter) {
return function (input) {
return _.zip(input);
};
};
transpose.$inject = ['$filter'];
app.filter('transpose', transpose);
function niceTime($filter) {
return Helpers.formatTime;
};
@@ -81,19 +157,17 @@ var app = (function () {
// controllers
app.controller('ElementController', ElementController);
ElementController.$inject = ['$scope', '$compile', 'game', 'detector', 'lab'];
ElementController.$inject = ['$scope', '$compile', 'game', 'lab'];
function ElementController($scope, $compile, game, detector, lab) {
function ElementController($scope, $compile, game, lab) {
var vm = this;
vm.dragOptions = {
revert: true, //"invalid",
zIndex: 100,
// helper: "clone", // drags a clone
// opacity: 0.75,
// start: vm.onRuneDrop.bind(vs),
// stop: vm.onRuneDrop.bind(vs),
cancel: false,
// containment:false
};
vm.onClick = function (card) {
game.play(card);
};
vm.elements = game.elements;
vm.isVisible = function (item) {
@@ -102,23 +176,19 @@ var app = (function () {
vm.isAvailable = function (item) {
return item.isAvailable(lab);
};
vm.onDrop = function (event, ui) {
// store the dropped element
var draggable = angular.element(ui.draggable);
var key = draggable.data('element');
if (!draggable.hasClass('element-store')) {
var Card = vm.elements.get(key);
var i = _.findIndex(vm.elements,{$$hashKey:draggable.data('hashkey')});
detector.elements.splice(i, 1);
Card.state.amount += 1;
}
};
};
function DetectorController($scope, game, detector, lab) {
function DetectorController($scope, game, lab, $filter) {
var vm = this;
vm.elements = detector.elements;
vm.rule = '';
vm.hints = [];
vm.limit = -12;
vm.hintCost = 10;
vm.ruleCost = 300;
vm.lastCards = game.lastCards;
vm.incorrectCards = game.incorrectCards;
vm.dropOptions = {
// accept: ".rune",
addClasses: true,
@@ -127,41 +197,26 @@ var app = (function () {
activeClass: "ui-state-hover",
hoverClass: "ui-state-active",
};
vm.dragOptions = {
revert: "invalid",
zIndex: 100,
// helper: "clone", // drags a clone
// opacity: 0.75,
// start: this.onRuneDrop.bind(this),
// stop: this.onRuneDrop.bind(this),
cancel: false,
// containment:false
};
vm.onDrop = function (event, ui) {
var result = detector.onDrop(event, ui, game);
if (result)
lab.observe(result);
var result = game.onDrop(event, ui, game);
};
vm.click = function () {
lab.clickDetector();
detector.addEvent();
UI.showUpdateValue("#update-data", lab.state.detector);
game.elements.addKnownToStore();
return false;
vm.revealRule = function () {
vm.rule = game.rule.describe();
lab.state.score -= vm.ruleCost;
};
vm.toggleFlameFuel = function () {
// console.log('toggleFlameFuel');
// detector.flamer.toggleFuel();
};
vm.clearAll = function () {
detector.clearAll(game);
vm.revealHint = function () {
var hint = game.rule.nextHint();
if (hint) {
vm.hints.push(hint);
lab.state.score -= vm.hintCost;
}
};
};
DetectorController.$inject = ['$scope', 'game', 'detector', 'lab'];
DetectorController.$inject = ['$scope', 'game', 'lab', '$filter'];
app.controller('DetectorController', DetectorController);
function LabController($interval, game, detector, lab) {
function LabController($interval, game, lab) {
// todo give workers instead of game
var vm = this;
vm.lab = lab;
@@ -171,68 +226,52 @@ var app = (function () {
}
UI.showModal('Detector', vm._detectorInfo);
};
$interval(function () { // one tick
var grant = lab.getGrant();
UI.showUpdateValue("#update-funding", grant);
var sum = 0;
for (var i = 0; i < game.workers.length; i++) {
sum += game.workers[i].state.hired * game.workers[i].state.rate;
}
if (sum > 0) {
lab.acquireData(sum);
UI.showUpdateValue("#update-data", sum);
detector.addEventExternal(game.workers.map(function (w) {
return w.state.hired;
}).reduce(function (a, b) {
return a + b;
}, 0));
}
}, 1000);
};
LabController.$inject = ['$interval', 'game', 'detector', 'lab'];
LabController.$inject = ['$interval', 'game', 'lab'];
app.controller('LabController', LabController);
function ObservationsController($scope, game, lab) {
var vm = this;
vm.observations = lab.state.observations;
vm.gridOptions = {
enableFiltering: true,
columnDefs: [{
field: 'inputs',
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'
}
}, {
field: 'catalysts',
visible: false
}, {
field: 'conditions',
visible: false
}, ],
data: vm.observations
};
};
ObservationsController.$inject = ['$scope', 'game', 'lab'];
app.controller('ObservationsController', ObservationsController);
function UpgradesController($scope, game, lab) {
var vm = this;
// present just a few hypothesis
var rules = Rules.rules.map(function (r) {
return angular.copy(r);
});
rules = _.sampleSize(rules, 2);
if (!_.find(rules, {
description: game.rule.description
})) {
var rule = angular.copy(game.rule);
rule.setOptions({}); // reset the value of this copy to not give away;
rules.push(rule);
} else {
rules.push(angular.copy(_.sample(Rules.rules)));
}
vm.rules = _.shuffle(rules);
// or present them without options?
var rules2 = Rules.rules.map(function (r) {
var rule = angular.copy(r);
rule.randomize();
return rule;
});
rules2 = _.sampleSize(rules, 4);
// add the real rule and a couple of variations
var rule = angular.copy(game.rule);
rules2.push(rule);
var rule = angular.copy(game.rule);
rule.randomize();
rules2.push(rule);
var rule = angular.copy(game.rule);
rule.randomize();
rules2.push(rule);
// clean and add to controller
rules2 = _.uniq(rules2);
rules2 = _.shuffle(rules2);
vm.rules2 = rules2;
vm.upgrades = game.upgrades;
vm.isVisible = function (upgrade) {
return upgrade.isVisible(lab, game.allObjects);
@@ -245,6 +284,30 @@ var app = (function () {
UI.showUpdateValue("#update-funding", upgrade.cost);
}
};
/** return a class based on none right or wrong guessed **/
vm.isGuessed = function(rule){
if (rule.state.guessed===true) return 'bg-success';
else if (rule.state.guessed===false) return 'bg-danger';
else return '';
};
vm.guess = function (e, rule) {
var params = angular.element(e.target).parent('form').serializeArray();
var sameRule = rule.description == game.rule.description;
var sameOpts = angular.equals(rule.options, game.rule.options);
if (sameRule && sameOpts) {
// right!
lab.state.score += 200;
if (!rule.state) rule.state={};
rule.state.guessed=true;
} else {
lab.state.score -= 200;
rule.state.guessed=false;
}
console.log('guess', arguments);
};
};
UpgradesController.$inject = ['$scope', 'game', 'lab'];
app.controller('UpgradesController', UpgradesController);
-230
View File
@@ -1,230 +0,0 @@
/*
Bubblr v1.0 - http://mikeyhogarth.wordpress.com/2012/04/15/bubblr-jquery-bubbles-plugin/
Copyright (c) 2012 Mikey Hogarth
template based on original jquery lightweight plugin biolerplate: @ajpiano, @addyosmani
This plugin available for use in all personal or commercial projects under both MIT and GPL licenses.
*/
(function ($, window, document, undefined) {
var pluginName = "bubblr";
/** default options **/
var defaults = {
numberOfBubbles: 10,
backgroundColor: "transparent",
bubbleColor: "White",
bubbleOpacity: 0.7,
bubbleMinSize: 1,
bubbleMaxSize: 2,
bubbleMaxSpeed: 2,
bubbleMinSpeed: 1,
animationSpeed: 10
};
/**
* Create the bubblr
* @param {dom element} element - dom canvas element
* @param {object} options - options see Bubblr._defaults for options
*/
function Bubblr(element, options) {
this.element = element;
this.options = $.extend({}, defaults, options);
this._defaults = defaults;
this.state = {
bubbles: [],
started: false,
globalTick: 0,
genBubbles: false,
}
this.init();
this.start();
}
Bubblr.prototype.init = function () {
if (this.element.getContext) {
this.ctx = this.element.getContext("2d");
} else {
return;
}
$canvas = $(this.element);
this.width = $canvas.width();
this.height = $canvas.height();
$canvas.css("background-color", this.options.backgroundColor)
// initial bubbles
for (i = 0; i < this.options.numberOfBubbles; i++) {
this.state.bubbles[i] = this.generateBubble();
}
for (i in this.state.bubbles) {
this.drawBubble(this.state.bubbles[i]);
}
};
/**
* Recenter on resize,
* bind this to $(window).on('resize',bubblr.onResize.bind(bubblr);) if needed
**/
Bubblr.prototype.onResize = function () {
this.clear();
this.width = $(this.element).outerWidth();
this.height = $(this.element).outerWidth();
};
/** Start animation loop if not started **/
Bubblr.prototype.start = function () {
if (!this.state.started) {
this.state.started = true;
this.animationLoop();
}
};
/** Stop animation loop **/
Bubblr.prototype.stop = function () {
this.state.started = false;
this.clear();
};
/** Animation loop run Bubblr.stop and start control this **/
Bubblr.prototype.animationLoop = function () {
window.requestAnimFrame(this.animationLoop.bind(this));
this.state.globalTick++;
if (this.state.started) this.animate();
};
Bubblr.prototype.animate = function (arguments) {
this.clear();
for (i in this.state.bubbles) {
this.update(i, this.state.bubbles[i]);
this.draw(i, this.state.bubbles[i]);
}
// clear bubbles where y<0 (pop em)
var toRemove = this.state.bubbles.findIndex(function (b) {
return b.y < 0;
});
for (var i = 0; i < toRemove.length; i++) {
var bubble = this.state.bubbles.splice(toRemove[i], 1)[0];
//bubble.pop();
}
};
Bubblr.prototype.clear = function () {
this.ctx.globalAlpha = 1;
this.ctx.fillStyle = this.options.backgroundColor;
this.ctx.clearRect(0, 0, this.width, this.height);
};
Bubblr.prototype.draw = function (index, bubble) {
this.ctx.globalAlpha = this.options.bubbleOpacity;
this.drawBubble(bubble);
};
/** Update bubble position **/
Bubblr.prototype.update = function (index, bubble) {
bubble.y = bubble.y - bubble.speed
if (bubble.y < 0 && this.state.continuous)
// reset to bottom if continuous running is on
this.state.bubbles[index] = this.generateBubble(true);
else {}
};
/**
* Generate a single bubble
* @param {object} options - options for the bubble
* @param {Number} options.x - Starting x position. Defaults to a random
* location.
* @param {Number} options.y - starting y level. Default to the bottom or
* null for random position, a number sets a
* specific level. Note the top is 0, while the
* bottom is `-$(element).height()`
*/
Bubblr.prototype.bubble = function (options) {
this.state.bubbles.push(this.generateBubble(options));
};
/**
* Helper to generate multiple bubble
* @param {[type]} amount - amount of bubbles to create
* @param {[type]} options - see Bubblr.bubble for description
*/
Bubblr.prototype.genBubbles = function (amount, options) {
for (var i = 0; i < amount; i++) {
this.state.bubbles.push(this.generateBubble(options));
}
}
Bubblr.prototype.drawBubble = function (bubble) {
this.ctx.fillStyle = this.options.bubbleColor;
this.ctx.beginPath();
this.ctx.arc(bubble.x, bubble.y, bubble.radius, 0, Math.PI * 2, true);
this.ctx.closePath();
this.ctx.fill();
};
Bubblr.prototype.generateBubble = function (options) {
options = options || {};
var bubble = new Object();
if (options.x === undefined || options.x === null)
bubble.x = this.randomize(1, this.width);
else
bubble.x = options.x
if (options.y === undefined)
bubble.y = this.height;
else if (options.y === null)
bubble.y = this.randomize(1, this.height);
else
bubble.y = options.y
if (options.radius)
bubble.radius = options.radius
else
bubble.radius = this.randomize(this.options.bubbleMinSize, this.options.bubbleMaxSize);
if (options.speed)
bubble.speed = options.speed;
else
bubble.speed = this.randomize(this.options.bubbleMinSpeed, this.options.bubbleMaxSpeed);
return bubble;
};
Bubblr.prototype.randomize = function (min, max) {
return Math.floor((Math.random() * max) + min);
};
// $.flame will create or return the bubblr element
$.fn[pluginName] = function (options) {
return this.each(function () {
if (!$.data(this, 'plugin_' + pluginName)) {
$.data(this, 'plugin_' + pluginName,
new Bubblr(this, options));
}
});
}
})(jQuery, window, document);
// shim for requestAnimFrame
window.requestAnimFrame = (function () {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function ( /* function */ callback, /* DOMElement */ element) {
window.setTimeout(callback, 1000 / 60);
};
})();
+8 -122
View File
@@ -5,6 +5,8 @@ var Detector = function(){
return {
elements: new GameObjects.Cards(),
lastCards: new GameObjects.Cards(),
incorrectCards: new GameObjects.Cards(),
visible: true,
@@ -17,70 +19,12 @@ var Detector = function(){
bubblr: undefined,
init: function()
init: function(game)
{
// this.initBubbles();
// this.initFlame();
// deal first card
this.lastCards.push(_.sample(game.elements));
},
initBubbles: function(){
// init the test tube animation
var bubblrElem = $('#detector-core').bubblr({
backgroundColor: '#e8e8e8',
bubbleColor: "#bfbfbf",
bubbleMinSize: 3,
bubbleMaxSize: 5,
bubbleMaxSpeed: 2,
bubbleMinSpeed: 1,
animationSpeed: 20,
// bubbleXposMultiplier allows us to position bubbles on the x-axis
bubbleXposMultiplier: 2,
// bubbleYpopLimit determines how far in pixels from the canvas's
// top edge bubbles will disappear
bubbleYpopLimit: 10
});
this.bubblr = bubblrElem.data('plugin_bubblr');
},
// initFlame: function(){
// var flameElem = $('#detector-flame').flame();
// this.flamer = flameElem.data('plugin_flame')
// },
bindOnResize: function(){
// HACK
$(window).on('resize',this.onResize.bind(this));
},
// onResize: function(){
// // TODO, do one, schedule a check and then prevent firing until then
// if ($(window).width() >= 1200) {
// if (this.width != 500) {
// $('#detector').width(500).height(500);
// this.init(500);
// }
// } else if ($(window).width() < 768 && $(window).height() - 90 < 300) {
// var newWidth = $(window).width() - Math.max($(window).width() - ($(window).height() - 90 + 10), 300) - 10;
// if (this.width != newWidth) {
// $('#detector').width(newWidth).height(newWidth);
// this.init(newWidth);
// }
// } else if ($(window).width() < 992) {
// if (this.width != 300) {
// $('#detector').width(300).height(300);
// this.init(300);
// }
// } else {
// if (this.width != 400) {
// $('#detector').width(400).height(400);
// this.init(400);
// }
// }
//
// this.bubblr.onResize();
// this.flamer.onResize();
// },
/** When a user clicks the detector **/
addEvent: function()
{
@@ -99,19 +43,6 @@ var Detector = function(){
// this.bubblr.bubble();
},
/** Clear an element back to element Store **/
storeElementBy: function(qObject,game){
var i = _.findIndex(this.elements,qObject);
var removedElement = this.elements.splice(i,1)[0];
return game.elements.get(removedElement.key).state.amount+=1;
},
clearAll: function(game){
var hashKeys = this.elements.map(function(e){return e.$$hashKey;});
for (var i = 0; i < hashKeys.length; i++) {
this.storeElementBy({'$$hashKey': hashKeys[i]}, game);
}
},
onDrop: function(event, ui, game){
var self=this;
@@ -122,60 +53,15 @@ var Detector = function(){
// if the dragger came from the elements panels, clone it to here
var newElement;
if ($draggable.hasClass('element-store')){
var elementStore = game.elements.filter(function(e){return e.key==$draggable.data('element');})[0];
elementStore.state.amount-=1;
newElement = angular.copy(elementStore);
newElement.state.top=$draggable.offset().top;
newElement.state.left=$draggable.offset().left;
this.elements.push(newElement);
}
// get everything intersecting the drop
var draggableTop = $draggable.offset().top;
var draggableHeight = $draggable.height();
var draggableBottom = draggableTop + draggableHeight;
var draggableLeft = $draggable.offset().left;
var draggableWidth = $draggable.height();
var draggableRight = draggableLeft + draggableWidth;
var detectorDOMElems = angular.element('#detector').find('.element').not('.element-store'); // replace with detector.elements
var intersectingDOMElems = detectorDOMElems.filter( function() {
var $elem = angular.element(this);
var top = $elem.offset().top;
var height = $elem.height();
var bottom = top + height;
var left = $elem.offset().left;
var width = $elem.width();
var right = left + width;
var isCoveredByDraggable = top <= draggableBottom && bottom >= draggableTop
&& left <= draggableRight && right >= draggableLeft;
return isCoveredByDraggable;
});
// We have the DOM's in the area (maybe missing the newly dropped one)
// var droppedModel = game.elements.getByHashKey($draggable.data('hashkey'));
var intersectingElements = intersectingDOMElems.toArray().map(function(input){
return self.elements.getByHashKey(angular.element(input).data('hashkey'));
});
// make sure we include the dropped element as in some cases
// it wont have DOM object yet so it wont be picked up as intersecting
if (newElement && intersectingElements.indexOf(newElement)===-1) intersectingElements.push(newElement);
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;
}
var observation = game.test
console.log('intersectingElements', intersectingElements.length, observation);
return observation;
},
/** Run an experiment depending on reactants and conditions **/
-219
View File
@@ -1,219 +0,0 @@
/**
* @name Flame
* @description jquery plugin to generate flame on a canvas element..
* Based on http://codepen.io/jackrugile/pen/Jbnpv
* @copyright (c) 2015 wassname
* @license: MIT
*/
;(function ( $, window, document, undefined ) {
var pluginName = "flame";
var defaults = {
hueRange: 50,
};
var defaultState = {
parts: [],
partCount: 200,
partsFull: false,
on: true,
rendering: false,
globalTick: 0,
width: null,
height: null,
}
var Part = function(flame){
this.flame=flame;
this.ctx=this.flame.ctx;
this.reset();
};
Part.prototype.reset = function(){
this.startRadius = this.rand(1, 25);
this.radius = this.startRadius;
this.width = this.flame.state.width;
this.height = this.flame.state.height;
this.x = this.width/2 + (this.rand(0, 6) - 3);
this.y = 250;
this.vx = 0;
this.vy = 0;
this.hue = this.rand(this.flame.state.globalTick - this.flame.options.hueRange, this.flame.state.globalTick + this.flame.options.hueRange);
this.saturation = this.rand(50, 100);
this.lightness = this.rand(20, 70);
this.startAlpha = this.rand(1, 10) / 100;
this.alpha = this.startAlpha;
this.decayRate = 0.1;
this.startLife = 7;
this.life = this.startLife;
this.lineWidth = this.rand(1, 3);
}
Part.prototype.update = function(){
this.vx += (this.rand(0, 200) - 100) / 1500;
this.vy -= this.life/50;
this.x += this.vx;
this.y += this.vy;
this.alpha = this.startAlpha * (this.life / this.startLife);
this.radius = this.startRadius * (this.life / this.startLife);
this.life -= this.decayRate;
if(
this.x > this.width + this.radius ||
this.x < -this.radius ||
this.y > this.height + this.radius ||
this.y < -this.radius ||
this.life <= this.decayRate
){
this.reset();
}
};
Part.prototype.render = function(){
this.ctx.beginPath();
this.ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
this.ctx.fillStyle = this.ctx.strokeStyle = 'hsla('+this.hue+', '+this.saturation+'%, '+this.lightness+'%, '+this.alpha+')';
this.ctx.lineWidth = this.lineWidth;
this.ctx.fill();
this.ctx.stroke();
};
Part.prototype.rand = function(min, max){
return Math.floor( (Math.random() * (max - min + 1) ) + min);
};
var Flame = function(element, options){
this.element = element;
this.options = $.extend( {}, defaults, options) ;
this._defaults = defaults;
this.init();
};
Flame.prototype.init = function (arguments) {
if(this.element.getContext) {
this.ctx = this.element.getContext("2d");
} else {
return;
}
if (this.state) this.stop();
this.state = $.extend( {parts: [],partsFull: false,globalTick: 0,}, defaultState, this.state);
// this.state = {
// parts: [],
// partCount: 200,
// partsFull: false,
// on: true,
// rendering: false,
// globalTick: 0,
// width: null,
// height: null,
// }
this.state.width = $(this.element).outerWidth();
this.state.height = $(this.element).outerWidth();
this.start();
}
/** If needed: $(window).on('resize',this.onResize.bind(this) **/
Flame.prototype.onResize = function (arguments) {
this.init();
}
Flame.prototype.createParts = function(){
if(this.state.parts.length > this.state.partCount){
this.state.partsFull = true;
} else {
this.state.parts.push(new Part(this));
}
};
Flame.prototype.updateParts = function(){
var i = this.state.parts.length;
while(i--){
this.state.parts[i].update();
}
};
Flame.prototype.renderParts = function(){
var i = this.state.parts.length;
while(i--){
this.state.parts[i].render();
}
};
Flame.prototype.clear = function (arguments) {
this.ctx.globalAlpha = 1;
this.ctx.fillStyle = 'hsla(0, 0%, 0%, .0)';
this.ctx.clearRect(0,0,this.state.width, this.state.height);
}
/** Fades the previous flames into opacity to product a blur **/
Flame.prototype.fade = function(){
this.ctx.globalCompositeOperation = 'destination-out';
this.ctx.fillStyle = 'hsla(0, 0%, 0%, .3)';
this.ctx.fillRect(0, 0, this.state.width, this.state.height);
this.ctx.globalCompositeOperation = 'lighter';
};
Flame.prototype.draw = function (arguments) {
this.fade();
if (this.state.on){
this.createParts();
} else if (this.state.parts.length>1){
this.state.parts.pop();
}
this.updateParts();
this.renderParts();
}
Flame.prototype.start = function (arguments) {
if (!this.state.rendering){
this.state.rendering=true
this.animate();
return this
} else {return;}
}
Flame.prototype.stop = function (arguments) {
this.state.rendering=false;
this.clear();
return this;
}
/** Toggle fuel and the flame will burn down or up again **/
Flame.prototype.toggleFuel = function (state) {
if (state===undefined)
this.state.on=!this.state.on;
else
this.state.on=state;
return this.state.on;
}
Flame.prototype.animate = function(){
window.requestAnimFrame(this.animate.bind(this),this.element);
this.state.globalTick++;
if (this.state.rendering) this.draw();
};
$.fn[pluginName] = function ( options ) {
return this.each(function () {
if (!$.data(this, 'plugin_' + pluginName)) {
$.data(this, 'plugin_' + pluginName,
new Flame( this, options ));
}
});
}
// shim for requestAnimFrame
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(/* function */ callback, /* DOMElement */ element){
window.setTimeout(callback, 1000 / 60);
};
})();
})( jQuery, window, document );
+77 -154
View File
@@ -5,7 +5,7 @@ var Game = (function (Helpers, GameObjects, ObjectStorage) {
'use strict';
var Game = function () {
// this.lab = new GameObjects.Lab();
this.lab = null;
this.elements = null;
this.workers = null;
this.upgrades = null;
@@ -14,7 +14,11 @@ var Game = (function (Helpers, GameObjects, ObjectStorage) {
// lab: this.lab
};
this.loaded = false;
this.rules = null;
this.rules = Rules.rules;
this.rule=undefined;
this.lastCards= [];
this.incorrectCards= [];
};
Game.prototype.load = function ($http, $q) {
@@ -28,10 +32,7 @@ var Game = (function (Helpers, GameObjects, ObjectStorage) {
// make it work with Angular. If you know a way, let me know, and I'll
// give you a beer. - Kevin
this.elements = Helpers.loadFile('json/elements.json');
this.workers = Helpers.loadFile('/json/workers.json');
this.upgrades = Helpers.loadFile('./json/upgrades.json');
this.achievements = Helpers.loadFile('./json/achievements.json');
this.keywords = Helpers.loadFile('./json/keywords.json');
// function successCallback(response) {
// return angular.fromJson(response.data);
@@ -88,14 +89,6 @@ var Game = (function (Helpers, GameObjects, ObjectStorage) {
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);
@@ -110,158 +103,88 @@ var Game = (function (Helpers, GameObjects, ObjectStorage) {
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;
// });
};
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;
Game.prototype.init = function () {
// setup game
this.rule = _.sample(Rules.rules);
this.rule.randomize();
// check if we have any cards in hand
// var totalElements = _(self.elements).map('state.amount').sum();
// if (totalElements<1) self.dealHand();
this.dealHand();
// deal first card
// TODO make sure these follow rule
this.lastCards.push.apply(this.lastCards,_.sampleSize(this.elements,3));
for (var i = 0; i < this.lastCards.length; i++) {
this.incorrectCards[i]=[];
}
// reset score
this.lab.state.score = 200;
return self;
};
Game.prototype.dealHand = function (n) {
n=n||12;
var hand=_.sampleSize(this.elements,n);
this.elements.map(function(card){
card.state.amount=0;
});
// but give us a random 4 number cards of one suite
var startSuit = _.sample(["Spades", "Hearts", "Diamonds", "Clubs"]);
for (var i = 0; i < hand.length; i++) {
var card = hand[i];
this.elements.get(card.key).state.amount++;
}
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;
}
};
Game.prototype.onClick = function (event, ui) {
var self=this;
console.debug('onClick',arguments);
/** Generate rules between runes **/
Game.prototype.generateRules = function () {
var cardType = angular.element(ui.draggable).data('element');
var card = _.find(this.elements,{key:cardType});
return this.play(card);
};
Game.prototype.onDrop = function (event, ui) {
var self=this;
console.debug('onDrop',arguments);
/**
* 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 cardType = angular.element(ui.draggable).data('element');
var card = _.find(this.elements,{key:cardType});
return this.play(card);
};
Game.prototype.play = function (card) {
var self=this;
card.state.amount-=1;
var rules = ObjectStorage.load('rules');
if (!rules) {
var turn = this.lastCards.length-1;
var correct = this.test(card);
if (correct){
if(!this.incorrectCards[turn]) this.incorrectCards[turn]=[];
this.lastCards.push(angular.copy(card));
if(!this.incorrectCards[turn+1]) this.incorrectCards[turn+1]=[];
this.lab.state.score+=1;
} else {
// add incorrect one to sidelines
if (!this.incorrectCards[turn]) this.incorrectCards[turn]=[];
this.incorrectCards[turn].push(angular.copy(card));
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);
}
}
// 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.save('rules', rules);
return rules;
},
/**
* 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
};
};
// deal 2 random cards
_.sample(this.elements).state.amount+=1;
_.sample(this.elements).state.amount+=1;
this.lab.state.score-=1;
}
return correct;
};
/** Test the rule **/
Game.prototype.test = function (card) {
return this.rule.test(card,this.lastCards,this.elements);
};
Game.prototype.save = function () {
// Save every object's state to local storage
for (var key in this.allObjects) {
+6 -1
View File
@@ -37,7 +37,7 @@ var GameObjects = (function () {
GameObject.apply(this, [{
key: 'lab',
state: {
name: 'Click here to give your lab an awesome name!',
name: 'Write your name here',
detector: 1,
factor: 5,
data: 0,
@@ -50,6 +50,11 @@ var GameObjects = (function () {
dataSpent: 0,
time: 0,
observations: [],
score: 0,
highScore: 0,
hints: 0,
rulesGuessed: [],
rulesFailed: [],
}
}]);
+405 -67
View File
@@ -5,13 +5,7 @@
*/
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
@@ -23,93 +17,162 @@ var Rules = (function functionName(_) {
* @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.
* `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)
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');
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;
this.optionDesc = optionDesc || {};
this.options = this.optionDefaults = optionDefaults || {};
this.hintTmpls = hintTmpls || [];
// init
this.state = {};
this.hintsUsed = 0;
this.hints=this.genHints();
this.otherOptions={nth:this.nth,lastn:this.lastn};
this.hints = this.genHints();
};
Rule.prototype.setOptions = function (options) {
this.options = _.defaults(options, this.optionDefaults);
this.hints=this.genHints();
this.hints = this.genHints();
return this.options;
};
Rule.prototype.randomize = function (options) {
var options = {};
for (var option in this.optionDesc) {
if (this.optionDesc.hasOwnProperty(option)) {
var vals = this.optionDesc[option].possibleVals;
options[option] = _.sample(vals);
}
}
return this.setOptions(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');
// 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;
if (result instanceof chai.Assertion) {
reason = result;
result = true;
}
} catch (e) {
if (e instanceof chai.AssertionError){
if (e instanceof chai.AssertionError) {
result = false;
reason=e;
} else {throw(e);}
reason = e;
} else {
throw (e);
}
}
return {result:result,reason:reason};
return {
result: result,
reason: reason
};
};
Rule.prototype.test = function (card, lastCards, allCards) {
var res = this.testAndTell(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);
};
// Rule.prototype.describeHtml = function (arguments) {
// // we want template params where each options is replace with a select box
// var tmplParams = {};
//
// for (var option in this.optionDesc) {
// if (this.optionDesc.hasOwnProperty(option)) {
// var vals = this.optionDesc[option].possibleVals;
// if (vals) {
// s = '<select name="' + option + '" ng-model="rule.options.' + option + '" class="form-control input-sm">\n';
// for (var i = 0; i < vals.length; i++) {
// s += '<option value="' + vals[i] + '">' + vals[i] + '</option>\n';
// }
// s += '</select>\n';
// tmplParams[option] = s;
// } else {
// tmplParams[option] = option;
// }
// }
// }
// return _.template(this.description)(tmplParams);
// };
/** Generate angular template for this rule **/
// Rule.prototype.describeNg = function (arguments) {
//
// // first put option name into this option template
// // _.templateSettings.interpolate = /<%=([\s\S]+?)%>/g;
// optionTmpl = '' +
// '<select name="<%=option%>" type="number" ng-model="rule.options.<%=option%>" class="form-control input-sm">\n' +
// '<option ng-repeat="v in rule.optionDesc.<%=option%>.possibleVals" value="{{v}}">{{v}}</option>\n' +
// '</select>\n';
//
// // we want template params where each options is replace with a select box
// var tmplParams = {};
//
// for (var option in this.optionDesc) {
// if (this.optionDesc.hasOwnProperty(option)) {
// var vals = this.optionDesc[option].possibleVals;
// if (vals) {
// tmplParams[option] = _.template(optionTmpl)({
// option: option
// });
// } else {
// tmplParams[option] = option;
// }
// }
// }
//
// // now in description replace params with select box
// return _.template(this.description)(tmplParams);
// };
/** Compile description using options to express rule**/
Rule.prototype.describeVariation = function (options) {
var compiled = _.template(this.description);
return compiled(_.defaults(options,this.options));
return compiled(_.defaults(options, this.options, this.otherOptions));
};
/** 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 ';
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';
// 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);
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="";
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';
var options = {};
options[option] = vals[i];
var v = this.describeVariation(options) + '\n';
s.push(v);
}
}
@@ -120,13 +183,13 @@ var Rules = (function functionName(_) {
Rule.prototype.nextHint = function () {
var hint = this.hints[this.hintsUsed];
this.hintsUsed++;
return hint||"";
return hint || "";
};
/** Generate an automatic hint from params **/
Rule.prototype.genHints = function () {
// first manual hints
this.hintsUsed=0;
this.hintsUsed = 0;
var hints = [];
// compile hints for each unused property
@@ -138,15 +201,15 @@ var Rules = (function functionName(_) {
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;
if (posVals) {
var optionUnused = _.difference(posVals, [this.options[option]]);
unusedOptions[option + 'Unused'] = optionUnused;
}
}
}
// copy options
var tmplParams = _.extend({},this.options);
var tmplParams = _.defaults({}, this.options, this.otherOptions);
// make hint from unused options if there are hint templates for them
@@ -156,12 +219,12 @@ var Rules = (function functionName(_) {
// 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){
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];
tmplParams[optionUnused] = unused[j];
var hint = tmplCmp(tmplParams);
hints.push(hint);
}
@@ -181,22 +244,51 @@ var Rules = (function functionName(_) {
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);
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;
var pos = _.map(this.optionDesc, 'possibleVals');
var c = 0;
for (var i = 0; i < pos.length; i++) {
c*pos[i].length;
c * pos[i].length;
}
return c;
};
// TODO estimate hardness perhaps through simulation, combinatorics or hints
/** Helper to format numbers **/
Rule.prototype.nth = function (d) {
if (d > 3 && d < 21) return 'th'; // thanks kennebec
switch (d % 10) {
case 1:
return "st";
case 2:
return "nd";
case 3:
return "rd";
default:
return "th";
}
};
/** Helper to format numbers **/
Rule.prototype.lastn = function (d) {
if (d > 3 && d < 21) return d+'th to last'; // thanks kennebec
switch (d % 10) {
case 1:
return d+" st to last";
case 2:
return d+" nd to last";
case 3:
return d+" rd to last";
default:
return d+" th to last";
}
};
// Now defined actual rules
var rules = [];
@@ -205,40 +297,286 @@ var Rules = (function functionName(_) {
// 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",
"Next card must not have the same <%= property %> as the <%= lastn(n) %> card",
function (card, lastCards, allCards, options) {
var lastNCard = lastCards[this.options.n-1];
var lastNCard = lastCards[lastCards.length - this.options.n];
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'],
possibleVals: ['color', 'face', 'number', 'value', 'suit'],
type: 'String'
},
n: {
description: 'Number for how many cards back. Start counting at 1',
possibleVals: [1,2,3],
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',
}, [
'This rule does not involve <%= propertyUnused %>',
'This rule does involve <%= property %>',
'This rule does involve the <%= n %><%=nth(n)%> last card',
'This rule does not involve the <%= nUnused %><%=nth(n)%> last card', // only indirectly
]
),
new Rule(
"If last <%= n %><%=nth(n)%> cards value was between <%= min %> and <%= max %> play a card that isn't and vice versa.",
function (card, lastCards, allCards, options) {
var lastNCard = lastCards[lastCards.length - this.options.n];
var property = this.options.property;
var lastWasbetween = options.min < lastNCard.value && lastNCard.value < options.max;
if (lastWasbetween)
return chai.expect(card)
.to.have.property('value')
.not.within(options.min, options.max);
else
return chai.expect(card)
.to.have.property('value')
.within(options.min, options.max);
}, {
n: 1,
min: 0,
max: 7
}, {
min: {
description: 'The min bounds to compare value to',
possibleVals: [0, 1, 2, 3, 4, 5],
type: 'Number'
},
max: {
description: 'The max bounds to compare value to',
possibleVals: [7, 8, 9, 10, 11, 12, 13, 14, 15],
type: 'Number'
},
n: {
description: 'Number for how many cards back. Start counting at 1',
possibleVals: [1, 2, 3],
type: 'Number'
}
}, [
'This rule does not involve color',
'This rule does not involve if it\'s a face card',
'This rule does not involve royalty',
'This rule does not involve color',
'This rule does not involve suit',
'This rule does not involve the <%= nUnused %><%=nth(nUnused)%> last card',
'This rule does involve value',
'This rule does involve a value cuttoff',
'This rule does involve the <%= n %><%=nth(n)%> last card',
]
),
new Rule(
"Play a card with a value <%= min %> to <%= max %> higher than the value of the last <%=n%><%=nth(n)%> card. The numbers wrap around once they reach the max",
function (card, lastCards, allCards, options) {
var lastNCard = lastCards[lastCards.length - this.options.n];
var property = this.options.property;
var lastWasbetween = options.min < lastNCard.value && lastNCard.value < options.max;
if (lastWasbetween)
return chai.expect(card)
.to.have.property('value')
.not.within(options.min, options.max);
else
return chai.expect(card)
.to.have.property('value')
.within(options.min, options.max);
}, {
n: 1,
min: 1,
max: 4
}, {
min: {
description: 'The min difference to the last nth card',
possibleVals: [1, 2, 3],
type: 'Number'
},
max: {
description: 'The max difference to the last nth card',
possibleVals: [4, 5, 6, 7, 8],
type: 'Number'
},
n: {
description: 'Number for how many cards back. Start counting at 1',
possibleVals: [1, 2, 3],
type: 'Number'
}
}, [
'This rule does not involve color',
'This rule does not involve if it\'s a face card',
'This rule does not involve royalty',
'This rule does not involve color',
'This rule does not involve suit',
'This rule does not involve the <%= nUnused %><%=nth(nUnused)%> last card',
'This rule does involve value',
'This rule does involve the difference in value to the last card',
'This rule does involve the difference in values that wraps around',
'This rule does involve the difference in values of at least <%= min %>',
'This rule does involve the <%= n %><%=nth(n)%> last card',
]
),
new Rule(
"If the last <%= n %><%=nth(n)%> card is an even-valued card, play a <%= evenColor %> card. Otherwise play the other color",
function (card, lastCards, allCards, options) {
var lastNCard = lastCards[lastCards.length - this.options.n];
var lastWasEven = lastNCard % 2 == 0;
if (lastWasEven)
return chai.expect(card)
.to.have.property('color')
.equals(options.evenColor);
else
return chai.expect(card)
.to.have.property('color')
.not.equals(options.evenColor);
}, {
n: 1,
evenColor: 'red',
}, {
evenColor: {
description: 'The color to play if lastNCard was even',
possibleVals: ['red', 'black'],
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 if it\'s a face card',
'This rule does not involve royalty',
'This rule does not involve suit',
'This rule does not involve the <%= nUnused %><%=nth(nUnused)%> last card',
'This rule does not involve the difference in value to the last card',
'This rule does involve color',
'This rule does involve value',
'This rule does involve the <%= n %><%=nth(n)%> last card',
]
),
new Rule(
"Play a card that has the same <%= property %> or color as the last <%= n %><%=nth(n)%> card but not both.",
function (card, lastCards, allCards, options) {
var lastNCard = lastCards[lastCards.length - options.n];
var matchesColor = lastNCard.color === card.color;
var matches2 = lastNCard.suit === lastNCard.suit;
return ((matchesColor && !matches2) || (!matchesColor && matches2));
}, {
n: 1,
property: 'suit',
}, {
property: {
description: 'The property to compare to the lastNCard',
possibleVals: ['suit', 'value', 'face', 'royal'],
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 the <%= nUnused %><%=nth(nUnused)%> last card',
'This rule does involve color',
'This rule does not involve <%= propertyUnused %>',
'This rule does involve <%= property %>',
'This rule does involve the <%= n %><%=nth(n)%> last card',
]
),
new Rule(
"If the last <%= n %><%=nth(n)%> card's number is higher than <%= min %>, change <%= property %>, and if lower, keep it the same.",
function (card, lastCards, allCards, options) {
var lastNCard = lastCards[lastCards.length - options.n];
var lastWasHigher = lastNCard.value > options.min;
if (lastWasHigher) {
return chai.expect(card)
.to.have.property(options.property)
.not.equals(lastNCard[options.property]);
} else {
return chai.expect(card)
.to.have.property(options.property)
.equals(lastNCard[options.property]);
}
var matches2 = lastNCard.suit === lastNCard.suit;
return ((matchesColor && !matches2) || (!matchesColor && matches2));
}, {
n: 1,
min: 7,
property: 'suit',
}, {
property: {
description: 'The property to compare to the lastNCard',
possibleVals: ['suit', 'face', 'royal', 'color'],
type: 'String'
},
n: {
description: 'Number for how many cards back. Start counting at 1',
possibleVals: [1, 2, 3],
type: 'Number'
},
min: {
description: 'The min difference to the last nth card',
possibleVals: [5, 6, 7, 8, 9],
type: 'Number'
},
}, [
'This rule does not involve the <%= nUnused %><%=nth(nUnused)%> last card',
'This rule does not involve <%= propertyUnused %>',
'This rule does involve value',
'This rule does a minimum value',
'This rule does involve <%= property %>',
'This rule does involve the <%= n %><%=nth(n)%> last card',
]
),
new Rule(
"If the last <%= n %><%=nth(n)%> card was a <%= property %> card, play a higher value card otherwise lower.",
function (card, lastCards, allCards, options) {
var lastNCard = lastCards[lastCards.length - options.n];
var lastHadProperty = lastNCard[options.property];
if (lastHadProperty) {
return chai.expect(card)
.to.have.property('value')
.above(lastNCard[options.value]);
} else {
return chai.expect(card)
.to.have.property('value')
.lessThan(lastNCard[options.value]);
}
}, {
n: 1,
property: 'face',
}, {
property: {
description: 'The property to compare to the lastNCard',
possibleVals: ['face', 'royal', 'spade', 'club', 'diamond', 'heart', 'red', 'black'],
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 the <%= nUnused %><%=nth(nUnused)%> last card',
'This rule does not check for <%= propertyUnused %> cards',
'This rule does involve <%= property %>',
'This rule wants you to play a higher card in some circumstances',
'This rule does involve the <%= n %><%=nth(n)%> last card',
]
)
);
);
return {
Rule: Rule,
rules: rules,
+1063 -59
View File
File diff suppressed because it is too large Load Diff
+32 -1
View File
@@ -2,6 +2,37 @@
/* jasmine specs for directives go here */
describe('directives', function() {
describe('directives', function () {
var $compile,
$rootScope;
// Load the myApp module, which contains the directive
beforeEach(module('scienceAlchemy'));
// beforeEach(module('Rules'));
// Store references to $rootScope and $compile
// so they are available to all tests in this describe block
beforeEach(inject(function (_$compile_, _$rootScope_) {
// The injector unwraps the underscores (_) from around the parameter names when matching
$compile = _$compile_;
$rootScope = _$rootScope_;
}));
it('Replaces the element with the appropriate content', function () {
// define a rule for testing
$rootScope.rule = Rules.rules[0];
// Compile a piece of HTML containing the directive
var element = $compile('<div cfs-rule="rule" ng-model="rule"></div>')($rootScope);
// fire all the watches, so the scope expressions will be evaluated
// $rootScope.$digest();
$rootScope.$apply();
// Check that the compiled element contains the templated content
var html = element.html();
expect(html).toContain("<select");
expect(html).toContain("<option");
expect(html).toContain("Next card must not have the same");
});
});
+64 -10
View File
@@ -47,7 +47,7 @@ describe('Rules', function () {
});
});
Rules.rules.forEach(function(rule){
describe('rule', function () {
describe('rule: "'+rule.describe()+'"', function () {
it('should test', function () {
var res = rule.test(card,lastCards,allCards);
Boolean(res);
@@ -64,7 +64,7 @@ describe('Rules', function () {
});
it('should describeVariations', function () {
var desc = rule.describeVariations();
expect(typeof desc).toBe('string');
expect(desc instanceof Array).toBe(true);
expect(desc).not.toContain(/[{}]+/);
});
it('should genHints', function () {
@@ -79,20 +79,34 @@ describe('Rules', function () {
expect(typeof opts).toBe('object');
expect(opts).not.toEqual(opts1);
});
it('should randomize', function () {
var opts1 = rule.options;
var opts = rule.randomize();
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);
var def = rule.optionDefaults[option];
describe('option: '+option, function () {
for (var i = 0; i < vals.length; i++) {
it('should test with val: '+vals[i],function(){
var options={};
options[option]=vals[i];
rule.setOptions(options);
var res = rule.test(card,lastCards,allCards);
expect(typeof res).toBe('boolean');
});
}
it('default should be in possible vals '+option+' = '+vals[i],function(){
expect(vals).toEqual(jasmine.arrayContaining([def]));
});
}
});
});
@@ -100,3 +114,43 @@ describe('Rules', function () {
});
});
});
describe('Rules compilation', function () {
var $compile,
$rootScope;
beforeEach(
module('scienceAlchemy')
);
// Store references to $rootScope and $compile
// so they are available to all tests in this describe block
beforeEach(inject(function (_$compile_, _$rootScope_) {
// The injector unwraps the underscores (_) from around the parameter names when matching
$compile = _$compile_;
$rootScope = _$rootScope_;
}));
Rules.rules.forEach(function(rule){
describe('rule: "'+rule.describe()+'"', function () {
it('should render using cfs-rule directive', function () {
// define a rule for testing
$rootScope.rule = Rules.rules[0];
// Compile a piece of HTML containing the directive
var element = $compile('<div cfs-rule="rule" ng-model="rule"></div>')($rootScope);
// fire all the watches, so the scope expressions will be evaluated
// $rootScope.$digest();
$rootScope.$apply();
// Check that the compiled element contains the templated content
var html = element.html();
expect(html).toContain("<select");
expect(html).toContain("<option");
expect(html).toContain("Next card must not have the same");
});
});
});
});
+6 -5
View File
@@ -5,6 +5,7 @@ describe('services', function () {
//load modules
beforeEach(module('scienceAlchemy'));
// Test service availability
describe('game', function () {
it('should exist', inject(function (game) {
@@ -12,11 +13,11 @@ describe('services', function () {
}));
});
describe('detector', function () {
it('should exist', inject(function (detector) {
expect(detector).toBeDefined();
}));
});
// describe('detector', function () {
// it('should exist', inject(function (detector) {
// expect(detector).toBeDefined();
// }));
// });
describe('lab', function () {
it('should exist', inject(function (lab) {