結對項目-數獨程序擴展

目錄

Github項目地址

接口設計

計算模塊接口的設計與實現過程

UML:計算模塊部分各個實體之間的關係

計算模塊接口部分的性能改進

Design by Contract, Code Contract

計算模塊部分單元測試展現

計算模塊部分異常處理說明

界面模塊的詳細設計過程

界面模塊與計算模塊的對接

結對的過程

結對編程

預計花費時間&實際花費的時間

附加題:界面模塊,測試模塊和核心模塊的鬆耦合



Github項目地址

返回目錄
https://github.com/514DNA/sudoku
git

    難度級別-m說明:
  • 1:生成35個空的單解數獨;

  • 2:生成40-50個空的單解數獨

  • 3:生成50-60個空的單解數獨

  • 理由:在國際數獨比賽中要求數獨題目均爲單解的標準數獨,在我的體驗中,也能感受到多解數獨會形成很大的困擾,所以統一輩子成單解數獨。數獨的難度很大程度上依賴空的個數有關,所以把空的個數做爲數獨難度的判別標準。


接口設計

返回目錄
github

方法:Information Hiding, Interface Design, Loose Coupling算法

生成數獨終局編程

void generate(int number, int[][] result)

生成設置難度級別的數獨遊戲數組

void generate(int number, int mode, int[][] result)

生成設置挖空數量的數獨遊戲緩存

void generate(int number, int lower, int upper, bool unique, int[][] result)

解數獨題性能優化

bool solve(int[] puzzle, int[] solution)

數據成員和方法分別有public類型和private類型,僅將外部須要調用的數據和方法公開,規範好傳入的參數的格式,返回符合要求的結果。函數的實現過程,內部調用的函數不對用戶公開dom



計算模塊接口的設計與實現過程

返回目錄
函數

代碼的組織: 類、函數及其關係


代碼一共有兩個類:數獨類和命令行參數處理類性能

數獨類主要有以下函數:

class Core

{
public

void set_play(bool a);//設置爲遊戲模式

void create_sudoku_puzzle(int n, int mode);

void create_sudoku_puzzles(int block_num, int mode, int n);

void create_random_sudoku();

void solve_all_soduku(FILE *fp);

void generate(int number, int result);

void generate(int number, int mode, int
result);

void generate(int number, int lower, int upper, bool unique, int result);

bool solve(int* puzzle, int* solution);

private:

void init_sudoku();//初始化數獨

int init_check_puzzle();//挖空後是否單解

int can_delete(int addr);//可否挖空

};


命令行處理類有以下函數:


class arg_info

{

public:

int read_arg_info(int argc, charargv); //讀取命令函參數

void run_cmd(int argc, char **argv);

private:

int str2num(char *str);

int str2range_num(char *str);

void error_out(int error);//錯誤輸出

void set_arg_bit_on(int mode);//模式設置

};

   
    

關鍵函數流程圖


主要說一下生成數獨遊戲的函數之間的關係

生成數獨遊戲首先要調用create_sudoku_puzzles這個函數,而後根據生成題目的數量調用n次create_sudoku_puzzle函數。

這個函數首先調用建立數獨的函數,若是是命令行中調用,那麼就調用create_test_sudoku,這個函數不會生成重複等價數獨;若是是GUI調用(給玩家玩的),那麼就調用create_random_sudoku,這個函數保證了隨機性。

生成完數獨就開始挖空,這些是挖空用的函數:

int can_delete(int addr);
int can_delete_senior(int addr);

調用can_delete用來判斷經過低級方法能夠挖掉的格子;調用can_delete_senior用來判斷經過高級方法能夠挖掉的格子;這三個函數是來回遞歸判斷數獨是不是惟一解的函數,由can_delete_senior調用:

int to_next(int i, int j, int n);
int init_check_puzzle();
int check_puzzle(int i, int j, int n);

這個函數是把原來嘗試挖掉的空加上的函數,也由can_delete_senior調用

void add_addr(int addr, int num);

找到能夠挖的空以後調用一次clear_addr把空挖掉,都挖完調用一次print_sudoku輸出





算法的關鍵及獨到之處


