diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..f548b31 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,9 @@ +{ + "parserOptions": { + "ecmaVersion": 6 + }, + "env": { + "browser": false, + "node": true + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0b24e6e --- /dev/null +++ b/.gitignore @@ -0,0 +1,92 @@ +output/ +TODO.md + +.idea/ + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties +### Node template +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..f4abeaa --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +8.* diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d9a10c0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/README.md b/README.md new file mode 100644 index 0000000..8974246 --- /dev/null +++ b/README.md @@ -0,0 +1,215 @@ +# Nonogram Solver + +[![npm version](https://img.shields.io/npm/v/nonogram-solver.svg)](https://www.npmjs.com/package/nonogram-solver) + +Solves black and white Nonogram puzzles (alsko known as Griddlers, Picross, Hanjie, Japanese Crosswords). Typically requires less than a second. + + +## Installation + +This program requires [Node.js][node] version 8. It might run with other versions, but has not been tested. + +Installation is as simple as + +``` bash +$ npm install nonogram-solver +``` + +## Usage + +Put your input file to `puzzles/input.json`*, then run + +``` bash +$ nonogram-solver +``` + +* There will be a CLI soon allowing arbitrary file names + +### Input format + +The input file must be valid JSON containing a `rows` and a `columns` attribute and an optional `content` attribute. + +``` +{ + "rows": [[1], [2]], + "columns": [[2], [1]], + "content": [0, 0, 1, 0] // optional +} +``` + +Use the `content` attribute to define partially solved puzzles; It should be an array of the values `-1`, `0`, `1`. Here `0` means: unsolved, `1` means: square is known to be occupied, `-1`: square is known to be empty. +It is an error if `content` contains not exactly `rows.length*columns.length` elements. + +### Output + +If your puzzle is solvable, the solution will be printed to the command line and saved as an svg file to the `output` folder. +``` +Puzzle solved! + 1 + 3 + 26 + 71 + 4 084 + 111 4367 34 1 + 2912813 2 55455 3 + 6 6 1111 248 2 41 1 11 + 338 55552 189009 2 054167353333334 + 5 17 1122 72 86111111 + 43444 433452 349744757 5 2245514333348332234228 + 11 1 1 41 2223322 + 49135436803344312521222335526667467796554434522385333359624 + + 1 xxxxxxxxxxxxxxxxxxxx█xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 1 2 xxxxxxxxxxxxxxxxxxxx█xxxxxxxx██xxxxxxxxxxxxxxxxxxxxxxxxxxxx + 2 7 xxxxxxxxxxxxxxxxxxx██xxxxxx███████xxxxxxxxxxxxxxxxxxxxxxxxx + 3 10 xxxxxxxxxxxxxxxxxxx███xxx██████████xxxxxxxxxxxxxxxxxxxxxxxx + 3 11 4 xxxxxxxxxxxxxxxxxxx███xxx███████████xxxxxx████xxxxxxxxxxxxx + 2 12 3 xxxxxxxxxxxxxxxxxxx██xxx████████████xxxxxx███xxxxxxxxxxxxxx + 4 13 3 xxxxxxxxxxxxxxxxx████xx█████████████xxxxx███xxxxxxxxxxxxxxx + 5 13 2 xxxxxxxxxxxxxxx█████xxx█████████████xxxxx██xxxxxxxxxxxxxxxx + 5 12 3 xxxxxxxxxxxxxx█████xxxxx████████████xxxxx███xxxxxxxxxxxxxxx + 6 11 3 4 xxxxxxxxxxxx██████xxxxxxx███████████xxxxxx███xxxxxx████xxxx + 5 10 4 6 xxxxxxxxxxx█████xxxxxxxxx██████████xxxxxxx████xxxx██████xxx + 4 8 3 8 xxxxxxxxxxx████xxxxxxxxxxx████████xxxxxxxxx███xxx████████xx + 4 11 3 4 3 xxxxxxxxxxx████xxxxxxxxx███████████xxxxxxxx███xx████xxx███x + 4 12 3 4 2 xxxxxxxxxxx████xxxxxxxxx████████████xxxxxxx███xx████xxxx██x + 4 13 4 4 3 xxxxxxxxxxx████xxxxxxxxx█████████████xxxxx████xx████xxxx███ + 5 4 13 5 4 2 xxxx█████xx████xxxxxxxxx█████████████xxxx█████xx████xxxxx██ + 7 5 12 7 4 2 xxx███████x█████xxxxxxxx████████████xx███████xxx████xxxxx██ + 8 7 20 4 2 xx████████x███████xxxxxx████████████████████xxxx████xxxxx██ + 4 34 4 3 x████xxx██████████████████████████████████xxxxxx████xxx███x + 3 3 29 4 3 x███xxxx███x█████████████████████████████xxxxxxx████xxx███x + 3 2 28 5 x███xxxx██xxx████████████████████████████xxxxxx█████xxxxxxx + 4 2 28 6 ████xxxx██xxxx████████████████████████████xxxx██████xxxxxxx + 4 1 38 ████xxxx█xxxx██████████████████████████████████████xxxxxxxx + 4 39 ████xxxxxxx███████████████████████████████████████xxxxxxxxx + 5 40 █████xxxx████████████████████████████████████████xxxxxxxxxx + 46 7 x██████████████████████████████████████████████xx███████xxx + 13 30 7 x█████████████x██████████████████████████████xxxx███████xxx +11 6 6 12 3 4 xx███████████xx██████x██████xxx████████████xxxxx███xx████xx + 8 5 6 13 2 3 xxx████████xxx█████xxx██████xxx█████████████xxxx██xxxx███xx + 5 5 4 7 3 3 xxxxxxxxxxxxxx█████xxx█████xxxxx████xx███████xx███xxxxx███x + 4 5 4 7 2 4 xxxxxxxxxxxxxxx████xxx█████xxxxx████xxx███████x██xxxxx████x + 4 5 4 8 3 xxxxxxxxxxxxxxx████xxx█████xxxxx████xxxxx████████xxxxx███xx + 4 4 5 14 xxxxxxxxxxxxxxxx████xxx████xxxxx█████xxxxxx██████████████xx + 2 4 4 5 11 xxxxxxxxxxxx██xx████xxx████xxxxx█████xxxxxxxx███████████xxx + 7 4 4 5 9 xxxxxxxxx███████x████xx████xxxxx█████xxxxxxxxx█████████xxxx + 8 4 4 6 2 xxxxxxxx████████xx████xx████xxxx██████xxxxxxxx██xxxxxxxxxxx + 5 4 4 4 7 2 xxxxxxx█████x████x████xx████xxxxx███████xxxxx██xxxxxxxxxxxx + 4 3 3 3 10 xxxxxxx████xxx███xx███xxx███xxxxx██████████xxxxxxxxxxxxxxxx + 4 3 4 3 14 xxxxxx████xxxx███x████xxx███xxxxxx██████████████xxxxxxxxxxx + 4 6 3 14 xxxxxx████xxxxx██████xxxxx███xxxxxx██████████████xxxxxxxxxx + 4 6 3 14 xxxxxx████xxxxx██████xxxxx███xxxxxxx██████████████xxxxxxxxx + 12 3 3 xxxxxxx████████████xxxxxxx███xxxxxxxxxxxxxxxxxx███xxxxxxxxx + 9 3 3 2 xxxxxxxx█████████xxxxxxxxx███xxxxxxxxxxxxx███xxx██xxxxxxxxx + 9 3 3 2 xxxxxxxxx█████████xxxxxxxx███xxxxxxxxxxxxx███xxx██xxxxxxxxx + 2 3 4 3 3 xxxxxxxxxxxx██x███xxxxxxx████xxxxxxxxxxxxx███xx███xxxxxxxxx + 3 3 6 xxxxxxxxxxxxxxx███xxxxxxx███xxxxxxxxxxxxxxx██████xxxxxxxxxx + 2 4 5 xxxxxxxxxxxxxxxx██xxxxxx████xxxxxxxxxxxxxxxx█████xxxxxxxxxx + 3 4 xxxxxxxxxxxxxxxx███xxxx████xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 1 8 xxxxxxxxxxxxxxx█xx████████xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + 4 xxxxxxxxxxxxxxxxxxxx████xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + +Output saved to output/result.svg. +``` + +![Solved puzzle](./doc/solved.png) + +The same is true if `nonogram-solver` cannot solve your puzzle, but this time the output is going to be a partially finished puzzle. + +``` +Could not solve puzzle + 1 + 3 + 26 + 71 + 4 084 + 111 4367 34 1 + 2912813 2 55455 3 + 6 6 1111 248 2 41 1 11 + 338 55552 189009 2 054167353333334 + 5 17 1122 72 86111111 + 43444 433452 349744757 5 2245514333348332234228 + 11 1 1 41 2223322 + 49135436803344312521222335526667467796554434522385333359624 + + 1 ░░░░░░░░░░░░░░░░░░░░░░x░xx░░xxx░░░xx░xxxxxxx░░xxxxxxxxxxxxx + 1 2 ░░░░░░░░░░░░░░░░░░░░░░x░x░░░x░░░░░xx░xxxxxxx░░xxxxxxxxxxxxx + 2 7 ░░░░░░░░░░░░░░░░░░░░░░xxx░░░░░░░░░xxxxxxxxxxxxxxxxxxxxxxxxx + 3 10 ░░░░░░░░░░░░░░░░░░░░░░xxx██████████xxxxxxxxxxxxxxxxxxxxxxxx + 3 11 4 ░░░░░░░░░░░░░░░░░░░░░░xx░██████████░xxxxx░░░░░░░░░░░░░░░░░░ + 2 12 3 ░░░░░░░░░░░░░░░░░░░░░░xx████████████xxxxx░░░░░░░░░░░░░░░░░░ + 4 13 3 ░░░░░░░░░░░░░░░░░░░░░░x░████████████░xxxx░░░░░░░░░░░░░░░░░░ + 5 13 2 ░░░░░░░░░░░░░░░░░░░░░░x░████████████░xxxx░░░░░░░░░░░░░░░░░░ + 5 12 3 ░░░░░░░░░░░░░░░░░░░░░░xx░███████████░xxxx░░░░░░░░░░░░░░░░░░ + 6 11 3 4 ░░░░░░░░░░░░░░░░░░░░░░xxx░██████████░xxxx░██░░░░░░░░░░░░░░░ + 5 10 4 6 ░░░░░░░░░░░░░░░░░░░░░░xxx░█████████░xxxxx░███░░░░░░░░░░░░░░ + 4 8 3 8 ░░░░░░░░░░░░░░░░░░░░░░xxxx████████xxxxxxx░░░░░░░░░░░░░░░░░░ + 4 11 3 4 3 ░░░░░░░░░░░░░░░░░░░░░░x░░░████████░░░xxx░░░░░░░░░░░░░░░░░░░ + 4 12 3 4 2 ░░░░░░░░░░░░░░░░░░░░░░░░░█████████░░░xx░░░░░░░░░░░░░░░░░░░░ + 4 13 4 4 3 ░░░░░░░░░░░░░░░░░░░░░░░░██████████░░░x░░░░░░░░░░░░░░░░░░░░░ + 5 4 13 5 4 2 ░░░░░░░░░░░░░░░░░░░░░░░░██████████░░░░░░░░░░░░░░░░░░░░░░░░░ + 7 5 12 7 4 2 ░░░░░░░░░░░░░░░░░░░░░░░░██████████░░░░░░░░░░░░░░░░░░░░░░░░░ + 8 7 20 4 2 ░░░░░░░█░░░░░░░░░░░░░░░░█████████████░░░░░░░░░░░░░░░░░░░░░░ + 4 34 4 3 ░░░░░░░░░░░░░░░░███████████████████████░░░░░░░░░░░░░░░░░░░░ + 3 3 29 4 3 ░░░░░░░░░░░░░░░█░░░░░██████████████████░░░░░░░░░░░░░░░░░░░░ + 3 2 28 5 ░░░░░░░░░░░░░░░██░░░░░█░███████████████░░░░░░░░░░░░░░░░░░░░ + 4 2 28 6 ░░░░░░░░░░░░░░░░░░░░░░█░███████████████░░░░░░░░░░░░░░░░░░░░ + 4 1 38 ░░░░░░░░░░░░░░░░░█░░░████████████████████████░░░░░░░░░░░░░░ + 4 39 ░░░░░░░░░░░░░░░░░░░░████████████████████████░░░░░░░░░░░░░░░ + 5 40 ░░░░░░░░░░░░░░░░░░░███████████████████████████░░░░░░░░░░░░░ + 46 7 ░░░░░█████████████████████████████████████████░░░░░░██░░░░░ + 13 30 7 ░░░░░░░██████░░░░░░░░███████████████████████░░░░░░░░░░░░░░░ +11 6 6 12 3 4 ░░░░░░░░░░░░░░░░░░░░░░░████░░░░░████░░██░░░░░░░░░░░░░░░░░░░ + 8 5 6 13 2 3 ░░░░░░░░░░░░░░░░░░░░░░░████░░░░░████░░░░░░░░░░░░░░░░░░░░░░░ + 5 5 4 7 3 3 ░░░░░░░░░░░░░░░░░░░░░░░░███░░xx░████░░░░░░░░░░░░░░░░░░░░░░░ + 4 5 4 7 2 4 ░░░░░░░░░░░░░░░░░░░░░░░░███░░xxx████░░░░░░░░░░░░░░░░░░░░░░░ + 4 5 4 8 3 ░░░░░░░░░░░░░░░░░░░░░░░░███░░xxx████░░░░░░░░░░░░░░░░░░░░░░░ + 4 4 5 14 ░░░░░░░░░░░░░░░░░░░░░░░░███░░xxx████░░░░░░░░░█░░░░░░░░░░░░░ + 2 4 4 5 11 ░░░░░░░░░░░░░░░░░░░░░░░░███░░xxx████░░░░░░░░░░░░░░░░░░░░░░░ + 7 4 4 5 9 ░░░░░░░░░░░░░░░░░░░░░░░░███░░xxx░███░░░░░░░░░░░░░░░░░░░░░░░ + 8 4 4 6 2 ░░░░░░░░░░░░░░░░░░░░░░░░███░░xxx░███░░░░░░░░░░░░░░░░░░░░░░░ + 5 4 4 4 7 2 ░░░░░░░░░░░░░░░░░░░░░░░░███░░xxx░░██░░░░░░░░░░░░░░░░░░░░░░░ + 4 3 3 3 10 ░░░░░░░░░░░░░░░░░░░░░░░░░███░xxxx░██░░░░░░░░░░░░░░░░░░░░░░░ + 4 3 4 3 14 ░░░░░░░░░░░░░░░░░░░░░░░░░███░xxxx░██░░░░░░░░░██░░░░░░░░░░░░ + 4 6 3 14 ░░░░░░░░░░░░░░░░░░░░░░░░░░██░xxxxx░░░░░░░░░░░███░░░░░░░░░░░ + 4 6 3 14 ░░░░░░░░░░░░░░░░░░░░░░░░░░█░░xxxxx░░░░░░░░░░░███░░░░░░░░░░░ + 12 3 3 ░░░░░░░░░░░░░░░░░░░░░░░░░░█░░xxxxx░░░░░░░░░░░x░░░░░░░░░░░░░ + 9 3 3 2 ░░░░░░░░░░░░░░░░░░░░░░░░░░█░░xxxxx░░░░░░░░░░░░░░░░░░░░░░░░░ + 9 3 3 2 ░░░░░░░░░░░░░░░░░░░░░░░░░░█░░xxxxx░░░░░░░░░░░░░░░░░░░░░░░░░ + 2 3 4 3 3 ░░░░░░░░░░░░░░░░░░░░░░░░░░█░░xxxxxxx░░░░░░░░░░░░░░░░░░░░░░░ + 3 3 6 ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░xxxxxxx░░░░░░░░░░░░░░░░░░░░░░░ + 2 4 5 ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░xxxxxxx░░░░░░░░░░░░░░░░░░░░░░░ + 3 4 ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░xxxxxxx░░░░░░░░░░░░░░░░░░░░░░░ + 1 8 ░░░░░░░░░░░░░░░░░░░░░░░░░░x░░xxxxxxx░░░░░░░░░░░░░░░░░░░░░░░ + 4 ░░░░░░░░░░░░░░░░░░░░░░░░░░xxxxxxxxxx░░░░░░░░░░░░░░░░░░░░░░░ + +Output saved to output/result.svg. +``` + +![Partially solved puzzle](./doc/partially-solved.png) + + +Additionally, it prints the current `content` in this case (omitted in the sample above). The content is given in the format [described above](#input-format). + +## Where can I get some puzzles for testing? + +Some samples are included in the `puzzles` folder. If you want more, run + +``` +$ nonogram-dl-samples +``` + +This will download selected puzzles from [nonograms.org](http://www.nonograms.org/). The files themselves are not included in this package for copyright reasons. + +Now you can copy each to `puzzles/input.json`. + +## How does it work? + +See [internals.md](./doc/internals.md). + + + +[![License: Apache 2.0](https://img.shields.io/github/license/ThomasR/nonogram-solver.svg)](LICENSE) + +[node]: https://nodejs.org/ diff --git a/doc/internals.md b/doc/internals.md new file mode 100644 index 0000000..506ce1d --- /dev/null +++ b/doc/internals.md @@ -0,0 +1,44 @@ +# How does nonogram-solver work? + +Solution of nonograms is a three-level process: + +1. Solve a single line (= row or column) +2. Iterate over all lines +3. If no solution is found, use trial and error + +## Solving a single line + +Mostly, a line cannot be completely solved, but one or more squares can be marked as `x` or `█`. Here's how it's done: + +There are [many strategies](http://www.nonograms.org/methods) for (partially) solving a Nonogram line for humans; but for a computer, only one is actually required: _Brute force_ + +> Determine all possible arrangements of blocks that comply with the rules (i.e. all possible solutions), and then for each cell, if that cell is _always_ occupied, mark it a `1`. If it is _always_ vacant, mark it a `-1`. + +However, this method is slow, so in practice, we use another method which I call the _push solver_: + +> Push all blocks to the left as far as possible, then push all blocks to the right as far as possible, while obeying the rules. Then check for overlappings on each block and on each gap. + +The push solver is way faster than the brute force solver but still relatively successful. + +You can read mode about this idea at [http://www.lancaster.ac.uk/%7Esimpsons/nonogram/theory#fastcompl](). + + +## Iterating over all lines + +As mentioned, there are two solvers that run on single lines. We prefer the faster one. So if possible, run the fast solver on every single row, then on every single column, and so on, until the puzzle is solved. + +Only if no progress is made, run the slow solver until it makes progress, and then start over. + +Once a solver has visited a line, it does not need to revisit that line until there is progress in that very line. So we remember visited lines and skip them if possible. + +## Trial and Error + +It may happen that a puzzle with a unique solution cannot be solved with the abovementioned approach. In this case, `nonogram-solver` uses the partial solution and randomly fills in `█` in an empty cell. Then, it starts the whole solution process with that new puzzle, and if a contradiction is encountered, that cell is known to be a empty. + +This continues until an iteration limit is reached or the puzzle is solved. + +Since the trial and error stage uses randomization, it might happen that certain puzzles are sometimes solved and sometimes not. + +## Debug Mode + +Want to know what exactly is going on behind the scenes? Set the environment variable `NONODEBUG` to launch in debug mode (produces a **lot** of output). diff --git a/doc/partially-solved.png b/doc/partially-solved.png new file mode 100644 index 0000000..a5a5825 Binary files /dev/null and b/doc/partially-solved.png differ diff --git a/doc/partially-solved.svg b/doc/partially-solved.svg new file mode 100644 index 0000000..9f50d9f --- /dev/null +++ b/doc/partially-solved.svg @@ -0,0 +1,405 @@ + + + + + + + + + 1 + 1 + 2 + 2 + 7 + 3 + 10 + 3 + 11 + 4 + 2 + 12 + 3 + 4 + 13 + 3 + 5 + 13 + 2 + 5 + 12 + 3 + 6 + 11 + 3 + 4 + 5 + 10 + 4 + 6 + 4 + 8 + 3 + 8 + 4 + 11 + 3 + 4 + 3 + 4 + 12 + 3 + 4 + 2 + 4 + 13 + 4 + 4 + 3 + 5 + 4 + 13 + 5 + 4 + 2 + 7 + 5 + 12 + 7 + 4 + 2 + 8 + 7 + 20 + 4 + 2 + 4 + 34 + 4 + 3 + 3 + 3 + 29 + 4 + 3 + 3 + 2 + 28 + 5 + 4 + 2 + 28 + 6 + 4 + 1 + 38 + 4 + 39 + 5 + 40 + 46 + 7 + 13 + 30 + 7 + 11 + 6 + 6 + 12 + 3 + 4 + 8 + 5 + 6 + 13 + 2 + 3 + 5 + 5 + 4 + 7 + 3 + 3 + 4 + 5 + 4 + 7 + 2 + 4 + 4 + 5 + 4 + 8 + 3 + 4 + 4 + 5 + 14 + 2 + 4 + 4 + 5 + 11 + 7 + 4 + 4 + 5 + 9 + 8 + 4 + 4 + 6 + 2 + 5 + 4 + 4 + 4 + 7 + 2 + 4 + 3 + 3 + 3 + 10 + 4 + 3 + 4 + 3 + 14 + 4 + 6 + 3 + 14 + 4 + 6 + 3 + 14 + 12 + 3 + 3 + 9 + 3 + 3 + 2 + 9 + 3 + 3 + 2 + 2 + 3 + 4 + 3 + 3 + 3 + 3 + 6 + 2 + 4 + 5 + 3 + 4 + 1 + 8 + 4 + 4 + 9 + 11 + 13 + 4 + 5 + 3 + 4 + 3 + 4 + 3 + 3 + 4 + 6 + 8 + 4 + 8 + 6 + 5 + 10 + 2 + 5 + 4 + 3 + 9 + 5 + 3 + 3 + 11 + 5 + 3 + 4 + 12 + 5 + 4 + 4 + 18 + 2 + 5 + 3 + 4 + 16 + 12 + 1 + 3 + 17 + 12 + 4 + 18 + 3 + 5 + 3 + 19 + 4 + 2 + 6 + 10 + 9 + 1 + 7 + 10 + 7 + 2 + 2 + 9 + 4 + 2 + 14 + 2 + 2 + 17 + 3 + 4 + 25 + 3 + 8 + 27 + 5 + 45 + 27 + 12 + 25 + 6 + 26 + 26 + 27 + 34 + 36 + 8 + 27 + 6 + 27 + 2 + 12 + 9 + 12 + 6 + 14 + 5 + 15 + 5 + 15 + 4 + 3 + 4 + 11 + 4 + 7 + 4 + 10 + 4 + 3 + 3 + 10 + 5 + 5 + 3 + 4 + 2 + 8 + 5 + 4 + 3 + 5 + 1 + 6 + 4 + 4 + 1 + 3 + 2 + 5 + 6 + 3 + 2 + 5 + 7 + 4 + 3 + 13 + 8 + 8 + 13 + 5 + 3 + 5 + 13 + 3 + 3 + 13 + 2 + 3 + 3 + 2 + 3 + 3 + 3 + 3 + 3 + 4 + 5 + 3 + 2 + 9 + 4 + 2 + 6 + 8 + 2 + 4 diff --git a/doc/solved.png b/doc/solved.png new file mode 100644 index 0000000..809cc7d Binary files /dev/null and b/doc/solved.png differ diff --git a/doc/solved.svg b/doc/solved.svg new file mode 100644 index 0000000..4cb6c59 --- /dev/null +++ b/doc/solved.svg @@ -0,0 +1,405 @@ + + + + + + + + + 1 + 1 + 2 + 2 + 7 + 3 + 10 + 3 + 11 + 4 + 2 + 12 + 3 + 4 + 13 + 3 + 5 + 13 + 2 + 5 + 12 + 3 + 6 + 11 + 3 + 4 + 5 + 10 + 4 + 6 + 4 + 8 + 3 + 8 + 4 + 11 + 3 + 4 + 3 + 4 + 12 + 3 + 4 + 2 + 4 + 13 + 4 + 4 + 3 + 5 + 4 + 13 + 5 + 4 + 2 + 7 + 5 + 12 + 7 + 4 + 2 + 8 + 7 + 20 + 4 + 2 + 4 + 34 + 4 + 3 + 3 + 3 + 29 + 4 + 3 + 3 + 2 + 28 + 5 + 4 + 2 + 28 + 6 + 4 + 1 + 38 + 4 + 39 + 5 + 40 + 46 + 7 + 13 + 30 + 7 + 11 + 6 + 6 + 12 + 3 + 4 + 8 + 5 + 6 + 13 + 2 + 3 + 5 + 5 + 4 + 7 + 3 + 3 + 4 + 5 + 4 + 7 + 2 + 4 + 4 + 5 + 4 + 8 + 3 + 4 + 4 + 5 + 14 + 2 + 4 + 4 + 5 + 11 + 7 + 4 + 4 + 5 + 9 + 8 + 4 + 4 + 6 + 2 + 5 + 4 + 4 + 4 + 7 + 2 + 4 + 3 + 3 + 3 + 10 + 4 + 3 + 4 + 3 + 14 + 4 + 6 + 3 + 14 + 4 + 6 + 3 + 14 + 12 + 3 + 3 + 9 + 3 + 3 + 2 + 9 + 3 + 3 + 2 + 2 + 3 + 4 + 3 + 3 + 3 + 3 + 6 + 2 + 4 + 5 + 3 + 4 + 1 + 8 + 4 + 4 + 9 + 11 + 13 + 4 + 5 + 3 + 4 + 3 + 4 + 3 + 3 + 4 + 6 + 8 + 4 + 8 + 6 + 5 + 10 + 2 + 5 + 4 + 3 + 9 + 5 + 3 + 3 + 11 + 5 + 3 + 4 + 12 + 5 + 4 + 4 + 18 + 2 + 5 + 3 + 4 + 16 + 12 + 1 + 3 + 17 + 12 + 4 + 18 + 3 + 5 + 3 + 19 + 4 + 2 + 6 + 10 + 9 + 1 + 7 + 10 + 7 + 2 + 2 + 9 + 4 + 2 + 14 + 2 + 2 + 17 + 3 + 4 + 25 + 3 + 8 + 27 + 5 + 45 + 27 + 12 + 25 + 6 + 26 + 26 + 27 + 34 + 36 + 8 + 27 + 6 + 27 + 2 + 12 + 9 + 12 + 6 + 14 + 5 + 15 + 5 + 15 + 4 + 3 + 4 + 11 + 4 + 7 + 4 + 10 + 4 + 3 + 3 + 10 + 5 + 5 + 3 + 4 + 2 + 8 + 5 + 4 + 3 + 5 + 1 + 6 + 4 + 4 + 1 + 3 + 2 + 5 + 6 + 3 + 2 + 5 + 7 + 4 + 3 + 13 + 8 + 8 + 13 + 5 + 3 + 5 + 13 + 3 + 3 + 13 + 2 + 3 + 3 + 2 + 3 + 3 + 3 + 3 + 3 + 4 + 5 + 3 + 2 + 9 + 4 + 2 + 6 + 8 + 2 + 4 diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..e0b44c4 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2191 @@ +{ + "name": "nonogram-solver", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "3.3.0" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, + "ajv": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.3.0.tgz", + "integrity": "sha1-RBT/dKUIecII7l/cgm4ywwNUnto=", + "dev": true, + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.0.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + }, + "ajv-keywords": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", + "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", + "dev": true + }, + "ansi-escapes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.0.0.tgz", + "integrity": "sha512-O/klc27mWNUigtv0F8NJWbLF00OcegQalkqKURWdosW08YZKi4m6CnSUSvIZG1otNJbTWhN01Hhz389DW7mvDQ==", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "argparse": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", + "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", + "dev": true, + "requires": { + "sprintf-js": "1.0.3" + } + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "1.0.3" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", + "dev": true + }, + "assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", + "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "requires": { + "util": "0.10.3" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.2" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + } + } + }, + "balanced-match": { + "version": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "boom": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", + "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", + "dev": true, + "requires": { + "hoek": "4.2.0" + } + }, + "brace-expansion": { + "version": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "dev": true, + "requires": { + "balanced-match": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "concat-map": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + } + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "chalk": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", + "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "dev": true, + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "supports-color": "4.5.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, + "requires": { + "color-convert": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz" + } + }, + "supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "requires": { + "has-flag": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz" + } + } + } + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "color-convert": { + "version": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz", + "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=", + "dev": true, + "requires": { + "color-name": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" + } + }, + "color-name": { + "version": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "combined-stream": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "dev": true, + "requires": { + "delayed-stream": "1.0.0" + } + }, + "commander": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", + "dev": true + }, + "concat-map": { + "version": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", + "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", + "dev": true, + "requires": { + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "readable-stream": "2.3.3", + "typedarray": "0.0.6" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cryptiles": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", + "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", + "dev": true, + "requires": { + "boom": "5.2.0" + }, + "dependencies": { + "boom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", + "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", + "dev": true, + "requires": { + "hoek": "4.2.0" + } + } + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "1.0.0" + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "5.0.0", + "is-path-cwd": "1.0.0", + "is-path-in-cwd": "1.0.0", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "rimraf": "2.6.2" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "doctrine": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz", + "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=", + "dev": true, + "requires": { + "esutils": "2.0.2", + "isarray": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" + } + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "dev": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "escape-string-regexp": { + "version": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.11.0.tgz", + "integrity": "sha512-UWbhQpaKlm8h5x/VLwm0S1kheMrDj8jPwhnBMjr/Dlo3qqT7MvcN/UfKAR3E1N4lr4YNtOvS4m3hwsrVc/ky7g==", + "dev": true, + "requires": { + "ajv": "5.3.0", + "babel-code-frame": "6.26.0", + "chalk": "2.3.0", + "concat-stream": "1.6.0", + "cross-spawn": "5.1.0", + "debug": "3.1.0", + "doctrine": "2.0.0", + "eslint-scope": "3.7.1", + "espree": "3.5.2", + "esquery": "1.0.0", + "estraverse": "4.2.0", + "esutils": "2.0.2", + "file-entry-cache": "2.0.0", + "functional-red-black-tree": "1.0.1", + "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "globals": "9.18.0", + "ignore": "3.3.7", + "imurmurhash": "0.1.4", + "inquirer": "3.3.0", + "is-resolvable": "1.0.0", + "js-yaml": "3.10.0", + "json-stable-stringify-without-jsonify": "1.0.1", + "levn": "0.3.0", + "lodash": "4.17.4", + "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "natural-compare": "1.4.0", + "optionator": "0.8.2", + "path-is-inside": "1.0.2", + "pluralize": "7.0.0", + "progress": "2.0.0", + "require-uncached": "1.0.3", + "semver": "5.4.1", + "strip-ansi": "4.0.0", + "strip-json-comments": "2.0.1", + "table": "4.0.2", + "text-table": "0.2.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "4.1.1", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + } + } + } + }, + "eslint-scope": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", + "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", + "dev": true, + "requires": { + "esrecurse": "4.2.0", + "estraverse": "4.2.0" + } + }, + "espree": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.2.tgz", + "integrity": "sha512-sadKeYwaR/aJ3stC2CdvgXu1T16TdYN+qwCpcWbMnGJ8s0zNWemzrvb2GbD4OhmJ/fwpJjudThAlLobGbWZbCQ==", + "dev": true, + "requires": { + "acorn": "5.2.1", + "acorn-jsx": "3.0.1" + }, + "dependencies": { + "acorn": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.2.1.tgz", + "integrity": "sha512-jG0u7c4Ly+3QkkW18V+NRDN+4bWHdln30NL1ZL2AvFZZmQe/BfopYCtghCKKVBUSetZ4QKcyA0pY6/4Gw8Pv8w==", + "dev": true + } + } + }, + "esprima": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "dev": true + }, + "esquery": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", + "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", + "dev": true, + "requires": { + "estraverse": "4.2.0" + } + }, + "esrecurse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", + "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", + "dev": true, + "requires": { + "estraverse": "4.2.0", + "object-assign": "4.1.1" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "expect": { + "version": "21.2.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-21.2.1.tgz", + "integrity": "sha512-orfQQqFRTX0jH7znRIGi8ZMR8kTNpXklTTz8+HGTpmTKZo3Occ6JNB5FXMb8cRuiiC/GyDqsr30zUa66ACYlYw==", + "dev": true, + "requires": { + "ansi-styles": "3.2.0", + "jest-diff": "21.2.1", + "jest-get-type": "21.2.0", + "jest-matcher-utils": "21.2.1", + "jest-message-util": "21.2.1", + "jest-regex-util": "21.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "requires": { + "arr-flatten": "1.1.0" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "requires": { + "expand-range": "1.8.2", + "preserve": "0.2.0", + "repeat-element": "1.1.2" + } + }, + "color-convert": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", + "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "diff": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.4.0.tgz", + "integrity": "sha512-QpVuMTEoJMF7cKzi6bvWhRulU1fZqZnvyVQgNhPaxxuTYwyjn/j1v9falseQ/uXWwPnO56RBfwtg4h/EQXmucA==", + "dev": true + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "requires": { + "is-posix-bracket": "0.1.1" + } + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "dev": true, + "requires": { + "fill-range": "2.2.3" + } + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", + "dev": true + }, + "fill-range": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", + "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", + "dev": true, + "requires": { + "is-number": "2.1.0", + "isobject": "2.1.0", + "randomatic": "1.1.7", + "repeat-element": "1.1.2", + "repeat-string": "1.6.1" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dev": true, + "requires": { + "for-in": "1.0.2" + } + }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "dev": true, + "requires": { + "glob-parent": "2.0.0", + "is-glob": "2.0.1" + } + }, + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "requires": { + "is-glob": "2.0.1" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", + "dev": true + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "dev": true, + "requires": { + "is-primitive": "2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", + "dev": true + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + }, + "jest-diff": { + "version": "21.2.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-21.2.1.tgz", + "integrity": "sha512-E5fu6r7PvvPr5qAWE1RaUwIh/k6Zx/3OOkZ4rk5dBJkEWRrUuSgbMt2EO8IUTPTd6DOqU3LW6uTIwX5FRvXoFA==", + "dev": true, + "requires": { + "chalk": "2.3.0", + "diff": "3.4.0", + "jest-get-type": "21.2.0", + "pretty-format": "21.2.1" + } + }, + "jest-get-type": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-21.2.0.tgz", + "integrity": "sha512-y2fFw3C+D0yjNSDp7ab1kcd6NUYfy3waPTlD8yWkAtiocJdBRQqNoRqVfMNxgj+IjT0V5cBIHJO0z9vuSSZ43Q==", + "dev": true + }, + "jest-matcher-utils": { + "version": "21.2.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-21.2.1.tgz", + "integrity": "sha512-kn56My+sekD43dwQPrXBl9Zn9tAqwoy25xxe7/iY4u+mG8P3ALj5IK7MLHZ4Mi3xW7uWVCjGY8cm4PqgbsqMCg==", + "dev": true, + "requires": { + "chalk": "2.3.0", + "jest-get-type": "21.2.0", + "pretty-format": "21.2.1" + } + }, + "jest-message-util": { + "version": "21.2.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-21.2.1.tgz", + "integrity": "sha512-EbC1X2n0t9IdeMECJn2BOg7buOGivCvVNjqKMXTzQOu7uIfLml+keUfCALDh8o4rbtndIeyGU8/BKfoTr/LVDQ==", + "dev": true, + "requires": { + "chalk": "2.3.0", + "micromatch": "2.3.11", + "slash": "1.0.0" + } + }, + "jest-regex-util": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-21.2.0.tgz", + "integrity": "sha512-BKQ1F83EQy0d9Jen/mcVX7D+lUt2tthhK/2gDWRgLDJRNOdRgSp1iVqFxP8EN1ARuypvDflRfPzYT8fQnoBQFQ==", + "dev": true + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true, + "requires": { + "arr-diff": "2.0.0", + "array-unique": "0.2.1", + "braces": "1.8.5", + "expand-brackets": "0.1.5", + "extglob": "0.3.2", + "filename-regex": "2.0.1", + "is-extglob": "1.0.0", + "is-glob": "2.0.1", + "kind-of": "3.2.2", + "normalize-path": "2.1.1", + "object.omit": "2.0.1", + "parse-glob": "3.0.4", + "regex-cache": "0.4.4" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "1.1.0" + } + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "dev": true, + "requires": { + "for-own": "0.1.5", + "is-extendable": "0.1.1" + } + }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "dev": true, + "requires": { + "glob-base": "0.3.0", + "is-dotfile": "1.0.3", + "is-extglob": "1.0.0", + "is-glob": "2.0.1" + } + }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", + "dev": true + }, + "pretty-format": { + "version": "21.2.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-21.2.1.tgz", + "integrity": "sha512-ZdWPGYAnYfcVP8yKA3zFjCn8s4/17TeYH28MXuC8vTp0o21eXjbFGcOAXZEaDaOFJjc3h2qa7HQNHNshhvoh2A==", + "dev": true, + "requires": { + "ansi-regex": "3.0.0", + "ansi-styles": "3.2.0" + } + }, + "randomatic": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", + "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", + "dev": true, + "requires": { + "is-number": "3.0.0", + "kind-of": "4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "dev": true, + "requires": { + "is-equal-shallow": "0.1.3" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", + "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + } + } + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", + "dev": true + }, + "external-editor": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.0.5.tgz", + "integrity": "sha512-Msjo64WT5W+NhOpQXh0nOHm+n0RfU1QUwDnKYvJ8dEJ8zlwLrqXNTv5mSUTJpepf41PDJGyhueTw2vNZW+Fr/w==", + "dev": true, + "requires": { + "iconv-lite": "0.4.19", + "jschardet": "1.6.0", + "tmp": "0.0.33" + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", + "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "1.3.0", + "object-assign": "4.1.1" + } + }, + "flat-cache": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", + "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", + "dev": true, + "requires": { + "circular-json": "0.3.3", + "del": "2.2.2", + "graceful-fs": "4.1.11", + "write": "0.2.1" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", + "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=", + "dev": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.17" + } + }, + "fs.realpath": { + "version": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "1.0.0" + } + }, + "glob": { + "version": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", + "dev": true, + "requires": { + "fs.realpath": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "inflight": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "path-is-absolute": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "1.0.2", + "arrify": "1.0.1", + "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "dev": true, + "requires": { + "ajv": "5.3.0", + "har-schema": "2.0.0" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "has-flag": { + "version": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "hawk": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", + "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", + "dev": true, + "requires": { + "boom": "4.3.1", + "cryptiles": "3.1.2", + "hoek": "4.2.0", + "sntp": "2.1.0" + } + }, + "hoek": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", + "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==", + "dev": true + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.13.1" + } + }, + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", + "dev": true + }, + "ignore": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.7.tgz", + "integrity": "sha512-YGG3ejvBNHRqu0559EOxxNFihD0AjpvHlC/pdGKd3X3ofe+CoJkYazwNJYTNebqpPKN+VVQbh4ZFn1DivMNuHA==", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "wrappy": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + } + }, + "inherits": { + "version": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "inquirer": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", + "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "dev": true, + "requires": { + "ansi-escapes": "3.0.0", + "chalk": "2.3.0", + "cli-cursor": "2.1.0", + "cli-width": "2.2.0", + "external-editor": "2.0.5", + "figures": "2.0.0", + "lodash": "4.17.4", + "mute-stream": "0.0.7", + "run-async": "2.3.0", + "rx-lite": "4.0.8", + "rx-lite-aggregates": "4.0.8", + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "through": "2.3.8" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + } + } + } + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", + "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", + "dev": true, + "requires": { + "is-path-inside": "1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz", + "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=", + "dev": true, + "requires": { + "path-is-inside": "1.0.2" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-resolvable": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz", + "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=", + "dev": true, + "requires": { + "tryit": "1.0.3" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", + "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", + "dev": true, + "requires": { + "argparse": "1.0.9", + "esprima": "4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true, + "optional": true + }, + "jschardet": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/jschardet/-/jschardet-1.6.0.tgz", + "integrity": "sha512-xYuhvQ7I9PDJIGBWev9xm0+SMSed3ZDBAmvVjbFR1ZRLAF+vlXcQu6cRI9uAlj81rzikElRVteehwV7DuX2ZmQ==", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "left-pad": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.1.3.tgz", + "integrity": "sha1-YS9hwDPzqeCOk58crr7qQbbzGZo=" + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2", + "type-check": "0.3.2" + } + }, + "lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", + "dev": true + }, + "lru-cache": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", + "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", + "dev": true, + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + } + }, + "mime-db": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=", + "dev": true + }, + "mime-types": { + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "dev": true, + "requires": { + "mime-db": "1.30.0" + } + }, + "mimic-fn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz", + "integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg=", + "dev": true + }, + "minimatch": { + "version": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "dev": true, + "requires": { + "brace-expansion": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz" + } + }, + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + } + }, + "mocha": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-4.0.1.tgz", + "integrity": "sha512-evDmhkoA+cBNiQQQdSKZa2b9+W2mpLoj50367lhy+Klnx9OV8XlCIhigUnn1gaTFLQCa0kdNhEGDr0hCXOQFDw==", + "dev": true, + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.11.0", + "debug": "3.1.0", + "diff": "3.3.1", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.3", + "he": "1.1.1", + "mkdirp": "0.5.1", + "supports-color": "4.4.0" + }, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "diff": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", + "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "growl": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", + "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", + "dev": true + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.8" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "supports-color": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", + "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + } + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "once": { + "version": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "1.1.0" + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "wordwrap": "1.0.0" + }, + "dependencies": { + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + } + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "path-is-absolute": { + "version": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "2.0.4" + } + }, + "pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "progress": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", + "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "dev": true + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "isarray": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "request": { + "version": "2.83.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", + "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", + "dev": true, + "requires": { + "aws-sign2": "0.7.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.3.1", + "har-validator": "5.0.3", + "hawk": "6.0.2", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.17", + "oauth-sign": "0.8.2", + "performance-now": "2.1.0", + "qs": "6.5.1", + "safe-buffer": "5.1.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.3", + "tunnel-agent": "0.6.0", + "uuid": "3.1.0" + } + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "0.1.0", + "resolve-from": "1.0.1" + } + }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "2.0.1", + "signal-exit": "3.0.2" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "2.1.0" + } + }, + "rx-lite": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", + "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", + "dev": true + }, + "rx-lite-aggregates": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", + "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "dev": true, + "requires": { + "rx-lite": "4.0.8" + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "dev": true + }, + "semver": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", + "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "slice-ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + } + } + }, + "sntp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", + "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", + "dev": true, + "requires": { + "hoek": "4.2.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", + "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "dev": true, + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + } + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "table": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", + "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "dev": true, + "requires": { + "ajv": "5.3.0", + "ajv-keywords": "2.1.1", + "chalk": "2.3.0", + "lodash": "4.17.4", + "slice-ansi": "1.0.0", + "string-width": "2.1.1" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "1.0.2" + } + }, + "tough-cookie": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", + "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", + "dev": true, + "requires": { + "punycode": "1.4.1" + } + }, + "tryit": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", + "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true, + "optional": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "requires": { + "inherits": "2.0.1" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "uuid": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", + "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + } + }, + "which": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", + "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "dev": true, + "requires": { + "isexe": "2.0.0" + } + }, + "wrappy": { + "version": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..f68d1af --- /dev/null +++ b/package.json @@ -0,0 +1,46 @@ +{ + "name": "nonogram-solver", + "version": "1.0.0", + "description": "Solver formatter for Nonograms (Griddlers, Picross, Hanjie, Japanese Crosswords)", + "keywords": [ + "nonogram", + "griddler", + "picross", + "hanjie", + "japanese crossword", + "solve", + "solver", + "puzzle", + "game", + "serialization" + ], + "homepage": "https://github.com/thomasr/nonogram-solver", + "license": "Apache 2.0", + "author": "Thomas Rosenau ", + "main": "src/index.js", + "bin": { + "nonogram-solver": "src/index.js", + "nonogram-dl-samples": "puzzles/nonograms.org/downloadSamples.js" + }, + "directories": { + "doc": "doc", + "test": "test" + }, + "repository": "thomasr/nonogram-solver", + "scripts": { + "test": "mocha" + }, + "dependencies": { + "assert": "^1.4.1", + "left-pad": "^1.1.3" + }, + "devDependencies": { + "eslint": "^4.11.0", + "expect": "^21.2.1", + "mocha": "^4.0.1", + "request": "^2.83.0" + }, + "engines": { + "node": ">=8.0.0" + } +} diff --git a/puzzles/input.json b/puzzles/input.json new file mode 100644 index 0000000..3c89635 --- /dev/null +++ b/puzzles/input.json @@ -0,0 +1,206 @@ +{ + "columns": [ + [43, 43], + [38, 38], + [34, 14, 34], + [31, 14, 31], + [29, 11, 29], + [27, 12, 27], + [25, 13, 25], + [24, 14, 24], + [22, 14, 22], + [21, 3, 3, 2, 21], + [19, 19], + [18, 18], + [17, 17], + [16, 16], + [15, 2, 15], + [14, 3, 14], + [13, 4, 13], + [12, 3, 12], + [11, 2, 2, 11], + [10, 5, 2, 10], + [10, 4, 6, 10], + [9, 5, 12, 9], + [8, 4, 16, 8], + [8, 3, 18, 8], + [7, 3, 20, 7], + [6, 2, 20, 6], + [6, 1, 21, 6], + [5, 2, 23, 5], + [5, 2, 2, 24, 5], + [4, 2, 3, 1, 24, 4], + [4, 3, 2, 4, 25, 4], + [3, 3, 3, 5, 25, 4], + [3, 3, 2, 5, 25, 3], + [3, 2, 3, 5, 25, 3], + [2, 3, 3, 2, 4, 25, 2], + [2, 2, 4, 2, 1, 2, 4, 27, 2], + [2, 2, 4, 1, 2, 6, 27, 2], + [2, 2, 5, 1, 5, 5, 27, 2], + [1, 8, 1, 7, 33, 1], + [1, 7, 6, 34, 1], + [1, 6, 7, 2, 36, 2, 1], + [1, 6, 7, 36, 5, 1], + [1, 6, 2, 8, 1, 44, 1], + [3, 2, 9, 2, 2, 46], + [2, 4, 8, 2, 1, 46], + [2, 6, 8, 2, 47], + [6, 9, 49], + [3, 1, 12, 50], + [1, 4, 13, 50], + [2, 3, 2, 15, 50], + [19, 50], + [21, 49], + [19, 3, 48], + [16, 4, 48], + [1, 12, 4, 47], + [15, 5, 46], + [1, 16, 5, 45], + [1, 1, 3, 14, 3, 4, 21, 16, 1], + [1, 1, 16, 11, 29, 4, 2, 1], + [1, 18, 12, 32, 1], + [1, 1, 1, 33, 30, 1], + [1, 41, 28, 1], + [2, 42, 16, 8, 2], + [2, 23, 21, 14, 2], + [2, 22, 21, 12, 1, 2], + [2, 21, 1, 34, 1, 3, 2], + [3, 23, 20, 8, 6, 3], + [3, 24, 6, 13, 7, 6, 3], + [3, 25, 6, 11, 6, 5, 4], + [4, 34, 10, 6, 4, 4], + [4, 34, 8, 4, 2, 4], + [5, 34, 8, 1, 5], + [5, 33, 7, 5], + [6, 33, 7, 6], + [6, 32, 5, 6], + [7, 32, 2, 7], + [8, 31, 8], + [8, 30, 8], + [9, 30, 9], + [10, 29, 10], + [10, 28, 10], + [11, 28, 11], + [12, 28, 12], + [13, 27, 13], + [14, 28, 14], + [15, 30, 15], + [16, 31, 16], + [17, 31, 17], + [18, 30, 18], + [19, 20, 2, 19], + [21, 15, 1, 21], + [22, 13, 22], + [24, 12, 24], + [25, 12, 1, 25], + [27, 10, 27], + [29, 10, 2, 29], + [31, 6, 9, 32], + [34, 5, 34], + [38, 38], + [43, 43] + ], + "rows": [ + [43, 43], + [38, 38], + [34, 1, 34], + [31, 6, 31], + [29, 10, 2, 29], + [27, 6, 8, 2, 1, 5, 27], + [25, 5, 9, 10, 25], + [24, 4, 8, 1, 11, 24], + [22, 4, 9, 2, 1, 13, 22], + [21, 5, 9, 2, 16, 21], + [19, 4, 10, 1, 16, 19], + [18, 4, 4, 20, 18], + [17, 4, 2, 1, 6, 1, 22, 17], + [16, 4, 3, 34, 16], + [15, 5, 8, 27, 15], + [14, 6, 5, 33, 14], + [13, 3, 1, 4, 35, 13], + [12, 3, 2, 6, 36, 12], + [11, 3, 1, 2, 1, 3, 36, 11], + [10, 1, 1, 2, 38, 10], + [10, 2, 1, 1, 1, 38, 10], + [9, 1, 2, 2, 42, 9], + [8, 3, 49, 8], + [8, 52, 8], + [7, 56, 7], + [6, 56, 6], + [6, 28, 26, 6], + [5, 19, 2, 5, 27, 5], + [5, 12, 9, 5, 26, 5], + [4, 7, 2, 8, 5, 25, 4], + [4, 6, 1, 2, 6, 2, 6, 26, 4], + [3, 6, 1, 2, 3, 1, 13, 25, 3], + [3, 4, 1, 3, 15, 25, 3], + [3, 3, 2, 43, 3], + [2, 11, 1, 5, 37, 2], + [2, 13, 37, 2], + [2, 14, 37, 2], + [2, 16, 37, 2], + [1, 18, 4, 39, 1], + [1, 43, 21, 5, 1], + [1, 45, 20, 4, 1], + [1, 47, 9, 4, 1], + [1, 35, 11, 2, 8, 3, 1], + [1, 37, 16, 5, 2], + [1, 38, 15, 5, 2], + [1, 38, 14, 4, 1, 1], + [1, 39, 13, 4, 1], + [1, 40, 13, 4, 2], + [1, 41, 11, 3, 1], + [2, 43, 8, 3, 1], + [2, 43, 7, 2, 2], + [2, 42, 5, 3, 2], + [2, 44, 3, 2, 1], + [2, 45, 2], + [2, 45, 3, 2], + [3, 49, 2], + [3, 49, 1], + [1, 3, 48, 1, 1], + [1, 4, 46, 1], + [1, 5, 35, 1], + [1, 5, 34, 1], + [1, 6, 30, 1], + [2, 7, 29, 2], + [2, 6, 28, 2], + [2, 6, 19, 7, 2], + [2, 5, 19, 6, 2], + [3, 4, 19, 5, 3], + [3, 4, 24, 3], + [3, 4, 22, 4], + [4, 3, 23, 4], + [4, 4, 22, 4], + [5, 3, 22, 5], + [5, 3, 22, 5], + [6, 1, 16, 4, 2, 6], + [6, 2, 17, 4, 3, 6], + [7, 1, 22, 3, 7], + [8, 23, 4, 8], + [8, 21, 4, 8], + [9, 18, 3, 9], + [10, 16, 3, 10], + [10, 17, 4, 10], + [11, 16, 2, 11], + [12, 15, 12], + [13, 14, 13], + [14, 12, 14], + [15, 10, 15], + [16, 8, 16], + [17, 6, 17], + [18, 18], + [19, 19], + [21, 21], + [22, 22], + [24, 24], + [25, 25], + [27, 27], + [29, 29], + [32, 32], + [34, 34], + [38, 38], + [43, 43] + ] +} diff --git a/puzzles/nonograms.org/.gitignore b/puzzles/nonograms.org/.gitignore new file mode 100644 index 0000000..d69d1a4 --- /dev/null +++ b/puzzles/nonograms.org/.gitignore @@ -0,0 +1,2 @@ +*.json + diff --git a/puzzles/nonograms.org/downloadSamples.js b/puzzles/nonograms.org/downloadSamples.js new file mode 100755 index 0000000..59ccd08 --- /dev/null +++ b/puzzles/nonograms.org/downloadSamples.js @@ -0,0 +1,38 @@ +#!/usr/bin/env node + +const downloadToFile = require('./downloadToFile'); +const sampleIds = [ + 1149, + 1608, + 2074, + 2684, + 3093, + 4388, + 4392, + 4436, + 5114, + 5126, + 5178, + 5876, + 5903, + 6004, + 6076, + 6577, + 8454, + 9664, + 10509, + 11418, + 13194, + 13677, + 14050, + 14099, + 14656, + 15283, + 15335, + 15466, + 15479, + 15500, + 15527 +]; + +downloadToFile(sampleIds); diff --git a/puzzles/nonograms.org/downloadToFile.js b/puzzles/nonograms.org/downloadToFile.js new file mode 100644 index 0000000..b4445bd --- /dev/null +++ b/puzzles/nonograms.org/downloadToFile.js @@ -0,0 +1,44 @@ +const fs = require("fs"); +const path = require("path"); + +const downloader = require('./downloader'); + +const run = args => args.map(a => +a).forEach((id, i) => { + setTimeout(() => { // required to avoid server errors + downloader(id, ({title, rows, columns}) => { + let fileName = path.join(__dirname, `${title}.json`); + let data = JSON.stringify({columns, rows}); + fs.readFile(fileName, (err) => { + if (err) { + fs.writeFile(fileName, data, (err) => { + if (err) { + console.error(`Error trying to save file ${fileName}`); + } else { + console.error(`Saved file ${fileName}`); + } + }); + } else { + console.error(`File ${fileName} already exists.`); + } + }); + }); + }, 10 * i); +}); + +if (require.main === module) { + let args = Array.from(process.argv); + args.shift(); + let scriptName = args.shift(); + let scriptBaseName = path.basename(scriptName); + + let usageHint = `Usage: node ${scriptBaseName} id [id …]. +Each id must be numeric and a nonograms.org puzzle id.`; + + if (args.length === 0 || !args.every(arg => /^[1-9][0-9]*$/.test(arg))) { + console.error(usageHint); + process.exit(1); + } + run(args); +} else { + module.exports = run; +} diff --git a/puzzles/nonograms.org/downloader.js b/puzzles/nonograms.org/downloader.js new file mode 100644 index 0000000..e549c05 --- /dev/null +++ b/puzzles/nonograms.org/downloader.js @@ -0,0 +1,42 @@ +const fs = require("fs"); +const path = require("path"); +const vm = require('vm'); + +const request = require('request'); + + +const dataRE = /d *= *(\[.*?);/; +const titleRE = /«(.+?)»/; + +module.exports = (id, callback) => { + + console.log(`Downloading puzzle ${id}`); + const getCB = (error, response, body) => { + if (error || response.statusCode !== 200) { + throw error || new Error(`statusCode: ${response.statusCode} while downloading puzzle ${id}`); + } + let d = body.match(dataRE)[1]; + let title = body.match(titleRE)[1]; + const sandbox = { + d: JSON.parse(d), + title + }; + sandbox.global = sandbox; + vm.createContext(sandbox); + + fs.readFile(path.join(__dirname, 'unscrambler.js'), {encoding: 'utf-8'}, (err, src) => { + if (err) { + throw err; + } + vm.runInContext(src, sandbox); + callback({ + title: sandbox.title, + columns: sandbox.columns, + rows: sandbox.rows, + solution: sandbox.content + }); + }); + }; + + request.get(`http://www.nonograms.org/nonograms/i/${id}`, getCB); +}; diff --git a/puzzles/nonograms.org/unscrambler.js b/puzzles/nonograms.org/unscrambler.js new file mode 100644 index 0000000..c36c233 --- /dev/null +++ b/puzzles/nonograms.org/unscrambler.js @@ -0,0 +1,60 @@ +(function () { + + const mod = (x, [a, b, c]) => { + return a * (x[0] % x[3]) + b * (x[1] % x[3]) + c * (x[2] % x[3]); + }; + + let data = d.slice(); + data.shift(); + const columnCount = mod(data.shift(), [1, 1, -1]); + const rowCount = mod(data.shift(), [1, 1, -1]); + const cutPoint = mod(data.shift(), [1, 1, -1]) + 1; + data = data.slice(cutPoint); + let dashInfo = data.shift(); + let dashCount = mod(dashInfo, [dashInfo[0] % dashInfo[3], 2, 1]); + + const solutions = Array(rowCount).fill(1).map(i => Array(columnCount).fill(0)); + let pivot = data.shift(); + let clearData = data.slice(0, dashCount).map(entry => entry.map((x, n) => x - pivot[n])); + clearData.forEach(([column, length, color, row]) => { + for (let i = 0; i < length; i++) { + solutions[row - 1][i + column - 1] = color; + } + }); + + let rowHints = Array(rowCount).fill(1).map(i => []); + solutions.forEach((solution, i) => { + for (let end = 0; end < columnCount; end++) { + const start = end; + const color = solution[start]; + if (!color) { + continue; + } + while (end < columnCount && solution[end] === color) { + end++; + } + rowHints[i].push(end - start); + } + }); + + let columnHints = Array(columnCount).fill(1).map(i => []); + columnHints.forEach((columnHint, i) => { + for (let end = 0; end < rowCount; end++) { + const start = end; + const color = solutions[start][i]; + if (!color) { + continue; + } + while (end < rowCount && solutions[end][i] === color) { + end++; + } + columnHint.push(end - start); + } + }); + + Object.assign(global, { + rows: rowHints, + columns: columnHints, + content: Array.prototype.concat.apply([], solutions) + }); +})(); diff --git a/puzzles/octopus.json b/puzzles/octopus.json new file mode 100644 index 0000000..3929aba --- /dev/null +++ b/puzzles/octopus.json @@ -0,0 +1,115 @@ +{ + "columns": [ + [4], + [9], + [11], + [13], + [4, 5], + [3, 4], + [3, 4, 3], + [3, 4, 6], + [8, 4, 8], + [6, 5, 10], + [2, 5, 4, 3], + [9, 5, 3, 3], + [11, 5, 3, 4], + [12, 5, 4, 4], + [18, 2, 5, 3], + [4, 16, 12, 1], + [3, 17, 12], + [4, 18, 3, 5], + [3, 19, 4, 2], + [6, 10, 9, 1], + [7, 10, 7, 2], + [2, 9, 4, 2], + [14, 2], + [2, 17, 3], + [4, 25, 3], + [8, 27, 5], + [45], + [27, 12], + [25, 6], + [26], + [26], + [27], + [34], + [36], + [8, 27], + [6, 27], + [2, 12, 9], + [12, 6], + [14, 5], + [15, 5], + [15, 4], + [3, 4, 11, 4], + [7, 4, 10, 4, 3], + [3, 10, 5, 5, 3, 4], + [2, 8, 5, 4, 3, 5], + [1, 6, 4, 4, 1, 3, 2], + [5, 6, 3, 2], + [5, 7, 4, 3], + [13, 8, 8], + [13, 5, 3, 5], + [13, 3, 3], + [13, 2, 3], + [3, 2, 3], + [3, 3, 3], + [3, 4, 5], + [3, 2, 9], + [4, 2, 6], + [8, 2], + [4] + ], + "rows": [ + [1], + [1, 2], + [2, 7], + [3, 10], + [3, 11, 4], + [2, 12, 3], + [4, 13, 3], + [5, 13, 2], + [5, 12, 3], + [6, 11, 3, 4], + [5, 10, 4, 6], + [4, 8, 3, 8], + [4, 11, 3, 4, 3], + [4, 12, 3, 4, 2], + [4, 13, 4, 4, 3], + [5, 4, 13, 5, 4, 2], + [7, 5, 12, 7, 4, 2], + [8, 7, 20, 4, 2], + [4, 34, 4, 3], + [3, 3, 29, 4, 3], + [3, 2, 28, 5], + [4, 2, 28, 6], + [4, 1, 38], + [4, 39], + [5, 40], + [46, 7], + [13, 30, 7], + [11, 6, 6, 12, 3, 4], + [8, 5, 6, 13, 2, 3], + [5, 5, 4, 7, 3, 3], + [4, 5, 4, 7, 2, 4], + [4, 5, 4, 8, 3], + [4, 4, 5, 14], + [2, 4, 4, 5, 11], + [7, 4, 4, 5, 9], + [8, 4, 4, 6, 2], + [5, 4, 4, 4, 7, 2], + [4, 3, 3, 3, 10], + [4, 3, 4, 3, 14], + [4, 6, 3, 14], + [4, 6, 3, 14], + [12, 3, 3], + [9, 3, 3, 2], + [9, 3, 3, 2], + [2, 3, 4, 3, 3], + [3, 3, 6], + [2, 4, 5], + [3, 4], + [1, 8], + [4] + ] +} diff --git a/src/Puzzle.js b/src/Puzzle.js new file mode 100644 index 0000000..5480861 --- /dev/null +++ b/src/Puzzle.js @@ -0,0 +1,143 @@ +const assert = require("assert"); +const clone = require('./util').clone; + +class Puzzle { + constructor(data) { + if (typeof data === 'string') { + data = JSON.parse(data); + } + let initialState = this.mapData(data); + this.initAccessors(initialState); + } + + mapData(data) { + this.rowHints = clone(data.rows); + this.columnHints = clone(data.columns); + this.height = this.rowHints.length; + this.width = this.columnHints.length; + if (data.content) { + this.checkConsistency(data); + this.originalContent = clone(data.content); + return clone(data.content); + } + return Array(this.width * this.height).fill(0); + } + + initAccessors(state) { + const width = this.width; + const height = this.height; + + let rows = Array(height); + let makeRow = (rowIndex) => { + let row = Array(width).fill(0); + row.forEach((_, colIndex) => { + Object.defineProperty(row, colIndex, { + get() { + return state[rowIndex * width + colIndex]; + }, + set(el) { + state[rowIndex * width + colIndex] = el; + } + }); + }); + return row; + }; + for (let rowIndex = 0; rowIndex < height; rowIndex++) { + let row = makeRow(rowIndex); + Object.defineProperty(rows, rowIndex, { + get() { + return row; + }, + set(newRow) { + newRow.forEach((el, x) => state[rowIndex * width + x] = el); + } + }); + } + + let columns = Array(width); + let makeColumn = (colIndex) => { + let column = Array(height).fill(0); + column.forEach((_, rowIndex) => { + Object.defineProperty(column, rowIndex, { + get() { + return state[rowIndex * width + colIndex]; + }, + set(el) { + state[rowIndex * width + colIndex] = el; + } + }); + }); + return column; + }; + for (let colIndex = 0; colIndex < width; colIndex++) { + let column = makeColumn(colIndex); + Object.defineProperty(columns, colIndex, { + get() { + return column; + }, + set(newCol) { + newCol.forEach((el, y) => state[y * width + colIndex] = el); + } + }); + } + + Object.defineProperties(this, { + rows: { + get() { + return rows; + }, + set(newRows) { + newRows.forEach((el, i) => { + rows[i] = el; + }); + } + }, + columns: { + get() { + return columns; + }, + set(cols) { + cols.forEach((el, i) => { + columns[i] = el; + }); + } + }, + isFinished: { + get() { + return state.every(item => item !== 0); + } + }, + snapshot: { + get() { + return clone(state); + } + }, + isSolved: { + get() { + let isOk = (line, hints) => { + let actual = line.join('').split(/(?:-1)+/g).map(x => x.length).filter(x => x); + return actual.length === hints.length && actual.every((x, i) => x === hints[i]); + }; + return ( + this.isFinished && + columns.every((col, i) => isOk(col, this.columnHints[i])) && + rows.every((row, i) => isOk(row, this.rowHints[i])) + ); + } + } + }); + + this.import = function(puzzle) { + state = clone(puzzle.snapshot); + } + } + + checkConsistency({content}) { + let invalid = !content || !Array.isArray(content); + invalid = invalid || (content.length !== this.height * this.width); + invalid = invalid || !content.every(i => i === -1 || i === 0 || i === 1); + assert(!invalid, 'Invalid content data'); + } +} + +module.exports = Puzzle; diff --git a/src/Strategy.js b/src/Strategy.js new file mode 100644 index 0000000..53b4d96 --- /dev/null +++ b/src/Strategy.js @@ -0,0 +1,206 @@ +const ascii = require('./serializers/ascii'); +const util = require("./util"); +const Puzzle = require('./Puzzle'); +const assert = require("assert"); + +const debugMode = process.env.hasOwnProperty('NONODEBUG'); + +class Strategy { + constructor(solvers) { + this.solvers = solvers; + } + + solve(puzzle, withTrialAndError = true, randomize = true) { + if (debugMode) { + var start = Date.now(); + var statistics = Array(this.solvers.length).fill(0); + var solutionSequence = []; + + } + this.visited = { + rows: Array(puzzle.height).fill(0).map(() => new Uint8Array(this.solvers.length)), + columns: Array(puzzle.width).fill(0).map(() => new Uint8Array(this.solvers.length)) + }; + let progress; + do { + let snapshot = puzzle.snapshot; + progress = false; + this.solvers.forEach((solver, i) => { + if (progress) { + return; + } + this.solveOnce(puzzle, solver, i, solutionSequence); + progress = puzzle.snapshot.toString() !== snapshot.toString(); + if (debugMode) { + statistics[i]++; + } + }); + } while(progress); + if (withTrialAndError && !puzzle.isFinished) { + if (debugMode) { + console.log('must start guessing'); + } + let deepResult = this.guessAndConquer(puzzle, randomize); + if (deepResult) { + puzzle.import(deepResult); + } + } + if (debugMode) { + console.log(`Solution sequence: [${solutionSequence.join(',')}]`); + console.log(`Time elapsed: ${Date.now() - start}ms`); + console.log(`Runs (on puzzle) per solver: ${JSON.stringify(statistics)}`); + } + } + + solveOnce(puzzle, solver, solverIndex, solutionSequence) { + let skipEarly = solver.speed === 'slow'; + let skip = false; + + let optimizeOrder = (lines, hints) => { + let unsolvedLines = lines.reduce((result, line, index) => { + let zeros = line.reduce((count, x) => count + (x === 0 ? 1 : 0), 0); + if (!zeros) { + return result; + } + result.push({line, index, zeros}); + return result; + }, []); + + if (skipEarly) { + unsolvedLines.forEach(lineMeta => { + let {index, zeros} = lineMeta; + let hintSum = util.hintSum(hints[index]); + let estimate = zeros < hintSum ? 0 : Math.pow(zeros - hintSum, hints[index].length); + lineMeta.estimate = estimate; + }); + unsolvedLines.sort(({estimate: left}, {estimate: right}) => left - right); + } + return unsolvedLines; + }; + + let run = (lines, hints, onRow) => { + let visited = onRow ? + {current: this.visited.rows, other: this.visited.columns} : + {current: this.visited.columns, other: this.visited.rows}; + let rearrangedLines = optimizeOrder(lines, hints); + rearrangedLines.forEach(({line, index: i, estimate}) => { + if (skip || visited.current[i][solverIndex]) { + return; + } + if (debugMode) { + console.log(`Running solver ${solverIndex} on ${onRow ? 'row' : 'column'} ${i}`, JSON.stringify(line.slice()), hints[i]); + if (estimate) { + console.log(`Estimated effort: ${estimate}`); + } + } + visited.current[i][solverIndex] = 1; + let [trimmedLine, trimmedHints, trimInfo] = util.trimLine(line, hints[i]); + let start = Date.now(); + let newLine = solver(trimmedLine, trimmedHints); + if (debugMode) { + let end = Date.now(); + if (end - start > 100) { + console.log(`Long run: ${end - start}ms`); + } + } + + let hasChanged = false; + let changedLines = []; + if (newLine) { + newLine = util.restoreLine(newLine, trimInfo); + line.forEach((el, i) => { + if (el !== newLine[i]) { + line[i] = newLine[i]; + visited.other[i].fill(0); + if (debugMode) { + changedLines.push(i); + } + } + }); + hasChanged = changedLines.length > 0; + skip = hasChanged && skipEarly; + } + if (!debugMode) { + process.stdout.write('.'); + } else if (hasChanged) { + console.log(`found ${newLine}`); + console.log(ascii(puzzle)); + console.log(`Must revisit ${onRow ? 'column' : 'row'}${changedLines.length > 1 ? 's' : ''} ${changedLines.join(',')}`); + solutionSequence.push(`(${solverIndex})${onRow ? 'r' : 'c'}${i}[${changedLines.join(',')}]`); + } + }); + }; + run(puzzle.rows, puzzle.rowHints, true); + if (skip) { + return; + } + run(puzzle.columns, puzzle.columnHints); + } + + guessAndConquer(puzzle, randomize, recursionDepth = 0) { + const maxRecursion = 2; + const maxGuessCount = 100; + if (puzzle.isFinished) { + return; + } + let snapshot = puzzle.snapshot; + let zeroIndexes = snapshot.reduce((result, x, i) => { + if (x === 0) { + result.push(i); + } + return result; + }, []); + assert(zeroIndexes.length > 0, 'Contradiction in trial and error'); + for (let i = 0; i < zeroIndexes.length && i < maxGuessCount; i++) { + let index; + if (randomize) { + let random = Math.floor(Math.random() * zeroIndexes.length); + index = zeroIndexes.splice(random, 1)[0]; + } else { + index = zeroIndexes.shift(); + } + snapshot[index] = 1; + let trial = new Puzzle({ + rows: puzzle.rowHints.slice(), + columns: puzzle.columnHints.slice(), + content: snapshot + }); + if (debugMode) { + console.log(`Using trialAndError method on ${i}. zero (index ${index})`); + } + try { + this.solve(trial, false); + if (trial.isFinished) { + if (!trial.isSolved) { + throw new Error('Not a solution'); + } + if (debugMode) { + console.log(`[${recursionDepth}] Successfully guessed square ${index}=1`); + } + return trial; + } else { + snapshot[index] = 0; + } + } catch (e) { + snapshot[index] = -1; + let anotherTry = new Puzzle({ + rows: trial.rowHints, + columns: trial.columnHints, + content: snapshot + }); + if (recursionDepth < maxRecursion) { + let result = this.guessAndConquer(anotherTry, randomize, recursionDepth + 1); + if (result) { + if (debugMode) { + console.log(`[${recursionDepth}] Successfully guessed square ${index}=-1`); + } + return result; + } + } + } + } + return null; + } +} + +module.exports = Strategy; diff --git a/src/allSolvers.js b/src/allSolvers.js new file mode 100644 index 0000000..8ee5fdb --- /dev/null +++ b/src/allSolvers.js @@ -0,0 +1,7 @@ +/** + * all solvers in descending order by speed + */ +module.exports = [ + require('./solvers/pushSolver').solve, + require('./solvers/bruteForceSolver').solve +]; diff --git a/src/gapDistributor.js b/src/gapDistributor.js new file mode 100644 index 0000000..1fa320c --- /dev/null +++ b/src/gapDistributor.js @@ -0,0 +1,52 @@ +const pushLeft = require('./solvers/pushSolver').pushLeft; + +let findGaps = line => line.reduce((result, el, i, line) => { + if (el > -1) { + if (line[i - 1] > -1) { + result[result.length - 1][1]++; + } else { + result.push([i, i + 1]); + } + } + return result; +}, []); + +let allWithOneGap = (line, gaps, hints) => { + let left = gaps[0][0]; + let right = gaps[0][1]; + if (pushLeft(line.slice(left, right), hints)) { + return {gaps, distributions: [[hints]]}; + } + return null; +}; + +let gapDistributor = (line, hints) => { + let gaps = findGaps(line); + if (gaps.length === 1) { + return allWithOneGap(line, gaps, hints); + } + let distributions = []; + let gap = gaps[0]; + for (let hintCount = 0; hintCount <= hints.length; hintCount++) { + let first = allWithOneGap(line, [gap], hints.slice(0, hintCount)); + if (!first) { + continue; + } + let second = gapDistributor(line.slice(gap[1]), hints.slice(hintCount)); + if (!second) { + continue; + } + first.distributions.forEach(x => { + x.forEach(e => { + second.distributions.forEach(y => { + let item = y.slice(); + item.unshift(e); + distributions.push(item); + }); + }); + }, []); + } + return {gaps, distributions}; +}; + +module.exports = gapDistributor; diff --git a/src/index.js b/src/index.js new file mode 100755 index 0000000..d563c39 --- /dev/null +++ b/src/index.js @@ -0,0 +1,12 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); +const runOnFile = require('./runOnFile'); + +let targetDir = path.join(__dirname, '..', 'output'); +if (!fs.existsSync(targetDir)) { + fs.mkdirSync(targetDir); +} + +runOnFile(path.join(__dirname, '..', 'puzzles', 'input.json'), path.join(targetDir, 'result.svg')); diff --git a/src/runOnFile.js b/src/runOnFile.js new file mode 100644 index 0000000..a6e2d7c --- /dev/null +++ b/src/runOnFile.js @@ -0,0 +1,24 @@ +const fs = require('fs'); + +const allSolvers = require('./allSolvers'); +const ascii = require('./serializers/ascii'); +const svg = require('./serializers/svg'); +const Puzzle = require('./Puzzle'); +let Strategy = require('./Strategy'); + +module.exports = (inputFilename, outputFilename) => { + let puzzleData = fs.readFileSync(inputFilename, 'utf-8'); + let puzzle = new Puzzle(puzzleData); + let strategy = new Strategy(allSolvers); + strategy.solve(puzzle); + + if (puzzle.isFinished) { + console.log('Puzzle solved!'); + } else { + console.log('Could not solve puzzle'); + console.log(JSON.stringify(puzzle.snapshot)); + } + console.log(ascii(puzzle)); + fs.writeFileSync(outputFilename, svg(puzzle)); + console.log(`Output saved to ${outputFilename}.`); +}; diff --git a/src/serializers/ascii.js b/src/serializers/ascii.js new file mode 100644 index 0000000..5fabecf --- /dev/null +++ b/src/serializers/ascii.js @@ -0,0 +1,39 @@ +let leftPad = require('left-pad'); + +/** + * Draw a puzzle in ASCII art + */ +let draw = ({rowHints, columnHints, rows}) => { + let result = ''; + let maxLength = a => a.map(x => x.length).reduce((max, i) => i > max ? i : max, 0); + + let joinedRowHints = rowHints.map(x => x.join(' ')); + let maxRowHintLength = maxLength(joinedRowHints); + + let colHints = columnHints.map(x => x.join(' ')); + let maxColHintLength = maxLength(colHints); + colHints = colHints.map(x => leftPad(x, maxColHintLength).split('')); + + for (let i = 0; i < maxColHintLength; i++) { + let n = colHints.map(x => x.shift()).join(''); + result += leftPad('', maxRowHintLength); + result += ' ' + n; + result += '\n'; + } + result += '\n'; + + rows.forEach((content, i) => { + let art = content.map(x => { + if (x === -1) { + return 'x'; + } + return ['░', '█'][x] + }).join(''); + result += `${leftPad(joinedRowHints[i], maxRowHintLength)} ${art}`; + result += '\n'; + }); + + return result; +}; + +module.exports = draw; diff --git a/src/serializers/svg.js b/src/serializers/svg.js new file mode 100644 index 0000000..b16e88b --- /dev/null +++ b/src/serializers/svg.js @@ -0,0 +1,130 @@ +/** + * Draw a puzzle as an SVG image + */ + + +let drawHeader = (viewBox) => { + const scaling = 20; + return ` + `; +}; + +let drawDefs = () => { + const c = .5; // cross size + const w = .75; // box width + return ` + + + + + + `; +}; + +let drawGrid = ({width, height}) => { + let result = ''; + for (let x = 0; x <= width; x++) { + result += ``; + } + result += ''; + for (let y = 0; y <= height; y++) { + result += ``; + } + result += ''; + return result; +}; + +let drawHints = ({rowHints, columnHints}) => { + let drawHint = ({x, y, text}) => { + return ` + ${text}`; + }; + let result = ''; + rowHints.forEach((rowHints, y) => { + rowHints.forEach((hint, x) => { + result += drawHint({x:x - rowHints.length - .1, y, text:hint}); + }); + }); + columnHints.forEach((colHints, x) => { + colHints.forEach((hint, y) => { + result += drawHint({x, y: y - colHints.length - .1, text:hint}); + }); + }); + result += ''; + return result; +}; + +let drawState = state => { + let result = ''; + state.forEach((row, y) => { + row.forEach((el, x) => { + if (el) { + result += ``; + } + }); + }); + result += ''; + return result; +}; + +let draw = ({rowHints, columnHints, rows}) => { + let maxLength = a => a.map(x => x.length).reduce((max, x) => x > max ? x : max, 0); + + let deltaX = maxLength(rowHints); + let deltaY = maxLength(columnHints); + let width = columnHints.length; + let height = rowHints.length; + + const rim = .5; + let viewBox = [ + -(deltaX + rim), + -(deltaY + rim), + width + deltaX + 2 * rim, + height + deltaY + 2 * rim + ]; + let result = ''; + result += drawHeader(viewBox); + result += drawDefs({width, height}); + result += ``; + result += drawHints({rowHints, columnHints}); + result += drawGrid({width, height}); + result += drawState(rows); + result += ''; + + return result; +}; + +module.exports = draw; diff --git a/src/solvers/bruteForceSolver.js b/src/solvers/bruteForceSolver.js new file mode 100644 index 0000000..81c38e0 --- /dev/null +++ b/src/solvers/bruteForceSolver.js @@ -0,0 +1,137 @@ +const util = require('../util'); +const findGapDistributions = require('../gapDistributor'); +const pushSolver = require('./pushSolver'); +const assert = require("assert"); + +const debugMode = process.env.hasOwnProperty('NONODEBUG'); + +const cacheLimits = [2, 20]; + +/** + * @returns {{zeros: Uint8Array, ones: Uint8Array}} + */ +let solveGap = (gap, hints) => { + let zeros = new Uint8Array(gap.length); + let ones = new Uint8Array(gap.length); + if (hints.length === 0) { + if (gap.includes(1)) { + return null; + } + return { + zeros: zeros.fill(1), + ones + }; + } + if (!solveGap.cache) { + solveGap.cache = {}; + } + if (cacheLimits[0] <= hints.length && hints.length <= cacheLimits[1]) { + let candidate = solveGap.cache[JSON.stringify([gap, hints])]; + if (candidate) { + return candidate; + } + } + let hint = hints[0]; + let maxIndex = gap.indexOf(1); + if (maxIndex === -1) { + maxIndex = gap.length; + } + let hintSum = util.hintSum(hints); + maxIndex = Math.min(maxIndex, gap.length - hintSum); + if (maxIndex > hintSum && !gap.includes(1)) { + return { + zeros: zeros.fill(1), + ones: ones.fill(1) + }; + } + for (let hintStart = 0; hintStart <= maxIndex; hintStart++) { + if (gap[hintStart + hint] === 1) { + continue; + } + let rest = solveGap(gap.slice(hintStart + hint + 1), hints.slice(1)); + if (!rest) { + continue; + } + for (let k = 0; k < gap.length; k++) { + if (k < hintStart || k === hintStart + hint) { + zeros[k] = 1; + } else if (k < hintStart + hint) { + ones[k] = 1; + } else { + zeros[k] = zeros[k] || rest.zeros[k - (hintStart + hint + 1)]; + ones[k] = ones[k] || rest.ones[k - (hintStart + hint + 1)]; + } + } + } + let result = {zeros, ones}; + solveGap.cache[JSON.stringify([gap, hints])] = result; + return result; +}; + +let solveGapWithHintList = (gap, hintList) => { + assert(!gap.includes(-1), 'solveGapWithHintList called with a non-gap'); + let zeros = new Uint8Array(gap.length); + let ones = new Uint8Array(gap.length); + hintList.forEach(hints => { + let item = solveGap(gap, hints); + zeros.forEach((zero, i) => zeros[i] = zero || item.zeros[i]); + ones.forEach((one, i) => ones[i] = one || item.ones[i]); + }); + let result = Array.from(zeros).map((zero, i) => { + let one = ones[i]; + if (zero) { + return one ? 0 : -1; + } + if (one) { + return 1; + } + throw new Error('Cannot fill gap', gap, hintList); + }); + if (debugMode) { + if (gap.some((x, i) => x !== result[i])) { + console.log(`Gap solved: [${gap}], ${JSON.stringify(hintList)} -> ${result}`); + } else { + console.log(`No progress on gap: [${gap}], ${JSON.stringify(hintList)}`); + } + } + return result; +}; + +let solve = (line, hints) => { + if (line.every(el => el === 0)) { + return pushSolver.solve(line, hints); + } + let {gaps, distributions} = findGapDistributions(line, hints); + if (debugMode) { + console.log(`Gap distributions: ${distributions.length}`); + } + assert.ok(distributions.length > 0, `Contradiction in line ${line} | ${hints}`); + let distributionsPerGap = gaps.map((gap, i) => { + let set = new Set(); + distributions.forEach(dist => { + set.add(JSON.stringify(dist[i])); + }); + return Array.from(set).map(x => JSON.parse(x)); + }); + let result = line.slice(); + let changed = new Set(); + gaps.forEach((gap, i) => { + let gapResult = solveGapWithHintList(line.slice(gap[0], gap[1]), distributionsPerGap[i]); + gapResult.forEach((item, i) => { + let before = result[gap[0] + i]; + if (before !== item) { + result[gap[0] + i] = item; + changed.add(item); + } + }); + }); + assert(!changed.has(0), `Contradiction in line ${line} | ${hints}`); + if (changed.has(-1)) { + return solve(result, hints) || result; + } + return changed.has(1) ? result : null; +}; + +solve.speed = 'slow'; + +module.exports = {solveGap, solveGapWithHintList, solve}; diff --git a/src/solvers/pushSolver.js b/src/solvers/pushSolver.js new file mode 100644 index 0000000..fc2c06b --- /dev/null +++ b/src/solvers/pushSolver.js @@ -0,0 +1,78 @@ +let shouldSkip = (line, hint, i) => { + let allZeros = line[i - 1] === 0; + let collision = line[i + hint] === 1; + for (let x = i; x < i + hint; x++) { + if (line[x] === -1 || x >= line.length) { + collision = true; + break; + } + if (line[x]) { + allZeros = false; + } + } + return allZeros || collision; +}; + +let pushLeft = (line, hints) => { + if (hints.length === 0) { + return line.includes(1) ? null : line; + } + let hint = hints[0]; + let maxIndex = line.indexOf(1); + if (maxIndex === -1) { + maxIndex = line.length - hint; + } + for (let i = 0; i <= maxIndex; i++) { + if (shouldSkip(line, hint, i)) { + continue; + } + let rest = pushLeft(line.slice(i + hint + 1), hints.slice(1)); + if (rest) { + line = line.slice(); + for (let x = i; x < i + hint; x++) { + line[x] = 1; + } + for (let x = 0; x < rest.length; x++) { + line[x + i + hint + 1] = rest[x]; + } + return line; + } + } + return null; +}; + + +let enumerate = (array) => { + for (let i = 0, j = array[0] % 2; i < array.length; i++) { + if (array[i] === -1) { array[i] = 0} + if (array[i] % 2 !== j % 2) { + j++; + } + array[i] = j; + } +}; + +let solve = (line, hints) => { + let leftmost = pushLeft(line, hints); + if (!leftmost) { + return null; + } + + let reverseLine = line.slice().reverse(); + let reverseHints = hints.slice().reverse(); + let rightmost = pushLeft(reverseLine, reverseHints).reverse(); + + enumerate(leftmost); + enumerate(rightmost); + + return leftmost.map((el, i) => { + if (el === rightmost[i]) { + return (el % 2) ? 1 : -1; + } + return line[i]; + }); +}; + +solve.speed = 'fast'; + +module.exports = {solve, pushLeft}; diff --git a/src/util.js b/src/util.js new file mode 100644 index 0000000..ba87e9d --- /dev/null +++ b/src/util.js @@ -0,0 +1,69 @@ +const clone = x => JSON.parse(JSON.stringify(x)); + +const hintSum = hints => hints.reduce((x, y, i) => x + y + (i ? 1 : 0)); + +const trimLine = (line, hints) => { + let minIndex = line.indexOf(0); + if (minIndex === -1) { + throw new Error('Cannot trim solved line'); + } + if (line[minIndex - 1] === 1) { + minIndex--; + } + let clonedHints = hints.slice(); + for (let i = 0; i < minIndex; i++) { + if (line[i] === 1) { + let start = i; + while (i < minIndex && line[i] === 1) { + i++; + } + if (i === minIndex) { // on the rim + clonedHints[0] -= i - start; + if (clonedHints[0] === 0) { + clonedHints[0] = 1; + minIndex -= 1; + break; + } + } else { + clonedHints.shift(); + } + } + } + let maxIndex = line.lastIndexOf(0); + if (line[maxIndex + 1] === 1) { + maxIndex++; + } + for (let i = line.length; i > maxIndex; i--) { + if (line[i] === 1) { + let start = i; + while (i > maxIndex && line[i] === 1) { + i--; + } + if (i === maxIndex) { // on the rim + clonedHints[clonedHints.length - 1] -= start - i; + if (clonedHints[clonedHints.length - 1] === 0) { + clonedHints[clonedHints.length - 1] = 1; + maxIndex += 1; + break; + } + } else { + clonedHints.pop(); + } + } + } + if (clonedHints.some(x => x < 0)) { + throw new Error(`Impossible line ${line}, ${hints}`); + } + return [line.slice(minIndex, maxIndex + 1), clonedHints, {left: line.slice(0, minIndex), right: line.slice(maxIndex + 1)}]; +}; + +const restoreLine = (line, trimInfo) => { + return trimInfo.left.concat(line).concat(trimInfo.right); +}; + +module.exports = { + clone, + trimLine, + restoreLine, + hintSum +}; diff --git a/test/Puzzle.test.js b/test/Puzzle.test.js new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/test/Puzzle.test.js @@ -0,0 +1 @@ + diff --git a/test/bruteForce.json b/test/bruteForce.json new file mode 100644 index 0000000..8199123 --- /dev/null +++ b/test/bruteForce.json @@ -0,0 +1,133 @@ +[{ + "describe": ["without gaps", { + "it": [ + [ + "works with empty line and one hint", + [3], + [0, 0, 0, 0, 0], + [0, 0, 1, 0, 0] + ], + [ + [4], + [0, 0, 0, 0, 0], + [0, 1, 1, 1, 0] + ], + [ + [5], + [0, 0, 0, 0, 0], + [1, 1, 1, 1, 1] + ], + [ + [2], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0] + ], + [ + "works with empty line and multiple hints", + [2, 2], + [0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 1, 0] + ], + [ + [4, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 1, 0, 0, 0, 0, 0, 0] + ], + [ + [2, 1], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0] + ], + [ + "works with partially filled line and one hint", + [3], + [0, 1, 0, 0, 0, 0], + [0, 1, 1, 0, -1, -1] + ], + [ + [3], + [1, 0, 0, 0, 0], + [1, 1, 1, -1, -1] + ], + [ + [4], + [0, 0, 0, 0, 1, 0, 0, 0, 0, 0], + [-1, 0, 0, 0, 1, 0, 0, 0, -1, -1] + ], + [ + [4], + [0, 0, 0, 0, 1, 0, 1, 0, 0, 0], + [-1, -1, -1, 0, 1, 1, 1, 0, -1, -1] + ], + [ + "works with partially filled line and multiple hints", + [3, 3], + [0, 1, 0, 0, 0, 0, 1, 0], + [0, 1, 1, 0, 0, 1, 1, 0] + ], + [ + [3, 3], + [0, 1, 0, 0, 0, 1, 0], + [1, 1, 1, -1, 1, 1, 1] + ], + [ + [3, 2], + [1, 0, 0, 0, 0, 1], + [1, 1, 1, -1, 1, 1] + ], + [ + [4, 1], + [0, 0, 0, 0, 1, 0, 0, 0, 0, 0], + [-1, 0, 0, 0, 1, 0, 0, 0, 0, 0] + ], + [ + [4, 2], + [0, 0, 0, 0, 1, 0, 1, 0, 0, 0], + [-1, 0, 0, 1, 1, 0, 1, 0, 0, 0] + ] + ] + }] +}, { + "describe": ["with gaps", { + "it": [ + [ + "works with one gap and some occupation and one hint", + [1], + [0, 0, -1, 0, 1], + [-1, -1, -1, -1, 1] + ], + [ + "works with multiple gaps and some occupation and multiple hints", + [3, 3], + [0, 0, 0, -1, 1, 0, 0, -1, 0, 0, 0], + [0, 0, 0, -1, 1, 1, 1, -1, 0, 0, 0] + ], + [ + [3, 1, 3], + [0, 0, 0, -1, 1, 0, 0, -1, 0, 0, 0], + [1, 1, 1, -1, 1, -1, -1, -1, 1, 1, 1] + ], + [ + [2, 3, 7], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, -1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, -1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0] + ], + [ + [2, 4, 1, 1], + [-1, -1, 1, 1, -1, -1, -1, -1, -1, 1, 1, 1, 1, -1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], + [-1, -1, 1, 1, -1, -1, -1, -1, -1, 1, 1, 1, 1, -1, 0, -1, 1, -1, 0, 0, 0, 0, 0, 0, 0] + ], + [ + [2, 4, 1, 1], + [-1, -1, -1, -1, 1, 1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], + null + ], + [ + "works with complicated cases", + [8, 1, 13, 4, 6, 9, 4, 3, 2, 4], + [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] + ] + }] +}] diff --git a/test/bruteForceSolver.test.js b/test/bruteForceSolver.test.js new file mode 100644 index 0000000..0213411 --- /dev/null +++ b/test/bruteForceSolver.test.js @@ -0,0 +1,12 @@ +const path = require('path'); +const expect = require('expect'); + +const runTestsOnJSON = require('./runTestsOnJSON'); +const bruteForceSolver = require('../src/solvers/bruteForceSolver'); + +describe('bruteForceSolver', () => { + + describe('solving', () => { + runTestsOnJSON(bruteForceSolver.solve, path.join(__dirname, 'bruteForce.json')); + }); +}); diff --git a/test/gapDistributor.test.js b/test/gapDistributor.test.js new file mode 100644 index 0000000..6d4333f --- /dev/null +++ b/test/gapDistributor.test.js @@ -0,0 +1,192 @@ +const expect = require('expect'); + +const gapDistributor = require('../src/gapDistributor'); + +describe('gapDistributor', () => { + + describe('distributing in empty gaps', () => { + describe('one gap', () => { + describe('one hint', () => { + it('works', () => { + let line = [0, 0, 0, 0, 0]; + let hints = [2]; + let expected = { + gaps: [[0, 5]], + distributions: [[[2]]] + }; + let actual = gapDistributor(line, hints); + expect(actual).toEqual(expected); + }); + }); + describe('two hints', () => { + it('works', () => { + let line = [0, 0, 0, 0, 0]; + let hints = [2, 2]; + let expected = { + gaps: [[0, 5]], + distributions: [[[2, 2]]] + }; + let actual = gapDistributor(line, hints); + expect(actual).toEqual(expected); + }); + }); + }); + describe('multiple gaps', () => { + describe('one hint', () => { + it('works with short hint', () => { + let line = [0, 0, 0, 0, -1, 0, 0, 0]; + let hints = [2]; + let expected = { + gaps: [[0, 4], [5, 8]], + distributions: [[[], [2]], [[2], []]] + }; + let actual = gapDistributor(line, hints); + expect(actual).toEqual(expected); + }); + it('works with long hint', () => { + let line = [0, 0, -1, 0, 0, 0, -1, 0, 0, 0]; + let hints = [3]; + let expected = { + gaps: [[0, 2], [3, 6], [7, 10]], + distributions: [[[], [], [3]], [[], [3], []]] + }; + let actual = gapDistributor(line, hints); + expect(actual).toEqual(expected); + }); + }); + describe('two hints', () => { + it('works with 2,1', () => { + let line = [0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, 0]; + let hints = [2, 1]; + let expected = { + gaps: [[0, 5], [7, 12]], + distributions: [[[], [2, 1]], [[2], [1]], [[2, 1], []]] + }; + let actual = gapDistributor(line, hints); + expect(actual).toEqual(expected); + }); + it('works with 2,2', () => { + let line = [0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, 0]; + let hints = [2, 2]; + let expected = { + gaps: [[0, 5], [7, 12]], + distributions: [[[], [2, 2]], [[2], [2]], [[2, 2], []]] + }; + let actual = gapDistributor(line, hints); + expect(actual).toEqual(expected); + }); + it('works with 2,3', () => { + let line = [0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, 0]; + let hints = [2, 3]; + let expected = { + gaps: [[0, 5], [7, 12]], + distributions: [[[2], [3]]] + }; + let actual = gapDistributor(line, hints); + expect(actual).toEqual(expected); + }); + }); + }); + }); + describe('distributing in non-empty gaps', () => { + describe('one gap', () => { + describe('one hint', () => { + it('works', () => { + let line = [0, 0, 1, 0, 0]; + let hints = [2]; + let expected = { + gaps: [[0, 5]], + distributions: [[[2]]] + }; + let actual = gapDistributor(line, hints); + expect(actual).toEqual(expected); + }); + }); + describe('two hints', () => { + it('works', () => { + let line = [0, 1, 0, 0, 0]; + let hints = [2, 2]; + let expected = { + gaps: [[0, 5]], + distributions: [[[2, 2]]] + }; + let actual = gapDistributor(line, hints); + expect(actual).toEqual(expected); + }); + it('works with impossible', () => { + let line = [0, 0, 1, 0]; + let hints = [2, 2]; + let expected = null; + let actual = gapDistributor(line, hints); + expect(actual).toEqual(expected); + }); + }); + describe('complicated case', () => { + it('works', () => { + let line = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1]; + let hints = [2, 2]; + let expected = null; + let actual = gapDistributor(line, hints); + expect(actual).toEqual(expected); + + }) + }); + }); + describe('multiple gaps', () => { + describe('one hint', () => { + it('works with short hint', () => { + let line = [0, 0, 0, 0, -1, 0, 1, 0]; + let hints = [2]; + let expected = { + gaps: [[0, 4], [5, 8]], + distributions: [[[], [2]]] + }; + let actual = gapDistributor(line, hints); + expect(actual).toEqual(expected); + }); + it('works with long hint', () => { + let line = [0, 0, -1, 0, 1, 0, -1, 0, 0, 0]; + let hints = [3, 3]; + let expected = { + gaps: [[0, 2], [3, 6], [7, 10]], + distributions: [[[], [3], [3]]] + }; + let actual = gapDistributor(line, hints); + expect(actual).toEqual(expected); + }); + }); + describe('two hints', () => { + it('works with 2,1', () => { + let line = [0, 0, 1, 0, 0, -1, -1, 0, 0, 0, 0, 0]; + let hints = [2, 1]; + let expected = { + gaps: [[0, 5], [7, 12]], + distributions: [[[2], [1]], [[2, 1], []]] + }; + let actual = gapDistributor(line, hints); + expect(actual).toEqual(expected); + }); + it('works with 2,2', () => { + let line = [0, 1, 0, 0, 0, -1, -1, 0, 0, 0, 1, 0]; + let hints = [2, 2]; + let expected = { + gaps: [[0, 5], [7, 12]], + distributions: [[[2], [2]]] + }; + let actual = gapDistributor(line, hints); + expect(actual).toEqual(expected); + }); + it('works with 2,3', () => { + let line = [0, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 0]; + let hints = [2, 3]; + let expected = { + gaps: [[0, 5], [7, 12]], + distributions: [[[2], [3]]] + }; + let actual = gapDistributor(line, hints); + expect(actual).toEqual(expected); + }); + }); + }); + }); +}); diff --git a/test/pushSolver.test.js b/test/pushSolver.test.js new file mode 100644 index 0000000..13aeb5c --- /dev/null +++ b/test/pushSolver.test.js @@ -0,0 +1,290 @@ +const expect = require('expect'); + +const pushSolver = require('../src/solvers/pushSolver'); + +describe('pushSolver', () => { + + describe('left pushing', () => { + describe('without gaps', () => { + it('works with a single hint on an empty line', () => { + let line = [0, 0, 0, 0, 0]; + let hints = [3]; + let expected = [1, 1, 1, 0, 0]; + let actual = pushSolver.pushLeft(line, hints); + expect(actual).toEqual(expected); + + line = [0, 0, 0]; + hints = [3]; + expected = [1, 1, 1]; + actual = pushSolver.pushLeft(line, hints); + expect(actual).toEqual(expected); + + line = [0, 0]; + hints = [3]; + expected = null; + actual = pushSolver.pushLeft(line, hints); + expect(actual).toEqual(expected); + }); + + it('works with multiple hints on an empty line', () => { + let line = [0, 0, 0, 0, 0]; + let hints = [1, 2]; + let expected = [1, 0, 1, 1, 0]; + let actual = pushSolver.pushLeft(line, hints); + expect(actual).toEqual(expected); + + line = [0, 0, 0, 0, 0, 0, 0, 0]; + hints = [3, 1, 1]; + expected = [1, 1, 1, 0, 1, 0, 1, 0]; + actual = pushSolver.pushLeft(line, hints); + expect(actual).toEqual(expected); + + line = [0, 0, 0, 0]; + hints = [2, 2]; + expected = null; + actual = pushSolver.pushLeft(line, hints); + expect(actual).toEqual(expected); + }); + + it('works with a single hint on a partially filled line', () => { + let line = [0, 1, 0, 0, 0]; + let hints = [3]; + let expected = [1, 1, 1, 0, 0]; + let actual = pushSolver.pushLeft(line, hints); + expect(actual).toEqual(expected); + + line = [0, 1, 0]; + hints = [3]; + expected = [1, 1, 1]; + actual = pushSolver.pushLeft(line, hints); + expect(actual).toEqual(expected); + + line = [0, 0, 0, 1, 0, 0]; + hints = [3]; + expected = [0, 1, 1, 1, 0, 0]; + actual = pushSolver.pushLeft(line, hints); + expect(actual).toEqual(expected); + + line = [0, 1, 0, 1, 0, 0]; + hints = [2]; + expected = null; + actual = pushSolver.pushLeft(line, hints); + expect(actual).toEqual(expected); + + line = [0, 0, 0, 1, 1, 0]; + hints = [3]; + expected = [0, 0, 1, 1, 1, 0]; + actual = pushSolver.pushLeft(line, hints); + expect(actual).toEqual(expected); + + line = [0, 0, 1, 0, 1, 0]; + hints = [4]; + expected = [0, 1, 1, 1, 1, 0]; + actual = pushSolver.pushLeft(line, hints); + expect(actual).toEqual(expected); + + line = [0, 1]; + hints = [3]; + expected = null; + actual = pushSolver.pushLeft(line, hints); + expect(actual).toEqual(expected); + + line = [1, 0, 1, 0, 1, 0, 1]; + hints = [2]; + expected = null; + actual = pushSolver.pushLeft(line, hints); + expect(actual).toEqual(expected); + }); + + it('works with multiple hints on a partially filled line', () => { + let line = [0, 1, 0, 0, 0]; + let hints = [3, 1]; + let expected = [1, 1, 1, 0, 1]; + let actual = pushSolver.pushLeft(line, hints); + expect(actual).toEqual(expected); + + line = [0, 1, 0, 0, 0, 0, 0]; + hints = [3, 2]; + expected = [1, 1, 1, 0, 1, 1, 0]; + actual = pushSolver.pushLeft(line, hints); + expect(actual).toEqual(expected); + + line = [0, 1, 0, 1, 0, 1]; + hints = [3, 1]; + expected = [0, 1, 1, 1, 0, 1]; + actual = pushSolver.pushLeft(line, hints); + expect(actual).toEqual(expected); + + line = [0, 0, 0, 1, 1, 0, 0, 0, 0]; + hints = [2, 3, 1]; + expected = [1, 1, 0, 1, 1, 1, 0, 1, 0]; + actual = pushSolver.pushLeft(line, hints); + expect(actual).toEqual(expected); + + line = [0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0]; + hints = [3, 3, 1]; + expected = [0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0]; + actual = pushSolver.pushLeft(line, hints); + expect(actual).toEqual(expected); + + line = [0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1]; + hints = [3, 3, 1]; + expected = null; + actual = pushSolver.pushLeft(line, hints); + expect(actual).toEqual(expected); + + }); + + }); + describe('with gaps', () => { + it('works with one gap and occupation', () => { + let line = [0, 0, -1, 0, 1]; + let hints = [1]; + let expected = [-1, -1, -1, -1, 1]; + let actual = pushSolver.solve(line, hints); + expect(actual).toEqual(expected); + }); + }); + }); + + describe('solving', () => { + describe('without gaps', () => { + it('works with empty line and one hint', () => { + let line = [0, 0, 0, 0, 0]; + let hints = [3]; + let expected = [0, 0, 1, 0, 0]; + let actual = pushSolver.solve(line, hints); + expect(actual).toEqual(expected); + + line = [0, 0, 0, 0, 0]; + hints = [4]; + expected = [0, 1, 1, 1, 0]; + actual = pushSolver.solve(line, hints); + expect(actual).toEqual(expected); + + line = [0, 0, 0, 0, 0]; + hints = [5]; + expected = [1, 1, 1, 1, 1]; + actual = pushSolver.solve(line, hints); + expect(actual).toEqual(expected); + + line = [0, 0, 0, 0, 0]; + hints = [2]; + expected = [0, 0, 0, 0, 0]; + actual = pushSolver.solve(line, hints); + expect(actual).toEqual(expected); + }); + + it('works with empty line and multiple hints', () => { + let line = [0, 0, 0, 0, 0, 0]; + let hints = [2, 2]; + let expected = [0, 1, 0, 0, 1, 0]; + let actual = pushSolver.solve(line, hints); + expect(actual).toEqual(expected); + + line = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + hints = [4, 1, 1]; + expected = [0, 0, 1, 1, 0, 0, 0, 0, 0, 0]; + actual = pushSolver.solve(line, hints); + expect(actual).toEqual(expected); + + line = [0, 0, 0, 0, 0, 0]; + hints = [2, 1]; + expected = [0, 0, 0, 0, 0, 0]; + actual = pushSolver.solve(line, hints); + expect(actual).toEqual(expected); + }); + + it('works with partially filled line and one hint', () => { + let line = [0, 1, 0, 0, 0, 0]; + let hints = [3]; + let expected = [0, 1, 1, 0, -1, -1]; + let actual = pushSolver.solve(line, hints); + expect(actual).toEqual(expected); + + line = [1, 0, 0, 0, 0]; + hints = [3]; + expected = [1, 1, 1, -1, -1]; + actual = pushSolver.solve(line, hints); + expect(actual).toEqual(expected); + + line = [0, 0, 0, 0, 1, 0, 0, 0, 0, 0]; + hints = [4]; + expected = [-1, 0, 0, 0, 1, 0, 0, 0, -1, -1]; + actual = pushSolver.solve(line, hints); + expect(actual).toEqual(expected); + + line = [0, 0, 0, 0, 1, 0, 1, 0, 0, 0]; + hints = [4]; + expected = [-1, -1, -1, 0, 1, 1, 1, 0, -1, -1]; + actual = pushSolver.solve(line, hints); + expect(actual).toEqual(expected); + }); + + it('works with partially filled line and multiple hints', () => { + let line = [0, 1, 0, 0, 0, 0, 1, 0]; + let hints = [3, 3]; + let expected = [0, 1, 1, 0, 0, 1, 1, 0]; + let actual = pushSolver.solve(line, hints); + expect(actual).toEqual(expected); + + line = [0, 1, 0, 0, 0, 1, 0]; + hints = [3, 3]; + expected = [1, 1, 1, -1, 1, 1, 1]; + actual = pushSolver.solve(line, hints); + expect(actual).toEqual(expected); + + line = [1, 0, 0, 0, 0, 1]; + hints = [3, 2]; + expected = [1, 1, 1, -1, 1, 1]; + actual = pushSolver.solve(line, hints); + expect(actual).toEqual(expected); + + line = [0, 0, 0, 0, 1, 0, 0, 0, 0, 0]; + hints = [4, 1]; + expected = [-1, 0, 0, 0, 1, 0, 0, 0, 0, 0]; + actual = pushSolver.solve(line, hints); + expect(actual).toEqual(expected); + + line = [0, 0, 0, 0, 1, 0, 1, 0, 0, 0]; + hints = [4, 2]; + expected = [-1, 0, 0, 1, 1, 0, 1, 0, 0, 0]; + actual = pushSolver.solve(line, hints); + expect(actual).toEqual(expected); + }); + }); + + describe('with gaps', () => { + it('works with one gap and some occupation and one hint', () => { + let line = [0, 0, -1, 0, 1]; + let hints = [1]; + let expected = [-1, -1, -1, -1, 1]; + let actual = pushSolver.solve(line, hints); + expect(actual).toEqual(expected); + }); + + it('works with multiple gaps and some occupation and multiple hints', () => { + let line = [0, 0, 0, -1, 1, 0, 0, -1, 0, 0, 0]; + let hints = [3, 1, 3]; + let expected = [1, 1, 1, -1, 1, -1, -1, -1, 1, 1, 1]; + let actual = pushSolver.solve(line, hints); + expect(actual).toEqual(expected); + }); + + it('works with multiple gaps and some occupation and multiple hints 2', () => { + let line = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, -1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0]; + let hints = [2, 3, 7]; + let expected = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, -1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0]; + let actual = pushSolver.solve(line, hints); + expect(actual).toEqual(expected); + }); + it('works with multiple gaps and some occupation and multiple hints 3', () => { + let line = [-1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, -1, -1, 1, 1, -1, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, 1, -1]; + let hints = [14, 1, 2, 2, 1, 1]; + let expected = [-1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 0, 0, -1, -1, 1, 1, -1, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, 1, -1]; + let actual = pushSolver.solve(line, hints); + expect(actual).toEqual(expected); + }); + }); + }); +}); diff --git a/test/runTestsOnJSON.js b/test/runTestsOnJSON.js new file mode 100644 index 0000000..fd17bb8 --- /dev/null +++ b/test/runTestsOnJSON.js @@ -0,0 +1,44 @@ +const fs = require('fs'); +const expect = require('expect'); + +const asciify = line => line.join('').replace(/0/g, '░').replace(/-1/g, 'x').replace(/1/g, '█'); + +const formatTitle = (title, hints, line) => { + let result = [title]; + result.push(`[${hints.join('|')}]`); + result.push(asciify(line)); + return result.join(' '); +}; + +const jsonRunner = (solve, filename) => { + + const runTests = o => { + if (o.describe) { + describe(o.describe[0], () => { + runTests(o.describe[1]); + }); + } + if (o.it) { + let lastTitle; + o.it.forEach(([title, hints, line, expected]) => { + if (!expected) { + expected = line; + line = hints; + hints = title; + title = null; + } + lastTitle = title || lastTitle; + it(formatTitle(lastTitle, hints, line), () => { + let actual = solve(line, hints); + expect(actual).toEqual(expected); + }); + }); + } + }; + + let assets = fs.readFileSync(filename); + assets = JSON.parse(assets); + assets.forEach(runTests); +}; + +module.exports = jsonRunner; diff --git a/test/util.test.js b/test/util.test.js new file mode 100644 index 0000000..daf37ea --- /dev/null +++ b/test/util.test.js @@ -0,0 +1,85 @@ +const util = require('../src/util'); +const expect = require('expect'); + +describe('trimLine', () => { + describe('trimming', () => { + it('does nothing if not needed', () => { + let fixture = [0, 0, 0]; + let expected = [[0, 0, 0], [0], {left: [], right: []}]; + let actual = util.trimLine(fixture, [0]); + expect(actual).toEqual(expected); + }); + it('trims on the left', () => { + let fixture = [-1, 1, -1, 1, 0]; + let expected = [[1, 0], [1], {"left": [-1, 1, -1], "right": []}]; + let actual = util.trimLine(fixture, [1, 1]); + expect(actual).toEqual(expected); + }); + it('trims on the right', () => { + let fixture = [0, 0, -1, -1]; + let expected = [[0, 0], [1], {"left": [], "right": [-1, -1]}]; + let actual = util.trimLine(fixture, [1]); + expect(actual).toEqual(expected); + }); + it('handles 1s on the left rim', () => { + let fixture = [-1, 1, 1, 0, 0, -1, -1]; + let expected = [[1, 0, 0], [1, 1], {"left": [-1, 1], "right": [-1, -1]}]; + let actual = util.trimLine(fixture, [2, 1]); + expect(actual).toEqual(expected); + }); + it('handles 1s on the right rim', () => { + let fixture = [-1, -1, 0, 0, 1, 1, 1, -1, -1]; + let expected = [[0, 0, 1], [1, 1], {"left": [-1, -1], "right": [1, 1, -1, -1]}]; + let actual = util.trimLine(fixture, [1, 3]); + expect(actual).toEqual(expected); + }); + it('trims on both ends', () => { + let fixture = [1, 1, -1, -1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, -1, -1, -1]; + let expected = [[1, 0, 0, 1, 0, 0, 0, 1], [2, 2, 3], {"left": [1, 1, - 1, -1, 1, 1], "right": [1, 1, -1, -1, -1]}]; + let actual = util.trimLine(fixture, [2, 4, 2, 5]); + expect(actual).toEqual(expected); + }); + it('handles complicated cases', () => { + let fixture = [-1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, 0, -1, 0, -1, 1, 1, -1, 0, 0, 0, 0, 0, 0, -1, -1, -1, 1, -1, -1]; + let hints = [ 16, 2, 2, 1, 2, 1 ]; + let expected = [[0, -1, 0, -1, 1, 1, -1, 0, 0, 0, 0, 0, 0], [2, 2, 1, 2], {"left": [-1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1], "right": [-1, -1, -1, 1, -1, -1]}]; + let actual = util.trimLine(fixture, hints); + expect(actual).toEqual(expected); + }); + + it('handles complicated cases 2', () => { + let fixture = [-1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, -1, -1, 1, 1, -1, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, 1, -1]; + let hints = [14, 1, 2, 2, 1, 1]; + let expected = [[1, 0, 0, 0, -1, -1, 1, 1, -1, 0, 0, 0, 0, 0, 0], [1, 1, 2, 2, 1], { + "left": [-1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + "right": [-1, -1, -1, -1, 1, -1] + }]; + let actual = util.trimLine(fixture, hints); + expect(actual).toEqual(expected); + }); + it('handles complicated cases 3', () => { + let fixture = [-1, 1, -1, -1, 1, -1, -1, -1, -1, -1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1]; + let hints = [1, 1, 2, 10, 4, 9]; + let expected = [[1, 0, 1], [1, 1], { + "left": [-1, 1, -1, -1, 1, -1, -1, -1, -1, -1, 1], + "right": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1] + }]; + let actual = util.trimLine(fixture, hints); + expect(actual).toEqual(expected); + }); + }); + describe('restoring', () => { + it('restores trimmed lines', () => { + let fixtures = [ + [0, 0, 0], + [-1, 1, 0], + [1, 0, -1, -1], + [-1, -1, 0, 0, 1, -1, -1, -1] + ]; + fixtures.forEach(line => { + let [trimmed, hints, info] = util.trimLine(line, [1]); + expect(util.restoreLine(trimmed, info)).toEqual(line); + }); + }); + }); +});