├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md └── src ├── Board.cpp ├── Board.h ├── Game.cpp ├── Game.h ├── Generator.cpp ├── Generator.h ├── Solver.cpp ├── Solver.h ├── Stopwatch.cpp ├── Stopwatch.h ├── Window.cpp ├── Window.h └── main.cpp /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /aur 2 | /src/.* 3 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at philipphuket@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Philip Johansson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This repository is no longer maintained 2 | instead use [tuidoku](https://github.com/flyingpeakock/tuidoku) instead. 3 | 4 | # Console Sudoku 5 | 6 | Simple ncurses program to play sudoku in the terminal 7 | written entirely in C++ 8 | 9 | ## How to Play 10 | Fill in all the missing squares with no repeating digits in 11 | the column, row or box. 12 | Use hjkl, wasd or arrow keys to navigate around the board. 13 | Press i or p to enter input mode or pencil mode respectively. 14 | You can also toggle between the two modes by pressing escape. 15 | To go to a specific box on the board type g followed by the 16 | column number then the row number. For example, to go to 17 | column 4, row 2 type g42. Press c to check the puzzle for any wrong 18 | inputs, mistakes turn red and correct inputs turn blue. Press c 19 | again to remove the colors. 20 | This game supports up to 3 pencil marks per box. 21 | To remove a mistake or the most recent pencil mark press 22 | your spacebar in their respective modes. 23 | To remove a specific pencil mark enter that number in the box 24 | when in pencil mode. 25 | 26 | ### Command line arguments 27 | You can pass the max number of empty boxes in the generated board 28 | by passing any integer greater than 0 as an argument. 29 | To change the text on the right from hjkl to wasd pass the argument -w or --wasd. 30 | This program is able to parse opensudoku and sdm files for puzzles. Pass --opensudoku or --sdm followed by the filename to parse the file. 31 | 32 | ## How to Install 33 | clone this repository then compile using g++ or clang++ 34 | 35 | ### Linux 36 | ``` 37 | git clone https://github.com/flyingpeakock/Console_sudoku.git 38 | cd Console_sudoku/ 39 | g++ -O3 -pthread ./src/*.cpp -lncursesw -o console_sudoku 40 | ``` 41 | ### OSX 42 | ``` 43 | git clone https://github.com/flyingpeakock/Console_sudoku.git 44 | cd Console_sudoku/ 45 | clang++ -O3 -pthread -std=c++11 -stdlib=libc++ ./src/*.cpp -lncursesw -o console_sudoku 46 | ``` 47 | 48 | ### AUR 49 | This program is also located on the arch user repository. 50 | ``` 51 | yay -S console_sudoku 52 | ``` 53 | 54 | -lncursesw flag is required to link ncurses.h 55 | -O3 is recommended to speed up puzzle generation but is not required. 56 | 57 | To run this program from any directory move the generated 58 | console_sudoku file to anywhere in your $PATH. 59 | ``` 60 | cp console_sudoku ~/.local/bin/console_sudoku 61 | ``` 62 | 63 | ### Troubleshooting 64 | If the compiler cannot find ncurses.h you need to make sure that ncurses is installed. 65 | If you cannot find ncurses in your package manager it might be called something like 66 | libcurses or curses instead. If it still doesn't work use the -lncurses flag instead 67 | of -lncursesw when compiling. 68 | 69 | ## Screenshots 70 | ![new game](https://i.imgur.com/qE879fN.png) 71 | ![same game played a little](https://i.imgur.com/nLHjHNu.png) 72 | ![same game with check](https://i.imgur.com/pZmdl6y.png) 73 | 74 | ## Generate or Solve Sudoku Puzzles 75 | You can use the Generator files and Solver files to generate 76 | or solve sudoku puzzles, some C++ experience is recommended 77 | but not required if you don't mind learning a little. 78 | 79 | To solve an unsolved puzzle create a Solver object and pass the 80 | unsolved puzzle to the constructor. The unsolved puzzle 81 | can be an int\*\*, int[9][9] or a 9x9 std::array. Solve the 82 | puzzle by calling the solve method on the Solver object. 83 | To get the solved puzzle call the getGrid method on the object, 84 | this method returns a 9x9 std::array. 85 | To check if the supplied puzzle generates a unique solution 86 | the method isUnique may be called, this returns a boolean. 87 | 88 | The generator uses Solver to build and check uniqueness of puzzles, 89 | To create a puzzle create a Generator object. To get the unsolved 90 | puzzle call the method getGrid. To get the solved puzzle call the 91 | method getSolution. 92 | 93 | -------------------------------------------------------------------------------- /src/Board.cpp: -------------------------------------------------------------------------------- 1 | #include "Board.h" 2 | 3 | Board::Board(Generator gen): playGrid(gen.getGrid()), startGrid(playGrid), solutionGrid(gen.getSolution()) { 4 | for (auto &array : pencilMarks) { 5 | for (auto &vec : array) { 6 | for (auto i = 0; i < 3; i++) { 7 | vec.push_back(' '); 8 | } 9 | } 10 | } 11 | 12 | for (auto i = 1; i <= 9; i++) { 13 | count.insert({i, 0}); 14 | } 15 | for (auto i = 0; i < 9; i++) { 16 | for (auto j = 0; j < 9; j++) { 17 | int val = startGrid[i][j]; 18 | if (val != 0) { 19 | count[val]++; 20 | } 21 | } 22 | } 23 | } 24 | 25 | void Board::startPlaying() { 26 | playing = true; 27 | } 28 | 29 | void Board::stopPlaying() { 30 | playing = false; 31 | } 32 | 33 | bool Board::isPlaying() { 34 | return playing; 35 | } 36 | 37 | bool Board::isWon() { 38 | if (playGrid == solutionGrid) { 39 | playing = false; 40 | return true; 41 | } 42 | return false; 43 | } 44 | 45 | std::array, 9>, 9> &Board::getPencilMarks() { 46 | return pencilMarks; 47 | } 48 | 49 | std::array, 9> &Board::getPlayGrid() { 50 | return playGrid; 51 | } 52 | 53 | std::array, 9> &Board::getStartGrid() { 54 | return startGrid; 55 | } 56 | 57 | std::array, 9> &Board::getSolution() { 58 | return solutionGrid; 59 | } 60 | 61 | void Board::insert(char val, int row, int col) { 62 | if (startGrid[row][col] != 0) { 63 | // Trying to change a correct checked square 64 | return; 65 | } 66 | 67 | if (val == ' ' || val == '0') { 68 | if (playGrid[row][col] != 0) { 69 | count[playGrid[row][col]]--; 70 | playGrid[row][col] = 0; 71 | } 72 | return; 73 | } 74 | 75 | if (val > '0' && val <= '9') { 76 | if (playGrid[row][col] != 0) 77 | count[playGrid[row][col]]--; 78 | count[val - '0']++; 79 | playGrid[row][col] = val - '0'; 80 | } 81 | 82 | if (playGrid[row][col] == solutionGrid[row][col]) { 83 | removeMarks(val, row, col); 84 | } 85 | } 86 | 87 | void Board::pencil(char val, int row, int col) { 88 | auto &marks = pencilMarks[row][col]; 89 | if (val == ' ') { 90 | if (marks[0] != ' ') { 91 | marks.erase(marks.begin()); 92 | } 93 | return; 94 | } 95 | 96 | // Check if mark exists, if visible delete 97 | // else move to front 98 | int idx = 0; 99 | for (auto &m : marks) { 100 | if (m == val) { 101 | if (idx > 2) { 102 | marks.erase(marks.begin() + idx); 103 | marks.insert(marks.begin(), val); 104 | } 105 | else { 106 | marks.erase(marks.begin() + idx); 107 | } 108 | return; 109 | } 110 | idx++; 111 | } 112 | 113 | if (val > '0' && val <= '9') { 114 | marks.insert(marks.begin(), val); 115 | } 116 | } 117 | 118 | void Board::removeMarks(char val, int row, int col) { 119 | for (auto i = 0; i < 9; i++) { 120 | for (auto j = 0; j < pencilMarks[row][i].size(); j++) { 121 | auto &mark = pencilMarks[row][i]; 122 | if (val == mark[j] && mark[j] != ' ') { 123 | mark.erase(mark.begin() + j); 124 | break; 125 | } 126 | } 127 | for (auto j = 0; j < pencilMarks[i][col].size(); j++) { 128 | auto &mark = pencilMarks[i][col]; 129 | if (val == mark[j] && mark[j] != ' ') { 130 | mark.erase(mark.begin() + j); 131 | break; 132 | } 133 | } 134 | } 135 | 136 | int boxRow = (row / 3) * 3; 137 | int boxCol = (col / 3) * 3; 138 | for (auto i = boxRow; i < boxRow + 3; i++) { 139 | for (auto j = boxCol; j < boxCol + 3; j++) { 140 | auto &mark = pencilMarks[i][j]; 141 | for (auto k = 0; k < mark.size(); k++) { 142 | if (val == mark[k] && mark[k] != ' ') { 143 | mark.erase(mark.begin() + k); 144 | break; 145 | } 146 | } 147 | } 148 | } 149 | } 150 | 151 | bool Board::isRemaining(int val) { 152 | if (val == 0) 153 | return true; 154 | return count[val] < 9; 155 | } -------------------------------------------------------------------------------- /src/Board.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Generator.h" 3 | #include 4 | #include 5 | 6 | class Board { 7 | private: 8 | std::array, 9> playGrid; 9 | std::array, 9> startGrid; 10 | std::array, 9> solutionGrid; 11 | 12 | std::array, 9>, 9> pencilMarks; 13 | 14 | std::map count; 15 | 16 | void removeMarks(char val, int row, int col); 17 | 18 | bool playing; 19 | public: 20 | Board(Generator gen); 21 | void startPlaying(); 22 | void stopPlaying(); 23 | bool isPlaying(); 24 | bool isWon(); 25 | bool isRemaining(int val); 26 | 27 | std::array, 9>, 9> &getPencilMarks(); 28 | std::array, 9> &getPlayGrid(); 29 | std::array, 9> &getStartGrid(); 30 | std::array, 9> &getSolution(); 31 | 32 | void insert(char val, int row, int col); 33 | void pencil(char val, int row, int col); 34 | }; -------------------------------------------------------------------------------- /src/Game.cpp: -------------------------------------------------------------------------------- 1 | #include "Game.h" 2 | #include "Stopwatch.h" 3 | #include 4 | #include 5 | 6 | Game::Game(Board b, char *navKeys): board(b), window(&board, navKeys) { 7 | mode = 'i'; 8 | row = 0; 9 | col = 0; 10 | } 11 | 12 | void Game::mainLoop() { 13 | board.startPlaying(); 14 | Stopwatch timer; 15 | timer.start(); 16 | while (board.isPlaying()) { 17 | window.printBoard(); 18 | wchar_t prevMode = mode; 19 | int ch = wgetch(stdscr); 20 | switch (ch) { 21 | case KEY_LEFT: 22 | case 'a': 23 | case 'h': 24 | left(); 25 | break; 26 | case KEY_DOWN: 27 | case 's': 28 | case 'j': 29 | down(); 30 | break; 31 | case KEY_UP: 32 | case 'w': 33 | case 'k': 34 | up(); 35 | break; 36 | case KEY_RIGHT: 37 | case 'd': 38 | case 'l': 39 | right(); 40 | break; 41 | case 'g': 42 | changeMode(ch); 43 | window.printBoard(); 44 | go(); 45 | changeMode(prevMode); 46 | break; 47 | case 'i': 48 | case 'p': 49 | changeMode(ch); 50 | break; 51 | case 'q': 52 | board.stopPlaying(); 53 | break; 54 | case 'c': 55 | window.check(); 56 | break; 57 | case 27: 58 | if (mode == 'i') { 59 | changeMode('p'); 60 | } 61 | else { 62 | changeMode('i'); 63 | } 64 | break; 65 | default: 66 | if ((ch > '0' && ch <= '9') || ch == ' ') { 67 | window.select(ch); 68 | if (mode == 'i') { 69 | insert(ch); 70 | } 71 | else if (mode == 'p') { 72 | pencil(ch); 73 | } 74 | } 75 | } 76 | 77 | board.isWon(); 78 | } 79 | timer.stop(); 80 | if (!board.isWon()) { 81 | return; 82 | } 83 | 84 | window.changeMode(timer.timeTaken()); 85 | window.printBoard(); 86 | 87 | getch(); 88 | } 89 | 90 | void Game::insert(char val) { 91 | board.insert(val, row, col); 92 | } 93 | 94 | void Game::pencil(char val) { 95 | board.pencil(val, row, col); 96 | } 97 | 98 | void Game::changeMode(wchar_t c) { 99 | std::string s; 100 | switch (c) { 101 | case L'i': 102 | s = "Input mode"; 103 | mode = c; 104 | break; 105 | case L'p': 106 | s = "Pencil mode"; 107 | mode = c; 108 | break; 109 | case L'g': 110 | s = "Go"; 111 | mode = c; 112 | break; 113 | } 114 | window.changeMode(s); 115 | } 116 | 117 | 118 | void Game::up() { 119 | if (row <= 0) { 120 | row = 9; 121 | } 122 | window.moveCursor(--row, col); 123 | } 124 | 125 | void Game::down() { 126 | if (row >= 8) { 127 | row = -1; 128 | } 129 | window.moveCursor(++row, col); 130 | } 131 | 132 | void Game::left() { 133 | if (col <= 0) { 134 | col = 9; 135 | } 136 | window.moveCursor(row, --col); 137 | } 138 | 139 | void Game::right() { 140 | if (col >= 8) { 141 | col = -1; 142 | } 143 | window.moveCursor(row, ++col); 144 | } 145 | 146 | void Game::go() { 147 | char r = 0; 148 | char c = 0; 149 | while (c < '1' || c > '9') { 150 | c = getch(); 151 | if (c == 'q') { 152 | return; 153 | } 154 | } 155 | 156 | while (r < '1' || r > '9') { 157 | r = getch(); 158 | if (r == 'q') { 159 | return; 160 | } 161 | } 162 | window.moveCursor(r - '1', c - '1'); 163 | row = r - '1'; 164 | col = c - '1'; 165 | } -------------------------------------------------------------------------------- /src/Game.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Window.h" 4 | #include "Board.h" 5 | 6 | class Game{ 7 | private: 8 | Board board; 9 | Window window; 10 | wchar_t mode; 11 | int row, col; 12 | public: 13 | Game(Board b, char *navKeys); 14 | void mainLoop(); 15 | void changeMode(wchar_t c); 16 | void up(); 17 | void down(); 18 | void left(); 19 | void right(); 20 | void insert(char val); 21 | void pencil(char val); 22 | void go(); 23 | void check(); 24 | }; -------------------------------------------------------------------------------- /src/Generator.cpp: -------------------------------------------------------------------------------- 1 | #include "Generator.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define SIZE 9 10 | 11 | Generator::Generator():Generator(81){}; 12 | 13 | Generator::Generator(const char *gridString) { 14 | if (strlen(gridString) != 81) 15 | throw std::invalid_argument("Wrong amount of digits for a sudoku puzzle"); 16 | 17 | auto idx = 0; 18 | for (auto i = 0; i < 9; i++) { 19 | for (auto j = 0; j < 9; j++) { 20 | grid[i][j] = gridString[idx++] - '0'; 21 | } 22 | } 23 | solver = Solver(gridString); 24 | solver.solve(); 25 | } 26 | 27 | Generator::Generator(int maxUnknowns) { 28 | 29 | struct Cell 30 | { 31 | int row; 32 | int col; 33 | int val; 34 | }; 35 | 36 | 37 | solver = Solver(); 38 | solver.solve(); 39 | grid = solver.getGrid(); 40 | 41 | std::array cells; 42 | int count = 0; 43 | for (auto i = 0; i < SIZE; i++) { 44 | for (auto j = 0; j < SIZE; j++) { 45 | cells[count].row = i; 46 | cells[count].col = j; 47 | cells[count].val = grid[i][j]; 48 | count++; 49 | } 50 | } 51 | 52 | // Shuffle array to randomly remove positions 53 | unsigned seed = std::chrono::system_clock::now().time_since_epoch().count(); 54 | shuffle (cells.begin(), cells.end(), std::default_random_engine(seed)); 55 | 56 | int unknowns = 0; 57 | for (auto cell: cells) { 58 | grid[cell.row][cell.col] = 0; 59 | solver = Solver(grid); 60 | solver.solve(); 61 | if (!solver.isUnique()) { 62 | // Removal of value does not produce unique solution 63 | // Put it back 64 | grid[cell.row][cell.col] = cell.val; 65 | } 66 | else 67 | unknowns++; 68 | if (unknowns == maxUnknowns) 69 | break; 70 | } 71 | 72 | // Solve again incase last removal broke the puzzle 73 | solver = Solver(grid); 74 | solver.solve(); 75 | } 76 | 77 | 78 | std::array, 9> Generator::getGrid() { 79 | return grid; 80 | } 81 | 82 | std::array, 9> Generator::getSolution() { 83 | return solver.getGrid(); 84 | } 85 | -------------------------------------------------------------------------------- /src/Generator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Solver.h" 3 | 4 | class Generator { 5 | private: 6 | std::array, 9> grid; 7 | Solver solver; 8 | 9 | public: 10 | Generator(); 11 | Generator(int maxUnknowns); 12 | Generator(const char *gridString); 13 | std::array, 9> getGrid(); 14 | std::array, 9> getSolution(); 15 | }; -------------------------------------------------------------------------------- /src/Solver.cpp: -------------------------------------------------------------------------------- 1 | #include "Solver.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define SIZE 9 9 | 10 | Solver::Solver() { 11 | solutions = 0; 12 | for (auto i = 0; i < SIZE; i++) { 13 | for (auto j = 0; j < SIZE; j++) { 14 | grid[i][j] = 0; 15 | } 16 | } 17 | } 18 | 19 | Solver::Solver(int board[9][9]) { 20 | solutions = 0; 21 | for (auto i = 0; i < SIZE; i++) { 22 | for (auto j = 0; j < SIZE; j++) { 23 | grid[i][j] = board[i][j]; 24 | } 25 | } 26 | } 27 | 28 | Solver::Solver(int **board) { 29 | solutions = 0; 30 | for (auto i = 0; i < SIZE; i++) { 31 | for (auto j = 0; j < SIZE; j++) { 32 | grid[i][j] = board[i][j]; 33 | } 34 | } 35 | } 36 | 37 | Solver::Solver(std::array, 9> board) { 38 | solutions = 0; 39 | grid = board; 40 | } 41 | 42 | Solver::Solver(const char *board) { 43 | solutions = 0; 44 | auto idx = 0; 45 | if (strlen(board) != 81) 46 | throw std::invalid_argument("Wrong amount of digits to make a 9x9 sudoku"); 47 | for (auto i = 0; i < SIZE; i++) { 48 | for (auto j = 0; j < SIZE; j++) { 49 | grid[i][j] = board[idx++] - '0'; 50 | } 51 | } 52 | } 53 | 54 | std::array, 9> Solver::getGrid() { 55 | return solution; 56 | } 57 | 58 | bool Solver::isSafe(int row, int col, int num) { 59 | 60 | // Check for same numb in same row 61 | for (auto i = 0; i < SIZE; i++) { 62 | if (grid[row][i] == num) return false; 63 | } 64 | 65 | // Check for same numb in same col 66 | for (auto i = 0; i < SIZE; i++) { 67 | if (grid[i][col] == num) return false; 68 | } 69 | 70 | // Check for same numb in same box 71 | int boxRow = (row / 3) * 3; 72 | int boxCol = (col / 3) * 3; 73 | for (auto i = boxRow; i < boxRow + 3; i++) { 74 | for (auto j = boxCol; j < boxCol + 3; j++) { 75 | if (grid[i][j] == num) return false; 76 | } 77 | } 78 | 79 | return true; // All checks passed 80 | } 81 | 82 | 83 | bool Solver::backtrack(int row, int col) { 84 | // Has reached the end of the board, return true 85 | if (row == SIZE - 1 && col == SIZE) { 86 | solutions++; 87 | solution = grid; 88 | return true; 89 | } 90 | 91 | // Recahed the end of row, go to next 92 | if (col == SIZE) { 93 | col = 0; 94 | row++; 95 | } 96 | 97 | // Position already has value, go to next 98 | if (grid[row][col] > 0) { 99 | return backtrack(row, col + 1); 100 | } 101 | 102 | // Here comes the fun part 103 | // Create array with randomness instead of normal loop 104 | // used to generate unique puzzles 105 | std::array nums {1,2,3,4,5,6,7,8,9}; 106 | unsigned seed = std::chrono::system_clock::now().time_since_epoch().count(); 107 | shuffle (nums.begin(), nums.end(), std::default_random_engine(seed)); 108 | 109 | //for (auto num = 1; num <= SIZE; num++) { 110 | for (auto num : nums) { 111 | // Check if safe to place num 112 | if (isSafe(row, col, num)) { 113 | grid[row][col] = num; 114 | if (backtrack(row, col + 1) && solutions > 1) 115 | //if (backtrack(row, col +1)) 116 | return true; 117 | } 118 | } 119 | // Number was wrong, reset 120 | grid[row][col] = 0; 121 | return false; 122 | } 123 | 124 | void Solver::solve() { 125 | backtrack(0, 0); 126 | } 127 | 128 | bool Solver::isUnique() { 129 | return solutions == 1; 130 | } -------------------------------------------------------------------------------- /src/Solver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | class Solver { 5 | private: 6 | std::array, 9> grid; 7 | std::array, 9> solution; 8 | bool isSafe(int row, int col, int num); 9 | bool backtrack(int row, int col); 10 | int solutions; 11 | public: 12 | Solver(); 13 | Solver(int board[9][9]); 14 | Solver(int **board); 15 | Solver(std::array, 9> board); 16 | Solver(const char *board); 17 | std::array, 9> getGrid(); 18 | void solve(); 19 | bool isUnique(); 20 | void changeGrid(int **board); 21 | }; -------------------------------------------------------------------------------- /src/Stopwatch.cpp: -------------------------------------------------------------------------------- 1 | #include "Stopwatch.h" 2 | #include 3 | 4 | bool Stopwatch::running = false; 5 | std::thread Stopwatch::counter; 6 | int Stopwatch::seconds = 0; 7 | int Stopwatch::minutes = 0; 8 | int Stopwatch::hours = 0; 9 | 10 | Stopwatch::Stopwatch(){ 11 | } 12 | 13 | void Stopwatch::start() { 14 | if (running) { 15 | return; 16 | } 17 | running = true; 18 | counter = std::thread(&count); 19 | } 20 | 21 | void Stopwatch::stop() { 22 | running = false; 23 | counter.join(); 24 | } 25 | 26 | void Stopwatch::count() { 27 | while (running) { 28 | std::this_thread::sleep_for(std::chrono::milliseconds(1000)); 29 | if (seconds == 60) { 30 | seconds = 0; 31 | minutes++; 32 | } 33 | else if (minutes == 60) { 34 | minutes = 0; 35 | hours++; 36 | } 37 | else { 38 | seconds++; 39 | } 40 | } 41 | } 42 | 43 | std::string Stopwatch::timeTaken() { 44 | std::ostringstream timeStr; 45 | 46 | timeStr << "Time taken: "; 47 | if (hours > 1) { 48 | timeStr << hours << " Hours "; 49 | } 50 | else if (hours == 1) { 51 | timeStr << hours << " Hour "; 52 | } 53 | if (minutes > 1) { 54 | timeStr << minutes << " Minutes "; 55 | } 56 | else if (minutes == 1) { 57 | timeStr << minutes << " Minute "; 58 | } 59 | if (seconds > 1 || seconds == 0) { 60 | timeStr << seconds << " Seconds"; 61 | } 62 | else if (seconds == 1) { 63 | timeStr << seconds << " Second"; 64 | } 65 | return timeStr.str(); 66 | } -------------------------------------------------------------------------------- /src/Stopwatch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // #include 3 | #include 4 | #include 5 | 6 | class Stopwatch { 7 | private: 8 | static bool running; 9 | static std::thread counter; 10 | static int seconds; 11 | static int minutes; 12 | static int hours; 13 | public: 14 | static void count(); 15 | Stopwatch(); 16 | void start(); 17 | void stop(); 18 | std::string timeTaken(); 19 | }; -------------------------------------------------------------------------------- /src/Window.cpp: -------------------------------------------------------------------------------- 1 | #include "Window.h" 2 | #include 3 | #include 4 | 5 | Window::Window(Board *g, char *navKeys){ 6 | game = g; 7 | 8 | mode = "Insert mode"; 9 | 10 | checkColors = false; 11 | cursorRow = 0; 12 | cursorCol = 0; 13 | highlightNum = 0; 14 | 15 | auto keyidx = 0; 16 | leftKey = navKeys[keyidx++]; 17 | downKey = navKeys[keyidx++]; 18 | upKey = navKeys[keyidx++]; 19 | rightKey = navKeys[keyidx++]; 20 | 21 | setlocale(LC_ALL, ""); 22 | 23 | initscr(); 24 | 25 | cbreak(); // Get input before enter is pressed; 26 | noecho(); // Don't show keypresses 27 | keypad(stdscr, true); // Use arrow keys to move 28 | 29 | if (has_colors()) { 30 | use_default_colors(); 31 | start_color(); 32 | init_pair(1, COLOR_RED, -1); 33 | init_pair(2, COLOR_BLUE, -1); 34 | init_pair(3, COLOR_YELLOW, -1); 35 | init_pair(4, COLOR_CYAN, -1); 36 | } 37 | 38 | printBoard(); 39 | 40 | } 41 | 42 | Window::~Window() { 43 | endwin(); 44 | } 45 | 46 | void Window::printBoard() { 47 | int oldRows = windowRows; 48 | int oldCols = windowCols; 49 | getmaxyx(stdscr, windowRows, windowCols); 50 | bool resize = (oldRows != windowRows || oldCols != windowCols); 51 | 52 | boardTop = (windowRows - BoardRows) / 2; 53 | boardLeft = (windowCols - BoardCols) / 2; 54 | int gridTop = boardTop + 1; 55 | int gridLeft = boardLeft + 2; 56 | 57 | if (resize) { 58 | clear(); 59 | if (windowRows < BoardRows || windowCols < BoardCols) { 60 | char error[] = "Not enough space to draw board"; 61 | mvprintw(windowRows / 2, (windowCols - strlen(error)) / 2, "%s", error); 62 | refresh(); 63 | return; 64 | } 65 | if (windowRows - BoardRows > 3) { 66 | attron(A_BOLD | A_UNDERLINE); 67 | char title[] = "Console Sudoku"; 68 | mvprintw(boardTop - 3, (windowCols - strlen(title)) / 2, "%s", title); 69 | attroff(A_BOLD | A_UNDERLINE); 70 | } 71 | printBoxes(); 72 | printInstructions(); 73 | printCoords(); 74 | } 75 | 76 | printNumbs(); 77 | printPencil(); 78 | printMode(); 79 | printCursor(); 80 | refresh(); 81 | } 82 | 83 | void Window::printBoxes() { 84 | 85 | const char topRow[] = "\u2554\u2550\u2550\u2550\u2564\u2550\u2550\u2550\u2564" 86 | "\u2550\u2550\u2550\u2566\u2550\u2550\u2550\u2564\u2550" 87 | "\u2550\u2550\u2564\u2550\u2550\u2550\u2566\u2550\u2550" 88 | "\u2550\u2564\u2550\u2550\u2550\u2564\u2550\u2550\u2550\u2557"; 89 | const char middleRow[] = "\u2551 \u2502 \u2502 \u2551 \u2502" 90 | " \u2502 \u2551 \u2502 \u2502 \u2551"; 91 | const char middleRow2[] = "\u255f\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u253c" 92 | "\u2500\u2500\u2500\u256b\u2500\u2500\u2500\u253c" 93 | "\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u256b" 94 | "\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u253c" 95 | "\u2500\u2500\u2500\u2562"; 96 | const char middleRow3[] = "\u2560\u2550\u2550\u2550\u256a\u2550\u2550\u2550\u256a" 97 | "\u2550\u2550\u2550\u256c\u2550\u2550\u2550\u256a" 98 | "\u2550\u2550\u2550\u256a\u2550\u2550\u2550\u256c" 99 | "\u2550\u2550\u2550\u256a\u2550\u2550\u2550\u256a" 100 | "\u2550\u2550\u2550\u2563"; 101 | const char botRow[] = "\u255a\u2550\u2550\u2550\u2567\u2550\u2550\u2550\u2567" 102 | "\u2550\u2550\u2550\u2569\u2550\u2550\u2550\u2567" 103 | "\u2550\u2550\u2550\u2567\u2550\u2550\u2550\u2569" 104 | "\u2550\u2550\u2550\u2567\u2550\u2550\u2550\u2567" 105 | "\u2550\u2550\u2550\u255d"; 106 | 107 | int startHeight = boardTop; 108 | attron(A_BOLD); 109 | mvprintw(startHeight, boardLeft, "%s", topRow); 110 | for (auto i = 0; i < 3; i++) { 111 | mvprintw(++startHeight, boardLeft, "%s", middleRow); 112 | mvprintw(++startHeight, boardLeft, "%s", middleRow2); 113 | mvprintw(++startHeight, boardLeft, "%s", middleRow); 114 | mvprintw(++startHeight, boardLeft, "%s", middleRow2); 115 | mvprintw(++startHeight, boardLeft, "%s", middleRow); 116 | if (i != 2) 117 | mvprintw(++startHeight, boardLeft, "%s", middleRow3); 118 | } 119 | mvprintw(++startHeight, boardLeft, "%s", botRow); 120 | attroff(A_BOLD); 121 | } 122 | 123 | void Window::printPencil() { 124 | attron(A_DIM); 125 | auto marks = game->getPencilMarks(); 126 | auto grid = game->getPlayGrid(); 127 | int row = boardTop + 1; 128 | for (auto i = 0; i < 9; i++) { 129 | int col = boardLeft + 1; 130 | for (auto j = 0; j < 9; j++) { 131 | if (grid[i][j] < 1) { 132 | int idx[] = {0, 2, 1}; 133 | move(row, col); 134 | for (auto k : idx) { 135 | char c = marks[i][j][k]; 136 | if (!checkColors && c - '0' == highlightNum){ 137 | attron(COLOR_PAIR(3)); 138 | } 139 | addch(c); 140 | attroff(COLOR_PAIR(3)); 141 | } 142 | } 143 | col += 4; 144 | } 145 | row += 2; 146 | } 147 | attroff(A_DIM); 148 | } 149 | 150 | void Window::printNumbs() { 151 | auto grid = game->getPlayGrid(); 152 | auto start = game->getStartGrid(); 153 | auto solution = game ->getSolution(); 154 | 155 | int row = boardTop + 1; 156 | for (auto i = 0; i < 9; i++) { 157 | int col = boardLeft + 2; 158 | for (auto j = 0; j < 9; j++) { 159 | const char ch = grid[i][j] + '0'; 160 | if (ch < '1') { 161 | col += 4; 162 | continue; 163 | } 164 | 165 | attron(A_BOLD); 166 | 167 | if (!game->isRemaining(ch - '0')) { 168 | attron(COLOR_PAIR(4)); 169 | } 170 | // Draw over potential pencilmarks 171 | mvprintw(row, col - 1, " "); 172 | 173 | if (!checkColors && ch - '0' == highlightNum) { 174 | attron(COLOR_PAIR(3)); 175 | } 176 | 177 | if (ch - '0' == start[i][j]) { 178 | attron(A_UNDERLINE); 179 | } 180 | else if (checkColors && grid[i][j] == solution[i][j]) { 181 | attron(COLOR_PAIR(2)); 182 | } 183 | else if (checkColors && grid[i][j] != solution[i][j]) { 184 | attron(COLOR_PAIR(1)); 185 | } 186 | mvaddch(row, col, ch); 187 | attroff(A_UNDERLINE); 188 | attroff(COLOR_PAIR(4)); 189 | attroff(COLOR_PAIR(3)); 190 | attroff(COLOR_PAIR(2)); 191 | attroff(COLOR_PAIR(1)); 192 | col += 4; 193 | } 194 | row += 2; 195 | } 196 | attroff(A_BOLD); 197 | } 198 | 199 | void Window::printInstructions() { 200 | if (windowCols - BoardCols < 24) { 201 | return; 202 | } 203 | 204 | int row = boardTop + 3; 205 | int col = boardLeft + BoardCols + 5; 206 | 207 | mvaddch(row, col + 3, upKey); 208 | mvaddch(row + 2, col, leftKey); 209 | mvaddch(row + 2, col + 6, rightKey); 210 | mvaddch(row + 4, col + 3, downKey); 211 | attron(A_UNDERLINE); 212 | mvaddch(row + 8, col, 'i'); 213 | attroff(A_UNDERLINE); 214 | printw("nsert"); 215 | attron(A_UNDERLINE); 216 | mvaddch(row + 9, col, 'p'); 217 | attroff(A_UNDERLINE); 218 | printw("encil"); 219 | attron(A_UNDERLINE); 220 | mvaddch(row + 10, col, 'g'); 221 | attroff(A_UNDERLINE); 222 | printw("o"); 223 | attron(A_UNDERLINE); 224 | mvaddch(row+11, col, 'c'); 225 | attroff(A_UNDERLINE); 226 | printw("heck"); 227 | attron(A_UNDERLINE); 228 | mvaddch(row + 12, col, 'q'); 229 | attroff(A_UNDERLINE); 230 | printw("uit"); 231 | } 232 | 233 | void Window::printCoords() { 234 | if (windowCols - BoardCols < 4) { 235 | return; 236 | } 237 | 238 | int col = boardLeft + 2; 239 | 240 | for (auto i = '1'; i <= '9'; i++) { 241 | mvaddch(boardTop - 1, col, i); 242 | col += 4; 243 | } 244 | 245 | col = boardLeft - 2; 246 | 247 | int row = boardTop + 1; 248 | for (auto i = '1'; i <= '9'; i++) { 249 | mvaddch(row, col, i); 250 | row += 2; 251 | } 252 | } 253 | 254 | void Window::printMode() { 255 | if (windowRows <= BoardRows) { 256 | return; 257 | } 258 | move(boardTop + BoardRows, boardLeft); 259 | clrtoeol(); 260 | printw("%s", mode.c_str()); 261 | } 262 | 263 | void Window::printCursor() { 264 | int row = cursorRow * 2 + boardTop + 1; 265 | int col = cursorCol * 4 + boardLeft + 2; 266 | move(row, col); 267 | } 268 | 269 | void Window::moveCursor(int row, int col) { 270 | cursorRow = row; 271 | cursorCol = col; 272 | } 273 | 274 | void Window::changeMode(std::string s) { 275 | mode = s; 276 | } 277 | 278 | void Window::check() { 279 | checkColors = !checkColors; 280 | } 281 | 282 | void Window::select(int val) { 283 | if (val == ' ') { 284 | int initValue = game->getStartGrid()[cursorRow][cursorCol]; 285 | if (initValue == 0) { 286 | highlightNum = 0; 287 | return; 288 | } 289 | highlightNum = initValue; 290 | } 291 | else { 292 | highlightNum = val - '0'; 293 | } 294 | } -------------------------------------------------------------------------------- /src/Window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Board.h" 4 | #include 5 | #include 6 | 7 | class Window { 8 | private: 9 | Board *game; 10 | int cursorRow, cursorCol; 11 | int windowRows, windowCols; 12 | const int BoardRows = 19; 13 | const int BoardCols = 37; 14 | int boardTop; 15 | int boardLeft; 16 | std::string mode; 17 | bool checkColors; 18 | int highlightNum; 19 | 20 | char leftKey; 21 | char downKey; 22 | char upKey; 23 | char rightKey; 24 | 25 | void printBoxes(); 26 | void printPencil(); 27 | void printNumbs(); 28 | void printInstructions(); 29 | void printCoords(); 30 | void printMode(); 31 | void printCursor(); 32 | public: 33 | Window(Board *g, char *navKeys); 34 | ~Window(); 35 | void moveCursor(int row, int col); 36 | void printBoard(); 37 | void changeMode(std::string s); 38 | void check(); 39 | void select(int val); 40 | }; -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "Game.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | void printHelp() { 9 | const char *helptext = "usage: console_sodoku [OPTIONS] \n\n" 10 | "Play Sudoku in the terminal.\n\n" 11 | "Optional args:\n" 12 | "-h, --help View this page\n" 13 | "-w, --wasd Display wasd instead of hjkl\n" 14 | "--opensudoku Load a puzzle from an opensudoku file\n" 15 | "--sdm Load a puzzle from an smd file\n" 16 | "INTEGER Number of squares to leave empty\n\n" 17 | "Move with hjkl, wasd or the arrow keys.\n" 18 | "Enter pencil mode by pressing 'p'.\n" 19 | "Enter insert mode by pressing 'i'.\n" 20 | "Go to a specific square by pressing 'g' followed by the column then row.\n" 21 | "Check the board for mistakes by pressing 'c'.\n" 22 | "Quit the game by pressing 'q'.\n" 23 | "To highlight any occurence of a number insert the number in any mode or\n" 24 | "press spacebar on a prefilled box.\n" 25 | "Hint: pencilmarks do not show up on filled in boxes, use this to highlight.\n\n" 26 | "For information on Sudoku puzzles see the wikipedia entry:\n" 27 | "https://en.wikipedia.org/wiki/Sudoku\n\n" 28 | "See the readme for more information.\n" 29 | "The readme can be read online at:\n" 30 | "https://github.com/flyingpeakock/Console_sudoku/blob/master/README.md\n" 31 | "https://philipj.ydns.eu/stagit/Console_sudoku/file/README.md.html\n"; 32 | printf(helptext); 33 | } 34 | 35 | std::string getRandomXMLPuzzle(const char *fileName) { 36 | std::ifstream file; 37 | std::string line; 38 | std::vector puzzles; 39 | file.open(fileName); 40 | while (getline(file, line)) { 41 | std::size_t found = line.find(" puzzles; 60 | std::string line; 61 | file.open(fileName); 62 | while(getline(file, line)) { 63 | puzzles.push_back(line); 64 | } 65 | unsigned seed = std::chrono::system_clock::now().time_since_epoch().count(); 66 | srand(seed); 67 | return puzzles[rand() % puzzles.size()]; 68 | } 69 | 70 | int main(int argc, char *argv[]) { 71 | char navKeys[] = "hjkl"; 72 | Generator *gen = nullptr; 73 | for (auto i = 1; i < argc; i++) { 74 | if (atoi(argv[i]) > 0) { 75 | gen = new Generator(atoi(argv[i])); 76 | } 77 | if (strcmp(argv[i], "-w") == 0 || strcmp(argv[i], "--wasd") == 0) { 78 | strcpy(navKeys, "aswd"); 79 | } 80 | if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { 81 | printHelp(); 82 | return 0; 83 | } 84 | if (strcmp(argv[i], "--opensudoku") == 0 && !gen) { 85 | gen = new Generator(getRandomXMLPuzzle(argv[i+1]).c_str()); 86 | } 87 | if (strcmp(argv[i], "--sdm") == 0 && !gen) { 88 | gen = new Generator(getRandomSdmPuzzle(argv[i+1]).c_str()); 89 | } 90 | } 91 | if (!gen) 92 | gen = new Generator(); 93 | Board board(*gen); 94 | Game game(board, navKeys); 95 | game.mainLoop(); 96 | delete gen; 97 | } --------------------------------------------------------------------------------