├── .gitignore
├── .vscode
└── settings.json
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
└── manifest.json
├── src
├── assets
│ ├── jp.png
│ ├── logo.png
│ └── preview.png
├── components
│ ├── App
│ │ ├── App.js
│ │ ├── index.js
│ │ ├── reducer.js
│ │ ├── style.css
│ │ └── tests
│ │ │ └── App.test.js
│ ├── Backdrop
│ │ ├── Backdrop.js
│ │ ├── index.js
│ │ └── style.css
│ ├── Board
│ │ ├── Board.js
│ │ ├── Sections
│ │ │ ├── SectionRow
│ │ │ │ ├── Section
│ │ │ │ │ ├── CellRow
│ │ │ │ │ │ ├── Cell
│ │ │ │ │ │ │ ├── Cell.js
│ │ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ │ └── style.css
│ │ │ │ │ │ ├── CellRow.js
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ └── style.css
│ │ │ │ │ ├── Section.js
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── style.css
│ │ │ │ ├── SectionRow.js
│ │ │ │ ├── index.js
│ │ │ │ └── style.css
│ │ │ ├── Sections.js
│ │ │ ├── index.js
│ │ │ └── style.css
│ │ ├── index.js
│ │ └── style.css
│ ├── Button
│ │ ├── Button.js
│ │ ├── index.js
│ │ └── style.css
│ ├── Circle
│ │ ├── Circle.js
│ │ ├── index.js
│ │ └── style.css
│ ├── Header
│ │ ├── header.js
│ │ ├── index.js
│ │ └── style.css
│ ├── Icons
│ │ ├── AlertTriangle
│ │ │ ├── AlertTriangle.js
│ │ │ ├── index.js
│ │ │ └── style.css
│ │ ├── Award
│ │ │ ├── Award.js
│ │ │ ├── index.js
│ │ │ └── style.css
│ │ ├── CheckCircle
│ │ │ ├── CheckCircle.js
│ │ │ ├── index.js
│ │ │ └── style.css
│ │ ├── Info
│ │ │ ├── Info.js
│ │ │ ├── index.js
│ │ │ └── style.css
│ │ ├── Stopwatch
│ │ │ ├── Stopwatch.js
│ │ │ ├── index.js
│ │ │ └── style.css
│ │ └── XCircle
│ │ │ ├── XCircle.js
│ │ │ ├── index.js
│ │ │ └── style.css
│ ├── Modal
│ │ ├── Modal.js
│ │ ├── index.js
│ │ └── style.css
│ ├── NewBoardForm
│ │ ├── ErrorMessage
│ │ │ ├── ErrorMessage.js
│ │ │ ├── index.js
│ │ │ └── style.css
│ │ ├── NewBoardForm.js
│ │ ├── index.js
│ │ └── style.css
│ ├── Spinner
│ │ ├── Spinner.js
│ │ ├── index.js
│ │ └── style.css
│ ├── StatusMessage
│ │ ├── StatusMessage.js
│ │ ├── index.js
│ │ └── style.css
│ ├── StepsLog
│ │ ├── LogsDetails
│ │ │ ├── LogsDetails.js
│ │ │ ├── SolutionStep
│ │ │ │ ├── SolutionStep.js
│ │ │ │ ├── index.js
│ │ │ │ └── style.css
│ │ │ ├── index.js
│ │ │ └── style.css
│ │ ├── StepLogs.js
│ │ ├── index.js
│ │ └── style.css
│ └── index.js
├── index.css
├── index.js
├── registerServiceWorker.js
└── services
│ └── Solver
│ ├── constants.js
│ ├── index.js
│ ├── logs.js
│ ├── solver.js
│ ├── tests
│ ├── config.test.js
│ ├── data.js
│ ├── solver.test.js
│ └── utils.test.js
│ └── utils.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true,
3 | "prettier.useTabs": true,
4 | "javascript.preferences.quoteStyle": "single",
5 | "prettier.singleQuote": true,
6 | "cSpell.words": [
7 | "ABCDEFGHI"
8 | ]
9 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## React Sudoku Solver. Author J.P. Solano
2 |
3 | This is a fresh attempt to port Peter Norvig's @norvig legendary Sudoku Solver from an hyper-efficient python base code
4 | to JS. See http://norvig.com/sudoku.html for his article and explanations.
5 |
6 | The latest js version that port Peter code was from 2014 (thanks to @einaregilsson /sudoku !)
7 |
8 | [LIVE DEMO HERE](http://jsolano.github.io/react-sudoku-solver)
9 |
10 | [Article in dev.to](https://dev.to/jsolano/building-a-react-app-to-solve-every-sudoku-puzzle-3c95)
11 |
12 | 
13 |
14 | ## General idea:
15 |
16 | Initially was just create a react app that used Peter Norvig's approach to solve any sudoku puzzle while collecting all steps to learn. But, Peter Norvig used a backtracking search strategy, and what it does is systematically try all possibilities until it hit one that works. In the search of a solution create temp steps that are not valid, making the learning part pointless.
17 |
18 | Then I did my research about others sudoku solving strategies, and there are more than 38 options! I was hook. See more here [https://www.sudokuwiki.org/sudoku.htm](https://www.sudokuwiki.org/sudoku.htm).
19 |
20 | But it comes with a caveats: you can produce many lines of code trying to implement some of this strategies and still won't solve all puzzles. (I learned this the hard way). So, here is a mixed approach: Create a react app that use human solving strategies (for learning purposes and because it's fun!) and only apply Peter Norvig's nuclear solver as the last option.
21 |
22 | ## List of Sudoku solver strategies:
23 |
24 | more detail here: [https://www.sudokuwiki.org/sudoku.htm](https://www.sudokuwiki.org/sudoku.htm)
25 |
26 | 1. Hidden Singles **SUPPORTED**
27 | 2. Naked Pairs/Triples **SUPPORTED**
28 | 3. Pointing Pairs **SUPPORTED**
29 | 4. Hidden Pairs/Triples
30 | 5. Naked/Hidden Quads
31 | 6. Box/Line Reduction
32 | 7. X-Wing (Tough Strategies)
33 | 8. Simple Colouring
34 | 9. Y-Wing
35 | 10. Swordfish
36 | 11. XYZ Wing
37 | 12. X-Cycles (Diabolical Strategies)
38 | 13. BUG
39 | 14. XY-Chain
40 | 15. 3D Medusa
41 | 16. Jellyfish
42 | 17. Unique Rectangles
43 | 18. SK Loops
44 | 19. Extended Unique Rect.
45 | 20. Hidden Unique Rect's
46 | 21. WXYZ Wing
47 | 22. Aligned Pair Exclusion
48 | 23. Exocet (Extreme Strategies)
49 | 24. Grouped X-Cycles
50 | 25. Empty Rectangles
51 | 26. Finned X-Wing
52 | 27. Finned Swordfish
53 | 28. Altern. Inference Chains
54 | 29. Sue-de-Coq
55 | 30. Digit Forcing Chains
56 | 31. Nishio Forcing Chains
57 | 32. Cell Forcing Chains
58 | 33. Unit Forcing Chains
59 | 34. Almost Locked Sets
60 | 35. Death Blossom
61 | 36. Pattern Overlay Method
62 | 37. Quad Forcing Chains
63 | 38. Bowman's Bingo
64 |
65 | Last strategy: Backtracking Search **SUPPORTED**
66 |
67 | Feel free to fork this project and open a PR with any improve or new strategy implementation.
68 |
69 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app).
70 |
71 | ## Next Steps
72 |
73 | **DONE** In the modal for Load a new Board, Add a button to select a random puzzle string.
74 |
75 | Below you will find some information on how to perform common tasks.
76 |
77 | ## Available Scripts
78 |
79 | In the project directory, you can run:
80 |
81 | Before anything:
82 |
83 | ### `npm install | yarn`
84 |
85 | After
86 |
87 | ### `npm|yarn start`
88 |
89 | Runs the app in the development mode.
90 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
91 |
92 | The page will reload if you make edits.
93 | You will also see any lint errors in the console.
94 |
95 | ### `npm|yarn test`
96 |
97 | Launches the test runner in the interactive watch mode.
98 | See the section about [running tests](#running-tests) for more information.
99 |
100 | ### `npm|yarn run build`
101 |
102 | Builds the app for production to the `build` folder.
103 | It correctly bundles React in production mode and optimizes the build for the best performance.
104 |
105 | The build is minified and the filenames include the hashes.
106 | Your app is ready to be deployed!
107 |
108 | See the section about [deployment](#deployment) for more information.
109 |
110 | ### `npm|yarn run eject`
111 |
112 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
113 |
114 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
115 |
116 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
117 |
118 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
119 |
120 | ## Supported Browsers
121 |
122 | By default, the generated project uses the latest version of React.
123 |
124 | You can refer [to the React documentation](https://reactjs.org/docs/react-dom.html#browser-support) for more information about supported browsers.
125 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "homepage": "http://jsolano.github.io/react-sudoku-solver",
3 | "name": "react-sudoku-solver",
4 | "version": "0.1.0",
5 | "private": true,
6 | "dependencies": {
7 | "react": "^16.13.1",
8 | "react-dom": "^16.13.1",
9 | "react-scripts": "2.0.3",
10 | "react-spring": "8.0.27"
11 | },
12 | "scripts": {
13 | "predeploy": "yarn run build",
14 | "deploy": "gh-pages -d build",
15 | "start": "react-scripts start",
16 | "build": "react-scripts build",
17 | "test": "react-scripts test --env=jsdom",
18 | "eject": "react-scripts eject"
19 | },
20 | "devDependencies": {
21 | "gh-pages": "^3.1.0"
22 | },
23 | "browserslist": [
24 | ">0.2%",
25 | "not dead",
26 | "not ie <= 11",
27 | "not op_mini all"
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsolano/react-sudoku-solver/02d1c5f7638488981b0cade236d3f55cde425950/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
18 |
19 |
23 |
24 |
25 |
29 |
30 |
31 |
40 | React Sudoku Solver
41 |
42 |
43 | You need to enable JavaScript to run this app.
44 |
45 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "React Sudoku Solver",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/src/assets/jp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsolano/react-sudoku-solver/02d1c5f7638488981b0cade236d3f55cde425950/src/assets/jp.png
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsolano/react-sudoku-solver/02d1c5f7638488981b0cade236d3f55cde425950/src/assets/logo.png
--------------------------------------------------------------------------------
/src/assets/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsolano/react-sudoku-solver/02d1c5f7638488981b0cade236d3f55cde425950/src/assets/preview.png
--------------------------------------------------------------------------------
/src/components/App/App.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useReducer } from 'react';
2 | import Board from '../Board/Board';
3 | import Button from '../Button/Button';
4 | import Circle from '../Circle/Circle';
5 | import StepsLog from '../StepsLog/StepLogs';
6 | import Modal from '../../components/Modal/Modal';
7 | import Header from '../Header/header';
8 | import NewBoardForm from '../../components/NewBoardForm/NewBoardForm';
9 | import StatusMessage from '../StatusMessage/StatusMessage';
10 | import { ACTIONS } from '../../services/Solver/constants';
11 | import Solver from '../../services/Solver/solver';
12 | import { appReducer, initialState } from './reducer';
13 | import './style.css';
14 |
15 | const app = (props) => {
16 | const [state, dispatch] = useReducer(appReducer, initialState);
17 |
18 | const {
19 | isSolving,
20 | initialBoardParsed,
21 | initialBoardState,
22 | initialBoardStatus,
23 | solveBoardState,
24 | solveBoardStatus,
25 | solveBoardAbort,
26 | solutionSteps,
27 | newBoardString,
28 | timerSolveBoard,
29 | timeElapsed,
30 | openModal,
31 | modalError,
32 | } = state;
33 |
34 | useEffect(() => {
35 | const raw = localStorage.getItem('data');
36 | dispatch({ type: ACTIONS.RELOAD, payload: JSON.parse(raw) });
37 | }, []);
38 |
39 | useEffect(() => {
40 | localStorage.setItem('data', JSON.stringify(state));
41 | }, [state]);
42 |
43 | const solverHandler = () => {
44 | dispatch({ type: ACTIONS.SOLVE });
45 |
46 | Solver(initialBoardParsed).then((result) => {
47 | dispatch({ type: ACTIONS.SUCCESS, result: result });
48 | });
49 | };
50 |
51 | const onLearnClick = () => {
52 | console.log('onLearnClick clicked!');
53 | };
54 |
55 | return (
56 |
57 |
58 |
59 |
60 |
61 |
62 |
dispatch({ type: ACTIONS.OPEN })}
65 | label="Load New Board"
66 | />
67 |
72 |
73 | dispatch({ type: ACTIONS.DEFAULT })}
76 | label="Use Default Board"
77 | />
78 |
79 |
83 |
84 |
85 |
86 |
87 |
88 |
94 |
95 |
96 |
97 |
98 | dispatch({ type: ACTIONS.CLEAR })}
101 | label="Clear"
102 | />
103 |
104 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
dispatch({ type: ACTIONS.CLOSE })}
124 | >
125 | dispatch({ type: ACTIONS.CLOSE })}
127 | changed={(e) =>
128 | dispatch({
129 | type: ACTIONS.SET,
130 | field: 'newBoardString',
131 | value: e.target.value,
132 | })
133 | }
134 | click={() => dispatch({ type: ACTIONS.CHANGE })}
135 | currentStringBoard={newBoardString}
136 | error={modalError}
137 | randomPuzzle={() => dispatch({ type: ACTIONS.RANDOM })}
138 | />
139 |
140 |
141 | );
142 | };
143 |
144 | export default app;
145 |
--------------------------------------------------------------------------------
/src/components/App/index.js:
--------------------------------------------------------------------------------
1 | export * from './App';
2 | export * from './reducer';
3 |
--------------------------------------------------------------------------------
/src/components/App/reducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | STATUS,
3 | ACTIONS,
4 | initialSudokuString,
5 | emptySudokuString,
6 | } from '../../services/Solver/constants';
7 | import {
8 | getBoardState,
9 | getRandomPuzzle,
10 | isSolved,
11 | stringBoardValidation,
12 | } from '../../services/Solver/utils';
13 | import { parseGrid } from '../../services/Solver/solver';
14 | import { resetLog } from '../../services/Solver/logs';
15 |
16 | const reset = (state) => {
17 | const emptyBoardParsed = parseGrid(emptySudokuString);
18 | return {
19 | ...state,
20 | initialBoardStatus: STATUS.UNKNOWN,
21 | solveBoardStatus: STATUS.UNKNOWN,
22 | solveBoardAbort: STATUS.UNKNOWN,
23 | solutionSteps: resetLog('solutionSteps'),
24 | solveBoardState: getBoardState(emptyBoardParsed),
25 | openModal: false,
26 | timerSolveBoard: STATUS.UNKNOWN,
27 | timeElapsed: 0,
28 | modalError: '',
29 | };
30 | };
31 |
32 | const change = (state, newBoardString) => {
33 | const newBoardParsed = parseGrid(newBoardString);
34 | return {
35 | ...reset(state),
36 | initialBoardStatus: isSolved(newBoardParsed) ? STATUS.SOLVE : '',
37 | initialBoardParsed: newBoardParsed,
38 | initialBoardState: getBoardState(newBoardParsed),
39 | currentBoardString: newBoardString,
40 | newBoardString: '',
41 | };
42 | };
43 |
44 | export const appReducer = (state, action) => {
45 | switch (action.type) {
46 | case ACTIONS.RELOAD: {
47 | return action.payload;
48 | }
49 | case ACTIONS.SET: {
50 | return {
51 | ...state,
52 | [action.field]: action.value,
53 | };
54 | }
55 | case ACTIONS.OPEN: {
56 | return {
57 | ...state,
58 | modalError: '',
59 | newBoardString: '',
60 | openModal: true,
61 | };
62 | }
63 | case ACTIONS.CLOSE: {
64 | return {
65 | ...state,
66 | openModal: false,
67 | newBoardString: '',
68 | modalError: '',
69 | };
70 | }
71 | case ACTIONS.SOLVE: {
72 | return {
73 | ...state,
74 | isSolving: true,
75 | };
76 | }
77 | case ACTIONS.SUCCESS: {
78 | const initialBoardParsed = parseGrid(state.currentBoardString);
79 | const result = action.result;
80 | const resultBoardParsed = result.board;
81 | return {
82 | ...state,
83 | isSolving: false,
84 | solveBoardStatus: result.status,
85 | initialBoardStatus: STATUS.UNKNOWN,
86 | solveBoardAbort: result.abort ? STATUS.ABORT : STATUS.UNKNOWN,
87 | timerSolveBoard: result.abort ? STATUS.ABORT : STATUS.TIMER,
88 | timeElapsed: result.timer.toFixed(2),
89 | solveBoardState: getBoardState(resultBoardParsed),
90 | solutionSteps: result.solutionSteps,
91 | initialBoardParsed: initialBoardParsed,
92 | initialBoardState: getBoardState(initialBoardParsed),
93 | };
94 | }
95 | case ACTIONS.CHANGE: {
96 | const isValidString = stringBoardValidation(state.newBoardString);
97 | if (isValidString === true) {
98 | return change(state, state.newBoardString);
99 | } else {
100 | const validationError = isValidString;
101 | return {
102 | ...state,
103 | 'modalError': validationError,
104 | };
105 | }
106 | }
107 | case ACTIONS.RANDOM: {
108 | const randomPuzzleString = getRandomPuzzle();
109 | return change(state, randomPuzzleString);
110 | }
111 | case ACTIONS.CLEAR: {
112 | const initialBoardParsed = parseGrid(state.currentBoardString);
113 | return {
114 | ...reset(state),
115 | initialBoardParsed: initialBoardParsed,
116 | initialBoardState: getBoardState(initialBoardParsed),
117 | };
118 | }
119 | case ACTIONS.DEFAULT: {
120 | const initialBoardParsed = parseGrid(initialSudokuString);
121 | return {
122 | ...reset(state),
123 | initialBoardParsed: initialBoardParsed,
124 | initialBoardState: getBoardState(initialBoardParsed),
125 | currentBoardString: initialSudokuString,
126 | };
127 | }
128 | default: {
129 | throw new Error(`Unhandled type: ${action.type}`);
130 | }
131 | }
132 | };
133 |
134 | const initialBoardParsed = parseGrid(initialSudokuString);
135 | const emptyBoardParsed = parseGrid(emptySudokuString);
136 |
137 | export const initialState = {
138 | currentBoardString: initialSudokuString,
139 | initialBoardParsed: initialBoardParsed,
140 | initialBoardState: getBoardState(initialBoardParsed),
141 | initialBoardStatus: STATUS.UNKNOWN,
142 | solveBoardState: getBoardState(emptyBoardParsed),
143 | solveBoardStatus: STATUS.UNKNOWN,
144 | solveBoardAbort: false,
145 | isSolving: false,
146 | solutionSteps: [],
147 | newBoardString: '',
148 | timerSolveBoard: STATUS.UNKNOWN,
149 | timeElapsed: 0,
150 | openModal: false,
151 | modalError: '',
152 | };
153 |
--------------------------------------------------------------------------------
/src/components/App/style.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | font-family: SourceSansPro, 'Helvetica Neue', Helvetica, Arial,
4 | 'Lucida Grande', sans-serif;
5 | }
6 |
7 | .App-status-message {
8 | line-height: 1.5;
9 | display: inline-block;
10 | margin-left: 10px;
11 | }
12 |
13 | .App-message-row {
14 | text-align: left;
15 | padding-left: 55px;
16 | height: 50px;
17 | }
18 |
19 | .App-status-abort-message {
20 | display: inline-block;
21 | font-size: 13px;
22 | width: 75%;
23 | padding-left: 15px;
24 | font-weight: 500;
25 | text-align: left;
26 | }
27 |
28 | .App-icon {
29 | font-size: 1.5em;
30 | display: inline-block;
31 | }
32 |
33 | .App-icon .invalid {
34 | width: 30px;
35 | }
36 |
37 | .App-body {
38 | background-color: #f3f5f8;
39 | font-size: large;
40 | padding-left: 16px;
41 | display: flex;
42 | height: 100vh;
43 | }
44 |
45 | .App-container {
46 | display: flex;
47 | flex-direction: row;
48 | }
49 |
50 | @media (max-width: 800px) {
51 | .App-container {
52 | flex-direction: column;
53 | }
54 | }
55 |
56 | .App-game-panel {
57 | display: inline-block;
58 | padding-top: 25px;
59 | padding-bottom: 25px;
60 | width: 30%;
61 | margin-top: 1em;
62 | margin-bottom: 2em;
63 | margin-right: 1em;
64 | background-color: #ffffff;
65 | border: 1px solid #eee;
66 | box-shadow: 0 2px 2px #ccc;
67 | }
68 |
69 | @media (max-width: 800px) {
70 | .App-game-panel {
71 | width: 98%;
72 | }
73 | }
74 |
75 | .App-game-panel .buttons-row {
76 | margin-top: 20px;
77 | }
78 |
79 | @keyframes App-logo-spin {
80 | from {
81 | transform: rotate(0deg);
82 | }
83 | to {
84 | transform: rotate(360deg);
85 | }
86 | }
87 |
88 | .marginLeft10 {
89 | margin-left: 10px;
90 | }
91 |
92 | .marginBottom10 {
93 | margin-bottom: 10px;
94 | }
95 |
96 | .marginTop10 {
97 | margin-top: 10px;
98 | }
99 |
100 | .marginTop15 {
101 | margin-top: 15px;
102 | }
103 |
104 | .center-me {
105 | margin: 0 auto;
106 | }
107 |
--------------------------------------------------------------------------------
/src/components/App/tests/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from '../App';
4 |
5 | describe(' ', () => {
6 | it('renders without crashing', () => {
7 | const div = document.createElement('div');
8 | ReactDOM.render( , div);
9 | ReactDOM.unmountComponentAtNode(div);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/src/components/Backdrop/Backdrop.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './style.css';
3 |
4 | const backdrop = (props) =>
5 | props.show ?
: null;
6 |
7 | export default backdrop;
8 |
--------------------------------------------------------------------------------
/src/components/Backdrop/index.js:
--------------------------------------------------------------------------------
1 | export * from './Backdrop';
2 |
--------------------------------------------------------------------------------
/src/components/Backdrop/style.css:
--------------------------------------------------------------------------------
1 | .backdrop {
2 | width: 100%;
3 | height: 100%;
4 | position: fixed;
5 | z-index: 100;
6 | left: 0;
7 | top: 0;
8 | background-color: rgba(0, 0, 0, 0.5);
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/Board/Board.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './style.css';
3 | import Sections from './Sections/Sections';
4 |
5 | const board = (props) => {
6 | let sections = null;
7 |
8 | if (props.board) {
9 | sections = (
10 |
11 |
15 |
16 | );
17 | }
18 |
19 | return {sections}
;
20 | };
21 |
22 | export default board;
23 |
--------------------------------------------------------------------------------
/src/components/Board/Sections/SectionRow/Section/CellRow/Cell/Cell.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './style.css';
3 |
4 | const cell = (props) => {
5 | const cellValue = props.cell.value === '.' ? '\u00A0' : props.cell.value;
6 | return (
7 |
10 | )
11 | }
12 |
13 | export default cell;
--------------------------------------------------------------------------------
/src/components/Board/Sections/SectionRow/Section/CellRow/Cell/index.js:
--------------------------------------------------------------------------------
1 | export * from './Cell';
--------------------------------------------------------------------------------
/src/components/Board/Sections/SectionRow/Section/CellRow/Cell/style.css:
--------------------------------------------------------------------------------
1 | .game-cell {
2 | flex-basis: 5%;
3 | box-sizing: border-box;
4 | position: relative;
5 | border-right: 1px solid #bec6d4;
6 | border-bottom: 1px solid #bec6d4;
7 | cursor: pointer;
8 | transform: translateZ(0);
9 | padding: 0;
10 | margin: 0;
11 | border-collapse: collapse;
12 | border-spacing: 0;
13 | display: inline-block;
14 | width: 33.3%;
15 | height: 33.3%;
16 | }
17 |
18 | .game-cell .cell-value {
19 | font-weight: 400;
20 | color: #000;
21 | cursor: default;
22 | }
23 |
24 | .cell-value {
25 | width: 100%;
26 | height: 100%;
27 | font-weight: 100;
28 | color: #666;
29 | text-align: center;
30 | }
--------------------------------------------------------------------------------
/src/components/Board/Sections/SectionRow/Section/CellRow/CellRow.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './style.css';
3 | import Cell from './Cell/Cell';
4 |
5 | const cellRow = (props) => {
6 | return props.cells.map((cell, index) => {
7 | return | ;
8 | });
9 | };
10 |
11 | export default cellRow;
12 |
--------------------------------------------------------------------------------
/src/components/Board/Sections/SectionRow/Section/CellRow/index.js:
--------------------------------------------------------------------------------
1 | export * from './CellRow';
--------------------------------------------------------------------------------
/src/components/Board/Sections/SectionRow/Section/CellRow/style.css:
--------------------------------------------------------------------------------
1 | .section-row {
2 | padding: 0;
3 | margin: 0;
4 | width: 100%;
5 | }
--------------------------------------------------------------------------------
/src/components/Board/Sections/SectionRow/Section/Section.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './style.css';
3 | import CellRow from './CellRow/CellRow';
4 |
5 | const section = (props) => {
6 | return props.cellRows.map((cellRow, index) => {
7 | return (
8 |
9 |
14 |
15 | );
16 | });
17 | };
18 |
19 | export default section;
20 |
--------------------------------------------------------------------------------
/src/components/Board/Sections/SectionRow/Section/index.js:
--------------------------------------------------------------------------------
1 | export * from './Section';
--------------------------------------------------------------------------------
/src/components/Board/Sections/SectionRow/Section/style.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsolano/react-sudoku-solver/02d1c5f7638488981b0cade236d3f55cde425950/src/components/Board/Sections/SectionRow/Section/style.css
--------------------------------------------------------------------------------
/src/components/Board/Sections/SectionRow/SectionRow.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './style.css';
3 | import Section from './Section/Section';
4 |
5 | const sectionRow = (props) => {
6 | return props.sectionRow.map((section) => {
7 | return (
8 |
9 |
13 |
14 | );
15 | });
16 | };
17 |
18 | export default sectionRow;
19 |
--------------------------------------------------------------------------------
/src/components/Board/Sections/SectionRow/index.js:
--------------------------------------------------------------------------------
1 | export * from './SectionRow';
--------------------------------------------------------------------------------
/src/components/Board/Sections/SectionRow/style.css:
--------------------------------------------------------------------------------
1 | .game-section {
2 | border: 2px solid #344861;
3 | height: 10%;
4 | width: 23%;
5 | display: inline-block;
6 | }
7 |
--------------------------------------------------------------------------------
/src/components/Board/Sections/Sections.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './style.css';
3 | import SectionRow from './SectionRow/SectionRow';
4 |
5 | const sections = (props) => {
6 | return props.sections.map((sectionRow, sectionIndex) => {
7 | return (
8 |
12 |
16 |
17 | );
18 | });
19 | };
20 |
21 | export default sections;
22 |
--------------------------------------------------------------------------------
/src/components/Board/Sections/index.js:
--------------------------------------------------------------------------------
1 | export * from './Sections';
--------------------------------------------------------------------------------
/src/components/Board/Sections/style.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsolano/react-sudoku-solver/02d1c5f7638488981b0cade236d3f55cde425950/src/components/Board/Sections/style.css
--------------------------------------------------------------------------------
/src/components/Board/index.js:
--------------------------------------------------------------------------------
1 | export * from './Board';
--------------------------------------------------------------------------------
/src/components/Board/style.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsolano/react-sudoku-solver/02d1c5f7638488981b0cade236d3f55cde425950/src/components/Board/style.css
--------------------------------------------------------------------------------
/src/components/Button/Button.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 | import Spinner from '../Spinner/Spinner';
3 | import { useSpring, animated } from 'react-spring';
4 | import './style.css';
5 |
6 | const button = (props) => {
7 | /* showSpinner is used to stay in the "isSpinning state" a bit longer
8 | to avoid loading flashes if the loading state is too short. */
9 | const [showSpinner, setShowSpinner] = useState(false);
10 |
11 | useEffect(() => {
12 | if (props.isSpinning) {
13 | setShowSpinner(true);
14 | }
15 |
16 | // Show loader a bits longer to avoid loading flash
17 | if (!props.isSpinning && showSpinner) {
18 | const timeout = setTimeout(() => {
19 | setShowSpinner(false);
20 | }, 300);
21 |
22 | return () => {
23 | clearTimeout(timeout);
24 | };
25 | }
26 | }, [props.isSpinning, showSpinner]);
27 |
28 | const [width, setWidth] = useState(0);
29 | const [height, setHeight] = useState(0);
30 | const ref = useRef(null);
31 |
32 | useEffect(() => {
33 | if (ref.current && ref.current.getBoundingClientRect().width) {
34 | setWidth(ref.current.getBoundingClientRect().width);
35 | }
36 | if (ref.current && ref.current.getBoundingClientRect().height) {
37 | setHeight(ref.current.getBoundingClientRect().height);
38 | }
39 | }, [props.classes]);
40 |
41 | const fadeOutProps = useSpring({ opacity: showSpinner ? 1 : 0 });
42 | const fadeInProps = useSpring({ opacity: showSpinner ? 0 : 1 });
43 |
44 | return (
45 |
59 | {showSpinner ? (
60 |
61 |
62 |
63 | ) : (
64 | {props.label}
65 | )}
66 |
67 | );
68 | };
69 |
70 | export default button;
71 |
--------------------------------------------------------------------------------
/src/components/Button/index.js:
--------------------------------------------------------------------------------
1 | export * from './Button';
--------------------------------------------------------------------------------
/src/components/Button/style.css:
--------------------------------------------------------------------------------
1 | .btn {
2 | display: inline-block;
3 | position: relative;
4 | margin-bottom: 20px;
5 | padding: 15px 24px 17px;
6 | box-sizing: border-box;
7 | background: #264653;
8 | font-family: SourceSansPro, 'Helvetica Neue', Helvetica, Arial,
9 | 'Lucida Grande', sans-serif;
10 | color: #fff;
11 | font-size: 19px;
12 | font-size: 1.0556rem;
13 | line-height: 19px;
14 | font-weight: 700;
15 | text-align: center;
16 | text-decoration: none;
17 | letter-spacing: 0.25px;
18 | cursor: pointer;
19 | border: 1px solid #009845;
20 | border-radius: 0;
21 | transition: background 0.2s ease-in-out, color 0.2s ease-in-out,
22 | border 0.2s ease-in-out;
23 | }
24 |
25 | .btn-small {
26 | font-size: 12px;
27 | font-size: 1.0556rem;
28 | font-weight: 400;
29 | padding: 10px 10px 10px;
30 | }
31 |
32 | .btn.btn-close {
33 | background: #34507e;
34 | }
35 |
--------------------------------------------------------------------------------
/src/components/Circle/Circle.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './style.css';
3 |
4 | const circle = (props) => {
5 | return {props.label}
6 | }
7 |
8 | export default circle;
--------------------------------------------------------------------------------
/src/components/Circle/index.js:
--------------------------------------------------------------------------------
1 | export * from './Circle';
--------------------------------------------------------------------------------
/src/components/Circle/style.css:
--------------------------------------------------------------------------------
1 | .circle {
2 | width: 40px;
3 | height: 40px;
4 | text-align: center;
5 | font-size: 28px;
6 | line-height: 1.428571429;
7 | border-radius: 35px;
8 | color: #ffffff;
9 | background-color: #428bca;
10 | border-color: #48a50a;
11 | font-weight: 800;
12 | background: #2a9d8f;
13 | }
14 |
15 | .circle.lg {
16 | width: 50px;
17 | height: 50px;
18 | padding: 10px 16px;
19 | font-size: 18px;
20 | line-height: 1.33;
21 | border-radius: 25px;
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/Header/header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './style.css';
3 | import logo from '../../assets/logo.png';
4 | import me from '../../assets/jp.png';
5 |
6 | const header = (props) => {
7 | return (
8 |
9 |
10 | React Sudoku Solver
11 | J.P.
12 |
13 |
14 | );
15 | };
16 |
17 | export default header;
18 |
--------------------------------------------------------------------------------
/src/components/Header/index.js:
--------------------------------------------------------------------------------
1 | export * from './header';
2 |
--------------------------------------------------------------------------------
/src/components/Header/style.css:
--------------------------------------------------------------------------------
1 | .App-header {
2 | width: 100%;
3 | max-width: 1504px;
4 | margin-left: auto;
5 | margin-right: auto;
6 | padding-left: 16px;
7 | padding-right: 16px;
8 | box-sizing: border-box;
9 | border-bottom: 1px solid rgba(198, 206, 217, 0.5);
10 | display: flex;
11 | align-items: center;
12 | }
13 |
14 | .App-title {
15 | flex: 1;
16 | font-size: 1.5em;
17 | align-items: center;
18 | padding-left: 50px;
19 | padding-right: 50px;
20 | }
21 |
22 | @media (max-width: 800px) {
23 | .App-title {
24 | font-size: 1.1em;
25 | }
26 | }
27 |
28 | .App-logo {
29 | height: 40px;
30 | padding-left: 10px;
31 | }
32 |
33 | .App-author {
34 | padding-right: 16px;
35 | }
36 |
37 | .App-author-image {
38 | height: 45px;
39 | }
40 |
--------------------------------------------------------------------------------
/src/components/Icons/AlertTriangle/AlertTriangle.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './style.css';
3 |
4 | const alertTriangle = (props) => {
5 | return (
6 |
21 | );
22 | };
23 |
24 | export default alertTriangle;
25 |
--------------------------------------------------------------------------------
/src/components/Icons/AlertTriangle/index.js:
--------------------------------------------------------------------------------
1 | export * from './AlertTriangle';
2 |
--------------------------------------------------------------------------------
/src/components/Icons/AlertTriangle/style.css:
--------------------------------------------------------------------------------
1 | .abort-board {
2 | color: #e2e221;
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/Icons/Award/Award.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './style.css';
3 |
4 | const award = (props) => {
5 | return (
6 |
22 | );
23 | };
24 |
25 | export default award;
26 |
--------------------------------------------------------------------------------
/src/components/Icons/Award/index.js:
--------------------------------------------------------------------------------
1 | export * from './Award';
2 |
--------------------------------------------------------------------------------
/src/components/Icons/Award/style.css:
--------------------------------------------------------------------------------
1 | .award-board {
2 | color: #ffd700;
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/Icons/CheckCircle/CheckCircle.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './style.css';
3 |
4 | const checkCircle = (props) => {
5 | return (
6 |
21 | );
22 | };
23 |
24 | export default checkCircle;
25 |
--------------------------------------------------------------------------------
/src/components/Icons/CheckCircle/index.js:
--------------------------------------------------------------------------------
1 | export * from './CheckCircle';
2 |
--------------------------------------------------------------------------------
/src/components/Icons/CheckCircle/style.css:
--------------------------------------------------------------------------------
1 | .solved-board {
2 | color: #2a9d8f;
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/Icons/Info/Info.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './style.css';
3 |
4 | const info = (props) => {
5 | return (
6 |
21 | );
22 | };
23 |
24 | export default info;
25 |
--------------------------------------------------------------------------------
/src/components/Icons/Info/index.js:
--------------------------------------------------------------------------------
1 | export * from './Info';
2 |
--------------------------------------------------------------------------------
/src/components/Icons/Info/style.css:
--------------------------------------------------------------------------------
1 | .bi {
2 | display: inline-block;
3 | vertical-align: text-bottom;
4 | }
5 |
6 | .bi .bi-info-circle-fill {
7 | color: #0d6efd;
8 | }
9 |
10 | .info {
11 | color: #0d6efd;
12 | }
13 |
--------------------------------------------------------------------------------
/src/components/Icons/Stopwatch/Stopwatch.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './style.css';
3 |
4 | const stopWatch = (props) => {
5 | return (
6 |
21 | );
22 | };
23 |
24 | export default stopWatch;
25 |
--------------------------------------------------------------------------------
/src/components/Icons/Stopwatch/index.js:
--------------------------------------------------------------------------------
1 | export * from './Stopwatch';
2 |
--------------------------------------------------------------------------------
/src/components/Icons/Stopwatch/style.css:
--------------------------------------------------------------------------------
1 | .timer-solve {
2 | color: blue;
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/Icons/XCircle/XCircle.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './style.css';
3 |
4 | const xCircle = (props) => {
5 | return (
6 |
21 | );
22 | };
23 |
24 | export default xCircle;
25 |
--------------------------------------------------------------------------------
/src/components/Icons/XCircle/index.js:
--------------------------------------------------------------------------------
1 | export * from './XCircle';
2 |
--------------------------------------------------------------------------------
/src/components/Icons/XCircle/style.css:
--------------------------------------------------------------------------------
1 | .unsolved-board {
2 | color: red;
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/Modal/Modal.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './style.css';
3 | import Backdrop from '../../components/Backdrop/Backdrop';
4 |
5 | const modal = (props) => (
6 |
7 |
8 |
15 | {props.children}
16 |
17 |
18 | );
19 |
20 | export default modal;
21 |
--------------------------------------------------------------------------------
/src/components/Modal/index.js:
--------------------------------------------------------------------------------
1 | export * from './Modal';
2 |
--------------------------------------------------------------------------------
/src/components/Modal/style.css:
--------------------------------------------------------------------------------
1 | .Modal {
2 | position: fixed;
3 | z-index: 500;
4 | background-color: white;
5 | width: 70%;
6 | border: 1px solid #ccc;
7 | box-shadow: 1px 1px 1px black;
8 | padding: 16px;
9 | left: 15%;
10 | top: 30%;
11 | box-sizing: border-box;
12 | transition: all 0.3s ease-out;
13 | }
14 |
15 | @media (min-width: 600px) {
16 | .Modal {
17 | width: 500px;
18 | left: calc(50% - 250px);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/NewBoardForm/ErrorMessage/ErrorMessage.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './style.css';
3 | import { ERRORS } from './../../../services/Solver/constants';
4 | import AlertTriangle from '../../Icons/AlertTriangle/AlertTriangle';
5 |
6 | const errorMessage = (props) => {
7 | switch (props.error) {
8 | case ERRORS.INVALID_LENGTH: {
9 | return (
10 |
11 |
12 |
13 | The string length must be equal to 81
14 |
15 |
16 | );
17 | }
18 | case ERRORS.EMPTY_VALUE: {
19 | return (
20 |
21 |
22 |
Enter a valid string
23 |
24 | );
25 | }
26 | case ERRORS.INVALID_VALUE: {
27 | return (
28 |
29 |
30 |
31 | Valid values must be . and numbers in the range 1-9
32 |
33 |
34 | );
35 | }
36 | default: {
37 | return
;
38 | }
39 | }
40 | };
41 |
42 | export default errorMessage;
43 |
--------------------------------------------------------------------------------
/src/components/NewBoardForm/ErrorMessage/index.js:
--------------------------------------------------------------------------------
1 | export * from './ErrorMessage';
2 |
--------------------------------------------------------------------------------
/src/components/NewBoardForm/ErrorMessage/style.css:
--------------------------------------------------------------------------------
1 | .new-board .errors {
2 | display: inline-block;
3 | min-width: 70%;
4 | max-width: 70%;
5 | }
6 |
--------------------------------------------------------------------------------
/src/components/NewBoardForm/NewBoardForm.js:
--------------------------------------------------------------------------------
1 | import React, { useRef, useEffect } from 'react';
2 | import './style.css';
3 | import Button from '../../components/Button/Button';
4 | import ErrorMessage from './ErrorMessage/ErrorMessage';
5 |
6 | const newBoardForm = (props) => {
7 | const inputRef = useRef(null);
8 |
9 | useEffect(() => {
10 | inputRef.current.focus();
11 | });
12 |
13 | return (
14 |
15 |
Enter a Sudoku string board
16 |
21 |
Valid strings could contain dots and numbers
22 |
30 |
31 |
32 |
33 |
38 |
43 |
44 |
45 |
46 | );
47 | };
48 |
49 | export default newBoardForm;
50 |
--------------------------------------------------------------------------------
/src/components/NewBoardForm/index.js:
--------------------------------------------------------------------------------
1 | export * from './NewBoardForm';
2 |
--------------------------------------------------------------------------------
/src/components/NewBoardForm/style.css:
--------------------------------------------------------------------------------
1 | .action-buttons {
2 | text-align: right;
3 | display: inline-block;
4 | }
5 |
6 | input {
7 | width: 100%;
8 | display: inline;
9 | min-height: 35px;
10 | padding: 5px 12px;
11 | font-size: 16px;
12 | /* line-height: 1.5; */
13 | color: #555555;
14 | vertical-align: middle;
15 | background-color: #ffffff;
16 | border: 1px solid #cccccc;
17 | border-radius: 2px;
18 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
19 | z-index: 2000;
20 |
21 | text-rendering: auto;
22 | color: -internal-light-dark(black, white);
23 | letter-spacing: normal;
24 | word-spacing: normal;
25 | text-transform: none;
26 | text-indent: 0px;
27 | text-shadow: none;
28 | display: inline-block;
29 | text-align: start;
30 | background-color: -internal-light-dark(rgb(255, 255, 255), rgb(59, 59, 59));
31 | cursor: text;
32 | margin: 0em;
33 | font: 400 13.3333px Arial;
34 | padding: 1px 2px;
35 | border-width: 2px;
36 | border-style: inset;
37 | border-color: -internal-light-dark(rgb(118, 118, 118), rgb(195, 195, 195));
38 | border-image: initial;
39 | }
40 |
41 | input::placeholder {
42 | padding: 10px;
43 | }
44 |
45 | .board-text-input {
46 | background: #ffffff;
47 | border-radius: 0px;
48 | background-clip: padding-box;
49 | box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.1);
50 | box-shadow: inset 0px 0px 0px rgba(0, 0, 0, 0.5);
51 | border: solid 1px #c4cfda;
52 | margin-bottom: 12px;
53 | }
54 |
55 | .new-board .pick-one {
56 | color: rgb(85, 26, 139);
57 | cursor: pointer;
58 | text-decoration: underline;
59 | }
60 |
--------------------------------------------------------------------------------
/src/components/Spinner/Spinner.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './style.css';
3 |
4 | const spinner = (props) => {
5 | return
;
6 | };
7 |
8 | export default spinner;
9 |
--------------------------------------------------------------------------------
/src/components/Spinner/index.js:
--------------------------------------------------------------------------------
1 | export * from './Spinner';
2 |
--------------------------------------------------------------------------------
/src/components/Spinner/style.css:
--------------------------------------------------------------------------------
1 | @keyframes load {
2 | 0% {
3 | transform: rotate(0deg);
4 | }
5 | 100% {
6 | transform: rotate(360deg);
7 | }
8 | }
9 |
10 | .spinner {
11 | border: 4px solid rgba(255, 255, 255, 0.2);
12 | border-left: 4px solid;
13 | animation: load 1s infinite linear;
14 | border-radius: 50%;
15 | width: 25px;
16 | height: 25px;
17 | margin-top: -5px;
18 | margin-left: 5px;
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/StatusMessage/StatusMessage.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './style.css';
3 | import CheckCircle from '../Icons/CheckCircle/CheckCircle';
4 | import XCircle from '../Icons/XCircle/XCircle';
5 | import AlertTriangle from '../Icons/AlertTriangle/AlertTriangle';
6 | import Info from '../Icons/Info/Info';
7 | import StopWatch from '../Icons/Stopwatch/Stopwatch';
8 | import { STATUS } from '../../services/Solver/constants';
9 |
10 | const statusMessage = (props) => {
11 | switch (props.status) {
12 | case STATUS.VALID: {
13 | return (
14 |
15 |
16 |
This Board is Valid
17 |
18 | );
19 | }
20 | case STATUS.INVALID: {
21 | return (
22 |
23 |
24 |
This Board is unsolved
25 |
26 | );
27 | }
28 | case STATUS.ABORT: {
29 | return (
30 |
31 |
32 |
33 | The processing is taking more time than expected and maybe cannot be
34 | solved with the current version. Please try another board.
35 |
36 |
37 | );
38 | }
39 | case STATUS.TIMER: {
40 | return (
41 |
42 |
43 |
44 | Time elapsed: {props.timeElapsed} ms
45 |
46 |
47 | );
48 | }
49 | case STATUS.SOLVE: {
50 | return (
51 |
52 |
53 |
54 |
This Board is Valid
55 |
56 |
57 |
58 |
59 | After parsing the initial board and looking for possible values
60 | for every cell, the puzzle is solved. So, there's not too much to
61 | do here.
62 |
63 |
64 |
65 | );
66 | }
67 | default: {
68 | return
;
69 | }
70 | }
71 | };
72 |
73 | export default statusMessage;
74 |
--------------------------------------------------------------------------------
/src/components/StatusMessage/index.js:
--------------------------------------------------------------------------------
1 | export * from './StatusMessage';
2 |
--------------------------------------------------------------------------------
/src/components/StatusMessage/style.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsolano/react-sudoku-solver/02d1c5f7638488981b0cade236d3f55cde425950/src/components/StatusMessage/style.css
--------------------------------------------------------------------------------
/src/components/StepsLog/LogsDetails/LogsDetails.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './style.css';
3 | import SolutionStep from './SolutionStep/SolutionStep';
4 |
5 | const logsDetails = (props) => {
6 | if (props.stepsLog) {
7 | return props.stepsLog.map((stepRow, stepIndex) => {
8 | return (
9 |
10 |
14 |
15 | );
16 | });
17 | } else {
18 | return
;
19 | }
20 | };
21 |
22 | export default logsDetails;
23 |
--------------------------------------------------------------------------------
/src/components/StepsLog/LogsDetails/SolutionStep/SolutionStep.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './style.css';
3 |
4 | const solutionStep = (props) => {
5 | return (
6 |
7 |
8 | Step {props.solutionStepNumber}: {props.solutionStep.method}
9 |
10 |
{props.solutionStep.msg}
11 |
12 | );
13 | };
14 |
15 | export default solutionStep;
16 |
--------------------------------------------------------------------------------
/src/components/StepsLog/LogsDetails/SolutionStep/index.js:
--------------------------------------------------------------------------------
1 | export * from './SolutionStep';
2 |
--------------------------------------------------------------------------------
/src/components/StepsLog/LogsDetails/SolutionStep/style.css:
--------------------------------------------------------------------------------
1 | .log-detail-row {
2 | border-top: 1px solid black;
3 | background: white;
4 | padding-bottom: 5px;
5 | }
6 |
7 | .log-detail-row .title {
8 | text-align: left;
9 | font-size: 13px;
10 | padding-left: 5px;
11 | padding-right: 5px;
12 | font-weight: bold;
13 | }
14 |
15 | .log-detail-row .details {
16 | text-align: left;
17 | font-size: 13px;
18 | padding-top: 5px;
19 | padding-left: 5px;
20 | padding-right: 5px;
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/StepsLog/LogsDetails/index.js:
--------------------------------------------------------------------------------
1 | export * from './LogsDetails';
--------------------------------------------------------------------------------
/src/components/StepsLog/LogsDetails/style.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsolano/react-sudoku-solver/02d1c5f7638488981b0cade236d3f55cde425950/src/components/StepsLog/LogsDetails/style.css
--------------------------------------------------------------------------------
/src/components/StepsLog/StepLogs.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './style.css';
3 | import LogsDetails from './LogsDetails/LogsDetails';
4 |
5 | const stepsLog = (props) => {
6 | return (
7 |
8 |
Solution Steps
9 |
10 | Here you can view the logical steps used to solve the board
11 |
12 |
13 |
14 | );
15 | };
16 |
17 | export default stepsLog;
18 |
--------------------------------------------------------------------------------
/src/components/StepsLog/index.js:
--------------------------------------------------------------------------------
1 | export * from './StepLogs';
--------------------------------------------------------------------------------
/src/components/StepsLog/style.css:
--------------------------------------------------------------------------------
1 | .steps-log {
2 | border: 2px solid #344861;
3 | height: 10%;
4 | width: 69%;
5 | height: 37%;
6 | display: inline-block;
7 | overflow: auto;
8 | }
9 |
10 | .steps-log .title {
11 | margin-top: 3px;
12 | font-size: 18px;
13 | }
14 |
15 | .steps-log .intro {
16 | text-align: left;
17 | font-size: 13px;
18 | padding-left: 5px;
19 | padding-right: 5px;
20 | margin-bottom: 3px;
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
1 | export {App} from './App';
2 |
3 | export {Board} from './Board';
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: Source Sans Pro, sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './components/App/App';
5 | import registerServiceWorker from './registerServiceWorker';
6 |
7 | ReactDOM.render( , document.getElementById('root'));
8 | registerServiceWorker();
9 |
--------------------------------------------------------------------------------
/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 |
39 | // Add some additional logging to localhost, pointing developers to the
40 | // service worker/PWA documentation.
41 | navigator.serviceWorker.ready.then(() => {
42 | console.log(
43 | 'This web app is being served cache-first by a service ' +
44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ'
45 | );
46 | });
47 | } else {
48 | // Is not local host. Just register service worker
49 | registerValidSW(swUrl);
50 | }
51 | });
52 | }
53 | }
54 |
55 | function registerValidSW(swUrl) {
56 | navigator.serviceWorker
57 | .register(swUrl)
58 | .then(registration => {
59 | registration.onupdatefound = () => {
60 | const installingWorker = registration.installing;
61 | installingWorker.onstatechange = () => {
62 | if (installingWorker.state === 'installed') {
63 | if (navigator.serviceWorker.controller) {
64 | // At this point, the old content will have been purged and
65 | // the fresh content will have been added to the cache.
66 | // It's the perfect time to display a "New content is
67 | // available; please refresh." message in your web app.
68 | console.log('New content is available; please refresh.');
69 | } else {
70 | // At this point, everything has been precached.
71 | // It's the perfect time to display a
72 | // "Content is cached for offline use." message.
73 | console.log('Content is cached for offline use.');
74 | }
75 | }
76 | };
77 | };
78 | })
79 | .catch(error => {
80 | console.error('Error during service worker registration:', error);
81 | });
82 | }
83 |
84 | function checkValidServiceWorker(swUrl) {
85 | // Check if the service worker can be found. If it can't reload the page.
86 | fetch(swUrl)
87 | .then(response => {
88 | // Ensure service worker exists, and that we really are getting a JS file.
89 | if (
90 | response.status === 404 ||
91 | response.headers.get('content-type').indexOf('javascript') === -1
92 | ) {
93 | // No service worker found. Probably a different app. Reload the page.
94 | navigator.serviceWorker.ready.then(registration => {
95 | registration.unregister().then(() => {
96 | window.location.reload();
97 | });
98 | });
99 | } else {
100 | // Service worker found. Proceed as normal.
101 | registerValidSW(swUrl);
102 | }
103 | })
104 | .catch(() => {
105 | console.log(
106 | 'No internet connection found. App is running in offline mode.'
107 | );
108 | });
109 | }
110 |
111 | export function unregister() {
112 | if ('serviceWorker' in navigator) {
113 | navigator.serviceWorker.ready.then(registration => {
114 | registration.unregister();
115 | });
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/services/Solver/constants.js:
--------------------------------------------------------------------------------
1 | export const digits = '123456789';
2 | export const letters = 'ABCDEFGHI';
3 | export const rows = letters.split('');
4 | export const cols = digits.split('');
5 |
6 | export const rRows = [
7 | ['A', 'B', 'C'],
8 | ['D', 'E', 'F'],
9 | ['G', 'H', 'I'],
10 | ];
11 |
12 | export const cCols = [
13 | ['1', '2', '3'],
14 | ['4', '5', '6'],
15 | ['7', '8', '9'],
16 | ];
17 |
18 | export const STRING_BOARD_LENGTH = 81;
19 |
20 | export const STATUS = {
21 | ABORT: 'abort',
22 | VALID: 'valid',
23 | INVALID: 'invalid',
24 | COMPLETED: 'completed',
25 | UNKNOWN: 'unknown',
26 | TIMER: 'timer',
27 | SOLVE: 'solve',
28 | };
29 | export const ERRORS = {
30 | EMPTY_VALUE: 'empty-value',
31 | INVALID_VALUE: 'invalid-value',
32 | INVALID_LENGTH: 'invalid-length',
33 | };
34 |
35 | export const ACTIONS = {
36 | OPEN: 'open load',
37 | CLOSE: 'close load',
38 | DEFAULT: 'use default',
39 | MODAL_ERROR: 'modal error',
40 | CHANGE: 'change',
41 | RANDOM: 'random',
42 | SUCCESS: 'success',
43 | SOLVE: 'solve',
44 | CLEAR: 'clear',
45 | RESET: 'reset',
46 | SET: 'set',
47 | RELOAD: 'reload',
48 | };
49 |
50 | export const STRATEGIES = {
51 | HIDDEN_SINGLE: 'Hidden Single in cell',
52 | NAKED_SINGLE: 'Naked Single in cell',
53 | POINTING_PAIRS: 'Pointing Pair in cells',
54 | BACKTRACKING: 'Backtracking search',
55 | };
56 |
57 | // String used to clean the board.
58 | export const emptySudokuString = '.'.repeat(STRING_BOARD_LENGTH);
59 |
60 | export const validStringRegExp = /^(([1-9]|\.)+|\W+)$/g;
61 |
62 | // Default sudoku string (valid)
63 | export const initialSudokuString =
64 | '4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......';
65 |
--------------------------------------------------------------------------------
/src/services/Solver/index.js:
--------------------------------------------------------------------------------
1 | export * from './solver';
2 |
3 | export * from './utils';
4 |
5 | export * from './logs';
6 |
7 | export * from './constant';
8 |
--------------------------------------------------------------------------------
/src/services/Solver/logs.js:
--------------------------------------------------------------------------------
1 | import { STRATEGIES } from './constants';
2 |
3 | let solverStrategy = '';
4 |
5 | export const setSolverStrategy = (strategy) => {
6 | solverStrategy = strategy;
7 | };
8 |
9 | export const clearSolverStrategy = () => {
10 | solverStrategy = '';
11 | };
12 |
13 | const logs = {
14 | solutionSteps: [],
15 | timeElapsedLog: [],
16 | gamesBoardLog: [],
17 | };
18 |
19 | export const addTimeElapsedLog = (
20 | timeElapsed,
21 | boardName,
22 | runCount,
23 | status,
24 | msg
25 | ) => {
26 | logs.timeElapsedLog.push({
27 | date: new Date().toLocaleString(),
28 | timeElapsed: timeElapsed,
29 | boardName: boardName,
30 | runCount: runCount,
31 | status: status,
32 | msg: msg,
33 | });
34 | };
35 |
36 | export const addSolutionStepsLog = (strategy, keys, value, method, msg) => {
37 | if (solverStrategy !== STRATEGIES.BACKTRACKING) {
38 | logs.solutionSteps.push({
39 | strategy: strategy,
40 | date: new Date().toLocaleString(),
41 | keys: [...keys],
42 | value: value,
43 | method: method,
44 | msg: msg,
45 | });
46 | }
47 | };
48 |
49 | export const getLog = (logName) => {
50 | return logs[logName];
51 | };
52 |
53 | export const showSolutionStepsLog = () => {
54 | console.log('Solution Steps: ', logs.solutionSteps);
55 | };
56 |
57 | export const resetLog = (logName) => {
58 | logs[logName] = [];
59 | return logs[logName];
60 | };
61 |
62 | export const startTimer = () => {
63 | return Date.now();
64 | };
65 |
66 | export const stopTimer = (startTimer) => {
67 | return Date.now() - startTimer;
68 | };
69 |
--------------------------------------------------------------------------------
/src/services/Solver/solver.js:
--------------------------------------------------------------------------------
1 | import { digits, STATUS, STRATEGIES } from './constants';
2 |
3 | import {
4 | all,
5 | copy,
6 | dict,
7 | isCompleted,
8 | getSquaresWithFewestCandidates,
9 | some,
10 | sectionList,
11 | squares,
12 | units,
13 | peers,
14 | log,
15 | hasPairValues,
16 | getPairSquares,
17 | unsolvedSquares,
18 | canEliminate,
19 | isSolved,
20 | display,
21 | getSquareUnitRowCol,
22 | getPeers,
23 | } from './utils';
24 |
25 | import {
26 | resetLog,
27 | startTimer,
28 | stopTimer,
29 | getLog,
30 | setSolverStrategy,
31 | } from './logs';
32 |
33 | ////////////////////////////// Solving Strategies //////////////////////////////////////
34 |
35 | /**
36 | * Backtracking Search with Constraint Propagation
37 | *
38 | * Javascript port of Peter Norvig's algorithm that Solve Every Sudoku Puzzle.
39 | * See http://norvig.com/sudoku.html for his article and explanations.
40 | * Also, many ideas came from @einaregilsson/sudoku JS solver
41 | *
42 | * This approach systematically try all possibilities until it hit one that works,
43 | * therefore has to be the last strategy to be applied.
44 | *
45 | * First make sure we haven't already found a solution or a contradiction,
46 | * and if not, choose one unfilled square and consider all its possible values.
47 | * One at a time, try assigning the square each value, and searching from the
48 | * resulting position. In other words, we search for a value d such that we can
49 | * successfully search for a solution from the result of assigning square s to d
50 | *
51 | * Constraint Propagation strategies:
52 | * (1: Naked Single) If a square has only one possible value,
53 | * then eliminate that value from the square's peers.
54 | * (2: Hidden Single) If a unit has only one possible place for a value,
55 | * then put the value there.
56 | *
57 | * This strategy can't collect solutions steps are as there are many false
58 | * positives attempts before it find the final solution.
59 | *
60 | */
61 |
62 | export const search = (values) => {
63 | // Set a global flag to stop collecting solution steps
64 | setSolverStrategy(STRATEGIES.BACKTRACKING);
65 |
66 | // Failed earlier
67 | if (!values) return false;
68 |
69 | // Solved!
70 | if (isCompleted(values)) return values;
71 |
72 | // Chose the unfilled square with the fewest candidate possibilities
73 | const square = getSquaresWithFewestCandidates(values)[0];
74 | const digits = values[square].split('');
75 |
76 | // Using depth-first search and propagation, try all possible values.
77 | return some(digits, (digit) => search(assign(copy(values), square, digit)));
78 | };
79 |
80 | export const assign = (values, square, digit) => {
81 | // Eliminate all the other values (except digit) from values[square] and propagate.
82 | // Return values, except return false if a contradiction is detected.
83 | const otherValues = values[square].replace(digit, '').split('');
84 | const isAssigned = all(otherValues, (otherValue) =>
85 | eliminate(values, square, otherValue)
86 | );
87 | if (isAssigned) {
88 | return values;
89 | } else {
90 | return false;
91 | }
92 | };
93 |
94 | export const eliminate = (values, square, digit) => {
95 | //Eliminate digit from values[square]; propagate when values or places <= 2.
96 | //return values, except return false if a contradiction is detected.
97 |
98 | const isDigitPresent = values[square].indexOf(digit) !== -1;
99 |
100 | if (!isDigitPresent) {
101 | // already eliminated.
102 | return values;
103 | }
104 |
105 | values[square] = values[square].replace(digit, '');
106 |
107 | // (1) If a square is reduced to oneValue, then eliminate oneValue
108 | // from the peers.
109 | const possibleValues = values[square];
110 | const isSquareContradicted = possibleValues.length === 0;
111 | const isSquareSolved = possibleValues.length === 1;
112 | if (isSquareContradicted) {
113 | // Contradiction: removed last value
114 | return false;
115 | } else if (isSquareSolved) {
116 | // Propagate change
117 | const oneValue = possibleValues;
118 | const peersSquares = Object.keys(peers[square]);
119 | const isSafePropagated = all(peersSquares, (peerSquare) =>
120 | eliminate(values, peerSquare, oneValue)
121 | );
122 | if (isSafePropagated) {
123 | log(STRATEGIES.NAKED_SINGLE, [square], oneValue);
124 | } else {
125 | return false;
126 | }
127 | }
128 |
129 | // (2) If a unit is reduced to only one place for a value digit, then put it there.
130 | for (const unit of units[square]) {
131 | const digitPlaces = unit.filter(
132 | (square) => values[square].indexOf(digit) !== -1
133 | );
134 | const areAnyPlaces = digitPlaces.length > 0;
135 | const isOnlyOnePlace = digitPlaces.length === 1;
136 |
137 | if (!areAnyPlaces) {
138 | // Contradiction: no place for this value
139 | return false;
140 | } else if (isOnlyOnePlace) {
141 | // digit can only be in one place in unit; assign it there
142 | if (assign(values, digitPlaces[0], digit)) {
143 | log(STRATEGIES.HIDDEN_SINGLE, [square], digit);
144 | } else {
145 | return false;
146 | }
147 | }
148 | }
149 | return values;
150 | };
151 |
152 | export const parseGrid = (grid) => {
153 | setSolverStrategy(STRATEGIES.BACKTRACKING);
154 | // Convert grid to a dict of possible values, {square: digits}, or
155 | // return False if a contradiction is detected.
156 |
157 | const values = dict(squares, digits);
158 | const input = gridValues(grid);
159 |
160 | for (const square in input) {
161 | const value = input[square];
162 | // square value could be '.' or 1..9
163 | const isAssignableValue =
164 | digits.indexOf(value) === -1 || assign(values, square, value);
165 | if (!isAssignableValue) {
166 | return false; // (Fail if we can't assign value to square.)
167 | }
168 | }
169 |
170 | return values;
171 | };
172 |
173 | const gridValues = (grid) => {
174 | //Convert grid into a dict of {square: char} with '0' or '.' for empties.
175 | const parseGrid = grid.replace(/[^0-9.]/g, '');
176 | return dict(squares, parseGrid.split(''));
177 | };
178 |
179 | /************************** End Backtrack Search ***********************************/
180 |
181 | /**
182 | * Pointing Pairs with Constraint Propagation
183 | *
184 | * Looking at each unsolved unit in turn there may be two occurrences
185 | * of a particular digit. If these digits are aligned on a single row or column,
186 | * we can remove any digit occurs anywhere else on the row or column outside the unit
187 | *
188 | */
189 | export const searchPointPair = async (values) => {
190 | setSolverStrategy(STRATEGIES.POINTING_PAIRS);
191 |
192 | // Failed earlier
193 | if (!values) return false;
194 |
195 | // Solved!
196 | if (isCompleted(values)) return values;
197 |
198 | for (const section of sectionList) {
199 | for (const square of unsolvedSquares(section, values)) {
200 | const digits = values[square].split('');
201 | if (!all(digits, (digit) => findPointPair(values, square, digit))) {
202 | return false;
203 | }
204 | }
205 | }
206 | return values;
207 | };
208 |
209 | export const findPointPair = (values, square, digit) => {
210 | const [unitCol, unitRow, unit] = units[square];
211 | const [unitRows, unitCols] = getSquareUnitRowCol(unit, square);
212 | const [rowPeers, colPeers, unitRowPeers, unitColPeers] = getPeers(
213 | unit,
214 | unitRow,
215 | unitRows,
216 | unitCol,
217 | unitCols,
218 | values
219 | );
220 |
221 | if (hasPairValues(unit, values, digit)) {
222 | if (hasPairValues(unitRows, values, digit)) {
223 | // aligned on a single row
224 | const areRowPeerValuesEliminated = eliminatePeerValues(
225 | rowPeers,
226 | values,
227 | digit
228 | );
229 | if (areRowPeerValuesEliminated) {
230 | const pairSquaresCol = getPairSquares(unitRows, values, digit);
231 | log(STRATEGIES.POINTING_PAIRS, pairSquaresCol, digit);
232 | }
233 | } else if (hasPairValues(unitCols, values, digit)) {
234 | // aligned on a single column
235 | const areColPeerValuesEliminated = eliminatePeerValues(
236 | colPeers,
237 | values,
238 | digit
239 | );
240 | if (areColPeerValuesEliminated) {
241 | const pairSquaresRow = getPairSquares(unitCols, values, digit);
242 | log(STRATEGIES.POINTING_PAIRS, pairSquaresRow, digit);
243 | }
244 | }
245 | } else if (
246 | hasPairValues(unitCols, values, digit) &&
247 | !canEliminate(colPeers, values, digit)
248 | ) {
249 | const areUnitColPeerValuesEliminated = eliminatePeerValues(
250 | unitColPeers,
251 | values,
252 | digit
253 | );
254 | if (areUnitColPeerValuesEliminated) {
255 | const pairSquaresCol = getPairSquares(unitCols, values, digit);
256 | log(STRATEGIES.POINTING_PAIRS, pairSquaresCol, digit);
257 | }
258 | } else if (
259 | hasPairValues(unitRows, values, digit) &&
260 | !canEliminate(rowPeers, values, digit)
261 | ) {
262 | const areUnitRowPeerValuesEliminated = eliminatePeerValues(
263 | unitRowPeers,
264 | values,
265 | digit
266 | );
267 | if (areUnitRowPeerValuesEliminated) {
268 | const pairSquaresRow = getPairSquares(unitRows, values, digit);
269 | log(STRATEGIES.POINTING_PAIRS, pairSquaresRow, digit);
270 | }
271 | }
272 |
273 | return values;
274 | };
275 |
276 | export const eliminatePeerValues = (peers, values, digit) =>
277 | canEliminate(peers, values, digit) &&
278 | all(peers, (sq) => eliminate(values, sq, digit));
279 |
280 | /**************************** End Pointing Pairs ***********************************/
281 |
282 | /**
283 | * Async method to solve a sudoku board. Stop on potential
284 | * infinite loops after a certain number of iterations. It will return
285 | * a promise until the board is solved or aborted.
286 | * @param values - The parsed board to be solve.
287 | * @returns a promise with the solveBoardResult with
288 | * @field timer = timer in ms
289 | * @field board = result board
290 | * @field solved = 'valid' | 'invalid';
291 | * @field abort = true | false;
292 | * @field completed = true | false;
293 | * @field solutionSteps = array of solutionSteps
294 | */
295 | const Solver = async (values) => {
296 | let loopCounter = 0;
297 | const loopLimit = 5;
298 | let isAborted,
299 | stopLoop = false;
300 |
301 | let solved = isSolved(values);
302 | let completed = solved || isCompleted(values);
303 |
304 | const solveBoardResult = {
305 | timer: 0,
306 | board: values,
307 | status: solved ? STATUS.VALID : STATUS.INVALID,
308 | abort: isAborted,
309 | completed: completed,
310 | solutionSteps: [],
311 | };
312 |
313 | if (completed) return solveBoardResult;
314 |
315 | let analysisBoard = values;
316 |
317 | resetLog('solutionSteps');
318 | let solveTimer = startTimer();
319 |
320 | try {
321 | // Loop until the board is solved or completed or aborted.
322 | // This in case the board will be returned with a non valid state.
323 | while (!solved && !completed && !stopLoop) {
324 | // Start solving strategies
325 | analysisBoard = await searchPointPair(analysisBoard);
326 | // Add new solvers here, i.e:
327 | // analysisBoard = await searchHiddenPairsTriples(analysisBoard);
328 | // analysisBoard = await searchNakedHiddenQuads(analysisBoard);
329 | // analysisBoard = await searchBoxLineReduction(analysisBoard);
330 | // End solving strategies
331 |
332 | solved = isSolved(analysisBoard);
333 | completed = isCompleted(analysisBoard);
334 |
335 | loopCounter++;
336 |
337 | if (!solved && loopCounter >= loopLimit) {
338 | // if board is still unsolved, use backtrack search
339 | log(STRATEGIES.BACKTRACKING);
340 | analysisBoard = search(analysisBoard);
341 | solved = isSolved(analysisBoard);
342 | completed = isCompleted(analysisBoard);
343 | isAborted = !solved;
344 | stopLoop = true;
345 | }
346 | }
347 |
348 | solveBoardResult.board = analysisBoard;
349 | solveBoardResult.timer = stopTimer(solveTimer);
350 | solveBoardResult.status = solved ? STATUS.VALID : STATUS.INVALID;
351 | solveBoardResult.abort = isAborted;
352 | solveBoardResult.completed = completed;
353 | solveBoardResult.solutionSteps = getLog('solutionSteps');
354 |
355 | if (!solved) {
356 | console.log(display(analysisBoard));
357 | }
358 |
359 | return solveBoardResult;
360 | } catch (error) {
361 | console.log(display(values));
362 | console.log('Error solver ', error);
363 | return solveBoardResult;
364 | }
365 | };
366 |
367 | export default Solver;
368 |
--------------------------------------------------------------------------------
/src/services/Solver/tests/config.test.js:
--------------------------------------------------------------------------------
1 | import { parseGrid } from '../../Solver/solver';
2 |
3 | import {
4 | initialSudokuString,
5 | validStringRegExp,
6 | emptySudokuString,
7 | } from '../constants';
8 |
9 | import { initialValuesState, parserGrid3 } from './data';
10 |
11 | import { getBoardState } from '../utils';
12 |
13 | describe('config', () => {
14 | it('getBoardState', () => {
15 | const values = parseGrid(initialSudokuString);
16 | expect(getBoardState(values)).toEqual(
17 | expect.objectContaining(initialValuesState)
18 | );
19 | });
20 |
21 | it('emptySudokuString', () => {
22 | const expected = '.'.repeat(81);
23 | expect(emptySudokuString).toEqual(expected);
24 | });
25 |
26 | it('initialSudokuString', () => {
27 | expect(initialSudokuString.length).toEqual(81);
28 | expect(initialSudokuString).toEqual(
29 | expect.stringMatching(validStringRegExp)
30 | );
31 | });
32 |
33 | it('parseGrid', () => {
34 | expect(parseGrid(initialSudokuString)).toEqual(parserGrid3);
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/src/services/Solver/tests/data.js:
--------------------------------------------------------------------------------
1 | export const squares = [
2 | 'A1',
3 | 'A2',
4 | 'A3',
5 | 'A4',
6 | 'A5',
7 | 'A6',
8 | 'A7',
9 | 'A8',
10 | 'A9',
11 | 'B1',
12 | 'B2',
13 | 'B3',
14 | 'B4',
15 | 'B5',
16 | 'B6',
17 | 'B7',
18 | 'B8',
19 | 'B9',
20 | 'C1',
21 | 'C2',
22 | 'C3',
23 | 'C4',
24 | 'C5',
25 | 'C6',
26 | 'C7',
27 | 'C8',
28 | 'C9',
29 | 'D1',
30 | 'D2',
31 | 'D3',
32 | 'D4',
33 | 'D5',
34 | 'D6',
35 | 'D7',
36 | 'D8',
37 | 'D9',
38 | 'E1',
39 | 'E2',
40 | 'E3',
41 | 'E4',
42 | 'E5',
43 | 'E6',
44 | 'E7',
45 | 'E8',
46 | 'E9',
47 | 'F1',
48 | 'F2',
49 | 'F3',
50 | 'F4',
51 | 'F5',
52 | 'F6',
53 | 'F7',
54 | 'F8',
55 | 'F9',
56 | 'G1',
57 | 'G2',
58 | 'G3',
59 | 'G4',
60 | 'G5',
61 | 'G6',
62 | 'G7',
63 | 'G8',
64 | 'G9',
65 | 'H1',
66 | 'H2',
67 | 'H3',
68 | 'H4',
69 | 'H5',
70 | 'H6',
71 | 'H7',
72 | 'H8',
73 | 'H9',
74 | 'I1',
75 | 'I2',
76 | 'I3',
77 | 'I4',
78 | 'I5',
79 | 'I6',
80 | 'I7',
81 | 'I8',
82 | 'I9',
83 | ];
84 |
85 | export const puzzles = [
86 | '4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......',
87 | '52...6.........7.13...........4..8..6......5...........418.........3..2...87.....',
88 | '6.....8.3.4.7.................5.4.7.3..2.....1.6.......2.....5.....8.6......1....',
89 | '48.3............71.2.......7.5....6....2..8.............1.76...3.....4......5....',
90 | '....14....3....2...7..........9...3.6.1.............8.2.....1.4....5.6.....7.8...',
91 | '......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3.',
92 | '6.2.5.........3.4..........43...8....1....2........7..5..27...........81...6.....',
93 | '.524.........7.1..............8.2...3.....6...9.5.....1.6.3...........897........',
94 | '6.2.5.........4.3..........43...8....1....2........7..5..27...........81...6.....',
95 | '.923.........8.1...........1.7.4...........658.........6.5.2...4.....7.....9.....',
96 | '6..3.2....5.....1..........7.26............543.........8.15........4.2........7..',
97 | '.6.5.1.9.1...9..539....7....4.8...7.......5.8.817.5.3.....5.2............76..8...',
98 | '..5...987.4..5...1..7......2...48....9.1.....6..2.....3..6..2.......9.7.......5..',
99 | '3.6.7...........518.........1.4.5...7.....6.....2......2.....4.....8.3.....5.....',
100 | '1.....3.8.7.4..............2.3.1...........958.........5.6...7.....8.2...4.......',
101 | '6..3.2....4.....1..........7.26............543.........8.15........4.2........7..',
102 | '....3..9....2....1.5.9..............1.2.8.4.6.8.5...2..75......4.1..6..3.....4.6.',
103 | '45.....3....8.1....9...........5..9.2..7.....8.........1..4..........7.2...6..8..',
104 | '.237....68...6.59.9.....7......4.97.3.7.96..2.........5..47.........2....8.......',
105 | '..84...3....3.....9....157479...8........7..514.....2...9.6...2.5....4......9..56',
106 | '.98.1....2......6.............3.2.5..84.........6.........4.8.93..5...........1..',
107 | '..247..58..............1.4.....2...9528.9.4....9...1.........3.3....75..685..2...',
108 | '4.....8.5.3..........7......2.....6.....5.4......1.......6.3.7.5..2.....1.9......',
109 | '.2.3......63.....58.......15....9.3....7........1....8.879..26......6.7...6..7..4',
110 | '1.....7.9.4...72..8.........7..1..6.3.......5.6..4..2.........8..53...7.7.2....46',
111 | '4.....3.....8.2......7........1...8734.......6........5...6........1.4...82......',
112 | '.......71.2.8........4.3...7...6..5....2..3..9........6...7.....8....4......5....',
113 | '6..3.2....4.....8..........7.26............543.........8.15........8.2........7..',
114 | '.47.8...1............6..7..6....357......5....1..6....28..4.....9.1...4.....2.69.',
115 | '......8.17..2........5.6......7...5..1....3...8.......5......2..4..8....6...3....',
116 | '38.6.......9.......2..3.51......5....3..1..6....4......17.5..8.......9.......7.32',
117 | '...5...........5.697.....2...48.2...25.1...3..8..3.........4.7..13.5..9..2...31..',
118 | '.2.......3.5.62..9.68...3...5..........64.8.2..47..9....3.....1.....6...17.43....',
119 | '.8..4....3......1........2...5...4.69..1..8..2...........3.9....6....5.....2.....',
120 | '..8.9.1...6.5...2......6....3.1.7.5.........9..4...3...5....2...7...3.8.2..7....4',
121 | '4.....5.8.3..........7......2.....6.....5.8......1.......6.3.7.5..2.....1.8......',
122 | '1.....3.8.6.4..............2.3.1...........958.........5.6...7.....8.2...4.......',
123 | '1....6.8..64..........4...7....9.6...7.4..5..5...7.1...5....32.3....8...4........',
124 | '249.6...3.3....2..8.......5.....6......2......1..4.82..9.5..7....4.....1.7...3...',
125 | '...8....9.873...4.6..7.......85..97...........43..75.......3....3...145.4....2..1',
126 | '...5.1....9....8...6.......4.1..........7..9........3.8.....1.5...2..4.....36....',
127 | '......8.16..2........7.5......6...2..1....3...8.......2......7..3..8....5...4....',
128 | '.476...5.8.3.....2.....9......8.5..6...1.....6.24......78...51...6....4..9...4..7',
129 | '.....7.95.....1...86..2.....2..73..85......6...3..49..3.5...41724................',
130 | '.4.5.....8...9..3..76.2.....146..........9..7.....36....1..4.5..6......3..71..2..',
131 | '.834.........7..5...........4.1.8..........27...3.....2.6.5....5.....8........1..',
132 | '..9.....3.....9...7.....5.6..65..4.....3......28......3..75.6..6...........12.3.8',
133 | '.26.39......6....19.....7.......4..9.5....2....85.....3..2..9..4....762.........4',
134 | '2.3.8....8..7...........1...6.5.7...4......3....1............82.5....6...1.......',
135 | '6..3.2....1.....5..........7.26............843.........8.15........8.2........7..',
136 | '1.....9...64..1.7..7..4.......3.....3.89..5....7....2.....6.7.9.....4.1....129.3.',
137 | '.........9......84.623...5....6...453...1...6...9...7....1.....4.5..2....3.8....9',
138 | '.2....5938..5..46.94..6...8..2.3.....6..8.73.7..2.........4.38..7....6..........5',
139 | '9.4..5...25.6..1..31......8.7...9...4..26......147....7.......2...3..8.6.4.....9.',
140 | '...52.....9...3..4......7...1.....4..8..453..6...1...87.2........8....32.4..8..1.',
141 | '53..2.9...24.3..5...9..........1.827...7.........981.............64....91.2.5.43.',
142 | '1....786...7..8.1.8..2....9........24...1......9..5...6.8..........5.9.......93.4',
143 | '....5...11......7..6.....8......4.....9.1.3.....596.2..8..62..7..7......3.5.7.2..',
144 | '.47.2....8....1....3....9.2.....5...6..81..5.....4.....7....3.4...9...1.4..27.8..',
145 | '......94.....9...53....5.7..8.4..1..463...........7.8.8..7.....7......28.5.26....',
146 | '.2......6....41.....78....1......7....37.....6..412....1..74..5..8.5..7......39..',
147 | '1.....3.8.6.4..............2.3.1...........758.........7.5...6.....8.2...4.......',
148 | '2....1.9..1..3.7..9..8...2.......85..6.4.........7...3.2.3...6....5.....1.9...2.5',
149 | '..7..8.....6.2.3...3......9.1..5..6.....1.....7.9....2........4.83..4...26....51.',
150 | '...36....85.......9.4..8........68.........17..9..45...1.5...6.4....9..2.....3...',
151 | '34.6.......7.......2..8.57......5....7..1..2....4......36.2..1.......9.......7.82',
152 | '......4.18..2........6.7......8...6..4....3...1.......6......2..5..1....7...3....',
153 | '.4..5..67...1...4....2.....1..8..3........2...6...........4..5.3.....8..2........',
154 | '.......4...2..4..1.7..5..9...3..7....4..6....6..1..8...2....1..85.9...6.....8...3',
155 | '8..7....4.5....6............3.97...8....43..5....2.9....6......2...6...7.71..83.2',
156 | '.8...4.5....7..3............1..85...6.....2......4....3.26............417........',
157 | '....7..8...6...5...2...3.61.1...7..2..8..534.2..9.......2......58...6.3.4...1....',
158 | '......8.16..2........7.5......6...2..1....3...8.......2......7..4..8....5...3....',
159 | '.2..........6....3.74.8.........3..2.8..4..1.6..5.........1.78.5....9..........4.',
160 | '.52..68.......7.2.......6....48..9..2..41......1.....8..61..38.....9...63..6..1.9',
161 | '....1.78.5....9..........4..2..........6....3.74.8.........3..2.8..4..1.6..5.....',
162 | '1.......3.6.3..7...7...5..121.7...9...7........8.1..2....8.64....9.2..6....4.....',
163 | '4...7.1....19.46.5.....1......7....2..2.3....847..6....14...8.6.2....3..6...9....',
164 | '......8.17..2........5.6......7...5..1....3...8.......5......2..3..8....6...4....',
165 | '963......1....8......2.5....4.8......1....7......3..257......3...9.2.4.7......9..',
166 | '15.3......7..4.2....4.72.....8.........9..1.8.1..8.79......38...........6....7423',
167 | '..........5724...98....947...9..3...5..9..12...3.1.9...6....25....56.....7......6',
168 | '....75....1..2.....4...3...5.....3.2...8...1.......6.....1..48.2........7........',
169 | '6.....7.3.4.8.................5.4.8.7..2.....1.3.......2.....5.....7.9......1....',
170 | '....6...4..6.3....1..4..5.77.....8.5...8.....6.8....9...2.9....4....32....97..1..',
171 | '.32.....58..3.....9.428...1...4...39...6...5.....1.....2...67.8.....4....95....6.',
172 | '...5.3.......6.7..5.8....1636..2.......4.1.......3...567....2.8..4.7.......2..5..',
173 | '.5.3.7.4.1.........3.......5.8.3.61....8..5.9.6..1........4...6...6927....2...9..',
174 | '..5..8..18......9.......78....4.....64....9......53..2.6.........138..5....9.714.',
175 | '..........72.6.1....51...82.8...13..4.........37.9..1.....238..5.4..9.........79.',
176 | '...658.....4......12............96.7...3..5....2.8...3..19..8..3.6.....4....473..',
177 | '.2.3.......6..8.9.83.5........2...8.7.9..5........6..4.......1...1...4.22..7..8.9',
178 | '.5..9....1.....6.....3.8.....8.4...9514.......3....2..........4.8...6..77..15..6.',
179 | '.....2.......7...17..3...9.8..7......2.89.6...13..6....9..5.824.....891..........',
180 | '3...8.......7....51..............36...2..4....7...........6.13..452...........8..',
181 | '...872.49384........7341..68...6..9...6...4...4..9...34..1879........78573.529...',
182 | '....14....3....2...7..........9...3.6.1.............8.2.....1.4....5.6.....7.8...',
183 | '6.2.5.........3.4..........43...8....1....2........7..5..27...........81...6.....',
184 | '.524.........7.1..............8.2...3.....6...9.5.....1.6.3...........897........',
185 | '4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......',
186 | '......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3.',
187 | '6.2.5.........4.3..........43...8....1....2........7..5..27...........81...6.....',
188 | '.923.........8.1...........1.7.4...........658.........6.5.2...4.....7.....9.....',
189 | '6..3.2....5.....1..........7.26............543.........8.15........4.2........7..',
190 | '.6.5.1.9.1...9..539....7....4.8...7.......5.8.817.5.3.....5.2............76..8...',
191 | '..5...987.4..5...1..7......2...48....9.1.....6..2.....3..6..2.......9.7.......5..',
192 | '3.6.7...........518.........1.4.5...7.....6.....2......2.....4.....8.3.....5.....',
193 | '1.....3.8.7.4..............2.3.1...........958.........5.6...7.....8.2...4.......',
194 | '6..3.2....4.....1..........7.26............543.........8.15........4.2........7..',
195 | '....3..9....2....1.5.9..............1.2.8.4.6.8.5...2..75......4.1..6..3.....4.6.',
196 | '45.....3....8.1....9...........5..9.2..7.....8.........1..4..........7.2...6..8..',
197 | '.237....68...6.59.9.....7......4.97.3.7.96..2.........5..47.........2....8.......',
198 | '..84...3....3.....9....157479...8........7..514.....2...9.6...2.5....4......9..56',
199 | '.98.1....2......6.............3.2.5..84.........6.........4.8.93..5...........1..',
200 | '..247..58..............1.4.....2...9528.9.4....9...1.........3.3....75..685..2...',
201 | '4.....8.5.3..........7......2.....6.....5.4......1.......6.3.7.5..2.....1.9......',
202 | '.2.3......63.....58.......15....9.3....7........1....8.879..26......6.7...6..7..4',
203 | '1.....7.9.4...72..8.........7..1..6.3.......5.6..4..2.........8..53...7.7.2....46',
204 | '4.....3.....8.2......7........1...8734.......6........5...6........1.4...82......',
205 | '.......71.2.8........4.3...7...6..5....2..3..9........6...7.....8....4......5....',
206 | '6..3.2....4.....8..........7.26............543.........8.15........8.2........7..',
207 | '.47.8...1............6..7..6....357......5....1..6....28..4.....9.1...4.....2.69.',
208 | '......8.17..2........5.6......7...5..1....3...8.......5......2..4..8....6...3....',
209 | '38.6.......9.......2..3.51......5....3..1..6....4......17.5..8.......9.......7.32',
210 | '...5...........5.697.....2...48.2...25.1...3..8..3.........4.7..13.5..9..2...31..',
211 | '.2.......3.5.62..9.68...3...5..........64.8.2..47..9....3.....1.....6...17.43....',
212 | '.8..4....3......1........2...5...4.69..1..8..2...........3.9....6....5.....2.....',
213 | '..8.9.1...6.5...2......6....3.1.7.5.........9..4...3...5....2...7...3.8.2..7....4',
214 | '4.....5.8.3..........7......2.....6.....5.8......1.......6.3.7.5..2.....1.8......',
215 | '1.....3.8.6.4..............2.3.1...........958.........5.6...7.....8.2...4.......',
216 | '1....6.8..64..........4...7....9.6...7.4..5..5...7.1...5....32.3....8...4........',
217 | '249.6...3.3....2..8.......5.....6......2......1..4.82..9.5..7....4.....1.7...3...',
218 | '...8....9.873...4.6..7.......85..97...........43..75.......3....3...145.4....2..1',
219 | '...5.1....9....8...6.......4.1..........7..9........3.8.....1.5...2..4.....36....',
220 | '......8.16..2........7.5......6...2..1....3...8.......2......7..3..8....5...4....',
221 | '.476...5.8.3.....2.....9......8.5..6...1.....6.24......78...51...6....4..9...4..7',
222 | '.....7.95.....1...86..2.....2..73..85......6...3..49..3.5...41724................',
223 | '.4.5.....8...9..3..76.2.....146..........9..7.....36....1..4.5..6......3..71..2..',
224 | '.834.........7..5...........4.1.8..........27...3.....2.6.5....5.....8........1..',
225 | '..9.....3.....9...7.....5.6..65..4.....3......28......3..75.6..6...........12.3.8',
226 | '.26.39......6....19.....7.......4..9.5....2....85.....3..2..9..4....762.........4',
227 | '2.3.8....8..7...........1...6.5.7...4......3....1............82.5....6...1.......',
228 | '6..3.2....1.....5..........7.26............843.........8.15........8.2........7..',
229 | '1.....9...64..1.7..7..4.......3.....3.89..5....7....2.....6.7.9.....4.1....129.3.',
230 | '.........9......84.623...5....6...453...1...6...9...7....1.....4.5..2....3.8....9',
231 | '.2....5938..5..46.94..6...8..2.3.....6..8.73.7..2.........4.38..7....6..........5',
232 | '9.4..5...25.6..1..31......8.7...9...4..26......147....7.......2...3..8.6.4.....9.',
233 | '...52.....9...3..4......7...1.....4..8..453..6...1...87.2........8....32.4..8..1.',
234 | '53..2.9...24.3..5...9..........1.827...7.........981.............64....91.2.5.43.',
235 | '1....786...7..8.1.8..2....9........24...1......9..5...6.8..........5.9.......93.4',
236 | '....5...11......7..6.....8......4.....9.1.3.....596.2..8..62..7..7......3.5.7.2..',
237 | '.47.2....8....1....3....9.2.....5...6..81..5.....4.....7....3.4...9...1.4..27.8..',
238 | '......94.....9...53....5.7..8.4..1..463...........7.8.8..7.....7......28.5.26....',
239 | '.2......6....41.....78....1......7....37.....6..412....1..74..5..8.5..7......39..',
240 | '1.....3.8.6.4..............2.3.1...........758.........7.5...6.....8.2...4.......',
241 | '2....1.9..1..3.7..9..8...2.......85..6.4.........7...3.2.3...6....5.....1.9...2.5',
242 | '..7..8.....6.2.3...3......9.1..5..6.....1.....7.9....2........4.83..4...26....51.',
243 | '...36....85.......9.4..8........68.........17..9..45...1.5...6.4....9..2.....3...',
244 | '34.6.......7.......2..8.57......5....7..1..2....4......36.2..1.......9.......7.82',
245 | '......4.18..2........6.7......8...6..4....3...1.......6......2..5..1....7...3....',
246 | '.4..5..67...1...4....2.....1..8..3........2...6...........4..5.3.....8..2........',
247 | '.......4...2..4..1.7..5..9...3..7....4..6....6..1..8...2....1..85.9...6.....8...3',
248 | '8..7....4.5....6............3.97...8....43..5....2.9....6......2...6...7.71..83.2',
249 | '.8...4.5....7..3............1..85...6.....2......4....3.26............417........',
250 | '....7..8...6...5...2...3.61.1...7..2..8..534.2..9.......2......58...6.3.4...1....',
251 | '......8.16..2........7.5......6...2..1....3...8.......2......7..4..8....5...3....',
252 | '.2..........6....3.74.8.........3..2.8..4..1.6..5.........1.78.5....9..........4.',
253 | '.52..68.......7.2.......6....48..9..2..41......1.....8..61..38.....9...63..6..1.9',
254 | '....1.78.5....9..........4..2..........6....3.74.8.........3..2.8..4..1.6..5.....',
255 | '1.......3.6.3..7...7...5..121.7...9...7........8.1..2....8.64....9.2..6....4.....',
256 | '4...7.1....19.46.5.....1......7....2..2.3....847..6....14...8.6.2....3..6...9....',
257 | '......8.17..2........5.6......7...5..1....3...8.......5......2..3..8....6...4....',
258 | '963......1....8......2.5....4.8......1....7......3..257......3...9.2.4.7......9..',
259 | '15.3......7..4.2....4.72.....8.........9..1.8.1..8.79......38...........6....7423',
260 | '..........5724...98....947...9..3...5..9..12...3.1.9...6....25....56.....7......6',
261 | '....75....1..2.....4...3...5.....3.2...8...1.......6.....1..48.2........7........',
262 | '6.....7.3.4.8.................5.4.8.7..2.....1.3.......2.....5.....7.9......1....',
263 | '....6...4..6.3....1..4..5.77.....8.5...8.....6.8....9...2.9....4....32....97..1..',
264 | '.32.....58..3.....9.428...1...4...39...6...5.....1.....2...67.8.....4....95....6.',
265 | '...5.3.......6.7..5.8....1636..2.......4.1.......3...567....2.8..4.7.......2..5..',
266 | '.5.3.7.4.1.........3.......5.8.3.61....8..5.9.6..1........4...6...6927....2...9..',
267 | '..5..8..18......9.......78....4.....64....9......53..2.6.........138..5....9.714.',
268 | '..........72.6.1....51...82.8...13..4.........37.9..1.....238..5.4..9.........79.',
269 | '...658.....4......12............96.7...3..5....2.8...3..19..8..3.6.....4....473..',
270 | '.2.3.......6..8.9.83.5........2...8.7.9..5........6..4.......1...1...4.22..7..8.9',
271 | '.5..9....1.....6.....3.8.....8.4...9514.......3....2..........4.8...6..77..15..6.',
272 | '.....2.......7...17..3...9.8..7......2.89.6...13..6....9..5.824.....891..........',
273 | '3...8.......7....51..............36...2..4....7...........6.13..452...........8..',
274 | '...872.49384........7341..68...6..9...6...4...4..9...34..1879........78573.529...',
275 | '7.....4...2..7..8...3..8.799..5..3...6..2..9...1.97..6...3..9...3..4..6...9..1.35',
276 | '4271...68..5..63..6.3...1..2...1.4..34..67.518.1.5..2..9....73.7.43..2.9.32.946..', // Easy Test #1 -VALID
277 | '4.....8.5.3..........7......2.....6.....8.4......1...s....6.3.7.5..2.....1.4.....', // Original -VALID
278 | '..2.3...8.....8....31.2.....6..5.27..1.....5.2.4.6..31....8.6.5.......13..531.4..', // Hidden Singles -VALID
279 | '3.542.81.4879.15.6.29.5637485.793.416132.8957.74.6528.2413.9.655.867.192.965124.8', // Naked Singles -VALID
280 | '2564891733746159829817234565932748617128.6549468591327635147298127958634849362715', // Last Empty Square -VALID
281 | '974236158638591742125487936316754289742918563589362417867125394253649871491873625', // Completed Puzzle -VALID
282 | '.................................................................................', // Empty x -INVALID
283 | '4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......', // TEST VALID
284 | '52...6.........7.13...........4..8..6......5...........418.........3..2...87.....',
285 | '6.....8.3.4.7.................5.4.7.3..2.....1.6.......2.....5.....8.6......1....', // TEST VALID
286 | '48.3............71.2.......7.5....6....2..8.............1.76...3.....4......5....',
287 | '....14....3....2...7..........9...3.6.1.............8.2.....1.4....5.6.....7.8...',
288 | '......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3.',
289 | '6.2.5.........3.4..........43...8....1....2........7..5..27...........81...6.....',
290 | '.524.........7.1..............8.2...3.....6...9.5.....1.6.3...........897........',
291 | '6.2.5.........4.3..........43...8....1....2........7..5..27...........81...6.....',
292 | '.923.........8.1...........1.7.4...........658.........6.5.2...4.....7.....9.....',
293 | '6..3.2....5.....1..........7.26............543.........8.15........4.2........7..',
294 | '.6.5.1.9.1...9..539....7....4.8...7.......5.8.817.5.3.....5.2............76..8...',
295 | '..5...987.4..5...1..7......2...48....9.1.....6..2.....3..6..2.......9.7.......5..',
296 | '3.6.7...........518.........1.4.5...7.....6.....2......2.....4.....8.3.....5.....',
297 | '1.....3.8.7.4..............2.3.1...........958.........5.6...7.....8.2...4.......',
298 | '6..3.2....4.....1..........7.26............543.........8.15........4.2........7..',
299 | '....3..9....2....1.5.9..............1.2.8.4.6.8.5...2..75......4.1..6..3.....4.6.',
300 | '45.....3....8.1....9...........5..9.2..7.....8.........1..4..........7.2...6..8..',
301 | '.237....68...6.59.9.....7......4.97.3.7.96..2.........5..47.........2....8.......',
302 | '..84...3....3.....9....157479...8........7..514.....2...9.6...2.5....4......9..56',
303 | '.98.1....2......6.............3.2.5..84.........6.........4.8.93..5...........1..',
304 | '..247..58..............1.4.....2...9528.9.4....9...1.........3.3....75..685..2...',
305 | '4.....8.5.3..........7......2.....6.....5.4......1.......6.3.7.5..2.....1.9......',
306 | '.2.3......63.....58.......15....9.3....7........1....8.879..26......6.7...6..7..4',
307 | '1.....7.9.4...72..8.........7..1..6.3.......5.6..4..2.........8..53...7.7.2....46',
308 | '4.....3.....8.2......7........1...8734.......6........5...6........1.4...82......',
309 | '.......71.2.8........4.3...7...6..5....2..3..9........6...7.....8....4......5....',
310 | '6..3.2....4.....8..........7.26............543.........8.15........8.2........7..',
311 | '.47.8...1............6..7..6....357......5....1..6....28..4.....9.1...4.....2.69.',
312 | '......8.17..2........5.6......7...5..1....3...8.......5......2..4..8....6...3....',
313 | '38.6.......9.......2..3.51......5....3..1..6....4......17.5..8.......9.......7.32',
314 | '...5...........5.697.....2...48.2...25.1...3..8..3.........4.7..13.5..9..2...31..',
315 | '.2.......3.5.62..9.68...3...5..........64.8.2..47..9....3.....1.....6...17.43....',
316 | '.8..4....3......1........2...5...4.69..1..8..2...........3.9....6....5.....2.....',
317 | '..8.9.1...6.5...2......6....3.1.7.5.........9..4...3...5....2...7...3.8.2..7....4',
318 | '4.....5.8.3..........7......2.....6.....5.8......1.......6.3.7.5..2.....1.8......',
319 | '1.....3.8.6.4..............2.3.1...........958.........5.6...7.....8.2...4.......',
320 | '1....6.8..64..........4...7....9.6...7.4..5..5...7.1...5....32.3....8...4........',
321 | '249.6...3.3....2..8.......5.....6......2......1..4.82..9.5..7....4.....1.7...3...',
322 | '...8....9.873...4.6..7.......85..97...........43..75.......3....3...145.4....2..1',
323 | '...5.1....9....8...6.......4.1..........7..9........3.8.....1.5...2..4.....36....',
324 | '......8.16..2........7.5......6...2..1....3...8.......2......7..3..8....5...4....',
325 | '.476...5.8.3.....2.....9......8.5..6...1.....6.24......78...51...6....4..9...4..7',
326 | '.....7.95.....1...86..2.....2..73..85......6...3..49..3.5...41724................',
327 | '.4.5.....8...9..3..76.2.....146..........9..7.....36....1..4.5..6......3..71..2..',
328 | '.834.........7..5...........4.1.8..........27...3.....2.6.5....5.....8........1..',
329 | '..9.....3.....9...7.....5.6..65..4.....3......28......3..75.6..6...........12.3.8',
330 | '.26.39......6....19.....7.......4..9.5....2....85.....3..2..9..4....762.........4',
331 | '2.3.8....8..7...........1...6.5.7...4......3....1............82.5....6...1.......',
332 | '6..3.2....1.....5..........7.26............843.........8.15........8.2........7..',
333 | '1.....9...64..1.7..7..4.......3.....3.89..5....7....2.....6.7.9.....4.1....129.3.',
334 | '.........9......84.623...5....6...453...1...6...9...7....1.....4.5..2....3.8....9',
335 | '.2....5938..5..46.94..6...8..2.3.....6..8.73.7..2.........4.38..7....6..........5',
336 | '9.4..5...25.6..1..31......8.7...9...4..26......147....7.......2...3..8.6.4.....9.',
337 | '...52.....9...3..4......7...1.....4..8..453..6...1...87.2........8....32.4..8..1.',
338 | '53..2.9...24.3..5...9..........1.827...7.........981.............64....91.2.5.43.',
339 | '1....786...7..8.1.8..2....9........24...1......9..5...6.8..........5.9.......93.4',
340 | '....5...11......7..6.....8......4.....9.1.3.....596.2..8..62..7..7......3.5.7.2..',
341 | '.47.2....8....1....3....9.2.....5...6..81..5.....4.....7....3.4...9...1.4..27.8..',
342 | '......94.....9...53....5.7..8.4..1..463...........7.8.8..7.....7......28.5.26....',
343 | '.2......6....41.....78....1......7....37.....6..412....1..74..5..8.5..7......39..',
344 | '1.....3.8.6.4..............2.3.1...........758.........7.5...6.....8.2...4.......',
345 | '2....1.9..1..3.7..9..8...2.......85..6.4.........7...3.2.3...6....5.....1.9...2.5',
346 | '..7..8.....6.2.3...3......9.1..5..6.....1.....7.9....2........4.83..4...26....51.',
347 | '...36....85.......9.4..8........68.........17..9..45...1.5...6.4....9..2.....3...',
348 | '34.6.......7.......2..8.57......5....7..1..2....4......36.2..1.......9.......7.82',
349 | '......4.18..2........6.7......8...6..4....3...1.......6......2..5..1....7...3....',
350 | '.4..5..67...1...4....2.....1..8..3........2...6...........4..5.3.....8..2........',
351 | '.......4...2..4..1.7..5..9...3..7....4..6....6..1..8...2....1..85.9...6.....8...3',
352 | '8..7....4.5....6............3.97...8....43..5....2.9....6......2...6...7.71..83.2',
353 | '.8...4.5....7..3............1..85...6.....2......4....3.26............417........',
354 | '....7..8...6...5...2...3.61.1...7..2..8..534.2..9.......2......58...6.3.4...1....',
355 | '......8.16..2........7.5......6...2..1....3...8.......2......7..4..8....5...3....',
356 | '.2..........6....3.74.8.........3..2.8..4..1.6..5.........1.78.5....9..........4.',
357 | '.52..68.......7.2.......6....48..9..2..41......1.....8..61..38.....9...63..6..1.9',
358 | '....1.78.5....9..........4..2..........6....3.74.8.........3..2.8..4..1.6..5.....',
359 | '1.......3.6.3..7...7...5..121.7...9...7........8.1..2....8.64....9.2..6....4.....',
360 | '4...7.1....19.46.5.....1......7....2..2.3....847..6....14...8.6.2....3..6...9....',
361 | '......8.17..2........5.6......7...5..1....3...8.......5......2..3..8....6...4....',
362 | '963......1....8......2.5....4.8......1....7......3..257......3...9.2.4.7......9..',
363 | '15.3......7..4.2....4.72.....8.........9..1.8.1..8.79......38...........6....7423',
364 | '..........5724...98....947...9..3...5..9..12...3.1.9...6....25....56.....7......6',
365 | '....75....1..2.....4...3...5.....3.2...8...1.......6.....1..48.2........7........',
366 | '6.....7.3.4.8.................5.4.8.7..2.....1.3.......2.....5.....7.9......1....',
367 | '....6...4..6.3....1..4..5.77.....8.5...8.....6.8....9...2.9....4....32....97..1..',
368 | '.32.....58..3.....9.428...1...4...39...6...5.....1.....2...67.8.....4....95....6.',
369 | '...5.3.......6.7..5.8....1636..2.......4.1.......3...567....2.8..4.7.......2..5..',
370 | '.5.3.7.4.1.........3.......5.8.3.61....8..5.9.6..1........4...6...6927....2...9..',
371 | '..5..8..18......9.......78....4.....64....9......53..2.6.........138..5....9.714.',
372 | '..........72.6.1....51...82.8...13..4.........37.9..1.....238..5.4..9.........79.',
373 | '...658.....4......12............96.7...3..5....2.8...3..19..8..3.6.....4....473..',
374 | '.2.3.......6..8.9.83.5........2...8.7.9..5........6..4.......1...1...4.22..7..8.9',
375 | '.5..9....1.....6.....3.8.....8.4...9514.......3....2..........4.8...6..77..15..6.',
376 | '.....2.......7...17..3...9.8..7......2.89.6...13..6....9..5.824.....891..........',
377 | '3...8.......7....51..............36...2..4....7...........6.13..452...........8..',
378 | '85...24..72......9..4.........1.7..23.5...9...4...........8..7..17..........36.4.',
379 | '..53.....8......2..7..1.5..4....53...1..7...6..32...8..6.5....9..4....3......97..',
380 | '12..4......5.69.1...9...5.........7.7...52.9..3......2.9.6...5.4..9..8.1..3...9.4',
381 | '...57..3.1......2.7...234......8...4..7..4...49....6.5.42...3.....7..9....18.....',
382 | '7..1523........92....3.....1....47.8.......6............9...5.6.4.9.7...8....6.1.',
383 | '1....7.9..3..2...8..96..5....53..9...1..8...26....4...3......1..4......7..7...3..',
384 | '1...34.8....8..5....4.6..21.18......3..1.2..6......81.52..7.9....6..9....9.64...2',
385 | '...92......68.3...19..7...623..4.1....1...7....8.3..297...8..91...5.72......64...',
386 | '.6.5.4.3.1...9...8.........9...5...6.4.6.2.7.7...4...5.........4...8...1.5.2.3.4.',
387 | '7.....4...2..7..8...3..8.799..5..3...6..2..9...1.97..6...3..9...3..4..6...9..1.35',
388 | '....7..2.8.......6.1.2.5...9.54....8.........3....85.1...3.2.8.4.......9.7..6....',
389 | ];
390 |
391 | export const unitList = [
392 | ['A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1', 'H1', 'I1'],
393 | ['A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'I2'],
394 | ['A3', 'B3', 'C3', 'D3', 'E3', 'F3', 'G3', 'H3', 'I3'],
395 | ['A4', 'B4', 'C4', 'D4', 'E4', 'F4', 'G4', 'H4', 'I4'],
396 | ['A5', 'B5', 'C5', 'D5', 'E5', 'F5', 'G5', 'H5', 'I5'],
397 | ['A6', 'B6', 'C6', 'D6', 'E6', 'F6', 'G6', 'H6', 'I6'],
398 | ['A7', 'B7', 'C7', 'D7', 'E7', 'F7', 'G7', 'H7', 'I7'],
399 | ['A8', 'B8', 'C8', 'D8', 'E8', 'F8', 'G8', 'H8', 'I8'],
400 | ['A9', 'B9', 'C9', 'D9', 'E9', 'F9', 'G9', 'H9', 'I9'],
401 | ['A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'A9'],
402 | ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B9'],
403 | ['C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9'],
404 | ['D1', 'D2', 'D3', 'D4', 'D5', 'D6', 'D7', 'D8', 'D9'],
405 | ['E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'E7', 'E8', 'E9'],
406 | ['F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9'],
407 | ['G1', 'G2', 'G3', 'G4', 'G5', 'G6', 'G7', 'G8', 'G9'],
408 | ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'H7', 'H8', 'H9'],
409 | ['I1', 'I2', 'I3', 'I4', 'I5', 'I6', 'I7', 'I8', 'I9'],
410 | ['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3'],
411 | ['A4', 'A5', 'A6', 'B4', 'B5', 'B6', 'C4', 'C5', 'C6'],
412 | ['A7', 'A8', 'A9', 'B7', 'B8', 'B9', 'C7', 'C8', 'C9'],
413 | ['D1', 'D2', 'D3', 'E1', 'E2', 'E3', 'F1', 'F2', 'F3'],
414 | ['D4', 'D5', 'D6', 'E4', 'E5', 'E6', 'F4', 'F5', 'F6'],
415 | ['D7', 'D8', 'D9', 'E7', 'E8', 'E9', 'F7', 'F8', 'F9'],
416 | ['G1', 'G2', 'G3', 'H1', 'H2', 'H3', 'I1', 'I2', 'I3'],
417 | ['G4', 'G5', 'G6', 'H4', 'H5', 'H6', 'I4', 'I5', 'I6'],
418 | ['G7', 'G8', 'G9', 'H7', 'H8', 'H9', 'I7', 'I8', 'I9'],
419 | ];
420 |
421 | export const sectionListData = [
422 | ['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3'],
423 | ['A4', 'A5', 'A6', 'B4', 'B5', 'B6', 'C4', 'C5', 'C6'],
424 | ['A7', 'A8', 'A9', 'B7', 'B8', 'B9', 'C7', 'C8', 'C9'],
425 | ['D1', 'D2', 'D3', 'E1', 'E2', 'E3', 'F1', 'F2', 'F3'],
426 | ['D4', 'D5', 'D6', 'E4', 'E5', 'E6', 'F4', 'F5', 'F6'],
427 | ['D7', 'D8', 'D9', 'E7', 'E8', 'E9', 'F7', 'F8', 'F9'],
428 | ['G1', 'G2', 'G3', 'H1', 'H2', 'H3', 'I1', 'I2', 'I3'],
429 | ['G4', 'G5', 'G6', 'H4', 'H5', 'H6', 'I4', 'I5', 'I6'],
430 | ['G7', 'G8', 'G9', 'H7', 'H8', 'H9', 'I7', 'I8', 'I9'],
431 | ];
432 |
433 | // textGrid => '52...6.........7.13...........4..8..6......5...........418.........3..2...87.....'
434 | export const parsedGrid1 = {
435 | A1: '5',
436 | A2: '2',
437 | A3: '479',
438 | A4: '139',
439 | A5: '14789',
440 | A6: '6',
441 | A7: '349',
442 | A8: '3489',
443 | A9: '349',
444 | B1: '489',
445 | B2: '689',
446 | B3: '469',
447 | B4: '2359',
448 | B5: '24589',
449 | B6: '234589',
450 | B7: '7',
451 | B8: '34689',
452 | B9: '1',
453 | C1: '3',
454 | C2: '1',
455 | C3: '4679',
456 | C4: '259',
457 | C5: '245789',
458 | C6: '245789',
459 | C7: '24569',
460 | C8: '4689',
461 | C9: '24569',
462 | D1: '1279',
463 | D2: '579',
464 | D3: '23579',
465 | D4: '4',
466 | D5: '125679',
467 | D6: '123579',
468 | D7: '8',
469 | D8: '13679',
470 | D9: '23679',
471 | E1: '6',
472 | E2: '789',
473 | E3: '23479',
474 | E4: '1239',
475 | E5: '12789',
476 | E6: '123789',
477 | E7: '12349',
478 | E8: '5',
479 | E9: '23479',
480 | F1: '124789',
481 | F2: '5789',
482 | F3: '234579',
483 | F4: '123569',
484 | F5: '1256789',
485 | F6: '1235789',
486 | F7: '123469',
487 | F8: '134679',
488 | F9: '234679',
489 | G1: '279',
490 | G2: '4',
491 | G3: '1',
492 | G4: '8',
493 | G5: '2569',
494 | G6: '259',
495 | G7: '3569',
496 | G8: '3679',
497 | G9: '35679',
498 | H1: '79',
499 | H2: '5679',
500 | H3: '5679',
501 | H4: '1569',
502 | H5: '3',
503 | H6: '1459',
504 | H7: '14569',
505 | H8: '2',
506 | H9: '8',
507 | I1: '29',
508 | I2: '3',
509 | I3: '8',
510 | I4: '7',
511 | I5: '124569',
512 | I6: '12459',
513 | I7: '14569',
514 | I8: '1469',
515 | I9: '4569',
516 | };
517 |
518 | // textGrid => '6.....8.3.4.7.................5.4.7.3..2.....1.6.......2.....5.....8.6......1....'
519 | export const parsedGrid2 = {
520 | A1: '6',
521 | A2: '1579',
522 | A3: '12579',
523 | A4: '149',
524 | A5: '2459',
525 | A6: '259',
526 | A7: '8',
527 | A8: '1249',
528 | A9: '3',
529 | B1: '2589',
530 | B2: '4',
531 | B3: '123589',
532 | B4: '7',
533 | B5: '23569',
534 | B6: '235689',
535 | B7: '1259',
536 | B8: '1269',
537 | B9: '12569',
538 | C1: '25789',
539 | C2: '135789',
540 | C3: '1235789',
541 | C4: '134689',
542 | C5: '234569',
543 | C6: '235689',
544 | C7: '124579',
545 | C8: '12469',
546 | C9: '1245679',
547 | D1: '289',
548 | D2: '89',
549 | D3: '289',
550 | D4: '5',
551 | D5: '369',
552 | D6: '4',
553 | D7: '1239',
554 | D8: '7',
555 | D9: '12689',
556 | E1: '3',
557 | E2: '5789',
558 | E3: '4',
559 | E4: '2',
560 | E5: '679',
561 | E6: '1',
562 | E7: '59',
563 | E8: '689',
564 | E9: '5689',
565 | F1: '1',
566 | F2: '5789',
567 | F3: '6',
568 | F4: '389',
569 | F5: '379',
570 | F6: '3789',
571 | F7: '23459',
572 | F8: '23489',
573 | F9: '24589',
574 | G1: '4789',
575 | G2: '2',
576 | G3: '13789',
577 | G4: '3469',
578 | G5: '34679',
579 | G6: '3679',
580 | G7: '13479',
581 | G8: '5',
582 | G9: '14789',
583 | H1: '4579',
584 | H2: '13579',
585 | H3: '13579',
586 | H4: '349',
587 | H5: '8',
588 | H6: '23579',
589 | H7: '6',
590 | H8: '12349',
591 | H9: '12479',
592 | I1: '45789',
593 | I2: '6',
594 | I3: '35789',
595 | I4: '349',
596 | I5: '1',
597 | I6: '23579',
598 | I7: '23479',
599 | I8: '23489',
600 | I9: '24789',
601 | };
602 |
603 | export const parserGrid3 = {
604 | A1: '4',
605 | A2: '1679',
606 | A3: '12679',
607 | A4: '139',
608 | A5: '2369',
609 | A6: '269',
610 | A7: '8',
611 | A8: '1239',
612 | A9: '5',
613 | B1: '26789',
614 | B2: '3',
615 | B3: '1256789',
616 | B4: '14589',
617 | B5: '24569',
618 | B6: '245689',
619 | B7: '12679',
620 | B8: '1249',
621 | B9: '124679',
622 | C1: '2689',
623 | C2: '15689',
624 | C3: '125689',
625 | C4: '7',
626 | C5: '234569',
627 | C6: '245689',
628 | C7: '12369',
629 | C8: '12349',
630 | C9: '123469',
631 | D1: '3789',
632 | D2: '2',
633 | D3: '15789',
634 | D4: '3459',
635 | D5: '34579',
636 | D6: '4579',
637 | D7: '13579',
638 | D8: '6',
639 | D9: '13789',
640 | E1: '3679',
641 | E2: '15679',
642 | E3: '15679',
643 | E4: '359',
644 | E5: '8',
645 | E6: '25679',
646 | E7: '4',
647 | E8: '12359',
648 | E9: '12379',
649 | F1: '36789',
650 | F2: '4',
651 | F3: '56789',
652 | F4: '359',
653 | F5: '1',
654 | F6: '25679',
655 | F7: '23579',
656 | F8: '23589',
657 | F9: '23789',
658 | G1: '289',
659 | G2: '89',
660 | G3: '289',
661 | G4: '6',
662 | G5: '459',
663 | G6: '3',
664 | G7: '1259',
665 | G8: '7',
666 | G9: '12489',
667 | H1: '5',
668 | H2: '6789',
669 | H3: '3',
670 | H4: '2',
671 | H5: '479',
672 | H6: '1',
673 | H7: '69',
674 | H8: '489',
675 | H9: '4689',
676 | I1: '1',
677 | I2: '6789',
678 | I3: '4',
679 | I4: '589',
680 | I5: '579',
681 | I6: '5789',
682 | I7: '23569',
683 | I8: '23589',
684 | I9: '23689',
685 | };
686 |
687 | export const C2Unit = [
688 | ['A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'I2'],
689 | ['C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9'],
690 | ['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3'],
691 | ];
692 |
693 | export const C2Peers = {
694 | A2: true,
695 | B2: true,
696 | D2: true,
697 | E2: true,
698 | F2: true,
699 | G2: true,
700 | H2: true,
701 | I2: true,
702 | C1: true,
703 | C3: true,
704 | C4: true,
705 | C5: true,
706 | C6: true,
707 | C7: true,
708 | C8: true,
709 | C9: true,
710 | A1: true,
711 | A3: true,
712 | B1: true,
713 | B3: true,
714 | };
715 |
716 | export const units = {
717 | A1: [
718 | ['A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1', 'H1', 'I1'],
719 | ['A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'A9'],
720 | ['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3'],
721 | ],
722 | A2: [
723 | ['A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'I2'],
724 | ['A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'A9'],
725 | ['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3'],
726 | ],
727 | A3: [
728 | ['A3', 'B3', 'C3', 'D3', 'E3', 'F3', 'G3', 'H3', 'I3'],
729 | ['A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'A9'],
730 | ['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3'],
731 | ],
732 | A4: [
733 | ['A4', 'B4', 'C4', 'D4', 'E4', 'F4', 'G4', 'H4', 'I4'],
734 | ['A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'A9'],
735 | ['A4', 'A5', 'A6', 'B4', 'B5', 'B6', 'C4', 'C5', 'C6'],
736 | ],
737 | A5: [
738 | ['A5', 'B5', 'C5', 'D5', 'E5', 'F5', 'G5', 'H5', 'I5'],
739 | ['A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'A9'],
740 | ['A4', 'A5', 'A6', 'B4', 'B5', 'B6', 'C4', 'C5', 'C6'],
741 | ],
742 | A6: [
743 | ['A6', 'B6', 'C6', 'D6', 'E6', 'F6', 'G6', 'H6', 'I6'],
744 | ['A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'A9'],
745 | ['A4', 'A5', 'A6', 'B4', 'B5', 'B6', 'C4', 'C5', 'C6'],
746 | ],
747 | A7: [
748 | ['A7', 'B7', 'C7', 'D7', 'E7', 'F7', 'G7', 'H7', 'I7'],
749 | ['A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'A9'],
750 | ['A7', 'A8', 'A9', 'B7', 'B8', 'B9', 'C7', 'C8', 'C9'],
751 | ],
752 | A8: [
753 | ['A8', 'B8', 'C8', 'D8', 'E8', 'F8', 'G8', 'H8', 'I8'],
754 | ['A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'A9'],
755 | ['A7', 'A8', 'A9', 'B7', 'B8', 'B9', 'C7', 'C8', 'C9'],
756 | ],
757 | A9: [
758 | ['A9', 'B9', 'C9', 'D9', 'E9', 'F9', 'G9', 'H9', 'I9'],
759 | ['A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'A9'],
760 | ['A7', 'A8', 'A9', 'B7', 'B8', 'B9', 'C7', 'C8', 'C9'],
761 | ],
762 | B1: [
763 | ['A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1', 'H1', 'I1'],
764 | ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B9'],
765 | ['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3'],
766 | ],
767 | B2: [
768 | ['A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'I2'],
769 | ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B9'],
770 | ['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3'],
771 | ],
772 | B3: [
773 | ['A3', 'B3', 'C3', 'D3', 'E3', 'F3', 'G3', 'H3', 'I3'],
774 | ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B9'],
775 | ['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3'],
776 | ],
777 | B4: [
778 | ['A4', 'B4', 'C4', 'D4', 'E4', 'F4', 'G4', 'H4', 'I4'],
779 | ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B9'],
780 | ['A4', 'A5', 'A6', 'B4', 'B5', 'B6', 'C4', 'C5', 'C6'],
781 | ],
782 | B5: [
783 | ['A5', 'B5', 'C5', 'D5', 'E5', 'F5', 'G5', 'H5', 'I5'],
784 | ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B9'],
785 | ['A4', 'A5', 'A6', 'B4', 'B5', 'B6', 'C4', 'C5', 'C6'],
786 | ],
787 | B6: [
788 | ['A6', 'B6', 'C6', 'D6', 'E6', 'F6', 'G6', 'H6', 'I6'],
789 | ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B9'],
790 | ['A4', 'A5', 'A6', 'B4', 'B5', 'B6', 'C4', 'C5', 'C6'],
791 | ],
792 | B7: [
793 | ['A7', 'B7', 'C7', 'D7', 'E7', 'F7', 'G7', 'H7', 'I7'],
794 | ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B9'],
795 | ['A7', 'A8', 'A9', 'B7', 'B8', 'B9', 'C7', 'C8', 'C9'],
796 | ],
797 | B8: [
798 | ['A8', 'B8', 'C8', 'D8', 'E8', 'F8', 'G8', 'H8', 'I8'],
799 | ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B9'],
800 | ['A7', 'A8', 'A9', 'B7', 'B8', 'B9', 'C7', 'C8', 'C9'],
801 | ],
802 | B9: [
803 | ['A9', 'B9', 'C9', 'D9', 'E9', 'F9', 'G9', 'H9', 'I9'],
804 | ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B9'],
805 | ['A7', 'A8', 'A9', 'B7', 'B8', 'B9', 'C7', 'C8', 'C9'],
806 | ],
807 | C1: [
808 | ['A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1', 'H1', 'I1'],
809 | ['C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9'],
810 | ['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3'],
811 | ],
812 | C2: [
813 | ['A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'I2'],
814 | ['C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9'],
815 | ['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3'],
816 | ],
817 | C3: [
818 | ['A3', 'B3', 'C3', 'D3', 'E3', 'F3', 'G3', 'H3', 'I3'],
819 | ['C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9'],
820 | ['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3'],
821 | ],
822 | C4: [
823 | ['A4', 'B4', 'C4', 'D4', 'E4', 'F4', 'G4', 'H4', 'I4'],
824 | ['C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9'],
825 | ['A4', 'A5', 'A6', 'B4', 'B5', 'B6', 'C4', 'C5', 'C6'],
826 | ],
827 | C5: [
828 | ['A5', 'B5', 'C5', 'D5', 'E5', 'F5', 'G5', 'H5', 'I5'],
829 | ['C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9'],
830 | ['A4', 'A5', 'A6', 'B4', 'B5', 'B6', 'C4', 'C5', 'C6'],
831 | ],
832 | C6: [
833 | ['A6', 'B6', 'C6', 'D6', 'E6', 'F6', 'G6', 'H6', 'I6'],
834 | ['C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9'],
835 | ['A4', 'A5', 'A6', 'B4', 'B5', 'B6', 'C4', 'C5', 'C6'],
836 | ],
837 | C7: [
838 | ['A7', 'B7', 'C7', 'D7', 'E7', 'F7', 'G7', 'H7', 'I7'],
839 | ['C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9'],
840 | ['A7', 'A8', 'A9', 'B7', 'B8', 'B9', 'C7', 'C8', 'C9'],
841 | ],
842 | C8: [
843 | ['A8', 'B8', 'C8', 'D8', 'E8', 'F8', 'G8', 'H8', 'I8'],
844 | ['C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9'],
845 | ['A7', 'A8', 'A9', 'B7', 'B8', 'B9', 'C7', 'C8', 'C9'],
846 | ],
847 | C9: [
848 | ['A9', 'B9', 'C9', 'D9', 'E9', 'F9', 'G9', 'H9', 'I9'],
849 | ['C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9'],
850 | ['A7', 'A8', 'A9', 'B7', 'B8', 'B9', 'C7', 'C8', 'C9'],
851 | ],
852 | D1: [
853 | ['A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1', 'H1', 'I1'],
854 | ['D1', 'D2', 'D3', 'D4', 'D5', 'D6', 'D7', 'D8', 'D9'],
855 | ['D1', 'D2', 'D3', 'E1', 'E2', 'E3', 'F1', 'F2', 'F3'],
856 | ],
857 | D2: [
858 | ['A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'I2'],
859 | ['D1', 'D2', 'D3', 'D4', 'D5', 'D6', 'D7', 'D8', 'D9'],
860 | ['D1', 'D2', 'D3', 'E1', 'E2', 'E3', 'F1', 'F2', 'F3'],
861 | ],
862 | D3: [
863 | ['A3', 'B3', 'C3', 'D3', 'E3', 'F3', 'G3', 'H3', 'I3'],
864 | ['D1', 'D2', 'D3', 'D4', 'D5', 'D6', 'D7', 'D8', 'D9'],
865 | ['D1', 'D2', 'D3', 'E1', 'E2', 'E3', 'F1', 'F2', 'F3'],
866 | ],
867 | D4: [
868 | ['A4', 'B4', 'C4', 'D4', 'E4', 'F4', 'G4', 'H4', 'I4'],
869 | ['D1', 'D2', 'D3', 'D4', 'D5', 'D6', 'D7', 'D8', 'D9'],
870 | ['D4', 'D5', 'D6', 'E4', 'E5', 'E6', 'F4', 'F5', 'F6'],
871 | ],
872 | D5: [
873 | ['A5', 'B5', 'C5', 'D5', 'E5', 'F5', 'G5', 'H5', 'I5'],
874 | ['D1', 'D2', 'D3', 'D4', 'D5', 'D6', 'D7', 'D8', 'D9'],
875 | ['D4', 'D5', 'D6', 'E4', 'E5', 'E6', 'F4', 'F5', 'F6'],
876 | ],
877 | D6: [
878 | ['A6', 'B6', 'C6', 'D6', 'E6', 'F6', 'G6', 'H6', 'I6'],
879 | ['D1', 'D2', 'D3', 'D4', 'D5', 'D6', 'D7', 'D8', 'D9'],
880 | ['D4', 'D5', 'D6', 'E4', 'E5', 'E6', 'F4', 'F5', 'F6'],
881 | ],
882 | D7: [
883 | ['A7', 'B7', 'C7', 'D7', 'E7', 'F7', 'G7', 'H7', 'I7'],
884 | ['D1', 'D2', 'D3', 'D4', 'D5', 'D6', 'D7', 'D8', 'D9'],
885 | ['D7', 'D8', 'D9', 'E7', 'E8', 'E9', 'F7', 'F8', 'F9'],
886 | ],
887 | D8: [
888 | ['A8', 'B8', 'C8', 'D8', 'E8', 'F8', 'G8', 'H8', 'I8'],
889 | ['D1', 'D2', 'D3', 'D4', 'D5', 'D6', 'D7', 'D8', 'D9'],
890 | ['D7', 'D8', 'D9', 'E7', 'E8', 'E9', 'F7', 'F8', 'F9'],
891 | ],
892 | D9: [
893 | ['A9', 'B9', 'C9', 'D9', 'E9', 'F9', 'G9', 'H9', 'I9'],
894 | ['D1', 'D2', 'D3', 'D4', 'D5', 'D6', 'D7', 'D8', 'D9'],
895 | ['D7', 'D8', 'D9', 'E7', 'E8', 'E9', 'F7', 'F8', 'F9'],
896 | ],
897 | E1: [
898 | ['A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1', 'H1', 'I1'],
899 | ['E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'E7', 'E8', 'E9'],
900 | ['D1', 'D2', 'D3', 'E1', 'E2', 'E3', 'F1', 'F2', 'F3'],
901 | ],
902 | E2: [
903 | ['A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'I2'],
904 | ['E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'E7', 'E8', 'E9'],
905 | ['D1', 'D2', 'D3', 'E1', 'E2', 'E3', 'F1', 'F2', 'F3'],
906 | ],
907 | E3: [
908 | ['A3', 'B3', 'C3', 'D3', 'E3', 'F3', 'G3', 'H3', 'I3'],
909 | ['E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'E7', 'E8', 'E9'],
910 | ['D1', 'D2', 'D3', 'E1', 'E2', 'E3', 'F1', 'F2', 'F3'],
911 | ],
912 | E4: [
913 | ['A4', 'B4', 'C4', 'D4', 'E4', 'F4', 'G4', 'H4', 'I4'],
914 | ['E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'E7', 'E8', 'E9'],
915 | ['D4', 'D5', 'D6', 'E4', 'E5', 'E6', 'F4', 'F5', 'F6'],
916 | ],
917 | E5: [
918 | ['A5', 'B5', 'C5', 'D5', 'E5', 'F5', 'G5', 'H5', 'I5'],
919 | ['E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'E7', 'E8', 'E9'],
920 | ['D4', 'D5', 'D6', 'E4', 'E5', 'E6', 'F4', 'F5', 'F6'],
921 | ],
922 | E6: [
923 | ['A6', 'B6', 'C6', 'D6', 'E6', 'F6', 'G6', 'H6', 'I6'],
924 | ['E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'E7', 'E8', 'E9'],
925 | ['D4', 'D5', 'D6', 'E4', 'E5', 'E6', 'F4', 'F5', 'F6'],
926 | ],
927 | E7: [
928 | ['A7', 'B7', 'C7', 'D7', 'E7', 'F7', 'G7', 'H7', 'I7'],
929 | ['E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'E7', 'E8', 'E9'],
930 | ['D7', 'D8', 'D9', 'E7', 'E8', 'E9', 'F7', 'F8', 'F9'],
931 | ],
932 | E8: [
933 | ['A8', 'B8', 'C8', 'D8', 'E8', 'F8', 'G8', 'H8', 'I8'],
934 | ['E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'E7', 'E8', 'E9'],
935 | ['D7', 'D8', 'D9', 'E7', 'E8', 'E9', 'F7', 'F8', 'F9'],
936 | ],
937 | E9: [
938 | ['A9', 'B9', 'C9', 'D9', 'E9', 'F9', 'G9', 'H9', 'I9'],
939 | ['E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'E7', 'E8', 'E9'],
940 | ['D7', 'D8', 'D9', 'E7', 'E8', 'E9', 'F7', 'F8', 'F9'],
941 | ],
942 | F1: [
943 | ['A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1', 'H1', 'I1'],
944 | ['F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9'],
945 | ['D1', 'D2', 'D3', 'E1', 'E2', 'E3', 'F1', 'F2', 'F3'],
946 | ],
947 | F2: [
948 | ['A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'I2'],
949 | ['F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9'],
950 | ['D1', 'D2', 'D3', 'E1', 'E2', 'E3', 'F1', 'F2', 'F3'],
951 | ],
952 | F3: [
953 | ['A3', 'B3', 'C3', 'D3', 'E3', 'F3', 'G3', 'H3', 'I3'],
954 | ['F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9'],
955 | ['D1', 'D2', 'D3', 'E1', 'E2', 'E3', 'F1', 'F2', 'F3'],
956 | ],
957 | F4: [
958 | ['A4', 'B4', 'C4', 'D4', 'E4', 'F4', 'G4', 'H4', 'I4'],
959 | ['F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9'],
960 | ['D4', 'D5', 'D6', 'E4', 'E5', 'E6', 'F4', 'F5', 'F6'],
961 | ],
962 | F5: [
963 | ['A5', 'B5', 'C5', 'D5', 'E5', 'F5', 'G5', 'H5', 'I5'],
964 | ['F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9'],
965 | ['D4', 'D5', 'D6', 'E4', 'E5', 'E6', 'F4', 'F5', 'F6'],
966 | ],
967 | F6: [
968 | ['A6', 'B6', 'C6', 'D6', 'E6', 'F6', 'G6', 'H6', 'I6'],
969 | ['F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9'],
970 | ['D4', 'D5', 'D6', 'E4', 'E5', 'E6', 'F4', 'F5', 'F6'],
971 | ],
972 | F7: [
973 | ['A7', 'B7', 'C7', 'D7', 'E7', 'F7', 'G7', 'H7', 'I7'],
974 | ['F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9'],
975 | ['D7', 'D8', 'D9', 'E7', 'E8', 'E9', 'F7', 'F8', 'F9'],
976 | ],
977 | F8: [
978 | ['A8', 'B8', 'C8', 'D8', 'E8', 'F8', 'G8', 'H8', 'I8'],
979 | ['F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9'],
980 | ['D7', 'D8', 'D9', 'E7', 'E8', 'E9', 'F7', 'F8', 'F9'],
981 | ],
982 | F9: [
983 | ['A9', 'B9', 'C9', 'D9', 'E9', 'F9', 'G9', 'H9', 'I9'],
984 | ['F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9'],
985 | ['D7', 'D8', 'D9', 'E7', 'E8', 'E9', 'F7', 'F8', 'F9'],
986 | ],
987 | G1: [
988 | ['A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1', 'H1', 'I1'],
989 | ['G1', 'G2', 'G3', 'G4', 'G5', 'G6', 'G7', 'G8', 'G9'],
990 | ['G1', 'G2', 'G3', 'H1', 'H2', 'H3', 'I1', 'I2', 'I3'],
991 | ],
992 | G2: [
993 | ['A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'I2'],
994 | ['G1', 'G2', 'G3', 'G4', 'G5', 'G6', 'G7', 'G8', 'G9'],
995 | ['G1', 'G2', 'G3', 'H1', 'H2', 'H3', 'I1', 'I2', 'I3'],
996 | ],
997 | G3: [
998 | ['A3', 'B3', 'C3', 'D3', 'E3', 'F3', 'G3', 'H3', 'I3'],
999 | ['G1', 'G2', 'G3', 'G4', 'G5', 'G6', 'G7', 'G8', 'G9'],
1000 | ['G1', 'G2', 'G3', 'H1', 'H2', 'H3', 'I1', 'I2', 'I3'],
1001 | ],
1002 | G4: [
1003 | ['A4', 'B4', 'C4', 'D4', 'E4', 'F4', 'G4', 'H4', 'I4'],
1004 | ['G1', 'G2', 'G3', 'G4', 'G5', 'G6', 'G7', 'G8', 'G9'],
1005 | ['G4', 'G5', 'G6', 'H4', 'H5', 'H6', 'I4', 'I5', 'I6'],
1006 | ],
1007 | G5: [
1008 | ['A5', 'B5', 'C5', 'D5', 'E5', 'F5', 'G5', 'H5', 'I5'],
1009 | ['G1', 'G2', 'G3', 'G4', 'G5', 'G6', 'G7', 'G8', 'G9'],
1010 | ['G4', 'G5', 'G6', 'H4', 'H5', 'H6', 'I4', 'I5', 'I6'],
1011 | ],
1012 | G6: [
1013 | ['A6', 'B6', 'C6', 'D6', 'E6', 'F6', 'G6', 'H6', 'I6'],
1014 | ['G1', 'G2', 'G3', 'G4', 'G5', 'G6', 'G7', 'G8', 'G9'],
1015 | ['G4', 'G5', 'G6', 'H4', 'H5', 'H6', 'I4', 'I5', 'I6'],
1016 | ],
1017 | G7: [
1018 | ['A7', 'B7', 'C7', 'D7', 'E7', 'F7', 'G7', 'H7', 'I7'],
1019 | ['G1', 'G2', 'G3', 'G4', 'G5', 'G6', 'G7', 'G8', 'G9'],
1020 | ['G7', 'G8', 'G9', 'H7', 'H8', 'H9', 'I7', 'I8', 'I9'],
1021 | ],
1022 | G8: [
1023 | ['A8', 'B8', 'C8', 'D8', 'E8', 'F8', 'G8', 'H8', 'I8'],
1024 | ['G1', 'G2', 'G3', 'G4', 'G5', 'G6', 'G7', 'G8', 'G9'],
1025 | ['G7', 'G8', 'G9', 'H7', 'H8', 'H9', 'I7', 'I8', 'I9'],
1026 | ],
1027 | G9: [
1028 | ['A9', 'B9', 'C9', 'D9', 'E9', 'F9', 'G9', 'H9', 'I9'],
1029 | ['G1', 'G2', 'G3', 'G4', 'G5', 'G6', 'G7', 'G8', 'G9'],
1030 | ['G7', 'G8', 'G9', 'H7', 'H8', 'H9', 'I7', 'I8', 'I9'],
1031 | ],
1032 | H1: [
1033 | ['A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1', 'H1', 'I1'],
1034 | ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'H7', 'H8', 'H9'],
1035 | ['G1', 'G2', 'G3', 'H1', 'H2', 'H3', 'I1', 'I2', 'I3'],
1036 | ],
1037 | H2: [
1038 | ['A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'I2'],
1039 | ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'H7', 'H8', 'H9'],
1040 | ['G1', 'G2', 'G3', 'H1', 'H2', 'H3', 'I1', 'I2', 'I3'],
1041 | ],
1042 | H3: [
1043 | ['A3', 'B3', 'C3', 'D3', 'E3', 'F3', 'G3', 'H3', 'I3'],
1044 | ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'H7', 'H8', 'H9'],
1045 | ['G1', 'G2', 'G3', 'H1', 'H2', 'H3', 'I1', 'I2', 'I3'],
1046 | ],
1047 | H4: [
1048 | ['A4', 'B4', 'C4', 'D4', 'E4', 'F4', 'G4', 'H4', 'I4'],
1049 | ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'H7', 'H8', 'H9'],
1050 | ['G4', 'G5', 'G6', 'H4', 'H5', 'H6', 'I4', 'I5', 'I6'],
1051 | ],
1052 | H5: [
1053 | ['A5', 'B5', 'C5', 'D5', 'E5', 'F5', 'G5', 'H5', 'I5'],
1054 | ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'H7', 'H8', 'H9'],
1055 | ['G4', 'G5', 'G6', 'H4', 'H5', 'H6', 'I4', 'I5', 'I6'],
1056 | ],
1057 | H6: [
1058 | ['A6', 'B6', 'C6', 'D6', 'E6', 'F6', 'G6', 'H6', 'I6'],
1059 | ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'H7', 'H8', 'H9'],
1060 | ['G4', 'G5', 'G6', 'H4', 'H5', 'H6', 'I4', 'I5', 'I6'],
1061 | ],
1062 | H7: [
1063 | ['A7', 'B7', 'C7', 'D7', 'E7', 'F7', 'G7', 'H7', 'I7'],
1064 | ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'H7', 'H8', 'H9'],
1065 | ['G7', 'G8', 'G9', 'H7', 'H8', 'H9', 'I7', 'I8', 'I9'],
1066 | ],
1067 | H8: [
1068 | ['A8', 'B8', 'C8', 'D8', 'E8', 'F8', 'G8', 'H8', 'I8'],
1069 | ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'H7', 'H8', 'H9'],
1070 | ['G7', 'G8', 'G9', 'H7', 'H8', 'H9', 'I7', 'I8', 'I9'],
1071 | ],
1072 | H9: [
1073 | ['A9', 'B9', 'C9', 'D9', 'E9', 'F9', 'G9', 'H9', 'I9'],
1074 | ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'H7', 'H8', 'H9'],
1075 | ['G7', 'G8', 'G9', 'H7', 'H8', 'H9', 'I7', 'I8', 'I9'],
1076 | ],
1077 | I1: [
1078 | ['A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1', 'H1', 'I1'],
1079 | ['I1', 'I2', 'I3', 'I4', 'I5', 'I6', 'I7', 'I8', 'I9'],
1080 | ['G1', 'G2', 'G3', 'H1', 'H2', 'H3', 'I1', 'I2', 'I3'],
1081 | ],
1082 | I2: [
1083 | ['A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'I2'],
1084 | ['I1', 'I2', 'I3', 'I4', 'I5', 'I6', 'I7', 'I8', 'I9'],
1085 | ['G1', 'G2', 'G3', 'H1', 'H2', 'H3', 'I1', 'I2', 'I3'],
1086 | ],
1087 | I3: [
1088 | ['A3', 'B3', 'C3', 'D3', 'E3', 'F3', 'G3', 'H3', 'I3'],
1089 | ['I1', 'I2', 'I3', 'I4', 'I5', 'I6', 'I7', 'I8', 'I9'],
1090 | ['G1', 'G2', 'G3', 'H1', 'H2', 'H3', 'I1', 'I2', 'I3'],
1091 | ],
1092 | I4: [
1093 | ['A4', 'B4', 'C4', 'D4', 'E4', 'F4', 'G4', 'H4', 'I4'],
1094 | ['I1', 'I2', 'I3', 'I4', 'I5', 'I6', 'I7', 'I8', 'I9'],
1095 | ['G4', 'G5', 'G6', 'H4', 'H5', 'H6', 'I4', 'I5', 'I6'],
1096 | ],
1097 | I5: [
1098 | ['A5', 'B5', 'C5', 'D5', 'E5', 'F5', 'G5', 'H5', 'I5'],
1099 | ['I1', 'I2', 'I3', 'I4', 'I5', 'I6', 'I7', 'I8', 'I9'],
1100 | ['G4', 'G5', 'G6', 'H4', 'H5', 'H6', 'I4', 'I5', 'I6'],
1101 | ],
1102 | I6: [
1103 | ['A6', 'B6', 'C6', 'D6', 'E6', 'F6', 'G6', 'H6', 'I6'],
1104 | ['I1', 'I2', 'I3', 'I4', 'I5', 'I6', 'I7', 'I8', 'I9'],
1105 | ['G4', 'G5', 'G6', 'H4', 'H5', 'H6', 'I4', 'I5', 'I6'],
1106 | ],
1107 | I7: [
1108 | ['A7', 'B7', 'C7', 'D7', 'E7', 'F7', 'G7', 'H7', 'I7'],
1109 | ['I1', 'I2', 'I3', 'I4', 'I5', 'I6', 'I7', 'I8', 'I9'],
1110 | ['G7', 'G8', 'G9', 'H7', 'H8', 'H9', 'I7', 'I8', 'I9'],
1111 | ],
1112 | I8: [
1113 | ['A8', 'B8', 'C8', 'D8', 'E8', 'F8', 'G8', 'H8', 'I8'],
1114 | ['I1', 'I2', 'I3', 'I4', 'I5', 'I6', 'I7', 'I8', 'I9'],
1115 | ['G7', 'G8', 'G9', 'H7', 'H8', 'H9', 'I7', 'I8', 'I9'],
1116 | ],
1117 | };
1118 |
1119 | // '52...6.........7.13...........4..8..6......5...........418.........3..2...87.....'
1120 | export const puzzle1Solved = {
1121 | A1: '5',
1122 | A2: '2',
1123 | A3: '7',
1124 | A4: '3',
1125 | A5: '1',
1126 | A6: '6',
1127 | A7: '4',
1128 | A8: '8',
1129 | A9: '9',
1130 | B1: '8',
1131 | B2: '9',
1132 | B3: '6',
1133 | B4: '5',
1134 | B5: '4',
1135 | B6: '2',
1136 | B7: '7',
1137 | B8: '3',
1138 | B9: '1',
1139 | C1: '3',
1140 | C2: '1',
1141 | C3: '4',
1142 | C4: '9',
1143 | C5: '8',
1144 | C6: '7',
1145 | C7: '5',
1146 | C8: '6',
1147 | C9: '2',
1148 | D1: '1',
1149 | D2: '7',
1150 | D3: '2',
1151 | D4: '4',
1152 | D5: '5',
1153 | D6: '3',
1154 | D7: '8',
1155 | D8: '9',
1156 | D9: '6',
1157 | E1: '6',
1158 | E2: '8',
1159 | E3: '9',
1160 | E4: '2',
1161 | E5: '7',
1162 | E6: '1',
1163 | E7: '3',
1164 | E8: '5',
1165 | E9: '4',
1166 | F1: '4',
1167 | F2: '5',
1168 | F3: '3',
1169 | F4: '6',
1170 | F5: '9',
1171 | F6: '8',
1172 | F7: '2',
1173 | F8: '1',
1174 | F9: '7',
1175 | G1: '9',
1176 | G2: '4',
1177 | G3: '1',
1178 | G4: '8',
1179 | G5: '2',
1180 | G6: '5',
1181 | G7: '6',
1182 | G8: '7',
1183 | G9: '3',
1184 | H1: '7',
1185 | H2: '6',
1186 | H3: '5',
1187 | H4: '1',
1188 | H5: '3',
1189 | H6: '4',
1190 | H7: '9',
1191 | H8: '2',
1192 | H9: '8',
1193 | I1: '2',
1194 | I2: '3',
1195 | I3: '8',
1196 | I4: '7',
1197 | I5: '6',
1198 | I6: '9',
1199 | I7: '1',
1200 | I8: '4',
1201 | I9: '5',
1202 | };
1203 |
1204 | // '6.....8.3.4.7.................5.4.7.3..2.....1.6.......2.....5.....8.6......1....'
1205 | export const puzzle2Solved = {
1206 | A1: '6',
1207 | A2: '1',
1208 | A3: '7',
1209 | A4: '4',
1210 | A5: '5',
1211 | A6: '9',
1212 | A7: '8',
1213 | A8: '2',
1214 | A9: '3',
1215 | B1: '2',
1216 | B2: '4',
1217 | B3: '8',
1218 | B4: '7',
1219 | B5: '3',
1220 | B6: '6',
1221 | B7: '9',
1222 | B8: '1',
1223 | B9: '5',
1224 | C1: '5',
1225 | C2: '3',
1226 | C3: '9',
1227 | C4: '1',
1228 | C5: '2',
1229 | C6: '8',
1230 | C7: '4',
1231 | C8: '6',
1232 | C9: '7',
1233 | D1: '9',
1234 | D2: '8',
1235 | D3: '2',
1236 | D4: '5',
1237 | D5: '6',
1238 | D6: '4',
1239 | D7: '3',
1240 | D8: '7',
1241 | D9: '1',
1242 | E1: '3',
1243 | E2: '7',
1244 | E3: '4',
1245 | E4: '2',
1246 | E5: '9',
1247 | E6: '1',
1248 | E7: '5',
1249 | E8: '8',
1250 | E9: '6',
1251 | F1: '1',
1252 | F2: '5',
1253 | F3: '6',
1254 | F4: '8',
1255 | F5: '7',
1256 | F6: '3',
1257 | F7: '2',
1258 | F8: '9',
1259 | F9: '4',
1260 | G1: '8',
1261 | G2: '2',
1262 | G3: '3',
1263 | G4: '6',
1264 | G5: '4',
1265 | G6: '7',
1266 | G7: '1',
1267 | G8: '5',
1268 | G9: '9',
1269 | H1: '7',
1270 | H2: '9',
1271 | H3: '1',
1272 | H4: '3',
1273 | H5: '8',
1274 | H6: '5',
1275 | H7: '6',
1276 | H8: '4',
1277 | H9: '2',
1278 | I1: '4',
1279 | I2: '6',
1280 | I3: '5',
1281 | I4: '9',
1282 | I5: '1',
1283 | I6: '2',
1284 | I7: '7',
1285 | I8: '3',
1286 | I9: '8',
1287 | };
1288 |
1289 | export const boardStringPuzzle1 =
1290 | '5 2 7 |3 1 6 |4 8 9 \n' +
1291 | '8 9 6 |5 4 2 |7 3 1 \n' +
1292 | '3 1 4 |9 8 7 |5 6 2 \n' +
1293 | '------+------+------\n' +
1294 | '1 7 2 |4 5 3 |8 9 6 \n' +
1295 | '6 8 9 |2 7 1 |3 5 4 \n' +
1296 | '4 5 3 |6 9 8 |2 1 7 \n' +
1297 | '------+------+------\n' +
1298 | '9 4 1 |8 2 5 |6 7 3 \n' +
1299 | '7 6 5 |1 3 4 |9 2 8 \n' +
1300 | '2 3 8 |7 6 9 |1 4 5 ';
1301 |
1302 | export const initialValuesState = {
1303 | sections: [
1304 | [
1305 | {
1306 | cellRows: [
1307 | [
1308 | { key: 'A1', value: '4' },
1309 | { key: 'A2', value: '.' },
1310 | { key: 'A3', value: '.' },
1311 | ],
1312 | [
1313 | { key: 'B1', value: '.' },
1314 | { key: 'B2', value: '3' },
1315 | { key: 'B3', value: '.' },
1316 | ],
1317 | [
1318 | { key: 'C1', value: '.' },
1319 | { key: 'C2', value: '.' },
1320 | { key: 'C3', value: '.' },
1321 | ],
1322 | ],
1323 | key: '1',
1324 | },
1325 | {
1326 | cellRows: [
1327 | [
1328 | { key: 'A4', value: '.' },
1329 | { key: 'A5', value: '.' },
1330 | { key: 'A6', value: '.' },
1331 | ],
1332 | [
1333 | { key: 'B4', value: '.' },
1334 | { key: 'B5', value: '.' },
1335 | { key: 'B6', value: '.' },
1336 | ],
1337 | [
1338 | { key: 'C4', value: '7' },
1339 | { key: 'C5', value: '.' },
1340 | { key: 'C6', value: '.' },
1341 | ],
1342 | ],
1343 | key: '2',
1344 | },
1345 | {
1346 | cellRows: [
1347 | [
1348 | { key: 'A7', value: '8' },
1349 | { key: 'A8', value: '.' },
1350 | { key: 'A9', value: '5' },
1351 | ],
1352 | [
1353 | { key: 'B7', value: '.' },
1354 | { key: 'B8', value: '.' },
1355 | { key: 'B9', value: '.' },
1356 | ],
1357 | [
1358 | { key: 'C7', value: '.' },
1359 | { key: 'C8', value: '.' },
1360 | { key: 'C9', value: '.' },
1361 | ],
1362 | ],
1363 | key: '3',
1364 | },
1365 | ],
1366 | [
1367 | {
1368 | cellRows: [
1369 | [
1370 | { key: 'D1', value: '.' },
1371 | { key: 'D2', value: '2' },
1372 | { key: 'D3', value: '.' },
1373 | ],
1374 | [
1375 | { key: 'E1', value: '.' },
1376 | { key: 'E2', value: '.' },
1377 | { key: 'E3', value: '.' },
1378 | ],
1379 | [
1380 | { key: 'F1', value: '.' },
1381 | { key: 'F2', value: '4' },
1382 | { key: 'F3', value: '.' },
1383 | ],
1384 | ],
1385 | key: '4',
1386 | },
1387 | {
1388 | cellRows: [
1389 | [
1390 | { key: 'D4', value: '.' },
1391 | { key: 'D5', value: '.' },
1392 | { key: 'D6', value: '.' },
1393 | ],
1394 | [
1395 | { key: 'E4', value: '.' },
1396 | { key: 'E5', value: '8' },
1397 | { key: 'E6', value: '.' },
1398 | ],
1399 | [
1400 | { key: 'F4', value: '.' },
1401 | { key: 'F5', value: '1' },
1402 | { key: 'F6', value: '.' },
1403 | ],
1404 | ],
1405 | key: '5',
1406 | },
1407 | {
1408 | cellRows: [
1409 | [
1410 | { key: 'D7', value: '.' },
1411 | { key: 'D8', value: '6' },
1412 | { key: 'D9', value: '.' },
1413 | ],
1414 | [
1415 | { key: 'E7', value: '4' },
1416 | { key: 'E8', value: '.' },
1417 | { key: 'E9', value: '.' },
1418 | ],
1419 | [
1420 | { key: 'F7', value: '.' },
1421 | { key: 'F8', value: '.' },
1422 | { key: 'F9', value: '.' },
1423 | ],
1424 | ],
1425 | key: '6',
1426 | },
1427 | ],
1428 | [
1429 | {
1430 | cellRows: [
1431 | [
1432 | { key: 'G1', value: '.' },
1433 | { key: 'G2', value: '.' },
1434 | { key: 'G3', value: '.' },
1435 | ],
1436 | [
1437 | { key: 'H1', value: '5' },
1438 | { key: 'H2', value: '.' },
1439 | { key: 'H3', value: '3' },
1440 | ],
1441 | [
1442 | { key: 'I1', value: '1' },
1443 | { key: 'I2', value: '.' },
1444 | { key: 'I3', value: '4' },
1445 | ],
1446 | ],
1447 | key: '7',
1448 | },
1449 | {
1450 | cellRows: [
1451 | [
1452 | { key: 'G4', value: '6' },
1453 | { key: 'G5', value: '.' },
1454 | { key: 'G6', value: '3' },
1455 | ],
1456 | [
1457 | { key: 'H4', value: '2' },
1458 | { key: 'H5', value: '.' },
1459 | { key: 'H6', value: '1' },
1460 | ],
1461 | [
1462 | { key: 'I4', value: '.' },
1463 | { key: 'I5', value: '.' },
1464 | { key: 'I6', value: '.' },
1465 | ],
1466 | ],
1467 | key: '8',
1468 | },
1469 | {
1470 | cellRows: [
1471 | [
1472 | { key: 'G7', value: '.' },
1473 | { key: 'G8', value: '7' },
1474 | { key: 'G9', value: '.' },
1475 | ],
1476 | [
1477 | { key: 'H7', value: '.' },
1478 | { key: 'H8', value: '.' },
1479 | { key: 'H9', value: '.' },
1480 | ],
1481 | [
1482 | { key: 'I7', value: '.' },
1483 | { key: 'I8', value: '.' },
1484 | { key: 'I9', value: '.' },
1485 | ],
1486 | ],
1487 | key: '9',
1488 | },
1489 | ],
1490 | ],
1491 | };
1492 |
--------------------------------------------------------------------------------
/src/services/Solver/tests/solver.test.js:
--------------------------------------------------------------------------------
1 | import { parseGrid, search, searchPointPair } from '../Solver';
2 | import {
3 | unitList,
4 | squares,
5 | units,
6 | peers,
7 | sectionList,
8 | cross,
9 | display,
10 | all,
11 | getRandomPuzzle,
12 | } from '../../Solver/utils';
13 |
14 | import { rows, cols, STRATEGIES } from '../../Solver/constants';
15 |
16 | import {
17 | puzzles,
18 | parsedGrid1,
19 | parsedGrid2,
20 | C2Unit,
21 | C2Peers,
22 | puzzle1Solved,
23 | puzzle2Solved,
24 | sectionListData,
25 | } from '../tests/data';
26 |
27 | describe('solver', () => {
28 | it('squares', () => {
29 | expect(squares.length).toEqual(81);
30 | });
31 |
32 | it('unitList', () => {
33 | expect(unitList.length).toEqual(27);
34 | });
35 |
36 | it('cross', () => {
37 | const expected = squares;
38 | expect(cross(rows, cols)).toEqual(expect.arrayContaining(expected));
39 | });
40 |
41 | it('sectionList', () => {
42 | expect(sectionList).toEqual(expect.arrayContaining(sectionListData));
43 | });
44 |
45 | it('parseGrid', () => {
46 | const grid1 = puzzles[1];
47 | const expected1 = parsedGrid1;
48 | expect(parseGrid(grid1)).toEqual(expected1);
49 |
50 | const grid2 = puzzles[2];
51 | const expected2 = parsedGrid2;
52 | expect(parseGrid(grid2)).toEqual(expected2);
53 | });
54 |
55 | it('search', () => {
56 | const grid1 = puzzles[1];
57 | expect(search(parseGrid(grid1), STRATEGIES.BACKTRACKING)).toEqual(
58 | puzzle1Solved
59 | );
60 |
61 | const grid2 = puzzles[2];
62 | expect(search(parseGrid(grid2), STRATEGIES.BACKTRACKING)).toEqual(
63 | puzzle2Solved
64 | );
65 | });
66 |
67 | it('boardString', () => {
68 | const sol1 = search(parseGrid(puzzles[1]), STRATEGIES.BACKTRACKING);
69 | console.log(display(sol1));
70 |
71 | const sol2 = search(parseGrid(puzzles[2]), STRATEGIES.BACKTRACKING);
72 | console.log(display(sol2));
73 |
74 | const sol3 = search(parseGrid(puzzles[3]), STRATEGIES.BACKTRACKING);
75 | console.log(display(sol3));
76 | });
77 |
78 | it('units', () => {
79 | expect(units['C2']).toEqual(expect.arrayContaining(C2Unit));
80 | });
81 |
82 | it('peers', () => {
83 | expect(peers['C2']).toEqual(C2Peers);
84 | });
85 |
86 | it('Solve ALL', () => {
87 | // for (const puzzle of puzzles) {
88 | // console.log(puzzle);
89 | // console.log(display(search(parseGrid(puzzle), STRATEGIES.BACKTRACKING)));
90 | // }
91 | });
92 |
93 | // it('searchPointPair', () => {
94 | // const grid1 = puzzles[1];
95 | // expect(searchPointPair(parseGrid(grid1))).toEqual(puzzle1Solved);
96 | // });
97 | });
98 |
--------------------------------------------------------------------------------
/src/services/Solver/tests/utils.test.js:
--------------------------------------------------------------------------------
1 | import { isUnitSolved, isSolved, getBoardState } from '../utils';
2 |
3 | import { initialSudokuString } from '../constants';
4 |
5 | import { parseGrid } from '../solver';
6 |
7 | import { initialValuesState, puzzle1Solved } from './data';
8 |
9 | import { digits } from '../utils';
10 |
11 | describe('util', () => {
12 | it('getBoardState', () => {
13 | const values = parseGrid(initialSudokuString);
14 |
15 | expect(getBoardState(values)).toEqual(
16 | expect.objectContaining(initialValuesState)
17 | );
18 | });
19 |
20 | it('isUnitSolved', () => {
21 | const col = ['A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1', 'H1', 'I1'];
22 | expect(isUnitSolved(col, puzzle1Solved)).toEqual(true);
23 |
24 | const row = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'H7', 'H8', 'H9'];
25 | expect(isUnitSolved(row, puzzle1Solved)).toEqual(true);
26 |
27 | const section = ['G7', 'G8', 'G9', 'H7', 'H8', 'H9', 'I7', 'I8', 'I9'];
28 | expect(isUnitSolved(section, puzzle1Solved)).toEqual(true);
29 | });
30 |
31 | it('isSolved', () => {
32 | expect(isSolved(puzzle1Solved)).toEqual(true);
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/src/services/Solver/utils.js:
--------------------------------------------------------------------------------
1 | import { addSolutionStepsLog } from './logs';
2 | import {
3 | digits,
4 | rows,
5 | cols,
6 | rRows,
7 | cCols,
8 | STRATEGIES,
9 | ERRORS,
10 | STRING_BOARD_LENGTH,
11 | validStringRegExp,
12 | } from './constants';
13 | import { puzzles } from '../Solver/tests/data';
14 |
15 | // Cross product of elements in A and elements in B.
16 | export const cross = (listA, listB) => {
17 | const crossProduct = [];
18 | for (const a of listA) {
19 | for (const b of listB) {
20 | crossProduct.push(a + b);
21 | }
22 | }
23 | return crossProduct;
24 | };
25 |
26 | export const member = (item, list) => {
27 | for (const elem of list) {
28 | if (item === elem) {
29 | return true;
30 | }
31 | }
32 | return false;
33 | };
34 |
35 | export const Squares = () => {
36 | return cross(rows, cols);
37 | };
38 |
39 | export const squares = Squares();
40 |
41 | export const SectionList = () => {
42 | const sectionList = [];
43 | for (const rs of rRows) {
44 | for (const cs of cCols) {
45 | sectionList.push(cross(rs, cs));
46 | }
47 | }
48 | return sectionList;
49 | };
50 |
51 | export const sectionList = SectionList();
52 |
53 | export const UnitList = () => {
54 | return [
55 | ...cols.map((col) => cross(rows, [col])),
56 | ...rows.map((row) => cross([row], cols)),
57 | ...sectionList,
58 | ];
59 | };
60 |
61 | export const unitList = UnitList();
62 |
63 | export const Units = () => {
64 | return squares.reduce((units, key) => {
65 | units[key] = unitList.filter((ul) => member(key, ul));
66 | return units;
67 | }, []);
68 | };
69 |
70 | export const units = Units();
71 |
72 | export const Peers = () => {
73 | let peers = {};
74 | for (const square of squares) {
75 | peers[square] = {};
76 | for (const unit of units[square]) {
77 | for (const square2 of unit) {
78 | if (square2 !== square) {
79 | peers[square][square2] = true;
80 | }
81 | }
82 | }
83 | }
84 | return peers;
85 | };
86 |
87 | export const peers = Peers();
88 |
89 | const getSectionUnits = (sectionNumber, values) => {
90 | let cellRows = [];
91 | let sectionRow = [];
92 | for (let i = 1; i <= sectionList[sectionNumber - 1].length; i++) {
93 | const unit = sectionList[sectionNumber - 1][i - 1];
94 | sectionRow.push({
95 | key: unit,
96 | value: values[unit].length === 1 ? values[unit] : '.',
97 | });
98 | if (i % 3 === 0) {
99 | cellRows = [...cellRows, sectionRow];
100 | sectionRow = [];
101 | }
102 | }
103 | return cellRows;
104 | };
105 |
106 | // Transform a valid sudoku model into an UI Board state
107 | export const generateValuesState = (values) => {
108 | let valuesState = [];
109 | let sectionRows = [];
110 | for (const sectionNumber of digits.split('')) {
111 | sectionRows.push({
112 | key: String(sectionNumber),
113 | cellRows: getSectionUnits(sectionNumber, values),
114 | });
115 | if (sectionNumber % 3 === 0) {
116 | valuesState = [...valuesState, [...sectionRows]];
117 | sectionRows = [];
118 | }
119 | }
120 | return valuesState;
121 | };
122 |
123 | // Generate a board state based on a valid sudoku string.
124 | export const getBoardState = (values) => {
125 | return { sections: generateValuesState(values) };
126 | };
127 |
128 | export const copy = (inObject) => {
129 | let outObject, value, key;
130 |
131 | if (typeof inObject !== 'object' || inObject === null) {
132 | return inObject; // Return the value if inObject is not an object
133 | }
134 |
135 | // Create an array or object to hold the values
136 | outObject = Array.isArray(inObject) ? [] : {};
137 |
138 | for (key in inObject) {
139 | value = inObject[key];
140 |
141 | // Recursively (deep) copy for nested objects, including arrays
142 | outObject[key] = copy(value);
143 | }
144 |
145 | return outObject;
146 | };
147 |
148 | export const dict = (keys, values) => {
149 | if (typeof values === 'string' || values === null) {
150 | return keys.reduce((result, key) => ({ ...result, [key]: values }), {});
151 | } else if (typeof values === 'object') {
152 | return keys.reduce(
153 | (result, key, i) => ({ ...result, [key]: values[i] }),
154 | {}
155 | );
156 | }
157 | };
158 |
159 | export const center = (s, w) => {
160 | let excess = w - s.length;
161 | while (excess > 0) {
162 | if (excess % 2) s += ' ';
163 | else s = ' ' + s;
164 | excess -= 1;
165 | }
166 | return s;
167 | };
168 |
169 | export const display = (values) => {
170 | // Used for debugging
171 | let width = 0;
172 | for (const s in squares) {
173 | if (values[squares[s]].length > width) width = values[squares[s]].length;
174 | }
175 |
176 | width += 1;
177 | let seg = '';
178 | for (let i = 0; i < width; i++) seg += '---';
179 | const line = '\n' + [seg, seg, seg].join('+');
180 | let board = '';
181 | for (const r in rows) {
182 | for (const c in cols) {
183 | board += center(values[rows[r] + cols[c]], width);
184 | if (c === 2 || c === 5) board += '|';
185 | }
186 | if (r === 2 || r === 5) board += line;
187 | board += '\n';
188 | }
189 | board += '\n';
190 | return board;
191 | };
192 |
193 | export const isCompleted = (values) => {
194 | return all(Object.values(values), (square) => square.length === 1);
195 | };
196 |
197 | //Return some element of values that is true.
198 | export const some = (values, cb) => {
199 | for (const digit of values) {
200 | const response = cb(digit);
201 | if (response) {
202 | return response;
203 | }
204 | }
205 | return false;
206 | };
207 |
208 | // check if all list values are valid with cb
209 | export const all = (list, cb) => {
210 | for (const value of list) {
211 | if (!cb(value)) {
212 | return false;
213 | }
214 | }
215 | return true;
216 | };
217 |
218 | // check if an unit is resolved.
219 | export const isUnitSolved = (unit, values) => {
220 | return (
221 | unit
222 | .map((square) => values[square])
223 | .sort()
224 | .join('') === digits
225 | );
226 | };
227 |
228 | export const isSolved = (values) => {
229 | //A puzzle is solved if each unit is a permutation of the digits 1 to 9.
230 | return (
231 | values !== false && all(unitList, (unit) => isUnitSolved(unit, values))
232 | );
233 | };
234 |
235 | export const getSquaresWithFewestCandidates = (values) => {
236 | return squares
237 | .filter((square) => values[square].length > 1)
238 | .sort((s1, s2) => values[s1].length - values[s2].length);
239 | };
240 |
241 | export const log = (strategy, squares, digit) => {
242 | if (strategy === STRATEGIES.BACKTRACKING) {
243 | addSolutionStepsLog(
244 | strategy,
245 | [],
246 | 0,
247 | 'Backtrack Search',
248 | "From this point was applied Peter Norvig's backtracking search algorithm that to solve every sudoku puzzle."
249 | );
250 | } else {
251 | const squareMsg =
252 | squares.length > 1 ? squares[0] + ', ' + squares[1] : squares[0];
253 | const msg =
254 | squares.length === 1
255 | ? 'was solved with the value: ' + digit
256 | : 'These cells are the only cells in section with the candidate value ' +
257 | digit +
258 | '. The candidate must be in one of these cells and can be removed from other cells in column or row.';
259 | addSolutionStepsLog(
260 | strategy,
261 | [...squares],
262 | digit,
263 | strategy + ' ( ' + squareMsg + ' )',
264 | strategy + ' ( ' + squareMsg + ' ) ' + msg
265 | );
266 | }
267 | };
268 |
269 | export const hasPairValues = (list, values, digit) =>
270 | list.filter(
271 | (square) => values[square].length > 1 && values[square].includes(digit)
272 | ).length === 2;
273 |
274 | export const getPairSquares = (list, values, digit) =>
275 | list.filter((square) => values[square].includes(digit));
276 |
277 | export const getOuterPeers = (innerList, outerList, values) =>
278 | innerList.filter(
279 | (square) => !outerList.includes(square) && values[square].length > 1
280 | );
281 |
282 | export const unsolvedSquares = (list, values) =>
283 | list.filter((square) => values[square].length > 1);
284 |
285 | export const canEliminate = (list, values, digit) =>
286 | list.filter((square) => values[square].includes(digit)).length > 0;
287 |
288 | export const getRandomPuzzle = () => {
289 | return puzzles[Math.floor(Math.random() * puzzles.length)];
290 | };
291 |
292 | export const getSquareUnitRowCol = (unit, square) => [
293 | unit.filter((sq) => sq.includes(square[0])),
294 | unit.filter((sq) => sq.includes(square[1])),
295 | ];
296 |
297 | export const getPeers = (
298 | unit,
299 | unitRow,
300 | unitRows,
301 | unitCol,
302 | unitCols,
303 | values
304 | ) => [
305 | getOuterPeers(unitRow, unitRows, values),
306 | getOuterPeers(unitCol, unitCols, values),
307 | getOuterPeers(unit, unitRows, values),
308 | getOuterPeers(unit, unitCols, values),
309 | ];
310 |
311 | export const stringBoardValidation = (entryString) => {
312 | if (!entryString) {
313 | return ERRORS.EMPTY_VALUE;
314 | }
315 |
316 | if (!validStringRegExp.test(entryString)) {
317 | return ERRORS.INVALID_VALUE;
318 | }
319 |
320 | if (entryString.length !== STRING_BOARD_LENGTH) {
321 | return ERRORS.INVALID_LENGTH;
322 | }
323 |
324 | return true;
325 | };
326 |
--------------------------------------------------------------------------------