結對項目-數獨程式擴充
step1~step3:github:SE-Sudoku-Pair-master##
step4:github:SE-Sudoku-Pair-dev-combine##
step5:github:SE-Sudoku-Pair-dev-product##
Part1: PSP表-預估
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) |
---|---|---|
Planning | 計劃 | 120 |
Estimate | · 估計這個任務需要多少時間 | 5 |
Development | 開發 | 10 |
Analysis | · 需求分析 (包括學習新技術) | 300 |
Design Spec | · 生成設計文檔 | 30 |
Design Review | · 設計複審 (和同僚稽核設計文檔) | |
Coding Standard | · 代碼規範 (為目前的開發制定合适的規範) | |
Design | · 具體設計 | |
Coding | · 具體編碼 | 1440 |
Code Review | · 代碼複審 | 60 |
Test | · 測試(自我測試,修改代碼,送出修改) | 180 |
Reporting | 報告 | |
Test Report | · 測試報告 | |
Size Measurement | · 計算工作量 | |
Postmortem & Process Improvement Plan | · 事後總結, 并提出過程改進計劃 | |
Total | 合計 | 2280 |
Part2: Information Hiding, Interface Design, Loose Coupling
2.1 Information Hiding
Information hiding is part of the foundation of both structured design and object-oriented design. In structured design, the notion of “black boxes” comes from
information hiding. In object-oriented design, it gives rise to the concepts of encapsulation and modularity, and it is associated with the concept of abstraction.
引自代碼大全,見:http://www.cnblogs.com/magiccode1023/archive/2012/10/23/2736257.html
不用多說,最基本的抽象原則,保證程式的健壯性和靈活性。
上述連結中也提到了,資訊隐藏要隐藏的資訊包括兩方面:
1.隐藏可能的複雜性。
2.隐藏外部改變會有危險的資訊。
隐藏複雜性中隐藏了分而治之的思想,而第2點則是為了程式的安全性。
代碼中的很多地方都隐藏了複雜性:
for (int k = 1; k <= LEN; ++k) {
if (Sudoku::count >= number) return;
if (checkGeneratePos(i, j, k)) { //check if it is ok to set k on (i,j)
board[i][j] = k + '0';
traceBackWriteFile(i, j + 1, number, outFile); //if can,recur to next place
}
}
上述代碼是回溯函數中的一部分,
checkGeneratePos(i,j,k)
就是一個典型的複雜性隐藏,它隐去了方法實作的内部細節,進而讓我們能專注于實作主要的算法。
我們在coding中避免類成員和一些輔助函數暴露,是以将它們設定為
private
:
private:
char board[LEN + 1][LEN + 1];
void init();
inline void traceBackN(int i, int j, int n, int result[][LEN*LEN]);
inline bool traceBackSolve(int i, int j);
inline int getBlock(int i);
void traceBackWriteFile(int i, int j, int number, fstream &outFile);
void traceBackCountSolution(int i, int j, int *solutionNumber, int bound);
void digHoles(int count, int mode, int lower, int upper, int result[][LEN*LEN]);
static long int count;
上述方法或者類成員一旦在外部被調用就會以不正常的方式修改類,是以需要避免。
我們設計方法的一個很重要的原則就是:這個方法具有單一的功能。有些時候總有一些控制方法會整合很多功能,這個時候我們需要分解這些功能為一個個小的功能,并分别實作它們。
2.2 Interface Design
Interface Design簡單來說就是事先約定好子產品的接口,這次實作的兩個
generate
接口以及
solve
接口都是這個的展現,因為對接口有嚴格的規定,在step4我們互換
Core
子產品的時候才沒有太多的麻煩,很容易就完成了代碼。
2.3 Loose Coupling
松耦合在下面連結中有較好的解釋:
stackoverflow-Loose Coupling
其中心思想就是盡量減少子產品之間的“依賴”,最理想的狀态大概是更換程式中任何一個方法或是函數都隻需要在方法或函數内部進行更改,這也是抽象的一種展現,個人認為實作這個原則的最好方法就是在各個子產品之間的輸入輸出之間增加抽象層,這就相當于“松弛”了各個子產品之間的耦合。
我們實作的
solve
方法很好地說明了這一點:
bool Sudoku::solve(int puzzle[], int solution[]) throw(IllegalLengthException) {
bool ret;
convertToTwoDimension(puzzle);
ret = traceBackSolve(1, 1);
if (!check()) {
return false;
}
convertToOneDimension(solution);
return ret;
}
這個方法中
convertToOneDimension
會把目前數獨的解複制到
solution
中,我們并不是直接将類成員中的數組傳出,我們實際上就是在方法的輸出上增加了抽象層。這樣的方法使得如果當我們将數獨類成員中的數組換成1維數組(原來是2維)的時候,我們隻需要在
convertToOneDimension
内部進行修改。
Part3: 計算子產品的設計和實作
我們主要的算法邏輯都集中在
Sudoku
這個類當中。
- 數獨求解的部分我們使用回溯的思想進行解決。回溯方法
對第traceBackSolve()
行第i
列元素及其後方(先向右,到最右則折返換行)空格進行求解,每次求解嘗試從1到9,檢測1到9每個數字是否适合在此格子中填入(行、列、宮不重複),并在嘗試中遞歸調用j
方法,進而驗證每次嘗試的正确性。求解數獨的接口traceBackSolve()
方法負責調用solve()
方法進行求解,并做一二維數組的轉換。traceBackSolve()
- 在生成數獨接口
中,我們采用先生成終盤,再從終盤中挖空的形式進行數獨生成。首先調用generate(int number, int lower, int upper, bool unique, int result[][])
這個已經實作的生成終盤方法,得到generateCompleteN()
個終盤,再使用number
方法進行挖空。挖空政策一共有兩種,一種為從頭數獨第一個數開始,一種為随機選擇。随機挖空由于速度較快,但容易出現挖出來的盤有多解的情況,我們隻在unique為假的情況下使用它。unique為真時,采用順序挖空的政策,以從左到右,從上到下的順序進行挖空,每次挖空之後,将原始數字用1到9中其他數字進行替換,并調用digHoles()
對數獨進行求解,若能解出,則證明此空不能挖,否則可挖,繼續向後挖空。solve()
- 第二個生成數獨接口二
中,我們利用了第一個generate(int number, int mode, int result[][LEN*LEN])
方法,根據generate()
得到相應的mode
和up
傳入down
,便可得到結果。generate()
Part4: UML圖
UML圖如下:

