数独的空格自由度,指除掉空格本身,空格所在行、列、九宫内的空格数总和。
因此,当只剩一个空格时,此时的自由度为0;当数独全为时,空格数为81,空间自由度为81*24=1944达到最大。
自由度越大代表数独越难,但自由度和空格数不完全程正比。因此那篇博主进行了自由度的阶段区分,并在后文中验证了模型的合理性。
我们关于自由度的定义和他略有不同,主要在于他在算同一行同一列的自由度时,算入了自己。而我们没有。他最终分了10个等级,我们要求的是3个。因此我们最终的难度设定如下:
流程图如下:
具体的Core类的所有函数即功能如下:
例如我测试求解一个数独是不是有唯一解这个函数,我就通过来填入一个有多个解的数独来进行求解其解数,来进行判断。之后我也对有唯一解的数独进行了测试。
TEST_METHOD(TestMethod7){//testsolve_uniqueCores;intppp[M]={6,1,0,3,0,0,0,0,9,0,4,0,7,0,0,0,2,0,0,8,9,1,2,0,0,0,0,0,2,3,0,0,7,0,9,0,4,5,6,0,9,1,2,3,7,0,0,7,0,0,3,0,5,0,0,0,0,0,0,8,0,6,0,5,6,0,0,0,0,0,0,3,9,0,0,6,3,0,0,0,0};intuni=s.solve_unique(ppp);s.out=fopen("unit_out_7.txt","w");fprintf(s.out,"%d\n",uni);Assert::AreEqual((uni>1),true);}测试的原函数代码如下:
intCore::solve_unique(inttmp[M]){hasAnswer=0;//falsememcpy(x,tmp,sizeof(x));memset(a,0,sizeof(a));rep(i,0,80){if(x[i]>0)modifyElement(i,x[i]);}dfs2(1,0);returnhasAnswer;}再例如测试-n-r,并且r1>r2的例子,我是通过在generate里捕获异常把一个布尔变量hasException置为true来判断的:
TEST_METHOD(TestMethod10){//Test-nwith-rCores;s.generate(1,50,40,true,lll);Assert::AreEqual(s.hasException,true);}而我的generate函数的代码如下:
voidCore::generate(intnumber,intlower,intupper,boolunique,intresult[][M]){try{if(number<1||number>10000){throwexception("-nnumbershouldbein[1,10000]");}if(lower<20||upper>55){throwexception("loweranduppershouldbein[20,55]");}if(lower>upper){throwexception("lowercannotbebiggerthanupper");}init_gen(50000,0);generate_single(number,lower,upper-lower+1,0,9999,unique,result);}catch(constexception&e){hasException=true;puts(e.what());}}单元测试覆盖率截图一共测试了20个例子,最终总的覆盖率为94%,主要的函数实现的cpp文件达到了98%,如下图:
我主要采用了在函数里直接catch住异常并打印错误信息到控制台的方法。常见的代码如下:
try{if(situation...){throwexception("...........");}catch(constexception&e){hasException=true;puts(e.what());}}现选取sudoku.cpp中捕获异常的情形和单元测试的例子如下表:
令选取主程序中捕获异常的情形和单元测试的例子如下表:
核心代码如下:
voidsudokuGUI::sudokuButtonClicked(){QPushButton*btn=qobject_cast
voidsudokuGUI::update(){for(inti=0;i
voidsudokuGUI::setBtnZoomAction(QPushButton&btn){QObject::connect(&btn,SIGNAL(pressed()),this,SLOT(setBtnZoomOut()));QObject::connect(&btn,SIGNAL(released()),this,SLOT(setBtnZoomIn()));}voidsudokuGUI::setBtnZoomOut(){QPushButton*btn=qobject_cast
实现对接的代码,调用generate生成数独初局:
voidsudokuGUI::initMatrix(){intres[1][M]={0};core.generate(1,difficultyChosen+1,res);for(inti=0;i boolok=core.solve(puzzle,solution);if(ok){btnTarget->setText(QString::number(solution[i*matrixLen+j],10));}else{QMessageBox::critical(&gameWindow,"warning","Nosolution!");}第四阶段互测我们是三个小组进行互相测试的,我测试的是15061186安万贺,测试我们的是由15061187窦鑫泽来完成的。 首先我们测试安万贺小组的情况如下,我们先把他们的Core.dll和Core.lib拷贝到了我们的sudokuGUI工程下进行测试,发现他们的生成有这样的问题,就是不管怎么开始,终盘的第一行总是1..9,即如下效果: 然后我就给他们提出了issue,后来通过交流发现他们的随机化放在了GUI里而不是generate里进行随机,因此导致我们用的时候总是1..9。 另外是关于命令行的调用。我发现不管是调用generate(1,1,53,false,a);还是c.generate(1,54,53,false,a);或者c.generate(1,20,500000,false,a); 反馈的错误都是: Thenumberafter-risnotintherange. 然后我希望能够他们区分这些错误,就提了issue. 一个可能的测试代码如下: try{Corec;c.generate(1,20,5000,false,a);}catch(NumberOutOfBoundException&e){cout< 如果solve函数的puzzle函数是一个本身就非法的数独,例如一行有两个数是一样的情况,没有捕捉到异常或者返回false,而是也求了一个解。 一个可能的测试代码如下.这个矩阵的第一行有两个9,显然是无解的: intpuzzle[81]={0,0,0,0,0,5,9,0,9,3,0,5,0,0,9,0,0,6,7,8,0,0,2,0,0,0,0,1,0,0,4,0,7,6,0,0,4,5,0,0,0,1,0,0,7,8,0,0,0,0,3,0,0,0,0,0,1,0,7,0,5,0,4,0,6,4,0,0,2,8,7,3,5,7,8,6,3,4,9,1,2};intsolution[81];Corec;c.solve(puzzle,solution);rep(j,0,80){cout< 612345989345789126789126345123457698456891237897263451231978564964512873578634912然后是别人给我们的测试进行的修正。最初始的改正就是把generate的数组的参数从83改成了81,即宏定义的M值。(之前写成83是害怕溢出)因为发现如果参数不同是无法调用别人的函数的。 根据窦哥给我们组的issue如下,是几个比较中肯与实际的测试问题: 然后第三点,关于sudoku.exe-cabc,其中abc是个不存在的文件,此时我们的是不能识别这个异常的。相当于直接闷声过去了。然后增加的修改如下: FILE*fp=NULL;fp=fopen(argv[2],"r");if(fp==NULL){throwstd::exception("-sfiledoesn'texist");}用fopen判断是不是NULL就可以增加捕捉了这个异常。 用户的反馈:用户1:按钮有动态效果,界面很好看。用户2:支持键盘输入数字,很方便。用户3:NoSolution的意义不是很明确。用户4:成功求解数独弹出right之后点击ok能返回主界面就好了。用户5:没有音效。用户6:没有暂停功能。用户7:点击黑色按钮最好不要有动画,因为不能填数字。用户8:不太明白record保存的是什么,如果可以让玩家自己输入文件名就好了。用户9:可以提示哪些数字是填错的就好了。用户10:游戏介绍是中文的就好了。 voidsudokuGUI::timerSwitch(){if(timer.isActive())timer.stop();else{timer.start();}}7.有动画也挺好看的啊,而且改起来没想象中的简单,就不改了。9.填一个数字按一下hint就可以直到当前填的这个数字是不是错的了10.中文有的字会变为乱码,还是英文比较保险。 考虑的优化如下:我们可以不是对一个确定的数独终盘进行一直来挖空,而是当一个数独终盘一直挖空不出唯一解时,跳到下一个进行执行。优化的代码如下: if(times>1000){//执行了这个数独终盘1000次还没有解,就做后一个数独终盘来生成。id++;times=0;}阅读作业看DesignbyContract,CodeContract的内容,描述这些做法的优缺点,说明你是如何把它们融入结对作业中的。首先引用一段契约的概念: 契约作用于两方,每一方都会完成一些任务,从而促成契约的达成,但同时,每一方也会接受一些义务,作为制定契约的前提,有任意一方无视了必尽义的义务,则契约失败 契约式编程,我们在面向对象时曾经常用这个概念。我们当时在在设计程序的时候,需要明确地规定一个类的实例,在调用某个操作前后应当属于何种状态。我们当时需要在所有的类和方法前协商了前置条件,后置条件,不变式等等。 优点:代码规范,有利于实现者、设计者、使用者进行协作。缺点:实现起来有困难,而是契约双方都需要履行责任。在一个大的项目里,一旦有人违反契约,后果不堪设想。 我在第二阶段处理异常时,就遇到了『遇到异常,是由我的Core程序catch住保证不crash还是throw给外面catch』的问题。当时弄的很头疼。最后我和我的partner约定了调用Core的要求,保证他调用时的参数是正确的,而另一方面,作为Core核心的编写者,对于预料范围内的参数错误,进行输出错误信息到控制台反馈给了调用者。 说明结对编程的优点和缺点。结对的每一个人的优点和缺点在哪里(要列出至少三个优点和一个缺点)。(5') partner优缺点 我的优缺点: InformationHiding就类似面向对象的封装的概念,把一些不能共享或者需要考虑安全的信息封装到内部,不让其他的使用者来修改这部分的内容。最明显的例子就是类里的private成员变量和函数。 InterfaceDesign接口是在一定粒度视图上同类事物的抽象表示。设计一套共用的interface来供多个对象来使用。对象的交互只需要通过接口来完成。 LooseCoupling上计组时高小鹏老师曾经多次强调的代码设计应该保持的风格就是高内聚低耦合,而低耦合的概念就是模块和模块间的联系要少。这样有利于代码移植和互相测试。 具体地在我们的程序中最明显的一点就是,我们用Core模块来包装核心的实现generate等功能,然后生成动态链接库给其它的地方直接调用就好,这样既保证了别人无法修改你的generate函数实现信息隐藏,又非常便于调用和移植。然后互测也只要交换dll就好了,很是方便。 人生第一次这种体验,我们采用的分工的方式,总体上我负责后端Core接口的编写、测试,他负责前端。界面的设计我也参与了其中,提供了一些美工和游戏的玩法设置上的建议,主要是颜色还有Button的颜色啊图片啊等等。形成了现在这种以黑色和红色为主色调的界面(和我最近穿的一件格子衬衫颜色几乎一样.-.)如果你们觉得设计的很土我背锅 然后感觉最大的收获就在于Git操作吧。虽然我们是双线并发作战,但仍然存在着两人一起改的冲突这样的局面,因此也算积累了一些协作编程写仓库的经验吧。下图是我们的Git信息的部分截图。