高級軟件工程第三次作業——為數獨游戲添加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.csdn.net/qq_24282081/article/details/58683586

 

  最后附上Coding.net的項目地址 :https://coding.net/u/dhlg_201810812011/p/sudokuWithGUI/git  

 (學號201810812011)

 

       

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM