Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #83 from doug-martin/master

v0.2.0
  • Loading branch information...
commit 00332c012d2ba88f5f8ff0c7bfc691cc2352302a 2 parents aa6cce7 + e58883d
@doug-martin doug-martin authored
Showing with 7,013 additions and 1,109 deletions.
  1. +0 −4 benchmark/manners/manners.nools
  2. +14 −0 benchmark/sendMoreMoney/benchmark.js
  3. +18 −0 benchmark/sendMoreMoney/sendMoreMoney.nools
  4. +2 −2 benchmark/waltzDb/waltzDb.nools
  5. +1 −0  docs-md/examples.md
  6. +12 −0 docs/History.html
  7. +1 −0  docs/examples.html
  8. +3 −0  docs/examples/browser/assets/array-extended.js
  9. +17 −0 docs/examples/browser/assets/conways.css
  10. +2 −0  docs/examples/browser/rules/conways3d.nools
  11. +377 −0 docs/examples/browser/rules/sudoku.nools
  12. +368 −0 docs/examples/browser/src/sudoku.js
  13. +90 −0 docs/examples/browser/src/sudokuPatterns.js
  14. +146 −0 docs/examples/browser/sudoku.html
  15. +131 −9 docs/index.html
  16. +8 −8 docs/nools.js
  17. +3 −0  examples/browser/assets/array-extended.js
  18. +17 −0 examples/browser/assets/conways.css
  19. +2 −0  examples/browser/rules/conways3d.nools
  20. +377 −0 examples/browser/rules/sudoku.nools
  21. +368 −0 examples/browser/src/sudoku.js
  22. +90 −0 examples/browser/src/sudokuPatterns.js
  23. +146 −0 examples/browser/sudoku.html
  24. +2 −2 examples/counter/counter.nools
  25. +3 −0  examples/counter/index.js
  26. +121 −0 examples/sudoku/index.js
  27. +386 −0 examples/sudoku/lib/rules/sudoku.nools
  28. +42 −0 examples/sudoku/lib/rules/validate.nools
  29. +332 −0 examples/sudoku/lib/sudoku.js
  30. +11 −0 history.md
  31. +33 −92 lib/agenda.js
  32. +54 −0 lib/conflict.js
  33. +28 −0 lib/constraint.js
  34. +14 −1 lib/context.js
  35. +38 −1 lib/extended.js
  36. +1 −2  lib/flow.js
  37. +1 −1  lib/linkedList.js
  38. +6 −2 lib/nodes/aliasNode.js
  39. +36 −1 lib/nodes/equalityNode.js
  40. +218 −0 lib/nodes/fromNode.js
  41. +165 −0 lib/nodes/fromNotNode.js
  42. +59 −32 lib/nodes/index.js
  43. +218 −69 lib/nodes/joinNode.js
  44. +12 −9 lib/nodes/joinReferenceNode.js
  45. +1 −1  lib/nodes/leftAdapterNode.js
  46. +11 −8 lib/nodes/node.js
  47. +207 −120 lib/nodes/notNode.js
  48. +16 −0 lib/nodes/propertyNode.js
  49. +1 −1  lib/nodes/rightAdapterNode.js
  50. +8 −13 lib/nodes/terminalNode.js
  51. +9 −3 lib/nodes/typeNode.js
  52. +1 −0  lib/parser/constraint/grammar.js
  53. +20 −18 lib/parser/constraint/parser.js
  54. +1 −1  lib/parser/nools/nool.parser.js
  55. +15 −1 lib/parser/nools/tokens.js
  56. +127 −75 lib/pattern.js
  57. +98 −20 lib/rule.js
  58. +56 −14 lib/workingMemory.js
  59. +1,518 −547 nools.js
  60. +8 −8 nools.min.js
  61. +1 −1  package.json
  62. +182 −11 readme.md
  63. +3 −1 test/constraintMatcher.test.js
  64. +571 −28 test/flow.test.js
  65. +63 −0 test/issues.test.js
  66. +60 −0 test/noolsParser.test.js
  67. +63 −3 test/rules.test.js
