返回目录
https://github.com/514DNA/sudoku
git
返回目录
github
方法:Information Hiding, Interface Design, Loose Coupling算法
生成数独终局编程
void generate(int number, int[][] result)
生成设置难度级别的数独游戏数组
void generate(int number, int mode, int[][] result)
生成设置挖空数量的数独游戏缓存
void generate(int number, int lower, int upper, bool unique, int[][] result)
解数独题性能优化
bool solve(int[] puzzle, int[] solution)
数据成员和方法分别有public类型和private类型,仅将外部须要调用的数据和方法公开,规范好传入的参数的格式,返回符合要求的结果。函数的实现过程,内部调用的函数不对用户公开dom
返回目录
函数
代码一共有两个类:数独类和命令行参数处理类性能
数独类主要有以下函数:
class Core
{
public
void set_play(bool a);//设置为游戏模式
void create_sudoku_puzzle(int n, int mode);
void create_sudoku_puzzles(int block_num, int mode, int n);
void create_random_sudoku();
void solve_all_soduku(FILE *fp);
void generate(int number, int result);
void generate(int number, int mode, int result);
void generate(int number, int lower, int upper, bool unique, int result);
bool solve(int* puzzle, int* solution);
private:
void init_sudoku();//初始化数独
int init_check_puzzle();//挖空后是否单解
int can_delete(int addr);//可否挖空
};
命令行处理类有以下函数:
class arg_info
{
public:
int read_arg_info(int argc, charargv); //读取命令函参数
void run_cmd(int argc, char **argv);
private:
int str2num(char *str);
int str2range_num(char *str);
void error_out(int error);//错误输出
void set_arg_bit_on(int mode);//模式设置
};
主要说一下生成数独游戏的函数之间的关系
生成数独游戏首先要调用create_sudoku_puzzles这个函数,而后根据生成题目的数量调用n次create_sudoku_puzzle函数。
这个函数首先调用建立数独的函数,若是是命令行中调用,那么就调用create_test_sudoku,这个函数不会生成重复等价数独;若是是GUI调用(给玩家玩的),那么就调用create_random_sudoku,这个函数保证了随机性。
生成完数独就开始挖空,这些是挖空用的函数:
int can_delete(int addr);
int can_delete_senior(int addr);
调用can_delete用来判断经过低级方法能够挖掉的格子;调用can_delete_senior用来判断经过高级方法能够挖掉的格子;这三个函数是来回递归判断数独是不是惟一解的函数,由can_delete_senior调用:
int to_next(int i, int j, int n);
int init_check_puzzle();
int check_puzzle(int i, int j, int n);
这个函数是把原来尝试挖掉的空加上的函数,也由can_delete_senior调用
void add_addr(int addr, int num);
找到能够挖的空以后调用一次clear_addr把空挖掉,都挖完调用一次print_sudoku输出
分阶段产生数独游戏
采用两种方法对生成的数独终局挖空:低级方法——四种排除法直接把一个数推出来;高级方法——猜数并验证。
若是某一个数挖完以后能被简单方法再推出来,那么就把这个格子放在缓存区内;找完当前全部的数,那么就随机挖掉一个缓存区里面的空。可是这样不容易挖够55个空,那么只能经过猜数来挖空。若是一个数挖完以后仍是单解数独,那么就挖掉。经过暴力回溯法判断数独解是否惟一;从数独格子中的最后一个空,往前寻找。若是挖完这个格子仍是单解数独,那么就挖掉,而后继续往前找,直到挖够55个空就中止挖空。
这个挖空方法的独到之处就是会先找到全部低级挖空方法能够挖的空,这样就减小了暴力回溯的次数
程序中主要费时间的地方是判断挖完空以后的数独仍是单解数独。咱们解数独有如下几种方法:1.四种排除法直接把一个数推出来(简单方法);2.猜数(复杂方法)
挖空过程当中,若是某一个数挖完以后能被简单方法再推出来,那么就把这个格子放在缓存区内。找完当前全部的数,那么就随机挖掉一个缓存区里面的空。可是这样不容易挖够55个空,那么只能经过猜数来挖空,猜数是这样。若是一个数挖完以后仍是单解数独,那么就挖掉。经过暴力回溯法判断数独解是否惟一,就形成了运行的缓慢
最开始是把全部挖过以后解惟一的数找到再随机一个,可是后来发现这样作,判断解惟一性的次数就多,要追求效率,就要减小这方面花的时间,也就是减小判断的次数。因此从前日后找,找到第一个能够挖的格子就挖掉,而后再继续找,直到挖的空够55个。
可是这样改了以后更慢了,这种回溯法对前面空多后面空少的数独很棘手,会判断的很是慢。因此就改为从后往前找,从后往前挖,速度提升。这就是执行世界最难指令:n 10000 -r 55~55 -u所用的时间,虽然挖空的函数仍是用了不少时间,可是总体已经有很大改进了
-c 1000000
-n 10000 -r 55~55 -u
生成数独时,消耗最大的函数是create_sudoku;生成数独游戏时,消耗最大的函数是create_sudoku_puzzle
单元测试代码 | 测试的函数 | 测试数据构造思路 |
---|---|---|
TEST_METHOD(SingleSolution) |
create_sudoku_puzzle(int n) |
|
TEST_METHOD(generate) { |
generate() |
|
TEST_METHOD(solve) { |
solve() |
|
有些用来更方便看到结果的输出到控制台的函数没有被调用,因此不能达到100%
类别 | 设计目标 | 单元测试样例 | 应用场景 |
---|---|---|---|
命令行参数(该部分使用命令行测试) | 处理非法的参数 | sudoku.exe -a |
输入未定义的参数 |
处理超出范围或格式不正确的数字 | sudoku.exe -c 100c |
n,c,r,m后面的参数不正确 | |
处理个数错误的参数 | sudoku.exe -c -c 100 |
参数重复出现,参数后面没有数字 | |
处理错误的参数组合 | sudoku.exe -c 100 -u |
参数组合错误,即不属于定义的6种组合 | |
读入的数独题 | 处理不存在的文件路径 | sudoku.exe -s "non.txt" | 文件不存在 |
处理格式不正确的文件 | sudoku.exe -s "non.exe" | 处理文件格式不正确的状况 | |
处理非法的数独题 | int puzzle[81] = { 0, 0, 0, 8, c, 9, 0, 2, 0, 7, 0, 0, 0, 0, 0, 8, 4, 5, 0, 0, 5, 0, 7, 6, 0, 9, 0, 0, 0, 8, 7, 0, 0, 3, 0, 0, 0, 9, 6, 0, 1, 8, 0, 0, 0, 4, 0, 0, 3, 0, 0, 0, 1, 0, 8, 0, 0, 0, 0, 2, 0, 0, 6, 0, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; |
处理数独题内容不正确的状况 | |
处理无解的数独题 | int puzzle[81] = { 0, 0, 0, 8, 8, 9, 0, 2, 0, 7, 0, 0, 0, 0, 0, 8, 4, 5, 0, 0, 5, 0, 7, 6, 0, 9, 0, 0, 0, 8, 7, 0, 0, 3, 0, 0, 0, 9, 6, 0, 1, 8, 0, 0, 0, 4, 0, 0, 3, 0, 0, 0, 1, 0, 8, 0, 0, 0, 0, 2, 0, 0, 6, 0, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; |
处理输入的数独题无解的状况 |
按照需求将页面划分为四个部分:起始状态的难度选择模块,数独题模块,功能按钮模块,时间、记录展现模块,界面中的各类控件使用.ui文件生成,控件的切换由transGUI控制,信号由coreConnect控制。
功能比较简单,主要有提示、暂停,能够在开始时进行难度选择,也能够在游戏过程当中点击返回按钮从新选择难度,或者询问后退出。游戏界面显示当前用时和本难度最高纪录。点击提交反馈是否正确,并询问是有继续游戏。
编辑UI文件,自动生成空间的代码
class Ui_sudokuGUIClass
{
public:
QWidget centralWidget;
QWidget verticalLayoutWidget_2;
QVBoxLayout panelLayout;
//...数据成员
void setupUi(QMainWindow sudokuGUIClass); // setupUi
void retranslateUi(QMainWindow *sudokuGUIClass); // retranslateUi
};
在transGUI中对各控件的状态进行设置,实现页面的转换,与计算模块的对接也设置在这一部分
class transGUI : public QObject{
Q_OBJECT
public:
QTimer *timer;
QLineEdit *sudokuLineEdit[9][9];
transGUI(Ui_sudokuGUIClass UI);
void writeRecord();
signals:
public slots:
void play();
void updateTime();
void stop();
void goOn();
void option();
void quit();
void quitCancel();
void inform();
void submit();
private:
Ui_sudokuGUIClass ui;
Core core; //计算模块
QTime recTime;
QLabel *sudokuLabel[9][9];
int **answer, **puzzle;
int mode = 0; //难度级别
FILE *fp; //记录文件
QTime recTimes[3]; //最高记录
void readRecord();
};
在coreConnect中实现信号函数和槽函数的连接。
class coreConnect :public QObject{
Q_OBJECT
public:
coreConnect(Ui_sudokuGUIClass UI, transGUI *Trans);
private:
Ui_sudokuGUIClass ui;
transGUI *trans;
QTimer *timer;
void startConnect();
void timeConnect();
void resetTimeConnect();
void backConnect();
void quitConnect();
void quitCancelConnect();
void informConnect();
void submitConnect();
void setTimer();
void againConnect();
signals:
private slots:
void reSetTimer();
};
须要用到计算模块的部分有获取数独题、提示、结果验证的部分。为了更好的用户体验,产生的数独都是标准数独,所以在生成数独时已经有了惟一解,在获取数独题将数独题存入puzzle数组的同时也将数独的答案存储进answer数组,方便提示和提交时使用。
游戏中难度的选择对应-m参数,所以点击游戏开始时,选择的难度级别做为参数m,调用generate(int number, int mode, int** result)函数,由于一次只产生一个数独游戏,因此number设为1,在计算模块中设置play参数,分别将数独终局和数独游戏存入result。
提示:用户点击空格后点击提示按钮,在右侧提示信息处会出现此处应填的数字
暂停:点击暂停按钮,中止计时
返回:退回难度选择页面,从新选择难度级别
退出:点击退出按钮,弹出对话框询问是否确认退出
提交:进行结果验证,告知用户答案是否正确,询问继续游戏仍是退出
在周二课上决定结对。
最开始在放假前完成了代码复审的部分,发现两我的思路思惟方式很是不同,对方对性能有很高的追求,思惟方式比较独特,有不少神奇的脑洞;我思路比较窄,也比较循规蹈矩。两我的最大的共同点是我的项目都没有写注释,也都没有写GUI。。。
国庆节期间两人大部分时间都不在学校,基本没有任何结对开展工做,双方独立进行,对方进行了算法设计,我学习了GUI和生成dll的方法。很是很差的一点是没有一块儿对项目进行规划和设计,基本顺其天然。
假期快结束时危机感上升,开始进入一有时间就一块儿写代码的状态,以对方的代码为基础,按照对方设计的算法两人共同完成了核心部分。接下来对方主要进行性能优化、异常处理,我完成了参数处理,对代码进行了测试和单元测试,完成了GUI。
项目 | 优势 | 缺点 |
---|---|---|
结对编程 |
|
|
对方 |
|
|
本身 |
|
|
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 60 | 30 |
60 | 30 | ||
Development | 开发 | 1380 | 1590 |
300 | 120 | ||
180 | 60 | ||
60 | 60 | ||
60 | 30 | ||
120 | 240 | ||
1800 | 2400 | ||
120 | 120 | ||
240 | 240 | ||
Reporting | 报告 | 240 | 180 |
180 | 120 | ||
60 | 60 | ||
60 | 60 | ||
Sum | 合计 | 3240 | 3480 |
刘畅 15061183
王辰昱 15231177
若是不改动GUI代码:合做小组使用咱们的Core.dll每次生成的puzzle都是相同的;咱们使用合做小组的Core.dll没法得到puzzle。
用咱们的测试模块测试合做小组的solve(int[] puzzle, int[] solution)时,若是输入的puzzle不合法(如同一行中有两个一样的数字),没有返回false
合做小组对非法数组进行了异常处理,会抛出一个InvalidPuzzleException,可是没有把Exception包含在Core.dll中,双方异常处理的位置和方式不一样
咱们小组的GUI和Core之间存在标准接口之外的交互,在Core中自定义了不少东西,致使合做小组没法正常使用。根本缘由是咱们小组在作GUI的部分时对Core的使用不规范,没有彻底按照定义的接口使用而是设置了其余变量,所以原有的GUI的代码也没法使用其余小组的Core.dll。
对GUI界面部分的代码进行修改,调用Core的标准接口。
另外咱们的异常处理都在计算模块外部进行,即默认传入Core的接口的参数合法,应该对参数进行检查,使它有必定的异常处理能力。
另外应该向合做小组学习,他们的Core模块很是简洁,包装很是好除了必要的接口没有多余的东西,能够模块划分应该也作得很是好,咱们不该该把计算模块、输入输出都混杂在Core里面。