cpp: Sudoku

发布时间 2023-07-01 10:16:16作者: ®Geovin Du Dream Park™

 

/*****************************************************************//**
 * \file   ConsoleSudoku.cpp   c++ 14
 * \brief  九宫独数填充游戏
 * from https://github.com/vaithak/Sudoku-Generator
 * \author geovindu
 * \date   July 2023
 *********************************************************************/
// ConsoleSudoku.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <iostream>
#include <algorithm>
#include <ctime>
#include <cstdlib>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>

#define UNASSIGNED 0

using namespace std;


class Sudoku {
private:
    int grid[9][9];
    int solnGrid[9][9];
    int guessNum[9];
    int gridPos[81];
    int difficultyLevel;
    bool grid_status;

public:
    Sudoku();
    Sudoku(string, bool row_major = true);
    void fillEmptyDiagonalBox(int);
    void createSeed();
    void printGrid();
    bool solveGrid();
    string getGrid();
    void countSoln(int& number);
    void genPuzzle();
    bool verifyGridStatus();
    void printSVG(string);

    void printSolveSVG(string);
    void printSolveDataSVG(vector<vector<int>> data,string);
    void calculateDifficulty();
    int  branchDifficultyScore();
    vector<vector<int>> getArr();
        
};

// START: Get grid as string in row major order
string Sudoku::getGrid()
{
    string s = "";
    for (int row_num = 0; row_num < 9; ++row_num)
    {
        for (int col_num = 0; col_num < 9; ++col_num)
        {
            s = s + to_string(grid[row_num][col_num]);
        }
    }

    return s;
}


vector<vector<int>> Sudoku::getArr()
{
    int gg[9][9] = {};
    int row_num = 9;
    int col_num = 9;
    vector<vector<int>> data;    //二维vector
    vector<int> data_row(col_num);   //存放每行数据的一维vecto

    //for (int row_num = 0; row_num < 9; ++row_num)
    //{
    //    for (int col_num = 0; col_num < 9; ++col_num)
    //    {
    //       gg[row_num][col_num]= grid[row_num][col_num];
    //    }
    //}

    for (int i = 0; i < row_num; i++)
    {
        for (int j = 0; j < col_num; j++)
            data_row[j] = grid[i][j];// i + j;     //使用下标对vector赋值时要注意vector容量是否足够
        data.push_back(data_row);
    }


    return data;
}
// END: Get grid as string in row major order


// START: Generate random number
int genRandNum(int maxLimit)
{
    return rand() % maxLimit;
}
// END: Generate random number


// START: Helper functions for solving grid
bool FindUnassignedLocation(int grid[9][9], int& row, int& col)
{
    for (row = 0; row < 9; row++)
    {
        for (col = 0; col < 9; col++)
        {
            if (grid[row][col] == UNASSIGNED)
                return true;
        }
    }

    return false;
}

bool UsedInRow(int grid[9][9], int row, int num)
{
    for (int col = 0; col < 9; col++)
    {
        if (grid[row][col] == num)
            return true;
    }

    return false;
}

bool UsedInCol(int grid[9][9], int col, int num)
{
    for (int row = 0; row < 9; row++)
    {
        if (grid[row][col] == num)
            return true;
    }

    return false;
}

bool UsedInBox(int grid[9][9], int boxStartRow, int boxStartCol, int num)
{
    for (int row = 0; row < 3; row++)
    {
        for (int col = 0; col < 3; col++)
        {
            if (grid[row + boxStartRow][col + boxStartCol] == num)
                return true;
        }
    }

    return false;
}

bool isSafe(int grid[9][9], int row, int col, int num)
{
    return !UsedInRow(grid, row, num) && !UsedInCol(grid, col, num) && !UsedInBox(grid, row - row % 3, col - col % 3, num);
}
// END: Helper functions for solving grid


// START: Create seed grid
void Sudoku::fillEmptyDiagonalBox(int idx)
{
    int start = idx * 3;
    random_shuffle(this->guessNum, (this->guessNum) + 9, genRandNum);
    for (int i = 0; i < 3; ++i)
    {
        for (int j = 0; j < 3; ++j)
        {
            this->grid[start + i][start + j] = guessNum[i * 3 + j];
        }
    }
}

void Sudoku::createSeed()
{
    /* Fill diagonal boxes to form:
        x | . | .
        . | x | .
        . | . | x
    */
    this->fillEmptyDiagonalBox(0);
    this->fillEmptyDiagonalBox(1);
    this->fillEmptyDiagonalBox(2);

    /* Fill the remaining blocks:
        x | x | x
        x | x | x
        x | x | x
    */
    this->solveGrid(); // TODO: not truly random, but still good enough because we generate random diagonals.

    // Saving the solution grid
    for (int i = 0; i < 9; i++)
    {
        for (int j = 0; j < 9; j++)
        {
            this->solnGrid[i][j] = this->grid[i][j];
        }
    }
}
// END: Create seed grid


// START: Intialising
Sudoku::Sudoku()
{

    // initialize difficulty level
    this->difficultyLevel = 0;

    // Randomly shuffling the array of removing grid positions
    for (int i = 0; i < 81; i++)
    {
        this->gridPos[i] = i;
    }

    random_shuffle(this->gridPos, (this->gridPos) + 81, genRandNum);

    // Randomly shuffling the guessing number array
    for (int i = 0; i < 9; i++)
    {
        this->guessNum[i] = i + 1;
    }

    random_shuffle(this->guessNum, (this->guessNum) + 9, genRandNum);

    // Initialising the grid
    for (int i = 0; i < 9; i++)
    {
        for (int j = 0; j < 9; j++)
        {
            this->grid[i][j] = 0;
        }
    }

    grid_status = true;
}
// END: Initialising


// START: Custom Initialising with grid passed as argument
Sudoku::Sudoku(string grid_str, bool row_major)
{
    if (grid_str.length() != 81)
    {
        grid_status = false;
        return;
    }

    // First pass: Check if all cells are valid
    for (int i = 0; i < 81; ++i)
    {
        int curr_num = grid_str[i] - '0';
        if (!((curr_num == UNASSIGNED) || (curr_num > 0 && curr_num < 10)))
        {
            grid_status = false;
            return;
        }

        if (row_major) grid[i / 9][i % 9] = curr_num;
        else          grid[i % 9][i / 9] = curr_num;
    }

    // Second pass: Check if all columns are valid
    for (int col_num = 0; col_num < 9; ++col_num)
    {
        bool nums[10] = { false };
        for (int row_num = 0; row_num < 9; ++row_num)
        {
            int curr_num = grid[row_num][col_num];
            if (curr_num != UNASSIGNED && nums[curr_num] == true)
            {
                grid_status = false;
                return;
            }
            nums[curr_num] = true;
        }
    }

    // Third pass: Check if all rows are valid
    for (int row_num = 0; row_num < 9; ++row_num)
    {
        bool nums[10] = { false };
        for (int col_num = 0; col_num < 9; ++col_num)
        {
            int curr_num = grid[row_num][col_num];
            if (curr_num != UNASSIGNED && nums[curr_num] == true)
            {
                grid_status = false;
                return;
            }
            nums[curr_num] = true;
        }
    }

    // Fourth pass: Check if all blocks are valid
    for (int block_num = 0; block_num < 9; ++block_num)
    {
        bool nums[10] = { false };
        for (int cell_num = 0; cell_num < 9; ++cell_num)
        {
            int curr_num = grid[((int)(block_num / 3)) * 3 + (cell_num / 3)][((int)(block_num % 3)) * 3 + (cell_num % 3)];
            if (curr_num != UNASSIGNED && nums[curr_num] == true)
            {
                grid_status = false;
                return;
            }
            nums[curr_num] = true;
        }
    }

    // Randomly shuffling the guessing number array
    for (int i = 0; i < 9; i++)
    {
        this->guessNum[i] = i + 1;
    }

    random_shuffle(this->guessNum, (this->guessNum) + 9, genRandNum);

    grid_status = true;
}
// END: Custom Initialising


// START: Verification status of the custom grid passed
bool Sudoku::verifyGridStatus()
{
    return grid_status;
}
// END: Verification of the custom grid passed


// START: Printing the grid
void Sudoku::printGrid()
{
    for (int i = 0; i < 9; i++)
    {
        for (int j = 0; j < 9; j++)
        {
            if (grid[i][j] == 0)
                cout << ".";
            else
                cout << grid[i][j];
            cout << "|";
        }
        cout << endl;
    }

    cout << "\nDifficulty of current sudoku(0 being easiest): " << this->difficultyLevel;
    cout << endl;
}
// END: Printing the grid


// START: Modified Sudoku solver
bool Sudoku::solveGrid()
{
    int row, col;

    // If there is no unassigned location, we are done
    if (!FindUnassignedLocation(this->grid, row, col))
        return true; // success!

    // Consider digits 1 to 9
    for (int num = 0; num < 9; num++)
    {
        // if looks promising
        if (isSafe(this->grid, row, col, this->guessNum[num]))
        {
            // make tentative assignment
            this->grid[row][col] = this->guessNum[num];

            // return, if success, yay!
            if (solveGrid())
                return true;

            // failure, unmake & try again
            this->grid[row][col] = UNASSIGNED;
        }
    }

    return false; // this triggers backtracking

}
// END: Modified Sudoku Solver


// START: Check if the grid is uniquely solvable
void Sudoku::countSoln(int& number)
{
    int row, col;

    if (!FindUnassignedLocation(this->grid, row, col))
    {
        number++;
        return;
    }


    for (int i = 0; i < 9 && number < 2; i++)
    {
        if (isSafe(this->grid, row, col, this->guessNum[i]))
        {
            this->grid[row][col] = this->guessNum[i];
            countSoln(number);
        }

        this->grid[row][col] = UNASSIGNED;
    }

}
// END: Check if the grid is uniquely solvable


// START: Gneerate puzzle
void Sudoku::genPuzzle()
{
    for (int i = 0; i < 81; i++)
    {
        int x = (this->gridPos[i]) / 9;
        int y = (this->gridPos[i]) % 9;
        int temp = this->grid[x][y];
        this->grid[x][y] = UNASSIGNED;

        // If now more than 1 solution , replace the removed cell back.
        int check = 0;
        countSoln(check);
        if (check != 1)
        {
            this->grid[x][y] = temp;
        }
    }
}
// END: Generate puzzle


// START: Printing into SVG file
void Sudoku::printSVG(string path = "")
{
    string fileName = path + "svgHead.txt";
    ifstream file1(fileName.c_str());
    stringstream svgHead;
    svgHead << file1.rdbuf();

    std::cout<<svgHead.str() << endl;

    file1.close();

    string svg="puzzle.svg";
    if (!path.empty())
        svg = path;         

    ofstream outFile(svg); // std::ios::binary
    outFile << svgHead.rdbuf();

    for (int i = 0; i < 9; i++)
    {
        for (int j = 0; j < 9; j++)
        {
            cout << grid[i][j]<< endl;
            if (this->grid[i][j] != 0)
            {
                int x = 50 * j + 16;
                int y = 50 * i + 35;

                stringstream text;
                text << "<text x=\"" << x << "\" y=\"" << y << "\" style=\"font-weight:bold\" font-size=\"30px\">" << this->grid[i][j] << "</text>\n";

                outFile << text.rdbuf();
            }
        }
    }

    outFile << "<text x=\"50\" y=\"500\" style=\"font-weight:bold\" font-size=\"15px\">Difficulty Level (0 being easiest): " << this->difficultyLevel << "</text>\n";
    outFile << "</svg>";
    outFile.close();

}


void Sudoku::printSolveSVG(string path = "")
{
    string fileName = path + "svgHead.txt";
    ifstream file1(fileName.c_str());
    stringstream svgHead;
    svgHead << file1.rdbuf();

    std::cout << svgHead.str() << endl;

    file1.close();

    string svg = "puzzle.svg";
    if (!path.empty())
        svg = path;

    ofstream outFile(svg); // std::ios::binary
    outFile << svgHead.rdbuf();

    for (int i = 0; i < 9; i++)
    {
        for (int j = 0; j < 9; j++)
        {
            cout << grid[i][j] << endl;
            if (this->grid[i][j] != 0)
            {
                int x = 50 * j + 16;
                int y = 50 * i + 35;

                stringstream text;
                text << "<text x=\"" << x << "\" y=\"" << y << "\" style=\"font-weight:bold\" font-size=\"30px\">" << this->grid[i][j] << "</text>\n";

                outFile << text.rdbuf();
            }
        }
    }

    outFile << "<text x=\"50\" y=\"500\" style=\"font-weight:bold\" font-size=\"15px\">Difficulty Level (0 being easiest): " << this->difficultyLevel << "</text>\n";
    outFile << "</svg>";
    outFile.close();

}

