高级软件工程第三次作业——为数独游戏添加GUI界面并实现相关功能

 

     前言

            首先,考虑到这次项目的要求之一是要为数独棋盘添加GUI,即图形用户界面,由于自己之前多数时候只是写的控制台程序,而对于带GUI的开发接触的是少之又少,于是在深思熟虑之后初步决定采用两种方式:

            一:直接利用Visual Studio建立MFC工程进行开发

            二:使用QT这一C++ GUI程序开发框架

            方案一的优势是相比QT自己更加熟悉VS工具(再补补MFC的知识就好了),而方案二的优势在于搭建界面方便、跨平台,但受众群体小,难以派上用场,而且自己在QT Creator工具的配置和安装过程中出现的问题较多(只能暂时放弃这一工具了,有时间再去研究研究),遂决定采用方案一。

     一、先给出PSP吧

      

PSP1.2Personal Software Process Stages预估耗时(minutes)实际耗时(minutes)

               

   

  Planning

计划2025

 

   
· Analysis· 需求分析 (包括学习MFC程序的开发)120145
· Code Review· 代码复审2025
· Coding· 具体编码180210
· Coding Standard· 代码规范2015
· Design· 具体设计3025
· Design Review· 设计复审 55
· Design Spec· 生成设计文档1015
· Estimate· 估计任务所需时间510
· Postmortem & Process Improvement Plan· 总结2030
· Size Measurement· 计算工作量1010
· Test· 测试(自我测试,Debug,提交修改)110125
· Test Report·分析测试报告2030
合计 580645

 

        二、项目要求(需求分析)

       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)

 

       

 

转载于:https://www.cnblogs.com/ecutwzl1996/p/9798379.html

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值