https://github.com/Issac-Newton/Sudoku_extendgit
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 30 |
· Estimate | · 估计这个任务须要多少时间 | 30 | 30 |
Development | 开发 | 990 | 2060 |
· Analysis | · 需求分析 (包括学习新技术) | 180 | 480 |
· Design Spec | · 生成设计文档 | 0 | 0 |
· Design Review | · 设计复审 (和同事审核设计文档) | 0 | 0 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 10 |
· Design | · 具体设计 | 120 | 360 |
· Coding | · 具体编码 | 360 | 600 |
· Code Review | · 代码复审 | 120 | 120 |
· Test | · 测试(自我测试,修改代码,提交修改) | 180 | 490 |
Reporting | 报告 | 130 | 150 |
· Test Report | · 测试报告 | 60 | 60 |
· Size Measurement | · 计算工做量 | 10 | 30 |
· Postmortem & Process Improvement Plan | · 过后总结, 并提出过程改进计划 | 60 | 60 |
合计 | 1150 | 2240 |
Information Hiding:Core类的全部数据成员都声明为private,全部访问都只能经过Core模块提供的四个接口实现;
Interface Design:针对-c,-s,-n -m,-n -r -u这几种参数组合咱们分别设计了接口,每一个接口负责完成一个类型的参数组合的功能,尽量保证接口功能的单一性;
Loose Coupling:Core模块的接口对传入的参数除类型外没有要求,会自行对参数的合法性进行检查,减小了对调用时参数的要求;另外Core发生改变时只要接口不变不会对调用它的类产生影响;github
咱们共实现了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的几种搭配;
首先对参数选项字符进行确认,而后对选项后面的参数进行提取,有错误则报异常。编程
各个异常类将在以后详细说明;数组
(咱们的计算模块只有一个Core,不太懂这个UML怎么画...)
函数
花费时间约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();
布局
Design By Contract:http://en.wikipedia.org/wiki/Design_by_contract
Code Contract:http://msdn.microsoft.com/en-us/devlabs/dd491992.aspx性能
单元测试结果:
单元测试
项目的单元测试主要是回归测试,对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函数以后断言这个变量为真。每一个测试点跑过以后,将该值设置为假。
覆盖率以下:
Ⅰ.参数个数异常(定义为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;
如下将按照从上到下的顺序来对整个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...以及在写界面的过程当中两个直男因为审美不一样还产生了一些分歧..
界面的最终效果图以下:
界面设计和计算模块之间的联系主要是界面使用的数字是从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二维数组的初始化,随机填了五个数字。
结对编程:
本人:
结对伙伴:
咱们测试的小组是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:
User Ten:
我对这款软件有几点建议:
首先,我建议增长一个帮助菜单或帮助按钮。由于软件的界面虽然简单,可是对于那几个按钮都没有功能介绍,在询问开发者以前我都不知道Hint按钮是须要先选中一个输入框再点击Hint按钮的。
其次,我建议增长一个Clear按钮,改变Restart按钮的功能。界面中Restart按钮的功能是从新开始本局游戏,数独是不会改变的,每次改变数独须要在Mode菜单中从新选择难易度,我认为不如增长一个Clear按钮实现目前Restart按钮实现的清空已输入的功能,Restart按钮的功能改变为从新生成一个新的当前难易度下的数独。
第三,我建议增长一个保存功能,能够保存当前正在作的数独,下次打开软件能够继续上次的游戏。
改进:
关于发布的目录:如今发布时将全部的依赖项都放到了一个文件夹下,而后将快捷方式放到了和文件夹同目录下。
关于帮助:如今提供了help功能,如图:
添加了这个图片同时解决了关于右下角空白的问题。 关于不一样电脑上各类图标大小显示比例的问题,通过更改界面,咱们已经可以支持在 100%和125%上界面是没问题的,可是若是这个比例更大会有些问题。 其余关于GUI的美化问题,作了一些修改,可是...让全部人都满意好难... 保存功能和其余一些功能因为时间缘由,未添加。