1)Github傳送門
https://github.com/Issac-Newton/Sudoku_extend
2)PSP表
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | 30 | |
· Estimate | · 估計這個任務需要多少時間 | ||
Development | 開發 | 990 | 2060 |
· Analysis | · 需求分析 (包括學習新技術) | 180 | 480 |
· Design Spec | · 生成設計文檔 | ||
· Design Review | · 設計複審 (和同僚稽核設計文檔) | ||
· Coding Standard | · 代碼規範 (為目前的開發制定合适的規範) | 10 | |
· Design | · 具體設計 | 120 | 360 |
· Coding | · 具體編碼 | 600 | |
· Code Review | · 代碼複審 | ||
· Test | · 測試(自我測試,修改代碼,送出修改) | 490 | |
Reporting | 報告 | 130 | 150 |
· Test Report | · 測試報告 | 60 | |
· Size Measurement | · 計算工作量 | ||
· Postmortem & Process Improvement Plan | · 事後總結, 并提出過程改進計劃 | ||
合計 | 1150 | 2240 |
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_contract
Code 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
-->右上角的九宮格不能放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");
}
【第五部分】
下載下傳位址:
使用者回報
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的美化問題,做了一些修改,但是...讓所有人都滿意好難...
儲存功能和其他一些功能由于時間原因,未添加。