天天看點

結對項目-數獨程式擴充

結對項目-數獨程式擴充(要求細化更新)

Github項目位址

https://github.com/ZhaoYi1031/Sudoku_Pair

PSP表格

PSP2.1 Personal Software Process Stages 預估耗時(分鐘) 實際耗時(分鐘)
Planning 計劃 60 30
· Estimate · 估計這個任務需要多少時間
Development 開發 2640 2280
· Analysis · 需求分析 (包括學習新技術) 480 600
· Design Spec · 生成設計文檔 120
· Design Review · 設計複審 (和同僚稽核設計文檔)
· Coding Standard · 代碼規範 (為目前的開發制定合适的規範)
· Design · 具體設計 240 300
· Coding · 具體編碼 1200 900
· Code Review · 代碼複審 180
· Test · 測試(自我測試,修改代碼,送出修改)
Reporting 報告 150
· Test Report · 測試報告
· Size Measurement · 計算工作量
· Postmortem & Process Improvement Plan · 事後總結, 并提出過程改進計劃
合計 2940 2460

難度(初級/中等/複雜)界定

關于難度的界定,我們小組沒有隻采取空格數界定難度的方式,因為數獨的難度空格多不一定難的. 比如 歐泊顆數獨這裡列的全是17數的數獨,但玩入門級與骨灰級,根本不是一個概念。

是以我閱讀了一些相關資料,從這篇博文,又引入了自由度的概念。

數獨的空格自由度,指除掉空格本身,空格所在行、列、九宮内的空格數總和。

是以,當隻剩一個空格時,此時的自由度為0;當數獨全為時,空格數為81,空間自由度為81*24=1944達到最大。

自由度越大代表數獨越難,但自由度和空格數不完全程正比。是以那篇部落客進行了自由度的階段區分,并在後文中驗證了模型的合理性。

結對項目-數獨程式擴充

我們關于自由度的定義和他略有不同,主要在于他在算同一行同一列的自由度時,算入了自己。而我們沒有。他最終分了10個等級,我們要求的是3個。是以我們最終的難度設定如下:

簡單 中等 複雜
空格數範圍 [40,49] [50,56] [56,65]
自由度範圍 [0,650] [651,999] [1000,1944]

看教科書和其它資料中關于Information Hiding, Interface Design, Loose Coupling的章節,說明你們在結對程式設計中是如何利用這些方法對接口進行設計的。

Information Hiding:

資訊隐藏使一個類把複雜的、敏感的、一旦被外界捕獲可能會引起不良後果的内容封裝在自身内部,這個類以外的代碼得不到此類資訊(通過反射等手段可能對得到),以提高程式的安全性與健壯性。

Interface Design:

接口設計是定義一套公共的接口,友善各個子產品之間的通訊,極大的提高了團隊的效率。

Loose Coupling:

松耦合使程式間以及程式内部都能夠獨立于其他程式,增加程式的可維護性和移植性。

在sudoku.h中對Core類的成員函數和成員變量進行申明,并在sudoku.cpp中完成這些成員函數的實體,再生成動态連結庫dll檔案進而實作資訊的隐藏。核心子產品中的Core類中封裝了generate和solve接口給UI子產品使用,友善了核心子產品與UI子產品的通訊,也使得程式相對獨立,增強了程式的可維護性和移植性。

計算子產品接口的設計與實作過程。設計包括代碼如何組織,比如會有幾個類,幾個函數,他們之間關系如何,關鍵函數是否需要畫出流程圖?說明你的算法的關鍵(不必列出源代碼),以及獨到之處。

首先主要的生成&求解仍然采用之前的回溯的方法,求解方面加了一點優化,關于這方面的具體思路見部落格數獨_個人項目。然後針對這次的新要求,在生成具有限制個數的數獨時,我采用的方法就是先生成足夠多的滿數獨,然後進行挖空。其中挖空的政策仍然采用回溯:如果要挖空number個格子,那麼每個格子被挖空的幾率就是number/81,我們每次

都按照這個幾率去決定格子的去留,執行一遍dfs. 一點小優化就是如果發現後面的格子都填上才能剛好夠number,就直接挖空;如果空的格子已經達到了number, 那麼之後的就直接保留。

流程圖如下:

結對項目-數獨程式擴充

具體的Core類的所有函數即功能如下:

bool find(int x, int y, int z); //判斷數獨[x][y]的位置能否填z
void init_gen(int val, int type); //處理-c
void init_sol(); //處理-s
void trans(int a[81]); //把目前數獨進行數字轉換,轉換成第一行為1..9的數獨
bool isEquivalent(int a[81], int b[81]); //判斷數獨a與數獨b是不是等效
bool deleteElement(int pos, int r); //求解數獨中的操作_增加一個數獨元素的限制
bool modifyElement(int pos, int r); //求解數獨中的操作_填充一個數獨元素
void out_file(int *s); //輸出數獨s到檔案(sudoku.txt)中
void dfs1(int k, int type); //生成數獨的回溯1
void dfs2(int k, int type); //求解數獨的回溯2
int freedom(int a[M]); //求解數獨a的自由度
double getRandData(int min, int max); //生成一個(min,max)的浮點數
void dfs3(int k, int tot); //從填滿元素的數獨中挖空的回溯3
int work3(int num_0_t, int id_t, double p_t); //生成确定0個數的數獨,原型是第id号滿數獨

void generate(int number, int mode, int result[][M]); //對于輸入的數獨遊戲的需求數量和難度等級,通過result傳回生成的數獨遊戲的集合
void generate(int number, int lower, int upper, bool unique, int result[][M]); //生成number個空白數下界為lower,上界為upper的數獨遊戲,并且如果unique為真,則生成的數獨遊戲的解必須唯一,計算的結果存儲在result中
bool check(int a[M]); //檢查含0的數獨a是不是合法的
bool solve(int puzzle[M], int solution[M]); //對于輸入的數獨題目puzzle(數獨矩陣按行展開為一維數組),傳回一個bool值表示是否有解。如果有解,則将一個可行解存儲在solution中(同樣表示為數獨矩陣的一維數組展開)

void generate_single(int number, int from, int ran, int dow, int upd, bool unique, int result[][M]); //生成numer個數獨,0的個數在[ran, ran+from-1]中,自由度在[dow, upd]中,unique代表要不要生成唯一解
int solve_unique(int tmp[M]); //求解一個單一的數獨tmp并求出解的個數, 結果放在ans_first,傳回hasAnswer的值代表有多少解
           

UI方面,當新開始一個不同難度的遊戲時,generate(1, mode, res)即生成一個模式為mode的數獨到數組中即可。然後在提示或者送出的時候,對目前的數獨進行solve(puzzle, solution)判斷有沒有接以及求出解即可。

閱讀有關UML的内容,畫出UML圖顯示計算子產品部分各個實體之間的關系(畫一個圖即可)。

結對項目-數獨程式擴充

計算子產品接口部分的性能改進。記錄在改進計算子產品性能上所花費的時間,描述你改進的思路,并展示一張性能分析圖(由VS 2015/2017的性能分析工具自動生成),并展示你程式中消耗最大的函數。

分别對

-n 10000 -r 55~55

-n 10000 -r 55~55 -u

進行了測試,然後性能分析圖如下,可以看到當沒有考慮-u時,init_gen這個生成50000的函數的時間占據了主導,但是一旦當測試進了-u,這個占據的時間就是非常小的一部分,此時solve_unique函數就占據了主導,因為我們得一直從一個确定的數獨終盤裡面去挖空來找到有唯一接的數獨。

<img src="https://i.loli.net/2017/10/15/59e2f51a2ca4c.jpg

" alt="Screen 2017-10-15 AM1.41.23.jpg" title="Screen 2017-10-15 AM1.41.23.jpg" />

<img src="https://i.loli.net/2017/10/14/59e226f0e8eb9.jpg

" alt="Screen 2017-10-14 AM10.17.45.jpg" title="Screen 2017-10-14 AM10.17.45.jpg" width = "60%"/>

<img src="https://i.loli.net/2017/10/14/59e226f0ec735.jpg

" alt="Screen 2017-10-14 AM10.55.15.jpg" title="Screen 2017-10-14 AM10.55.15.jpg" />

考慮的優化如下:我們可以不是對一個确定的數獨終盤進行一直來挖空,而是當一個數獨終盤一直挖空不出唯一解時,跳到下一個進行執行。優化的代碼如下:

if (times > 1000) { //執行了這個數獨終盤1000次還沒有解,就做後一個數獨終盤來生成。
	id++;
	times = 0;
}
           

看Design by Contract, Code Contract的内容,描述這些做法的優缺點, 說明你是如何把它們融入結對作業中的。

