结对项目-数独程序扩展

mac2022-06-30  30

1)Github传送门

https://github.com/Issac-Newton/Sudoku_extend

2)PSP表

PSP2.1Personal Software Process Stages预估耗时(分钟)实际耗时(分钟)Planning计划3030· Estimate· 估计这个任务需要多少时间3030Development开发9902060· Analysis· 需求分析 (包括学习新技术)180480· Design Spec· 生成设计文档00· Design Review· 设计复审 (和同事审核设计文档)00· Coding Standard· 代码规范 (为目前的开发制定合适的规范)3010· Design· 具体设计120360· Coding· 具体编码360600· Code Review· 代码复审120120· Test· 测试(自我测试,修改代码,提交修改)180490Reporting报告130150· Test Report· 测试报告6060· Size Measurement· 计算工作量1030· Postmortem & Process Improvement Plan· 事后总结, 并提出过程改进计划6060合计11502240

3)结对编程中Information Hiding, Interface Design, Loose Coupling原则的使用

Information Hiding:Core类的所有数据成员都声明为private,所有访问都只能通过Core模块提供的四个接口实现;Interface Design:针对-c,-s,-n -m,-n -r -u这几种参数组合我们分别设计了接口,每个接口负责完成一个类型的参数组合的功能,尽可能保证接口功能的单一性;Loose Coupling:Core模块的接口对传入的参数除类型外没有要求,会自行对参数的合法性进行检查,减少了对调用时参数的要求;另外Core发生改变时只要接口不变不会对调用它的类产生影响;

4)计算模块接口的设计与实现过程。

我们共实现了10个类,分别为Core,Handler以及8个异常类;计算模块为Core模块;在Core中设计和实现了4个接口,分别是: void generate(int number, int result[][CELL]);用来生成最多100,0000个数独终局,对于该接口的实现,我们在判断传进来的参数的合法后,直接调用TraceBack()函数生成满足数量要求的数独终盘; void generate(int number, int mode, int result[][CELL]);用来生成最多10,000个模式为mode的数独题目,对于该接口的实现,我们首先检查参数的合法性,然后生成满足数量要求的数独终盘,之后再根据模式来对数独进行修改; void generate(int number, int lower, int upper, bool unique, int result[][CELL]);用来生成最多10,000个挖空数在lower到upper之间的数独题目,其中unique为true时表示数独题目只有唯一解;对于该接口的实现,我们首先检查参数的合法性,然后生成满足数量要求的数独终盘,之后再根据挖空数以及unique的值来对数独进行修改,若unique的值为true则每次修改完成后都调用IsSingleSolution()函数来检查数独题目是否为唯一解; bool solve(int puzzle[CELL], int solution[CELL]);用来解sudoku.txt文件中的数独题目,若能解出数独题目则调用CopySudoku()将答案复制到solution中;共10个函数,除上述的4个接口外,还有: Core();构造函数 bool IsValid(int pos, bool isSolve);检查每次填的数是否满足数独的要求,若满足则返回true,否则返回false; bool TraceBackSolve(int pos);用回溯法检查数独问题是否有解,若有解则返回true并求解,否则返回false; int TraceBack(int pos, int number, int& count, int result[][CELL], bool isSolve);用回溯法生成最多100,0000个数独题目,每填一个格子都会调用IsValid()函数来检查正确性,每生成一个数独终盘都会调用CopySudoku()函数将终盘复制到result中; void CopySudoku(int result[CELL], int temp[GRIDSIZE][GRIDSIZE]);将结果复制到result中; bool IsSingleSolution(int tot, int& ans);用回溯法判断生成的数独题目是否为唯一解,若有唯一解则返回false,否则返回true;算法的关键..就是回溯,没有多余的技巧; 以下是int TraceBack(int pos, int number, int& count, int result[][CELL], bool isSolve);的流程图: 以下是void generate(int number, int lower, int upper, bool unique, int result[][CELL]);的流程图:

Handler类用于处理命令行输入; 这个类的成员变量会有输入参数的信息,像是生成数独终盘的个数和生成数独游戏时要挖空的个数。 在对参数进行处理时,我们是按照参数个数对输入进行判断的。具体情况如下: 参数个数大于6或者是小于3,参数个数异常; 参数个数等于3,有效输入只可能是-c或者是-s; 参数个数等于4,有效输入只可能是-n和-u的搭配; 参数个数等于5,有效输入可能是-n和-m的搭配或者是-n和-r的搭配; 参数个数等于6,有效输入可能是-n -u -r的几种搭配; 首先对参数选项字符进行确认,然后对选项后面的参数进行提取,有错误则报异常。

