高级软件工程第三次作业——为数独游戏添加GUI界面并实现相关功能
前言
首先,考虑到这次项目的要求之一是要为数独棋盘添加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.****.net/qq_24282081/article/details/58683586)
最后附上Coding.net的项目地址 :https://coding.net/u/dhlg_201810812011/p/sudokuWithGUI/git
(学号201810812011)