契約式程式設計,在正式程式設計之前先約定一個契約,這個契約規定了類的規格包括前置條件、後置條件以及不變式。

Design by Contract

優點:使程式設計規範化,程式更加友善閱讀。

缺點:對程式語言有一定的要求。

Code Contract

優點:保證調用者和被調用者地位平等。

缺點:沒有标準化,給代碼造成混亂。

在作業中

前置條件為輸入滿足作業要求的指令行參數,傳入的數字在作業要求的範圍内,否則會提示錯誤資訊。

後置條件為輸出正确的挖空數獨或數獨解,或提示該數獨無解。

不變式為輸出正确的挖空數獨或數獨解,或提示該數獨無解。

計算子產品部分單元測試展示。展示出項目部分單元測試代碼,并說明測試的函數,構造測試資料的思路。并将單元測試得到的測試覆寫率截圖,發表在部落格中。要求總體覆寫率到90%以上,否則單元測試部分視作無效。

例如我測試求解一個數獨是不是有唯一解這個函數,我就通過來填入一個有多個解的數獨來進行求解其解數,來進行判斷。之後我也對有唯一解的數獨進行了測試。

TEST_METHOD(TestMethod7)
{//test solve_unique
	Core s;
	int ppp[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
	};
	int uni = s.solve_unique(ppp);
			
	s.out = fopen("unit_out_7.txt", "w");
	fprintf(s.out, "%d\n", uni);
	Assert::AreEqual((uni>1), true);
}		
           

測試的原函數代碼如下:

int Core::solve_unique(int tmp[M]) {
	hasAnswer = 0;//false
	memcpy(x, tmp, sizeof(x));

	memset(a, 0, sizeof(a));
	rep(i, 0, 80) {
		if (x[i] > 0)
			modifyElement(i, x[i]);
	}
	dfs2(1, 0);
	return hasAnswer;
}
           

再例如測試-n -r,并且r1 > r2的例子,我是通過在generate裡捕獲異常把一個布爾變量hasException置為true來判斷的:

TEST_METHOD(TestMethod10)
{//Test -n with -r
	Core s;
	s.generate(1, 50,40, true, lll);
	Assert::AreEqual(s.hasException, true);
}
           

而我的generate函數的代碼如下:

void Core::generate(int number, int lower, int upper, bool unique, int result[][M]) {
	try
	{
		if (number < 1 || number > 10000) {
			throw exception("-n number should be in [1,10000]");
		}
		if (lower < 20 || upper > 55) {
			throw exception("lower and upper should be in [20, 55]");
		}
		if (lower > upper)
		{
			throw exception("lower can not be bigger than upper");
		}
		init_gen(50000, 0);
		generate_single(number, lower, upper - lower + 1, 0, 9999, unique, result);

	}
	catch (const exception& e)
	{
		hasException = true;
		puts(e.what());
	}
}
           

一共測試了20個例子,最終總的覆寫率為94%,主要的函數實作的cpp檔案達到了98%,如下圖:

結對項目-數獨程式擴充

計算子產品部分異常處理說明。在部落格中詳細介紹每種異常的設計目标。每種異常都要選擇一個單元測試樣例釋出在部落格中,并指明錯誤對應的場景。

我主要采用了在函數裡直接catch住異常并列印錯誤資訊到控制台的方法。

常見的代碼如下:

try {
    if (situation ...) {
		throw exception("...........");
    }
	catch (const exception& e)
	{
		hasException = true;
		puts(e.what());
	}
}
           

現選取sudoku.cpp中捕獲異常的情形和單元測試的例子如下表:

異常情況 如何處理 單元測試
generate的參數number<1或>10000 throw exception(“-n number should be in [1,10000]");

Core s;

s.generate(-1, 2, lll);

Assert::AreEqual(s.hasException, true);

generate的參數mode<1或>3 throw exception(“-m mode shoule be 1 or 2 or 3"); s.generate(1, 4, lll);
generate的參數lower<20或者upper>55 throw exception(“lower and upper should be in [20,55]"); s.generate(100, 30, 56, true, lll);
generate的參數lower>upper throw exception(“lower can not be bigger than upper"); s.generate(100, 50, 40, true, lll);

令選取主程式中捕獲異常的情形和單元測試的例子如下表:

-m參數缺少-n參數 throw exception(“-m must be with -n");

char *command[] = {"sudoku.exe", "-m", "2"};

main(3, command);

Assert::AreEqual(mainException, true);

參數重複 throw exception(“Repeated parameter!");

char *command[] = {"sudoku.exe","-n", "100", "-n", "100", "-m", "2"};

main(5, command);

不支援的參數 throw exception(“Supported arguments: -c -s -n -m -r -u"); char *command[] = {"sudoku.exe","-g","100"};
-c 後面沒有數字 throw exception(“Use a number after -c"); char *command[] = {"sudoku.exe","-c" };
-n 後缺少-r和-m throw exception(“-n Must be with -m or -r"); char *command[] = { "sudoku.exe","-n 100 - u"};

界面子產品的詳細設計過程。在部落格中詳細介紹界面子產品是如何設計的,并寫一些必要的代碼說明解釋實作過程。

遊戲一共有兩個界面,一個是coverWindow(封面),另一個是gameWindow(遊戲界面)。

在coverWindow中放置了一個QLabel呈現"SUDOKU"标題和三個QPushButton來對應"Start"、"Record"、和"Setting"。玩家單擊"Start"按鈕可以切換到遊戲界面gameWindow。

結對項目-數獨程式擴充

單擊"Record"按鈕檢視三個難度的最佳記錄。

結對項目-數獨程式擴充

單擊"Setting"來設定遊戲難度。

結對項目-數獨程式擴充

在gameWindow中用81個QPushButton來呈現數獨,9個QPushButton來填數,"clear"和"hint"兩個QPushButton分别對應清除一個格子内容和顯示一個格子的提示。

結對項目-數獨程式擴充

使用"clear"和"hint"時先通過滑鼠單擊選中一個需要填數字的格子。

結對項目-數獨程式擴充

選中後該格子會變成綠色。

結對項目-數獨程式擴充

然後再點選"clear"按鈕即可清空上面的數字。

結對項目-數獨程式擴充

或者點選"hint"按鈕獲得這個格子應該填的數字。

結對項目-數獨程式擴充

"submit"QPushButton用于檢查答案。

結對項目-數獨程式擴充

"home"按鈕用于傳回coverWindow。

結對項目-數獨程式擴充

一個計時器用于記錄花費的時間。點選時鐘圖示可以暫停和開始計時。

結對項目-數獨程式擴充

玩家也可以通過上面的菜單欄來進行新的遊戲或檢視遊戲規則等。

結對項目-數獨程式擴充
void sudokuGUI::sudokuButtonClicked() {
		QPushButton *btn = qobject_cast<QPushButton*>(sender());
		int temp = btn->objectName().toInt();
		int j = temp % 10;
		int i = temp / 10;
		if (empty[i][j] == 0)
			return;
		if (btnTarget != NULL) {
			int temp = btnTarget->objectName().toInt();
			int j = temp % 10;
			int i = temp / 10;
			setRowStyleSheet(i, btnNotEmptyStyle, 0);
			setColumnStyleSheet(j, btnNotEmptyStyle, 0);
			setJiugongStyleSheet(i, j, btnNotEmptyStyle, 0);
			setRowStyleSheet(i, btnEmptyStyle, 1);
			setColumnStyleSheet(j, btnEmptyStyle, 1);
			setJiugongStyleSheet(i, j, btnEmptyStyle, 1);
			btnTarget->setStyleSheet(btnEmptyStyle);
		}
		btnTarget = btn;
		setRowStyleSheet(i, btnNumberStyle, 0);
		setColumnStyleSheet(j, btnNumberStyle, 0);
		setJiugongStyleSheet(i, j, btnNumberStyle, 0);
		setRowStyleSheet(i, btnNumberStyle, 1);
		setColumnStyleSheet(j, btnNumberStyle, 1);
		setJiugongStyleSheet(i, j, btnNumberStyle, 1);
		btnTarget->setStyleSheet(btnTargetStyle);
	}
           

數獨矩陣上按鈕對點選事件的相應。

void sudokuGUI::update() {
		for (int i = 0; i < matrixLen; i++)
			for (int j = 0; j < matrixLen; j++)
				if (empty[i][j]) {
					bool ok = false;
					int x = btnFill[i][j].text().toInt(&ok, 10);
					if (ok)
						matrix[i][j] = x;
					else
						matrix[i][j] = -1;
				}
		if (solver.checkMatrix(matrix)) {
			result.setText("Right !    ");
			timer.stop();
			int finishTime = timerCnt;
			string res[3];
			readRecordFile(res);
			int temp = atoi(res[difficultyChosen].c_str());
			if (temp == 0 || temp > finishTime)
				res[difficultyChosen] = to_string(finishTime);
			writeRecordFile(res);
		}
		else {
			result.setText("Wrong !    ");
		}
		QMessageBox::about(&gameWindow, "Result", result.text());
	}
           

點選submit按鈕後檢查使用者送出答案的正确性。

void sudokuGUI::setBtnZoomAction(QPushButton &btn) {
		QObject::connect(&btn, SIGNAL(pressed()), this, SLOT(setBtnZoomOut()));
		QObject::connect(&btn, SIGNAL(released()), this, SLOT(setBtnZoomIn()));
	}
	void sudokuGUI::setBtnZoomOut() {
		QPushButton *btn = qobject_cast<QPushButton*>(sender());
		int cx = btn->x() + btn->width() / 2;
		int cy = btn->y() + btn->height() / 2;
		int nw = btn->width()*ZOOM_OUT_RATIO;
		int nh = btn->height()*ZOOM_OUT_RATIO;
		btn->setGeometry(cx - nw / 2, cy - nh / 2, nw, nh);
		QFont f = btn->font();
		f.setPixelSize(f.pixelSize() + 2);
		btn->setFont(f);
	}
	void sudokuGUI::setBtnZoomIn() {
		QPushButton *btn = qobject_cast<QPushButton*>(sender());
		int cx = btn->x() + btn->width() / 2;
		int cy = btn->y() + btn->height() / 2;
		int nw = btn->width() / ZOOM_OUT_RATIO;
		int nh = btn->height() / ZOOM_OUT_RATIO;
		btn->setGeometry(cx - nw / 2, cy - nh / 2, nw, nh);
		QFont f = btn->font();
		f.setPixelSize(f.pixelSize() - 2);
		btn->setFont(f);
	}
           

為按鈕添加縮放動畫。

if (btn->text() == HINT) {
		int puzzle[M] = { 0 };
		int solution[M] = { 0 };
		for (int i = 0; i < matrixLen; i++)
			for (int j = 0; j < matrixLen; j++) {
				bool ok = false;
				int x = btnFill[i][j].text().toInt(&ok, 10);
				puzzle[i*matrixLen + j] = ok ? x : 0;
			}
		bool ok = core.solve(puzzle, solution);
		if (ok) {
			btnTarget->setText(QString::number(solution[i*matrixLen + j], 10));
		}
		else {
			QMessageBox::critical(&gameWindow, "warning", "No solution!  ");
		}
	}
           

hint功能的實作。

界面子產品與計算子產品的對接。詳細地描述UI子產品的設計與兩個子產品的對接,并在部落格中截圖實作的功能。

将計算子產品封裝在Core類裡,生成dll檔案供UI子產品調用,并将計算子產品的頭檔案放入UI子產品中。

結對項目-數獨程式擴充
結對項目-數獨程式擴充

實作對接的代碼:

void sudokuGUI::initMatrix() {
		int res[1][M] = { 0 };
		core.generate(1, difficultyChosen + 1, res);
		for (int i = 0; i < matrixLen; i++)
			for (int j = 0; j < matrixLen; j++)
				matrix[i][j] = res[0][i*matrixLen + j];
	}
           

調用generate生成數獨初局。

bool ok = core.solve(puzzle, solution);
	if (ok) {
		btnTarget->setText(QString::number(solution[i*matrixLen + j], 10));
	}
	else {
		QMessageBox::critical(&gameWindow, "warning", "No solution!  ");
	}
           

調用solve求解數獨。

描述結對的過程,提供非擺拍的兩人在讨論的結對照片。

結對項目-數獨程式擴充

看教科書和其它參考書,網站中關于結對程式設計的章節,說明結對程式設計的優點和缺點。結對的每一個人的優點和缺點在哪裡 (要列出至少三個優點和一個缺點)。

結對程式設計

優點:

(1)不間斷地複審,更容易發現錯誤,提高代碼的品質。

(2)程式設計中駕駛員和領航員的互換可以讓程式員輪流工作,進而避免出現過度思考而導緻觀察力和判斷力下降。

(3)兩個人之間可以互相交流學習,進而互相取長補短。

缺點:

(1)可能會出現一人程式設計,另一人偷懶的情況。

(2)可能會降低項目開發效率。

我的優點:

勇于探索,善于利用搜尋工具,舍得花時間來不斷完善程式。

遇到bug會慌張。

趙奕的優點:

手速快,技術高,調理清晰,代碼風格優秀。

有拖延症。

附加題

界面子產品,測試子產品和核心子產品的松耦合

我們是三個小組進行互相測試的,我測試的是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);

回報的錯誤都是:

The number after -r is not in the range.

然後我希望能夠他們區分這些錯誤,就提了issue.

一個可能的測試代碼如下:

try {
	Core c;
	c.generate(1, 20, 5000, false, a);
}
catch (NumberOutOfBoundException& e){
	cout << e.m_msg << endl;
}
           

還有一個問題是關于solve的異常捕捉的。

如果solve函數的puzzle函數是一個本身就非法的數獨,例如一行有兩個數是一樣的情況,沒有捕捉到異常或者傳回false,而是也求了一個解。

一個可能的測試代碼如下. 這個矩陣的第一行有兩個9,顯然是無解的:

int puzzle[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
};
int solution[81];
Core c;
c.solve(puzzle, solution);
rep(j, 0, 80) {
	cout << solution[j] << " ";
	if ((j + 1) % 9 == 0)
		puts("");
}
           

而最終卻給出了一個解如下:

6 1 2 3 4 5 9 8 9
3 4 5 7 8 9 1 2 6
7 8 9 1 2 6 3 4 5
1 2 3 4 5 7 6 9 8
4 5 6 8 9 1 2 3 7
8 9 7 2 6 3 4 5 1
2 3 1 9 7 8 5 6 4
9 6 4 5 1 2 8 7 3
5 7 8 6 3 4 9 1 2
           

然後是别人給我們的測試進行的修正。最初始的改正就是把generate的數組的參數從83改成了81,即宏定義的M值。(之前寫成83是害怕溢出)因為發現如果參數不同是無法調用别人的函數的。

根據窦哥給我們組的issue如下,是幾個比較中肯與實際的測試問題:

結對項目-數獨程式擴充

軟體隻能通過右上角的紅叉來結束,在開始界面中沒有退出軟體的按鈕。

結對項目-數獨程式擴充

于是我們增加了可以在開始界面中退出遊戲的按鈕。

結對項目-數獨程式擴充

軟體在點選"home"按鈕傳回開始界面時沒有"Setting"按鈕,影響使用者體驗。

結對項目-數獨程式擴充

于是我們修複了這個bug,時使用者可以在遊戲途中傳回主界面對遊戲難度進行設定,進而優化了使用者體驗。

結對項目-數獨程式擴充

然後第三點,關于sudoku.exe -c abc, 其中abc是個不存在的檔案,此時我們的是不能識别這個異常的。相當于直接悶聲過去了。然後增加的修改如下:

FILE *fp = NULL;
fp = fopen(argv[2], "r");
if (fp == NULL) {
	throw std::exception("-s file doesn't exist");
}
           

用fopen判斷是不是NULL就可以增加捕捉了這個異常。

通過增量修改的方式,改程序式,釋出一個真正的軟體

使用者的回報:

使用者1:按鈕有動态效果,界面很好看。

使用者2:支援鍵盤輸入數字,很友善。

使用者3:No Solution的意義不是很明确。

使用者4:成功求解數獨彈出right之後點選ok能傳回主界面就好了。

使用者5:沒有音效。

使用者6:沒有暫停功能。

使用者7:點選黑色按鈕最好不要有動畫,因為不能填數字。

使用者8:挺好的。

使用者9:可以提示哪些數字是填錯的就好了。

使用者10:遊戲介紹是中文的就好了。

改進:

3.No Solution就是代表目前數獨無解,意思應該還是比較明确吧。

4.成功求解數獨彈出right之後點選ok停留在目前界面也挺好的吧,如果想重新玩的話通過左上角的New菜單進行難度選擇即可。

5.添加音效的話确實能夠優化使用者體驗,不過時間不夠了就不加了。

6.

void sudokuGUI::timerSwitch() {
	if (timer.isActive())
		timer.stop();
	else {
		timer.start();
	}
}
           

增加了該功能,現在點選時鐘圖示可以暫停計時和開始計時了。

7.有動畫也挺好看的啊,而且改起來沒想象中的簡單,就不改了。

9.填一個數字按一下hint就可以直到目前填的這個數字是不是錯的了

10.中文有的字會變為亂碼,還是英文比較保險。