各个异常类将在之后详细说明;

5)计算模块的UML图

(我们的计算模块只有一个Core,不太懂这个UML怎么画...)

6)计算模块接口部分的性能改进

花费时间约3小时; 改进思路:由于我们依旧采用回溯法,因此对之前的功能的性能没有更多改进;我们主要针对void generate(int number, int lower, int upper, bool unique, int result[][CELL]);这一函数进行性能改进,一开始我们的算法是针对某一个数独终盘,每随机挖一个空都立刻检查是否有唯一解,若唯一则随机挖下一个,否则还原这个空重新挖,若无法找到满足条件的挖空位置则回溯,但测试以后发现算法本身好像出了写问题,生成了多解数独; 于是我们采用了一次性随机产生所有挖空位置,挖好后再检查是否有唯一解的算法,我们的性能改进主要是减少产生的随机数的碰撞次数(实际上就是凑...),但是一直都最后也没能很好的提高产生挖空数为55的唯一解的数独题目的性能。

性能分析图是void generate(int number, int lower, int upper, bool unique, int result[][CELL]);在生成1个挖空数为55的唯一解的数独问题的性能分析图;消耗最大的函数是IsValid();

7)Design By Contract,Code Contract的优缺点以及在结对编程时的实际应用

Design By Contract:http://en.wikipedia.org/wiki/Design_by_contractCode Contract:http://msdn.microsoft.com/en-us/devlabs/dd491992.aspx

优点:使用者和被调用者地位平等,双方必须彼此履行义务,才可以行驶权利。调用者必须提供正确的参数,被调用者必须保证正确的结果和调用者要求的不变性。双方都有必须履行的义务,也有使用的权利,这样就保证了双方代码的质量,提高了软件工程的效率和质量。缺点:会给接口的调用带来较大的风险,需要严格的验证参数的正确性;应用:一开始我们对Core模块的接口采用契约式设计...后来不知怎么的我们又在Core中对传入的部分参数做了正确性验证..但又没有验证传进来的数组size是否足够..现在看来设计的有点四不像,看来需要改进;

8)项目的单元测试展示:

单元测试结果:

项目的单元测试主要是回归测试,对generate和solve的测试以及对输入处理的测试。回归测试: 增量修改工程之后要对之前的功能做一个覆盖性检查

//-c TEST_METHOD(TestMethod4) { int result[100][CELL]; int grid[GRIDSIZE][GRIDSIZE]; set<string> container; string inserted; Core core; core.generate(100, result); for (int i = 0; i < 100; i++) { for (int j = 0; j < GRIDSIZE; j++) { for (int k = 0; k < GRIDSIZE; k++) { grid[j][k] = result[i][j * GRIDSIZE + k]; assert(!(grid[j][k] <= 9 && grid[j][k] >= 1)); inserted.push_back(grid[j][k] + '0'); } } Assert::IsTrue(valid(grid)); container.insert(inserted); inserted.clear(); } assert(container.size() == 100); }

每生成一个数独终盘就对数独的有效性进行检测,最后对数量进行检测,方法是将每个数独都转化为一个字符串,将字符串插入到一个集合中,可以找到生成的数独的数量。

//-s TEST_METHOD(TestMethod7) { int puzzle[CELL]; int solution[CELL]; Core core; bool flag = true; FILE* file_in; freopen_s(&file_in, "C:\\Users\\dell\\Source\\sudoku\\ModeTest\\sudoku.txt", "r", stdin); assert(file_in != NULL); while (true) { if (fscanf(file_in, "%d", &puzzle[0]) == EOF) { break; } for (int i = 1; i < CELL; i++) { fscanf(file_in, "%d", &puzzle[i]); } assert(core.solve(puzzle,solution)); int grid[GRIDSIZE][GRIDSIZE]; for (int j = 0; j < CELL; j++) { grid[j / GRIDSIZE][j % GRIDSIZE] = solution[j]; } assert(valid(grid)); } }

每次从文件中读入一个数独就调用solve函数进行求解,求解之后对数独有效性进行判断,然后和solve函数的返回值进行比较

