高級軟件工程第三次做業——爲數獨遊戲添加GUI界面並實現相關功能

 

     前言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)

相關文章
相關標籤/搜索