void Sudoku::printSolveDataSVG(vector<vector<int>> data, string path = "")
{

    string fileName = path + "svgHead.txt";
    ifstream file1(fileName.c_str());
    stringstream svgHead;
    
    svgHead << file1.rdbuf();

    std::cout << svgHead.str() << endl;

    file1.close();

    string svg = "puzzle.svg";
    if (!path.empty())
        svg = path;

    ofstream outFile(svg); // std::ios::binary
    outFile << svgHead.rdbuf();

    for (int i = 0; i < 9; i++)
    {
        for (int j = 0; j < 9; j++)
        {
            cout << data[i][j] << endl;
            if (data[i][j] != 0)
            {
                int x = 50 * j + 16;
                int y = 50 * i + 35;

                stringstream text;
                text << "<text x=\"" << x << "\" y=\"" << y << "\" style=\"font-weight:bold\" font-size=\"30px\">" << this->grid[i][j] << "</text>\n";

                outFile << text.rdbuf();
            }
        }
    }

    outFile << "<text x=\"50\" y=\"500\" style=\"font-weight:bold\" font-size=\"15px\">Difficulty Level (0 being easiest): " << this->difficultyLevel << "</text>\n";
    outFile << "</svg>";
    outFile.close();

}
// END: Printing into SVG file


// START: Calculate branch difficulty score
int Sudoku::branchDifficultyScore()
{
    int emptyPositions = -1;
    int tempGrid[9][9];
    int sum = 0;

    for (int i = 0; i < 9; i++)
    {
        for (int j = 0; j < 9; j++)
        {
            tempGrid[i][j] = this->grid[i][j];
        }
    }

    while (emptyPositions != 0)
    {
        vector<vector<int> > empty;

        for (int i = 0; i < 81; i++)
        {
            if (tempGrid[(int)(i / 9)][(int)(i % 9)] == 0)
            {
                vector<int> temp;
                temp.push_back(i);

                for (int num = 1; num <= 9; num++)
                {
                    if (isSafe(tempGrid, i / 9, i % 9, num))
                    {
                        temp.push_back(num);
                    }
                }

                empty.push_back(temp);
            }

        }

        if (empty.size() == 0)
        {
            cout << "Hello: " << sum << endl;
            return sum;
        }

        int minIndex = 0;

        int check = empty.size();
        for (int i = 0; i < check; i++)
        {
            if (empty[i].size() < empty[minIndex].size())
                minIndex = i;
        }

        int branchFactor = empty[minIndex].size();
        int rowIndex = empty[minIndex][0] / 9;
        int colIndex = empty[minIndex][0] % 9;

        tempGrid[rowIndex][colIndex] = this->solnGrid[rowIndex][colIndex];
        sum = sum + ((branchFactor - 2) * (branchFactor - 2));

        emptyPositions = empty.size() - 1;
    }

    return sum;

}
// END: Finish branch difficulty score


// START: Calculate difficulty level of current grid
void Sudoku::calculateDifficulty()
{
    int B = branchDifficultyScore();
    int emptyCells = 0;

    for (int i = 0; i < 9; i++)
    {
        for (int j = 0; j < 9; j++)
        {
            if (this->grid[i][j] == 0)
                emptyCells++;
        }
    }

    this->difficultyLevel = B * 100 + emptyCells;
}
// END: calculating difficulty level

