个人项目-数独游戏
0. 前言
个人简介
作者:icode_499
学校:北京理工大学
专业:计算机科学与技术专业
项目简介
任务:生成数独终局并且能求解数独问题的控制台程序。
1. PSP表格
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
· Estimate | · 估计这个任务需要多少时间 | 30 | 30 |
Development | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 120 | 20+ |
· Design Spec | · 生成设计文档 | 90 | 30+ |
· Design Review | · 设计复审 (和同事审核设计文档) | - | |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | - | |
· Design | · 具体设计 | 90 | 30+ |
· Coding | · 具体编码 | 600 | 300+ |
· Code Review | · 代码复审 | 60 | 10+ |
· Test | · 测试(自我测试,修改代码,提交修改) | 120 | 30+ |
Reporting | 报告 | ||
· Test Report | · 测试报告 | 60 | |
· Size Measurement | · 计算工作量 | 20 | |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | |
合计 | 920 |
iteration-1 需求
- 在命令行中使用-c 参数加数字 N(1<=N<=1000000)控制生成数独终局的数量,例如下述命令将生成 20 个数独终局至文件中。
sudoku -c 20
- 将生成的数独终局用一个文本文件(假设名字叫 sudoku.txt)的形式保存起来,每次生成的 txt 文件需要覆盖上次生成的 txt 文件,文件内的格式如下,数与数之间由空格分开,终局与终局之间空一行,行末无空格。
- 程序在处理命令行参数时,不仅能处理格式正确的参数,还能够处理各种异常的情况。
- 在生成数独矩阵时,左上角的第一个数为:(学号后两位相加)% 9 + 1。例如学生 A 学号后 2 位是 80,则该数字为(8+0)% 9 + 1 = 9,那么生成的数独棋盘应如下(x 表示满足数独规则的任意数字)。
解题思路
- 按照需求4,我的学号后两位为65,则左上角的第一个数为(6+5)% 9 + 1 = 3。
- 按照要求1,最多要生成 1000000 个不同的数独终局。
- 数独终局的生成思路:
经过学习,我发现可以这样构建一个合法的数独:对于这个给定的数独终局,从第二行开始,每行分别是第一行左移 3,6,1,7,4,2,5,8 列的结果。
这样构建出来的数独终局可以分解成 9 个 3 × 3 3 \times 3 3×3 的表,每个表中都有 1-9 九个不重复数字。
而题目中要求我生成的数独终局左上角第一个数为 3,第一行还有 8 个空可以随意填入 {1, 2, 4, 5, 6, 7, 8, 9} 这 8 个数字,相当于是 8 个数字的全排列,这样可以得到 8!= 40320 种数独终局。我们将这样的数独终局称为基础数独终局。
但是这还并未达到预期要生成的最大数独终局数,于是我们可以在已生成的数独终局上进行变换产生新的数独终局。
注意到,对于任何数独,都可以任意交换它的1-3行,4-6行,7-9行,1-3列,4-6列,7-9列,而使得交换后的数独仍然是一个合法的数独。
对于本项目而言,由于第 1 行第 1 个数字固定为 3,因此我们不能和第一行数据交换,因此本项目提出了这样的变换策略:
(1) 任意交换4-6行。
(2) 任意交换7-9行。
这样可以使数独终局数目变为原来的 3 ! × 3 ! = 36 3! \times 3! =36 3!×3!=36 倍,此时数独终局一共 36 × 40320 = 1451520 36 \times 40320=1451520 36×40320=1451520 种终局,已经超过了 1000000 种,已经满足需求了。
设计实现
主函数流程图
关键类介绍
这里只介绍关键的实现,给出两个关键类的定义:
- Sudoku类
/* Sudoku.h */
#pragma once
#include "include.h"
class Sudoku
{
private:
int data[9][9]; // 数独终局
/* exMid[0]=5表示 交换后中间三行的第一行是基础数独终局的第(5+1)=6行
也即交换后第4行数据换为第6行数据 */
int exMid[3];
int exLast[3];
void exchangeMid(const int _numMid);
void exchangeLast(const int _numLast);
void setRow(const int num, const int row[9]); // 将row的数据写入第num行
public:
Sudoku();
Sudoku(const int data[9][9]);
~Sudoku();
void writeToFile(FILE *fp) const; // 将data数据写入文件
void setFirstRow(const int firstRow[9]); // 设置首行数据
void createFromFirstRow();// 由首行数据分别移动{3,6,1,4,7,2,5,8}得到数独终局
void createNew(const int _num, FILE *outputFile, int flag = 0); // 36种变换
};
- SudokuGenerator类
#pragma once
#include "include.h"
#include "Sudoku.h"
class SudokuGenerator
{
private:
FILE *outputFile; // 输出文件,sudoku.txt
int sudokuNum; // 需要生成的数独终局数量
int firstRow[9]{};
public:
SudokuGenerator(const std::string &outputFile, const int sudokuNum);
~SudokuGenerator();
void GenerateSudoku(); // 生成数独终局
};