返回目錄
https://github.com/514DNA/sudoku
git
返回目錄
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個空就中止挖空。
這個挖空方法的獨到之處就是會先找到全部低級挖空方法能夠挖的空,這樣就減小了暴力回溯的次數
程序中主要費時間的地方是判斷挖完空以後的數獨仍是單解數獨。咱們解數獨有如下幾種方法:1.四種排除法直接把一個數推出來(簡單方法);2.猜數(複雜方法)
挖空過程當中,若是某一個數挖完以後能被簡單方法再推出來,那麼就把這個格子放在緩存區內。找完當前全部的數,那麼就隨機挖掉一個緩存區裏面的空。可是這樣不容易挖夠55個空,那麼只能經過猜數來挖空,猜數是這樣。若是一個數挖完以後仍是單解數獨,那麼就挖掉。經過暴力回溯法判斷數獨解是否惟一,就形成了運行的緩慢
最開始是把全部挖過以後解惟一的數找到再隨機一個,可是後來發現這樣作,判斷解惟一性的次數就多,要追求效率,就要減小這方面花的時間,也就是減小判斷的次數。因此從前日後找,找到第一個能夠挖的格子就挖掉,而後再繼續找,直到挖的空夠55個。
可是這樣改了以後更慢了,這種回溯法對前面空多後面空少的數獨很棘手,會判斷的很是慢。因此就改爲從後往前找,從後往前挖,速度提升。這就是執行世界最難指令:n 10000 -r 55~55 -u所用的時間,雖然挖空的函數仍是用了不少時間,可是總體已經有很大改進了
-c 1000000
-n 10000 -r 55~55 -u
生成數獨時,消耗最大的函數是create_sudoku;生成數獨遊戲時,消耗最大的函數是create_sudoku_puzzle
單元測試代碼 | 測試的函數 | 測試數據構造思路 |
---|---|---|
TEST_METHOD(SingleSolution) |
create_sudoku_puzzle(int n) |
|
TEST_METHOD(generate) { |
generate() |
|
TEST_METHOD(solve) { |
solve() |
|
有些用來更方便看到結果的輸出到控制檯的函數沒有被調用,因此不能達到100%
類別 | 設計目標 | 單元測試樣例 | 應用場景 |
---|---|---|---|
命令行參數(該部分使用命令行測試) | 處理非法的參數 | sudoku.exe -a |
輸入未定義的參數 |
處理超出範圍或格式不正確的數字 | sudoku.exe -c 100c |
n,c,r,m後面的參數不正確 | |
處理個數錯誤的參數 | sudoku.exe -c -c 100 |
參數重複出現,參數後面沒有數字 | |
處理錯誤的參數組合 | sudoku.exe -c 100 -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 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 }; |
處理輸入的數獨題無解的狀況 |
按照需求將頁面劃分爲四個部分:起始狀態的難度選擇模塊,數獨題模塊,功能按鈕模塊,時間、記錄展現模塊,界面中的各類控件使用.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。
項目 | 優勢 | 缺點 |
---|---|---|
結對編程 |
|
|
對方 |
|
|
本身 |
|
|
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | 60 | 30 |
60 | 30 | ||
Development | 開發 | 1380 | 1590 |
300 | 120 | ||
180 | 60 | ||
60 | 60 | ||
60 | 30 | ||
120 | 240 | ||
1800 | 2400 | ||
120 | 120 | ||
240 | 240 | ||
Reporting | 報告 | 240 | 180 |
180 | 120 | ||
60 | 60 | ||
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裏面。