互相之間沒有依存關系。
Part5: 計算子產品的性能改進
5.1 -c
下圖展示了生成1000000個完整數獨的性能分析
由于這次繼承了我上次的代碼,是以代碼本身已經被優化過。
5.272秒,幾乎所有的時間都花費在回溯遞歸上,速度已經可以接受。
一個可能的優化是在判斷重複的時候使用位操作。
5.2 -s
下圖展示了解1000個數獨時候的性能分析:
首先注意到
checksolve
花費較長時間,這個函數原來使用了3×9的時間來判斷,注意到這個方法的下界是1×9,遂更改了實作方式:
int row, col;
row = getBlock(i);
col = getBlock(j);
for (int a = 1; a <= LEN; ++a) {
if ((board[i][a] == k + '0') || (board[a][j] == k + '0')
|| (board[row + ((a - 1) / 3)][col + ((a - 1) % 3)] == k + '0'))
return false;
}
不過,這是常數級别的優化,是以效果很差,改進之後再次性能分析發現效果微弱。
一個可能的改進是使用bitmap來優化。
5.3 -n
直接在
-u
模式下測試,由于當r的參數的值變大的時候生成10000個解的時間幾乎不可接受,是以選擇較低的數值,下圖是指令
-n 10000 -r 25~55
的效能分析:
24秒
熱路徑主要集中于
solve
函數,判斷原因還是由于遞歸時造成的指數級增長的函數調用,在不更改現有結構的情況下已經很難改進。
改進效能花費了30分鐘。
Part6: Design by Contract & Code Contract
契約式程式設計我們已經在OO課上實踐過,其中心思想為:在完成代碼子產品的時候,首先制定“契約”,它描述這個子產品的一些性質,包括調用時候滿足的前置條件(precondition)、方法完成時候滿足的後置條件(postcondition)、方法抛出的異常(exception)以及方法滿足的不變式(invariant),最後根據“契約”來完成代碼。一個比較典型的契約式程式設計的例子就是Assert語句了。
這種程式設計方式首先假定所有輸入都滿足前置條件(precondition),而與其相反的防禦式程式設計則假定輸入會有所有可能的情況,包括正确和錯誤的。
很明顯,契約式程式設計非常适合于在程式開發時使用,同時也有很多工具簡化了這種程式設計方式,這次給出的參考連結中,Code Contracts for .NET就是一個這樣的工具,這個工具可以自動檢測子產品中的契約來測試它們是否滿足條件,進而實作Runtime checking,static checking等功能。不過,在程式釋出的時候一般需要取消契約檢查,因為它對性能也有一定影響。
契約式程式設計非常有助于錯誤的精确定位,雖然絕大多數流行的程式語言在程式運作出錯時都會在一定程度上給出提示,但我們更希望在早期發現程式的錯誤,而不是等到錯誤一層層傳遞到頂層才發現他們。
總結來說,契約式程式設計在設計層面保證了程式的正确性,但當我們将程式釋出,我們就必須做好準備應付各種可能的錯誤,而不是等待使用者去滿足契約了。
這次實作的Core計算子產品的接口也是一種契約,比如
generate(int number,int lower,int upper,bool unique,int result[][])
方法中,調用者需要滿足參數的一些條件,而這個方法也需要滿足在方法完成之後在
result
數組中存儲
number
個規定的數獨遊戲的後置條件(postcondition),我們在測試的時候,也是根據這些條件進行測試的。
Part7: 計算子產品單元測試
7.1 solve函數
測試思路:給出一個題目,和答案對比。
ret = sudoku.solve(puzzle, temp);
Assert::AreEqual(ret, true);
for (int i = 0; i < 81; ++i) {
Assert::AreEqual(temp[i], solution[i]);
}
7.2 generate函數
測試思路:對
-r
指令,首先在生成之後用
solve
函數測試是否可解,然後計算遊戲中的空的個數,判斷是否滿足要求;對
-u
指令,在
-r
的基礎之上用回溯法求出解的個數,如果個數大于1,則出錯,測試
-m
的時候也是類似的方式。
下面是測試
-n 10 -r lower~upper -u
的部分代碼:
sudoku.generate(10, lower, upper, true, result);
for (int i = 0; i < number; ++i) {
Assert::AreEqual(sudoku.solve(result[i], solution), true);
int solutionNumber = sudoku.countSolutionNumber(result[i], 2);
Assert::AreEqual(solutionNumber, 1);
int count = 0;
for (int j = 0; j < 81; ++j) {
if (result[i][j] == 0) count++;
}
Assert::AreEqual(count <= upper && count >= lower, true);
}
7.3 測試異常
測試思路:設定一個
bool
型變量
exceptionThrown
(初始值為
false
)以及異常的條件,隻要
catch
到異常,就将
exceptionThrown
設定為
true
,然後進行斷言。
SudokuCountException
的代碼:
bool exceptionThrown = false;
try { // Test first SudokuCountException
sudoku.generate(-1, 1, result);
}
catch (SudokuCountException& e) {
exceptionThrown = true;
e.what();
}
Assert::IsTrue(exceptionThrown);
這裡
generate
方法生成的數獨個數不能是負數,是以會抛出異常。
7.4 測試輸入參數的分析
測試思路:用
strcpy_s
初始化
argv
,設定
argc
,然後進行調用相關方法進行分析和斷言。
下面是測試指令
-n 1000 -m 2
InputHandler* input;
strcpy_s(argv[3], length, "-n");
strcpy_s(argv[4], length, "1000");
strcpy_s(argv[1], length, "-m");
strcpy_s(argv[2], length, "2");
argc = 5;
input = new InputHandler(argc, argv);
input->analyze();
Assert::AreEqual(input->getMode(), 'n');
Assert::AreEqual(input->getNumber(), 1000);
Assert::AreEqual(input->getHardness(), 2);
delete input;
這裡打亂了參數的順序,其他參數的組合也是用類似的方法來測試的。
7.5 參數解析魯棒性測試
我們的program中,參數錯誤的情況下會直接報錯然後退出,同時輸入分析在完成之後一般不會改變,是以我們直接在控制台中進行了測試,主要看是否有相應的輸出,錯誤種類參看下圖:
Error Code | 異常說明 | 錯誤提示 |
---|---|---|
1 | 參數數量不正确 | bad number of parameters. |
2 | 參數模式錯誤 | bad instruction.expect -c or -s or -n |
3 | -c指令的數字範圍錯誤 | bad number of instruction -c |
4 | -s指令找不到檔案 | bad file name |
-s指令的puzzle.txt中的數獨格式錯誤 | bad file format | |
6 | -s指令的puzzle.txt中的數獨不可解 | bad file can not solve the sudoku |
9 | -r指令後的數字範圍有錯誤 | the range of -r must in [20,55] |
-m指令後的模式有錯誤 | the range of -m must be 1,2 or 3 | |
11 | 11 -m指令與-u或-r指令同時出現 | -u or -r can not be used with -m |
12 | c指令的參數範圍錯誤 | the number of -c must in [1,1000000] |
13 | -n指令的參數範圍錯誤 | the number of -n must in [1,10000] |
14 | -n指令的參數類型錯誤 | the parameter of -n must be a integer |
18 | -n不能單獨使用 | parameter -n cann't be used without other parameters |
其中code不連續是因為有的code替換成了exception。
一些測試情景可以參考下圖:
7.6 單元測試覆寫率分析
總的覆寫率約為94%
沒有測到的代碼主要是Output相關的代碼,已經在7.5節進行了說明。
Part8: 計算子產品異常處理
下圖展示了我們對于異常的設計:
異常類 | |||
---|---|---|---|
8 | SudokuCountRangeException | generate(int number,int lower,int upper,bool unique,int result[][])中number範圍錯誤 | number in generate(int number,int lower,int upper,bool unique,int result[][]) must in[1,10000] |
16 | LowerUpperException | generate(int number,int lower,int upper,bool unique,int result[][])中lower和upper的值錯誤 | the lower and upper in generate(int number,int lower,int upper,bool unique,int result[][]) must satisfy:lower<upper,lower > 20,upper < 55 |
17 | ModeRangeException | generate(int number, int mode, int result[][])函數中mode的值錯誤 | the number of mode must in [1,3] |
下面分别給出異常對應的測試樣例,測試方法已經在之前說明。
8.1 SudokuCountException
int result[1][81];
bool exceptionThrown = false;
try { // Test first SudokuCountException
sudoku.generate(0, 1, result);
}
catch (SudokuCountException& e) {
exceptionThrown = true;
e.what();
}
Assert::IsTrue(exceptionThrown);
exceptionThrown = false;
try {
sudoku.generate(100000, 20, 50, true, result);
}
catch (SudokuCountException& e) {
exceptionThrown = true;
e.what();
}
Assert::IsTrue(exceptionThrown);
上例中兩次調用
generate
函數,生成數量分别為0和100000,都會抛出異常。
8.2 LowerUpperException
//test LowerUpperException,case 1
exceptionThrown = false;
try {
sudoku.generate(1, 1, 50, true, result);
}
catch (LowerUpperException& e) {
exceptionThrown = true;
e.what();
}
Assert::IsTrue(exceptionThrown);
//test LowerUpperException,case 2
exceptionThrown = false;
try {
sudoku.generate(1, 20, 56, true, result);
}
catch (LowerUpperException& e) {
exceptionThrown = true;
e.what();
}
Assert::IsTrue(exceptionThrown);
//test LowerUpperException,case 3
exceptionThrown = false;
try {
sudoku.generate(1, 50, 1, true, result);
}
catch (LowerUpperException& e) {
exceptionThrown = true;
e.what();
}
Assert::IsTrue(exceptionThrown);
上例中測試了
upper
lower
抛出異常的3種情況,分别是
lower
超出範圍,
upper
超出範圍和
lower
、
upper
不滿足lower<upper的情況
8.3 ModeRangeException
//test ModeRangeException
exceptionThrown = false;
try {
sudoku.generate(1, -1, result);
}
catch (ModeRangeException& e) {
exceptionThrown = true;
e.what();
}
Assert::IsTrue(exceptionThrown);
上例中
generate
調用的模式出錯,隻能是1、2、3,是以抛出異常。
Part9: 界面子產品設計
9.1 風格
-
界面風格采用QSS檔案統一修改。QSS代碼改自csdn部落格作者一去、二三裡的黑色炫酷風格。
基本風格見下圖
- Hint按鈕風格:
QPushButton#blueButton {
color: white;
}
QPushButton#blueButton:enabled {
background: rgb(0, 165, 235);
color: white;
}
QPushButton#blueButton:!enabled {
background: gray;
color: rgb(200, 200, 200);
}
QPushButton#blueButton:enabled:hover {
background: rgb(0, 180, 255);
}
QPushButton#blueButton:enabled:pressed {
background: rgb(0, 140, 215);
}
- 數獨棋盤單元格風格(普通格、角落格、宮邊緣格):
QPushButton#puzzleButton {
border-width: 1px;
border-style: solid;
border-radius: 0;
}
QPushButton#puzzleButtonTLCorner {
border-radius: 0;
border-top-left-radius: 4px;
border-width: 1px;
border-style: solid;
}
QPushButton#puzzleButtonTRCorner {
border-radius: 0;
border-top-right-radius: 4px;
border-width: 1px;
border-style: solid;
}
QPushButton#puzzleButtonBLCorner {
border-radius: 0;
border-bottom-left-radius: 4px;
border-width: 1px;
border-style: solid;
}
QPushButton#puzzleButtonBRCorner {
border-radius: 0;
border-bottom-right-radius: 4px;
border-width: 1px;
border-style: solid;
}
QPushButton#puzzleButtonRE {
border-radius: 0;
border-width: 1px;
border-right-width: 3px;
border-style: solid;
}
QPushButton#puzzleButtonBE {
border-radius: 0;
border-width: 1px;
border-bottom-width: 3px;
border-style: solid;
}
QPushButton#puzzleButtonBRE {
border-radius: 0;
border-width: 1px;
border-right-width:3px;
border-bottom-width: 3px;
border-style: solid;
}
小結:界面風格不是我們在設計UI時最早考慮的部分,本來打算風格隻進行簡單修改,隻用setStyleSheet()方法來設計界面風格。不過後來發現自帶的界面實在太醜,于是決定借鑒已有的風格,針對項目要求進行調整,最終效果還算不錯。
9.2 布局
- 布局設計采用純代碼的設計,使用Layout進行對齊。
-
歡迎、幫助與選擇難度界面統一使用QVBoxLayout對控件進行對齊
效果見下圖
[buaa-SE-2017]結對項目-數獨程式擴充 [buaa-SE-2017]結對項目-數獨程式擴充 [buaa-SE-2017]結對項目-數獨程式擴充 -
遊戲界面采用Layout嵌套Layout的形式進行布局管理。我們先設計了一個mainLayout作為最外層Layout,将其他Layout豎直放入mainLayout。
其他Layout見下圖
[buaa-SE-2017]結對項目-數獨程式擴充 - 為保持數獨棋盤排列的緊密,在棋盤周圍加了spacer把棋盤上的格子擠壓到一起,且能保持形狀。
- 為保證比例的美觀,遊戲窗體被強制固定,無法進行縮小與放大。
小結: 設計布局過程有些小曲折,一開始由于沒有經驗,不知道該如何用代碼該出想要的布局效果,也想過不使用代碼修改布局,直接在界面上拖拽。但考慮到代碼的靈活性,還是決定使用代碼,放棄了拖拽設計(下次有機會做UI,希望嘗試下拖拽設計和代碼設計結合的形式)。好在有部落格和Qt官方文檔的支援,還是成功學會了Qt的布局設計,做出了目前這個效果。
Part10: 界面和計算子產品對接
10.1 generate
主要在開始新遊戲的時候使用,首先用
generate
中生成數獨遊戲,然後再轉換成
QString
顯示在界面的
button
上,部分代碼如下:
int result[10][LEN*LEN];
sudoku->generate(10, degOfDifficulty, result);
QString temp;
QString vac("");
for (int i = 0; i < LEN; ++i) {
for (int j = 0; j < LEN; ++j) {
if (result[target][i*LEN + j] == 0) {
tableClickable[i][j] = true;
puzzleButtons[i][j]->setText(vac);
puzzleButtons[i][j]->setEnabled(true);
puzzleButtons[i][j]->setCheckable(true); // Able to be checked
}
else {
tableClickable[i][j] = false;
puzzleButtons[i][j]->setText(temp.setNum(result[target][i*LEN + j]));
puzzleButtons[i][j]->setEnabled(false); // Unable to be editted
}
}
}
對于已經有數字的位置,則設定按鈕不可用,一個樣例的盤面如下:
10.2 solve
主要用在提示功能上,首先判斷是否可解,如果可解則在相應的位置上給出提示,不可解則給出相應的提示,部分代碼如下:
if (sudoku->solve(board, solution)) {
puzzleButtons[currentX][currentY]->setText(QString::number(solution[currentX*LEN + currentY]));
puzzleButtons[currentX][currentY]->setChecked(false); // Set button unchecked
checkGame();
} else {
QMessageBox::information(this, tr("Bad Sudoku"), tr("Can not give a hint.The current Sudoku\
is not valid\nPlease check the row,rolumn or 3x3 block to correct it."));
}
Part11: 結對過程
這次我們的結對過程比較順利,雙方都能做到互相了解支援,我們的大部分工作在國慶期間完成,過程按照《建構之法》上講到的,1小時切換一次。我的partner有些缺乏積極性,是以雖然有點不好意思不過我會去督促他,這樣就保證了效率,另一方面我在UI設計上經驗不足,我的partner解決了這個問題。我認為我們基本實作了取長補短。
同時我也體會到了在高強度程式設計的時候,高頻次地更換駕駛員和領航員的職責是很有必要的,這樣會緩解疲勞和壓力,進而提高了代碼的品質。
不過,在結對的過程中,我也因為程式設計過程被人監督而有些不自在,感覺沒有完全發揮自己的水準。
總體而言,我認為我們發揮了結對程式設計的優勢,但要進一步提高效率和品質,也許我和partner之間需要更多的磨合。
下面是隊友的感受:
我們結對的過程總體來說算是不錯的,成功完成了基本功能要求與附加的Step4、Step5。我們的大部分工作在國慶期間完成,那段時間嚴格遵守結對程式設計規範,一人敲代
碼,另一人在一旁幫助稽核代碼與提供思路,每一小時進行工作交換,每次交換都把代碼push到Github上,記錄這一步工作的結果。我們用了三天時間實作了邏輯部分的> 完善與測試,并搭建起了UI的三個頁面架構,總體效率還算不錯。期間也遇到過找不着源頭的bug,費了我們不少時間,不過好在是兩個人合力查資料、想辦法,最終還是> 解決了問題。國慶過後由于兩人的時間不太能湊得上,我們便将工作分工,一人主攻功能,一人主攻界面,一步步推進項目并達到預期目标。
以下為我們二人結對程式設計時的照片。
Part12: 結對程式設計
12.1 結對程式設計的優缺點
- 優點
- 兩人可以随時交流,領航員會不斷複審代碼,是以代碼的品質得到有效地提高
- 兩人合作效率高、體量小,這樣靈活和高效兼具,非常适合小項目地開發。
- 由于自己的代碼不斷被審閱,是以駕駛員會專注在代碼上,比平時更加認真。
- 兩人互換角色很适合在高強度程式設計時保持代碼的品質。
- 缺點
- 程式設計變成了一種“表演”,一些人可能受不了這種喪失個人技術習慣被暴露的感覺。
- 如果兩人的了解和合作不夠深入,那麼代碼會受到雙方風格不一緻的影響,反而有可能降低程式品質,是以事先講好一些規則是很重要的。
- 當兩人的時間表不一緻的時候,合作幾乎變得不可能。
- 領航員很多時候要指出代碼中的問題,這就意味着從某種程度上指責partner,但是很多人可能并不願意這樣做。
在我看來,如果想要發揮結對程式設計的全部作用,就需要本人和partner之間加深了解和合作、互相不介意暴露問題、并且深刻領會領航員和駕駛員的職責所在,取長補短,這樣才能有好的結果。
12.2 結對每個人的優缺點
我們互評了優缺點,結果如下:
-
15061119
優點:
1.極高的編碼效率。
2.專注于解決每個問題。
3.充滿責任心與工作熱情。
缺點:
1.編碼風格不太統一。
-
15061104
1.能了解支援partner。
2.能力較強。
3.解決了我一直苦惱的設計問題。
1.某種程度上,欠缺一些積極性。
以下是我的自我評價:
-
自我評價:
1.對partner的了解還不夠
2.有一點獨斷專行
3.有些事情喜歡藏着,不願意交流。
Part13: Step4實作
在部落格中指明合作小組兩位同學的學号,分析兩組不同的子產品合并之後出現的問題,為何會出現這樣的問題,
以及是如何根據回報改進自己子產品的。
13.1 合作小組學号
15061111
15061129
13.2 合并之後出現的問題
13.2.1 問題1:dll生成的環境不同
-
問題描述
我們組的dll在64位下生成,而合作小組的是在32位下生成的,這樣導緻子產品不可調用。
-
解決方案
重新生成了64位的dll,問題解決。
13.2.2 問題2:接口名不同
- 我們合作小組的接口為:
SODUCORE_API void generate_m(int number, int mode, int **result);
SODUCORE_API void generate_r(int number, int lower, int upper, bool unique, int **result);
SODUCORE_API bool solve_s(int *puzzle, int *solution);
而我們自己的接口為:
void generate(int number, int lower, int upper, bool unique, int result[][LEN*LEN]);
void generate(int number, int mode, int result[][LEN*LEN]);
bool solve(int puzzle[], int solution[]);
這就導緻改變計算子產品之後需要改名字。
- 把相應接口的名稱更換即可
13.2.3 問題3:參數規格不同
- 注意到在13.2.2的雙方的接口中,我們組定義result位二維數組,而合作小組定義為二維指針,這就導緻參數錯誤。
- 将result轉換位二維指針即可。
Part14: Step5實作
我們在step4的基礎上進行了增量開發,主要實作了:幫助、錯誤提示、快速存檔讀檔以及繼續遊戲的功能。
14.1 幫助
我們在主界面加入了幫助按鈕,進入之後會顯示數獨的規則以及一個完整的數獨:
使用者點選return按鈕可以傳回到主界面。
14.2 錯誤提示
錯誤提示就是當使用者填入的數字不滿足數獨的限制條件的時候,對不滿足的數字對标紅,這樣使用者可以很容易發現自己的錯誤,參看下圖:
上圖中填入的1和同列以及同行的1沖突,是以顯示為紅色,更改之後顔色回複正常:
14.3 快速存檔/讀檔
使用者進入遊戲之後如果想要儲存目前的盤面,則隻需要點選菜單欄的QuickSave進行存檔,之後如果想回到存檔時候的狀态,則隻需要點選QuicjLoad。
點選QuickSave存檔:
繼續遊戲:
然後點選QuicjLoad相應的存檔點:
恢複:
14.4 繼續遊戲
當使用者上次未完成遊戲直接退出,再一次進入遊戲可以點選Continue來恢複界面:
點選之後恢複:
14.5 功能回報
Part15: PSP表-實際
實際耗時(分鐘) | ||
---|---|---|
20 | ||
50 | ||
2700 | ||
240 | ||
3790 |
Part16: 總結
這次作業中,我收獲了很多,我學會了如何用Qt進行GUI設計、以及如何将程式導出成DLL進行複用,同時我也實踐了結對程式設計。
通過這次的結對程式設計我對合作的優勢和劣勢有了更深的體會,如果兩人的之間有足夠的支援,并能積極改進自身的缺點,就能很好地進行合作,合作的重點就在于對事不對人、取長補短和了解包容。