View
4 benchmark/manners/manners.nools
@@ -106,7 +106,6 @@ rule findSeating {
not(chosen : Chosen chosen.id == sid && chosen.guestName == leftGuestName && chosen.hobby == rightGuestHobby);
}
then {
- console.log("findSeating");
assert(new Seating({
id : countValue,
pid : sid,
@@ -132,7 +131,6 @@ rule makePath {
not( p2 : Path p2.id == sid && p2.guestName == pathGuestName);
}
then {
- console.log("makePath");
assert(new Path({id : sid, seat : pathSeat, guestName : pathGuestName}));
}
}
@@ -143,7 +141,6 @@ rule pathDone {
s : Seating s.path == false;
}
then {
- console.log("pathDone");
modify(s, function(){
this.path = true;
});
@@ -171,7 +168,6 @@ rule continue {
c : Context c.state == 'check';
}
then {
- console.log("continue");
modify(c, function(){this.state = 'assign'});
}
}
View
14 benchmark/sendMoreMoney/benchmark.js
@@ -0,0 +1,14 @@
+"use strict";
+
+var nools = require("../../index"),
+ flow = nools.compile(__dirname + "/sendMoreMoney.nools");
+
+var start = new Date(),
+ session;
+console.log("starting");
+(session = flow.getSession(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)).match().then(function () {
+ console.log("%dms", +(new Date()) - start);
+ session.dispose();
+});
+
+
View
18 benchmark/sendMoreMoney/sendMoreMoney.nools
@@ -0,0 +1,18 @@
+rule SendMoreMoney {
+ when {
+ s : Number s != 0;
+ e : Number e != s;
+ n : Number n != s && n != e;
+ d : Number d != s && d != e && d != n;
+ m : Number m != 0 && m != s && m != e && m != n && m != d;
+ o : Number o != s && o != e && o != n && o != d && o != m;
+ r : Number r != s && r != e && r != n && r != d && r != m && r != o;
+ y : Number y != s && y != e && y != n && y != d && y != m && y != o && y != r
+ && (s*1000 + e*100 + n*10 + d + m*1000 + o*100 + r*10 + e) == (m*10000 + o*1000 + n*100 + e*10 + y);
+ }
+ then {
+ console.log('pathDone', {s : s, e : e, n : n, d : d, m : m, o: o, r : r, y : y});
+ console.log((s*1000 + e*100 + n*10 + d + m*1000 + o*100 + r*10 + e));
+ console.log((m*10000 + o*1000 + n*100 + e*10 + y))
+ }
+}
View
4 benchmark/waltzDb/waltzDb.nools
@@ -829,7 +829,7 @@ rule visit_2j_2 {
// ; (write edge_label <bp> <p3> <n3> <id> (crlf))
// (make edge_label ^p1 <bp> ^p2 <p1> ^l_name <n1> ^l_id <id>)
// (make edge_label ^p1 <bp> ^p2 <p2> ^l_name <n2> ^l_id <id>))
-rule visit_2j_0 {
+rule visit_2j_3 {
when {
s : Stage s.value == 'VISITING_2J';
j : Junction j.visited == 'now' {name : name, basePoint : basePoint, p1 : p1, p2 : p2};
@@ -922,7 +922,7 @@ rule start_checking {
// -->
// (modify 1 ^value remove_label)
// (make illegal ^bp <bp> ^l_id <id>))
-rule checking1 {
+rule checking {
when {
stage : Stage stage.value == 'CHECKING';
junction : Junction junction.visited == 'check' {basePoint : basePoint};
View
1  docs-md/examples.md
@@ -2,6 +2,7 @@
* [Conways 2D](./examples/browser/conways_2d.html)
* [Conways 3D](./examples/browser/conways_3d.html)
+ * [Sudoku](./examples/browser/sudoku.html)
* [Fibonacci](./examples/browser/fibonacci.html)
* [Miss Manners](./examples/browser/manners.html)
* [Diagnosis](./examples/browser/diagnose.html)
View
12 docs/History.html
@@ -178,6 +178,18 @@
+<h1>0.2.0 / 2013-10-14</h1>
+<ul>
+<li>Nools now supports true modify!!!<ul>
+<li>This is a major leap forward for <code>nools</code> opening the door for more complex actions and expressions in the rules.</li>
+</ul>
+</li>
+<li>Added support from for <code>from</code> conditions in the <code>rhs</code>.</li>
+<li>Fixed issue <a href="https://github.com/C2FO/nools/issues/81">#81</a>.</li>
+<li>Fixed issue <a href="https://github.com/C2FO/nools/issues/82">#82</a>.</li>
+<li>Added new <code>sudoku</code> web example.</li>
+<li>Added <a href="http://en.wikipedia.org/wiki/Verbal_arithmetic">Send More Money</a> benchmark see <a href="https://github.com/C2FO/nools/issues/78">#78</a>.</li>
+</ul>
<h1>0.1.14</h1>
<ul>
<li>Fixed issue with async actions and early match termination.</li>
View
1  docs/examples.html
@@ -182,6 +182,7 @@
<ul>
<li><a href="./examples/browser/conways_2d.html">Conways 2D</a></li>
<li><a href="./examples/browser/conways_3d.html">Conways 3D</a></li>
+<li><a href="./examples/browser/sudoku.html">Sudoku</a></li>
<li><a href="./examples/browser/fibonacci.html">Fibonacci</a></li>
<li><a href="./examples/browser/manners.html">Miss Manners</a></li>
<li><a href="./examples/browser/diagnose.html">Diagnosis</a></li>
View
3  docs/examples/browser/assets/array-extended.js
@@ -0,0 +1,3 @@
+/*! array-extended - v0.0.9 - 2013-06-17
+ * Copyright (c) 2013 Doug Martin; Licensed MIT */
+!function(){"use strict";function a(a,b,c){function d(a,b){return o(b,function(b,c){return N(c)||(c=[c]),c.unshift(a),b.unshift(c),b},[])}function e(a,b,c){for(var d=[],e=0;e<b.length;e++)d.push([a].concat(y(b,e)).slice(0,c));return d}function f(a,b){var c,d,e=[],f=-1;for(d=a.length;++f<d;)c=a[f],-1!==g(b,c)&&e.push(c);return e}function g(a,b,c){for(var d=(c||0)-1,e=a.length;++d<e;)if(a[d]===b)return d;return-1}function h(a,b){if(!N(a))throw new TypeError;var c=Object(a),d=c.length>>>0;if(0===d)return-1;var e=d;arguments.length>2&&(e=Number(arguments[2]),e!==e?e=0:0!==e&&e!==1/0&&e!==-(1/0)&&(e=(e>0||-1)*P(Q(e))));for(var f=e>=0?R(e,d-1):d-Q(e);f>=0;f--)if(f in c&&c[f]===b)return f;return-1}function i(a,b,c){if(a&&X&&X===a.filter)return a.filter(b,c);if(!N(a)||"function"!=typeof b)throw new TypeError;for(var d=Object(a),e=d.length>>>0,f=[],g=0;e>g;g++)if(g in d){var h=d[g];b.call(c,h,g,d)&&f.push(h)}return f}function j(a,b,c){if(!N(a)||"function"!=typeof b)throw new TypeError;if(a&&T&&T===a.forEach)return a.forEach(b,c),a;for(var d=0,e=a.length;e>d;++d)b.call(c||a,a[d],d,a);return a}function k(a,b,c){if(a&&Y&&Y===a.every)return a.every(b,c);if(!N(a)||"function"!=typeof b)throw new TypeError;for(var d=Object(a),e=d.length>>>0,f=0;e>f;f++)if(f in d&&!b.call(c,d[f],f,d))return!1;return!0}function l(a,b,c){if(a&&Z&&Z===a.some)return a.some(b,c);if(!N(a)||"function"!=typeof b)throw new TypeError;for(var d=Object(a),e=d.length>>>0,f=0;e>f;f++)if(f in d&&b.call(c,d[f],f,d))return!0;return!1}function m(a,b,c){if(a&&U&&U===a.map)return a.map(b,c);if(!N(a)||"function"!=typeof b)throw new TypeError;for(var d=Object(a),e=d.length>>>0,f=[],g=0;e>g;g++)g in d&&f.push(b.call(c,d[g],g,d));return f}function n(a,b,c){var d=arguments.length>2;if(a&&V&&V===a.reduce)return d?a.reduce(b,c):a.reduce(b);if(!N(a)||"function"!=typeof b)throw new TypeError;var e=0,f=a.length>>0;if(arguments.length<3){if(0===f)throw new TypeError("Array length is 0 and no second argument");c=a[0],e=1}else c=arguments[2];for(;f>e;)e in a&&(c=b.call(void 0,c,a[e],e,a)),++e;return c}function o(a,b,c){var d=arguments.length>2;if(a&&W&&W===a.reduceRight)return d?a.reduceRight(b,c):a.reduceRight(b);if(!N(a)||"function"!=typeof b)throw new TypeError;var e=Object(a),f=e.length>>>0;if(0===f&&2===arguments.length)throw new TypeError;var g=f-1;if(arguments.length>=3)c=arguments[2];else for(;;)if(g in a){c=a[g--];break}for(;g>=0;)g in e&&(c=b.call(void 0,c,e[g],g,e)),g--;return c}function p(a){var c=[];if(null!==a){var d=$(arguments);if(1===d.length)if(N(a))c=a;else if(b.isHash(a))for(var e in a)a.hasOwnProperty(e)&&c.push([e,a[e]]);else c.push(a);else j(d,function(a){c=c.concat(p(a))})}return c}function q(a){return a=a||[],a.length?n(a,function(a,b){return a+b}):0}function r(a){if(a=a||[],a.length){var c=q(a);if(b.isNumber(c))return c/a.length;throw new Error("Cannot average an array of non numbers.")}return 0}function s(a,b){return _(a,b)}function t(a,b){return _(a,b)[0]}function u(a,b){return _(a,b)[a.length-1]}function v(a){var b=a,c=J($(arguments,1));return N(a)&&(b=i(a,function(a){return-1===g(c,a)})),b}function w(a){var b,c=[],d=-1,e=0;if(a)for(b=a.length;++d<b;){var f=a[d];-1===g(c,f)&&(c[e++]=f)}return c}function x(a){return w(a)}function y(a,b){var c=a.slice();return"number"!=typeof b&&(b=1),b&&N(a)?(b>0?(c.push(c.shift()),b--):(c.unshift(c.pop()),b++),y(c,b)):c}function z(a,b){var c=[];if(N(a)){var d=a.slice(0);"number"!=typeof b&&(b=a.length),b?b<=a.length&&(c=n(a,function(a,c,f){var g;return g=b>1?e(c,y(d,f).slice(1),b):[[c]],a.concat(g)},[])):c=[[]]}return c}function A(){var a=[],c=$(arguments);if(c.length>1){var d=c.shift();N(d)&&(a=n(d,function(a,d,e){for(var f=[d],g=0;g<c.length;g++){var h=c[g];N(h)&&!b.isUndefined(h[e])?f.push(h[e]):f.push(null)}return a.push(f),a},[]))}return a}function B(a){var b=[];if(N(a)&&a.length){var c;j(a,function(a){!N(a)||c&&a.length!==c.length||(j(a,function(a,c){b[c]||(b[c]=[]),b[c].push(a)}),c=a)})}return b}function C(a,b){var c=[];if(b=$(arguments),a=b.shift(),N(a)&&b.length)for(var d=0,e=b.length;e>d;d++)c.push(a[b[d]]||null);return c}function D(){var a=[],b=$(arguments);if(b.length>1){for(var c=0,d=b.length;d>c;c++)a=a.concat(b[c]);a=w(a)}return a}function E(){var a,b,c=[],d=-1;if(a=arguments.length>1?$(arguments):arguments[0],N(a))for(c=a[0],d=0,b=a.length;++d<b;)c=f(c,a[d]);return w(c)}function F(a){var b=[];return N(a)&&a.length&&(b=n(a,function(a,b){var c=m(a,function(a){return a.concat(b)});return a.concat(c)},[[]])),b}function G(a,b){var c=[];return N(a)&&N(b)&&a.length&&b.length&&(c=d(a[0],b).concat(G(a.slice(1),b))),c}function H(a){var c=[];return N(a)&&a.length&&(c=i(a,function(a){return!b.isUndefinedOrNull(a)})),c}function I(a,c){c=b.isNumber(c)?c:1,c||(c=1),a=p(a||[]);for(var d=[],e=0;++e<=c;)d=d.concat(a);return d}function J(a){var b,c=$(arguments);return b=c.length>1?c:p(a),n(b,function(a,b){return a.concat(b)},[])}function K(a,b){b=b.split(".");var c=a.slice(0);return j(b,function(a){var b=a.match(/(\w+)\(\)$/);c=m(c,function(c){return b?c[b[1]]():c[a]})}),c}function L(a,b,c){return c=$(arguments,2),m(a,function(a){var d=M(b)?a[b]:b;return d.apply(a,c)})}var M=b.isString,N=Array.isArray||b.isArray,O=b.isDate,P=Math.floor,Q=Math.abs,R=(Math.max,Math.min),S=Array.prototype,T=(S.indexOf,S.forEach),U=S.map,V=S.reduce,W=S.reduceRight,X=S.filter,Y=S.every,Z=S.some,$=c.argsToArray,_=function(){var a=function(a,b){return k(a,b)},b=function(a,b){return a-b},c=function(a,b){return a.getTime()-b.getTime()};return function(d,e){var f=[];return N(d)&&(f=d.slice(),e?"function"==typeof e?f.sort(e):f.sort(function(a,b){var c=a[e],d=b[e];return M(c)&&M(d)?c>d?1:d>c?-1:0:O(c)&&O(d)?c.getTime()-d.getTime():c-d}):a(f,M)?f.sort():a(f,O)?f.sort(c):f.sort(b)),f}}(),ab={toArray:p,sum:q,avg:r,sort:s,min:t,max:u,difference:v,removeDuplicates:w,unique:x,rotate:y,permutations:z,zip:A,transpose:B,valuesAt:C,union:D,intersect:E,powerSet:F,cartesian:G,compact:H,multiply:I,flatten:J,pluck:K,invoke:L,forEach:j,map:m,filter:i,reduce:n,reduceRight:o,some:l,every:k,indexOf:g,lastIndexOf:h};return a.define(N,ab).expose(ab)}"undefined"!=typeof exports?"undefined"!=typeof module&&module.exports&&(module.exports=a(require("extended"),require("is-extended"),require("arguments-extended"))):"function"==typeof define&&define.amd?define(["extended","is-extended","arguments-extended"],function(b,c,d){return a(b,c,d)}):this.arrayExtended=a(this.extended,this.isExtended,this.argumentsExtended)}.call(this);
View
17 docs/examples/browser/assets/conways.css
@@ -152,6 +152,23 @@ input[type="submit"], input[type="button"] {
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.2);
}
+.sudoku .col {
+ width: 50px;
+ height: 50px;
+ font-size: 30px;
+ vertical-align: middle;
+ text-align: center;
+}
+
+.sudoku .col.error {
+ background-color: rgba(255, 0, 0, 0.43);
+}
+
+.sudoku #grid{
+ width: 465px;
+ height: 470px;
+}
+
.col.live {
background-color: black;
}
View
2  docs/examples/browser/rules/conways3d.nools
@@ -71,6 +71,8 @@ rule Transition {
if(cell.transition()){
emit("cell-transition", cell);
transitioned = true;
+ }else{
+ retract(cell);
}
});
emit("evaluate");
View
377 docs/examples/browser/rules/sudoku.nools
@@ -0,0 +1,377 @@
+
+rule "terminate group" {
+ salience: -100;
+ agenda-group: "validate";
+ when {
+ $state: String $state == "validate";
+ }
+ then{
+ console.log("Validation complete." );
+ retract($state);
+ halt();
+ }
+}
+
+
+
+rule "duplicate in cell col" {
+ agenda-group: "validate";
+ when{
+ $c: Cell $c.value;
+ $e: Cell $e.value == $c.value from $c.exCells;
+ }
+ then {
+ emit("invalid", $c);
+ emit("invalid", $e);
+ }
+}
+
+
+rule "halt after setup" {
+ when{
+ $ctr: Counter $ctr.count == 0;
+ }
+ then{
+ console.log("halt after setup");
+ halt();
+ }
+}
+
+
+rule "emergency halt 2" {
+ salience: 100;
+ when{
+ $c: Cell !$c.value && $c.count == 0;
+ }
+ then{
+ console.log("emergency halt cell without a value and no free values");
+ halt();
+ //process.exit();
+ }
+
+}
+
+// A Setting object is inserted to define the value of a Cell.
+// Rule "set a value" updates the cell and all cell groups containing it.
+rule "set a value"{
+ when{
+ // a Setting with row and column number, and a value
+ $s: Setting {rowNo: $rn, colNo: $cn, value: $v};
+
+ // a matching Cell, with no value set
+ $c: Cell $c.rowNo == $rn && $c.colNo == $cn && !$c.value {cellRow: $cr, cellCol: $cc, cellSqr: $cs};
+ }
+ then {
+ // modify the Cell by setting its value
+ modify( $c, function(){ this.blockExcept(); this.value = $v;});
+ emit("set-value", $c);
+ console.log( "set cell " + $c.toString() );
+ modify( $cr, function(){ this.blockValue( $v ) });
+ modify( $cc, function(){ this.blockValue( $v ) });
+ modify( $cs, function(){ this.blockValue( $v ) });
+ }
+}
+
+
+// Rule for removing a value from all cells that are siblings
+// in one of the three cell groups.
+rule "eliminate a value from Cell" {
+ salience: 1;
+ when{
+ // a Setting with row and column number, and a value
+ $s: Setting {rowNo: $rn, colNo: $cn, value: $v};
+
+ // the matching Cell, with the value already set
+ $mc : Cell $mc.rowNo == $rn && $mc.colNo == $cn && $mc.value == $v {exCells: $exCells};
+
+ // for all Cells that are in cahoots with the updated cell
+ $c: Cell $v in $c.free from $exCells;
+ }
+ then{
+ console.log( "clear " + $v + " from cell " + $c.posAsString() + " because of " + $mc.posAsString() );
+ // modify a related Cell by blocking the assigned value
+ modify( $c, function(){ this.blockValue( $v ); });
+ }
+}
+
+//Rule for elimination the Setting fact.
+rule "retract setting"{
+ when {
+ // a Setting with row and column number, and a value
+ $s: Setting {rowNo: $rn, colNo: $cn, value: $v};
+
+ // the matching Cell, with the value already set
+ $c: Cell $c.rowNo == $rn && $c.colNo == $cn && $c.value == $v;
+
+ // This is the negation of the last pattern in the previous rule.
+ // Now the Setting fact can be safely retracted.
+ not( $x: Cell $v in $x.free && $x in $c.exCells);
+ // count down
+ $ctr: Counter {count: $count};
+
+ }
+ then{
+ console.log("retracting " + $s.toString());
+ // Discard the Setter fact.
+ retract( $s );
+ console.log("COUNT = %s", $count);
+ modify( $ctr, function(){ this.count = $count - 1;});
+ console.log( "done setting cell " + $c.toString() );
+ }
+}
+
+
+// Detect a set of candidate values with cardinality 1 for some Cell.
+// This is the value to be set.
+rule "single"{
+ when {
+ // There is currently no setting under way.
+ not($ns: Setting);
+
+ // One element in the "free" set.
+ $c: Cell $c.count == 1 {rowNo: $rn, colNo: $cn};
+ }
+ then {
+ var i = $c.freeValue();
+ console.log( "single " + i + " at " + $c.posAsString() );
+ // Insert another Setter fact.
+ assert( new Setting( $rn, $cn, i ) );
+ }
+}
+
+// Detect a set of candidate values with a value that is the only one
+// in one of its groups. This is the value to be set.
+rule "hidden single" {
+ when{
+ // There is currently no setting under way.
+ not($ns: Setting);
+ not($nc: Cell $nc.count == 1 );
+
+ // Some integer.
+ $i: Number;
+
+ // The "free" set contains this number.
+ $c: Cell $c.count > 1 && $i in $c.free {rowNo: $rn, colNo: $cn};
+
+ // We have a cell group containing this cell $c
+ $cg: CellGroup $c in $cg.cells;
+ // but no other cell from that group contains $i
+ not ($nc2: Cell $nc2 != $c && $i in $nc2.free from $cg.cells);
+ }
+ then {
+ console.log( "hidden single " + $i + " at " + $c.posAsString() );
+ // Insert another Setter fact.
+ assert( new Setting( $rn, $cn, $i ) );
+ }
+}
+
+
+// A "naked pair" is two cells in some cell group with their sets of
+// permissible values being equal with cardinality 2. These two values
+// can be removed from all other candidate lists in the group.
+rule "naked pair" {
+ when {
+ // There is currently no setting under way.
+ not($ns: Setting);
+ not($nc: Cell $nc.count == 1 );
+
+
+ // One cell with two candidates.
+ $c1: Cell $c1.count == 2 {free: $f1, cellRow: $r1, rowNo: $rn1, colNo: $cn1, cellSqr: $b1};
+
+ // The containing cell group.
+ $cg: CellGroup $cg.count > 2 && $c1 in $cg.cells;
+
+ // Another cell with two candidates, not the one we already have.
+ $c2: Cell( $c2 != $c1 && deepEqual($c2.free, $f1) ) from $cg.cells;
+
+ // Get one of the "naked pair".
+ $v: Number from $c1.free;
+
+ // Get some other cell with a candidate equal to one from the pair.
+ $c3: Cell $c3 != $c1 && $c3 != $c2 && $c3.count > 1 && $v in $c3.free from $cg.cells;
+ }
+ then{
+ console.log( "remove " + $v + " from " + $c3.posAsString() + " due to naked pair at " + $c1.posAsString() + ":[" + $f1.join(",") + "] and " + $c2.posAsString() + ":[" + $c2.free.join(",") + "]" );
+ // Remove the value.
+ modify( $c3, function(){ this.blockValue( $v ); });
+ }
+}
+
+// If two cells within the same cell group contain candidate sets with more than
+// two values, with two values being in both of them but in none of the other
+// cells, then we have a "hidden pair". We can remove all other candidates from
+// these two cells.
+//
+rule "hidden pair in row" {
+ when{
+ // There is currently no setting under way.
+ not($ns: Setting);
+ not($nc: Cell $nc.count == 1 );
+
+ // Establish a pair of Integer facts
+ $i1: Number;
+ $i2: Number $i2 > $i1;
+
+ // Look for a Cell with these two among its candidates. (The upper bound on
+ // the number of candidates avoids a lot of useless work during startup.)
+ $c1: Cell $c1.count > 2 && $c1.count < 9 && $i1 in $c1.free && $i2 in $c1.free {rowNo: $rn1, colNo: $cn1, cellRow: $cellRow};
+
+ // Get another one from the same row, with the same pair among its candidates,
+ $c2: Cell $c2 != $c1 && $c2.cellRow == $cellRow && $c2.count > 2 && $i1 in $c2.free && $i2 in $c2.free;
+
+ // Assertain that no other cell in the group has one of these two values.
+ not($nc2: Cell $nc2 != $c1 && $nc2 != $c2 && ($i1 in $nc2.free || $i2 in $nc2.free ) from $cellRow.cells)
+ }
+ then{
+ console.log( "hidden pair in row at " + $c1.posAsString() + " and " + $c2.posAsString() );
+ // Set the candidate lists of these two Cells to the "hidden pair".
+ modify( $c1, function(){this.blockExcept([$i1, $i2]) });
+ modify( $c2, function(){this.blockExcept([$i1, $i2]) });
+ }
+}
+
+rule "hidden pair in column"{
+ when{
+ not($ns: Setting);
+ not($nc: Cell $nc.count == 1 );
+
+ $i1: Number;
+ $i2: Number $i2 > $i1;
+
+ $c1: Cell $c1.count > 2 && $c1.count < 9 && $i1 in $c1.free && $i2 in $c1.free {rowNo: $rn1, colNo: $cn1, cellCol: $cellCol};
+ $c2: Cell $c2 != $c1 && $c2.cellCol == $cellCol && $c2.count > 2 && $i1 in $c2.free && $i2 in $c2.free;
+ not($nc2: Cell $nc2 != $c1 && $nc2 != $c2 && ($i1 in $nc2.free || $i2 in $nc2.free ) from $cellCol.cells)
+ }
+ then{
+ console.log( "hidden pair in column at " + $c1.posAsString() + " and " + $c2.posAsString() );
+ modify( $c1, function(){this.blockExcept([$i1, $i2]) });
+ modify( $c2, function(){this.blockExcept([$i1, $i2]) });
+ }
+}
+
+rule "hidden pair in square" {
+ when{
+ not($ns: Setting);
+ not($nc: Cell $nc.count == 1 );
+
+ $i1: Number;
+ $i2: Number $i2 > $i1;
+
+ $c1: Cell $c1.count > 2 && $c1.count < 9 && $i1 in $c1.free && $i2 in $c1.free {rowNo: $rn1, colNo: $cn1, cellSqr: $cellSqr};
+ $c2: Cell $c2 != $c1 && $c2.cellSqr == $cellSqr && $c2.count > 2 && $i1 in $c2.free && $i2 in $c2.free;
+
+ not($nc2: Cell $nc2 != $c1 && $nc2 != $c2 && ($i1 in $nc2.free || $i2 in $nc2.free ) from $cellSqr.cells)
+ }
+ then{
+ console.log( "hidden pair in square " + $c1.posAsString() + " and " + $c2.posAsString() );
+ modify( $c1, function(){this.blockExcept([$i1, $i2]) });
+ modify( $c2, function(){this.blockExcept([$i1, $i2]) });
+ }
+}
+
+
+rule "X-wings in rows" {
+ when{
+ not($ns: Setting);
+ not($nc: Cell $nc.count == 1 );
+
+ $i: Number;
+
+ $ca1: Cell $ca1.count > 1 && $i in $ca1.free {cellRow: $ra, rowNo: $rano, cellCol: $c1,colNo: $c1no};
+ $cb1: Cell $cb1.count > 1 && $i in $cb1.free && $cb1.rowNo > $rano && $cb1.cellCol == $c1 {cellRow: $rb, rowNo: $rbno};
+ not($nc2: Cell $nc2 != $ca1 && $nc2 != $cb1 && $i in $nc2.free from $c1.cells);
+
+ $ca2: Cell $ca2.count > 1 && $1 in $ca2.free && $ca2.cellRow == $ra && $ca2.colNo > $c1no {cellCol: $c2, colNo: $c2no};
+ $cb2: Cell $cb2.count > 1 && $i in $cb2.free && $cb2.cellRow == $rb && $cb2.cellCol == $c2;
+ not($nc3: Cell $nc3 != $ca2 && $nc3 != $cb2 && $i in $nc3.free from $c2.cells);
+
+ $cx: Cell ($cx.rowNo == $rano || $cx.rowNo == $rbno) && $cx.colNo != $c1no && $cx.colNo != $c2no && $cx.count > 1 && $i in $cx.free;
+ }
+ then{
+
+ console.log( "X-wing with " + $i + " in rows " +
+ $ca1.posAsString() + " - " + $cb1.posAsString() +
+ $ca2.posAsString() + " - " + $cb2.posAsString() + ", remove from " + $cx.posAsString() );
+
+ modify( $cx, function(){ this.blockValue( $i ) });
+ }
+}
+
+rule "X-wings in columns"{
+ when{
+ not($ns: Setting);
+ not($nc: Cell $nc.count == 1 );
+
+ $i: Number;
+
+
+ $ca1: Cell $ca1.count > 1 && $i in $ca1.free {cellRow: $ra, rowNo: $rano, cellCol: $c1,colNo: $c1no};
+
+ $ca2: Cell $ca2.count > 1 && $i in $ca2.free && $ca2.colNo > $c1No && $ca2.cellRow == $ra {cellCol: $c2, colNo: $c2no};
+ not( $nc2: Cell $nc2 != $ca1 && $nc2 != $ca2 && $i in $nc2.free from $ra.cells);
+
+ $cb1: Cell $cb1.count > 1 && $i in $cb1.free && $cb1.cellCol == $c1 && $cb1.rowNo > $rano {cellRow: $rb, rowNo: $rbno};
+ $cb2: Cell $cb2.count > 1 && $i in $cb2.free && $cb2.cellCol == $c2 && $cb2.cellRow == $rb;
+ not($nc3: Cell $nc3 != $cb1 && $nc3 != $cb2 && $i in $nc3.free from $rb.cells);
+
+ $cx: Cell ($cx.colNo == $c1no || $cx.colNo == $c2no) && $cx.rowNo != $rano && $cx.rowNo != $rbno && $cx.count > 1 && $i in $cx.free;
+ }
+ then{
+
+ console.log( "X-wing with " + $i + " in columns " +
+ $ca1.posAsString() + " - " + $ca2.posAsString() +
+ $cb1.posAsString() + " - " + $cb2.posAsString() + ", remove from " + $cx.posAsString() );
+
+ modify( $cx, function(){this.blockValue( $i ) });
+ }
+}
+
+rule "intersection removal column" {
+ when {
+ not($ns: Setting);
+ not($nc: Cell $nc.count == 1 );
+
+ $i: Number;
+
+ // occurs in a Cell
+ $c: Cell $i in $c.free {cellSqr: $cs, cellCol: $cc};
+ // but not in another cell of the same square and a different column
+ not($nc2: Cell $nc2 != $c && $i in $nc2.free && $nc2.cellSqr == $cs && $nc2.cellCol != $cc );
+
+ // but there is a cell in the same column and another sqstuare containing this value
+ $cx: Cell $cx.count > 1 && $i in $cx.free && $cx.cellCol == $cc && $cx.cellSqr != $cs;
+ }
+ then{
+ // remove the value from that other cell
+ //if (explain) {
+ console.log( "column elimination due to " + $c.posAsString() +
+ ": remove " + $i + " from " + $cx.posAsString() );
+ //}
+ modify( $cx, function(){this.blockValue( $i ) });
+ }
+}
+
+rule "intersection removal row"{
+ when{
+ not($ns: Setting);
+ not($nc: Cell $nc.count == 1 );
+
+ $i: Number;
+ // occurs in a Cell
+ $c: Cell $i in $c.free {cellSqr: $cs, cellRow: $cr};
+ // but not in another cell of the same square and a different row
+ not($nc2: Cell $nc2 != $c && $i in $nc2.free && $nc2.cellSqr == $cs && $nc2.cellRow != $cr);
+
+ // but there is a cell in the same row and another square containing this value
+ $cx: Cell $cx.count > 1 && $i in $cx.free && $cx.cellRow == $cr && $cx.cellSqr != $cs;
+ }
+ then {
+ // remove the value from that other cell
+ //if (explain) {
+ console.log( "row elimination due to " + $c.posAsString() +
+ ": remove " + $i + " from " + $cx.posAsString() );
+ //}
+ modify( $cx, function(){ this.blockValue( $i ) });
+ }
+}
View
368 docs/examples/browser/src/sudoku.js
@@ -0,0 +1,368 @@
+(function () {
+ /*global $:true*/
+ var declare = this.declare,
+ arr = this.arrayExtended,
+ unique = arr.unique;
+
+ var allNine = [1, 2, 3, 4, 5, 6, 7, 8, 9];
+
+ var SetOfNine = declare({
+ instance: {
+
+ free: null,
+ count: 0,
+
+ constructor: function () {
+ this.count = (this.free = allNine.slice()).length;
+ },
+
+ blockValue: function (value) {
+ var index = arr.indexOf(this.free, value);
+ if (index !== -1) {
+ this.free.splice(index, 1);
+ this.count = this.free.length;
+ }
+ return this;
+ },
+
+ blockExcept: function (values) {
+ this.free = values || [];
+ this.count = this.free.length;
+ return this;
+ },
+
+ freeValue: function () {
+ return this.free[0];
+ }
+ }
+ }).as(this, "SetOfNine");
+
+ var CellGroup = SetOfNine.extend({
+ instance: {
+ cells: null,
+
+ constructor: function () {
+ this._super(arguments);
+ this.cells = [];
+ },
+
+ addCell: function (cell) {
+ this.cells.push(cell);
+ }
+ }
+ }).as(this, "CellGroup");
+
+
+ var CellFile = CellGroup.extend({
+ instance: {
+ number: null,
+
+ constructor: function (number) {
+ this._super(arguments);
+ this.number = number;
+ },
+
+ toString: function () {
+ var ret = [], cells = this.cells;
+ for (var i = 0, l = cells.length; i < l; i++) {
+ ret.push(cells[i].toString());
+ }
+ return ret.join(", ");
+ }
+ }
+ }).as(this, "CellFile");
+
+ var Cell = SetOfNine.extend({
+ instance: {
+ value: null,
+ cellRow: null,
+ cellCol: null,
+ cellSqr: null,
+ exCells: null,
+ el: null,
+
+ constructor: function () {
+ this._super(arguments);
+ this.exCells = [];
+ },
+
+ makeReferences: function (cr, col, sqr) {
+ this.cellRow = cr;
+ this.cellCol = col;
+ this.cellSqr = sqr;
+ this.colNo = col.number;
+ this.rowNo = cr.number;
+ this.exCells = unique(this.exCells.concat(cr.cells).concat(col.cells).concat(sqr.cells));
+ this.exCells.splice(this.exCells.indexOf(this), 1);
+ return this;
+ },
+
+ toString: function () {
+ return [this.posAsString(), this.valueAsString()].join(": ");
+ },
+
+ valueAsString: function () {
+ return this.value === null ? " ": this.value;
+ },
+
+ posAsString: function () {
+ return ["[", this.cellRow.number, ",", this.cellCol.number, "]"].join("");
+ }
+ }
+ }).as(this, "Cell");
+
+ var CellCol = CellFile.extend({
+ instance: {
+ toString: function () {
+ return ["Column ", this.number, ": ", this._super(arguments)];
+ }
+ }
+ }).as(this, "CellCol");
+
+
+ var CellRow = CellFile.extend({
+ instance: {
+ toString: function () {
+ return ["Row ", this.number, ": ", this._super(arguments)];
+ }
+ }
+ }).as(this, "CellRow");
+
+ var CellSqr = CellGroup.extend({
+ instance: {
+ constructor: function (cr1, cr2, cr3, cc1, cc2, cc3) {
+ this._super(arguments);
+ for (var i = cr1.number; i <= cr3.number; i++) {
+ this.addCell(cc1.cells[i]);
+ this.addCell(cc2.cells[i]);
+ this.addCell(cc3.cells[i]);
+ }
+
+ }
+ }
+ }).as(this, "CellSqr");
+
+ var Counter = declare({
+ instance: {
+ count: 0,
+ constructor: function (count) {
+ this.count = count;
+ }
+ }
+ }).as(this, "Counter");
+
+
+ var Setting = declare({
+ instance: {
+ rowNo: null,
+ colNo: null,
+ value: null,
+
+ constructor: function (row, col, value) {
+ this.rowNo = row;
+ this.colNo = col;
+ this.value = value;
+ },
+
+ toString: function () {
+ return "Setting [" + this.rowNo + "," + this.colNo + "] : " + this.value;
+ }
+
+ }
+ }).as(this, "Setting");
+
+
+ var Stepping = declare({
+ instance: {
+ emergency: false
+ }
+ }).as(this, "Stepping");
+
+ var Sudoku = declare({
+ instance: {
+
+ constructor: function (flow, statsListener) {
+ this.rows = [];
+ this.cols = [];
+ this.sqrs = [];
+ this.cells = [];
+ this.flow = flow;
+ this.statsListener = statsListener;
+ this.session = null;
+ this.stepping = null;
+ },
+
+ stop: function () {
+ if (this.session) {
+ this.session.halt();
+ this.session.dispose();
+ }
+ return this;
+ },
+
+ clear: function () {
+ this.stop();
+ this.setCellValues([]);
+ },
+
+ step: function () {
+ this.session.modify(this.counter, function () {
+ this.count = 1;
+ });
+ if (!this.stepping) {
+ this.session.assert(new Stepping());
+ }
+ return this.session.matchUntilHalt(function (err) {
+ if (err) {
+ console.log(err);
+ }
+ });
+ },
+
+ solve: function () {
+ this.session.modify(this.counter, function () {
+ this.count = Infinity;
+ });
+ if (this.stepping) {
+ this.session.retract(this.stepping);
+ }
+ return this.session.match(function (err) {
+ if (err) {
+ console.log(err);
+ }
+ });
+ },
+
+ setCellValue: function setCellValue(cell) {
+ cell.el.text(cell.value ? cell.value: "");
+ return cell;
+ },
+
+ create: function () {
+ var grid;
+ (grid = $("#grid")).empty();
+ var rows = this.rows, cols = this.cols, sqrs = this.sqrs, session = this.session;
+ for (var i = 0; i < 9; i++) {
+ session.assert(i + 1);
+ rows[i] = new CellRow(i);
+ cols[i] = new CellCol(i);
+ }
+
+ var cells = this.cells = [], iCol;
+ for (var iRow = 0; iRow < 9; iRow++) {
+ var rowDom = $("<div/>").addClass("row").appendTo(grid);
+ cells[iRow] = [];
+ for (iCol = 0; iCol < 9; iCol++) {
+ var cell = cells[iRow][iCol] = new Cell();
+ cell.el = $("<div class='col'></div>").appendTo(rowDom);
+ rows[iRow].addCell(cell);
+ cols[iCol].addCell(cell);
+ }
+ }
+
+ for (i = 0; i < 3; i++) {
+ sqrs[i] = [];
+ for (var j = 0; j < 3; j++) {
+ sqrs[i][j] = new CellSqr(rows[i * 3], rows[i * 3 + 1], rows[i * 3 + 2],
+ cols[j * 3], cols[j * 3 + 1], cols[j * 3 + 2]);
+ }
+ }
+
+ for (iRow = 0; iRow < 9; iRow++) {
+ for (iCol = 0; iCol < 9; iCol++) {
+ session.assert(cells[iRow][iCol].makeReferences(rows[iRow], cols[iCol], sqrs[Math.floor(iRow / 3)][Math.floor(iCol / 3)]));
+ }
+ session.assert(rows[iRow]);
+ session.assert(cols[iRow]);
+ session.assert(sqrs[Math.floor(iRow / 3)][iRow % 3]);
+ }
+ return this;
+ },
+
+ setCellValues: function (cellValues) {
+ this.stop();
+ var session = (this.session = this.statsListener.listen(this.flow.getSession()))
+ .on("set-value", this.setCellValue);
+
+ var s000 = new Setting(0, 0, 0);
+ this.session.assert(s000);
+ this.create();
+
+ var initial = 0;
+ for (var iRow = 0; iRow < 9; iRow++) {
+ var row = cellValues[iRow] || [];
+ for (var iCol = 0; iCol < 9; iCol++) {
+ var value = row[iCol] || null;
+ if (value) {
+ session.assert(new Setting(iRow, iCol, value));
+ initial++;
+ //console.log(initial);
+ }
+ }
+ }
+ var counter = this.counter = new Counter(initial);
+ this.session.assert(counter);
+ this.session.retract(s000);
+ return this.session.matchUntilHalt(function (err) {
+ if (err) {
+ console.log(err);
+ }
+ this.dumpGrid();
+ }.bind(this));
+ },
+
+ dumpGrid: function () {
+ var cells = this.cells;
+ var print = [" "];
+ for (var iCol = 0; iCol < 9; iCol++) {
+ print.push("Col: " + iCol + " ");
+ }
+ console.log(print.join(""));
+ for (var iRow = 0; iRow < 9; iRow++) {
+ print = ["Row " + (iRow) + ": "];
+ for (iCol = 0; iCol < 9; iCol++) {
+ if (cells[iRow][iCol].value !== null) {
+ print.push(" --- " + cells[iRow][iCol].value + " --- ");
+ } else {
+ var perms = cells[iRow][iCol].free, sb = [];
+ for (var i = 1; i <= 9; i++) {
+ if (perms.indexOf(i) !== -1) {
+ sb.push(i);
+ } else {
+ sb.push(' ');
+ }
+ }
+ print.push(" % " + sb.join(""));
+ }
+ }
+ console.log(print.join(""));
+ }
+ },
+
+ setInvalidCellValue: function setCellValue(cell) {
+ cell.el.addClass("error");
+ return cell;
+ },
+
+ validate: function () {
+ this.session.assert("validate");
+ return this.session.focus("validate")
+ .on("invalid", this.setInvalidCellValue)
+ .matchUntilHalt();
+ }
+
+ }
+ });
+
+ this.Sudoku = Sudoku;
+}).call(this);
+
+
+
+
+
+
+
+
+
+
View
90 docs/examples/browser/src/sudokuPatterns.js
@@ -0,0 +1,90 @@
+(function () {
+
+ this.sudokuExamples = {
+ Simple: [
+ [null, 5, 6, 8, null, 1, 9, 4, null],
+ [9, null, null, 6, null, 5, null, null, 3],
+ [7, null, null, 4, 9, 3, null, null, 8],
+ [8, 9, 7, null, 4, null, 6, 3, 5],
+ [null, null, 3, 9, null, 6, 8, null, null],
+ [4, 6, 5, null, 8, null, 2, 9, 1],
+ [5, null, null, 2, 6, 9, null, null, 7],
+ [6, null, null, 5, null, 4, null, null, 9],
+ [null, 4, 9, 7, null, 8, 3, 5, null]
+ ],
+
+ Medium: [
+ [8, 4, 7, null, null, null, 2, 5, 6],
+ [5, null, null, null, 8, null, null, null, 4],
+ [2, null, null, null, 7, null, null, null, 8],
+ [null, null, null, 3, null, 8, null, null, null],
+ [null, 5, 1, null, null, null, 8, 7, 2],
+ [null, null, null, 5, null, 7, null, null, null],
+ [4, null, null, null, 5, null, null, null, 7],
+ [6, null, null, null, 3, null, null, null, 9],
+ [1, 3, 2, null, null, null, 4, 8, 5]
+ ],
+
+ "Hard 1": [
+ [null, null, null, null, 5, 1, null, 8, null],
+ [null, 8, null, null, 4, null, null, null, 5],
+ [null, null, 3, null, null, null, 2, null, null],
+ [null, null, null, null, 6, null, null, null, 9],
+ [6, 7, null, 4, null, 9, null, 1, 3],
+ [8, null, null, null, 3, null, null, null, null],
+ [null, null, 2, null, null, null, 4, null, null],
+ [5, null, null, null, 9, null, null, 2, null],
+ [null, 9, null, 7, 1, null, null, null, null]
+ ],
+
+ "Hard 2": [
+ [null, null, null, 6, null, null, 1, null, null],
+ [null, null, null, null, null, 5, null, null, 6],
+ [5, null, 7, null, null, null, 2, 3, null],
+ [null, 8, null, 9, null, 7, null, null, null],
+ [9, 3, null, null, null, null, null, 6, 7],
+ [null, null, null, 4, null, 6, null, 1, null],
+ [null, 7, 4, null, null, null, 9, null, 1],
+ [8, null, null, 7, null, null, null, null, null],
+ [null, null, 3, null, null, 8, null, null, null]
+ ],
+
+ "Hard 3": [
+ [null, 8, null, null, null, 6, null, null, 5],
+ [2, null, null, null, null, null, 4, 8, null],
+ [null, null, 9, null, null, 8, null, 1, null],
+ [null, null, null, null, 8, null, 1, null, 2],
+ [null, null, null, 3, null, 1, null, null, null],
+ [6, null, 1, null, 9, null, null, null, null],
+ [null, 9, null, 4, null, null, 8, null, null],
+ [null, 7, 6, null, null, null, null, null, 3],
+ [1, null, null, 7, null, null, null, 5, null]
+ ],
+
+ "Hard 4": [
+ [null, null, null, null, null, 4, null, 9, 5],
+ [6, 7, null, 5, null, null, null, 1, null],
+ [null, null, null, 6, null, 9, null, null, null],
+ [null, 2, null, null, null, null, 4, null, null],
+ [8, 1, null, null, null, null, null, 7, 2],
+ [null, null, 7, null, null, null, null, 8, null],
+ [null, null, null, 3, null, 5, null, null, null],
+ [null, 6, null, null, null, 1, null, 5, 8],
+ [7, 3, null, 9, null, null, null, null, null]
+ ],
+
+ "Broken": [
+ [5, null, null, null, null, 4, null, 9, 5],
+ [6, 7, null, 5, null, null, null, 1, null],
+ [null, null, null, 6, null, 9, null, null, null],
+ [null, 2, null, null, null, null, 4, null, null],
+ [8, 1, null, null, null, null, null, 7, 2],
+ [null, null, 7, null, null, null, null, 8, null],
+ [8, null, null, 3, null, 5, null, null, null],
+ [null, 6, null, null, null, 1, null, 5, 8],
+ [7, 3, null, 9, null, null, null, null, null]
+ ]
+
+ };
+
+}).call(this);
View
146 docs/examples/browser/sudoku.html
@@ -0,0 +1,146 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Sudoku</title>
+ <link type="text/css" rel="stylesheet" href="assets/conways.css">
+ <link type="text/css" rel="stylesheet" href="//code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css">
+ <link href='http://fonts.googleapis.com/css?family=Londrina+Shadow' rel='stylesheet' type='text/css'>
+</head>
+<body>
+
+<div class="container sudoku">
+ <header>
+ <h1>Sudoku</h1>
+
+ <form>
+ <input type="submit" id="run" value="Run!"/>
+ <input type="button" id="step" value="Step"/>
+ <input type="button" id="stop" value="Stop"/>
+ <input type="button" id="clear" value="Clear"/>
+ <input type="button" id="edit" value="Edit Rules"/>
+ <select id="patterns"></select>
+ </form>
+ </header>
+ <div id="grid"></div>
+</div>
+
+<div id="editor-dialog">
+ <div id="editor"></div>
+</div>
+
+<script type="text/javascript" src="//code.jquery.com/jquery-1.10.1.min.js"></script>
+<script type="text/javascript" src="//code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
+<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/ace/0.2.0/ace.js"></script>
+<script type="text/javascript" src="../../nools.js"></script>
+<script type="text/javascript" src="assets/declare.js"></script>
+<script type="text/javascript" src="assets/extender.js"></script>
+<script type="text/javascript" src="assets/extended.js"></script>
+<script type="text/javascript" src="assets/is-extended.js"></script>
+<script type="text/javascript" src="assets/arguments-extended.js"></script>
+<script type="text/javascript" src="assets/array-extended.js"></script>
+<script type="text/javascript" src="assets/function-extended.js"></script>
+<script type="text/javascript" src="src/common.js"></script>
+<script type="text/javascript" src="src/sudokuPatterns.js"></script>
+<script type="text/javascript" src="src/sudoku.js"></script>
+<!--<script type="text/javascript" src="rules/sudoku.js"></script>-->
+<script type="text/javascript">
+
+ (function () {
+ var flow = null,
+ currPattern = sudokuExamples["Simple"];
+
+
+ var grid = $("#grid"), cells = [], rows = 9, cols = 9;
+
+ $(document).ready(function () {
+
+ $.ajax({
+ url: "./rules/sudoku.nools"
+ }).then(function (flowStr) {
+ //var flowStr = res;
+ // flow = nools.compile(res, {name: "conways2d", define: {Cell: Cell}, scope: { loop: loop}});
+ //validateFlow = nools.compile(validateNools, {name: 'validate', define: {Cell: Cell}});
+ flow = nools.compile(flowStr, {name: 'sudoku', define: {
+ SetOfNine: SetOfNine,
+ CellGroup: CellGroup,
+ CellFile: CellFile,
+ Cell: Cell,
+ CellCol: CellCol,
+ CellRow: CellRow,
+ CellSqr: CellSqr,
+ Counter: Counter,
+ Setting: Setting,
+ Stepping: Stepping
+
+ }});
+ var sud = new Sudoku(flow, stats());
+ var $select = $("#patterns");
+ $select.append($('<option/>', {
+ value: i,
+ text: i
+ }));
+ for (var i in sudokuExamples) {
+ $select.append($('<option/>', {
+ value: i,
+ text: i
+ }));
+ }
+ $select.on("change", function () {
+ currPattern = sudokuExamples[$(this).val()];
+ sud.setCellValues(currPattern).then(function () {
+ return sud.validate();
+ });
+ });
+ $select.val("Simple").trigger("change");
+
+ $("form").on("submit", function () {
+ sud.solve();
+ return false;
+ });
+
+ $("#step").on("click", function () {
+ sud.step();
+ });
+
+ $("#stop").on("click", function () {
+ sud.solve();
+ });
+
+ $("#clear").on("click", function () {
+ sud.clear();
+ });
+
+ $("#edit").on("click", function () {
+ $("#editor-dialog").dialog("open");
+ return false;
+ });
+
+ $("#editor-dialog").dialog({
+ autoOpen: false,
+ modal: true,
+ width: window.innerWidth * 0.9,
+ height: window.innerHeight * 0.9,
+ open: function () {
+ (editor = ace.edit("editor").getSession()).setValue(flowStr);
+ },
+ buttons: {
+ "Save": function () {
+ $(this).dialog("close");
+ nools.deleteFlow("sudoku");
+ flow = nools.compile((flowStr = editor.getValue()), {name: "sudoku", define: {Cell: Cell}, scope: { loop: loop}});
+ },
+ Cancel: function () {
+ $(this).dialog("close");
+ }
+ }
+ });
+
+ });
+ });
+ }());
+</script>
+
+</body>
+</html>
View
140 docs/index.html
@@ -191,7 +191,12 @@
<li>Flows<ul>
<li><a href="#flow">Defining A Flow</a> </li>
<li><a href="#session">Sessions</a> </li>
-<li><a href="#facts">Facts</a> </li>
+<li><a href="#facts">Facts</a><ul>
+<li><a href="#facts-assert">Assert</a></li>
+<li><a href="#facts-retract">Retract</a></li>
+<li><a href="#facts-modify">Modify</a></li>
+</ul>
+</li>
<li><a href="#firing">Firing</a> </li>
<li><a href="#disposing">Disposing</a></li>
<li><a href="#removing-flow">Removing A Flow</a></li>
@@ -208,7 +213,12 @@
<li><a href="#rule-structure">Structure</a></li>
<li><a href="#rule-salience">Salience</a></li>
<li><a href="#rule-scope">Scope</a></li>
-<li><a href="#constraints">Constraints</a></li>
+<li><a href="#constraints">Constraints</a><ul>
+<li><a href="#not-constraint">Not</a></li>
+<li><a href="#or-constraint">Or</a></li>
+<li><a href="#from-constraint">From</a></li>
+</ul>
+</li>
<li><a href="#action">Actions</a></li>
<li><a href="#globals">Globals</a></li>
<li><a href="#import">Import</a></li>
@@ -217,8 +227,12 @@
<li><a href="#browser-support">Browser Support</a></li>
<li><a href="#fib">Fibonacci</a></li>
</ul>
-<p>The <a href="https://github.com/C2FO/nools/tree/master/examples">examples</a> and <a href="https://github.com/C2FO/nools/tree/master/test">tests</a> are a
-great place to get started. You can ask your questions on the <a href="https://groups.google.com/forum/#!forum/nools">Nools Google group</a>.</p>
+<h2>Resources</h2>
+<ul>
+<li><a href="https://groups.google.com/forum/#!forum/nools">Nools Google group</a></li>
+<li><a href="http://c2fo.github.io/nools/examples.html">Examples</a></li>
+<li><a href="https://github.com/C2FO/nools/tree/master/test">Tests</a></li>
+</ul>
<p><a name="flow"></a></p>
<h2>Defining a flow</h2>
<p>When using nools you define a <strong>flow</strong> which acts as a container for rules that can later be used to get
@@ -375,13 +389,18 @@
<p><a name="facts"></a></p>
<h2>Working with facts</h2>
<p>Facts are items that the rules should try to match.</p>
-<p>To add facts to the session use <strong>assert</strong> method.</p>
+<p><a name="facts-assert"></a></p>
+<h3>Assert</h3>
+<p>To add facts to the session use <code>assert</code> method.</p>
<pre class='prettyprint linenums lang-js'><code class="lang-javascript">session.assert(new Message(&quot;hello&quot;));
session.assert(new Message(&quot;hello world&quot;));
session.assert(new Message(&quot;goodbye&quot;));</code></pre>
<p>As a convenience any object passed into <strong>getSession</strong> will also be asserted.</p>
+<p><strong>Note</strong> assert is typically used pre engine execution and during the execution of the rules.</p>
<pre class='prettyprint linenums lang-js'><code class="lang-javascript">flow.getSession(new Message(&quot;hello&quot;), new Message(&quot;hello world&quot;), new Message(&quot;goodbye&quot;));</code></pre>
-<p>To retract facts from the session use the <strong>retract</strong> method.</p>
+<p><a name="facts-retract"></a></p>
+<h3>Retract</h3>
+<p>To remove facts from the session use the <code>retract</code> method.</p>
<pre class='prettyprint linenums lang-js'><code class="lang-javascript">var m = new Message(&quot;hello&quot;);
//assert the fact into the engine
@@ -389,7 +408,10 @@
//remove the fact from the engine
session.retract(m);</code></pre>
-<p>To modify a fact use the <strong>modify</strong> method.</p>
+<p><strong>Note</strong> <code>retract</code> is typically used during the execution of the rules.</p>
+<p><a name="facts-modify"></a></p>
+<h3>Modify</h3>
+<p>To modify a fact use the <code>modify</code> method.</p>
<p><strong>Note</strong> modify will not work with immutable objects (i.e. strings). </p>
<pre class='prettyprint linenums lang-js'><code class="lang-javascript">
var m = new Message(&quot;hello&quot;);
@@ -399,8 +421,7 @@
m.message = &quot;hello goodbye&quot;;
session.modify(m);</code></pre>
-<p><strong>assert</strong> is typically used pre engine execution and during the execution of the rules.</p>
-<p><strong>modify</strong> and <strong>retract</strong> are typically used during the execution of the rules.</p>
+<p><strong>Note</strong> <code>modify</code> is typically used during the execution of the rules.</p>
<p><a name="firing"></a></p>
<h2>Firing the rules</h2>
<p>When you get a session from a <strong>flow</strong> no rules will be fired until the <strong>match</strong> method is called.</p>
@@ -771,6 +792,38 @@
this.rule(&quot;Hello1&quot;, {salience: 10}, [Message, &quot;m&quot;, &quot;m.name == &#39;Hello&#39;&quot;], function (facts) {
});</code></pre>
+<p>Or using the DSL</p>
+<pre class='prettyprint linenums lang-js'><code class="lang-javascript">rule Hello4 {
+ salience: 7;
+ when {
+ m: Message m.name == &#39;hello&#39;;
+ }
+ then {}
+}
+
+rule Hello3 {
+ salience: 8;
+ when {
+ m: Message m.name == &#39;hello&#39;;
+ }
+ then {}
+}
+
+rule Hello2 {
+ salience: 9;
+ when {
+ m: Message m.name == &#39;hello&#39;;
+ }
+ then {}
+}
+
+rule Hello1 {
+ salience: 10;
+ when {
+ m: Message m.name == &#39;hello&#39;;
+ }
+ then {}
+}</code></pre>
<p>In the above flow we define four rules each with a different salience, when a single message is asserted they will fire in order of salience (highest to lowest).</p>
<pre class='prettyprint linenums lang-js'><code class="lang-javascript">var fired = [];
flow1
@@ -916,6 +969,75 @@
<li><p>Reference(optional) - An object where the keys are properties on the current object, and values are aliases to use. The alias may be used in succeeding patterns.</p>
</li>
</ol>
+<p><a name="not-constraint"></a></p>
+<h4>Not Constraint</h4>
+<p>The <code>not</code> constraint allow you to check that particular <code>fact</code> does <strong>not</strong> exist.</p>
+<pre class='prettyprint linenums lang-js'><code class="lang-javascript">
+[
+ [Number, &quot;n1&quot;],
+ [&quot;not&quot;, Number, &quot;n2&quot;, &quot;n1 &gt; n2&quot;]
+]</code></pre>
+<p>Or using the DSL.</p>
+<pre class='prettyprint linenums lang-js'><code>
+when {
+ n1: Number;
+ not(n2: Number n1 &gt; n2);
+}</code></pre>
+<p>The previous example will check that for all numbers in the <code>workingMemory</code> there is <strong>not</strong> one that is greater than <code>n1</code>.</p>
+<p><a name="or-constraint"></a></p>
+<h4>Or Constraint</h4>
+<p>The <code>or</code> constraint can be used to check for either one fact or another.</p>
+<pre class='prettyprint linenums lang-js'><code class="lang-javascript">
+[
+ [&quot;or&quot;,
+ [String, &quot;s&quot;, &quot;s == &#39;hello&#39;&quot;],
+ [String, &quot;s&quot;, &quot;s == &#39;world&#39;&quot;]
+ ]
+]</code></pre>
+<p>Using the DSL.</p>
+<pre class='prettyprint linenums lang-js'><code>when {
+ or(
+ s : String s == &#39;hello&#39;,
+ s : String s == &#39;world&#39;
+ );
+}</code></pre>
+<p>The previous example will evaluate to <code>true</code> if you have a string in <code>workingMemory</code> that equals <code>hello</code> or `world.</p>
+<p><a name="from-constraint"></a></p>
+<h4>From Constraint</h4>
+<p>The <code>from</code> modifier allows for the checking of facts that are not necessarily in the <code>workingMemory</code> but instead exist as a property on a fact.</p>
+<pre class='prettyprint linenums lang-js'><code class="lang-javascript">[
+ [Person, &quot;p&quot;],
+ [Address, &quot;a&quot;, &quot;a.zipcode == 88847&quot;, &quot;from p.address&quot;],
+ [String, &quot;first&quot;, &quot;first == &#39;bob&#39;&quot;, &quot;from p.firstName&quot;],
+ [String, &quot;last&quot;, &quot;last == &#39;yukon&#39;&quot;, &quot;from p.lastName&quot;]
+]</code></pre>
+<p>Or using the DSL.</p>
+<pre class='prettyprint linenums lang-js'><code>when {
+ p: Person:
+ a: Address a.zipcode == 88847 from p.address;
+ first: String first == &#39;bob&#39; from p.firstName;
+ last: String last == &#39;yukon&#39; from p.lastName;
+}</code></pre>
+<p>The above example will used the address, firstName and lastName from the <code>person</code> fact.</p>
+<p>You can also use the <code>from</code> modifier to check facts that create a graph.</p>
+<p>For example assume the person object from above has friends that are also of type <code>Person</code>.</p>
+<pre class='prettyprint linenums lang-js'><code class="lang-javascript">[
+ [Person, &quot;p&quot;],
+ [Person, &quot;friend&quot;, &quot;friend.firstName != p.firstName&quot;, &quot;from p.friends&quot;],
+ [String, &quot;first&quot;, &quot;first =~ /^a/&quot;, &quot;from friend.firstName&quot;]
+]</code></pre>
+<p>Or using the DSL.</p>
+<pre class='prettyprint linenums lang-js'><code>when {
+ p: Person;
+ friend: Person friend.firstName != p.firstName;
+ first: String first =~ /^a/ from friend.firstName;
+}</code></pre>
+<p>The above example will pull the <code>friend</code> fact from the friends array property on fact <code>p</code>, and first from the <code>friend</code>&#39;s <code>firstName</code>.</p>
+<p>You could achieve the same thing using the following code if you assert all friends into working memory.</p>
+<pre class='prettyprint linenums lang-js'><code>when {
+ p: Person;
+ friend: Person friend in p.friends &amp;&amp; friend.firstName != p.firstName &amp;&amp; p.firstName =~ /^a/;
+}</code></pre>
<p><a name="action"></a></p>
<h3>Action</h3>
<p>The action is a function that should be fired when all patterns in the rule match. The action is called in the scope
View
16 docs/nools.js
8 additions, 8 deletions not shown
View
3  examples/browser/assets/array-extended.js
@@ -0,0 +1,3 @@
+/*! array-extended - v0.0.9 - 2013-06-17
+ * Copyright (c) 2013 Doug Martin; Licensed MIT */
+!function(){"use strict";function a(a,b,c){function d(a,b){return o(b,function(b,c){return N(c)||(c=[c]),c.unshift(a),b.unshift(c),b},[])}function e(a,b,c){for(var d=[],e=0;e<b.length;e++)d.push([a].concat(y(b,e)).slice(0,c));return d}function f(a,b){var c,d,e=[],f=-1;for(d=a.length;++f<d;)c=a[f],-1!==g(b,c)&&e.push(c);return e}function g(a,b,c){for(var d=(c||0)-1,e=a.length;++d<e;)if(a[d]===b)return d;return-1}function h(a,b){if(!N(a))throw new TypeError;var c=Object(a),d=c.length>>>0;if(0===d)return-1;var e=d;arguments.length>2&&(e=Number(arguments[2]),e!==e?e=0:0!==e&&e!==1/0&&e!==-(1/0)&&(e=(e>0||-1)*P(Q(e))));for(var f=e>=0?R(e,d-1):d-Q(e);f>=0;f--)if(f in c&&c[f]===b)return f;return-1}function i(a,b,c){if(a&&X&&X===a.filter)return a.filter(b,c);if(!N(a)||"function"!=typeof b)throw new TypeError;for(var d=Object(a),e=d.length>>>0,f=[],g=0;e>g;g++)if(g in d){var h=d[g];b.call(c,h,g,d)&&f.push(h)}return f}function j(a,b,c){if(!N(a)||"function"!=typeof b)throw new TypeError;if(a&&T&&T===a.forEach)return a.forEach(b,c),a;for(var d=0,e=a.length;e>d;++d)b.call(c||a,a[d],d,a);return a}function k(a,b,c){if(a&&Y&&Y===a.every)return a.every(b,c);if(!N(a)||"function"!=typeof b)throw new TypeError;for(var d=Object(a),e=d.length>>>0,f=0;e>f;f++)if(f in d&&!b.call(c,d[f],f,d))return!1;return!0}function l(a,b,c){if(a&&Z&&Z===a.some)return a.some(b,c);if(!N(a)||"function"!=typeof b)throw new TypeError;for(var d=Object(a),e=d.length>>>0,f=0;e>f;f++)if(f in d&&b.call(c,d[f],f,d))return!0;return!1}function m(a,b,c){if(a&&U&&U===a.map)return a.map(b,c);if(!N(a)||"function"!=typeof b)throw new TypeError;for(var d=Object(a),e=d.length>>>0,f=[],g=0;e>g;g++)g in d&&f.push(b.call(c,d[g],g,d));return f}function n(a,b,c){var d=arguments.length>2;if(a&&V&&V===a.reduce)return d?a.reduce(b,c):a.reduce(b);if(!N(a)||"function"!=typeof b)throw new TypeError;var e=0,f=a.length>>0;if(arguments.length<3){if(0===f)throw new TypeError("Array length is 0 and no second argument");c=a[0],e=1}else c=arguments[2];for(;f>e;)e in a&&(c=b.call(void 0,c,a[e],e,a)),++e;return c}function o(a,b,c){var d=arguments.length>2;if(a&&W&&W===a.reduceRight)return d?a.reduceRight(b,c):a.reduceRight(b);if(!N(a)||"function"!=typeof b)throw new TypeError;var e=Object(a),f=e.length>>>0;if(0===f&&2===arguments.length)throw new TypeError;var g=f-1;if(arguments.length>=3)c=arguments[2];else for(;;)if(g in a){c=a[g--];break}for(;g>=0;)g in e&&(c=b.call(void 0,c,e[g],g,e)),g--;return c}function p(a){var c=[];if(null!==a){var d=$(arguments);if(1===d.length)if(N(a))c=a;else if(b.isHash(a))for(var e in a)a.hasOwnProperty(e)&&c.push([e,a[e]]);else c.push(a);else j(d,function(a){c=c.concat(p(a))})}return c}function q(a){return a=a||[],a.length?n(a,function(a,b){return a+b}):0}function r(a){if(a=a||[],a.length){var c=q(a);if(b.isNumber(c))return c/a.length;throw new Error("Cannot average an array of non numbers.")}return 0}function s(a,b){return _(a,b)}function t(a,b){return _(a,b)[0]}function u(a,b){return _(a,b)[a.length-1]}function v(a){var b=a,c=J($(arguments,1));return N(a)&&(b=i(a,function(a){return-1===g(c,a)})),b}function w(a){var b,c=[],d=-1,e=0;if(a)for(b=a.length;++d<b;){var f=a[d];-1===g(c,f)&&(c[e++]=f)}return c}function x(a){return w(a)}function y(a,b){var c=a.slice();return"number"!=typeof b&&(b=1),b&&N(a)?(b>0?(c.push(c.shift()),b--):(c.unshift(c.pop()),b++),y(c,b)):c}function z(a,b){var c=[];if(N(a)){var d=a.slice(0);"number"!=typeof b&&(b=a.length),b?b<=a.length&&(c=n(a,function(a,c,f){var g;return g=b>1?e(c,y(d,f).slice(1),b):[[c]],a.concat(g)},[])):c=[[]]}return c}function A(){var a=[],c=$(arguments);if(c.length>1){var d=c.shift();N(d)&&(a=n(d,function(a,d,e){for(var f=[d],g=0;g<c.length;g++){var h=c[g];N(h)&&!b.isUndefined(h[e])?f.push(h[e]):f.push(null)}return a.push(f),a},[]))}return a}function B(a){var b=[];if(N(a)&&a.length){var c;j(a,function(a){!N(a)||c&&a.length!==c.length||(j(a,function(a,c){b[c]||(b[c]=[]),b[c].push(a)}),c=a)})}return b}function C(a,b){var c=[];if(b=$(arguments),a=b.shift(),N(a)&&b.length)for(var d=0,e=b.length;e>d;d++)c.push(a[b[d]]||null);return c}function D(){var a=[],b=$(arguments);if(b.length>1){for(var c=0,d=b.length;d>c;c++)a=a.concat(b[c]);a=w(a)}return a}function E(){var a,b,c=[],d=-1;if(a=arguments.length>1?$(arguments):arguments[0],N(a))for(c=a[0],d=0,b=a.length;++d<b;)c=f(c,a[d]);return w(c)}function F(a){var b=[];return N(a)&&a.length&&(b=n(a,function(a,b){var c=m(a,function(a){return a.concat(b)});return a.concat(c)},[[]])),b}function G(a,b){var c=[];return N(a)&&N(b)&&a.length&&b.length&&(c=d(a[0],b).concat(G(a.slice(1),b))),c}function H(a){var c=[];return N(a)&&a.length&&(c=i(a,function(a){return!b.isUndefinedOrNull(a)})),c}function I(a,c){c=b.isNumber(c)?c:1,c||(c=1),a=p(a||[]);for(var d=[],e=0;++e<=c;)d=d.concat(a);return d}function J(a){var b,c=$(arguments);return b=c.length>1?c:p(a),n(b,function(a,b){return a.concat(b)},[])}function K(a,b){b=b.split(".");var c=a.slice(0);return j(b,function(a){var b=a.match(/(\w+)\(\)$/);c=m(c,function(c){return b?c[b[1]]():c[a]})}),c}function L(a,b,c){return c=$(arguments,2),m(a,function(a){var d=M(b)?a[b]:b;return d.apply(a,c)})}var M=b.isString,N=Array.isArray||b.isArray,O=b.isDate,P=Math.floor,Q=Math.abs,R=(Math.max,Math.min),S=Array.prototype,T=(S.indexOf,S.forEach),U=S.map,V=S.reduce,W=S.reduceRight,X=S.filter,Y=S.every,Z=S.some,$=c.argsToArray,_=function(){var a=function(a,b){return k(a,b)},b=function(a,b){return a-b},c=function(a,b){return a.getTime()-b.getTime()};return function(d,e){var f=[];return N(d)&&(f=d.slice(),e?"function"==typeof e?f.sort(e):f.sort(function(a,b){var c=a[e],d=b[e];return M(c)&&M(d)?c>d?1:d>c?-1:0:O(c)&&O(d)?c.getTime()-d.getTime():c-d}):a(f,M)?f.sort():a(f,O)?f.sort(c):f.sort(b)),f}}(),ab={toArray:p,sum:q,avg:r,sort:s,min:t,max:u,difference:v,removeDuplicates:w,unique:x,rotate:y,permutations:z,zip:A,transpose:B,valuesAt:C,union:D,intersect:E,powerSet:F,cartesian:G,compact:H,multiply:I,flatten:J,pluck:K,invoke:L,forEach:j,map:m,filter:i,reduce:n,reduceRight:o,some:l,every:k,indexOf:g,lastIndexOf:h};return a.define(N,ab).expose(ab)}"undefined"!=typeof exports?"undefined"!=typeof module&&module.exports&&(module.exports=a(require("extended"),require("is-extended"),require("arguments-extended"))):"function"==typeof define&&define.amd?define(["extended","is-extended","arguments-extended"],function(b,c,d){return a(b,c,d)}):this.arrayExtended=a(this.extended,this.isExtended,this.argumentsExtended)}.call(this);
View
17 examples/browser/assets/conways.css
@@ -152,6 +152,23 @@ input[type="submit"], input[type="button"] {
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.2);
}
+.sudoku .col {
+ width: 50px;
+ height: 50px;
+ font-size: 30px;
+ vertical-align: middle;
+ text-align: center;
+}
+
+.sudoku .col.error {
+ background-color: rgba(255, 0, 0, 0.43);
+}
+
+.sudoku #grid{
+ width: 465px;
+ height: 470px;
+}
+
.col.live {
background-color: black;
}
View
2  examples/browser/rules/conways3d.nools
@@ -71,6 +71,8 @@ rule Transition {
if(cell.transition()){
emit("cell-transition", cell);
transitioned = true;
+ }else{
+ retract(cell);
}
});
emit("evaluate");
View
377 examples/browser/rules/sudoku.nools
@@ -0,0 +1,377 @@
+
+rule "terminate group" {
+ salience: -100;
+ agenda-group: "validate";
+ when {
+ $state: String $state == "validate";
+ }
+ then{
+ console.log("Validation complete." );
+ retract($state);
+ halt();
+ }
+}
+
+
+
+rule "duplicate in cell col" {
+ agenda-group: "validate";
+ when{
+ $c: Cell $c.value;
+ $e: Cell $e.value == $c.value from $c.exCells;
+ }
+ then {
+ emit("invalid", $c);
+ emit("invalid", $e);
+ }
+}
+
+
+rule "halt after setup" {
+ when{
+ $ctr: Counter $ctr.count == 0;
+ }
+ then{
+ console.log("halt after setup");
+ halt();
+ }
+}
+
+
+rule "emergency halt 2" {
+ salience: 100;
+ when{
+ $c: Cell !$c.value && $c.count == 0;
+ }
+ then{
+ console.log("emergency halt cell without a value and no free values");
+ halt();
+ //process.exit();
+ }
+
+}
+
+// A Setting object is inserted to define the value of a Cell.
+// Rule "set a value" updates the cell and all cell groups containing it.
+rule "set a value"{
+ when{
+ // a Setting with row and column number, and a value
+ $s: Setting {rowNo: $rn, colNo: $cn, value: $v};
+
+ // a matching Cell, with no value set
+ $c: Cell $c.rowNo == $rn && $c.colNo == $cn && !$c.value {cellRow: $cr, cellCol: $cc, cellSqr: $cs};
+ }
+ then {
+ // modify the Cell by setting its value
+ modify( $c, function(){ this.blockExcept(); this.value = $v;});
+ emit("set-value", $c);
+ console.log( "set cell " + $c.toString() );
+ modify( $cr, function(){ this.blockValue( $v ) });
+ modify( $cc, function(){ this.blockValue( $v ) });
+ modify( $cs, function(){ this.blockValue( $v ) });
+ }
+}
+
+
+// Rule for removing a value from all cells that are siblings
+// in one of the three cell groups.
+rule "eliminate a value from Cell" {
+ salience: 1;
+ when{
+ // a Setting with row and column number, and a value
+ $s: Setting {rowNo: $rn, colNo: $cn, value: $v};
+
+ // the matching Cell, with the value already set
+ $mc : Cell $mc.rowNo == $rn && $mc.colNo == $cn && $mc.value == $v {exCells: $exCells};
+
+ // for all Cells that are in cahoots with the updated cell
+ $c: Cell $v in $c.free from $exCells;
+ }
+ then{
+ console.log( "clear " + $v + " from cell " + $c.posAsString() + " because of " + $mc.posAsString() );
+ // modify a related Cell by blocking the assigned value
+ modify( $c, function(){ this.blockValue( $v ); });
+ }
+}
+
+//Rule for elimination the Setting fact.
+rule "retract setting"{
+ when {
+ // a Setting with row and column number, and a value
+ $s: Setting {rowNo: $rn, colNo: $cn, value: $v};
+
+ // the matching Cell, with the value already set
+ $c: Cell $c.rowNo == $rn && $c.colNo == $cn && $c.value == $v;
+
+ // This is the negation of the last pattern in the previous rule.
+ // Now the Setting fact can be safely retracted.
+ not( $x: Cell $v in $x.free && $x in $c.exCells);
+ // count down
+ $ctr: Counter {count: $count};
+
+ }
+ then{
+ console.log("retracting " + $s.toString());
+ // Discard the Setter fact.
+ retract( $s );
+ console.log("COUNT = %s", $count);
+ modify( $ctr, function(){ this.count = $count - 1;});
+ console.log( "done setting cell " + $c.toString() );
+ }
+}
+
+
+// Detect a set of candidate values with cardinality 1 for some Cell.
+// This is the value to be set.
+rule "single"{
+ when {
+ // There is currently no setting under way.
+ not($ns: Setting);
+
+ // One element in the "free" set.
+ $c: Cell $c.count == 1 {rowNo: $rn, colNo: $cn};
+ }
+ then {
+ var i = $c.freeValue();
+ console.log( "single " + i + " at " + $c.posAsString() );
+ // Insert another Setter fact.
+ assert( new Setting( $rn, $cn, i ) );
+ }
+}
+
+// Detect a set of candidate values with a value that is the only one
+// in one of its groups. This is the value to be set.
+rule "hidden single" {
+ when{
+ // There is currently no setting under way.
+ not($ns: Setting);
+ not($nc: Cell $nc.count == 1 );
+
+ // Some integer.
+ $i: Number;
+
+ // The "free" set contains this number.
+ $c: Cell $c.count > 1 && $i in $c.free {rowNo: $rn, colNo: $cn};
+
+ // We have a cell group containing this cell $c
+ $cg: CellGroup $c in $cg.cells;
+ // but no other cell from that group contains $i
+ not ($nc2: Cell $nc2 != $c && $i in $nc2.free from $cg.cells);
+ }
+ then {
+ console.log( "hidden single " + $i + " at " + $c.posAsString() );
+ // Insert another Setter fact.
+ assert( new Setting( $rn, $cn, $i ) );
+ }
+}
+
+
+// A "naked pair" is two cells in some cell group with their sets of
+// permissible values being equal with cardinality 2. These two values
+// can be removed from all other candidate lists in the group.
+rule "naked pair" {
+ when {
+ // There is currently no setting under way.
+ not($ns: Setting);
+ not($nc: Cell $nc.count == 1 );
+
+
+ // One cell with two candidates.
+ $c1: Cell $c1.count == 2 {free: $f1, cellRow: $r1, rowNo: $rn1, colNo: $cn1, cellSqr: $b1};
+
+ // The containing cell group.
+ $cg: CellGroup $cg.count > 2 && $c1 in $cg.cells;
+
+ // Another cell with two candidates, not the one we already have.
+ $c2: Cell( $c2 != $c1 && deepEqual($c2.free, $f1) ) from $cg.cells;
+
+ // Get one of the "naked pair".
+ $v: Number from $c1.free;
+
+ // Get some other cell with a candidate equal to one from the pair.
+ $c3: Cell $c3 != $c1 && $c3 != $c2 && $c3.count > 1 && $v in $c3.free from $cg.cells;
+ }
+ then{
+ console.log( "remove " + $v + " from " + $c3.posAsString() + " due to naked pair at " + $c1.posAsString() + ":[" + $f1.join(",") + "] and " + $c2.posAsString() + ":[" + $c2.free.join(",") + "]" );
+ // Remove the value.
+ modify( $c3, function(){ this.blockValue( $v ); });
+ }
+}
+
+// If two cells within the same cell group contain candidate sets with more than
+// two values, with two values being in both of them but in none of the other
+// cells, then we have a "hidden pair". We can remove all other candidates from
+// these two cells.
+//
+rule "hidden pair in row" {
+ when{
+ // There is currently no setting under way.
+ not($ns: Setting);
+ not($nc: Cell $nc.count == 1 );
+
+ // Establish a pair of Integer facts
+ $i1: Number;
+ $i2: Number $i2 > $i1;
+
+ // Look for a Cell with these two among its candidates. (The upper bound on
+ // the number of candidates avoids a lot of useless work during startup.)
+ $c1: Cell $c1.count > 2 && $c1.count < 9 && $i1 in $c1.free && $i2 in $c1.free {rowNo: $rn1, colNo: $cn1, cellRow: $cellRow};
+
+ // Get another one from the same row, with the same pair among its candidates,
+ $c2: Cell $c2 != $c1 && $c2.cellRow == $cellRow && $c2.count > 2 && $i1 in $c2.free && $i2 in $c2.free;
+
+ // Assertain that no other cell in the group has one of these two values.
+ not($nc2: Cell $nc2 != $c1 && $nc2 != $c2 && ($i1 in $nc2.free || $i2 in $nc2.free ) from $cellRow.cells)
+ }
+ then{
+ console.log( "hidden pair in row at " + $c1.posAsString() + " and " + $c2.posAsString() );
+ // Set the candidate lists of these two Cells to the "hidden pair".
+ modify( $c1, function(){this.blockExcept([$i1, $i2]) });
+ modify( $c2, function(){this.blockExcept([$i1, $i2]) });
+ }
+}
+
+rule "hidden pair in column"{
+ when{
+ not($ns: Setting);
+ not($nc: Cell $nc.count == 1 );
+
+ $i1: Number;
+ $i2: Number $i2 > $i1;
+
+ $c1: Cell $c1.count > 2 && $c1.count < 9 && $i1 in $c1.free && $i2 in $c1.free {rowNo: $rn1, colNo: $cn1, cellCol: $cellCol};
+ $c2: Cell $c2 != $c1 && $c2.cellCol == $cellCol && $c2.count > 2 && $i1 in $c2.free && $i2 in $c2.free;
+ not($nc2: Cell $nc2 != $c1 && $nc2 != $c2 && ($i1 in $nc2.free || $i2 in $nc2.free ) from $cellCol.cells)
+ }
+ then{
+ console.log( "hidden pair in column at " + $c1.posAsString() + " and " + $c2.posAsString() );
+ modify( $c1, function(){this.blockExcept([$i1, $i2]) });
+ modify( $c2, function(){this.blockExcept([$i1, $i2]) });
+ }
+}
+
+rule "hidden pair in square" {
+ when{
+ not($ns: Setting);
+ not($nc: Cell $nc.count == 1 );
+
+ $i1: Number;
+ $i2: Number $i2 > $i1;
+
+ $c1: Cell $c1.count > 2 && $c1.count < 9 && $i1 in $c1.free && $i2 in $c1.free {rowNo: $rn1, colNo: $cn1, cellSqr: $cellSqr};
+ $c2: Cell $c2 != $c1 && $c2.cellSqr == $cellSqr && $c2.count > 2 && $i1 in $c2.free && $i2 in $c2.free;
+
+ not($nc2: Cell $nc2 != $c1 && $nc2 != $c2 && ($i1 in $nc2.free || $i2 in $nc2.free ) from $cellSqr.cells)
+ }
+ then{
+ console.log( "hidden pair in square " + $c1.posAsString() + " and " + $c2.posAsString() );
+ modify( $c1, function(){this.blockExcept([$i1, $i2]) });
+ modify( $c2, function(){this.blockExcept([$i1, $i2]) });
+ }
+}
+
+
+rule "X-wings in rows" {
+ when{
+ not($ns: Setting);
+ not($nc: Cell $nc.count == 1 );
+
+ $i: Number;
+
+ $ca1: Cell $ca1.count > 1 && $i in $ca1.free {cellRow: $ra, rowNo: $rano, cellCol: $c1,colNo: $c1no};
+ $cb1: Cell $cb1.count > 1 && $i in $cb1.free && $cb1.rowNo > $rano && $cb1.cellCol == $c1 {cellRow: $rb, rowNo: $rbno};
+ not($nc2: Cell $nc2 != $ca1 && $nc2 != $cb1 && $i in $nc2.free from $c1.cells);
+
+ $ca2: Cell $ca2.count > 1 && $1 in $ca2.free && $ca2.cellRow == $ra && $ca2.colNo > $c1no {cellCol: $c2, colNo: $c2no};
+ $cb2: Cell $cb2.count > 1 && $i in $cb2.free && $cb2.cellRow == $rb && $cb2.cellCol == $c2;
+ not($nc3: Cell $nc3 != $ca2 && $nc3 != $cb2 && $i in $nc3.free from $c2.cells);
+
+ $cx: Cell ($cx.rowNo == $rano || $cx.rowNo == $rbno) && $cx.colNo != $c1no && $cx.colNo != $c2no && $cx.count > 1 && $i in $cx.free;
+ }
+ then{
+
+ console.log( "X-wing with " + $i + " in rows " +
+ $ca1.posAsString() + " - " + $cb1.posAsString() +
+ $ca2.posAsString() + " - " + $cb2.posAsString() + ", remove from " + $cx.posAsString() );
+
+ modify( $cx, function(){ this.blockValue( $i ) });
+ }
+}
+
+rule "X-wings in columns"{
+ when{
+ not($ns: Setting);
+ not($nc: Cell $nc.count == 1 );
+
+ $i: Number;
+
+
+ $ca1: Cell $ca1.count > 1 && $i in $ca1.free {cellRow: $ra, rowNo: $rano, cellCol: $c1,colNo: $c1no};
+
+ $ca2: Cell $ca2.count > 1 && $i in $ca2.free && $ca2.colNo > $c1No && $ca2.cellRow == $ra {cellCol: $c2, colNo: $c2no};
+ not( $nc2: Cell $nc2 != $ca1 && $nc2 != $ca2 && $i in $nc2.free from $ra.cells);
+
+ $cb1: Cell $cb1.count > 1 && $i in $cb1.free && $cb1.cellCol == $c1 && $cb1.rowNo > $rano {cellRow: $rb, rowNo: $rbno};
+ $cb2: Cell $cb2.count > 1 && $i in $cb2.free && $cb2.cellCol == $c2 && $cb2.cellRow == $rb;
+ not($nc3: Cell $nc3 != $cb1 && $nc3 != $cb2 && $i in $nc3.free from $rb.cells);
+
+ $cx: Cell ($cx.colNo == $c1no || $cx.colNo == $c2no) && $cx.rowNo != $rano && $cx.rowNo != $rbno && $cx.count > 1 && $i in $cx.free;
+ }
+ then{
+
+ console.log( "X-wing with " + $i + " in columns " +
+ $ca1.posAsString() + " - " + $ca2.posAsString() +
+ $cb1.posAsString() + " - " + $cb2.posAsString() + ", remove from " + $cx.posAsString() );
+
+ modify( $cx, function(){this.blockValue( $i ) });
+ }
+}
+
+rule "intersection removal column" {
+ when {
+ not($ns: Setting);
+ not($nc: Cell $nc.count == 1 );
+
+ $i: Number;
+
+ // occurs in a Cell
+ $c: Cell $i in $c.free {cellSqr: $cs, cellCol: $cc};
+ // but not in another cell of the same square and a different column
+ not($nc2: Cell $nc2 != $c && $i in $nc2.free && $nc2.cellSqr == $cs && $nc2.cellCol != $cc );
+
+ // but there is a cell in the same column and another sqstuare containing this value
+ $cx: Cell $cx.count > 1 && $i in $cx.free && $cx.cellCol == $cc && $cx.cellSqr != $cs;
+ }
+ then{
+ // remove the value from that other cell
+ //if (explain) {
+ console.log( "column elimination due to " + $c.posAsString() +
+ ": remove " + $i + " from " + $cx.posAsString() );
+ //}
+ modify( $cx, function(){this.blockValue( $i ) });
+ }
+}
+
+rule "intersection removal row"{
+ when{
+ not($ns: Setting);
+ not($nc: Cell $nc.count == 1 );
+
+ $i: Number;
+ // occurs in a Cell
+ $c: Cell $i in $c.free {cellSqr: $cs, cellRow: $cr};
+ // but not in another cell of the same square and a different row
+ not($nc2: Cell $nc2 != $c && $i in $nc2.free && $nc2.cellSqr == $cs && $nc2.cellRow != $cr);
+
+ // but there is a cell in the same row and another square containing this value
+ $cx: Cell $cx.count > 1 && $i in $cx.free && $cx.cellRow == $cr && $cx.cellSqr != $cs;
+ }
+ then {
+ // remove the value from that other cell
+ //if (explain) {
+ console.log( "row elimination due to " + $c.posAsString() +
+ ": remove " + $i + " from " + $cx.posAsString() );
+ //}
+ modify( $cx, function(){ this.blockValue( $i ) });
+ }
+}
View
368 examples/browser/src/sudoku.js
@@ -0,0 +1,368 @@
+(function () {
+ /*global $:true*/
+ var declare = this.declare,
+ arr = this.arrayExtended,
+ unique = arr.unique;
+
+ var allNine = [1, 2, 3, 4, 5, 6, 7, 8, 9];
+
+ var SetOfNine = declare({
+ instance: {
+
+ free: null,
+ count: 0,
+
+ constructor: function () {
+ this.count = (this.free = allNine.slice()).length;
+ },
+
+ blockValue: function (value) {
+ var index = arr.indexOf(this.free, value);
+ if (index !== -1) {
+ this.free.splice(index, 1);
+ this.count = this.free.length;
+ }
+ return this;
+ },
+
+ blockExcept: function (values) {
+ this.free = values || [];
+ this.count = this.free.length;
+ return this;
+ },
+
+ freeValue: function () {
+ return this.free[0];
+ }
+ }
+ }).as(this, "SetOfNine");
+
+ var CellGroup = SetOfNine.extend({
+ instance: {
+ cells: null,
+
+ constructor: function () {
+ this._super(arguments);
+ this.cells = [];
+ },
+
+ addCell: function (cell) {
+ this.cells.push(cell);
+ }
+ }
+ }).as(this, "CellGroup");
+
+
+ var CellFile = CellGroup.extend({
+ instance: {
+ number: null,
+
+ constructor: function (number) {
+ this._super(arguments);
+ this.number = number;
+ },
+
+ toString: function () {
+ var ret = [], cells = this.cells;
+ for (var i = 0, l = cells.length; i < l; i++) {
+ ret.push(cells[i].toString());
+ }
+ return ret.join(", ");
+ }
+ }
+ }).as(this, "CellFile");
+
+ var Cell = SetOfNine.extend({
+ instance: {
+ value: null,
+ cellRow: null,
+ cellCol: null,
+ cellSqr: null,
+ exCells: null,
+ el: null,
+
+ constructor: function () {
+ this._super(arguments);
+ this.exCells = [];
+ },
+
+ makeReferences: function (cr, col, sqr) {
+ this.cellRow = cr;
+ this.cellCol = col;
+ this.cellSqr = sqr;
+ this.colNo = col.number;
+ this.rowNo = cr.number;
+ this.exCells = unique(this.exCells.concat(cr.cells).concat(col.cells).concat(sqr.cells));
+ this.exCells.splice(this.exCells.indexOf(this), 1);
+ return this;
+ },
+
+ toString: function () {
+ return [this.posAsString(), this.valueAsString()].join(": ");
+ },
+
+ valueAsString: function () {
+ return this.value === null ? " ": this.value;
+ },
+
+ posAsString: function () {
+ return ["[", this.cellRow.number, ",", this.cellCol.number, "]"].join("");
+ }
+ }
+ }).as(this, "Cell");
+
+ var CellCol = CellFile.extend({
+ instance: {
+ toString: function () {
+ return ["Column ", this.number, ": ", this._super(arguments)];
+ }
+ }
+ }).as(this, "CellCol");
+
+
+ var CellRow = CellFile.extend({
+ instance: {
+ toString: function () {
+ return ["Row ", this.number, ": ", this._super(arguments)];
+ }
+ }
+ }).as(this, "CellRow");
+
+ var CellSqr = CellGroup.extend({
+ instance: {
+ constructor: function (cr1, cr2, cr3, cc1, cc2, cc3) {
+ this._super(arguments);
+ for (var i = cr1.number; i <= cr3.number; i++) {
+ this.addCell(cc1.cells[i]);
+ this.addCell(cc2.cells[i]);
+ this.addCell(cc3.cells[i]);
+ }
+
+ }
+ }
+ }).as(this, "CellSqr");
+
+ var Counter = declare({
+ instance: {
+ count: 0,
+ constructor: function (count) {
+ this.count = count;
+ }
+ }
+ }).as(this, "Counter");
+
+
+ var Setting = declare({
+ instance: {
+ rowNo: null,
+ colNo: null,
+ value: null,
+
+ constructor: function (row, col, value) {
+ this.rowNo = row;
+ this.colNo = col;
+ this.value = value;
+ },
+
+ toString: function () {
+ return "Setting [" + this.rowNo + "," + this.colNo + "] : " + this.value;
+ }
+
+ }
+ }).as(this, "Setting");
+
+
+ var Stepping = declare({
+ instance: {
+ emergency: false
+ }
+ }).as(this, "Stepping");
+
+ var Sudoku = declare({
+ instance: {
+
+ constructor: function (flow, statsListener) {
+ this.rows = [];
+ this.cols = [];
+ this.sqrs = [];
+ this.cells = [];
+ this.flow = flow;
+ this.statsListener = statsListener;
+ this.session = null;
+ this.stepping = null;
+ },
+
+ stop: function () {
+ if (this.session) {
+ this.session.halt();
+ this.session.dispose();
+ }
+ return this;
+ },
+
+ clear: function () {
+ this.stop();
+ this.setCellValues([]);
+ },
+
+ step: function () {
+ this.session.modify(this.counter, function () {
+ this.count = 1;
+ });
+ if (!this.stepping) {
+ this.session.assert(new Stepping());
+ }
+ return this.session.matchUntilHalt(function (err) {
+ if (err) {
+ console.log(err);
+ }
+ });
+ },
+
+ solve: function () {
+ this.session.modify(this.counter, function () {
+ this.count = Infinity;
+ });
+ if (this.stepping) {
+ this.session.retract(this.stepping);
+ }
+ return this.session.match(function (err) {
+ if (err) {
+ console.log(err);
+ }
+ });
+ },
+
+ setCellValue: function setCellValue(cell) {
+ cell.el.text(cell.value ? cell.value: "");
+ return cell;
+ },
+
+ create: function () {
+ var grid;
+ (grid = $("#grid")).empty();
+ var rows = this.rows, cols = this.cols, sqrs = this.sqrs, session = this.session;
+ for (var i = 0; i < 9; i++) {
+ session.assert(i + 1);
+ rows[i] = new CellRow(i);
+ cols[i] = new CellCol(i);
+ }
+
+ var cells = this.cells = [], iCol;
+ for (var iRow = 0; iRow < 9; iRow++) {
+ var rowDom = $("<div/>").addClass("row").appendTo(grid);
+ cells[iRow] = [];
+ for (iCol = 0; iCol < 9; iCol++) {
+ var cell = cells[iRow][iCol] = new Cell();
+ cell.el = $("<div class='col'></div>").appendTo(rowDom);
+ rows[iRow].addCell(cell);
+ cols[iCol].addCell(cell);
+ }
+ }
+
+ for (i = 0; i < 3; i++) {
+ sqrs[i] = [];
+ for (var j = 0; j < 3; j++) {
+ sqrs[i][j] = new CellSqr(rows[i * 3], rows[i * 3 + 1], rows[i * 3 + 2],
+ cols[j * 3], cols[j * 3 + 1], cols[j * 3 + 2]);
+ }
+ }
+
+ for (iRow = 0; iRow < 9; iRow++) {
+ for (iCol = 0; iCol < 9; iCol++) {
+ session.assert(cells[iRow][iCol].makeReferences(rows[iRow], cols[iCol], sqrs[Math.floor(iRow / 3)][Math.floor(iCol / 3)]));
+ }
+ session.assert(rows[iRow]);
+ session.assert(cols[iRow]);
+ session.assert(sqrs[Math.floor(iRow / 3)][iRow % 3]);
+ }
+ return this;
+ },
+
+ setCellValues: function (cellValues) {
+ this.stop();
+ var session = (this.session = this.statsListener.listen(this.flow.getSession()))
+ .on("set-value", this.setCellValue);
+
+ var s000 = new Setting(0, 0, 0);
+ this.session.assert(s000);
+ this.create();
+
+ var initial = 0;
+ for (var iRow = 0; iRow < 9; iRow++) {
+ var row = cellValues[iRow] || [];
+ for (var iCol = 0; iCol < 9; iCol++) {
+ var value = row[iCol] || null;
+ if (value) {
+ session.assert(new Setting(iRow, iCol, value));
+ initial++;
+ //console.log(initial);
+ }
+ }
+ }
+ var counter = this.counter = new Counter(initial);
+ this.session.assert(counter);
+ this.session.retract(s000);
+ return this.session.matchUntilHalt(function (err) {
+ if (err) {
+ console.log(err);
+ }
+ this.dumpGrid();
+ }.bind(this));
+ },
+
+ dumpGrid: function () {
+ var cells = this.cells;
+ var print = [" "];
+ for (var iCol = 0; iCol < 9; iCol++) {
+ print.push("Col: " + iCol + " ");
+ }
+ console.log(print.join(""));
+ for (var iRow = 0; iRow < 9; iRow++) {
+ print = ["Row " + (iRow) + ": "];
+ for (iCol = 0; iCol < 9; iCol++) {
+ if (cells[iRow][iCol].value !== null) {
+ print.push(" --- " + cells[iRow][iCol].value + " --- ");
+ } else {
+ var perms = cells[iRow][iCol].free, sb = [];
+ for (var i = 1; i <= 9; i++) {
+ if (perms.indexOf(i) !== -1) {
+ sb.push(i);
+ } else {
+ sb.push(' ');
+ }
+ }
+ print.push(" % " + sb.join(""));
+ }
+ }
+ console.log(print.join(""));
+ }
+ },
+
+ setInvalidCellValue: function setCellValue(cell) {
+ cell.el.addClass("error");
+ return cell;
+ },
+
+ validate: function () {
+ this.session.assert("validate");
+ return this.session.focus("validate")
+ .on("invalid", this.setInvalidCellValue)
+ .matchUntilHalt();
+ }
+
+ }
+ });
+
+ this.Sudoku = Sudoku;
+}).call(this);
+
+
+
+
+
+
+
+
+
+
View
90 examples/browser/src/sudokuPatterns.js
@@ -0,0 +1,90 @@
+(function () {
+
+ this.sudokuExamples = {
+ Simple: [
+ [null, 5, 6, 8, null, 1, 9, 4, null],
+ [9, null, null, 6, null, 5, null, null, 3],
+ [7, null, null, 4, 9, 3, null, null, 8],
+ [8, 9, 7, null, 4, null, 6, 3, 5],
+ [null, null, 3, 9, null, 6, 8, null, null],
+ [4, 6, 5, null, 8, null, 2, 9, 1],
+ [5, null, null, 2, 6, 9, null, null, 7],
+ [6, null, null, 5, null, 4, null, null, 9],
+ [null, 4, 9, 7, null, 8, 3, 5, null]
+ ],
+
+ Medium: [
+ [8, 4, 7, null, null, null, 2, 5, 6],
+ [5, null, null, null, 8, null, null, null, 4],
+ [2, null, null, null, 7, null, null, null, 8],
+ [null, null, null, 3, null, 8, null, null, null],
+ [null, 5, 1, null, null, null, 8, 7, 2],
+ [null, null, null, 5, null, 7, null, null, null],
+ [4, null, null, null, 5, null, null, null, 7],
+ [6, null, null, null, 3, null, null, null, 9],
+ [1, 3, 2, null, null, null, 4, 8, 5]
+ ],
+
+ "Hard 1": [
+ [null, null, null, null, 5, 1, null, 8, null],
+ [null, 8, null, null, 4, null, null, null, 5],
+ [null, null, 3, null, null, null, 2, null, null],
+ [null, null, null, null, 6, null, null, null, 9],
+ [6, 7, null, 4, null, 9, null, 1, 3],
+ [8,