zoukankan      html  css  js  c++  java
  • 高级软件工程第三次作业——为数独游戏添加GUI界面并实现相关功能

         前言

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

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

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

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

         一、先给出PSP吧

          

    PSP1.2Personal 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, "警告: 对话框创建失败,应用程序将意外终止。
    ");
            TRACE(traceAppMsg, 0, "警告: 如果您在对话框上使用 MFC 控件,则无法 #define _AFX_NO_MFC_CONTROLS_IN_DIALOGS。
    ");
        }
        // 删除上面创建的 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)

           

  • 相关阅读:
    C++ 多线程编程
    协程简介(coroutine)
    Yanhua Digimaster 3如何使用免焊适配器重置仪表板?
    Autel OTOFIX IM1 远程/在线技术支持指南
    Xhorse奥迪免焊适配器套装功能列表+常见问题
    如何通过 DDD 构建一辆汽车
    周末复习一波Linux,Linux常用命令总结,还有语法+案例
    Dubbo 基础知识
    GIT版本控制学习博客
    C++检测和定位内存泄漏
  • 原文地址:https://www.cnblogs.com/ecutwzl1996/p/9798379.html
Copyright © 2011-2022 走看看