前言
首先,考慮到這次項目的要求之一是要為數獨棋盤添加GUI,即圖形用戶界面,由於自己之前多數時候只是寫的控制台程序,而對於帶GUI的開發接觸的是少之又少,於是在深思熟慮之后初步決定采用兩種方式:
一:直接利用Visual Studio建立MFC工程進行開發
二:使用QT這一C++ GUI程序開發框架
方案一的優勢是相比QT自己更加熟悉VS工具(再補補MFC的知識就好了),而方案二的優勢在於搭建界面方便、跨平台,但受眾群體小,難以派上用場,而且自己在QT Creator工具的配置和安裝過程中出現的問題較多(只能暫時放棄這一工具了,有時間再去研究研究),遂決定采用方案一。
一、先給出PSP吧
| PSP1.2 | Personal Software Process Stages | 預估耗時(minutes) | 實際耗時(minutes) |
|---|---|---|---|
|
|
|||
| Planning |
計划 | 20 | 25 |
|
|
|||
| · Analysis | · 需求分析 (包括學習MFC程序的開發) | 120 | 145 |
| · Code Review | · 代碼復審 | 20 | 25 |
| · Coding | · 具體編碼 | 180 | 210 |
| · Coding Standard | · 代碼規范 | 20 | 15 |
| · Design | · 具體設計 | 30 | 25 |
| · Design Review | · 設計復審 | 5 | 5 |
| · Design Spec | · 生成設計文檔 | 10 | 15 |
| · Estimate | · 估計任務所需時間 | 5 | 10 |
| · Postmortem & Process Improvement Plan | · 總結 | 20 | 30 |
| · Size Measurement | · 計算工作量 | 10 | 10 |
| · Test | · 測試(自我測試,Debug,提交修改) | 110 | 125 |
| · Test Report | ·分析測試報告 | 20 | 30 |
| 合計 | 580 | 645 |
二、項目要求(需求分析)
1.生成任意數目的帶有GUI的數獨題目棋盤(非解好的棋盤),同時棋盤上生成用0表示的空格(30~60個),每個小塊(3*3矩陣)中空格數不少於2個。
2.用戶可以在棋盤上自行填入數字,若成功解答則提示“解答成功!”,且每個數獨棋盤有唯一解,同時還要輸出至“sudotiku.txt”文件中。
三、開發思路
建立MFC工程,包括sudotiku.cpp源文件(用來生成所要的數獨棋盤題目)、sudokuDlg.cpp源文件(用於數獨棋盤對話框的生成,即GUI)、sudokulunch.cpp(用於對話框的啟動)、sudokutip.cpp源文件(用於解答完畢后的彈框提示),並基於之前作業二的回溯法將數獨棋盤矩陣生成后,在sudotiku.cpp源文件中新編寫一個zerogenerator()函數將一些數字取為0得到新的矩陣(數獨題目)后,再實例化一個對話框對象,通過該對象調用對話框生成函數OnPaint()函數顯示數獨棋盤,同時也將所生成的數獨矩陣輸出至"sudotiku.txt"文件中,當用戶填完棋盤上所有空格(即值為0的格子)后再判斷其正確性,隨后彈框給出提示。
四、具體源碼
1.sudotiku.cpp源文件
#include<iostream> #include<fstream> #include <chrono>//std下的一個子命名空間,為持續時間類服務chrono::system_clock #include <random>//shuffle隨機排列函數 default_random_engine #include <algorithm>//使用for_each循環 #include <functional>//定義了多個類模板 using namespace std; void sudomatrixgenerator() { int field[9][9] = { 0 }; //隨機生成一行1~9 auto init = [](int* list) //使用auto進行變量類型的自動匹配 { for_each(list, list + 9, [=](int &i) //用來遍歷list進行操作 =for(int i=0;i<9;i++) { i = &i - list + 1; } ) unsigned seed = chrono::system_clock::now().time_since_epoch().count();//調用當前系統時間作為隨機種子seed的初始值 shuffle(list, list + 9, default_random_engine(seed));//生成隨機序列,將list至list+9區間內的數值隨機排列 } init(field[0]); //初始化第一行元素 int trylist[9]; init(trylist); //用於確定數字的嘗試順序 int judge = [&field](int i, int j, int num) -> bool //判斷填入的數字是否合法 { for (int k(0); k < j; k++) //判斷同一行中是否有重復元素 if (field[i][k] == num) return false; for (int k(0); k < i; k++) //判斷同一列中是否有重復元素相同 if (field[k][j] == num) return false; int count = j % 3 + i % 3 * 3; //判斷整個3*3區域中是否有重復元素 while (count--) if (!(field[i - i % 3 + count / 3][j - j % 3 + count % 3] - num)) return false; return true; }; function<bool(int, int, int*)>//類模板 fill = [&trylist, &fill, &field, judge](int y, int x, int* numloc) -> bool //用簡單回溯方法進行數字的填入 { if (y > 8) return true; if (judge(y, x, *numloc)) { field[y][x] = *numloc; if (fill(y + (x + 1) / 9, (x + 1) % 9, trylist)) return true; } field[y][x] = 0; if (numloc - trylist >= 8) return false; if (fill(y, x, numloc + 1)) return true; }; fill(1, 0, trylist);//確定某位置要填入的數字 //編寫函數將棋盤中的某些數字取為0 void zerogenerator(int x, int y) { char val = Get(x, y); for (int i = x / 3 * 3; i < x / 3 * 3 + 3; i++) { for (int j = y / 3 * 3; j < y / 3 * 3 + 3; j++) { if (Get(i, y) == val && Get(i, y) != ' ' && i != x && j != y) { return false; } } } return true; zerogenerator::Sudoku(char* data) { for (int i = 0; i < 81; i++) {
shuffle(list, list + 9, default_random_engine(seed));i=list,j=i%list;
bool isOri = (data[i] >= '1' && data[i] <= '9') return ? TRUE : FALSE;
field[i / 9][i % 9] = new field(data[i], isOri);
}
}
field[i][j] = '0';
}
//根據參數輸出相應的數獨矩陣
for (int i=0; i < 9; i++) {
for (int j : field[i])
cout << j << " "; cout << endl;
}
cout << endl;//每個矩陣相隔一行
}
int main() {
int N; cout << "請輸入數獨棋盤題目個數:" << endl;
cin >> N; void sudomatrixgenerator();
ofstream out;
try { out.open("sudotiku.txt", ios::trunc);
}
catch (exception e) {
cout << "打開文件sudotiku.txt失敗!!!" << endl;
}
for (int i = 0; i <= N; i++) {
sudomatrixgenerator();
}
out.close();
return 0;
}
2.sudokuDlg.cpp源文件
// sudokuDlg.cpp : 實現文件 //數獨棋盤對話框的生成 #include "stdafx.h" #include "sudokuDlg.h" #include "sudotiku.h" #include "afxdialogex.h" #ifdef _DEBUG #define new DEBUG_NEW #endif #define ID_TIMER 0 // CSudokuDlg 對話框 CSudokuAppDlg::CSudokuAppDlg(CWnd* pParent /*=NULL*/) : CDialogEx(IDD_SUDOKU_DIALOG, pParent) { m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); } void CSudokuAppDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); } BEGIN_MESSAGE_MAP(CSudokuAppDlg, CDialogEx) ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_WM_LBUTTONDOWN() ON_BN_CLICKED(IDOK, &CSudokuAppDlg::OnBnClickedOk) ON_WM_TIMER() ON_NOTIFY(NM_CUSTOMDRAW, IDC_PROGRESS1, &CSudokuAppDlg::OnNMCustomdrawProgress1) END_MESSAGE_MAP() // CSudokuDlg 消息處理程序 BOOL CSudokuAppDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // 設置此對話框的圖標。 當應用程序主窗口不是對話框時,框架將自動 // 執行此操作 SetIcon(m_hIcon, TRUE); // 設置大圖標 SetIcon(m_hIcon, FALSE); // 設置小圖標 // TODO: 在此添加額外的初始化代碼 m_pSudoku = new SudokuGame(this); SetTimer(ID_TIMER, 1000, NULL); // 設置窗口大小 CRect client; GetClientRect(client); int size = m_pSudoku->GetBoardSize(); MoveWindow(client.left, client.top, client.left+size+15, client.top+size+80, FALSE); // 設置Button和Static的位置 CWnd* pWButton = GetDlgItem(IDOK); int buttonSize = 110; pWButton->SetWindowPos(NULL, client.top+size-buttonSize, client.left+size, 0, 0, SWP_NOZORDER | SWP_NOSIZE); CWnd* pWStatic = GetDlgItem(IDC_STATIC); pWStatic->SetWindowPos(pWButton, 270, 450, 0, 0, SWP_NOZORDER | SWP_NOSIZE); return TRUE; // 除非將焦點設置到控件,否則返回 TRUE } // 如果向對話框添加最小化按鈕,則需要下面的代碼 // 來繪制該圖標。 對於使用文檔/視圖模型的 MFC 應用程序, // 這將由框架自動完成。 void CSudokuAppDlg::OnPaint() { if (IsIconic()) { CPaintDC dc(this); // 用於繪制的設備上下文 SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0); // 使圖標在工作區矩形中居中 int cxIcon = GetSystemMetrics(SM_CXICON); int cyIcon = GetSystemMetrics(SM_CYICON); CRect rect; GetClientRect(&rect); int x = (rect.Width() - cxIcon + 1) / 2; int y = (rect.Height() - cyIcon + 1) / 2; // 繪制圖標 dc.DrawIcon(x, y, m_hIcon); } else { m_pSudoku->DrawBoard(); CDialogEx::OnPaint(); } } //當用戶拖動最小化窗口時系統調用此函數取得光標 //顯示。 HCURSOR CSudokuAppDlg::OnQueryDragIcon() { return static_cast<HCURSOR>(m_hIcon); } void CSudokuAppDlg::OnLButtonDown(UINT nFlags, CPoint point) { // TODO: 在此添加消息處理程序代碼和/或調用默認值 m_pSudoku->Select(point); } BOOL CSudokuAppDlg::PreTranslateMessage(MSG* pMsg) { // TODO: 在此添加專用代碼和/或調用基類 if (pMsg->message == WM_KEYDOWN) { if (m_pSudoku->OnKeyDown(pMsg->wParam)) return true; } return CDialogEx::PreTranslateMessage(pMsg); } void CSudokuAppDlg::OnBnClickedOk() { // TODO: 在此添加控件通知處理程序代碼 m_pSudoku->NewGame(); // Loading Effect AfxBeginThread(ProgressThread, this, THREAD_PRIORITY_IDLE); } UINT CSudokuAppDlg::ProgressThread(void* param) { CWnd* pCwnd = (CWnd*)param; CRect client; pCwnd->GetClientRect(client); CRect ProgRect = CRect(client.left, client.top, client.right, client.left + 4); CProgressCtrl *pProgCtrl = new CProgressCtrl(); pProgCtrl->Create(WS_VISIBLE, ProgRect, pCwnd, 99); pProgCtrl->SetRange(0, 100); pProgCtrl->SetStep(1); for (int i = 0; i < 5000; i++) { pProgCtrl->SetPos(i); } delete pProgCtrl; return 0; } void CSudokuAppDlg::OnTimer(UINT_PTR nIDEvent) { // TODO: 在此添加消息處理程序代碼和/或調用默認值 CDialogEx::OnTimer(nIDEvent); switch (nIDEvent) { case ID_TIMER: { m_pSudoku->TimerUpdate(); SetDlgItemText(IDC_STATIC, m_pSudoku->GetTimer()); break; } default: KillTimer(nIDEvent); break; } } void CSudokuAppDlg::OnNMCustomdrawProgress1(NMHDR *pNMHDR, LRESULT *pResult) { LPNMCUSTOMDRAW pNMCD = reinterpret_cast<LPNMCUSTOMDRAW>(pNMHDR); // TODO: 在此添加控件通知處理程序代碼 *pResult = 0; }
3.sudokulunch.cpp源文件
//游戲交互窗口(對話框)的啟動 #include "stdafx.h" #include "sudotiku.h" #include "sudokuDlg.h" #ifdef _DEBUG #define new DEBUG_NEW #endif // CSudokuApp BEGIN_MESSAGE_MAP(CSudokuApp, CWinApp) ON_COMMAND(ID_HELP, &CWinApp::OnHelp) END_MESSAGE_MAP() // CSudokuApp 構造 CSudokuApp::CSudokuApp() { // 支持重新啟動管理器 m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_RESTART; // TODO: 在此處添加構造代碼, // 將所有重要的初始化放置在 InitInstance 中 } // 唯一的一個 CSudokuApp 對象 CSudokuApp theApp; // CSudokuApp 初始化 BOOL CSudokuApp::InitInstance() { // 如果一個運行在 Windows XP 上的應用程序清單指定要 // 使用 ComCtl32.dll 版本 6 或更高版本來啟用可視化方式, //則需要 InitCommonControlsEx()。 否則,將無法創建窗口。 INITCOMMONCONTROLSEX InitCtrls; InitCtrls.dwSize = sizeof(InitCtrls); // 將它設置為包括所有要在應用程序中使用的 InitCtrls.dwICC = ICC_WIN95_CLASSES; InitCommonControlsEx(field[i][j]);//將所生成的數獨題目顯示至對話框 CWinApp::InitInstance(); AfxEnableControlContainer(); // 任何 shell 樹視圖控件或 shell 列表視圖控件。 CShellManager *pShellManager = new CShellManager; // 激活“Windows Native”視覺管理器,以便在 MFC 控件中啟用主題 CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows)); // 標准初始化 SetRegistryKey(_T("應用程序向導生成的本地應用程序")); CSudokuAppDlg dlg; m_pMainWnd = &dlg; INT_PTR nResponse = dlg.DoModal(); if (nResponse == IDOK) { // “確定”來關閉對話框的代碼 } else if (nResponse == IDCANCEL) { // “取消”來關閉對話框的代碼 } else if (nResponse == -1) { TRACE(traceAppMsg, 0, "警告: 對話框創建失敗,應用程序將意外終止。\n"); TRACE(traceAppMsg, 0, "警告: 如果您在對話框上使用 MFC 控件,則無法 #define _AFX_NO_MFC_CONTROLS_IN_DIALOGS。\n"); } // 刪除上面創建的 shell 管理器。 if (pShellManager != NULL) { delete pShellManager; } }
4.sudokutip.cpp源文件
#include "stdafx.h" //根據結果給出正確性提示 #include "sudokulunch.h" #pragma comment(lib, "wininet.lib") char* data = new char[81]; for (int i = 0; i < 81; i++){ if (cData[i] == _TCHAR('0')) data[i] = char(cData[i]); } bool sudokutip::IsFinish(data[]) { if ((data[i] >= '1' && data[i] <= '9') || data== '0 ') { Set(data[i]); return true; } else if (data[i]>= left && data[i] <= right) { return true; } return false; } void sudokutip::tip(char value) { bool tag=false; if (IsFinish()) { tag = true; AfxMessageBox(_T("成功解答數獨棋盤!"));//提示已經解答完畢 } else { AfxMessageBox(_T("錯誤解答!"));//提示用戶解答錯誤 } }
五、測試運行
開始測試程序,例如輸入棋盤生成個數為5,用戶可點擊生成數獨棋盤按鈕5次即可先后生成5個數獨題目供用戶解答,如下:





