1.github地址:https://github.com/514DNA/sudoku
2|14.
3.Information Hiding:
数独类中所有数据均用private修饰,防止玩家作弊
类与类之间也是通过接口函数访问的
Interface Design:
由于这个项目较为简单(相对系统设计等大型工程),接口较少,所以也没有做太多接口设计
Loose Coupling:
没有考虑相关问题
4. 代码一共有两个类:数独类和命令行参数处理类
数独类刨去构建函数析构函数有如下几个函数
命令行处理类刨去构建函数析构函数有如下几个函数
我主要说一下生成数独游戏的函数之间的关系(生成终局和解数独的前面的博客说过,在此就不赘述)
生成数独游戏首先要调用create_sudoku_puzzles这个函数
然后根据生成题目的数量调用n次create_sudoku_puzzle函数
这个函数首先调用创建数独的函数
如果是命令行中调用,那么就调用create_test_sudoku,这个函数不会生成重复等价数独
如果是GUI调用(给玩家玩的),那么就调用create_random_sudoku,这个函数保证了随机性
生成完数独就开始挖空
这些是挖空用的函数
调用can_delete用来判断通过低级方法可以挖掉的格子(后面会说低级算法是啥)
调用can_delete_senior用来判断通过高级方法可以挖掉的格子(后面会说高级算法是啥)
这三个函数是来回递归判断数独是否是唯一解的函数,由can_delete_senior调用
这个函数是把原来尝试挖掉的空加上的函数,也由can_delete_senior调用
找到可以挖的空之后调用一次clear_addr把空挖掉
都挖完调用一次print_sudoku输出
代码思路:我主要说一下挖空的思路
低级方法:四种排除法直接把一个数推出来(低级方法)
高级方法:猜数(高级方法)
我挖空是这样,如果某一个数挖完之后能被简单方法再推出来,那么就把这个格子放在缓存区内
找完当前所有的数,那么就随机挖掉一个缓存区里面的空
但是这样不容易挖够55个空
那么只能通过猜数来挖空,猜数是这样
如果一个数挖完之后还是单解数独,那么就挖掉
如何判断数独解是否唯一,那么就通过暴力回溯法
从数独格子中的最后一个空,往前寻找
如果挖完这个格子还是单解数独,那么就挖掉
然后继续往前找
直到挖够55个空就停止挖空
这个挖空方法的独到之处就是会先找到所有低级挖空方法可以挖的空,这样就减少了暴力回溯的次数
5.
6. 我们在改进性能方面花了大致有两小时
程序中主要费时间的地方是:判断挖完空之后的数独还是单解数独
我们解数独大致有这么几种方法:
1.四种排除法直接把一个数推出来(简单方法)
2.猜数(复杂方法)
我挖空是这样,如果某一个数挖完之后能被简单方法再推出来,那么就把这个格子放在缓存区内
找完当前所有的数,那么就随机挖掉一个缓存区里面的空
但是这样不容易挖够55个空
那么只能通过猜数来挖空,猜数是这样
如果一个数挖完之后还是单解数独,那么就挖掉
如何判断数独解是否唯一,那么就通过暴力回溯法
这样就造成了运行的缓慢
最开始是把所有挖过之后解唯一的数找到再随机一个
但是后来发现这样做,判断解唯一性的次数就多
要追求效率,就要减少这方面花的时间,也就是减少判断的次数
所以我从前往后找,找到第一个可以挖的格子就挖掉,然后再继续找,直到挖的空够55个
但是这样改了之后更慢了,然后我就想到,这种回溯法对前面空多后面空少的数独很苦手,会判断的非常慢
所以就改成从后往前找,从后往前挖,速度蹭一下就上来了
这就是执行世界最难指令:n 10000 -r 55~55 -u所用的时间
虽然挖空的函数还是用了很多时间,但是整体已经有很大改进了
7. Design by Contract,Code Contract契约是设计,契约式编码:
我认为,契约式的优点在于可以通过程序降低代码的BUG率。缺点在于会让代码更加难懂,而且在一定程度上会拖慢写代码的速度
我没有使用这种方法来设计我的程序,主要是这种方法加大了编码难度。
我认为,契约式设计虽然减少debug的时间,但是加大了阅读代码的难度
debug的时候我用了类似契约式的方法,就是判断某个值在不在合法区间内,但是最后做好的代码一定是把原来的契约删掉,就算是注释掉也会变得很乱
8.
通过的单元测试
单元测试覆盖率
9.
10.界面模块的详细设计过程
界面模块的设计
按照需求将页面划分为四个部分:起始状态的难度选择模块,数独题模块,功能按钮模块,时间、记录展示模块,界面中的各种控件使用.ui文件生成,控件的切换由transGUI控制,信号由coreConnect控制。
功能比较简单,主要有提示、暂停,可以在开始时进行难度选择,也可以在游戏过程中点击返回按钮重新选择难度,或者询问后退出。游戏界面显示当前用时和本难度最高纪录。点击提交反馈是否正确,并询问是有继续游戏。
代码说明&实现过程
编辑UI文件,自动生成空间的代码
在transGUI中对各控件的状态进行设置,实现页面的转换,与计算模块的对接也设置在这一部分
在coreConnect中实现信号函数和槽函数的链接。
11.界面模块与计算模块的对接
设计
需要用到计算模块的部分有获取数独题、提示、结果验证的部分。为了更好的用户体验,产生的数独都是标准数独,因此在生成数独时已经有了唯一解,在获取数独题将数独题存入puzzle数组的同时也将数独的答案存储进answer数组,方便提示和提交时使用。
对接
游戏中难度的选择对应-m参数,因此点击游戏开始时,选择的难度级别作为参数m,调用generate(int number, int mode, int** result)函数,因为一次只产生一个数独游戏,所以number设为1,在计算模块中设置play参数,分别将数独终局和数独游戏存入 result。
实现的功能
提示:用户点击空格后点击提示按钮,在右侧提示信息处会出现此处应填的数字
暂停:点击暂停按钮,停止计时
返回:退回难度选择页面,重新选择难度级别
12.结对的过程
在周二课上决定结对。
最开始在放假前完成了代码复审的部分,发现两个人思路思维方式非常不一样,对方比较循规蹈矩,比较注重规范,我对性能有很高的追求,思维方式比较独特;两个人最大的共同点是个人项目都没有写注释,也都没有写GUI。。。
国庆节期间两人大部分时间都不在学校,基本没有任何结对开展工作,双方独立进行,对方学习了GUI和生成dll的方法,我进行了算法设计。非常不好的一点是没有一起对项目进行规划和设计,基本顺其自然。
假期快结束时危机感上升,开始进入一有时间就一起写代码的状态,以我的代码为基础,按照我设计的算法两人共同完成了核心部分。对方完成了参数处理,对代码进行了测试和单元测试,完成了GUI,接下来我主要进行性能优化、异常处理。
13.结对编程项目的优点:
可降低BUG发生率,由于是两人轮替工作,因此可工作更长时间,避免了疲劳带来的干扰。
结对编程项目的缺点:
如果运用不当,会出现两人吵架进而降低编码效率
15231058(王子铭)
优点:1.想法主意比较多
2.编码较为灵活
3.代码运行速度较快
缺点:规范性较差
15061180(索一奇)
优点:1.规范性好
2.编码速度较快
3.学习新技术时间较短
缺点:debug较慢
附加题:界面模块,测试模块和核心模块的松耦合
合作小组
刘畅 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的接口的参数合法,应该对参数进行检查,使它有一定的异常处理能力。