diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000..43056e9
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,84 @@
+module.exports = {
+ env: {
+ browser: true,
+ es2021: true,
+ es6: true,
+ },
+ extends: [
+ "plugin:react/recommended",
+ "prettier",
+ "plugin:prettier/recommended",
+ "plugin:react-hooks/recommended",
+ "plugin:import/recommended",
+ ],
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ ecmaVersion: "latest",
+ sourceType: "module",
+ },
+ plugins: ["react", "prettier", "react-hooks", "import"],
+ rules: {
+ "react/prop-types": "off",
+ "react/no-unescaped-entities": "warn",
+ "react-hooks/rules-of-hooks": "error",
+ "react-hooks/exhaustive-deps": "warn",
+ "no-unused-vars": "warn",
+ "func-names": "warn",
+ "object-shorthand": "off",
+ "class-methods-use-this": "off",
+ "consistent-return": "off",
+ "import/no-dynamic-require": "off",
+ "global-require": "off",
+ "jsx-a11y/click-events-have-key-events": "off",
+ "jsx-a11y/mouse-events-have-key-events": "off",
+ "max-classes-per-file": "off",
+ "no-nested-ternary": "off",
+ "no-new": "off",
+ "no-plusplus": "off",
+ "no-process-exit": "off",
+ "no-restricted-globals": "off",
+ "node/exports-style": "off",
+ "node/no-unpublished-import": "off",
+ "node/no-unsupported-features/es-syntax": "off",
+ "no-console": ["warn", {allow: ["warn", "debug", "error", "info"]}],
+ "no-unused-expressions": [
+ "error",
+ {
+ allowShortCircuit: true,
+ allowTernary: true,
+ },
+ ],
+ "react/no-array-index-key": "warn",
+ "no-shadow": "off",
+ "linebreak-style": ["error", "unix"],
+ "prettier/prettier": [
+ "error",
+ {
+ singleQuote: false,
+ parser: "flow",
+ bracketSpacing: false,
+ jsxBracketSameLine: false,
+ printWidth: 80,
+ semi: true,
+ tabWidth: 2,
+ trailingComma: "es5",
+ useTabs: false,
+ proseWrap: "always",
+ jsxSingleQuote: true,
+ importOrder: ["^[./]"],
+ importOrderSeparation: true,
+ importOrderSortSpecifiers: true,
+ },
+ ],
+ },
+ settings: {
+ "import/resolver": {
+ node: {
+ extensions: [".js", ".json"],
+ },
+ },
+ "import/ignore": ["node_modules", ".json$", ".(scss|css)$"],
+ },
+};
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..97ce882
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,18 @@
+{
+ "files.autoSave": "onFocusChange",
+ "editor.defaultFormatter": "dbaeumer.vscode-eslint",
+ "editor.formatOnPaste": true,
+ "eslint.validate": [
+ "javascript",
+ "javascriptreact",
+ "typescript",
+ "typescriptreact"
+ ],
+ "eslint.format.enable": true,
+ "[javascript]": {
+ "editor.defaultFormatter": "dbaeumer.vscode-eslint",
+ },
+ "[javascriptreact]": {
+ "editor.defaultFormatter": "dbaeumer.vscode-eslint",
+ },
+}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 00274de..0de69a9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3070,6 +3070,11 @@
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="
},
+ "array-uniq": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
+ "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY="
+ },
"array.prototype.flat": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz",
@@ -3100,6 +3105,11 @@
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
"integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0="
},
+ "astral-regex": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
+ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ=="
+ },
"async": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
@@ -3664,6 +3674,48 @@
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
"integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="
},
+ "cli-cursor": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
+ "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
+ "requires": {
+ "restore-cursor": "^3.1.0"
+ }
+ },
+ "cli-truncate": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz",
+ "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==",
+ "requires": {
+ "slice-ansi": "^5.0.0",
+ "string-width": "^5.0.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA=="
+ },
+ "string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "requires": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ }
+ },
+ "strip-ansi": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz",
+ "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==",
+ "requires": {
+ "ansi-regex": "^6.0.1"
+ }
+ }
+ }
+ },
"cliui": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
@@ -4484,6 +4536,11 @@
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
"integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg=="
},
+ "eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
+ },
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -4502,6 +4559,11 @@
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.92.tgz",
"integrity": "sha512-YAVbvQIcDE/IJ/vzDMjD484/hsRbFPW2qXJPaYTfOhtligmfYEYOep+5QojpaEU9kq6bMvNeC2aG7arYvTHYsA=="
},
+ "email-addresses": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz",
+ "integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg=="
+ },
"emittery": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz",
@@ -4777,6 +4839,43 @@
}
}
},
+ "eslint-config-airbnb": {
+ "version": "19.0.4",
+ "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz",
+ "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==",
+ "dev": true,
+ "requires": {
+ "eslint-config-airbnb-base": "^15.0.0",
+ "object.assign": "^4.1.2",
+ "object.entries": "^1.1.5"
+ }
+ },
+ "eslint-config-airbnb-base": {
+ "version": "15.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz",
+ "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==",
+ "dev": true,
+ "requires": {
+ "confusing-browser-globals": "^1.0.10",
+ "object.assign": "^4.1.2",
+ "object.entries": "^1.1.5",
+ "semver": "^6.3.0"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ }
+ }
+ },
+ "eslint-config-prettier": {
+ "version": "8.5.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz",
+ "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==",
+ "dev": true
+ },
"eslint-config-react-app": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.0.tgz",
@@ -4879,6 +4978,16 @@
}
}
},
+ "eslint-plugin-eslint-plugin": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-eslint-plugin/-/eslint-plugin-eslint-plugin-4.1.0.tgz",
+ "integrity": "sha512-QJVw+WYXJuG2469gx5G929bz7crfxySDlK1i569FkuT6dpeHDeP7MmDrKaswCx17snG25LRFD6wmVX+AO5x7Qg==",
+ "dev": true,
+ "requires": {
+ "eslint-utils": "^3.0.0",
+ "estraverse": "^5.2.0"
+ }
+ },
"eslint-plugin-flowtype": {
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz",
@@ -4958,6 +5067,15 @@
"minimatch": "^3.0.4"
}
},
+ "eslint-plugin-prettier": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz",
+ "integrity": "sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==",
+ "dev": true,
+ "requires": {
+ "prettier-linter-helpers": "^1.0.0"
+ }
+ },
"eslint-plugin-react": {
"version": "7.29.4",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.29.4.tgz",
@@ -5217,6 +5335,12 @@
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
+ "fast-diff": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
+ "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
+ "dev": true
+ },
"fast-glob": {
"version": "3.2.11",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
@@ -5298,6 +5422,21 @@
"minimatch": "^3.0.4"
}
},
+ "filename-reserved-regex": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz",
+ "integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik="
+ },
+ "filenamify": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz",
+ "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==",
+ "requires": {
+ "filename-reserved-regex": "^2.0.0",
+ "strip-outer": "^1.0.1",
+ "trim-repeated": "^1.0.0"
+ }
+ },
"filesize": {
"version": "8.0.7",
"resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz",
@@ -5586,6 +5725,70 @@
"get-intrinsic": "^1.1.1"
}
},
+ "gh-pages": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-3.2.3.tgz",
+ "integrity": "sha512-jA1PbapQ1jqzacECfjUaO9gV8uBgU6XNMV0oXLtfCX3haGLe5Atq8BxlrADhbD6/UdG9j6tZLWAkAybndOXTJg==",
+ "requires": {
+ "async": "^2.6.1",
+ "commander": "^2.18.0",
+ "email-addresses": "^3.0.1",
+ "filenamify": "^4.3.0",
+ "find-cache-dir": "^3.3.1",
+ "fs-extra": "^8.1.0",
+ "globby": "^6.1.0"
+ },
+ "dependencies": {
+ "array-union": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
+ "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
+ "requires": {
+ "array-uniq": "^1.0.1"
+ }
+ },
+ "commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
+ },
+ "fs-extra": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
+ "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+ "requires": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ }
+ },
+ "globby": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
+ "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
+ "requires": {
+ "array-union": "^1.0.1",
+ "glob": "^7.0.3",
+ "object-assign": "^4.0.1",
+ "pify": "^2.0.0",
+ "pinkie-promise": "^2.0.0"
+ }
+ },
+ "jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
+ "requires": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
+ }
+ }
+ },
"glob": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
@@ -5906,6 +6109,11 @@
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
"integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="
},
+ "husky": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/husky/-/husky-7.0.4.tgz",
+ "integrity": "sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ=="
+ },
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -5942,6 +6150,11 @@
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.12.tgz",
"integrity": "sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA=="
},
+ "immutable": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz",
+ "integrity": "sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw=="
+ },
"import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -7731,6 +7944,101 @@
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
},
+ "lint-staged": {
+ "version": "12.3.7",
+ "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.3.7.tgz",
+ "integrity": "sha512-/S4D726e2GIsDVWIk1XGvheCaDm1SJRQp8efamZFWJxQMVEbOwSysp7xb49Oo73KYCdy97mIWinhlxcoNqIfIQ==",
+ "requires": {
+ "cli-truncate": "^3.1.0",
+ "colorette": "^2.0.16",
+ "commander": "^8.3.0",
+ "debug": "^4.3.3",
+ "execa": "^5.1.1",
+ "lilconfig": "2.0.4",
+ "listr2": "^4.0.1",
+ "micromatch": "^4.0.4",
+ "normalize-path": "^3.0.0",
+ "object-inspect": "^1.12.0",
+ "pidtree": "^0.5.0",
+ "string-argv": "^0.3.1",
+ "supports-color": "^9.2.1",
+ "yaml": "^1.10.2"
+ },
+ "dependencies": {
+ "commander": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
+ "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="
+ },
+ "lilconfig": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz",
+ "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA=="
+ },
+ "supports-color": {
+ "version": "9.2.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.2.1.tgz",
+ "integrity": "sha512-Obv7ycoCTG51N7y175StI9BlAXrmgZrFhZOb0/PyjHBher/NmsdBgbbQ1Inhq+gIhz6+7Gb+jWF2Vqi7Mf1xnQ=="
+ }
+ }
+ },
+ "listr2": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/listr2/-/listr2-4.0.5.tgz",
+ "integrity": "sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA==",
+ "requires": {
+ "cli-truncate": "^2.1.0",
+ "colorette": "^2.0.16",
+ "log-update": "^4.0.0",
+ "p-map": "^4.0.0",
+ "rfdc": "^1.3.0",
+ "rxjs": "^7.5.5",
+ "through": "^2.3.8",
+ "wrap-ansi": "^7.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "cli-truncate": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz",
+ "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==",
+ "requires": {
+ "slice-ansi": "^3.0.0",
+ "string-width": "^4.2.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ },
+ "slice-ansi": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz",
+ "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==",
+ "requires": {
+ "ansi-styles": "^4.0.0",
+ "astral-regex": "^2.0.0",
+ "is-fullwidth-code-point": "^3.0.0"
+ }
+ }
+ }
+ },
"loader-runner": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz",
@@ -7784,6 +8092,60 @@
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
"integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M="
},
+ "log-update": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz",
+ "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==",
+ "requires": {
+ "ansi-escapes": "^4.3.0",
+ "cli-cursor": "^3.1.0",
+ "slice-ansi": "^4.0.0",
+ "wrap-ansi": "^6.2.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ },
+ "slice-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
+ "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==",
+ "requires": {
+ "ansi-styles": "^4.0.0",
+ "astral-regex": "^2.0.0",
+ "is-fullwidth-code-point": "^3.0.0"
+ }
+ },
+ "wrap-ansi": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+ "requires": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ }
+ }
+ }
+ },
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -8393,6 +8755,29 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
},
+ "pidtree": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.5.0.tgz",
+ "integrity": "sha512-9nxspIM7OpZuhBxPg73Zvyq7j1QMPMPsGKTqRc2XOaFQauDvoNz9fM1Wdkjmeo7l9GXOZiRs97sPkuayl39wjA=="
+ },
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw="
+ },
+ "pinkie": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+ "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA="
+ },
+ "pinkie-promise": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+ "requires": {
+ "pinkie": "^2.0.0"
+ }
+ },
"pirates": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz",
@@ -9173,6 +9558,21 @@
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="
},
+ "prettier": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.0.tgz",
+ "integrity": "sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A==",
+ "dev": true
+ },
+ "prettier-linter-helpers": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
+ "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
+ "dev": true,
+ "requires": {
+ "fast-diff": "^1.1.2"
+ }
+ },
"pretty-bytes": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
@@ -9789,6 +10189,15 @@
"resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz",
"integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ=="
},
+ "restore-cursor": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
+ "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
+ "requires": {
+ "onetime": "^5.1.0",
+ "signal-exit": "^3.0.2"
+ }
+ },
"retry": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
@@ -9799,6 +10208,11 @@
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="
},
+ "rfdc": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz",
+ "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA=="
+ },
"rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@@ -9867,6 +10281,21 @@
"queue-microtask": "^1.2.2"
}
},
+ "rxjs": {
+ "version": "7.5.5",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz",
+ "integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==",
+ "requires": {
+ "tslib": "^2.1.0"
+ },
+ "dependencies": {
+ "tslib": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
+ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
+ }
+ }
+ },
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
@@ -9882,6 +10311,16 @@
"resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-13.0.0.tgz",
"integrity": "sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA=="
},
+ "sass": {
+ "version": "1.49.9",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.9.tgz",
+ "integrity": "sha512-YlYWkkHP9fbwaFRZQRXgDi3mXZShslVmmo+FVK3kHLUELHHEYrCmL1x6IUjC7wLS6VuJSAFXRQS/DxdsC4xL1A==",
+ "requires": {
+ "chokidar": ">=3.0.0 <4.0.0",
+ "immutable": "^4.0.0",
+ "source-map-js": ">=0.6.2 <2.0.0"
+ }
+ },
"sass-loader": {
"version": "12.6.0",
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz",
@@ -10103,6 +10542,27 @@
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="
},
+ "slice-ansi": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz",
+ "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==",
+ "requires": {
+ "ansi-styles": "^6.0.0",
+ "is-fullwidth-code-point": "^4.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.1.0.tgz",
+ "integrity": "sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ=="
+ },
+ "is-fullwidth-code-point": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz",
+ "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="
+ }
+ }
+ },
"sockjs": {
"version": "0.3.24",
"resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz",
@@ -10238,6 +10698,11 @@
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
},
+ "string-argv": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz",
+ "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg=="
+ },
"string-length": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
@@ -10356,6 +10821,14 @@
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="
},
+ "strip-outer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz",
+ "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==",
+ "requires": {
+ "escape-string-regexp": "^1.0.2"
+ }
+ },
"style-loader": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz",
@@ -10609,6 +11082,11 @@
"resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz",
"integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w=="
},
+ "through": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
+ },
"thunky": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
@@ -10667,6 +11145,14 @@
"punycode": "^2.1.1"
}
},
+ "trim-repeated": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz",
+ "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=",
+ "requires": {
+ "escape-string-regexp": "^1.0.2"
+ }
+ },
"tryer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz",
diff --git a/package.json b/package.json
index 8f99129..3a7a535 100644
--- a/package.json
+++ b/package.json
@@ -6,12 +6,23 @@
"@testing-library/jest-dom": "^5.16.3",
"@testing-library/react": "^12.1.4",
"@testing-library/user-event": "^13.5.0",
+ "gh-pages": "^3.2.3",
+ "husky": "^7.0.4",
+ "lint-staged": "^12.3.7",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "5.0.0",
+ "sass": "^1.49.9",
"web-vitals": "^2.1.4"
},
+ "lint-staged": {
+ "src/**/*.{js,jsx,ts,tsx,json,css,scss,md}": [
+ "prettier --write"
+ ]
+ },
"scripts": {
+ "predeploy": "npm run build",
+ "deploy": "gh-pages -d build",
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
@@ -34,5 +45,23 @@
"last 1 firefox version",
"last 1 safari version"
]
+ },
+ "husky": {
+ "hooks": {
+ "pre-commit": "lint-staged"
+ }
+ },
+ "homepage": "https://ameygohil.github.io/slither-link",
+ "devDependencies": {
+ "eslint": "^8.11.0",
+ "eslint-config-airbnb": "^19.0.4",
+ "eslint-config-prettier": "^8.5.0",
+ "eslint-plugin-eslint-plugin": "^4.1.0",
+ "eslint-plugin-import": "^2.25.4",
+ "eslint-plugin-jsx-a11y": "^6.5.1",
+ "eslint-plugin-prettier": "^4.0.0",
+ "eslint-plugin-react": "^7.29.4",
+ "eslint-plugin-react-hooks": "^4.3.0",
+ "prettier": "^2.6.0"
}
}
diff --git a/public/favicon.ico b/public/favicon.ico
index a11777c..643ac30 100644
Binary files a/public/favicon.ico and b/public/favicon.ico differ
diff --git a/public/index.html b/public/index.html
index aa069f2..05a8ed4 100644
--- a/public/index.html
+++ b/public/index.html
@@ -7,7 +7,7 @@
-
React App
+ SlitherLink
You need to enable JavaScript to run this app.
diff --git a/public/logo192.png b/public/logo192.png
index fc44b0a..c175075 100644
Binary files a/public/logo192.png and b/public/logo192.png differ
diff --git a/public/logo512.png b/public/logo512.png
index a4e47a6..3132b31 100644
Binary files a/public/logo512.png and b/public/logo512.png differ
diff --git a/public/manifest.json b/public/manifest.json
index 080d6c7..b063c6e 100644
--- a/public/manifest.json
+++ b/public/manifest.json
@@ -1,6 +1,6 @@
{
- "short_name": "React App",
- "name": "Create React App Sample",
+ "short_name": "SlitherLink",
+ "name": "SlitherLink React App",
"icons": [
{
"src": "favicon.ico",
diff --git a/src/App.css b/src/App.css
deleted file mode 100644
index 74b5e05..0000000
--- a/src/App.css
+++ /dev/null
@@ -1,38 +0,0 @@
-.App {
- text-align: center;
-}
-
-.App-logo {
- height: 40vmin;
- pointer-events: none;
-}
-
-@media (prefers-reduced-motion: no-preference) {
- .App-logo {
- animation: App-logo-spin infinite 20s linear;
- }
-}
-
-.App-header {
- background-color: #282c34;
- min-height: 100vh;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- font-size: calc(10px + 2vmin);
- color: white;
-}
-
-.App-link {
- color: #61dafb;
-}
-
-@keyframes App-logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
diff --git a/src/App.js b/src/App.js
index 3784575..a766fa1 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,25 +1,35 @@
-import logo from './logo.svg';
-import './App.css';
+import * as React from "react";
-function App() {
- return (
-
+import styles from "./App.styles.module.scss";
+import CheckSolution from "./components/CheckSolution";
+import Grid from "./components/Grid";
+import Info from "./components/Info";
+import {examples} from "./examples";
+
+const App = () => {
+ const [matrix, setMatirx] = React.useState([]);
+ const [{dim, numbers, solution}] = React.useState(
+ examples[Math.floor(Math.random() * examples.length)]
);
-}
+ const [check, setCheck] = React.useState("none");
+
+ return (
+
+
+ Slither
+ Link
+
+
+
+
+
+ );
+};
export default App;
diff --git a/src/App.styles.module.scss b/src/App.styles.module.scss
new file mode 100644
index 0000000..802b7e5
--- /dev/null
+++ b/src/App.styles.module.scss
@@ -0,0 +1,32 @@
+main {
+ font-size: 24px;
+ font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+ background-color: #23262f;
+ height: 100%;
+ width: 100%;
+ color: #fafafa;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 20px;
+ flex-direction: column;
+ box-sizing: border-box;
+ padding: 0 22px;
+}
+
+.heading {
+ font-size: 25px;
+ text-align: center;
+
+ span {
+ color: #7986cb;
+
+ &:nth-of-type(even) {
+ color: #8c9eff;
+ }
+
+ &:nth-of-type(odd) {
+ color: #64b5f6;
+ }
+ }
+}
diff --git a/src/App.test.js b/src/App.test.js
index 1f03afe..ed977f8 100644
--- a/src/App.test.js
+++ b/src/App.test.js
@@ -1,7 +1,9 @@
-import { render, screen } from '@testing-library/react';
-import App from './App';
+import * as React from "react";
-test('renders learn react link', () => {
+import {render, screen} from "@testing-library/react";
+import App from "./App";
+
+test("renders learn react link", () => {
render( );
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
diff --git a/src/components/AddPuzzleScreen/index.js b/src/components/AddPuzzleScreen/index.js
new file mode 100644
index 0000000..bea4d52
--- /dev/null
+++ b/src/components/AddPuzzleScreen/index.js
@@ -0,0 +1,116 @@
+import * as React from "react";
+import {getNumbers, multiStyles} from "../../utils";
+import Grid from "../Grid";
+import N from "../Grid/components/N";
+
+import styles from "./styles.module.scss";
+
+const AddPuzzleScreen = ({open, onClose}) => {
+ const [dim, setDim] = React.useState([3, 3]);
+ const [gridMat, setGridMat] = React.useState([]);
+
+ const [gridNumbers, setGridNumbers] = React.useState([]);
+ React.useEffect(() => {
+ setGridNumbers(getNumbers(gridMat));
+ }, [JSON.stringify(gridMat)]);
+
+ const [gridReset, setGridReset] = React.useState(false);
+ React.useEffect(() => {
+ if (gridReset) {
+ setGridReset(false);
+ }
+ }, [gridReset]);
+
+ const [submitState, setSubmitState] = React.useState("none");
+ const [submitText, setSubmitText] = React.useState("Submit");
+ const onSubmit = () => {
+ setSubmitState("passed");
+ setSubmitText("Thanks for submitting! 😀");
+ };
+ React.useEffect(() => {
+ let timeout;
+ if (submitState === "passed") {
+ timeout = setTimeout(() => {
+ setSubmitState("none");
+ setSubmitText("Submit");
+ }, 5000);
+ }
+
+ return () => !isNaN(timeout) && clearTimeout(timeout);
+ }, [submitState]);
+
+ const shouldShowGrid =
+ !isNaN(dim[0]) && dim[0] > 0 && !isNaN(dim[1]) && dim[1] > 0 && !gridReset;
+
+ return (
+ <>
+ {open && (
+
+
+
+
+ Set dimensions
+
+
+ {
+ setGridReset(true);
+ setDim([n, dim[1]]);
+ }}
+ editorMode
+ className={styles.input}
+ />
+ X
+ {
+ setGridReset(true);
+ setDim([dim[0], n]);
+ }}
+ editorMode
+ className={styles.input}
+ />
+
+
+
+
+ Set matrix
+
+
+ {shouldShowGrid ? (
+ {
+ setGridNumbers([]);
+ setGridReset(true);
+ }}
+ editorMode
+ />
+ ) : (
+ "Please set valid dimensions above"
+ )}
+
+
+
+
+
+ )}
+ >
+ );
+};
+
+export default AddPuzzleScreen;
diff --git a/src/components/AddPuzzleScreen/styles.module.scss b/src/components/AddPuzzleScreen/styles.module.scss
new file mode 100644
index 0000000..d04b397
--- /dev/null
+++ b/src/components/AddPuzzleScreen/styles.module.scss
@@ -0,0 +1,159 @@
+* {
+ box-sizing: border-box;
+}
+
+.screen {
+ position: fixed;
+ height: 100vh;
+ width: 100vw;
+ background-color: #212121;
+ z-index: 3;
+ font-family: monospace;
+ display: flex;
+ padding: 50px 40px;
+ align-items: flex-start;
+ justify-content: flex-start;
+ overflow-y: auto;
+ flex-direction: column;
+ gap: 50px;
+
+ .cross {
+ $size: 20px;
+ height: $size;
+ width: $size;
+ position: fixed;
+ top: 40px;
+ right: 40px;
+ background-color: inherit;
+ padding: 3 * $size / 4;
+ z-index: 3;
+ cursor: pointer;
+
+ &:after, &:before {
+ content: "";
+ height: $size / 8;
+ width: $size;
+ background-color: #b0bec5;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ border-radius: $size / 8;
+ }
+
+ &:after {
+ transform: translate(-50%, -50%) rotate(45deg);
+ }
+
+ &:before {
+ transform: translate(-50%, -50%) rotate(-45deg);
+ }
+ }
+
+ section {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: flex-start;
+ gap: 20px;
+ font-size: 17px;
+ color: #e0e0e0;
+ width: 100%;
+
+ & > span {
+ padding-left: 20px;
+ }
+
+ a {
+ color: #00b0ff;
+ text-decoration: none;
+ }
+ }
+
+ .title {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ margin-bottom: 10px;
+ font-size: 26px;
+ text-transform: uppercase;
+ font-weight: 600;
+ color: #f5f5f5;
+ }
+
+ .content {
+ line-height: inherit;
+ font-size: inherit;
+ padding-left: 20px;
+ color: inherit;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ width: 100%;
+
+ .matrixGrid {
+ display: flex;
+ align-self: center;
+ justify-self: center;
+ gap: 20px;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ }
+
+ .submitButton {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: #263238;
+ height: auto;
+ width: auto;
+ padding: 10px 15px;
+ font-size: 20px;
+ border: 2px solid #455a64;
+ margin-top: 20px;
+ color: #fafafa;
+
+ align-self: center;
+ justify-self: center;
+ border-radius: 10px;
+ cursor: pointer;
+ transition: all 0.2s;
+
+ &:hover {
+ border-color: #78909c;
+ transform: scale(1.01);
+ color: white;
+ }
+
+ &.failed {
+ color: #ff5722;
+ border-color: #ff5722;
+ }
+
+ &.passed {
+ color: #00e676;
+ border-color: #00e676;
+ }
+ }
+ }
+
+ .dimInputWrapper {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+
+ .input {
+ background-color: #282c34;
+ border-radius: 6px;
+ display: inline;
+
+ input {
+ border-radius: 6px;
+ font-family: monospace;
+ }
+ }
+ }
+}
diff --git a/src/components/CheckSolution/index.js b/src/components/CheckSolution/index.js
new file mode 100644
index 0000000..70baedf
--- /dev/null
+++ b/src/components/CheckSolution/index.js
@@ -0,0 +1,44 @@
+import * as React from "react";
+import {getActiveEdges, multiStyles} from "../../utils";
+
+import styles from "./styles.module.scss";
+
+const CheckSolution = ({matrix, solution, setCheck}) => {
+ const [text, setText] = React.useState("Check Solution");
+ const [state, setState] = React.useState("none");
+
+ React.useEffect(() => {
+ let timeout;
+ if (state !== "none") {
+ timeout = setTimeout(() => {
+ setState("none");
+ setCheck("none");
+ setText("Check Solution");
+ }, 4000);
+ }
+
+ return () => !isNaN(timeout) && clearTimeout(timeout);
+ }, [state]);
+ return (
+ {
+ let check =
+ JSON.stringify(getActiveEdges(matrix)) === JSON.stringify(solution);
+ if (check) {
+ setText("That is correct! 😀");
+ setState("passed");
+ setCheck("passed");
+ } else {
+ setText("Wrong Solution 😔");
+ setState("failed");
+ setCheck("failed");
+ }
+ }}
+ >
+ {text}
+
+ );
+};
+
+export default CheckSolution;
diff --git a/src/components/CheckSolution/styles.module.scss b/src/components/CheckSolution/styles.module.scss
new file mode 100644
index 0000000..c2a9cb5
--- /dev/null
+++ b/src/components/CheckSolution/styles.module.scss
@@ -0,0 +1,38 @@
+* {
+ box-sizing: border-box;
+}
+
+.button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: #263238;
+ height: 50px;
+ width: auto;
+ padding: 10px 15px;
+ font-size: 20px;
+ border: 2px solid #455a64;
+ margin-top: 20px;
+ color: #fafafa;
+
+ border-radius: 10px;
+ cursor: pointer;
+ transition: all 0.2s;
+
+
+ &:hover {
+ border-color: #78909c;
+ transform: scale(1.01);
+ color: white;
+ }
+
+ &.failed {
+ color: #ff5722;
+ border-color: #ff5722;
+ }
+
+ &.passed {
+ color: #00e676;
+ border-color: #00e676;
+ }
+}
diff --git a/src/components/Controls/index.js b/src/components/Controls/index.js
new file mode 100644
index 0000000..b679069
--- /dev/null
+++ b/src/components/Controls/index.js
@@ -0,0 +1,37 @@
+import * as React from "react";
+import AddPuzzleScreen from "../AddPuzzleScreen";
+import {Plus, Refresh, Reset} from "../Icons";
+
+import styles from "./styles.module.scss";
+
+const Controls = ({onRefresh, onReset, editorMode}) => {
+ const [showAddScreen, setShowAddScreen] = React.useState(false);
+ return (
+ <>
+
+ {!editorMode && (
+
+
+ Refresh
+
+ )}
+
+
+ Reset
+
+ {!editorMode && (
+
setShowAddScreen(true)}>
+
+
Add
+
+ )}
+
+ setShowAddScreen(false)}
+ />
+ >
+ );
+};
+
+export default Controls;
diff --git a/src/components/Controls/styles.module.scss b/src/components/Controls/styles.module.scss
new file mode 100644
index 0000000..4106fa1
--- /dev/null
+++ b/src/components/Controls/styles.module.scss
@@ -0,0 +1,48 @@
+* {
+ box-sizing: border-box;
+}
+
+.controls {
+ position: fixed;
+ bottom: 10px;
+ left: 10px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+
+ & > div {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ font-size: 12px;
+ color: #90a4ae;
+ gap: 2px;
+
+ & > svg {
+ background-color: #263238;
+ height: 40px;
+ width: 40px;
+ padding: 9px;
+ border: 2px solid #455a64;
+ font-family: Georgia, 'Times New Roman', Times, serif;
+ color: #fafafa;
+
+ border-radius: 50%;
+ cursor: pointer;
+ transition: all 0.2s;
+ font-style: italic;
+
+ &:hover {
+ border-color: #78909c;
+ font-size: 22px;
+ color: white;
+ }
+ }
+
+ &:hover {
+ transform: scale(1.1);
+ }
+ }
+}
diff --git a/src/components/Grid/components/Cross.js b/src/components/Grid/components/Cross.js
new file mode 100644
index 0000000..209c0eb
--- /dev/null
+++ b/src/components/Grid/components/Cross.js
@@ -0,0 +1,37 @@
+import * as React from "react";
+import {multiStyles} from "../../../utils";
+import styles from "../styles.module.scss";
+
+const Cross = ({
+ orientation = "h",
+ className,
+ onRightClick,
+ onMiddleClick,
+ ...props
+}) => {
+ return (
+ {
+ e.preventDefault();
+ onRightClick();
+ }}
+ onMouseDown={(e) => {
+ if (e.button === 1) {
+ onMiddleClick();
+ }
+ }}
+ >
+
+
+
+ );
+};
+
+export default Cross;
diff --git a/src/components/Grid/components/Edge.js b/src/components/Grid/components/Edge.js
new file mode 100644
index 0000000..0694b5a
--- /dev/null
+++ b/src/components/Grid/components/Edge.js
@@ -0,0 +1,9 @@
+import * as React from "react";
+import Cross from "./Cross";
+import Line from "./Line";
+
+const Edge = ({notAllowed, ...props}) => {
+ return notAllowed ? : ;
+};
+
+export default Edge;
diff --git a/src/components/Grid/components/Line.js b/src/components/Grid/components/Line.js
new file mode 100644
index 0000000..57dfebc
--- /dev/null
+++ b/src/components/Grid/components/Line.js
@@ -0,0 +1,36 @@
+import * as React from "react";
+import {multiStyles} from "../../../utils";
+import styles from "../styles.module.scss";
+
+const Line = ({
+ orientation = "h",
+ hovered,
+ active,
+ className,
+ onRightClick,
+ onMiddleClick,
+ ...props
+}) => {
+ return (
+ {
+ e.preventDefault();
+ onRightClick();
+ }}
+ onMouseDown={(e) => {
+ if (e.button === 1) {
+ onMiddleClick();
+ }
+ }}
+ />
+ );
+};
+
+export default Line;
diff --git a/src/components/Grid/components/LineRow.js b/src/components/Grid/components/LineRow.js
new file mode 100644
index 0000000..05e99a5
--- /dev/null
+++ b/src/components/Grid/components/LineRow.js
@@ -0,0 +1,58 @@
+import * as React from "react";
+import {EDGE_STATE} from "../../../constants";
+import {findNeighbor, isNodeActive} from "../../../utils";
+import Edge from "./Edge";
+import Node from "./Node";
+import Row from "./Row";
+
+const LineRow = ({
+ matX,
+ mat = [],
+ n = 1,
+ onLineClick,
+ onLineRightClick,
+ onLineMiddleClick,
+}) => {
+ return (
+
+ {mat.length &&
+ Array(n - 1)
+ .fill(0)
+ .map((_, i) => {
+ const edgeIdx = findNeighbor([matX, i], [matX, i + 1], mat);
+ const showEdge = edgeIdx !== -1;
+ return [
+ ,
+ onLineClick(matX, i, "horizontal")}
+ onRightClick={() => onLineRightClick(matX, i, "horizontal")}
+ onMiddleClick={() => onLineMiddleClick(matX, i, "horizontal")}
+ hovered={
+ showEdge &&
+ mat[matX][i].neigh[edgeIdx].state === EDGE_STATE.HOVERED
+ ? 1
+ : 0
+ }
+ key={`edge_${matX + i}`}
+ />,
+ ];
+ })
+ .concat([
+ ,
+ ])}
+
+ );
+};
+
+export default LineRow;
diff --git a/src/components/Grid/components/N.js b/src/components/Grid/components/N.js
new file mode 100644
index 0000000..cb48447
--- /dev/null
+++ b/src/components/Grid/components/N.js
@@ -0,0 +1,17 @@
+import * as React from "react";
+import {multiStyles} from "../../../utils";
+import styles from "../styles.module.scss";
+
+const N = ({className, value, setNum, editorMode, ...props}) => {
+ return (
+
+ setNum(parseInt(e.target.value))}
+ value={value >= 0 && !isNaN(value) ? value : undefined}
+ />
+
+ );
+};
+
+export default N;
diff --git a/src/components/Grid/components/Node.js b/src/components/Grid/components/Node.js
new file mode 100644
index 0000000..f16be0f
--- /dev/null
+++ b/src/components/Grid/components/Node.js
@@ -0,0 +1,14 @@
+import * as React from "react";
+import {multiStyles} from "../../../utils";
+import styles from "../styles.module.scss";
+
+const Node = ({className, active, ...props}) => {
+ return (
+
+ );
+};
+
+export default Node;
diff --git a/src/components/Grid/components/NumberRow.js b/src/components/Grid/components/NumberRow.js
new file mode 100644
index 0000000..60bbf2c
--- /dev/null
+++ b/src/components/Grid/components/NumberRow.js
@@ -0,0 +1,81 @@
+import * as React from "react";
+import {EDGE_STATE} from "../../../constants";
+import {findNeighbor} from "../../../utils";
+import Edge from "./Edge";
+import N from "./N";
+import Row from "./Row";
+
+const NumberRow = ({
+ matX,
+ mat = [],
+ n = 1,
+ onLineClick,
+ onLineRightClick,
+ onLineMiddleClick,
+ onNumberChange,
+ editorMode,
+}) => {
+ const endEdgeIdx =
+ mat.length && findNeighbor([matX, n - 1], [matX + 1, n - 1], mat);
+ const showEndEdge = endEdgeIdx !== -1;
+ return (
+
+ {mat.length &&
+ Array(n - 1)
+ .fill(0)
+ .map((_, i) => {
+ const edgeIdx = findNeighbor([matX, i], [matX + 1, i], mat);
+ const showEdge = edgeIdx !== -1;
+ return [
+ onLineClick(matX, i, "vertical")}
+ onRightClick={() => onLineRightClick(matX, i, "vertical")}
+ onMiddleClick={() => onLineMiddleClick(matX, i, "vertical")}
+ hovered={
+ showEdge &&
+ mat[matX][i].neigh[edgeIdx].state === EDGE_STATE.HOVERED
+ ? 1
+ : 0
+ }
+ key={`edge_${matX + i}`}
+ />,
+ onNumberChange(matX, i, n)}
+ editorMode={editorMode}
+ key={`number_${matX + i}`}
+ />,
+ ];
+ })
+ .concat([
+ onLineClick(matX, n - 1, "vertical")}
+ onRightClick={() => onLineRightClick(matX, n - 1, "vertical")}
+ onMiddleClick={() => onLineMiddleClick(matX, n - 1, "vertical")}
+ hovered={
+ showEndEdge &&
+ mat[matX][n - 1].neigh[endEdgeIdx].state === EDGE_STATE.HOVERED
+ ? 1
+ : 0
+ }
+ key={`edge_${matX + n - 1}`}
+ />,
+ ])}
+
+ );
+};
+
+export default NumberRow;
diff --git a/src/components/Grid/components/Row.js b/src/components/Grid/components/Row.js
new file mode 100644
index 0000000..e7381f9
--- /dev/null
+++ b/src/components/Grid/components/Row.js
@@ -0,0 +1,6 @@
+import * as React from "react";
+import styles from "../styles.module.scss";
+
+const Row = ({children}) =>
{children}
;
+
+export default Row;
diff --git a/src/components/Grid/index.js b/src/components/Grid/index.js
new file mode 100644
index 0000000..11cd724
--- /dev/null
+++ b/src/components/Grid/index.js
@@ -0,0 +1,291 @@
+import * as React from "react";
+import {EDGE_STATE} from "../../constants";
+import {areValid, findNeighbor, getNeighbors, multiStyles} from "../../utils";
+import Controls from "../Controls";
+import LineRow from "./components/LineRow";
+import NumberRow from "./components/NumberRow";
+import styles from "./styles.module.scss";
+
+/*
+ Grid traversal
+ (0, 0) -- (0, 1) -- (0, 2) -->
+ | | |
+ | | |
+ (1, 0) -- (1, 1) -- (1, 2) -->
+ | | |
+ | | |
+ (2, 0) -- (2, 1) -- (2, 2) -->
+ | | |
+ | | |
+ V V V
+*/
+
+const DEFAULT_NODE = {neigh: [], n: -1};
+/*
+ structure for neigh elements:
+ {state:
, loc: [x, y]}
+*/
+
+const Grid = ({
+ dim = [3, 3],
+ edges = [],
+ numbers = [],
+ matrix: propMatrix = [],
+ updateMatrix: updatePropMatrix = () => {},
+ readOnly,
+ onReset: propOnReset,
+ editorMode,
+ check,
+ className,
+}) => {
+ const nRows = dim[0];
+ const nCols = dim[1];
+ const nHorLine = nRows + 1;
+ const nVerLine = nCols + 1;
+ const [matrix, setMatrix] = React.useState([]);
+
+ const setPropMatrix = (matrix) => {
+ if (Array.isArray(propMatrix)) {
+ updatePropMatrix(matrix);
+ }
+ };
+
+ // initializing matrix with prop edges and numbers
+ React.useEffect(() => {
+ let temp = new Array(nHorLine)
+ .fill(0)
+ .map(() => new Array(nVerLine).fill(DEFAULT_NODE));
+ numbers.forEach(({r, c, n}) => {
+ if (r <= nRows && c <= nCols) {
+ temp[r - 1][c - 1] = {...temp[r - 1][c - 1], n};
+ }
+ });
+ edges.forEach(({a: [sx, sy], b: [ex, ey], notAllowed, hovered}) => {
+ const edgeState = notAllowed
+ ? EDGE_STATE.NOT_ALLOWED
+ : hovered
+ ? EDGE_STATE.HOVERED
+ : EDGE_STATE.ACTIVE;
+ let startX, startY, endX, endY, hor, ver;
+ hor = sx === ex;
+ ver = sy === ey;
+
+ // excluding cases where both points are same
+ // or where edge is not hor | ver
+ // Assumption: all edges are between two adjacent nodes
+ if ((hor && ver) || (!hor && !ver)) {
+ return;
+ }
+
+ // horizonal line
+ if (hor) {
+ startX = endX = sx;
+ if (sy > ey) {
+ startY = ey;
+ endY = sy;
+ }
+ if (sy < ey) {
+ startY = sy;
+ endY = ey;
+ }
+ }
+ // vertical line
+ if (ver) {
+ startY = endY = sy;
+ if (sx > ex) {
+ startX = ex;
+ endX = sx;
+ }
+ if (sx < ex) {
+ startX = sx;
+ endX = ex;
+ }
+ }
+
+ // check if all nodes are valid nodes
+ if (areValid([startX, endX], nRows) && areValid([startY, endY], nCols)) {
+ temp[startX][startY] = {
+ ...temp[startX][startY],
+ neigh: [
+ ...temp[startX][startY].neigh,
+ {state: edgeState, loc: [endX, endY]},
+ ],
+ };
+ temp[endX][endY] = {
+ ...temp[endX][endY],
+ neigh: [
+ ...temp[endX][endY].neigh,
+ {state: edgeState, loc: [startX, startY]},
+ ],
+ };
+ }
+ });
+
+ setMatrix(temp);
+ setPropMatrix(temp);
+ }, []);
+
+ const updateMatrix = (dataArray) => {
+ let temp = matrix.slice();
+ dataArray.forEach(({node: [x, y], data}) => {
+ temp[x][y] = {...temp[x][y], ...data};
+ });
+ setMatrix(temp);
+ setPropMatrix(temp);
+ };
+
+ const onLineClick = (x, y, direction, click) => {
+ const isMiddleClick = click === "middle";
+ const node = [x, y];
+ const otherNode = direction === "horizontal" ? [x, y + 1] : [x + 1, y];
+ let nodeUpdateData = {};
+ let otherNodeUpdateData = {};
+ const neighborIndex = findNeighbor(node, otherNode, matrix);
+ const otherNeighborIndex = findNeighbor(otherNode, node, matrix);
+
+ // if there was already an edge (ACTIVE | NOT_ALLOWED)
+ if (neighborIndex !== -1) {
+ // if the edge was NOT_ALLOWED
+ const isNewEdgeStateEmpty =
+ matrix[x][y].neigh[neighborIndex].state === EDGE_STATE.NOT_ALLOWED;
+ let newNeighbors = getNeighbors(node, matrix).slice();
+ newNeighbors.splice(neighborIndex, 1);
+ nodeUpdateData = {
+ neigh: [
+ ...newNeighbors,
+ ...(isNewEdgeStateEmpty || isMiddleClick
+ ? []
+ : [{state: EDGE_STATE.NOT_ALLOWED, loc: otherNode}]),
+ ],
+ };
+ let newOtherNeighbors = getNeighbors(otherNode, matrix).slice();
+ newOtherNeighbors.splice(otherNeighborIndex, 1);
+ otherNodeUpdateData = {
+ neigh: [
+ ...newOtherNeighbors,
+ ...(isNewEdgeStateEmpty || isMiddleClick
+ ? []
+ : [{state: EDGE_STATE.NOT_ALLOWED, loc: node}]),
+ ],
+ };
+ } else if (!isMiddleClick) {
+ nodeUpdateData = {
+ neigh: [
+ ...matrix[x][y].neigh,
+ {
+ state:
+ click === "left" ? EDGE_STATE.ACTIVE : EDGE_STATE.NOT_ALLOWED,
+ loc: otherNode,
+ },
+ ],
+ };
+ otherNodeUpdateData = {
+ neigh: [
+ ...matrix[otherNode[0]][otherNode[1]].neigh,
+ {
+ state:
+ click === "left" ? EDGE_STATE.ACTIVE : EDGE_STATE.NOT_ALLOWED,
+ loc: node,
+ },
+ ],
+ };
+ }
+ updateMatrix([
+ {node, data: nodeUpdateData},
+ {node: otherNode, data: otherNodeUpdateData},
+ ]);
+ };
+
+ const onLineLeftClick = (x, y, direction) =>
+ onLineClick(x, y, direction, "left");
+ const onLineRightClick = (x, y, direction) =>
+ onLineClick(x, y, direction, "right");
+ const onLineMiddleClick = (x, y, direction) =>
+ onLineClick(x, y, direction, "middle");
+
+ const onNumberChange = (x, y, num) => {
+ updateMatrix([{node: [x, y], data: {n: num}}]);
+ };
+
+ const onReset = () => {
+ let temp = new Array(nHorLine)
+ .fill(0)
+ .map(() => new Array(nVerLine).fill(DEFAULT_NODE));
+ numbers.forEach(({r, c, n}) => {
+ if (r <= nRows && c <= nCols) {
+ temp[r - 1][c - 1] = {...temp[r - 1][c - 1], n};
+ }
+ });
+ if (editorMode) {
+ propOnReset();
+ }
+ setMatrix(temp);
+ setPropMatrix(temp);
+ };
+
+ const onRefresh = () => {
+ window.location.reload();
+ };
+
+ return (
+ <>
+ {matrix.length > 0 && (
+
+ {Array(nHorLine - 1)
+ .fill(0)
+ .map((_, i) => {
+ return [
+ ,
+ ,
+ ];
+ })
+ .concat([
+ ,
+ ])}
+
+ )}
+ {!readOnly && (
+
+ )}
+ >
+ );
+};
+
+export default Grid;
diff --git a/src/components/Grid/styles.module.scss b/src/components/Grid/styles.module.scss
new file mode 100644
index 0000000..3c1c03d
--- /dev/null
+++ b/src/components/Grid/styles.module.scss
@@ -0,0 +1,296 @@
+$gridColor: #607d8b;
+$gridActiveColor: #90a4ae;
+$gridHoverColor: #eceff1;
+$invalidColor: #ff8a80;
+$validColor: #00e676;
+
+$transitionDuration: 0.2s;
+
+$gridScale: 1;
+
+$nodeSize: $gridScale * 6px;
+@media (max-width: 500px) {
+ $gridScale: 0.5;
+}
+$nodeActiveSize: $gridScale * 7px;
+
+$edgeLength: $gridScale * 50px;
+$edgeBreadth: $gridScale * 2px;
+
+$fontSize: $gridScale * 24px;
+
+$animationSeperation: 0.075s;
+$animationScale: 1.5;
+$animationDuration: 0.25s;
+$animationTimingFunction: linear;
+$animationFillMode: forwards;
+
+// canvas
+.row, .canvas {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.canvas {
+ align-items: flex-start;
+ flex-direction: column;
+ overflow-x: auto;
+ box-sizing: border-box;
+ max-width: 100%;
+ padding: 50px;
+ border-radius: 10px;
+ background-color: #282c34;
+
+ &.readOnly, &.passed {
+ * {
+ pointer-events: none;
+ cursor: default;
+ }
+ }
+}
+
+// grid elements
+.node {
+ display: flex;
+ height: $nodeSize;
+ width: $nodeSize;
+ background-color: $gridColor;
+ border-radius: 50%;
+ z-index: 1;
+ transition: background-color $transitionDuration;
+
+ &.active {
+ background-color: $gridActiveColor;
+ width: $nodeActiveSize;
+ height: $nodeActiveSize;
+ margin: - ($nodeActiveSize - $nodeSize) / 2;
+ }
+}
+
+.line {
+ display: flex;
+ $lineColor: $gridColor;
+ background-repeat: repeat;
+ opacity: 0;
+ position: relative;
+ cursor: pointer;
+
+ &:after, &:before {
+ content: "";
+ background-color: transparent;
+ position: absolute;
+ }
+
+ &.horizontal {
+ background-size: 4*$edgeBreadth 100%;
+ background-image: linear-gradient(90deg, $lineColor 50%, transparent 50%);
+ height: $edgeBreadth;
+ width: $edgeLength;
+
+ &:after, &:before {
+ height: ($nodeSize - $edgeBreadth);
+ width: 100%;
+ left: 0;
+ }
+
+ &:after {
+ top: 100%;
+ }
+
+ &:before {
+ bottom: 100%;
+ }
+ }
+
+ &.vertical {
+ background-size: 100% 4*$edgeBreadth;
+ background-image: linear-gradient(180deg, $lineColor 50%, transparent 50%);
+ height: $edgeLength;
+ width: $edgeBreadth;
+ margin: 0 ($nodeSize - $edgeBreadth) / 2;
+
+ &:after, &:before {
+ width: ($nodeSize - $edgeBreadth);
+ height: 100%;
+ top: 0;
+ }
+
+ &:after {
+ right: 100%;
+ }
+
+ &:before {
+ left: 100%;
+ }
+ }
+
+ &:hover, &.hovered {
+ transition: opacity $transitionDuration;
+ opacity: 1;
+ }
+
+ &.active {
+ opacity: 1;
+ background: $gridActiveColor;
+ transition: background-color $transitionDuration;
+
+ &:hover {
+ background: $gridHoverColor;
+
+ &.horizontal {
+ height: 1.5 * $edgeBreadth;
+ }
+
+ &.vertical {
+ width: 1.5 * $edgeBreadth;
+ margin: 0 (($nodeSize - 1.5 * $edgeBreadth) / 2);
+ }
+ }
+ }
+
+ &.cross {
+ opacity: 1;
+ background: none;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ & > span {
+ position: absolute;
+ width: 6 * $edgeBreadth;
+ height: 3 * $edgeBreadth / 4;
+ background-color: $invalidColor;
+ border-radius: 2 * $edgeBreadth;
+ transition: all $transitionDuration;
+
+ &:first-child {
+ transform: rotate(45deg);
+ }
+
+ &:last-child {
+ transform: rotate(-45deg);
+ }
+ }
+
+ &:hover {
+ & > span {
+ width: 7 * $edgeBreadth;
+ height: $edgeBreadth;
+ background-color: $gridHoverColor;
+ }
+ }
+ }
+}
+
+.number {
+ font-size: $fontSize;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: $edgeLength;
+ width: $edgeLength;
+
+ .invalid {
+ color: $invalidColor;
+ }
+
+ input {
+ text-align: center;
+ height: inherit;
+ width: inherit;
+ background: inherit;
+ outline: none;
+ box-shadow: none;
+ border: inherit;
+ color: inherit;
+ font-size: inherit;
+ display: inherit;
+ align-items: inherit;
+ justify-content: inherit;
+ cursor: inherit;
+ }
+}
+
+.canvas.failed {
+ .node.active {
+ background-color: $invalidColor;
+ }
+
+ .line {
+ &:not(.cross){
+ &:not(:hover) {
+ background-color: $invalidColor;
+ }
+ }
+ }
+}
+
+.canvas.passed {
+
+ .node.active {
+ animation: node $animationDuration $animationTimingFunction $animationFillMode;
+ }
+
+ .line:not(.cross) {
+ &.horizontal {
+ animation: lineX $animationDuration $animationTimingFunction $animationFillMode;
+ }
+
+ &.vertical {
+ animation: lineY $animationDuration $animationTimingFunction $animationFillMode;
+ }
+ }
+
+ .node.active, .line:not(.cross) {
+ @for $i from 1 through 50 {
+ &:nth-child(#{$i}) {
+ animation-delay: ($i - 1) * $animationSeperation;
+ }
+ }
+ }
+
+}
+
+@keyframes node {
+ 0% {
+ background-color: $validColor;
+ transform: scale(1);
+ }
+ 50% {
+ transform: scale($animationScale);
+ }
+ 100% {
+ background-color: $validColor;
+ transform: scale(1);
+ }
+}
+
+@keyframes lineX {
+ 0% {
+ background-color: $validColor;
+ transform: scaleY(1);
+ }
+ 50% {
+ transform: scaleY(1.5 * $animationScale);
+ }
+ 100% {
+ background-color: $validColor;
+ transform: scaleY(1);
+ }
+}
+
+
+@keyframes lineY {
+ 0% {
+ background-color: $validColor;
+ transform: scaleX(1);
+ }
+ 50% {
+ transform: scaleX(1.5 * $animationScale);
+ }
+ 100% {
+ background-color: $validColor;
+ transform: scaleX(1);
+ }
+}
diff --git a/src/components/Icons.js b/src/components/Icons.js
new file mode 100644
index 0000000..fc9a769
--- /dev/null
+++ b/src/components/Icons.js
@@ -0,0 +1,89 @@
+import * as React from "react";
+
+export const Undo = ({className, onClick}) => {
+ return (
+
+
+
+ );
+};
+
+export const Redo = ({className, onClick}) => {
+ return (
+
+
+
+
+ );
+};
+
+export const Refresh = ({className, onClick}) => {
+ return (
+
+
+
+
+ );
+};
+
+export const Reset = ({className, onClick}) => {
+ return (
+
+
+
+
+ );
+};
+
+export const Plus = ({className, onClick}) => {
+ return (
+
+
+
+
+ );
+};
diff --git a/src/components/Info/index.js b/src/components/Info/index.js
new file mode 100644
index 0000000..64d77ac
--- /dev/null
+++ b/src/components/Info/index.js
@@ -0,0 +1,127 @@
+import * as React from "react";
+import {
+ EXAMPLE_GRID_PROPS,
+ NOT_ALLOWED_1_GRID_PROPS,
+ NOT_ALLOWED_2_GRID_PROPS,
+} from "../../constants";
+import Grid from "../Grid";
+
+import styles from "./styles.module.scss";
+
+const Info = () => {
+ const [open, setOpen] = React.useState(false);
+
+ return (
+ <>
+ setOpen(true)}>
+ i
+
+ {open && (
+
+
setOpen(false)} />
+
+
+ Rules
+
+ Connect adjacent dots with vertical or horizontal lines, creating
+ a single loop.
+
+
+
+
+
+ Crossovers or branches are not allowed (as shown by dotted lines
+ below).
+
+
+
+
+
+
+ Numbers in the puzzle indicate the number of lines that should
+ surround it.
+
+
+ You can't draw lines around zeroes.
+
+
+ Each puzzle has just one uniques solution.
+
+
+
+ How to begin
+
+ Begin with the zero next to 3. If no such case is present, then
+ use one of the rules in the{" "}
+
+ wikipedia link
+ {" "}
+ to start off.
+
+
+ Since no lines can be drawn around zero, mark crosses around it as
+ shown.
+
+
+ Now there's a cross in one space around 3. So we know the three
+ lines of 3 can only be drawn in the remaining three spaces.
+
+
+ Next, these lines can only be extended in one direction each.
+
+ Continue using the same logic.
+
+
+ Hints
+
+ Keep eliminating possibilities by marking crosses in spaces
+ between dots where a line isn't possible, i.e., if you have
+ already completed required lines or where a line creation may
+ create a branch or a deadend. Visit the{" "}
+
+ wikipedia link
+ {" "}
+ for more hints.
+
+
+
+ Controls
+
+
+
+
+ Left Mouse Button
+
+ Toggle line
+
+
+
+ Right Mouse Button
+
+ Mark line as crossed/not allowed
+
+
+
+ Middle Mouse Button
+
+ Reset line state
+
+
+
+
+
+ )}
+ >
+ );
+};
+
+export default Info;
diff --git a/src/components/Info/styles.module.scss b/src/components/Info/styles.module.scss
new file mode 100644
index 0000000..fe869bd
--- /dev/null
+++ b/src/components/Info/styles.module.scss
@@ -0,0 +1,171 @@
+* {
+ box-sizing: border-box;
+}
+
+.button {
+ position: fixed;
+ bottom: 10px;
+ right: 10px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: #263238;
+ height: 40px;
+ width: 40px;
+ padding: 10px;
+ font-size: 20px;
+ border: 2px solid #455a64;
+ font-family: Georgia, 'Times New Roman', Times, serif;
+
+ border-radius: 50%;
+ cursor: pointer;
+ transition: all 0.2s;
+ font-style: italic;
+
+ &:hover {
+ border-color: #78909c;
+ font-size: 22px;
+ transform: scale(1.1);
+ color: white;
+ }
+}
+
+.info {
+ position: fixed;
+ height: 100vh;
+ width: 100vw;
+ background-color: #212121;
+ z-index: 2;
+ font-family: monospace;
+ display: flex;
+ padding: 50px 40px;
+ align-items: flex-start;
+ justify-content: flex-start;
+ overflow-y: auto;
+ flex-direction: column;
+ gap: 50px;
+
+ .cross {
+ $size: 20px;
+ height: $size;
+ width: $size;
+ position: fixed;
+ top: 40px;
+ right: 40px;
+ background-color: inherit;
+ padding: 3 * $size / 4;
+ z-index: 3;
+ cursor: pointer;
+
+ &:after, &:before {
+ content: "";
+ height: $size / 8;
+ width: $size;
+ background-color: #b0bec5;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ border-radius: $size / 8;
+ }
+
+ &:after {
+ transform: translate(-50%, -50%) rotate(45deg);
+ }
+
+ &:before {
+ transform: translate(-50%, -50%) rotate(-45deg);
+ }
+ }
+
+ section {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: flex-start;
+ gap: 20px;
+ font-size: 17px;
+ color: #e0e0e0;
+ width: 100%;
+
+ & > span {
+ padding-left: 20px;
+ }
+
+ a {
+ color: #00b0ff;
+ text-decoration: none;
+ }
+ }
+
+ .title {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
+ margin-bottom: 10px;
+ font-size: 26px;
+ text-transform: uppercase;
+ font-weight: 600;
+ color: #f5f5f5;
+ }
+
+ .item {
+ line-height: inherit;
+ font-size: inherit;
+ padding-left: 20px;
+ color: inherit;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ width: 100%;
+
+ &:before {
+ content: "";
+ position: absolute;
+ width: 7px;
+ height: 7px;
+ top: 7px;
+ left: 0;
+ box-sizing: border-box;
+ border-radius: 50%;
+ background-color: #424242;
+ border: 1.5px solid #9e9e9e;
+ }
+
+ .eg {
+ display: flex;
+ flex-wrap: wrap;
+ align-self: center;
+ justify-self: center;
+ gap: 20px;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ }
+ }
+
+ table {
+ width: 100%;
+ padding-left: 20px;
+
+ tr {
+ width: 100%;
+
+ td {
+ width: 50%;
+ padding-bottom: 10px;
+
+ &:last-child {
+ padding-left: 10px;
+ }
+ }
+
+ &:last-child {
+ td {
+ padding-bottom: 0;
+ }
+ }
+ }
+ }
+}
diff --git a/src/constants.js b/src/constants.js
new file mode 100644
index 0000000..2b1ae2c
--- /dev/null
+++ b/src/constants.js
@@ -0,0 +1,162 @@
+export const EDGE_STATE = {
+ ACTIVE: "ACTIVE",
+ NOT_ALLOWED: "NOT_ALLOWED",
+ HOVERED: "HOOVERED",
+};
+
+export const EXAMPLE_GRID_PROPS = {
+ readOnly: true,
+ dim: [7, 7],
+ edges: [
+ {a: [0, 0], b: [0, 1]},
+ {a: [0, 0], b: [1, 0]},
+ {a: [0, 1], b: [0, 2], notAllowed: true},
+ {a: [0, 1], b: [1, 1]},
+ {a: [0, 2], b: [0, 3], notAllowed: true},
+ {a: [0, 2], b: [1, 2], notAllowed: true},
+ {a: [0, 3], b: [0, 4]},
+ {a: [0, 3], b: [1, 3]},
+ {a: [0, 4], b: [0, 5]},
+ {a: [0, 4], b: [1, 4], notAllowed: true},
+ {a: [0, 5], b: [0, 6]},
+ {a: [0, 5], b: [1, 5], notAllowed: true},
+ {a: [0, 6], b: [0, 7]},
+ {a: [0, 6], b: [1, 6], notAllowed: true},
+ {a: [0, 7], b: [1, 7]},
+ {a: [1, 0], b: [1, 1], notAllowed: true},
+ {a: [1, 0], b: [2, 0]},
+ {a: [1, 1], b: [1, 2]},
+ {a: [1, 2], b: [1, 3], notAllowed: true},
+ {a: [1, 2], b: [2, 2]},
+ {a: [1, 3], b: [1, 4]},
+ {a: [1, 3], b: [2, 3], notAllowed: true},
+ {a: [1, 4], b: [1, 5]},
+ {a: [1, 4], b: [2, 4], notAllowed: true},
+ {a: [1, 5], b: [1, 6]},
+ {a: [1, 5], b: [2, 5], notAllowed: true},
+ {a: [1, 6], b: [1, 7], notAllowed: true},
+ {a: [1, 6], b: [2, 6]},
+ {a: [1, 7], b: [2, 7]},
+ {a: [2, 0], b: [2, 1]},
+ {a: [2, 1], b: [3, 1]},
+ {a: [2, 2], b: [2, 3]},
+ {a: [2, 2], b: [3, 2], notAllowed: true},
+ {a: [2, 3], b: [2, 4], notAllowed: true},
+ {a: [2, 3], b: [3, 3]},
+ {a: [2, 4], b: [2, 5], notAllowed: true},
+ {a: [2, 4], b: [3, 4], notAllowed: true},
+ {a: [2, 5], b: [2, 6]},
+ {a: [2, 5], b: [3, 5]},
+ {a: [2, 6], b: [2, 7], notAllowed: true},
+ {a: [2, 6], b: [3, 6], notAllowed: true},
+ {a: [2, 7], b: [3, 7]},
+ {a: [3, 0], b: [3, 1], notAllowed: true},
+ {a: [3, 1], b: [3, 2]},
+ {a: [3, 1], b: [4, 1], notAllowed: true},
+ {a: [3, 2], b: [4, 2]},
+ {a: [3, 3], b: [3, 4]},
+ {a: [3, 4], b: [3, 5], notAllowed: true},
+ {a: [3, 4], b: [4, 4]},
+ {a: [3, 5], b: [3, 6], notAllowed: true},
+ {a: [3, 5], b: [4, 5]},
+ {a: [3, 6], b: [3, 7]},
+ {a: [3, 6], b: [4, 6]},
+ {a: [4, 0], b: [4, 1], notAllowed: true},
+ {a: [4, 1], b: [4, 2], notAllowed: true},
+ {a: [4, 1], b: [5, 1], notAllowed: true},
+ {a: [4, 2], b: [4, 3]},
+ {a: [4, 2], b: [5, 2], notAllowed: true},
+ {a: [4, 3], b: [4, 4], notAllowed: true},
+ {a: [4, 3], b: [5, 3]},
+ {a: [4, 4], b: [4, 5]},
+ {a: [4, 4], b: [5, 4], notAllowed: true},
+ {a: [4, 5], b: [4, 6], notAllowed: true},
+ {a: [4, 5], b: [5, 5], notAllowed: true},
+ {a: [4, 6], b: [4, 7]},
+ {a: [4, 6], b: [5, 6], notAllowed: true},
+ {a: [4, 7], b: [5, 7]},
+ {a: [5, 0], b: [5, 1], notAllowed: true},
+ {a: [5, 1], b: [5, 2]},
+ {a: [5, 1], b: [6, 1]},
+ {a: [5, 2], b: [5, 3]},
+ {a: [5, 2], b: [6, 2], notAllowed: true},
+ {a: [5, 3], b: [5, 4], notAllowed: true},
+ {a: [5, 3], b: [6, 3], notAllowed: true},
+ {a: [5, 4], b: [5, 5]},
+ {a: [5, 4], b: [6, 4]},
+ {a: [5, 5], b: [5, 6], notAllowed: true},
+ {a: [5, 5], b: [6, 5]},
+ {a: [5, 6], b: [5, 7]},
+ {a: [5, 6], b: [6, 6]},
+ {a: [6, 0], b: [6, 1]},
+ {a: [6, 0], b: [7, 0]},
+ {a: [6, 1], b: [7, 1], notAllowed: true},
+ {a: [6, 2], b: [6, 3]},
+ {a: [6, 2], b: [7, 2]},
+ {a: [6, 3], b: [6, 4], notAllowed: true},
+ {a: [6, 3], b: [7, 3]},
+ {a: [6, 4], b: [6, 5], notAllowed: true},
+ {a: [6, 4], b: [7, 4]},
+ {a: [6, 5], b: [6, 6], notAllowed: true},
+ {a: [6, 5], b: [7, 5]},
+ {a: [6, 6], b: [6, 7]},
+ {a: [6, 6], b: [7, 6], notAllowed: true},
+ {a: [6, 7], b: [7, 7]},
+ {a: [7, 0], b: [7, 1]},
+ {a: [7, 1], b: [7, 2]},
+ {a: [7, 3], b: [7, 4]},
+ {a: [7, 5], b: [7, 6]},
+ {a: [7, 6], b: [7, 7]},
+ ],
+ numbers: [
+ {r: 1, c: 4, n: 3},
+ {r: 1, c: 5, n: 2},
+ {r: 1, c: 6, n: 2},
+ {r: 2, c: 1, n: 2},
+ {r: 2, c: 2, n: 2},
+ {r: 2, c: 3, n: 2},
+ {r: 2, c: 6, n: 3},
+ {r: 3, c: 1, n: 2},
+ {r: 3, c: 2, n: 2},
+ {r: 3, c: 5, n: 1},
+ {r: 3, c: 6, n: 2},
+ {r: 4, c: 3, n: 2},
+ {r: 4, c: 5, n: 3},
+ {r: 4, c: 7, n: 3},
+ {r: 5, c: 1, n: 0},
+ {r: 5, c: 6, n: 0},
+ {r: 5, c: 7, n: 3},
+ {r: 6, c: 1, n: 2},
+ {r: 6, c: 7, n: 3},
+ {r: 7, c: 2, n: 2},
+ {r: 7, c: 3, n: 3},
+ {r: 7, c: 5, n: 2},
+ ],
+};
+
+export const NOT_ALLOWED_1_GRID_PROPS = {
+ readOnly: true,
+ dim: [2, 2],
+ edges: [
+ {a: [0, 1], b: [1, 1], hovered: true},
+ {a: [1, 0], b: [1, 1], hovered: true},
+ {a: [1, 1], b: [1, 2]},
+ {a: [1, 1], b: [2, 1]},
+ ],
+ numbers: [{r: 2, c: 2, n: 2}],
+};
+
+export const NOT_ALLOWED_2_GRID_PROPS = {
+ readOnly: true,
+ dim: [2, 3],
+ edges: [
+ {a: [0, 1], b: [1, 1]},
+ {a: [0, 2], b: [1, 2]},
+ {a: [1, 0], b: [1, 1], hovered: true},
+ {a: [1, 1], b: [1, 2]},
+ {a: [1, 1], b: [2, 1], hovered: true},
+ {a: [1, 2], b: [1, 3], hovered: true},
+ {a: [1, 2], b: [2, 2], hovered: true},
+ ],
+ numbers: [{r: 1, c: 2, n: 3}],
+};
diff --git a/src/examples.js b/src/examples.js
new file mode 100644
index 0000000..7b93bf0
--- /dev/null
+++ b/src/examples.js
@@ -0,0 +1,608 @@
+export const examples = [
+ {
+ dim: [7, 7],
+ numbers: [
+ {r: 1, c: 2, n: 2},
+ {r: 1, c: 7, n: 3},
+ {r: 2, c: 2, n: 3},
+ {r: 2, c: 4, n: 2},
+ {r: 2, c: 5, n: 2},
+ {r: 2, c: 7, n: 1},
+ {r: 3, c: 2, n: 3},
+ {r: 3, c: 3, n: 2},
+ {r: 3, c: 4, n: 2},
+ {r: 3, c: 6, n: 2},
+ {r: 3, c: 7, n: 2},
+ {r: 4, c: 3, n: 2},
+ {r: 4, c: 4, n: 0},
+ {r: 4, c: 5, n: 3},
+ {r: 4, c: 6, n: 2},
+ {r: 5, c: 3, n: 2},
+ {r: 5, c: 5, n: 3},
+ {r: 5, c: 6, n: 2},
+ {r: 6, c: 2, n: 2},
+ {r: 6, c: 3, n: 1},
+ {r: 6, c: 5, n: 1},
+ {r: 7, c: 5, n: 3},
+ {r: 7, c: 7, n: 3},
+ ],
+ solution: [
+ {a: [0, 0], b: [0, 1]},
+ {a: [0, 0], b: [1, 0]},
+ {a: [0, 1], b: [1, 1]},
+ {a: [0, 3], b: [0, 4]},
+ {a: [0, 3], b: [1, 3]},
+ {a: [0, 4], b: [1, 4]},
+ {a: [0, 6], b: [0, 7]},
+ {a: [0, 6], b: [1, 6]},
+ {a: [0, 7], b: [1, 7]},
+ {a: [1, 0], b: [2, 0]},
+ {a: [1, 1], b: [1, 2]},
+ {a: [1, 2], b: [2, 2]},
+ {a: [1, 3], b: [2, 3]},
+ {a: [1, 4], b: [2, 4]},
+ {a: [1, 5], b: [1, 6]},
+ {a: [1, 5], b: [2, 5]},
+ {a: [1, 7], b: [2, 7]},
+ {a: [2, 0], b: [3, 0]},
+ {a: [2, 1], b: [2, 2]},
+ {a: [2, 1], b: [3, 1]},
+ {a: [2, 3], b: [3, 3]},
+ {a: [2, 4], b: [3, 4]},
+ {a: [2, 5], b: [2, 6]},
+ {a: [2, 6], b: [3, 6]},
+ {a: [2, 7], b: [3, 7]},
+ {a: [3, 0], b: [4, 0]},
+ {a: [3, 1], b: [3, 2]},
+ {a: [3, 2], b: [3, 3]},
+ {a: [3, 4], b: [3, 5]},
+ {a: [3, 5], b: [4, 5]},
+ {a: [3, 6], b: [4, 6]},
+ {a: [3, 7], b: [4, 7]},
+ {a: [4, 0], b: [5, 0]},
+ {a: [4, 1], b: [4, 2]},
+ {a: [4, 1], b: [5, 1]},
+ {a: [4, 2], b: [4, 3]},
+ {a: [4, 3], b: [5, 3]},
+ {a: [4, 4], b: [4, 5]},
+ {a: [4, 4], b: [5, 4]},
+ {a: [4, 6], b: [5, 6]},
+ {a: [4, 7], b: [5, 7]},
+ {a: [5, 0], b: [6, 0]},
+ {a: [5, 1], b: [6, 1]},
+ {a: [5, 3], b: [6, 3]},
+ {a: [5, 4], b: [5, 5]},
+ {a: [5, 5], b: [5, 6]},
+ {a: [5, 7], b: [6, 7]},
+ {a: [6, 0], b: [7, 0]},
+ {a: [6, 1], b: [6, 2]},
+ {a: [6, 2], b: [7, 2]},
+ {a: [6, 3], b: [6, 4]},
+ {a: [6, 4], b: [7, 4]},
+ {a: [6, 5], b: [6, 6]},
+ {a: [6, 5], b: [7, 5]},
+ {a: [6, 6], b: [7, 6]},
+ {a: [6, 7], b: [7, 7]},
+ {a: [7, 0], b: [7, 1]},
+ {a: [7, 1], b: [7, 2]},
+ {a: [7, 4], b: [7, 5]},
+ {a: [7, 6], b: [7, 7]},
+ ],
+ },
+ {
+ dim: [7, 7],
+ numbers: [
+ {r: 1, c: 4, n: 1},
+ {r: 1, c: 7, n: 3},
+ {r: 2, c: 1, n: 2},
+ {r: 2, c: 2, n: 2},
+ {r: 2, c: 3, n: 3},
+ {r: 2, c: 4, n: 3},
+ {r: 2, c: 5, n: 2},
+ {r: 3, c: 1, n: 2},
+ {r: 3, c: 2, n: 2},
+ {r: 3, c: 5, n: 2},
+ {r: 3, c: 6, n: 2},
+ {r: 3, c: 7, n: 3},
+ {r: 4, c: 1, n: 3},
+ {r: 4, c: 2, n: 0},
+ {r: 4, c: 3, n: 2},
+ {r: 4, c: 7, n: 3},
+ {r: 5, c: 1, n: 3},
+ {r: 5, c: 5, n: 3},
+ {r: 5, c: 6, n: 2},
+ {r: 6, c: 2, n: 1},
+ {r: 6, c: 4, n: 2},
+ {r: 6, c: 6, n: 3},
+ {r: 7, c: 2, n: 2},
+ {r: 7, c: 6, n: 2},
+ ],
+ solution: [
+ {a: [0, 0], b: [0, 1]},
+ {a: [0, 0], b: [1, 0]},
+ {a: [0, 1], b: [0, 2]},
+ {a: [0, 2], b: [0, 3]},
+ {a: [0, 3], b: [0, 4]},
+ {a: [0, 4], b: [0, 5]},
+ {a: [0, 5], b: [0, 6]},
+ {a: [0, 6], b: [0, 7]},
+ {a: [0, 7], b: [1, 7]},
+ {a: [1, 0], b: [1, 1]},
+ {a: [1, 1], b: [2, 1]},
+ {a: [1, 2], b: [1, 3]},
+ {a: [1, 2], b: [2, 2]},
+ {a: [1, 3], b: [2, 3]},
+ {a: [1, 4], b: [1, 5]},
+ {a: [1, 4], b: [2, 4]},
+ {a: [1, 5], b: [1, 6]},
+ {a: [1, 6], b: [1, 7]},
+ {a: [2, 1], b: [3, 1]},
+ {a: [2, 2], b: [3, 2]},
+ {a: [2, 3], b: [2, 4]},
+ {a: [2, 5], b: [2, 6]},
+ {a: [2, 5], b: [3, 5]},
+ {a: [2, 6], b: [2, 7]},
+ {a: [2, 7], b: [3, 7]},
+ {a: [3, 0], b: [3, 1]},
+ {a: [3, 0], b: [4, 0]},
+ {a: [3, 2], b: [3, 3]},
+ {a: [3, 3], b: [3, 4]},
+ {a: [3, 4], b: [3, 5]},
+ {a: [3, 6], b: [3, 7]},
+ {a: [3, 6], b: [4, 6]},
+ {a: [4, 0], b: [4, 1]},
+ {a: [4, 1], b: [5, 1]},
+ {a: [4, 2], b: [4, 3]},
+ {a: [4, 2], b: [5, 2]},
+ {a: [4, 3], b: [5, 3]},
+ {a: [4, 4], b: [4, 5]},
+ {a: [4, 4], b: [5, 4]},
+ {a: [4, 5], b: [5, 5]},
+ {a: [4, 6], b: [4, 7]},
+ {a: [4, 7], b: [5, 7]},
+ {a: [5, 0], b: [5, 1]},
+ {a: [5, 0], b: [6, 0]},
+ {a: [5, 2], b: [6, 2]},
+ {a: [5, 3], b: [5, 4]},
+ {a: [5, 5], b: [5, 6]},
+ {a: [5, 6], b: [6, 6]},
+ {a: [5, 7], b: [6, 7]},
+ {a: [6, 0], b: [7, 0]},
+ {a: [6, 2], b: [7, 2]},
+ {a: [6, 3], b: [6, 4]},
+ {a: [6, 3], b: [7, 3]},
+ {a: [6, 4], b: [6, 5]},
+ {a: [6, 5], b: [6, 6]},
+ {a: [6, 7], b: [7, 7]},
+ {a: [7, 0], b: [7, 1]},
+ {a: [7, 1], b: [7, 2]},
+ {a: [7, 3], b: [7, 4]},
+ {a: [7, 4], b: [7, 5]},
+ {a: [7, 5], b: [7, 6]},
+ {a: [7, 6], b: [7, 7]},
+ ],
+ },
+ {
+ dim: [7, 7],
+ numbers: [
+ {r: 1, c: 2, n: 2},
+ {r: 1, c: 4, n: 3},
+ {r: 1, c: 5, n: 2},
+ {r: 1, c: 6, n: 3},
+ {r: 2, c: 2, n: 1},
+ {r: 2, c: 3, n: 3},
+ {r: 2, c: 4, n: 2},
+ {r: 2, c: 6, n: 2},
+ {r: 3, c: 2, n: 2},
+ {r: 3, c: 4, n: 0},
+ {r: 3, c: 5, n: 3},
+ {r: 4, c: 1, n: 1},
+ {r: 4, c: 2, n: 2},
+ {r: 4, c: 3, n: 3},
+ {r: 4, c: 5, n: 3},
+ {r: 5, c: 1, n: 2},
+ {r: 5, c: 2, n: 2},
+ {r: 5, c: 3, n: 1},
+ {r: 5, c: 4, n: 1},
+ {r: 5, c: 5, n: 1},
+ {r: 5, c: 6, n: 2},
+ {r: 6, c: 1, n: 2},
+ {r: 6, c: 3, n: 2},
+ {r: 7, c: 4, n: 2},
+ {r: 7, c: 5, n: 3},
+ {r: 7, c: 7, n: 3},
+ ],
+ solution: [
+ {a: [0, 0], b: [0, 1]},
+ {a: [0, 0], b: [1, 0]},
+ {a: [0, 1], b: [0, 2]},
+ {a: [0, 2], b: [1, 2]},
+ {a: [0, 3], b: [0, 4]},
+ {a: [0, 3], b: [1, 3]},
+ {a: [0, 4], b: [1, 4]},
+ {a: [0, 5], b: [0, 6]},
+ {a: [0, 5], b: [1, 5]},
+ {a: [0, 6], b: [0, 7]},
+ {a: [0, 7], b: [1, 7]},
+ {a: [1, 0], b: [2, 0]},
+ {a: [1, 2], b: [2, 2]},
+ {a: [1, 3], b: [2, 3]},
+ {a: [1, 4], b: [2, 4]},
+ {a: [1, 5], b: [1, 6]},
+ {a: [1, 6], b: [2, 6]},
+ {a: [1, 7], b: [2, 7]},
+ {a: [2, 0], b: [2, 1]},
+ {a: [2, 1], b: [3, 1]},
+ {a: [2, 2], b: [2, 3]},
+ {a: [2, 4], b: [2, 5]},
+ {a: [2, 5], b: [3, 5]},
+ {a: [2, 6], b: [3, 6]},
+ {a: [2, 7], b: [3, 7]},
+ {a: [3, 1], b: [3, 2]},
+ {a: [3, 2], b: [3, 3]},
+ {a: [3, 3], b: [4, 3]},
+ {a: [3, 4], b: [3, 5]},
+ {a: [3, 4], b: [4, 4]},
+ {a: [3, 6], b: [4, 6]},
+ {a: [3, 7], b: [4, 7]},
+ {a: [4, 0], b: [4, 1]},
+ {a: [4, 0], b: [5, 0]},
+ {a: [4, 1], b: [4, 2]},
+ {a: [4, 2], b: [4, 3]},
+ {a: [4, 4], b: [4, 5]},
+ {a: [4, 5], b: [4, 6]},
+ {a: [4, 7], b: [5, 7]},
+ {a: [5, 0], b: [6, 0]},
+ {a: [5, 1], b: [5, 2]},
+ {a: [5, 1], b: [6, 1]},
+ {a: [5, 2], b: [6, 2]},
+ {a: [5, 3], b: [5, 4]},
+ {a: [5, 3], b: [6, 3]},
+ {a: [5, 4], b: [6, 4]},
+ {a: [5, 5], b: [5, 6]},
+ {a: [5, 5], b: [6, 5]},
+ {a: [5, 6], b: [6, 6]},
+ {a: [5, 7], b: [6, 7]},
+ {a: [6, 0], b: [7, 0]},
+ {a: [6, 1], b: [7, 1]},
+ {a: [6, 2], b: [7, 2]},
+ {a: [6, 3], b: [7, 3]},
+ {a: [6, 4], b: [7, 4]},
+ {a: [6, 5], b: [7, 5]},
+ {a: [6, 6], b: [7, 6]},
+ {a: [6, 7], b: [7, 7]},
+ {a: [7, 0], b: [7, 1]},
+ {a: [7, 2], b: [7, 3]},
+ {a: [7, 4], b: [7, 5]},
+ {a: [7, 6], b: [7, 7]},
+ ],
+ },
+ {
+ dim: [7, 7],
+ numbers: [
+ {r: 1, c: 2, n: 2},
+ {r: 1, c: 3, n: 2},
+ {r: 1, c: 4, n: 2},
+ {r: 1, c: 5, n: 2},
+ {r: 1, c: 6, n: 2},
+ {r: 1, c: 7, n: 2},
+ {r: 2, c: 1, n: 2},
+ {r: 2, c: 7, n: 2},
+ {r: 3, c: 1, n: 2},
+ {r: 3, c: 3, n: 2},
+ {r: 3, c: 4, n: 2},
+ {r: 3, c: 5, n: 2},
+ {r: 3, c: 7, n: 2},
+ {r: 4, c: 1, n: 3},
+ {r: 4, c: 5, n: 2},
+ {r: 4, c: 7, n: 2},
+ {r: 5, c: 2, n: 1},
+ {r: 5, c: 4, n: 3},
+ {r: 5, c: 5, n: 0},
+ {r: 5, c: 6, n: 2},
+ {r: 5, c: 7, n: 1},
+ {r: 6, c: 1, n: 2},
+ {r: 6, c: 2, n: 3},
+ {r: 6, c: 4, n: 3},
+ {r: 6, c: 5, n: 2},
+ {r: 7, c: 5, n: 2},
+ {r: 7, c: 6, n: 2},
+ ],
+ solution: [
+ {a: [0, 0], b: [0, 1]},
+ {a: [0, 0], b: [1, 0]},
+ {a: [0, 1], b: [0, 2]},
+ {a: [0, 2], b: [0, 3]},
+ {a: [0, 3], b: [0, 4]},
+ {a: [0, 4], b: [0, 5]},
+ {a: [0, 5], b: [0, 6]},
+ {a: [0, 6], b: [0, 7]},
+ {a: [0, 7], b: [1, 7]},
+ {a: [1, 0], b: [1, 1]},
+ {a: [1, 1], b: [1, 2]},
+ {a: [1, 2], b: [1, 3]},
+ {a: [1, 3], b: [1, 4]},
+ {a: [1, 4], b: [1, 5]},
+ {a: [1, 5], b: [1, 6]},
+ {a: [1, 6], b: [2, 6]},
+ {a: [1, 7], b: [2, 7]},
+ {a: [2, 0], b: [2, 1]},
+ {a: [2, 0], b: [3, 0]},
+ {a: [2, 1], b: [2, 2]},
+ {a: [2, 2], b: [2, 3]},
+ {a: [2, 3], b: [2, 4]},
+ {a: [2, 4], b: [2, 5]},
+ {a: [2, 5], b: [3, 5]},
+ {a: [2, 6], b: [3, 6]},
+ {a: [2, 7], b: [3, 7]},
+ {a: [3, 0], b: [4, 0]},
+ {a: [3, 1], b: [3, 2]},
+ {a: [3, 1], b: [4, 1]},
+ {a: [3, 2], b: [3, 3]},
+ {a: [3, 3], b: [3, 4]},
+ {a: [3, 4], b: [4, 4]},
+ {a: [3, 5], b: [4, 5]},
+ {a: [3, 6], b: [4, 6]},
+ {a: [3, 7], b: [4, 7]},
+ {a: [4, 0], b: [4, 1]},
+ {a: [4, 3], b: [4, 4]},
+ {a: [4, 3], b: [5, 3]},
+ {a: [4, 5], b: [4, 6]},
+ {a: [4, 7], b: [5, 7]},
+ {a: [5, 1], b: [5, 2]},
+ {a: [5, 1], b: [6, 1]},
+ {a: [5, 2], b: [6, 2]},
+ {a: [5, 3], b: [5, 4]},
+ {a: [5, 4], b: [6, 4]},
+ {a: [5, 5], b: [5, 6]},
+ {a: [5, 5], b: [6, 5]},
+ {a: [5, 6], b: [6, 6]},
+ {a: [5, 7], b: [6, 7]},
+ {a: [6, 0], b: [6, 1]},
+ {a: [6, 0], b: [7, 0]},
+ {a: [6, 2], b: [6, 3]},
+ {a: [6, 3], b: [6, 4]},
+ {a: [6, 5], b: [7, 5]},
+ {a: [6, 6], b: [7, 6]},
+ {a: [6, 7], b: [7, 7]},
+ {a: [7, 0], b: [7, 1]},
+ {a: [7, 1], b: [7, 2]},
+ {a: [7, 2], b: [7, 3]},
+ {a: [7, 3], b: [7, 4]},
+ {a: [7, 4], b: [7, 5]},
+ {a: [7, 6], b: [7, 7]},
+ ],
+ },
+ {
+ dim: [5, 5],
+ numbers: [
+ {r: 1, c: 2, n: 3},
+ {r: 1, c: 3, n: 1},
+ {r: 1, c: 4, n: 2},
+ {r: 2, c: 2, n: 3},
+ {r: 2, c: 5, n: 2},
+ {r: 3, c: 2, n: 2},
+ {r: 3, c: 5, n: 3},
+ {r: 4, c: 1, n: 2},
+ {r: 4, c: 3, n: 1},
+ {r: 4, c: 5, n: 2},
+ {r: 5, c: 1, n: 0},
+ {r: 5, c: 4, n: 3},
+ ],
+ solution: [
+ {a: [0, 0], b: [0, 1]},
+ {a: [0, 0], b: [1, 0]},
+ {a: [0, 1], b: [0, 2]},
+ {a: [0, 2], b: [1, 2]},
+ {a: [0, 4], b: [0, 5]},
+ {a: [0, 4], b: [1, 4]},
+ {a: [0, 5], b: [1, 5]},
+ {a: [1, 0], b: [2, 0]},
+ {a: [1, 1], b: [1, 2]},
+ {a: [1, 1], b: [2, 1]},
+ {a: [1, 3], b: [1, 4]},
+ {a: [1, 3], b: [2, 3]},
+ {a: [1, 5], b: [2, 5]},
+ {a: [2, 0], b: [3, 0]},
+ {a: [2, 1], b: [2, 2]},
+ {a: [2, 2], b: [3, 2]},
+ {a: [2, 3], b: [3, 3]},
+ {a: [2, 4], b: [2, 5]},
+ {a: [2, 4], b: [3, 4]},
+ {a: [3, 0], b: [3, 1]},
+ {a: [3, 1], b: [4, 1]},
+ {a: [3, 2], b: [3, 3]},
+ {a: [3, 4], b: [3, 5]},
+ {a: [3, 5], b: [4, 5]},
+ {a: [4, 1], b: [4, 2]},
+ {a: [4, 2], b: [5, 2]},
+ {a: [4, 3], b: [4, 4]},
+ {a: [4, 3], b: [5, 3]},
+ {a: [4, 4], b: [5, 4]},
+ {a: [4, 5], b: [5, 5]},
+ {a: [5, 2], b: [5, 3]},
+ {a: [5, 4], b: [5, 5]},
+ ],
+ },
+ {
+ dim: [7, 7],
+ numbers: [
+ {r: 1, c: 3, n: 2},
+ {r: 2, c: 1, n: 2},
+ {r: 2, c: 2, n: 3},
+ {r: 2, c: 4, n: 2},
+ {r: 2, c: 6, n: 2},
+ {r: 3, c: 3, n: 1},
+ {r: 3, c: 5, n: 3},
+ {r: 4, c: 3, n: 2},
+ {r: 4, c: 4, n: 3},
+ {r: 4, c: 6, n: 3},
+ {r: 4, c: 7, n: 2},
+ {r: 5, c: 1, n: 3},
+ {r: 5, c: 3, n: 0},
+ {r: 5, c: 4, n: 2},
+ {r: 6, c: 1, n: 2},
+ {r: 6, c: 2, n: 3},
+ {r: 6, c: 6, n: 2},
+ {r: 7, c: 3, n: 1},
+ {r: 7, c: 5, n: 3},
+ {r: 7, c: 7, n: 3},
+ ],
+ solution: [
+ {a: [0, 0], b: [0, 1]},
+ {a: [0, 0], b: [1, 0]},
+ {a: [0, 1], b: [1, 1]},
+ {a: [0, 2], b: [0, 3]},
+ {a: [0, 2], b: [1, 2]},
+ {a: [0, 3], b: [0, 4]},
+ {a: [0, 4], b: [1, 4]},
+ {a: [0, 5], b: [0, 6]},
+ {a: [0, 5], b: [1, 5]},
+ {a: [0, 6], b: [0, 7]},
+ {a: [0, 7], b: [1, 7]},
+ {a: [1, 0], b: [2, 0]},
+ {a: [1, 1], b: [2, 1]},
+ {a: [1, 2], b: [2, 2]},
+ {a: [1, 3], b: [1, 4]},
+ {a: [1, 3], b: [2, 3]},
+ {a: [1, 5], b: [1, 6]},
+ {a: [1, 6], b: [2, 6]},
+ {a: [1, 7], b: [2, 7]},
+ {a: [2, 0], b: [3, 0]},
+ {a: [2, 1], b: [2, 2]},
+ {a: [2, 3], b: [3, 3]},
+ {a: [2, 4], b: [2, 5]},
+ {a: [2, 4], b: [3, 4]},
+ {a: [2, 5], b: [3, 5]},
+ {a: [2, 6], b: [3, 6]},
+ {a: [2, 7], b: [3, 7]},
+ {a: [3, 0], b: [3, 1]},
+ {a: [3, 1], b: [3, 2]},
+ {a: [3, 2], b: [4, 2]},
+ {a: [3, 3], b: [4, 3]},
+ {a: [3, 4], b: [4, 4]},
+ {a: [3, 5], b: [4, 5]},
+ {a: [3, 6], b: [4, 6]},
+ {a: [3, 7], b: [4, 7]},
+ {a: [4, 0], b: [4, 1]},
+ {a: [4, 0], b: [5, 0]},
+ {a: [4, 1], b: [4, 2]},
+ {a: [4, 3], b: [4, 4]},
+ {a: [4, 5], b: [4, 6]},
+ {a: [4, 7], b: [5, 7]},
+ {a: [5, 0], b: [5, 1]},
+ {a: [5, 1], b: [5, 2]},
+ {a: [5, 2], b: [6, 2]},
+ {a: [5, 3], b: [5, 4]},
+ {a: [5, 3], b: [6, 3]},
+ {a: [5, 4], b: [5, 5]},
+ {a: [5, 5], b: [5, 6]},
+ {a: [5, 6], b: [6, 6]},
+ {a: [5, 7], b: [6, 7]},
+ {a: [6, 0], b: [6, 1]},
+ {a: [6, 0], b: [7, 0]},
+ {a: [6, 1], b: [6, 2]},
+ {a: [6, 3], b: [6, 4]},
+ {a: [6, 4], b: [6, 5]},
+ {a: [6, 5], b: [7, 5]},
+ {a: [6, 6], b: [7, 6]},
+ {a: [6, 7], b: [7, 7]},
+ {a: [7, 0], b: [7, 1]},
+ {a: [7, 1], b: [7, 2]},
+ {a: [7, 2], b: [7, 3]},
+ {a: [7, 3], b: [7, 4]},
+ {a: [7, 4], b: [7, 5]},
+ {a: [7, 6], b: [7, 7]},
+ ],
+ },
+ {
+ dim: [7, 7],
+ numbers: [
+ {r: 1, c: 3, n: 1},
+ {r: 1, c: 6, n: 3},
+ {r: 1, c: 7, n: 2},
+ {r: 2, c: 1, n: 2},
+ {r: 2, c: 4, n: 2},
+ {r: 2, c: 7, n: 2},
+ {r: 3, c: 2, n: 2},
+ {r: 3, c: 3, n: 1},
+ {r: 3, c: 4, n: 2},
+ {r: 3, c: 5, n: 0},
+ {r: 3, c: 6, n: 3},
+ {r: 4, c: 1, n: 2},
+ {r: 4, c: 4, n: 2},
+ {r: 4, c: 5, n: 3},
+ {r: 5, c: 1, n: 3},
+ {r: 5, c: 4, n: 2},
+ {r: 5, c: 6, n: 2},
+ {r: 6, c: 1, n: 2},
+ {r: 6, c: 2, n: 2},
+ {r: 6, c: 3, n: 2},
+ {r: 6, c: 7, n: 1},
+ {r: 7, c: 2, n: 2},
+ {r: 7, c: 5, n: 1},
+ ],
+ solution: [
+ {a: [0, 0], b: [0, 1]},
+ {a: [0, 0], b: [1, 0]},
+ {a: [0, 1], b: [0, 2]},
+ {a: [0, 2], b: [0, 3]},
+ {a: [0, 3], b: [0, 4]},
+ {a: [0, 4], b: [1, 4]},
+ {a: [0, 5], b: [0, 6]},
+ {a: [0, 5], b: [1, 5]},
+ {a: [0, 6], b: [1, 6]},
+ {a: [1, 0], b: [1, 1]},
+ {a: [1, 1], b: [1, 2]},
+ {a: [1, 2], b: [2, 2]},
+ {a: [1, 3], b: [1, 4]},
+ {a: [1, 3], b: [2, 3]},
+ {a: [1, 5], b: [2, 5]},
+ {a: [1, 6], b: [1, 7]},
+ {a: [1, 7], b: [2, 7]},
+ {a: [2, 0], b: [2, 1]},
+ {a: [2, 0], b: [3, 0]},
+ {a: [2, 1], b: [2, 2]},
+ {a: [2, 3], b: [3, 3]},
+ {a: [2, 5], b: [2, 6]},
+ {a: [2, 6], b: [3, 6]},
+ {a: [2, 7], b: [3, 7]},
+ {a: [3, 0], b: [3, 1]},
+ {a: [3, 1], b: [3, 2]},
+ {a: [3, 2], b: [4, 2]},
+ {a: [3, 3], b: [3, 4]},
+ {a: [3, 4], b: [4, 4]},
+ {a: [3, 5], b: [3, 6]},
+ {a: [3, 5], b: [4, 5]},
+ {a: [3, 7], b: [4, 7]},
+ {a: [4, 0], b: [4, 1]},
+ {a: [4, 0], b: [5, 0]},
+ {a: [4, 1], b: [5, 1]},
+ {a: [4, 2], b: [4, 3]},
+ {a: [4, 3], b: [5, 3]},
+ {a: [4, 4], b: [4, 5]},
+ {a: [4, 6], b: [4, 7]},
+ {a: [4, 6], b: [5, 6]},
+ {a: [5, 0], b: [6, 0]},
+ {a: [5, 1], b: [5, 2]},
+ {a: [5, 2], b: [6, 2]},
+ {a: [5, 3], b: [5, 4]},
+ {a: [5, 4], b: [6, 4]},
+ {a: [5, 5], b: [5, 6]},
+ {a: [5, 5], b: [6, 5]},
+ {a: [6, 0], b: [6, 1]},
+ {a: [6, 1], b: [7, 1]},
+ {a: [6, 2], b: [6, 3]},
+ {a: [6, 3], b: [6, 4]},
+ {a: [6, 5], b: [6, 6]},
+ {a: [6, 6], b: [6, 7]},
+ {a: [6, 7], b: [7, 7]},
+ {a: [7, 1], b: [7, 2]},
+ {a: [7, 2], b: [7, 3]},
+ {a: [7, 3], b: [7, 4]},
+ {a: [7, 4], b: [7, 5]},
+ {a: [7, 5], b: [7, 6]},
+ {a: [7, 6], b: [7, 7]},
+ ],
+ },
+];
diff --git a/src/index.css b/src/index.css
index ec2585e..8684abe 100644
--- a/src/index.css
+++ b/src/index.css
@@ -11,3 +11,9 @@ code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
+
+body, html, #root {
+ height: 100%;
+ width: 100%;
+ box-sizing: border-box;
+}
diff --git a/src/index.js b/src/index.js
index ef2edf8..26419cd 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,14 +1,14 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-import './index.css';
-import App from './App';
-import reportWebVitals from './reportWebVitals';
+import * as React from "react";
+import ReactDOM from "react-dom";
+import "./index.css";
+import App from "./App";
+import reportWebVitals from "./reportWebVitals";
ReactDOM.render(
,
- document.getElementById('root')
+ document.getElementById("root")
);
// If you want to start measuring performance in your app, pass a function
diff --git a/src/reportWebVitals.js b/src/reportWebVitals.js
index 5253d3a..81d1046 100644
--- a/src/reportWebVitals.js
+++ b/src/reportWebVitals.js
@@ -1,6 +1,6 @@
-const reportWebVitals = onPerfEntry => {
+const reportWebVitals = (onPerfEntry) => {
if (onPerfEntry && onPerfEntry instanceof Function) {
- import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
+ import("web-vitals").then(({getCLS, getFID, getFCP, getLCP, getTTFB}) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
diff --git a/src/setupTests.js b/src/setupTests.js
index 8f2609b..1dd407a 100644
--- a/src/setupTests.js
+++ b/src/setupTests.js
@@ -2,4 +2,4 @@
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
-import '@testing-library/jest-dom';
+import "@testing-library/jest-dom";
diff --git a/src/utils.js b/src/utils.js
new file mode 100644
index 0000000..d4c44d1
--- /dev/null
+++ b/src/utils.js
@@ -0,0 +1,105 @@
+import {EDGE_STATE} from "./constants";
+
+// to get multiple styles in space-seperated format
+export const multiStyles = (styles, classNames) =>
+ classNames
+ .reduce(
+ (classNames, className) => [
+ ...classNames,
+ ...(!className ? [] : [styles?.[className] || className]),
+ ],
+ []
+ )
+ .join(" ");
+
+// to check if number is valid number
+export const isValid = (n, lim) => n >= 0 && n <= lim;
+
+// to check all elements are valid numbers
+export const areValid = (n, lim) => n.every((n) => isValid(n, lim));
+
+// to check if a node has an active edge
+export const isNodeActive = (n) =>
+ Array.isArray(n?.neigh) &&
+ n.neigh.some((neighbor) => neighbor.state === EDGE_STATE.ACTIVE);
+
+// to get neighbors from matrix
+export const getNeighbors = ([ax, ay], mat) => {
+ return mat[ax][ay]?.neigh || [];
+};
+
+// to check if two nodes are neighbor in a matrix
+export const findNeighbor = ([ax, ay], [bx, by], mat) => {
+ const index = mat?.[ax]?.[ay]?.neigh.findIndex(
+ ({loc}) => loc[0] === bx && loc[1] === by
+ );
+ return isNaN(index) ? -1 : index;
+};
+
+// get an array of all numbers present in the matrix
+export const getNumbers = (mat) => {
+ let numbers = [];
+ let nRow = mat?.length;
+ let nCol = mat?.[0]?.length;
+ if (!nRow || !nCol) {
+ return;
+ }
+
+ mat.forEach((row, r) => {
+ if (r >= nRow - 1) return;
+ row.forEach((node, c) => {
+ if (c >= nCol - 1) return;
+ numbers = [
+ ...numbers,
+ ...(node.n > -1 ? [{r: r + 1, c: c + 1, n: node.n}] : []),
+ ];
+ });
+ });
+ return numbers;
+};
+
+// get an array of all edges present in the matrix
+export const getEdges = (mat) => {
+ let edges = [];
+ mat.forEach((row, sX) => {
+ row.forEach((node, sY) => {
+ let horEdges = [];
+ let verEdges = [];
+ node.neigh.forEach((edge) => {
+ const [eX, eY] = edge.loc;
+ if (eX > sX && eY === sY) {
+ verEdges = [
+ ...verEdges,
+ {
+ a: [sX, sY],
+ b: [eX, eY],
+ ...(edge?.state === EDGE_STATE.NOT_ALLOWED
+ ? {notAllowed: true}
+ : {}),
+ ...(edge?.state === EDGE_STATE.HOVERED ? {hovered: true} : {}),
+ },
+ ];
+ } else if (eY > sY && eX === sX) {
+ horEdges = [
+ ...horEdges,
+ {
+ a: [sX, sY],
+ b: [eX, eY],
+ ...(edge?.state === EDGE_STATE.NOT_ALLOWED
+ ? {notAllowed: true}
+ : {}),
+ ...(edge?.state === EDGE_STATE.HOVERED ? {hovered: true} : {}),
+ },
+ ];
+ }
+ });
+ edges = [...edges, ...horEdges, ...verEdges];
+ });
+ });
+ return edges;
+};
+
+// get an array of all active edges present in the matrix
+export const getActiveEdges = (mat) => {
+ return getEdges(mat).filter((edge) => !edge?.notAllowed && !edge?.hovered);
+};