对新增功能的测试: 下面代码是对-u -r -n组合的测试

TEST_METHOD(TestMethod5) { int result[1000][CELL]; Core core; core.generate(2, 55, 55, true, result); bool flag = true; for (int i = 0; i < 2; i++) { int grid[GRIDSIZE][GRIDSIZE]; for (int j = 0; j < CELL; j++) { grid[j / GRIDSIZE][j % GRIDSIZE] = result[i][j]; } int ans = 0; if (MultiSolution(0, ans, grid)) { flag = false; Assert::IsTrue(flag); } } }

我们对生成好的数独游戏进行暴力求解(回溯法),如果有多解,那么断言失败。

//判断数独是不是有多解的函数 bool MultiSolution(int tot, int& ans, int grid[GRIDSIZE][GRIDSIZE]) { if (tot == GRIDSIZE * GRIDSIZE) { ans++; return true; } else { int x = tot / GRIDSIZE; int y = tot % GRIDSIZE; if (grid[x][y] == 0) { for (int i = 1; i <= 9; i++) { grid[x][y] = i; if (IsValid(tot, grid)) { if (MultiSolution(tot + 1, ans, grid)) { if (ans > 1) { return true; } continue; } } } grid[x][y] = 0; } else { return MultiSolution(tot + 1, ans, grid); } } return false; }

对异常的测试: 因为新增的有效输入只有几种,我们对每种都做出检查

以参数的数字范围异常为例,代码如下:

TEST_METHOD(TestMethod10) { //-c char* command[5] = { "sudoku.txt","-c","10000001"}; try { main(3, command); } catch (exception& e) { Assert::IsTrue(typeid(e) == typeid(NumberOutOfBoundException)); } assert(hasException); hasException = false; //-n char* command1[5] = {"sudoku.txt","-n","10001","-m","1"}; try { main(5,command1); } catch (exception& e) { Assert::IsTrue(typeid(e) == typeid(NumberOutOfBoundException)); } assert(hasException); hasException = false; //-m(模式错误) char* command2[5] = { "sudoku.txt","-n","1000","-m","4" }; try { main(5,command2); } catch (exception& e) { Assert::IsTrue(typeid(e) == typeid(ModeException)); } assert(hasException); hasException = false; //-r char* command3[5] = {"sudoku.exe","-n","10","-r","50~56"}; try { main(5,command3); } catch (exception& e) { Assert::IsTrue(typeid(e) == typeid(NumberOutOfBoundException)); } assert(hasException); hasException = false;

我们首先将参数传入main函数,然后用assert将异常抛出的异常类型和应该抛出的异常类型做比较,但是如果没有抛出异常岂不是漏了bug。所以,在一个头文件里我定义了一个标记异常发生过的变量,main函数每次捕捉到异常之后就将该变量赋值为真,main函数之后断言这个变量为真。每个测试点跑过之后,将该值设置为假。

覆盖率如下:

9)计算模块部分异常处理说明

Ⅰ.参数个数异常(定义为ParametersNumberException) 设计目标:如果输入命令参数过多,那么程序抛出异常。 设计单元测试:

char* command[8] = {"sudoku.exe","-n","100","-n","-r","-s","-m","-d"}; try { main(9,(char**)command); } catch (exception& e) { Assert::IsTrue(typeid(e) == typeid(ParametersNumberException)); } assert(hasException); hasException = false;

Ⅱ.文件不存在异常(定义为FileNotExistException) 设计目标:-s命令下,如果打开文件失败,那么抛出异常。 设计单元测试:

TEST_METHOD(TestMethod9) { char* command[3] = { "sudoku.exe","-s","NotExist.txt" }; try { main(3,command); } catch (exception& e) { Assert::IsTrue(typeid(e) == typeid(FileNotExistException)); } assert(hasException); hasException = false; }

Ⅲ.命令中的各种数字溢出异常(定义为NumberOutOfBoundException) 设计目标:在各种参数下,如果数字不符合规范,抛出异常。 设计单元测试: 见上部分异常单元测试部分 ↑↑

Ⅳ.-r选项后面的数字异常(定义为RParametersException) 设计目标:在-r参数后面,如果后面跟的参数字符长度不是5或者第三个字符不是 ~ 或者存在不是1-9的字符,那么抛出异常。 设计单元测试:

char* command[5] = { "sudoku.exe","-n","10","-r","20-55"}; try { main(5, command); } catch (exception& e) { Assert::IsTrue(typeid(e) == typeid(RParametersException)); } assert(hasException); hasException = false; char* command2[20] = { "sudoku.exe","-n","10","-r","3n~40" }; try { main(5, command); } catch (exception& e) { Assert::IsTrue(typeid(e) == typeid(RParametersException)); } assert(hasException); hasException = false;

Ⅴ.命令中包含非法字符(定义为IllegalCharException) 设计目标:在-c这样的选项不能匹配时抛出异常。 设计单元测试

char* command[20] = { "sudoku.exe","-nn","10","-r","20~55" }; try { main(5, (char**)command); } catch (exception& e) { Assert::IsTrue(typeid(e) == typeid(IllegalCharException)); } assert(hasException); hasException = false;

Ⅵ.-s参数中数独无解(定义为NoSolutionException) 设计目标:如果-s参数后面文件中的数独无解,抛出异常 设计单元测试:

char* command[20] = { "sudoku.exe","-s","puzzle.txt"}; try { main(3,command); } catch (exception& e) { Assert::IsTrue(typeid(e) == typeid(NoSolutionException)); } assert(hasException); hasException = false;

文件中的数独: 000000123 009000000 000009000 000000000 000000000 000000000 000000000 000000000 000000000 -->右上角的九宫格不能放9

Ⅶ.数字错误异常(定义为IllegalNumberException) 设计目标:在求解数独的时候,如果从文件中读入的数字不在1-9,抛出异常 设计单元测试: 单元测试代码同上一个异常类型,但是文件中的数独中包含不在1-9的数字

Ⅷ. -m 后面的模式错误(定义为ModeException) 设计目标:检查generate参数中模式是不是1,2,3如果不是,抛出异常 设计单元测试:

char* command2[5] = { "sudoku.txt","-n","1000","-m","4" }; try { main(5,command2); } catch (exception& e) { Assert::IsTrue(typeid(e) == typeid(ModeException)); } assert(hasException); hasException = false;

10)界面模块的详细设计过程

以下将按照从上到下的顺序来对整个GUI进行描述 GUI菜单栏中有选择模式和查看每个模式下最佳记录的两个Action,每个里面都有三个选项-->easy,normal,hard 下面就是数独盘(左上角),右上角是计时器。 数独的实现使用的控件是textEdit,我们重写了这个控件的部分函数,改变鼠标focusIn和focusOut的行为,使之能够在鼠标定位到某个未填块的时候将边框标红;在鼠标离开的时候能够对输入的字符进行判断处理。代码如下:

void MyTextEdit::focusInEvent(QFocusEvent *e) { if (!isReadOnly()) { setStyleSheet(QString::fromUtf8("font: 20pt \"\351\273\221\344\275\223\";""border: 3px solid red")); } emit cursorPositionChanged(); } void MyTextEdit::focusOutEvent(QFocusEvent *e) { QString str; str = toPlainText(); int position = textCursor().position(); int length = str.count(); if (!isReadOnly()) { setStyleSheet(QString::fromUtf8("font: 23pt \"\351\273\221\344\275\223\";""border: 1px solid grey;color:blue")); if (!IsValidChar(str)) { setPlainText(""); setCursorWidth(0); } else { //setStyleSheet("color:blue"); setAlignment(Qt::AlignCenter); } } }

右上角的时钟支持暂停功能,暂停之后,数独盘上的所有模块都会清空,当继续之后又会恢复之前的数字。 清空和恢复的代码如下:

//清空 for (int i = 0; i < GRIDSIZE; i++) { for (int j = 0; j < GRIDSIZE; j++) { QString str = ui.textEdit[i][j]->toPlainText(); if (IsValidChar(str)) { int num = str.toInt(); m_fill[i * GRIDSIZE + j] = num; if (ui.textEdit[i][j]->isReadOnly()) { m_fillBlack[i * GRIDSIZE + j] = num; } ui.textEdit[i][j]->setReadOnly(false); QString strIn = ""; ui.textEdit[i][j]->setText(strIn); } ui.textEdit[i][j]->setReadOnly(true); } } //显示 for (int i = 0; i < GRIDSIZE; i++) { for (int j = 0; j < GRIDSIZE; j++) { ui.textEdit[i][j]->setReadOnly(false); if (m_fill[i * GRIDSIZE + j] > 0) { QString str = QString::number(m_fill[i * GRIDSIZE + j], 10); ui.textEdit[i][j]->setText(str); if (m_fillBlack[i * GRIDSIZE + j] == 0) //此时要用蓝色字体 { ui.textEdit[i][j]->setStyleSheet(QString::fromUtf8("font: 23pt \"\351\273\221\344\275\223\";""border: 1px solid grey;color:blue")); ui.textEdit[i][j]->setAlignment(Qt::AlignCenter); ui.textEdit[i][j]->setReadOnly(false); } else { ui.textEdit[i][j]->setStyleSheet(QString::fromUtf8("font: 23pt \"\351\273\221\344\275\223\";")); ui.textEdit[i][j]->setAlignment(Qt::AlignCenter); ui.textEdit[i][j]->setReadOnly(true); } } else { QString str = ""; ui.textEdit[i][j]->setText(str); } } }

清空的时候我们是先将数独中现有的数字拷贝下来,因为我们在生成的时候有一个挖空的未填的备份,所以可以知道之后哪个空是人为填的,所以显示的时候可以区分开人为填的空格(这两个显示是不一样的)。

数独盘下面有三个按钮,功能分别是“重新开始”,“检查答案”,“提示”, 重新开始就是将原来的textEdit控件上面的字符清空,然后将原来的那个重新填入。 检查答案就是将现在textEdit上的数字和答案数字相对比,如果有不同,那么会弹出一个弹窗。 提示功能是提示上一次鼠标定位到的未填的格子中的数字,并且将这个数字填入这个格子,之后这个格子的数字和最开始生成游戏时的字体一样。

if (ui.focusIn != NULL) { int col; int line; for (int i = 0; i < GRIDSIZE; i++) { for (int j = 0; j < GRIDSIZE; j++) { if (ui.focusIn == ui.textEdit[i][j]) { line = i; col = j; break; } } } int num = m_result[line * GRIDSIZE + col]; QString str = QString::number(num, 10); ui.textEdit[line][col]->setText(str); ui.textEdit[line][col]->setStyleSheet(QString::fromUtf8("font: 21pt \"\351\273\221\344\275\223\";""border: 1px solid grey")); ui.textEdit[line][col]->setAlignment(Qt::AlignCenter); ui.textEdit[line][col]->setReadOnly(true); informSuccess = true; }

其中,我们在记录上一次鼠标定位的位置遇到了困难,因为不能够在类定义中对该对象进行赋值,所以没办法在鼠标focusIn的时候将指示对象指针赋值。所以我们只能在类定义外面对对象指针进行赋值。我们对每个textEdit块 connect 一个槽函数,在focusIn的时候 emit 一个信号。focusIn的函数见上,其中,可以看到emit 一个 cursorPositionChanged() 信号,之后触发槽函数,槽函数获得调用者,对指针进行赋值

MyTextEdit* temp = qobject_cast<MyTextEdit*>(sender()); if (!temp->isReadOnly()) { ui.focusIn = temp; }

另外还做得一些工作就是美工,这部分比较复杂,字体,背景,边框等等...每个控件用的方法也不一样,不过大体上使用 palette和setStyleSheet两种方法居多。 使用palette示例:

//整个窗口的背景 QPixmap pixmap = QPixmap("background.jpg").scaled(GUITestClass->size()); QPalette palette(GUITestClass->palette()); palette.setBrush(QPalette::Background, QBrush(pixmap)); GUITestClass->setPalette(palette); //最下面三个按钮的样式 QString button_style = "QPushButton{font-family:Comic Sans MS;font-size:16pt;background-image:url(button1.jpg); color:white; border-radius:10px;border-style: outset;}" "QPushButton:pressed{background-image:url(pressed1.jpg);border-style:inset;}"; pushButton_3->setStyleSheet(button_style); pushButton_4->setStyleSheet(button_style); pushButton_5->setStyleSheet(button_style);

感受:一开始以为加个界面应该很快,后来我们才发现自己还是naive...以及在写界面的过程中两个直男由于审美不同还产生了一些分歧..

11)界面模块与计算模块的对接

界面的最终效果图如下:

界面设计和计算模块之间的联系主要是界面使用的数字是从Core模块中产生出来的,界面调用Core模块中函数的代码如下:

int save_sudoku[1][CELL]; memset(save_sudoku, 0, sizeof(save_sudoku)); bool choosen[10]; memset(choosen,0,sizeof(choosen)); srand(time(0)); for (int i = 0; i < 5; i++) { int posi = rand() % 9 + 1; while (choosen[posi]) { posi = rand() % 9 + 1; } choosen[posi] = true; save_sudoku[0][i] = posi; } int reduce; int empty; switch (m_mode) { case EASY: reduce = 40 + rand() % 8; break; case MIDDLE: reduce = 32 + rand() % 8; break; case HARD: reduce = 31 + rand() % 8; break; default: break; } empty = CELL - reduce; Core temp; temp.generate(1, empty, empty, true, save_sudoku); memset(m_fillBlack, 0, sizeof(m_fillBlack)); memset(m_fill,0,sizeof(m_fill)); for (int i = 0; i < CELL; i++) { m_fill[i] = save_sudoku[0][i]; m_fillBlack[i] = save_sudoku[0][i]; m_backup[i] = save_sudoku[0][i]; } m_hasStarted = true; temp.solve(save_sudoku[0], m_result); showNumber();

因为在Core模块中为了保证生成数独的速度,所以传入的result矩阵是空矩阵。但是,因为使用的是回溯法,这样就会造成每两个相邻的矩阵十分相似,可想而知,这样会严重影响用户的体验,所以,我们在GUI模块里添加了对result二维数组的初始化,随机填了五个数字。

12)描述结对的过程

13)结对编程的优点和缺点&结对的每一个人的优点和缺点

结对编程:

优点:1.在设计时,两个人的思维更加开阔,设计的比一个人设计时更全面;2.不间断的交流与复审,可以在开发和写代码的过程中就减少大量bug;3.在遇到问题时,两人合作解决问题的能力更强;缺点:1.在遇到一些细节问题(比如界面用哪种字体哪张图片)时,两个人容易发生分歧,需要一方退让;2.两个人的思维有时可能不在同一个点上,导致讨论了半天发现讲的不是同一个问题,总之就是很难保证结对双方思维的一致性;3.一个人在写代码时,时间一长边上的另一个人可能注意力会越来越不集中;4.每个人对另一个人写的代码印象不深刻,导致在出现问题时大部分时间只能靠写那段代码的人分析;

本人:

优点:考虑的比较多比较全面;在遇到问题时思维比较开阔;容易沟通;缺点:在一旁复审代码时注意力容易不集中;对于结对伙伴忽略我的建议而自己思考会有些不耐烦;对项目进度一直不够乐观;

结对伙伴:

优点:脾气好,容易沟通;对工具的使用非常优秀;学习能力强;缺点:经常当前阶段的bug还没解决就直接想着后面的阶段了;

附加题部分

【第四部分】

我们测试的小组是15061187窦鑫泽 + 15061189李欣泽,测试我们的小组是15061199李奕君 + 14011100赵奕 我们找到的错误Issue到了对应小组的github项目地址 我们使用他们的Core模块发现不能捕捉到异常,也就是说他们的异常抛出是在他们项目的main函数里面。 我们被找的错误 Github 其中一个问题是我们的solve函数的问题,因为solve函数用的是回溯法来解,只会判断每个位置是不是满足数独对这个位置的要求,但是没有考虑到整体的要求。 最终导致那个错误的发生,所以,我们在求解完之后又对求解的数独进行了一次检验。

for (int i = 0; i < GRIDSIZE; i++) { for (int j = 0; j < GRIDSIZE; j++) { m_grid[i][j] = puzzle[i*GRIDSIZE + j]; } } if (TraceBackSolve(0)) { CopySudoku(solution, m_grid); if (valid(m_grid)) { return true; } } throw NoSolutionException("The sudoku has no solution.\n\n");

valid就是对数独进行有效性检验的函数。

针对另外一个问题,因为我们用回溯法生成数独终盘之后挖空,而且传入的数组是空数组,所以就会从第一个位置开始回溯,这样导致每两个数独之间的相似性很大, 设计游戏的时候我们也考虑到了这个问题,所以在GUI工程里面调用generate函数之前先对矩阵进行一些初始化,所以,这就导致我们的模块不具备随机化的功能。 根据赵奕、李奕君小组提出的问题,我们把那个初始化放到了core模块里面。

