前言html
首先,考慮到此次項目的要求之一是要爲數獨棋盤添加GUI,即圖形用戶界面,因爲本身以前多數時候只是寫的控制檯程序,而對於帶GUI的開發接觸的是少之又少,因而在深思熟慮以後初步決定採用兩種方式:ios
一:直接利用Visual Studio創建MFC工程進行開發git
二:使用QT這一C++ GUI程序開發框架shell
方案一的優點是相比QT本身更加熟悉VS工具(再補補MFC的知識就行了),而方案二的優點在於搭建界面方便、跨平臺,但受衆羣體小,難以派上用場,並且本身在QT Creator工具的配置和安裝過程當中出現的問題較多(只能暫時放棄這一工具了,有時間再去研究研究),遂決定採用方案一。框架
1、先給出PSP吧dom
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 |
2、項目要求(需求分析)工具
1.生成任意數目的帶有GUI的數獨題目棋盤(非解好的棋盤),同時棋盤上生成用0表示的空格(30~60個),每一個小塊(3*3矩陣)中空格數很多於2個。性能
2.用戶能夠在棋盤上自行填入數字,若成功解答則提示「解答成功!」,且每一個數獨棋盤有惟一解,同時還要輸出至「sudotiku.txt」文件中。學習
3、開發思路
創建MFC工程,包括sudotiku.cpp源文件(用來生成所要的數獨棋盤題目)、sudokuDlg.cpp源文件(用於數獨棋盤對話框的生成,即GUI)、sudokulunch.cpp(用於對話框的啓動)、sudokutip.cpp源文件(用於解答完畢後的彈框提示),並基於以前做業二的回溯法將數獨棋盤矩陣生成後,在sudotiku.cpp源文件中新編寫一個zerogenerator()函數將一些數字取爲0獲得新的矩陣(數獨題目)後,再實例化一個對話框對象,經過該對象調用對話框生成函數OnPaint()函數顯示數獨棋盤,同時也將所生成的數獨矩陣輸出至"sudotiku.txt"文件中,當用戶填完棋盤上全部空格(即值爲0的格子)後再判斷其正確性,隨後彈框給出提示。
4、具體源碼
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次便可前後生成5個數獨題目供用戶解答,以下:
如若成功解答棋盤,則提示「成功解答數獨棋盤!」,以下:
不然提示「錯誤解答(數字7重複)!」,以下:
同時輸出到"sudotiku.txt"文件中,以下:
從測試結果來看,基本能夠知足項目需求。
6、性能分析
棋盤個數爲5時的cpu時間:14.357秒(5個峯谷表示生成5個數獨棋盤的瞬間,平均每次佔用cpu值爲25%)
各主要函數的cpu佔用:主要是對話框的生成和棋盤數據的傳遞佔用較多cpu
從時間角度來看效率仍是不夠,空間上看整個程序的運行大體佔用9M的進程內存,基本能夠知足設備運行的最低要求
7、心得體會
總的來講此次項目的的主要工做量(也能夠說是難點吧)一方面是數獨棋盤中數字零該如何選取(要保證有惟一解且數字0的分佈也要考慮,這樣生成的棋盤題目難度差別很大,原本應該設定一個難度級別選擇的,但考慮到本身的水平。。。因此這也是一個很大的不足吧,並且本身UI實在是作的很爛),另外一方面是考慮怎麼把生成的數據放到textEdit等控件上讓它們顯示出來,最後還要對用戶填寫的結果進行驗證等等。另外一個很大的不足之處是本身對於MFC工程很生疏,其中sudokuDlg.cpp和sudokulunch.cpp兩個源文件的創建與編寫是在參考學習了大量的資料後勉強完成的(固然裏面多數函數的聲明與編寫是由系統自動完成的,也是幸虧有這麼強大的IDE),包括後面利用AfxMessageBox函數進行彈框提示(附部分參考連接http://www.javashuo.com/article/p-wztqnhxd-be.html 和 http://www.javashuo.com/article/p-glomxmxr-s.html https://blog.csdn.net/qq_24282081/article/details/58683586)
最後附上Coding.net的項目地址 :https://coding.net/u/dhlg_201810812011/p/sudokuWithGUI/git
(學號201810812011)