├── demo ├── .gitignore ├── assets │ ├── img │ │ ├── glyphicons-halflings-white.png │ │ ├── pacman.gif │ │ └── glyphicons-halflings.png │ ├── ico │ │ ├── favicon.ico │ │ ├── apple-touch-icon-57-precomposed.png │ │ ├── apple-touch-icon-72-precomposed.png │ │ ├── apple-touch-icon-114-precomposed.png │ │ └── apple-touch-icon-144-precomposed.png │ ├── css │ │ ├── styles.css │ │ └── bootstrap-responsive.min.css │ └── js │ │ ├── html5shiv-pre3.6.js │ │ ├── app.js │ │ └── bootstrap.min.js ├── README.md └── index.html ├── .gitignore ├── test ├── index.html ├── qunit │ ├── qunit-1.11.0.css │ └── qunit-1.11.0.js └── tests.js ├── LICENSE ├── README.md └── sudoku.js /demo/.gitignore: -------------------------------------------------------------------------------- 1 | .c9revisions 2 | 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .c9revisions 2 | ref 3 | archive 4 | -------------------------------------------------------------------------------- /demo/assets/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo/assets/img/pacman.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whatever/sudoku.js/master/demo/assets/img/pacman.gif -------------------------------------------------------------------------------- /demo/assets/ico/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whatever/sudoku.js/master/demo/assets/ico/favicon.ico -------------------------------------------------------------------------------- /demo/assets/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whatever/sudoku.js/master/demo/assets/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /demo/assets/ico/apple-touch-icon-57-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whatever/sudoku.js/master/demo/assets/ico/apple-touch-icon-57-precomposed.png -------------------------------------------------------------------------------- /demo/assets/ico/apple-touch-icon-72-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whatever/sudoku.js/master/demo/assets/ico/apple-touch-icon-72-precomposed.png -------------------------------------------------------------------------------- /demo/assets/ico/apple-touch-icon-114-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whatever/sudoku.js/master/demo/assets/ico/apple-touch-icon-114-precomposed.png -------------------------------------------------------------------------------- /demo/assets/ico/apple-touch-icon-144-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whatever/sudoku.js/master/demo/assets/ico/apple-touch-icon-144-precomposed.png -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | Sudoku.js Demo Page 2 | =================== 3 | 4 | Simple demo page for the Sudoku puzzle generator and solver library [Sudoku.js](https://github.com/robatron/sudoku.js). 5 | 6 | View it live [here](http://htmlpreview.github.com/?https://github.com/robatron/sudoku.js/blob/master/demo/index.html). 7 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sudoku.js Test Suite 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /demo/assets/css/styles.css: -------------------------------------------------------------------------------- 1 | /* Styles for the Sudoku.js demo page 2 | */ 3 | 4 | body { 5 | padding-top: 60px; 6 | padding-bottom: 40px; 7 | } 8 | 9 | .container-narrow { 10 | /* Custom, narrow container */ 11 | margin: 0 auto; 12 | max-width: 600px; 13 | } 14 | 15 | #footer { 16 | margin-top: 120px; 17 | } 18 | 19 | #sudoku-board { 20 | border: 2px solid black; 21 | } 22 | 23 | #sudoku-board td { 24 | padding: 0px; 25 | margin: 0px; 26 | } 27 | 28 | #sudoku-board input.square { 29 | display: block; 30 | margin-left:auto; 31 | margin-right:auto; 32 | margin-bottom:0px; 33 | width: 63px; 34 | height: 45px; 35 | padding: 0px; 36 | font-size: 40px; 37 | text-align: center; 38 | } 39 | 40 | #sudoku-board .border-right { 41 | border-right: 2px solid black; 42 | } 43 | 44 | #sudoku-board .border-bottom { 45 | border-bottom: 2px solid black; 46 | } 47 | 48 | .green-text { 49 | color: ForestGreen !important; 50 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Rob McGuire-Dale 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /demo/assets/js/html5shiv-pre3.6.js: -------------------------------------------------------------------------------- 1 | /*! HTML5 Shiv vpre3.6 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 2 | Uncompressed source: https://github.com/aFarkas/html5shiv */ 3 | (function(a,b){function h(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function i(){var a=l.elements;return typeof a=="string"?a.split(" "):a}function j(a){var b={},c=a.createElement,f=a.createDocumentFragment,g=f();a.createElement=function(a){if(!l.shivMethods)return c(a);var f;return b[a]?f=b[a].cloneNode():e.test(a)?f=(b[a]=c(a)).cloneNode():f=c(a),f.canHaveChildren&&!d.test(a)?g.appendChild(f):f},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+i().join().replace(/\w+/g,function(a){return c(a),g.createElement(a),'c("'+a+'")'})+");return n}")(l,g)}function k(a){var b;return a.documentShived?a:(l.shivCSS&&!f&&(b=!!h(a,"article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio{display:none}canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden]{display:none}audio[controls]{display:inline-block;*display:inline;*zoom:1}mark{background:#FF0;color:#000}")),g||(b=!j(a)),b&&(a.documentShived=b),a)}var c=a.html5||{},d=/^<|^(?:button|form|map|select|textarea|object|iframe|option|optgroup)$/i,e=/^<|^(?:a|b|button|code|div|fieldset|form|h1|h2|h3|h4|h5|h6|i|iframe|img|input|label|li|link|ol|option|p|param|q|script|select|span|strong|style|table|tbody|td|textarea|tfoot|th|thead|tr|ul)$/i,f,g;(function(){var c=b.createElement("a");c.innerHTML="",f="hidden"in c,f&&typeof injectElementWithStyles=="function"&&injectElementWithStyles("#modernizr{}",function(b){b.hidden=!0,f=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle).display=="none"}),g=c.childNodes.length==1||function(){try{b.createElement("a")}catch(a){return!0}var c=b.createDocumentFragment();return typeof c.cloneNode=="undefined"||typeof c.createDocumentFragment=="undefined"||typeof c.createElement=="undefined"}()})();var l={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:k};a.html5=l,k(b)})(this,document) -------------------------------------------------------------------------------- /test/qunit/qunit-1.11.0.css: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.11.0 - A JavaScript Unit Testing Framework 3 | * 4 | * http://qunitjs.com 5 | * 6 | * Copyright 2012 jQuery Foundation and other contributors 7 | * Released under the MIT license. 8 | * http://jquery.org/license 9 | */ 10 | 11 | /** Font Family and Sizes */ 12 | 13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 15 | } 16 | 17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 18 | #qunit-tests { font-size: smaller; } 19 | 20 | 21 | /** Resets */ 22 | 23 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { 24 | margin: 0; 25 | padding: 0; 26 | } 27 | 28 | 29 | /** Header */ 30 | 31 | #qunit-header { 32 | padding: 0.5em 0 0.5em 1em; 33 | 34 | color: #8699a4; 35 | background-color: #0d3349; 36 | 37 | font-size: 1.5em; 38 | line-height: 1em; 39 | font-weight: normal; 40 | 41 | border-radius: 5px 5px 0 0; 42 | -moz-border-radius: 5px 5px 0 0; 43 | -webkit-border-top-right-radius: 5px; 44 | -webkit-border-top-left-radius: 5px; 45 | } 46 | 47 | #qunit-header a { 48 | text-decoration: none; 49 | color: #c2ccd1; 50 | } 51 | 52 | #qunit-header a:hover, 53 | #qunit-header a:focus { 54 | color: #fff; 55 | } 56 | 57 | #qunit-testrunner-toolbar label { 58 | display: inline-block; 59 | padding: 0 .5em 0 .1em; 60 | } 61 | 62 | #qunit-banner { 63 | height: 5px; 64 | } 65 | 66 | #qunit-testrunner-toolbar { 67 | padding: 0.5em 0 0.5em 2em; 68 | color: #5E740B; 69 | background-color: #eee; 70 | overflow: hidden; 71 | } 72 | 73 | #qunit-userAgent { 74 | padding: 0.5em 0 0.5em 2.5em; 75 | background-color: #2b81af; 76 | color: #fff; 77 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 78 | } 79 | 80 | #qunit-modulefilter-container { 81 | float: right; 82 | } 83 | 84 | /** Tests: Pass/Fail */ 85 | 86 | #qunit-tests { 87 | list-style-position: inside; 88 | } 89 | 90 | #qunit-tests li { 91 | padding: 0.4em 0.5em 0.4em 2.5em; 92 | border-bottom: 1px solid #fff; 93 | list-style-position: inside; 94 | } 95 | 96 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 97 | display: none; 98 | } 99 | 100 | #qunit-tests li strong { 101 | cursor: pointer; 102 | } 103 | 104 | #qunit-tests li a { 105 | padding: 0.5em; 106 | color: #c2ccd1; 107 | text-decoration: none; 108 | } 109 | #qunit-tests li a:hover, 110 | #qunit-tests li a:focus { 111 | color: #000; 112 | } 113 | 114 | #qunit-tests li .runtime { 115 | float: right; 116 | font-size: smaller; 117 | } 118 | 119 | .qunit-assert-list { 120 | margin-top: 0.5em; 121 | padding: 0.5em; 122 | 123 | background-color: #fff; 124 | 125 | border-radius: 5px; 126 | -moz-border-radius: 5px; 127 | -webkit-border-radius: 5px; 128 | } 129 | 130 | .qunit-collapsed { 131 | display: none; 132 | } 133 | 134 | #qunit-tests table { 135 | border-collapse: collapse; 136 | margin-top: .2em; 137 | } 138 | 139 | #qunit-tests th { 140 | text-align: right; 141 | vertical-align: top; 142 | padding: 0 .5em 0 0; 143 | } 144 | 145 | #qunit-tests td { 146 | vertical-align: top; 147 | } 148 | 149 | #qunit-tests pre { 150 | margin: 0; 151 | white-space: pre-wrap; 152 | word-wrap: break-word; 153 | } 154 | 155 | #qunit-tests del { 156 | background-color: #e0f2be; 157 | color: #374e0c; 158 | text-decoration: none; 159 | } 160 | 161 | #qunit-tests ins { 162 | background-color: #ffcaca; 163 | color: #500; 164 | text-decoration: none; 165 | } 166 | 167 | /*** Test Counts */ 168 | 169 | #qunit-tests b.counts { color: black; } 170 | #qunit-tests b.passed { color: #5E740B; } 171 | #qunit-tests b.failed { color: #710909; } 172 | 173 | #qunit-tests li li { 174 | padding: 5px; 175 | background-color: #fff; 176 | border-bottom: none; 177 | list-style-position: inside; 178 | } 179 | 180 | /*** Passing Styles */ 181 | 182 | #qunit-tests li li.pass { 183 | color: #3c510c; 184 | background-color: #fff; 185 | border-left: 10px solid #C6E746; 186 | } 187 | 188 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 189 | #qunit-tests .pass .test-name { color: #366097; } 190 | 191 | #qunit-tests .pass .test-actual, 192 | #qunit-tests .pass .test-expected { color: #999999; } 193 | 194 | #qunit-banner.qunit-pass { background-color: #C6E746; } 195 | 196 | /*** Failing Styles */ 197 | 198 | #qunit-tests li li.fail { 199 | color: #710909; 200 | background-color: #fff; 201 | border-left: 10px solid #EE5757; 202 | white-space: pre; 203 | } 204 | 205 | #qunit-tests > li:last-child { 206 | border-radius: 0 0 5px 5px; 207 | -moz-border-radius: 0 0 5px 5px; 208 | -webkit-border-bottom-right-radius: 5px; 209 | -webkit-border-bottom-left-radius: 5px; 210 | } 211 | 212 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 213 | #qunit-tests .fail .test-name, 214 | #qunit-tests .fail .module-name { color: #000000; } 215 | 216 | #qunit-tests .fail .test-actual { color: #EE5757; } 217 | #qunit-tests .fail .test-expected { color: green; } 218 | 219 | #qunit-banner.qunit-fail { background-color: #EE5757; } 220 | 221 | 222 | /** Result */ 223 | 224 | #qunit-testresult { 225 | padding: 0.5em 0.5em 0.5em 2.5em; 226 | 227 | color: #2b81af; 228 | background-color: #D2E0E6; 229 | 230 | border-bottom: 1px solid white; 231 | } 232 | #qunit-testresult .module-name { 233 | font-weight: bold; 234 | } 235 | 236 | /** Fixture */ 237 | 238 | #qunit-fixture { 239 | position: absolute; 240 | top: -10000px; 241 | left: -10000px; 242 | width: 1000px; 243 | height: 1000px; 244 | } 245 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | Sudoku.js Demo 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 29 | 31 | 33 | 35 | 36 | 37 | 38 |
39 |

40 | Loading... 41 |

42 |
43 | 172 | 173 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Sudoku.js 2 | ========== 3 | 4 | A Sudoku puzzle **generator** and **solver** JavaScript library. 5 | 6 | Check out the [online demo][demo] to see it in action. 7 | 8 | Implementation ideas borrowed from 9 | ["Solving Every Sudoku Puzzle"][norvig-sudoku] by 10 | [Peter Norvig][norvig], and a [generator/solver][anderson-sudoku] by 11 | [Michael Anderson][anderson]. 12 | 13 | [demo]:http://htmlpreview.github.io/?https://github.com/robatron/sudoku.js/blob/master/demo/index.html 14 | 15 | Intro 16 | -------------------------------------------------------------------------------- 17 | 18 | Puzzles are represented by a string of digits, 1-9, and '.' as spaces. Each 19 | character represents a square, e.g., 20 | 21 | "52...6.........7.13...........4..8..6......5...........418.........3..2...87....." 22 | 23 | Represents the following board: 24 | 25 | 5 2 . | . . 6 | . . . 26 | . . . | . . . | 7 . 1 27 | 3 . . | . . . | . . . 28 | ------+-------+------ 29 | . . . | 4 . . | 8 . . 30 | 6 . . | . . . | . 5 . 31 | . . . | . . . | . . . 32 | ------+-------+------ 33 | . 4 1 | 8 . . | . . . 34 | . . . | . 3 . | . 2 . 35 | . . 8 | 7 . . | . . . 36 | 37 | (See the included [converstion functions](#board-string-%E2%86%94-grid) to 38 | convert between string representations and grids, i.e., two-dimensional arrays.) 39 | 40 | 41 | Generate a Sudoku puzzle 42 | -------------------------------------------------------------------------------- 43 | 44 | Generage a Sudoku puzzle of a particular difficulty, e.g, 45 | 46 | ```javascript 47 | >>> sudoku.generate("easy") 48 | "672819345193..4862485..3197824137659761945283359...714.38..1426.174.6.38.463...71" 49 | 50 | >>> sudoku.generate("medium") 51 | "8.4.71.9.976.3....5.196....3.7495...692183...4.5726..92483591..169847...753612984" 52 | 53 | >>> sudoku.generate("hard") 54 | ".17..69..356194.2..89..71.6.65...273872563419.43...685521......798..53..634...59." 55 | ``` 56 | 57 | Valid difficulties are as follows, and represent the number of given squares: 58 | 59 | "easy": 62 60 | "medium": 53 61 | "hard": 44 62 | "very-hard": 35 63 | "insane": 26 64 | "inhuman": 17 65 | 66 | 67 | You may also enter a custom number of squares to give, e.g., 68 | 69 | ```javascript 70 | >>> sudoku.generate(60) 71 | "8941376521532687497269548...72.9158.538.4219..19.852..3874.69.52415793689658.34.." 72 | ``` 73 | 74 | The number of givens must be a number between 17 and 81 inclusive. If it's 75 | outside of that range, the number of givens will be set to the closest bound, 76 | e.g., 0 will be treated as 17, and 100 as 81. 77 | 78 | 79 | By default, the puzzles should have unique solutions, unless you set `unique` to 80 | false, e.g., 81 | 82 | ```javascript 83 | sudoku.generate("easy", false) 84 | ``` 85 | 86 | Note: **Puzzle uniqueness is not yet implemented**, so puzzles are *not* 87 | guaranteed to have unique solutions. 88 | 89 | 90 | Solve a Sudoku puzzle 91 | -------------------------------------------------------------------------------- 92 | 93 | Solve a Sudoku puzzle given a Sudoku puzzle represented as a string, e.g., 94 | 95 | ```javascript 96 | >>> sudoku.solve(".17..69..356194.2..89..71.6.65...273872563419.43...685521......798..53..634...59."); 97 | "217386954356194728489257136165948273872563419943712685521439867798625341634871592" 98 | ``` 99 | 100 | 101 | Board string ↔ grid 102 | -------------------------------------------------------------------------------- 103 | 104 | Board string → grid: 105 | 106 | ```javascript 107 | >>> sudoku.board_string_to_grid("23.94.67.8..3259149..76.32.1.....7925.321.4864..68.5317..1....96598721433...9...7") 108 | [ 109 | ["2","3",".","9","4",".","6","7","."], 110 | ["8",".",".","3","2","5","9","1","4"], 111 | ["9",".",".","7","6",".","3","2","."], 112 | ["1",".",".",".",".",".","7","9","2"], 113 | ["5",".","3","2","1",".","4","8","6"], 114 | ["4",".",".","6","8",".","5","3","1"], 115 | ["7",".",".","1",".",".",".",".","9"], 116 | ["6","5","9","8","7","2","1","4","3"], 117 | ["3",".",".",".","9",".",".",".","7"] 118 | ] 119 | ``` 120 | 121 | Board grid → string: 122 | 123 | ```javascript 124 | >>> sudoku.board_grid_to_string([ 125 | ["2","3",".","9","4",".","6","7","."], 126 | ["8",".",".","3","2","5","9","1","4"], 127 | ["9",".",".","7","6",".","3","2","."], 128 | ["1",".",".",".",".",".","7","9","2"], 129 | ["5",".","3","2","1",".","4","8","6"], 130 | ["4",".",".","6","8",".","5","3","1"], 131 | ["7",".",".","1",".",".",".",".","9"], 132 | ["6","5","9","8","7","2","1","4","3"], 133 | ["3",".",".",".","9",".",".",".","7"] 134 | ]) 135 | "23.94.67.8..3259149..76.32.1.....7925.321.4864..68.5317..1....96598721433...9...7" 136 | ``` 137 | 138 | 139 | Get candidates 140 | -------------------------------------------------------------------------------- 141 | 142 | Get a grid of squares and their candidate values, propagating constraints, i.e., 143 | candidates restrict their peer candidates. 144 | 145 | ```javascript 146 | >>> sudoku.get_candidates("4.25..389....4.265..523.147..1652.7.6..1945322543876915....3.1....4..9.....8....3") 147 | [ 148 | ["4", "167", "2", "5", "167", "16", "3", "8", "9" ], 149 | ["13789", "13789", "3789", "79", "4", "189", "2", "6", "5" ], 150 | ["89", "689", "5", "2", "3", "689", "1", "4", "7" ], 151 | ["389", "389", "1", "6", "5", "2", "48", "7", "48" ], 152 | ["6", "78", "78", "1", "9", "4", "5", "3", "2" ], 153 | ["2", "5", "4", "3", "8", "7", "6", "9", "1" ], 154 | ["5", "246789", "6789", "79", "267", "3", "478", "1", "468"], 155 | ["1378", "123678", "3678", "4", "1267", "156", "9", "25", "68" ], 156 | ["179", "124679", "679", "8", "1267", "1569", "47", "25", "3" ] 157 | ] 158 | ``` 159 | 160 | 161 | Print a board to the console 162 | ---------------------------- 163 | 164 | ```javascript 165 | >>> sudoku.print_board(".17..69..356194.2..89..71.6.65...273872563419.43...685521......798..53..634...59."); 166 | . 1 7 . . 6 9 . . 167 | 3 5 6 1 9 4 . 2 . 168 | . 8 9 . . 7 1 . 6 169 | 170 | . 6 5 . . . 2 7 3 171 | 8 7 2 5 6 3 4 1 9 172 | . 4 3 . . . 6 8 5 173 | 174 | 5 2 1 . . . . . . 175 | 7 9 8 . . 5 3 . . 176 | 6 3 4 . . . 5 9 . 177 | ``` 178 | 179 | 180 | References: 181 | ----------- 182 | 183 | - ["Solving Every Sudoku Puzzle"][norvig-sudoku] by [Peter Norvig][norvig] 184 | - Sudoku [generator/solver][anderson-sudoku] for Mac OS X by [Michael Anderson][anderson] 185 | - [95 Sudoku Puzzles][95-sudokus] 186 | - Andrew Stuart's [online Sudoku Solver][stuart-sudoku] 187 | 188 | 189 | [norvig-sudoku]: http://norvig.com/sudoku.html 190 | [anderson-sudoku]: https://github.com/andermic/cousins/tree/master/sudoku 191 | [stuart-sudoku]: http://www.sudokuwiki.org/sudoku.htm 192 | [95-sudokus]: http://magictour.free.fr/top95 193 | [norvig]: http://norvig.com 194 | [anderson]: https://github.com/andermic/ 195 | 196 | 197 | 198 | License: 199 | -------- 200 | 201 | The MIT License (MIT) 202 | 203 | Copyright (c) 2014 Rob McGuire-Dale 204 | 205 | Permission is hereby granted, free of charge, to any person obtaining a copy 206 | of this software and associated documentation files (the "Software"), to deal 207 | in the Software without restriction, including without limitation the rights 208 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 209 | copies of the Software, and to permit persons to whom the Software is 210 | furnished to do so, subject to the following conditions: 211 | 212 | The above copyright notice and this permission notice shall be included in all 213 | copies or substantial portions of the Software. 214 | 215 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 216 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 217 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 218 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 219 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 220 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 221 | SOFTWARE. 222 | -------------------------------------------------------------------------------- /demo/assets/js/app.js: -------------------------------------------------------------------------------- 1 | /* 2 | Quick-n-dirty demo page for Sudoku.js. 3 | 4 | For more information, please see https://github.com/robatron/sudoku.js 5 | */ 6 | 7 | // Selectors 8 | var BOARD_SEL = "#sudoku-board"; 9 | var TABS_SEL = "#generator-tabs"; 10 | var MESSAGE_SEL = "#message"; 11 | var PUZZLE_CONTROLS_SEL = "#puzzle-controls"; 12 | var IMPORT_CONTROLS_SEL = "#import-controls"; 13 | var SOLVER_CONTROLS_SEL = "#solver-controls"; 14 | 15 | // Boards 16 | // TODO: Cache puzzles as strings instead of grids to cut down on conversions? 17 | var boards = { 18 | "easy": null, 19 | "medium": null, 20 | "hard": null, 21 | "very-hard": null, 22 | "insane": null, 23 | "inhuman": null, 24 | "import": null, 25 | }; 26 | 27 | var build_board = function(){ 28 | /* Build the Sudoku board markup 29 | 30 | TODO: Hardcode the result 31 | */ 32 | for(var r = 0; r < 9; ++r){ 33 | var $row = $("", {}); 34 | for(var c = 0; c < 9; ++c){ 35 | var $square = $("", {}); 36 | if(c % 3 == 2 && c != 8){ 37 | $square.addClass("border-right"); 38 | } 39 | $square.append( 40 | $("", { 41 | id: "row" + r + "-col" + c, 42 | class: "square", 43 | maxlength: "9", 44 | type: "text" 45 | }) 46 | ); 47 | $row.append($square); 48 | } 49 | if(r % 3 == 2 && r != 8){ 50 | $row.addClass("border-bottom"); 51 | } 52 | $(BOARD_SEL).append($row); 53 | } 54 | }; 55 | 56 | var init_board = function(){ 57 | /* Initialize board interactions 58 | */ 59 | $(BOARD_SEL + " input.square").change(function(){ 60 | /* Resize font size in each square depending on how many characters are 61 | in it. 62 | */ 63 | var $square = $(this); 64 | var nr_digits = $square.val().length; 65 | var font_size = "40px"; 66 | if(nr_digits === 3){ 67 | font_size = "35px"; 68 | } else if(nr_digits === 4){ 69 | font_size = "25px"; 70 | } else if(nr_digits === 5){ 71 | font_size = "20px"; 72 | } else if(nr_digits === 6){ 73 | font_size = "17px"; 74 | } else if(nr_digits === 7){ 75 | font_size = "14px"; 76 | } else if(nr_digits === 8){ 77 | font_size = "13px"; 78 | } else if(nr_digits >= 9){ 79 | font_size = "11px"; 80 | } 81 | $(this).css("font-size", font_size); 82 | }); 83 | $(BOARD_SEL + " input.square").keyup(function(){ 84 | /* Fire a change event on keyup, enforce digits 85 | */ 86 | $(this).change(); 87 | }); 88 | 89 | }; 90 | 91 | var init_tabs = function(){ 92 | /* Initialize the Sudoku generator tabs 93 | */ 94 | $(TABS_SEL + " a").click(function(e){ 95 | e.preventDefault(); 96 | var $t = $(this); 97 | var t_name = $t.attr("id"); 98 | 99 | // Hide any error messages 100 | $(MESSAGE_SEL).hide(); 101 | 102 | // If it's the import tab 103 | if(t_name === "import"){ 104 | $(PUZZLE_CONTROLS_SEL).hide(); 105 | $(IMPORT_CONTROLS_SEL).show(); 106 | 107 | // Otherwise it's a normal difficulty tab 108 | } else { 109 | $(PUZZLE_CONTROLS_SEL).show(); 110 | $(IMPORT_CONTROLS_SEL).hide(); 111 | } 112 | show_puzzle(t_name); 113 | $t.tab('show'); 114 | }); 115 | }; 116 | 117 | var init_controls = function(){ 118 | /* Initialize the controls 119 | */ 120 | 121 | // Puzzle controls 122 | $(PUZZLE_CONTROLS_SEL + " #refresh").click(function(e){ 123 | /* Refresh the current puzzle 124 | */ 125 | e.preventDefault(); 126 | var tab_name = get_tab(); 127 | if(tab_name !== "import"){ 128 | show_puzzle(tab_name, true); 129 | } 130 | }); 131 | 132 | // Import controls 133 | $(IMPORT_CONTROLS_SEL + " #import-string").change(function(){ 134 | /* Update the board to reflect the import string 135 | */ 136 | var import_val = $(this).val(); 137 | var processed_board = ""; 138 | for(var i = 0; i < 81; ++i){ 139 | if(typeof import_val[i] !== "undefined" && 140 | (sudoku._in(import_val[i], sudoku.DIGITS) || 141 | import_val[i] === sudoku.BLANK_CHAR)){ 142 | processed_board += import_val[i]; 143 | } else { 144 | processed_board += sudoku.BLANK_CHAR; 145 | } 146 | } 147 | boards["import"] = sudoku.board_string_to_grid(processed_board); 148 | show_puzzle("import"); 149 | }); 150 | $(IMPORT_CONTROLS_SEL + " #import-string").keyup(function(){ 151 | /* Fire a change event on keyup, enforce digits 152 | */ 153 | $(this).change(); 154 | }); 155 | 156 | // Solver controls 157 | $(SOLVER_CONTROLS_SEL + " #solve").click(function(e){ 158 | /* Solve the current puzzle 159 | */ 160 | e.preventDefault(); 161 | solve_puzzle(get_tab()); 162 | }); 163 | 164 | $(SOLVER_CONTROLS_SEL + " #get-candidates").click(function(e){ 165 | /* Get candidates for the current puzzle 166 | */ 167 | e.preventDefault(); 168 | get_candidates(get_tab()); 169 | }); 170 | }; 171 | 172 | var init_message = function(){ 173 | /* Initialize the message bar 174 | */ 175 | 176 | //Hide initially 177 | $(MESSAGE_SEL).hide(); 178 | } 179 | 180 | var solve_puzzle = function(puzzle){ 181 | /* Solve the specified puzzle and show it 182 | */ 183 | 184 | // Solve only if it's a valid puzzle 185 | if(typeof boards[puzzle] !== "undefined"){ 186 | display_puzzle(boards[puzzle], true); 187 | 188 | var error = false; 189 | try{ 190 | var solved_board = 191 | sudoku.solve(sudoku.board_grid_to_string(boards[puzzle])); 192 | } catch(e) { 193 | error = true; 194 | } 195 | 196 | // Display the solved puzzle if solved successfully, display error if 197 | // unable to solve. 198 | if(solved_board && !error){ 199 | display_puzzle(sudoku.board_string_to_grid(solved_board), true); 200 | $(MESSAGE_SEL).hide(); 201 | } else { 202 | $(MESSAGE_SEL + " #text") 203 | .html("Unable to solve! " 204 | + "Check puzzle and try again."); 205 | $(MESSAGE_SEL).show(); 206 | } 207 | } 208 | }; 209 | 210 | var get_candidates = function(puzzle){ 211 | /* Get the candidates for the specified puzzle and show it 212 | */ 213 | 214 | // Get candidates only if it's a valid puzzle 215 | if(typeof boards[puzzle] !== "undefined"){ 216 | display_puzzle(boards[puzzle], true); 217 | 218 | var error = false; 219 | try{ 220 | var candidates = 221 | sudoku.get_candidates( 222 | sudoku.board_grid_to_string(boards[puzzle]) 223 | ); 224 | } catch(e) { 225 | error = true; 226 | } 227 | 228 | // Display the candidates if solved successfully, display error if 229 | // unable to solve. 230 | if(candidates && !error){ 231 | display_puzzle(candidates, true); 232 | $(MESSAGE_SEL).hide(); 233 | } else { 234 | $(MESSAGE_SEL + " #text") 235 | .html("Unable to display candidates! " + 236 | "Contradictions encountered. Check puzzle and try again."); 237 | $(MESSAGE_SEL).show(); 238 | } 239 | } 240 | } 241 | 242 | var show_puzzle = function(puzzle, refresh){ 243 | /* Show the puzzle of the specified puzzle. If the board has not been 244 | generated yet, generate a new one and save. Optionally, set `refresh` to 245 | force a refresh of the specified puzzle. 246 | */ 247 | 248 | // default refresh to false 249 | refresh = refresh || false; 250 | 251 | // If not a valid puzzle, default -> "easy" 252 | if(typeof boards[puzzle] === "undefined"){ 253 | puzzle = "easy"; 254 | } 255 | 256 | // If the board at the specified puzzle doesn't exist yet, or `refresh` 257 | // is set, generate a new one 258 | if(boards[puzzle] === null || refresh){ 259 | if(puzzle === "import"){ 260 | boards[puzzle] = sudoku.board_string_to_grid(sudoku.BLANK_BOARD); 261 | } else { 262 | boards[puzzle] = 263 | sudoku.board_string_to_grid(sudoku.generate(puzzle)); 264 | } 265 | } 266 | 267 | // Display the puzzle 268 | display_puzzle(boards[puzzle]); 269 | } 270 | 271 | var display_puzzle = function(board, highlight){ 272 | /* Display a Sudoku puzzle on the board, optionally highlighting the new 273 | values, with green if `highlight` is set. Additionally do not disable the 274 | new value squares. 275 | */ 276 | for(var r = 0; r < 9; ++r){ 277 | for(var c = 0; c < 9; ++c){ 278 | var $square = $(BOARD_SEL + " input#row" + r + "-col" + c); 279 | $square.removeClass("green-text"); 280 | $square.attr("disabled", "disabled"); 281 | if(board[r][c] != sudoku.BLANK_CHAR){ 282 | var board_val = board[r][c]; 283 | var square_val = $square.val(); 284 | if(highlight && board_val != square_val){ 285 | $square.addClass("green-text"); 286 | } 287 | $square.val(board_val); 288 | } else { 289 | $square.val(''); 290 | } 291 | // Fire off a change event on the square 292 | $square.change(); 293 | } 294 | } 295 | }; 296 | 297 | var get_tab = function(){ 298 | /* Return the name of the currently-selected tab 299 | */ 300 | return $(TABS_SEL + " li.active a").attr("id"); 301 | }; 302 | 303 | var click_tab = function(tab_name){ 304 | /* Click the specified tab by name 305 | */ 306 | $(TABS_SEL + " #" + tab_name).click(); 307 | }; 308 | 309 | // "Main" (document ready) 310 | $(function(){ 311 | build_board(); 312 | init_board(); 313 | init_tabs(); 314 | init_controls(); 315 | init_message(); 316 | 317 | // Initialize tooltips 318 | $("[rel='tooltip']").tooltip(); 319 | 320 | // Start with generating an easy puzzle 321 | click_tab("easy"); 322 | 323 | // Hide the loading screen, show the app 324 | $("#app-wrap").removeClass("hidden"); 325 | $("#loading").addClass("hidden"); 326 | }); -------------------------------------------------------------------------------- /demo/assets/css/bootstrap-responsive.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.2.2 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | */@-ms-viewport{width:device-width}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .dropdown-menu a:hover{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:hover{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} 10 | -------------------------------------------------------------------------------- /test/tests.js: -------------------------------------------------------------------------------- 1 | /* Unit tests for sudoku.js 2 | */ 3 | 4 | // 95 "difficult" puzzles from http://magictour.free.fr/top95 5 | var TEST_PUZZLES = [ 6 | "4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......", 7 | "52...6.........7.13...........4..8..6......5...........418.........3..2...87.....", 8 | "6.....8.3.4.7.................5.4.7.3..2.....1.6.......2.....5.....8.6......1....", 9 | "48.3............71.2.......7.5....6....2..8.............1.76...3.....4......5....", 10 | "....14....3....2...7..........9...3.6.1.............8.2.....1.4....5.6.....7.8...", 11 | "......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3.", 12 | "6.2.5.........3.4..........43...8....1....2........7..5..27...........81...6.....", 13 | ".524.........7.1..............8.2...3.....6...9.5.....1.6.3...........897........", 14 | "6.2.5.........4.3..........43...8....1....2........7..5..27...........81...6.....", 15 | ".923.........8.1...........1.7.4...........658.........6.5.2...4.....7.....9.....", 16 | "6..3.2....5.....1..........7.26............543.........8.15........4.2........7..", 17 | ".6.5.1.9.1...9..539....7....4.8...7.......5.8.817.5.3.....5.2............76..8...", 18 | "..5...987.4..5...1..7......2...48....9.1.....6..2.....3..6..2.......9.7.......5..", 19 | "3.6.7...........518.........1.4.5...7.....6.....2......2.....4.....8.3.....5.....", 20 | "1.....3.8.7.4..............2.3.1...........958.........5.6...7.....8.2...4.......", 21 | "6..3.2....4.....1..........7.26............543.........8.15........4.2........7..", 22 | "....3..9....2....1.5.9..............1.2.8.4.6.8.5...2..75......4.1..6..3.....4.6.", 23 | "45.....3....8.1....9...........5..9.2..7.....8.........1..4..........7.2...6..8..", 24 | ".237....68...6.59.9.....7......4.97.3.7.96..2.........5..47.........2....8.......", 25 | "..84...3....3.....9....157479...8........7..514.....2...9.6...2.5....4......9..56", 26 | ".98.1....2......6.............3.2.5..84.........6.........4.8.93..5...........1..", 27 | "..247..58..............1.4.....2...9528.9.4....9...1.........3.3....75..685..2...", 28 | "4.....8.5.3..........7......2.....6.....5.4......1.......6.3.7.5..2.....1.9......", 29 | ".2.3......63.....58.......15....9.3....7........1....8.879..26......6.7...6..7..4", 30 | "1.....7.9.4...72..8.........7..1..6.3.......5.6..4..2.........8..53...7.7.2....46", 31 | "4.....3.....8.2......7........1...8734.......6........5...6........1.4...82......", 32 | ".......71.2.8........4.3...7...6..5....2..3..9........6...7.....8....4......5....", 33 | "6..3.2....4.....8..........7.26............543.........8.15........8.2........7..", 34 | ".47.8...1............6..7..6....357......5....1..6....28..4.....9.1...4.....2.69.", 35 | "......8.17..2........5.6......7...5..1....3...8.......5......2..4..8....6...3....", 36 | "38.6.......9.......2..3.51......5....3..1..6....4......17.5..8.......9.......7.32", 37 | "...5...........5.697.....2...48.2...25.1...3..8..3.........4.7..13.5..9..2...31..", 38 | ".2.......3.5.62..9.68...3...5..........64.8.2..47..9....3.....1.....6...17.43....", 39 | ".8..4....3......1........2...5...4.69..1..8..2...........3.9....6....5.....2.....", 40 | "..8.9.1...6.5...2......6....3.1.7.5.........9..4...3...5....2...7...3.8.2..7....4", 41 | "4.....5.8.3..........7......2.....6.....5.8......1.......6.3.7.5..2.....1.8......", 42 | "1.....3.8.6.4..............2.3.1...........958.........5.6...7.....8.2...4.......", 43 | "1....6.8..64..........4...7....9.6...7.4..5..5...7.1...5....32.3....8...4........", 44 | "249.6...3.3....2..8.......5.....6......2......1..4.82..9.5..7....4.....1.7...3...", 45 | "...8....9.873...4.6..7.......85..97...........43..75.......3....3...145.4....2..1", 46 | "...5.1....9....8...6.......4.1..........7..9........3.8.....1.5...2..4.....36....", 47 | "......8.16..2........7.5......6...2..1....3...8.......2......7..3..8....5...4....", 48 | ".476...5.8.3.....2.....9......8.5..6...1.....6.24......78...51...6....4..9...4..7", 49 | ".....7.95.....1...86..2.....2..73..85......6...3..49..3.5...41724................", 50 | ".4.5.....8...9..3..76.2.....146..........9..7.....36....1..4.5..6......3..71..2..", 51 | ".834.........7..5...........4.1.8..........27...3.....2.6.5....5.....8........1..", 52 | "..9.....3.....9...7.....5.6..65..4.....3......28......3..75.6..6...........12.3.8", 53 | ".26.39......6....19.....7.......4..9.5....2....85.....3..2..9..4....762.........4", 54 | "2.3.8....8..7...........1...6.5.7...4......3....1............82.5....6...1.......", 55 | "6..3.2....1.....5..........7.26............843.........8.15........8.2........7..", 56 | "1.....9...64..1.7..7..4.......3.....3.89..5....7....2.....6.7.9.....4.1....129.3.", 57 | ".........9......84.623...5....6...453...1...6...9...7....1.....4.5..2....3.8....9", 58 | ".2....5938..5..46.94..6...8..2.3.....6..8.73.7..2.........4.38..7....6..........5", 59 | "9.4..5...25.6..1..31......8.7...9...4..26......147....7.......2...3..8.6.4.....9.", 60 | "...52.....9...3..4......7...1.....4..8..453..6...1...87.2........8....32.4..8..1.", 61 | "53..2.9...24.3..5...9..........1.827...7.........981.............64....91.2.5.43.", 62 | "1....786...7..8.1.8..2....9........24...1......9..5...6.8..........5.9.......93.4", 63 | "....5...11......7..6.....8......4.....9.1.3.....596.2..8..62..7..7......3.5.7.2..", 64 | ".47.2....8....1....3....9.2.....5...6..81..5.....4.....7....3.4...9...1.4..27.8..", 65 | "......94.....9...53....5.7..8.4..1..463...........7.8.8..7.....7......28.5.26....", 66 | ".2......6....41.....78....1......7....37.....6..412....1..74..5..8.5..7......39..", 67 | "1.....3.8.6.4..............2.3.1...........758.........7.5...6.....8.2...4.......", 68 | "2....1.9..1..3.7..9..8...2.......85..6.4.........7...3.2.3...6....5.....1.9...2.5", 69 | "..7..8.....6.2.3...3......9.1..5..6.....1.....7.9....2........4.83..4...26....51.", 70 | "...36....85.......9.4..8........68.........17..9..45...1.5...6.4....9..2.....3...", 71 | "34.6.......7.......2..8.57......5....7..1..2....4......36.2..1.......9.......7.82", 72 | "......4.18..2........6.7......8...6..4....3...1.......6......2..5..1....7...3....", 73 | ".4..5..67...1...4....2.....1..8..3........2...6...........4..5.3.....8..2........", 74 | ".......4...2..4..1.7..5..9...3..7....4..6....6..1..8...2....1..85.9...6.....8...3", 75 | "8..7....4.5....6............3.97...8....43..5....2.9....6......2...6...7.71..83.2", 76 | ".8...4.5....7..3............1..85...6.....2......4....3.26............417........", 77 | "....7..8...6...5...2...3.61.1...7..2..8..534.2..9.......2......58...6.3.4...1....", 78 | "......8.16..2........7.5......6...2..1....3...8.......2......7..4..8....5...3....", 79 | ".2..........6....3.74.8.........3..2.8..4..1.6..5.........1.78.5....9..........4.", 80 | ".52..68.......7.2.......6....48..9..2..41......1.....8..61..38.....9...63..6..1.9", 81 | "....1.78.5....9..........4..2..........6....3.74.8.........3..2.8..4..1.6..5.....", 82 | "1.......3.6.3..7...7...5..121.7...9...7........8.1..2....8.64....9.2..6....4.....", 83 | "4...7.1....19.46.5.....1......7....2..2.3....847..6....14...8.6.2....3..6...9....", 84 | "......8.17..2........5.6......7...5..1....3...8.......5......2..3..8....6...4....", 85 | "963......1....8......2.5....4.8......1....7......3..257......3...9.2.4.7......9..", 86 | "15.3......7..4.2....4.72.....8.........9..1.8.1..8.79......38...........6....7423", 87 | "..........5724...98....947...9..3...5..9..12...3.1.9...6....25....56.....7......6", 88 | "....75....1..2.....4...3...5.....3.2...8...1.......6.....1..48.2........7........", 89 | "6.....7.3.4.8.................5.4.8.7..2.....1.3.......2.....5.....7.9......1....", 90 | "....6...4..6.3....1..4..5.77.....8.5...8.....6.8....9...2.9....4....32....97..1..", 91 | ".32.....58..3.....9.428...1...4...39...6...5.....1.....2...67.8.....4....95....6.", 92 | "...5.3.......6.7..5.8....1636..2.......4.1.......3...567....2.8..4.7.......2..5..", 93 | ".5.3.7.4.1.........3.......5.8.3.61....8..5.9.6..1........4...6...6927....2...9..", 94 | "..5..8..18......9.......78....4.....64....9......53..2.6.........138..5....9.714.", 95 | "..........72.6.1....51...82.8...13..4.........37.9..1.....238..5.4..9.........79.", 96 | "...658.....4......12............96.7...3..5....2.8...3..19..8..3.6.....4....473..", 97 | ".2.3.......6..8.9.83.5........2...8.7.9..5........6..4.......1...1...4.22..7..8.9", 98 | ".5..9....1.....6.....3.8.....8.4...9514.......3....2..........4.8...6..77..15..6.", 99 | ".....2.......7...17..3...9.8..7......2.89.6...13..6....9..5.824.....891..........", 100 | "3...8.......7....51..............36...2..4....7...........6.13..452...........8.." 101 | ] 102 | 103 | 104 | // Solve 105 | // ===== 106 | module('Solve'); 107 | 108 | test("Solve", function(){ 109 | var puz1 = [ 110 | "52...6.........7.13...........4..8..6......5...........418.........3."+ 111 | ".2...87.....", 112 | "527316489896542731314987562172453896689271354453698217941825673765134"+ 113 | "928238769145" 114 | ] 115 | var puz2 = [ 116 | "6.....8.3.4.7.................5.4.7.3..2.....1.6.......2.....5.....8."+ 117 | "6......1....", 118 | "617459823248736915539128467982564371374291586156873294823647159791385"+ 119 | "642465912738" 120 | ] 121 | var puz3 = [ 122 | ".........9......84.623...5....6...453...1...6...9...7....1.....4.5..2"+ 123 | "....3.8....9", 124 | "174589362953261784862347951219673845387415296546928173628194537495732"+ 125 | "618731856429" 126 | ] 127 | var puz_unsolvable = 128 | ".........9......84.623...5....6...453...1...6...9...7....1.....4.5..2"+ 129 | "....3.8...99"; 130 | var puz_unsolvable2 = "5168497323.46.2...5.97...65135.6.9.7472591..696837."+ 131 | ".5.253186.746842.75..791.5.6.8"; 132 | var puz_too_big = 133 | ".........9......84.623...5....6...453...1...6...9...7....1.....4.5..2"+ 134 | "....3.8....91"; 135 | var puz_invalid_chars = 136 | ".........9......84.623...5....6...453...1...6...9...7....1.....4.5..2"+ 137 | "....3.8..!.9"; 138 | 139 | // Solve some hard puzzles with known answers 140 | ok(sudoku.solve(puz1[0]) === puz1[1], puz1[0] + " -> " + puz1[1]); 141 | ok(sudoku.solve(puz2[0]) === puz2[1], puz2[0] + " -> " + puz2[1]); 142 | ok(sudoku.solve(puz3[0]) === puz3[1], puz3[0] + " -> " + puz3[1]); 143 | 144 | // Solve all 95 difficult puzzles from above without any errors 145 | for(var i in TEST_PUZZLES){ 146 | ok(sudoku.solve(TEST_PUZZLES[i])); 147 | } 148 | 149 | // Try to solve unsolvable puzzles 150 | ok(!sudoku.solve(puz_unsolvable), "Unsolvable"); 151 | ok(!sudoku.solve(puz_unsolvable2), "Unsolvable 2"); 152 | 153 | // Board too big 154 | throws(function(){sudoku.solve(puz_too_big)}, "Invalid board size"); 155 | 156 | // Board has invalid character 157 | throws(function(){sudoku.solve(puz_invalid_chars)}, "Invalid characters"); 158 | }); 159 | 160 | test("Get candidates map", function(){ 161 | var puz = 162 | "4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.."+ 163 | "...1.4......"; 164 | var puz_candidates = { 165 | "A1": "4", "A2": "1679", "A3": "12679", "A4": "139", "A5": "2369", 166 | "A6": "269", "A7": "8", "A8": "1239", "A9": "5", "B1": "26789", 167 | "B2": "3", "B3": "1256789", "B4": "14589", "B5": "24569", 168 | "B6": "245689", "B7": "12679", "B8": "1249", "B9": "124679", 169 | "C1": "2689", "C2": "15689", "C3": "125689", "C4": "7", "C5": "234569", 170 | "C6": "245689", "C7": "12369", "C8": "12349", "C9": "123469", 171 | "D1": "3789", "D2": "2", "D3": "15789", "D4": "3459", "D5": "34579", 172 | "D6": "4579", "D7": "13579", "D8": "6", "D9": "13789", "E1": "3679", 173 | "E2": "15679", "E3": "15679", "E4": "359", "E5": "8", "E6": "25679", 174 | "E7": "4", "E8": "12359", "E9": "12379", "F1": "36789", "F2": "4", 175 | "F3": "56789", "F4": "359", "F5": "1", "F6": "25679", "F7": "23579", 176 | "F8": "23589", "F9": "23789", "G1": "289", "G2": "89", "G3": "289", 177 | "G4": "6", "G5": "459", "G6": "3", "G7": "1259", "G8": "7", 178 | "G9": "12489", 179 | "H1": "5", "H2": "6789", "H3": "3", "H4": "2", "H5": "479", "H6": "1", 180 | "H7": "69", "H8": "489", "H9": "4689", "I1": "1", "I2": "6789", 181 | "I3": "4", "I4": "589", "I5": "579", "I6": "5789", "I7": "23569", 182 | "I8": "23589", "I9": "23689" 183 | } 184 | var puz_too_big = 185 | ".........9......84.623...5....6...453...1...6...9...7....1.....4.5..2"+ 186 | "....3.8....91"; 187 | var puz_invalid_chars = 188 | ".........9......84.623...5....6...453...1...6...9...7....1.....4.5..2"+ 189 | "....3.8..!.9"; 190 | 191 | // Get candidates for puzzle 192 | deepEqual(sudoku._get_candidates_map(puz), puz_candidates, puz); 193 | 194 | // Board too big 195 | throws(function(){sudoku._get_candidates_map(puz_too_big)}, 196 | "Invalid board size"); 197 | 198 | // Board has invalid character 199 | throws(function(){sudoku._get_candidates_map(puz_invalid_chars)}, 200 | "Invalid characters"); 201 | }); 202 | 203 | test("Get candidates", function(){ 204 | deepEqual( 205 | sudoku.get_candidates( 206 | "4.25..389....4.265..523.147..1652.7.6..1945322543876915....3.1....4..9.....8....3" 207 | ),[ 208 | ["4","167","2","5","167","16","3","8","9"], 209 | ["13789","13789","3789","79","4","189","2","6","5"], 210 | ["89","689","5","2","3","689","1","4","7"], 211 | ["389","389","1","6","5","2","48","7","48"], 212 | ["6","78","78","1","9","4","5","3","2"], 213 | ["2","5","4","3","8","7","6","9","1"], 214 | ["5","246789","6789","79","267","3","478","1","468"], 215 | ["1378","123678","3678","4","1267","156","9","25","68"], 216 | ["179","124679","679","8","1267","1569","47","25","3"] 217 | ]) 218 | }); 219 | 220 | // Square relationships 221 | // ==================== 222 | 223 | module("Square relationships"); 224 | 225 | test("Get square -> vals map", function(){ 226 | var puz = 227 | "4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.."+ 228 | "...1.4......"; 229 | 230 | deepEqual(sudoku._get_square_vals_map(puz), { 231 | "A1": "4", "A2": ".", "A3": ".", "A4": ".", "A5": ".", "A6": ".", 232 | "A7": "8", "A8": ".", "A9": "5", "B1": ".", "B2": "3", "B3": ".", 233 | "B4": ".", "B5": ".", "B6": ".", "B7": ".", "B8": ".", "B9": ".", 234 | "C1": ".", "C2": ".", "C3": ".", "C4": "7", "C5": ".", "C6": ".", 235 | "C7": ".", "C8": ".", "C9": ".", "D1": ".", "D2": "2", "D3": ".", 236 | "D4": ".", "D5": ".", "D6": ".", "D7": ".", "D8": "6", "D9": ".", 237 | "E1": ".", "E2": ".", "E3": ".", "E4": ".", "E5": "8", "E6": ".", 238 | "E7": "4", "E8": ".", "E9": ".", "F1": ".", "F2": ".", "F3": ".", 239 | "F4": ".", "F5": "1", "F6": ".", "F7": ".", "F8": ".", "F9": ".", 240 | "G1": ".", "G2": ".", "G3": ".", "G4": "6", "G5": ".", "G6": "3", 241 | "G7": ".", "G8": "7", "G9": ".", "H1": "5", "H2": ".", "H3": ".", 242 | "H4": "2", "H5": ".", "H6": ".", "H7": ".", "H8": ".", "H9": ".", 243 | "I1": "1", "I2": ".", "I3": "4", "I4": ".", "I5": ".", "I6": ".", 244 | "I7": ".", "I8": ".", "I9": "." 245 | }, "Testing with medium puzzle"); 246 | 247 | throws(function(){sudoku._get_square_vals_map("")}, 248 | "Size squares/puzzle size mismatch") 249 | }); 250 | 251 | test("Get square -> units map", function(){ 252 | var rows = "ABCDEFGHI"; 253 | var cols = "123456789"; 254 | var squares = sudoku._cross(rows, cols); 255 | var units = sudoku._get_all_units(rows, cols); 256 | var square_units_map = sudoku._get_square_units_map(squares, units); 257 | 258 | // Check units for A1 259 | deepEqual(square_units_map["A1"], [ 260 | ["A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9"], 261 | ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1", "I1"], 262 | ["A1", "A2", "A3", "B1", "B2", "B3", "C1", "C2", "C3"] 263 | ], "Units for A1"); 264 | 265 | // Check units for E5 266 | deepEqual(square_units_map["E5"], [ 267 | ["E1", "E2", "E3", "E4", "E5", "E6", "E7", "E8", "E9"], 268 | ["A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5", "I5"], 269 | ["D4", "D5", "D6", "E4", "E5", "E6", "F4", "F5", "F6"] 270 | ], "Units for E5"); 271 | }); 272 | 273 | test("Get square -> peers map", function(){ 274 | var rows = "ABCDEFGHI"; 275 | var cols = "123456789"; 276 | var squares = sudoku._cross(rows, cols); 277 | var units = sudoku._get_all_units(rows, cols); 278 | var square_units_map = sudoku._get_square_units_map(squares, units); 279 | var square_peers_map = sudoku._get_square_peers_map(squares, 280 | square_units_map); 281 | 282 | // Check peers for A1 283 | deepEqual(square_peers_map["A1"], [ 284 | "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "B1", "C1", "D1", "E1", 285 | "F1", "G1", "H1", "I1", "B2", "B3", "C2", "C3"], 286 | "Peers for A1"); 287 | 288 | // Check peers for C2 289 | deepEqual(square_peers_map["C2"], [ 290 | "C1", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "A2", "B2", "D2", "E2", 291 | "F2", "G2", "H2", "I2", "A1", "A3", "B1", "B3"], 292 | "Peers for C2"); 293 | }); 294 | 295 | test("Get all units", function(){ 296 | var rows = "ABCDEFGHI"; 297 | var cols = "123456789"; 298 | var all_units = sudoku._get_all_units(rows, cols); 299 | 300 | // Make sure we have exactly 27 units (9 rows + 9 columns + 9 boxes) 301 | equal(all_units.length, 27, "27 units"); 302 | 303 | // Look for the first row unit 304 | deepEqual(all_units[0], 305 | ["A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9"], 306 | "First row unit") 307 | 308 | // Look for the first col unit 309 | deepEqual(all_units[9], 310 | ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1", "I1"], 311 | "First col unit") 312 | 313 | // Look for the first box unit 314 | deepEqual(all_units[18], 315 | ["A1", "A2", "A3", "B1", "B2", "B3", "C1", "C2", "C3"], 316 | "First box unit") 317 | }); 318 | 319 | 320 | // Conversions 321 | // =========== 322 | module("Conversions"); 323 | 324 | test("Board string -> grid", function(){ 325 | var board1 = ".5.1972.8.9.2651.7217483596.4.3..7.9.3.7.98...796..32.981536472325974681764812953"; 326 | var board2 = ".1.872.4..47695....651437..4217.8.3..7.2.4...58.9.12746583294171924873..734516..."; 327 | var board3 = "23.94.67.8..3259149..76.32.1.....7925.321.4864..68.5317..1....96598721433...9...7"; 328 | 329 | deepEqual(sudoku.board_string_to_grid(board1), [ 330 | [".","5",".","1","9","7","2",".","8"], 331 | [".","9",".","2","6","5","1",".","7"], 332 | ["2","1","7","4","8","3","5","9","6"], 333 | [".","4",".","3",".",".","7",".","9"], 334 | [".","3",".","7",".","9","8",".","."], 335 | [".","7","9","6",".",".","3","2","."], 336 | ["9","8","1","5","3","6","4","7","2"], 337 | ["3","2","5","9","7","4","6","8","1"], 338 | ["7","6","4","8","1","2","9","5","3"] 339 | ]); 340 | deepEqual(sudoku.board_string_to_grid(board2), [ 341 | [".","1",".","8","7","2",".","4","."], 342 | [".","4","7","6","9","5",".",".","."], 343 | [".","6","5","1","4","3","7",".","."], 344 | ["4","2","1","7",".","8",".","3","."], 345 | [".","7",".","2",".","4",".",".","."], 346 | ["5","8",".","9",".","1","2","7","4"], 347 | ["6","5","8","3","2","9","4","1","7"], 348 | ["1","9","2","4","8","7","3",".","."], 349 | ["7","3","4","5","1","6",".",".","."] 350 | ]); 351 | deepEqual(sudoku.board_string_to_grid(board3), [ 352 | ["2","3",".","9","4",".","6","7","."], 353 | ["8",".",".","3","2","5","9","1","4"], 354 | ["9",".",".","7","6",".","3","2","."], 355 | ["1",".",".",".",".",".","7","9","2"], 356 | ["5",".","3","2","1",".","4","8","6"], 357 | ["4",".",".","6","8",".","5","3","1"], 358 | ["7",".",".","1",".",".",".",".","9"], 359 | ["6","5","9","8","7","2","1","4","3"], 360 | ["3",".",".",".","9",".",".",".","7"] 361 | ]); 362 | }); 363 | 364 | test("Board grid -> string", function(){ 365 | var grid1 = [ 366 | [".","5",".","1","9","7","2",".","8"], 367 | [".","9",".","2","6","5","1",".","7"], 368 | ["2","1","7","4","8","3","5","9","6"], 369 | [".","4",".","3",".",".","7",".","9"], 370 | [".","3",".","7",".","9","8",".","."], 371 | [".","7","9","6",".",".","3","2","."], 372 | ["9","8","1","5","3","6","4","7","2"], 373 | ["3","2","5","9","7","4","6","8","1"], 374 | ["7","6","4","8","1","2","9","5","3"] 375 | ]; 376 | var grid2 = [ 377 | [".","1",".","8","7","2",".","4","."], 378 | [".","4","7","6","9","5",".",".","."], 379 | [".","6","5","1","4","3","7",".","."], 380 | ["4","2","1","7",".","8",".","3","."], 381 | [".","7",".","2",".","4",".",".","."], 382 | ["5","8",".","9",".","1","2","7","4"], 383 | ["6","5","8","3","2","9","4","1","7"], 384 | ["1","9","2","4","8","7","3",".","."], 385 | ["7","3","4","5","1","6",".",".","."] 386 | ]; 387 | var grid3 = [ 388 | ["2","3",".","9","4",".","6","7","."], 389 | ["8",".",".","3","2","5","9","1","4"], 390 | ["9",".",".","7","6",".","3","2","."], 391 | ["1",".",".",".",".",".","7","9","2"], 392 | ["5",".","3","2","1",".","4","8","6"], 393 | ["4",".",".","6","8",".","5","3","1"], 394 | ["7",".",".","1",".",".",".",".","9"], 395 | ["6","5","9","8","7","2","1","4","3"], 396 | ["3",".",".",".","9",".",".",".","7"] 397 | ]; 398 | 399 | deepEqual(sudoku.board_grid_to_string(grid1), 400 | ".5.1972.8.9.2651.7217483596.4.3..7.9.3.7.98...796..32.981536472325974681764812953" 401 | ); 402 | deepEqual(sudoku.board_grid_to_string(grid2), 403 | ".1.872.4..47695....651437..4217.8.3..7.2.4...58.9.12746583294171924873..734516..." 404 | ); 405 | deepEqual(sudoku.board_grid_to_string(grid3), 406 | "23.94.67.8..3259149..76.32.1.....7925.321.4864..68.5317..1....96598721433...9...7" 407 | ); 408 | }); 409 | 410 | test("Integration", function(){ 411 | for(var i = 0; i < 10; ++i){ 412 | var board = sudoku.generate(); 413 | var board_grid = sudoku.board_string_to_grid(board); 414 | var board_string = sudoku.board_grid_to_string(board_grid); 415 | 416 | ok(board === board_string); 417 | } 418 | }); 419 | 420 | 421 | // Utility 422 | // ======= 423 | module("Utility"); 424 | 425 | test("Validate board", function(){ 426 | var report_empty = sudoku.validate_board(); 427 | var report_invalid_size = sudoku.validate_board("123"); 428 | var report_invalid_char = sudoku.validate_board( 429 | ".3.9564272..784136764231589327149658.4.56..73..637._14412893765.8.627"+ 430 | "341673415892"); 431 | var report_good = sudoku.validate_board( 432 | ".3.9564272..784136764231589327149658.4.56..73..637..14412893765.8.627"+ 433 | "341673415892"); 434 | 435 | ok(report_empty !== true); 436 | ok(report_invalid_size !== true); 437 | ok(report_invalid_char !== true); 438 | ok(report_good); 439 | }); 440 | 441 | test("Cross product", function(){ 442 | 443 | // Simple case 444 | deepEqual(sudoku._cross('a','1'), ["a1"], "Simple case"); 445 | 446 | // Classic case 447 | deepEqual( 448 | sudoku._cross("abc", "123"), 449 | ["a1", "a2", "a3", "b1", "b2", "b3", "c1", "c2", "c3"], 450 | "Classic case" 451 | ); 452 | 453 | // Empty case 454 | deepEqual(sudoku._cross('',''), [], "Empty case"); 455 | 456 | // Classic case with arrays 457 | deepEqual( 458 | sudoku._cross(['a', 'b', 'c'], [1, 2, 3]), 459 | ["a1", "a2", "a3", "b1", "b2", "b3", "c1", "c2", "c3"], 460 | "Classic case with arrays" 461 | ); 462 | 463 | // Mismatched size 464 | deepEqual( 465 | sudoku._cross(['a', 'b', 'c'], [1, 2]), 466 | ["a1", "a2", "b1", "b2", "c1", "c2"], 467 | "Mismatched size" 468 | ); 469 | 470 | // Empty default 471 | deepEqual(sudoku._cross(), [], "Empty arrays"); 472 | }); 473 | 474 | test("_in", function(){ 475 | var seq = [1, 2, 3]; 476 | var seq_string = "abc"; 477 | 478 | // Normal 479 | ok(sudoku._in(1, seq), "Normal use case"); 480 | ok(!sudoku._in(0, seq), "Normal use case (not)"); 481 | 482 | // String 483 | ok(sudoku._in('a', seq_string), "String"); 484 | ok(!sudoku._in('z', seq_string), "String (not)"); 485 | }); 486 | 487 | -------------------------------------------------------------------------------- /demo/assets/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap.js by @fat & @mdo 3 | * Copyright 2012 Twitter, Inc. 4 | * http://www.apache.org/licenses/LICENSE-2.0.txt 5 | */ 6 | !function($){"use strict";$(function(){$.support.transition=function(){var transitionEnd=function(){var name,el=document.createElement("bootstrap"),transEndEventNames={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(name in transEndEventNames)if(void 0!==el.style[name])return transEndEventNames[name]}();return transitionEnd&&{end:transitionEnd}}()})}(window.jQuery),!function($){"use strict";var dismiss='[data-dismiss="alert"]',Alert=function(el){$(el).on("click",dismiss,this.close)};Alert.prototype.close=function(e){function removeElement(){$parent.trigger("closed").remove()}var $parent,$this=$(this),selector=$this.attr("data-target");selector||(selector=$this.attr("href"),selector=selector&&selector.replace(/.*(?=#[^\s]*$)/,"")),$parent=$(selector),e&&e.preventDefault(),$parent.length||($parent=$this.hasClass("alert")?$this:$this.parent()),$parent.trigger(e=$.Event("close")),e.isDefaultPrevented()||($parent.removeClass("in"),$.support.transition&&$parent.hasClass("fade")?$parent.on($.support.transition.end,removeElement):removeElement())};var old=$.fn.alert;$.fn.alert=function(option){return this.each(function(){var $this=$(this),data=$this.data("alert");data||$this.data("alert",data=new Alert(this)),"string"==typeof option&&data[option].call($this)})},$.fn.alert.Constructor=Alert,$.fn.alert.noConflict=function(){return $.fn.alert=old,this},$(document).on("click.alert.data-api",dismiss,Alert.prototype.close)}(window.jQuery),!function($){"use strict";var Button=function(element,options){this.$element=$(element),this.options=$.extend({},$.fn.button.defaults,options)};Button.prototype.setState=function(state){var d="disabled",$el=this.$element,data=$el.data(),val=$el.is("input")?"val":"html";state+="Text",data.resetText||$el.data("resetText",$el[val]()),$el[val](data[state]||this.options[state]),setTimeout(function(){"loadingText"==state?$el.addClass(d).attr(d,d):$el.removeClass(d).removeAttr(d)},0)},Button.prototype.toggle=function(){var $parent=this.$element.closest('[data-toggle="buttons-radio"]');$parent&&$parent.find(".active").removeClass("active"),this.$element.toggleClass("active")};var old=$.fn.button;$.fn.button=function(option){return this.each(function(){var $this=$(this),data=$this.data("button"),options="object"==typeof option&&option;data||$this.data("button",data=new Button(this,options)),"toggle"==option?data.toggle():option&&data.setState(option)})},$.fn.button.defaults={loadingText:"loading..."},$.fn.button.Constructor=Button,$.fn.button.noConflict=function(){return $.fn.button=old,this},$(document).on("click.button.data-api","[data-toggle^=button]",function(e){var $btn=$(e.target);$btn.hasClass("btn")||($btn=$btn.closest(".btn")),$btn.button("toggle")})}(window.jQuery),!function($){"use strict";var Carousel=function(element,options){this.$element=$(element),this.options=options,"hover"==this.options.pause&&this.$element.on("mouseenter",$.proxy(this.pause,this)).on("mouseleave",$.proxy(this.cycle,this))};Carousel.prototype={cycle:function(e){return e||(this.paused=!1),this.options.interval&&!this.paused&&(this.interval=setInterval($.proxy(this.next,this),this.options.interval)),this},to:function(pos){var $active=this.$element.find(".item.active"),children=$active.parent().children(),activePos=children.index($active),that=this;if(!(pos>children.length-1||0>pos))return this.sliding?this.$element.one("slid",function(){that.to(pos)}):activePos==pos?this.pause().cycle():this.slide(pos>activePos?"next":"prev",$(children[pos]))},pause:function(e){return e||(this.paused=!0),this.$element.find(".next, .prev").length&&$.support.transition.end&&(this.$element.trigger($.support.transition.end),this.cycle()),clearInterval(this.interval),this.interval=null,this},next:function(){return this.sliding?void 0:this.slide("next")},prev:function(){return this.sliding?void 0:this.slide("prev")},slide:function(type,next){var e,$active=this.$element.find(".item.active"),$next=next||$active[type](),isCycling=this.interval,direction="next"==type?"left":"right",fallback="next"==type?"first":"last",that=this;if(this.sliding=!0,isCycling&&this.pause(),$next=$next.length?$next:this.$element.find(".item")[fallback](),e=$.Event("slide",{relatedTarget:$next[0]}),!$next.hasClass("active")){if($.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(e),e.isDefaultPrevented())return;$next.addClass(type),$next[0].offsetWidth,$active.addClass(direction),$next.addClass(direction),this.$element.one($.support.transition.end,function(){$next.removeClass([type,direction].join(" ")).addClass("active"),$active.removeClass(["active",direction].join(" ")),that.sliding=!1,setTimeout(function(){that.$element.trigger("slid")},0)})}else{if(this.$element.trigger(e),e.isDefaultPrevented())return;$active.removeClass("active"),$next.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return isCycling&&this.cycle(),this}}};var old=$.fn.carousel;$.fn.carousel=function(option){return this.each(function(){var $this=$(this),data=$this.data("carousel"),options=$.extend({},$.fn.carousel.defaults,"object"==typeof option&&option),action="string"==typeof option?option:options.slide;data||$this.data("carousel",data=new Carousel(this,options)),"number"==typeof option?data.to(option):action?data[action]():options.interval&&data.cycle()})},$.fn.carousel.defaults={interval:5e3,pause:"hover"},$.fn.carousel.Constructor=Carousel,$.fn.carousel.noConflict=function(){return $.fn.carousel=old,this},$(document).on("click.carousel.data-api","[data-slide]",function(e){var href,$this=$(this),$target=$($this.attr("data-target")||(href=$this.attr("href"))&&href.replace(/.*(?=#[^\s]+$)/,"")),options=$.extend({},$target.data(),$this.data());$target.carousel(options),e.preventDefault()})}(window.jQuery),!function($){"use strict";var Collapse=function(element,options){this.$element=$(element),this.options=$.extend({},$.fn.collapse.defaults,options),this.options.parent&&(this.$parent=$(this.options.parent)),this.options.toggle&&this.toggle()};Collapse.prototype={constructor:Collapse,dimension:function(){var hasWidth=this.$element.hasClass("width");return hasWidth?"width":"height"},show:function(){var dimension,scroll,actives,hasData;if(!this.transitioning){if(dimension=this.dimension(),scroll=$.camelCase(["scroll",dimension].join("-")),actives=this.$parent&&this.$parent.find("> .accordion-group > .in"),actives&&actives.length){if(hasData=actives.data("collapse"),hasData&&hasData.transitioning)return;actives.collapse("hide"),hasData||actives.data("collapse",null)}this.$element[dimension](0),this.transition("addClass",$.Event("show"),"shown"),$.support.transition&&this.$element[dimension](this.$element[0][scroll])}},hide:function(){var dimension;this.transitioning||(dimension=this.dimension(),this.reset(this.$element[dimension]()),this.transition("removeClass",$.Event("hide"),"hidden"),this.$element[dimension](0))},reset:function(size){var dimension=this.dimension();return this.$element.removeClass("collapse")[dimension](size||"auto")[0].offsetWidth,this.$element[null!==size?"addClass":"removeClass"]("collapse"),this},transition:function(method,startEvent,completeEvent){var that=this,complete=function(){"show"==startEvent.type&&that.reset(),that.transitioning=0,that.$element.trigger(completeEvent)};this.$element.trigger(startEvent),startEvent.isDefaultPrevented()||(this.transitioning=1,this.$element[method]("in"),$.support.transition&&this.$element.hasClass("collapse")?this.$element.one($.support.transition.end,complete):complete())},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var old=$.fn.collapse;$.fn.collapse=function(option){return this.each(function(){var $this=$(this),data=$this.data("collapse"),options="object"==typeof option&&option;data||$this.data("collapse",data=new Collapse(this,options)),"string"==typeof option&&data[option]()})},$.fn.collapse.defaults={toggle:!0},$.fn.collapse.Constructor=Collapse,$.fn.collapse.noConflict=function(){return $.fn.collapse=old,this},$(document).on("click.collapse.data-api","[data-toggle=collapse]",function(e){var href,$this=$(this),target=$this.attr("data-target")||e.preventDefault()||(href=$this.attr("href"))&&href.replace(/.*(?=#[^\s]+$)/,""),option=$(target).data("collapse")?"toggle":$this.data();$this[$(target).hasClass("in")?"addClass":"removeClass"]("collapsed"),$(target).collapse(option)})}(window.jQuery),!function($){"use strict";function clearMenus(){$(toggle).each(function(){getParent($(this)).removeClass("open")})}function getParent($this){var $parent,selector=$this.attr("data-target");return selector||(selector=$this.attr("href"),selector=selector&&/#/.test(selector)&&selector.replace(/.*(?=#[^\s]*$)/,"")),$parent=$(selector),$parent.length||($parent=$this.parent()),$parent}var toggle="[data-toggle=dropdown]",Dropdown=function(element){var $el=$(element).on("click.dropdown.data-api",this.toggle);$("html").on("click.dropdown.data-api",function(){$el.parent().removeClass("open")})};Dropdown.prototype={constructor:Dropdown,toggle:function(){var $parent,isActive,$this=$(this);if(!$this.is(".disabled, :disabled"))return $parent=getParent($this),isActive=$parent.hasClass("open"),clearMenus(),isActive||$parent.toggleClass("open"),$this.focus(),!1},keydown:function(e){var $this,$items,$parent,isActive,index;if(/(38|40|27)/.test(e.keyCode)&&($this=$(this),e.preventDefault(),e.stopPropagation(),!$this.is(".disabled, :disabled"))){if($parent=getParent($this),isActive=$parent.hasClass("open"),!isActive||isActive&&27==e.keyCode)return $this.click();$items=$("[role=menu] li:not(.divider):visible a",$parent),$items.length&&(index=$items.index($items.filter(":focus")),38==e.keyCode&&index>0&&index--,40==e.keyCode&&$items.length-1>index&&index++,~index||(index=0),$items.eq(index).focus())}}};var old=$.fn.dropdown;$.fn.dropdown=function(option){return this.each(function(){var $this=$(this),data=$this.data("dropdown");data||$this.data("dropdown",data=new Dropdown(this)),"string"==typeof option&&data[option].call($this)})},$.fn.dropdown.Constructor=Dropdown,$.fn.dropdown.noConflict=function(){return $.fn.dropdown=old,this},$(document).on("click.dropdown.data-api touchstart.dropdown.data-api",clearMenus).on("click.dropdown touchstart.dropdown.data-api",".dropdown form",function(e){e.stopPropagation()}).on("touchstart.dropdown.data-api",".dropdown-menu",function(e){e.stopPropagation()}).on("click.dropdown.data-api touchstart.dropdown.data-api",toggle,Dropdown.prototype.toggle).on("keydown.dropdown.data-api touchstart.dropdown.data-api",toggle+", [role=menu]",Dropdown.prototype.keydown)}(window.jQuery),!function($){"use strict";var Modal=function(element,options){this.options=options,this.$element=$(element).delegate('[data-dismiss="modal"]',"click.dismiss.modal",$.proxy(this.hide,this)),this.options.remote&&this.$element.find(".modal-body").load(this.options.remote)};Modal.prototype={constructor:Modal,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var that=this,e=$.Event("show");this.$element.trigger(e),this.isShown||e.isDefaultPrevented()||(this.isShown=!0,this.escape(),this.backdrop(function(){var transition=$.support.transition&&that.$element.hasClass("fade");that.$element.parent().length||that.$element.appendTo(document.body),that.$element.show(),transition&&that.$element[0].offsetWidth,that.$element.addClass("in").attr("aria-hidden",!1),that.enforceFocus(),transition?that.$element.one($.support.transition.end,function(){that.$element.focus().trigger("shown")}):that.$element.focus().trigger("shown")}))},hide:function(e){e&&e.preventDefault(),e=$.Event("hide"),this.$element.trigger(e),this.isShown&&!e.isDefaultPrevented()&&(this.isShown=!1,this.escape(),$(document).off("focusin.modal"),this.$element.removeClass("in").attr("aria-hidden",!0),$.support.transition&&this.$element.hasClass("fade")?this.hideWithTransition():this.hideModal())},enforceFocus:function(){var that=this;$(document).on("focusin.modal",function(e){that.$element[0]===e.target||that.$element.has(e.target).length||that.$element.focus()})},escape:function(){var that=this;this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.modal",function(e){27==e.which&&that.hide()}):this.isShown||this.$element.off("keyup.dismiss.modal")},hideWithTransition:function(){var that=this,timeout=setTimeout(function(){that.$element.off($.support.transition.end),that.hideModal()},500);this.$element.one($.support.transition.end,function(){clearTimeout(timeout),that.hideModal()})},hideModal:function(){this.$element.hide().trigger("hidden"),this.backdrop()},removeBackdrop:function(){this.$backdrop.remove(),this.$backdrop=null},backdrop:function(callback){var animate=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var doAnimate=$.support.transition&&animate;this.$backdrop=$('