分階段產生數獨遊戲
採用兩種方法對生成的數獨終局挖空:低級方法——四種排除法直接把一個數推出來;高級方法——猜數並驗證。
若是某一個數挖完以後能被簡單方法再推出來,那麼就把這個格子放在緩存區內;找完當前全部的數,那麼就隨機挖掉一個緩存區裏面的空。可是這樣不容易挖夠55個空,那麼只能經過猜數來挖空。若是一個數挖完以後仍是單解數獨,那麼就挖掉。經過暴力回溯法判斷數獨解是否惟一;從數獨格子中的最後一個空,往前尋找。若是挖完這個格子仍是單解數獨,那麼就挖掉,而後繼續往前找,直到挖夠55個空就中止挖空。
這個挖空方法的獨到之處就是會先找到全部低級挖空方法能夠挖的空,這樣就減小了暴力回溯的次數



UML:計算模塊部分各個實體之間的關係

返回目錄


計算模塊接口部分的性能改進

返回目錄

花費的時間:2小時左右


改進的思路


程序中主要費時間的地方是判斷挖完空以後的數獨仍是單解數獨。咱們解數獨有如下幾種方法:1.四種排除法直接把一個數推出來(簡單方法);2.猜數(複雜方法)
挖空過程當中,若是某一個數挖完以後能被簡單方法再推出來,那麼就把這個格子放在緩存區內。找完當前全部的數,那麼就隨機挖掉一個緩存區裏面的空。可是這樣不容易挖夠55個空,那麼只能經過猜數來挖空,猜數是這樣。若是一個數挖完以後仍是單解數獨,那麼就挖掉。經過暴力回溯法判斷數獨解是否惟一,就形成了運行的緩慢
最開始是把全部挖過以後解惟一的數找到再隨機一個,可是後來發現這樣作,判斷解惟一性的次數就多,要追求效率,就要減小這方面花的時間,也就是減小判斷的次數。因此從前日後找,找到第一個能夠挖的格子就挖掉,而後再繼續找,直到挖的空夠55個。
可是這樣改了以後更慢了,這種回溯法對前面空多後面空少的數獨很棘手,會判斷的很是慢。因此就改爲從後往前找,從後往前挖,速度提升。這就是執行世界最難指令:n 10000 -r 55~55 -u所用的時間,雖然挖空的函數仍是用了不少時間,可是總體已經有很大改進了


性能分析圖


-c 1000000



-n 10000 -r 55~55 -u


消耗最大的函數

生成數獨時,消耗最大的函數是create_sudoku;生成數獨遊戲時,消耗最大的函數是create_sudoku_puzzle


Design by Contract, Code Contract

返回目錄


  • 基本思想:函數的調用者保證傳入參數的函數符合函數的要求,若是不復合函數的要求,函數將拒絕執行,寫函數時應該對傳入函數的參數進行檢查

  • 優勢

    • 傳統的server/client模式下,對被調用者要求嚴格,致使了調用者的質量低劣,契約式編程中,調用者和被調用者地位平等,保證了雙方的代碼質量,提升軟件工程的效率和質量

    • 在多人合做編程中,使用契約式編程,有利於團隊編程接口的統一性,更容易劃分模塊,便於模塊之間的對接



  • 缺點

    • 契約式編程須要必定的機制驗證契約成立與否,對程序語言有必定的要求

    • 契約式編程沒有被標準化,項目之間的定義和修改的不一樣,可能會給代碼帶來混亂



  • 融入做業

    • 按照要求設計和實現generate和solve等接口,傳入符合要求的參數執行函數能夠得到正確的結果。事先對傳入的result,number等參數進行參數檢查,若是不符合要求將會進行異常處理,不會進行下一步的操做,保證運行結果的正確性。





計算模塊部分單元測試展現
返回目錄

單元測試代碼 測試的函數 測試數據構造思路
TEST_METHOD(SingleSolution)
{
Core core;
core.create_sudoku_puzzle(1, 3);
int result = 0;
result = core.check_puzzle(0, 0, 0);
Assert::AreEqual(result, 1);
}
create_sudoku_puzzle(int n)
  • 測試生成的數獨是不是單解
  • 生成算法保證了不重複,所以沒有對重複性測試,也沒有針對等價數獨進行測試