如若成功解答棋盤,則提示“成功解答數獨棋盤!”,如下:

否則提示“錯誤解答(數字7重復)!”,如下:

同時輸出到"sudotiku.txt"文件中,如下:

從測試結果來看,基本可以滿足項目需求。
六、性能分析
棋盤個數為5時的cpu時間:14.357秒(5個峰谷表示生成5個數獨棋盤的瞬間,平均每次占用cpu值為25%)

各主要函數的cpu占用:主要是對話框的生成和棋盤數據的傳遞占用較多cpu

從時間角度來看效率還是不夠,空間上看整個程序的運行大致占用9M的進程內存,基本可以滿足設備運行的最低要求

七、心得體會
總的來說這次項目的的主要工作量(也可以說是難點吧)一方面是數獨棋盤中數字零該如何選取(要保證有唯一解且數字0的分布也要考慮,這樣生成的棋盤題目難度差異很大,本來應該設定一個難度級別選擇的,但考慮到自己的水平。。。所以這也是一個很大的不足吧,而且自己UI實在是做的很爛),另一方面是考慮怎么把生成的數據放到textEdit等控件上讓它們顯示出來,最后還要對用戶填寫的結果進行驗證等等。另一個很大的不足之處是自己對於MFC工程很生疏,其中sudokuDlg.cpp和sudokulunch.cpp兩個源文件的建立與編寫是在參考學習了大量的資料后勉強完成的(當然里面多數函數的聲明與編寫是由系統自動完成的,也是幸好有這么強大的IDE),包括后面利用AfxMessageBox函數進行彈框提示(附部分參考鏈接https://www.cnblogs.com/junjunjun123/p/8811150.html 和 https://www.cnblogs.com/saintdingspage/p/9469025.html https://blog.csdn.net/qq_24282081/article/details/58683586)
最后附上Coding.net的項目地址 :https://coding.net/u/dhlg_201810812011/p/sudokuWithGUI/git
(學號201810812011)