int main(int argc, char const* argv[])
{
    std::cout << "Hello World!涂聚文!geovindu,Geovin Du\n";

    /*
    int grid[9][9] = {
    {0, 0, 0, 0, 0, 9, 0, 5, 0},
    {0, 0, 8, 0, 0, 0, 0, 7, 9},
    {0, 0, 1, 5, 0, 2, 0, 0, 0},
    {3, 0, 0, 0, 1, 0, 5, 0, 7},
    {2, 0, 4, 0, 0, 7, 0, 0, 0},
    {0, 0, 0, 6, 0, 0, 2, 0, 0},
    {0, 0, 0, 0, 7, 0, 3, 4, 0},
    {1, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 3, 0, 0, 5, 6, 0, 0, 0}
    };
    string fileName = "svgHead.txt";
    ifstream file1(fileName.c_str());
    stringstream svgHead;
    svgHead << file1.rdbuf();

    std::cout << svgHead.str() << endl;

    ofstream outFile("puzzle.svg"); // std::ios::binary
    outFile << svgHead.rdbuf();
    
    for (int i = 0; i < 9; i++)
    {
        for (int j = 0; j < 9; j++)
        {
            if (grid[i][j] != 0)
            {
                int x = 50 * j + 16;
                int y = 50 * i + 35;

                stringstream text;
                text << "<text x=\"" << x << "\" y=\"" << y << "\" style=\"font-weight:bold\" font-size=\"30px\">" << grid[i][j] << "</text>\n";

                outFile << text.rdbuf();
            }
        }
    }

    outFile << "<text x=\"50\" y=\"500\" style=\"font-weight:bold\" font-size=\"15px\">Difficulty Level (0 being easiest): " << 3 << "</text>\n";
    outFile << "</svg>";
    outFile.close();
    */

    // Initialising seed for random number generation
    srand(time(NULL));

    // Creating an instance of Sudoku
    Sudoku* puzzle = new Sudoku();

    // Creating a seed for puzzle generation
    puzzle->createSeed();

    // Generating the puzzle
    puzzle->genPuzzle();

    // Calculating difficulty of puzzle
    puzzle->calculateDifficulty();

    // testing by printing the grid
    puzzle->printGrid();

    // Printing the grid into SVG file
    string rem = "sudokuGen";
    string path = argv[0];
    path = path.substr(0, path.size() - rem.size());
    //puzzle->printSVG(path);
    puzzle->printSVG("");// 生成当前问题图
    cout << "The above sudoku puzzle has been stored in puzzles.svg in current folder\n";

    puzzle->solveGrid(); //解决方案
    puzzle->printGrid();//打印解决方案
    puzzle->printSVG("solvepuzzle.svg");// 生成当前答案问题图 没有生成
    /*
    puzzle->getGrid();
    cout << puzzle->getGrid() << endl;
    cout << puzzle->getArr().size() << endl;
    int row = puzzle->getArr().size();
    int col = puzzle->getArr().size();
    vector<vector<int>> data = puzzle->getArr();
    for (int i = 0; i < row; i++)
        for (int j = 0; j < col; j++)
        {
            if (j < col - 1)
                cout << data[i][j] << "\t";
            else
                cout << data[i][j] << endl;
        }
    puzzle->printSolveDataSVG(data, "sovepuzzle.svg");
    //puzzle->printSolveSVG("sovepuzzle.svg");
    */
    // freeing the memory
    delete puzzle;
    system("pause");
    return 0;
}

// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
// 调试程序: F5 或调试 >“开始调试”菜单

// 入门使用技巧: 
//   1. 使用解决方案资源管理器窗口添加/管理文件
//   2. 使用团队资源管理器窗口连接到源代码管理
//   3. 使用输出窗口查看生成输出和其他消息
//   4. 使用错误列表窗口查看错误
//   5. 转到“项目”>“添加新项”以创建新的代码文件,或转到“项目”>“添加现有项”以将现有代码文件添加到项目
//   6. 将来,若要再次打开此项目,请转到“文件”>“打开”>“项目”并选择 .sln 文件

 

 

from:

https://github.com/robatron/sudoku.js js
https://github.com/huaminghuangtw/Web-Sudoku-Puzzle-Game js
https://github.com/t-dillon/tdoku c++
https://github.com/MorvanZhou/sudoku python
https://github.com/RutledgePaulV/sudoku-generator python
https://github.com/JoeKarlsson/python-sudoku-generator-solver python
https://github.com/BurnYourPc/Sudoku python
https://github.com/ArjArav98/Sudoku-Solver c++
https://github.com/flyingpeakock/Console_sudoku c++
https://github.com/sfuhrm/sudoku java
https://github.com/basil2style/Sudoku-Pattern-Generator-App java
https://github.com/Yanndroid/Sudoku java
https://github.com/firateski/SudokuLibrary C#
https://github.com/firateski/SudokuGameWindowsForms C#
https://github.com/nayanbunny/Sudoku-CSharp C#
https://github.com/ashplaysbass97/Sudoku C#
https://github.com/Maxhebda/SudokuMaxSolver C#
https://github.com/ilyakom/Sudoku c#
https://github.com/topics/sudoku-generator