TEST_METHOD(generate) {
Core core;
Tool tool;
int **result = tool.CreateArray(1000, 81);
core.generate(1000, result);
core.generate(1000, 1, result);
core.generate(1000, 25, 50, true, result);
}
TEST_METHOD(LegalSudoku) {
int result = 0;
Core core;
core.create_random_sudoku();
core.solve_sudoku(); Assert::AreEqual(result, 0); }
generate()
  • 測試生成數獨的數量是否符合要求
  • 測試生成的是不是合法數獨
TEST_METHOD(solve) {
int puzzle[81] = { 0, 0, 0, 8, 0, 9, 0, 2, 0, 7, 0, 0, 0, 0, 0, 8, 4, 5, 0, 0, 5, 0, 7, 6, 0, 9, 0, 0, 0, 8, 7, 0, 0, 3, 0, 0, 0, 9, 6, 0, 1, 8, 0, 0, 0, 4, 0, 0, 3, 0, 0, 0, 1, 0, 8, 0, 0, 0, 0, 2, 0, 0, 6, 0, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
int *solution = new int[81];
Core core;
bool result = false;
result = core.solve(puzzle, solution);
Assert::AreEqual(result, true);
puzzle[0] = 8;
result = core.solve(puzzle, solution);
Assert::AreEqual(result, false);
}
solve()
  • 分別對文件讀入和直接解決的狀況進行測試
  • 測試文件不存在、文件格式錯誤、文件中存儲的數獨格式錯誤的狀況
  • 測試數獨錯誤、數獨無解的狀況

經過的單元測試


單元測試覆蓋率


有些用來更方便看到結果的輸出到控制檯的函數沒有被調用,因此不能達到100%




計算模塊部分異常處理說明
返回目錄

類別 設計目標 單元測試樣例 應用場景
命令行參數(該部分使用命令行測試) 處理非法的參數
sudoku.exe -a
輸入未定義的參數
處理超出範圍或格式不正確的數字
sudoku.exe -c 100c
sudoku.exe -c 100009999
n,c,r,m後面的參數不正確
處理個數錯誤的參數
sudoku.exe -c -c 100
sudoku.exe -n -m 1
參數重複出現,參數後面沒有數字
處理錯誤的參數組合
sudoku.exe -c 100 -u
sudoku.exe -n 100 -m 3 -u
參數組合錯誤,即不屬於定義的6種組合
讀入的數獨題 處理不存在的文件路徑 sudoku.exe -s "non.txt" 文件不存在
處理格式不正確的文件 sudoku.exe -s "non.exe" 處理文件格式不正確的狀況
處理非法的數獨題
int puzzle[81] = { 0, 0, 0, 8, c, 9, 0, 2, 0, 7, 0, 0, 0, 0, 0, 8, 4, 5, 0, 0, 5, 0, 7, 6, 0, 9, 0, 0, 0, 8, 7, 0, 0, 3, 0, 0, 0, 9, 6, 0, 1, 8, 0, 0, 0, 4, 0, 0, 3, 0, 0, 0, 1, 0, 8, 0, 0, 0, 0, 2, 0, 0, 6, 0, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
int *solution = new int[81];
solve(puzzle, solution)
處理數獨題內容不正確的狀況
處理無解的數獨題 int puzzle[81] = { 0, 0, 0, 8, 8, 9, 0, 2, 0, 7, 0, 0, 0, 0, 0, 8, 4, 5, 0, 0, 5, 0, 7, 6, 0, 9, 0, 0, 0, 8, 7, 0, 0, 3, 0, 0, 0, 9, 6, 0, 1, 8, 0, 0, 0, 4, 0, 0, 3, 0, 0, 0, 1, 0, 8, 0, 0, 0, 0, 2, 0, 0, 6, 0, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
int *solution = new int[81];
solve(puzzle, solution)
處理輸入的數獨題無解的狀況

界面模塊的詳細設計過程

返回目錄

界面模塊的設計


按照需求將頁面劃分爲四個部分:起始狀態的難度選擇模塊,數獨題模塊,功能按鈕模塊,時間、記錄展現模塊,界面中的各類控件使用.ui文件生成,控件的切換由transGUI控制,信號由coreConnect控制。
功能比較簡單,主要有提示、暫停,能夠在開始時進行難度選擇,也能夠在遊戲過程當中點擊返回按鈕從新選擇難度,或者詢問後退出。遊戲界面顯示當前用時和本難度最高紀錄。點擊提交反饋是否正確,並詢問是有繼續遊戲。


代碼說明&實現過程


編輯UI文件,自動生成空間的代碼

class Ui_sudokuGUIClass

{

public:

QWidget centralWidget;

QWidget
verticalLayoutWidget_2;

QVBoxLayout panelLayout;

//...數據成員

void setupUi(QMainWindow
sudokuGUIClass); // setupUi

void retranslateUi(QMainWindow *sudokuGUIClass); // retranslateUi

};

在transGUI中對各控件的狀態進行設置,實現頁面的轉換,與計算模塊的對接也設置在這一部分

class transGUI : public QObject{
Q_OBJECT
public:
QTimer *timer;
QLineEdit *sudokuLineEdit[9][9];
transGUI(Ui_sudokuGUIClass UI);
void writeRecord();
signals:
public slots:
void play();
void updateTime();
void stop();
void goOn();
void option();
void quit();
void quitCancel();
void inform();
void submit();
private:
Ui_sudokuGUIClass ui;
Core core; //計算模塊
QTime recTime;
QLabel *sudokuLabel[9][9];
int **answer, **puzzle;
int mode = 0; //難度級別
FILE *fp; //記錄文件
QTime recTimes[3]; //最高記錄
void readRecord();
};

在coreConnect中實現信號函數和槽函數的連接。

class coreConnect :public QObject{
Q_OBJECT
public:
coreConnect(Ui_sudokuGUIClass UI, transGUI *Trans);
private:
Ui_sudokuGUIClass ui;
transGUI *trans;
QTimer *timer;
void startConnect();
void timeConnect();
void resetTimeConnect();
void backConnect();
void quitConnect();
void quitCancelConnect();
void informConnect();
void submitConnect();
void setTimer();
void againConnect();
signals:
private slots:
void reSetTimer();
};


界面模塊與計算模塊的對接

返回目錄

設計


須要用到計算模塊的部分有獲取數獨題、提示、結果驗證的部分。爲了更好的用戶體驗,產生的數獨都是標準數獨,所以在生成數獨時已經有了惟一解,在獲取數獨題將數獨題存入puzzle數組的同時也將數獨的答案存儲進answer數組,方便提示和提交時使用。


對接


遊戲中難度的選擇對應-m參數,所以點擊遊戲開始時,選擇的難度級別做爲參數m,調用generate(int number, int mode, int** result)函數,由於一次只產生一個數獨遊戲,因此number設爲1,在計算模塊中設置play參數,分別將數獨終局和數獨遊戲存入result。


實現的功能


提示:用戶點擊空格後點擊提示按鈕,在右側提示信息處會出現此處應填的數字



暫停:點擊暫停按鈕,中止計時



返回:退回難度選擇頁面,從新選擇難度級別



退出:點擊退出按鈕,彈出對話框詢問是否確認退出



提交:進行結果驗證,告知用戶答案是否正確,詢問繼續遊戲仍是退出



結對的過程

返回目錄

在週二課上決定結對。


最開始在放假前完成了代碼複審的部分,發現兩我的思路思惟方式很是不同,對方對性能有很高的追求,思惟方式比較獨特,有不少神奇的腦洞;我思路比較窄,也比較循規蹈矩。兩我的最大的共同點是我的項目都沒有寫註釋,也都沒有寫GUI。。。


國慶節期間兩人大部分時間都不在學校,基本沒有任何結對開展工做,雙方獨立進行,對方進行了算法設計,我學習了GUI和生成dll的方法。很是很差的一點是沒有一塊兒對項目進行規劃和設計,基本順其天然。


假期快結束時危機感上升,開始進入一有時間就一塊兒寫代碼的狀態,以對方的代碼爲基礎,按照對方設計的算法兩人共同完成了核心部分。接下來對方主要進行性能優化、異常處理,我完成了參數處理,對代碼進行了測試和單元測試,完成了GUI。


結對照片



結對編程
返回目錄

項目 優勢 缺點
結對編程
  • 效率更高:雙方互相起監督做用,更容易集中精力
  • 有助於結對雙方的進步:雙方互相學習,增加技術、技能,培養更好的編程習慣
  • 有助於提升代碼質量:有效減小bug,更容易貫徹設計
  • 溝通困難:雙方意見不一致,不少時間花費在統一意見上
  • 不良的相互影響:可能致使兩我的精力不集中,討論與編程無關的事情
對方
  • 邏輯嚴密:先產生完整的思路,而後按照設想將代碼逐步實現,整個過程當中基本不會由於考慮不全面而出現問題
  • 有創造力:可以用一些很是創造性的方法更加簡單的解決問題
  • 編程能力強:可以按照設計思路很快的並且不多有bug的完成代碼實現
  • 對程序性能有較高的追求
  • 代碼風格不太好
  • 比較注重自身想法的實現,不注重設計和規劃,後期時間緊張
本身
  • 編寫代碼比較規範,模塊劃分和可讀性較好
  • 有比較強的學習能力
  • 比較注重設計和時間安排,認真負責
  • 編程能力差,常常出現很弱智的bug
  • 思路不夠開闊

預計花費時間&實際花費的時間
返回目錄

PSP2.1 Personal Software Process Stages 預估耗時(分鐘) 實際耗時(分鐘)
Planning 計劃 60 30
  • Estimate
  • 估計這個任務須要多少時間
  • 60 30
    Development 開發 1380 1590
  • Analysis
  • 需求分析 (包括學習新技術)
  • 300 120
  • Design Spec
  • 生成設計文檔
  • 180 60
  • Design Review
  • 設計複審(和同事審覈設計文檔)
  • 60 60
  • Coding Standard
  • 代碼規範 (爲目前的開發制定合適的規範)
  • 60 30
  • Design
  • 具體設計
  • 120 240
  • Coding
  • 具體編碼
  • 1800 2400
  • Code Reviewg
  • 代碼複審
  • 120 120
  • Test
  • 測試(自我測試,修改代碼,提交修改)
  • 240 240
    Reporting 報告 240 180
  • Test Report
  • 測試報告
  • 180 120
  • Size Measurement
  • 計算工做量
  • 60 60
  • Postmortem & Process Improvement Plan
  • 過後總結, 並提出過程改進計劃
  • 60 60
    Sum 合計 3240 3480

    附加題:界面模塊,測試模塊和核心模塊的鬆耦合

    返回目錄


    合做小組

    劉暢 15061183
    王辰昱 15231177


    出現的問題


    若是不改動GUI代碼:合做小組使用咱們的Core.dll每次生成的puzzle都是相同的;咱們使用合做小組的Core.dll沒法得到puzzle。
    用咱們的測試模塊測試合做小組的solve(int[] puzzle, int[] solution)時,若是輸入的puzzle不合法(如同一行中有兩個一樣的數字),沒有返回false


    緣由


    合做小組對非法數組進行了異常處理,會拋出一個InvalidPuzzleException,可是沒有把Exception包含在Core.dll中,雙方異常處理的位置和方式不一樣
    咱們小組的GUI和Core之間存在標準接口之外的交互,在Core中自定義了不少東西,致使合做小組沒法正常使用。根本緣由是咱們小組在作GUI的部分時對Core的使用不規範,沒有徹底按照定義的接口使用而是設置了其餘變量,所以原有的GUI的代碼也沒法使用其餘小組的Core.dll。


    改進


    對GUI界面部分的代碼進行修改,調用Core的標準接口。
    另外咱們的異常處理都在計算模塊外部進行,即默認傳入Core的接口的參數合法,應該對參數進行檢查,使它有必定的異常處理能力。
    另外應該向合做小組學習,他們的Core模塊很是簡潔,包裝很是好除了必要的接口沒有多餘的東西,能夠模塊劃分應該也作得很是好,咱們不該該把計算模塊、輸入輸出都混雜在Core裏面。


    總結

    返回目錄


    • 沒有對項目進行充分的設計和時間規劃,在需求分析和設計上花費的時間明顯不足,致使對進度的把握很很差。

    • 從對方身上能夠看到一些獨特的思惟方式以及很是執著的精神品質,很是的值得學習。

    • 結對雙方沒有爭論可是對課程的理解、關注的重點不一致。
    相關文章
    相關標籤/搜索