bool choosen[10]; memset(choosen, 0, sizeof(choosen)); srand(time(0)); for (int i = 0; i < 5; i++) { int posi = rand() % 9 + 1; while (choosen[posi]) { posi = rand() % 9 + 1; } choosen[posi] = true; m_grid[0][i] = posi; }

针对遇到异常时的反馈不明确,我们又对这一部分进行了细化。

if ((number < 1)) { throw NumberOutOfBoundException("The number after -n is smaller than minimum 1.\n\n"); } if ((number < 1) || (number > MAX_N)) { throw NumberOutOfBoundException("The number after -n is bigger than maximum 1000000.\n\n"); } if ((upper > EMPTY_UPPER)) { throw NumberOutOfBoundException("The number of upper is bigger than maximum 50.\n\n"); } if ((upper < EMPTY_LOWER)) { throw NumberOutOfBoundException("The number of upper is smaller than minimum 20.\n\n"); } if ((lower > EMPTY_UPPER)) { throw NumberOutOfBoundException("The number of lower is bigger than maximum 50.\n\n"); } if ((lower < EMPTY_LOWER)) { throw NumberOutOfBoundException("The number of lower is smaller than minimum 20.\n\n"); }

【第五部分】

下载地址:

https://github.com/Issac-Newton/Sudoku_extend

用户反馈

User One: 和一般的软件认知不一样,不能将单独的exe文件拷贝到桌面上。 不同电脑上字符有差异。

User Two: 没有说明; Hint的功能对新手不是特别容易使用; 界面过于单调,做对做错的弹窗差别不是特别明显。

User Three: 界面对新手不是很友好。 用户提出了新的需求(添加回退功能)。

User Four: 希望能够添加一个保存功能,保存上次未做完的游戏。

User Five: 希望可以有帮助菜单提供数独规则。

User Six: 不同电脑上显示的兼容性有差异。 希望提示功能做得更加智能一些,不要只是简单的显示答案。

User Seven: 亮点在于:游戏有暂停功能,方便用户使用;数独支持键盘填写,有一定便捷性。 不足在于:在未完成的时候,check应该显示未完成,而不是错误答案;界面的布局不够美观,如计时功能不够居中,右下方存在一定的蜜汁空白;对用户的提示过于简单,用户只能靠个人去摸索需要用键盘输入。

User Eight: 暂停功能是亮点,感觉打开gui直接进入到游戏页面有些突兀。界面右侧的说明引导步骤必要,但是有些过于简略。数独按钮的风格不知能不能在优美一点?

User Nine:

exe标题的sudoku.exe多了个点.上面的menu的“Personal Best”中间的空格不建议,给人一种2个menu的错觉。建议用一个单词或去掉空格当暂停的时,再点Hint会出现一个格子的值。

User Ten: 我对这款软件有几点建议: 首先,我建议增加一个帮助菜单或帮助按钮。因为软件的界面虽然简单,但是对于那几个按钮都没有功能介绍,在询问开发者之前我都不知道Hint按钮是需要先选中一个输入框再点击Hint按钮的。 其次,我建议增加一个Clear按钮,改变Restart按钮的功能。界面中Restart按钮的功能是重新开始本局游戏,数独是不会改变的,每次改变数独需要在Mode菜单中重新选择难易度,我认为不如增加一个Clear按钮实现目前Restart按钮实现的清空已输入的功能,Restart按钮的功能改变为重新生成一个新的当前难易度下的数独。 第三,我建议增加一个保存功能,可以保存当前正在做的数独,下次打开软件可以继续上次的游戏。

改进: 关于发布的目录:现在发布时将所有的依赖项都放到了一个文件夹下,然后将快捷方式放到了和文件夹同目录下。 关于帮助:现在提供了help功能,如图:

添加了这个图片同时解决了关于右下角空白的问题。 关于不同电脑上各种图标大小显示比例的问题,经过更改界面,我们已经能够支持在 100%和125%上界面是没问题的,但是如果这个比例更大会有些问题。 其他关于GUI的美化问题,做了一些修改,但是...让所有人都满意好难... 保存功能和其他一些功能由于时间原因,未添加。

转载于:https://www.cnblogs.com/Minstrel/p/7668919.html

相关资源:JAVA上百实例源码以及开源项目
最新回复(0)