結對項目-數獨程序擴展

1)Github傳送門

https://github.com/Issac-Newton/Sudoku_extendgit

2)PSP表

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

3)結對編程中Information Hiding, Interface Design, Loose Coupling原則的使用

Information Hiding:Core類的全部數據成員都聲明爲private,全部訪問都只能經過Core模塊提供的四個接口實現;
Interface Design:針對-c,-s,-n -m,-n -r -u這幾種參數組合咱們分別設計了接口,每一個接口負責完成一個類型的參數組合的功能,儘量保證接口功能的單一性;
Loose Coupling:Core模塊的接口對傳入的參數除類型外沒有要求,會自行對參數的合法性進行檢查,減小了對調用時參數的要求;另外Core發生改變時只要接口不變不會對調用它的類產生影響;github

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

咱們共實現了10個類,分別爲Core,Handler以及8個異常類;
計算模塊爲Core模塊;在Core中設計和實現了4個接口,分別是:
void generate(int number, int result[][CELL]);用來生成最多100,0000個數獨終局,對於該接口的實現,咱們在判斷傳進來的參數的合法後,直接調用TraceBack()函數生成知足數量要求的數獨終盤;
void generate(int number, int mode, int result[][CELL]);用來生成最多10,000個模式爲mode的數獨題目,對於該接口的實現,咱們首先檢查參數的合法性,而後生成知足數量要求的數獨終盤,以後再根據模式來對數獨進行修改;
void generate(int number, int lower, int upper, bool unique, int result[][CELL]);用來生成最多10,000個挖空數在lower到upper之間的數獨題目,其中unique爲true時表示數獨題目只有惟一解;對於該接口的實現,咱們首先檢查參數的合法性,而後生成知足數量要求的數獨終盤,以後再根據挖空數以及unique的值來對數獨進行修改,若unique的值爲true則每次修改完成後都調用IsSingleSolution()函數來檢查數獨題目是否爲惟一解;
bool solve(int puzzle[CELL], int solution[CELL]);用來解sudoku.txt文件中的數獨題目,若能解出數獨題目則調用CopySudoku()將答案複製到solution中;
共10個函數,除上述的4個接口外,還有:
Core();構造函數
bool IsValid(int pos, bool isSolve);檢查每次填的數是否知足數獨的要求,若知足則返回true,不然返回false;
bool TraceBackSolve(int pos);用回溯法檢查數獨問題是否有解,如有解則返回true並求解,不然返回false;
int TraceBack(int pos, int number, int& count, int result[][CELL], bool isSolve);用回溯法生成最多100,0000個數獨題目,每填一個格子都會調用IsValid()函數來檢查正確性,每生成一個數獨終盤都會調用CopySudoku()函數將終盤複製到result中;
void CopySudoku(int result[CELL], int temp[GRIDSIZE][GRIDSIZE]);將結果複製到result中;
bool IsSingleSolution(int tot, int& ans);用回溯法判斷生成的數獨題目是否爲惟一解,如有惟一解則返回false,不然返回true;
算法的關鍵..就是回溯,沒有多餘的技巧;
如下是int TraceBack(int pos, int number, int& count, int result[][CELL], bool isSolve);的流程圖:

如下是void generate(int number, int lower, int upper, bool unique, int result[][CELL]);的流程圖:
算法

Handler類用於處理命令行輸入;
這個類的成員變量會有輸入參數的信息,像是生成數獨終盤的個數和生成數獨遊戲時要挖空的個數。
在對參數進行處理時,咱們是按照參數個數對輸入進行判斷的。具體狀況以下:
參數個數大於6或者是小於3,參數個數異常;
參數個數等於3,有效輸入只多是-c或者是-s;
參數個數等於4,有效輸入只多是-n和-u的搭配;
參數個數等於5,有效輸入多是-n和-m的搭配或者是-n和-r的搭配;
參數個數等於6,有效輸入多是-n -u -r的幾種搭配;
首先對參數選項字符進行確認,而後對選項後面的參數進行提取,有錯誤則報異常。編程

各個異常類將在以後詳細說明;數組

5)計算模塊的UML圖

(咱們的計算模塊只有一個Core,不太懂這個UML怎麼畫...)
函數

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

花費時間約3小時;
改進思路:因爲咱們依舊採用回溯法,所以對以前的功能的性能沒有更多改進;咱們主要針對void generate(int number, int lower, int upper, bool unique, int result[][CELL]);這一函數進行性能改進,一開始咱們的算法是針對某一個數獨終盤,每隨機挖一個空都馬上檢查是否有惟一解,若惟一則隨機挖下一個,不然還原這個空從新挖,若沒法找到知足條件的挖空位置則回溯,但測試之後發現算法自己好像出了寫問題,生成了多解數獨;
因而咱們採用了一次性隨機產生全部挖空位置,挖好後再檢查是否有惟一解的算法,咱們的性能改進主要是減小產生的隨機數的碰撞次數(實際上就是湊...),可是一直都最後也沒能很好的提升產生挖空數爲55的惟一解的數獨題目的性能。工具

性能分析圖是void generate(int number, int lower, int upper, bool unique, int result[][CELL]);在生成1個挖空數爲55的惟一解的數獨問題的性能分析圖;消耗最大的函數是IsValid();
佈局

7)Design By Contract,Code Contract的優缺點以及在結對編程時的實際應用

Design By Contract:http://en.wikipedia.org/wiki/Design_by_contract
Code Contract:http://msdn.microsoft.com/en-us/devlabs/dd491992.aspx性能

  • 優勢:使用者和被調用者地位平等,雙方必須彼此履行義務,才能夠行駛權利。調用者必須提供正確的參數,被調用者必須保證正確的結果和調用者要求的不變性。雙方都有必須履行的義務,也有使用的權利,這樣就保證了雙方代碼的質量,提升了軟件工程的效率和質量。
  • 缺點:會給接口的調用帶來較大的風險,須要嚴格的驗證參數的正確性;
  • 應用:一開始咱們對Core模塊的接口採用契約式設計...後來不知怎麼的咱們又在Core中對傳入的部分參數作了正確性驗證..但又沒有驗證傳進來的數組size是否足夠..如今看來設計的有點四不像,看來須要改進;

8)項目的單元測試展現:

單元測試結果:
單元測試

項目的單元測試主要是迴歸測試,對generate和solve的測試以及對輸入處理的測試。
迴歸測試:
增量修改工程以後要對以前的功能作一個覆蓋性檢查

//-c
TEST_METHOD(TestMethod4)
        {
            int result[100][CELL];
            int grid[GRIDSIZE][GRIDSIZE];

            set<string> container;
            string inserted;

            Core core;
            core.generate(100, result);
            for (int i = 0; i < 100; i++)
            {
                for (int j = 0; j < GRIDSIZE; j++)
                {
                    for (int k = 0; k < GRIDSIZE; k++)
                    {
                        grid[j][k] = result[i][j * GRIDSIZE + k];
                        assert(!(grid[j][k] <= 9 && grid[j][k] >= 1));
                        inserted.push_back(grid[j][k] + '0');
                    }
                }
                Assert::IsTrue(valid(grid));
                container.insert(inserted);
                inserted.clear();
            }
                        assert(container.size() == 100);
        }

每生成一個數獨終盤就對數獨的有效性進行檢測,最後對數量進行檢測,方法是將每一個數獨都轉化爲一個字符串,將字符串插入到一個集合中,能夠找到生成的數獨的數量。

//-s
TEST_METHOD(TestMethod7)
        {
            int puzzle[CELL];
            int solution[CELL];
            Core core;

            bool flag = true;

            FILE* file_in;
            freopen_s(&file_in, "C:\\Users\\dell\\Source\\sudoku\\ModeTest\\sudoku.txt", "r", stdin);
            assert(file_in != NULL);
            while (true)
            {
                if (fscanf(file_in, "%d", &puzzle[0]) == EOF)
                {
                    break;
                }
                for (int i = 1; i < CELL; i++)
                {
                    fscanf(file_in, "%d", &puzzle[i]);
                }
                assert(core.solve(puzzle,solution));
                int grid[GRIDSIZE][GRIDSIZE];
                for (int j = 0; j < CELL; j++)
                {
                    grid[j / GRIDSIZE][j % GRIDSIZE] = solution[j];
                }
                assert(valid(grid));
            }
        }

每次從文件中讀入一個數獨就調用solve函數進行求解,求解以後對數獨有效性進行判斷,而後和solve函數的返回值進行比較

對新增功能的測試:
下面代碼是對-u -r -n組合的測試

TEST_METHOD(TestMethod5)
        {
            int result[1000][CELL];
            Core core;
            core.generate(2, 55, 55, true, result);
            
            bool flag = true;

            for (int i = 0; i < 2; i++)
            {
                int grid[GRIDSIZE][GRIDSIZE];
                for (int j = 0; j < CELL; j++)
                {
                    grid[j / GRIDSIZE][j % GRIDSIZE] = result[i][j];
                }
                int ans = 0;
                if (MultiSolution(0, ans, grid))
                {
                    flag = false;
                    Assert::IsTrue(flag);
                }
            }
        }

咱們對生成好的數獨遊戲進行暴力求解(回溯法),若是有多解,那麼斷言失敗。

//判斷數獨是否是有多解的函數
        bool MultiSolution(int tot, int& ans, int grid[GRIDSIZE][GRIDSIZE])
        {
            if (tot == GRIDSIZE * GRIDSIZE) {
                ans++;
                return true;
            }
            else {
                int x = tot / GRIDSIZE;
                int y = tot % GRIDSIZE;

                if (grid[x][y] == 0) {
                    for (int i = 1; i <= 9; i++) {
                        grid[x][y] = i;
                        if (IsValid(tot, grid)) {
                            if (MultiSolution(tot + 1, ans, grid)) {
                                if (ans > 1)
                                {
                                    return true;
                                }
                                continue;
                            }
                        }
                    }
                    grid[x][y] = 0;
                }
                else {
                    return MultiSolution(tot + 1, ans, grid);
                }
            }
            return false;
        }

對異常的測試:
由於新增的有效輸入只有幾種,咱們對每種都作出檢查

以參數的數字範圍異常爲例,代碼以下:

TEST_METHOD(TestMethod10)
        {
            //-c
            char* command[5] = { "sudoku.txt","-c","10000001"};
            try
            {
                main(3, command);
            }
            catch (exception& e)
            {
                Assert::IsTrue(typeid(e) == typeid(NumberOutOfBoundException));
            }
            assert(hasException);
            hasException = false;

            //-n
            char* command1[5] = {"sudoku.txt","-n","10001","-m","1"};
            try
            {
                main(5,command1);
                
            }
            catch (exception& e)
            {
                Assert::IsTrue(typeid(e) == typeid(NumberOutOfBoundException));
            }
            assert(hasException);
            hasException = false;

            //-m(模式錯誤)
            char* command2[5] = { "sudoku.txt","-n","1000","-m","4" };
            try
            {
                main(5,command2);
            }
            catch (exception& e)
            {
                Assert::IsTrue(typeid(e) == typeid(ModeException));
            }
            assert(hasException);
            hasException = false;

            //-r
            char* command3[5] = {"sudoku.exe","-n","10","-r","50~56"};
            try
            {
                main(5,command3);
            }
            catch (exception& e)
            {
                Assert::IsTrue(typeid(e) == typeid(NumberOutOfBoundException));
            }
            assert(hasException);
            hasException = false;

咱們首先將參數傳入main函數,而後用assert將異常拋出的異常類型和應該拋出的異常類型作比較,可是若是沒有拋出異常豈不是漏了bug。因此,在一個頭文件裏我定義了一個標記異常發生過的變量,main函數每次捕捉到異常以後就將該變量賦值爲真,main函數以後斷言這個變量爲真。每一個測試點跑過以後,將該值設置爲假。

覆蓋率以下:

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

Ⅰ.參數個數異常(定義爲ParametersNumberException)
設計目標:若是輸入命令參數過多,那麼程序拋出異常。
設計單元測試:

char* command[8] = {"sudoku.exe","-n","100","-n","-r","-s","-m","-d"};
            try {
                main(9,(char**)command);
            }
            catch (exception& e)
            {
                Assert::IsTrue(typeid(e) == typeid(ParametersNumberException));
            }
            assert(hasException);
            hasException = false;

Ⅱ.文件不存在異常(定義爲FileNotExistException)
設計目標:-s命令下,若是打開文件失敗,那麼拋出異常。
設計單元測試:

TEST_METHOD(TestMethod9)
        {
            char* command[3] = { "sudoku.exe","-s","NotExist.txt" };
            try
            {
                main(3,command);
            }
            catch (exception& e)
            {
                Assert::IsTrue(typeid(e) == typeid(FileNotExistException));
            }
            assert(hasException);
            hasException = false;
        }

Ⅲ.命令中的各類數字溢出異常(定義爲NumberOutOfBoundException)
設計目標:在各類參數下,若是數字不符合規範,拋出異常。
設計單元測試:
見上部分異常單元測試部分 ↑↑

Ⅳ.-r選項後面的數字異常(定義爲RParametersException)
設計目標:在-r參數後面,若是後面跟的參數字符長度不是5或者第三個字符不是 ~ 或者存在不是1-9的字符,那麼拋出異常。
設計單元測試:

char* command[5] = { "sudoku.exe","-n","10","-r","20-55"};
            try
            {
                main(5, command);
            }
            catch (exception& e)
            {
                Assert::IsTrue(typeid(e) == typeid(RParametersException));
            }
            assert(hasException);
            hasException = false;

            char* command2[20] = { "sudoku.exe","-n","10","-r","3n~40" };
            try
            {
                main(5, command);
            }
            catch (exception& e)
            {
                Assert::IsTrue(typeid(e) == typeid(RParametersException));
            }
            assert(hasException);
            hasException = false;

Ⅴ.命令中包含非法字符(定義爲IllegalCharException)
設計目標:在-c這樣的選項不能匹配時拋出異常。
設計單元測試

char* command[20] = { "sudoku.exe","-nn","10","-r","20~55" };
            try
            {
                main(5, (char**)command);
            }
            catch (exception& e)
            {
                Assert::IsTrue(typeid(e) == typeid(IllegalCharException));
            }
            assert(hasException);
            hasException = false;

Ⅵ.-s參數中數獨無解(定義爲NoSolutionException)
設計目標:若是-s參數後面文件中的數獨無解,拋出異常
設計單元測試:

char* command[20] = { "sudoku.exe","-s","puzzle.txt"};
            try
            {
                main(3,command);
            }
            catch (exception& e)
            {
                Assert::IsTrue(typeid(e) == typeid(NoSolutionException));
            }
            assert(hasException);
            hasException = false;

文件中的數獨:
000000123
009000000
000009000
000000000
000000000
000000000
000000000
000000000
000000000
-->右上角的九宮格不能放9

Ⅶ.數字錯誤異常(定義爲IllegalNumberException)
設計目標:在求解數獨的時候,若是從文件中讀入的數字不在1-9,拋出異常
設計單元測試:
單元測試代碼同上一個異常類型,可是文件中的數獨中包含不在1-9的數字

Ⅷ. -m 後面的模式錯誤(定義爲ModeException)
設計目標:檢查generate參數中模式是否是1,2,3若是不是,拋出異常
設計單元測試:

char* command2[5] = { "sudoku.txt","-n","1000","-m","4" };
            try
            {
                main(5,command2);
            }
            catch (exception& e)
            {
                Assert::IsTrue(typeid(e) == typeid(ModeException));
            }
            assert(hasException);
            hasException = false;

10)界面模塊的詳細設計過程

如下將按照從上到下的順序來對整個GUI進行描述
GUI菜單欄中有選擇模式和查看每一個模式下最佳記錄的兩個Action,每一個裏面都有三個選項-->easy,normal,hard
下面就是數獨盤(左上角),右上角是計時器。
數獨的實現使用的控件是textEdit,咱們重寫了這個控件的部分函數,改變鼠標focusIn和focusOut的行爲,使之可以在鼠標定位到某個未填塊的時候將邊框標紅;在鼠標離開的時候可以對輸入的字符進行判斷處理。代碼以下:

void MyTextEdit::focusInEvent(QFocusEvent *e)
{
    if (!isReadOnly())
    {
        setStyleSheet(QString::fromUtf8("font: 20pt \"\351\273\221\344\275\223\";""border: 3px solid red"));
    }
    emit cursorPositionChanged();
}

void MyTextEdit::focusOutEvent(QFocusEvent *e)
{
    QString str;
    str = toPlainText();
    int position = textCursor().position();
    int length = str.count();
    if (!isReadOnly())
    {
        setStyleSheet(QString::fromUtf8("font: 23pt \"\351\273\221\344\275\223\";""border: 1px solid grey;color:blue"));
        if (!IsValidChar(str))
        {
            setPlainText("");
            setCursorWidth(0);
        }
        else
        {
            //setStyleSheet("color:blue");
            setAlignment(Qt::AlignCenter);
        }
        }
}

右上角的時鐘支持暫停功能,暫停以後,數獨盤上的全部模塊都會清空,當繼續以後又會恢復以前的數字。
清空和恢復的代碼以下:

//清空
for (int i = 0; i < GRIDSIZE; i++)
        {
            for (int j = 0; j < GRIDSIZE; j++)
            {
                QString str = ui.textEdit[i][j]->toPlainText();
                
                if (IsValidChar(str))
                {
                    int num = str.toInt();
                    m_fill[i * GRIDSIZE + j] = num;
                    if (ui.textEdit[i][j]->isReadOnly())
                    {
                        m_fillBlack[i * GRIDSIZE + j] = num;
                    }
                    ui.textEdit[i][j]->setReadOnly(false);
                    QString strIn = "";
                    ui.textEdit[i][j]->setText(strIn);
                }
                ui.textEdit[i][j]->setReadOnly(true);
            }
        }
//顯示
    for (int i = 0; i < GRIDSIZE; i++)
    {
        for (int j = 0; j < GRIDSIZE; j++)
        {
            ui.textEdit[i][j]->setReadOnly(false);
            if (m_fill[i * GRIDSIZE + j] > 0)
            {
                QString str = QString::number(m_fill[i * GRIDSIZE + j], 10);
                ui.textEdit[i][j]->setText(str);
                if (m_fillBlack[i * GRIDSIZE + j] == 0)       //此時要用藍色字體
                {
                    ui.textEdit[i][j]->setStyleSheet(QString::fromUtf8("font: 23pt \"\351\273\221\344\275\223\";""border: 1px solid grey;color:blue"));
                    ui.textEdit[i][j]->setAlignment(Qt::AlignCenter);
                    ui.textEdit[i][j]->setReadOnly(false);
                }
                else
                {
                    ui.textEdit[i][j]->setStyleSheet(QString::fromUtf8("font: 23pt \"\351\273\221\344\275\223\";"));
                    ui.textEdit[i][j]->setAlignment(Qt::AlignCenter);
                    ui.textEdit[i][j]->setReadOnly(true);
                }
                
            }
            else
            {
                QString str = "";
                ui.textEdit[i][j]->setText(str);
            }
        }
        }

清空的時候咱們是先將數獨中現有的數字拷貝下來,由於咱們在生成的時候有一個挖空的未填的備份,因此能夠知道以後哪一個空是人爲填的,因此顯示的時候能夠區分開人爲填的空格(這兩個顯示是不同的)。

數獨盤下面有三個按鈕,功能分別是「從新開始」,「檢查答案」,「提示」,
從新開始就是將原來的textEdit控件上面的字符清空,而後將原來的那個從新填入。
檢查答案就是將如今textEdit上的數字和答案數字相對比,若是有不一樣,那麼會彈出一個彈窗。
提示功能是提示上一次鼠標定位到的未填的格子中的數字,而且將這個數字填入這個格子,以後這個格子的數字和最開始生成遊戲時的字體同樣。

if (ui.focusIn != NULL)
    {
        int col;
        int line;
        for (int i = 0; i < GRIDSIZE; i++)
        {
            for (int j = 0; j < GRIDSIZE; j++)
            {
                if (ui.focusIn == ui.textEdit[i][j])
                {
                    line = i;
                    col = j;
                    break;
                }
            }
        }

        int num = m_result[line * GRIDSIZE + col];

        QString str = QString::number(num, 10);
        ui.textEdit[line][col]->setText(str);
        ui.textEdit[line][col]->setStyleSheet(QString::fromUtf8("font: 21pt \"\351\273\221\344\275\223\";""border: 1px solid grey"));    
        ui.textEdit[line][col]->setAlignment(Qt::AlignCenter);
        ui.textEdit[line][col]->setReadOnly(true);
        informSuccess = true;
    }

其中,咱們在記錄上一次鼠標定位的位置遇到了困難,由於不可以在類定義中對該對象進行賦值,因此沒辦法在鼠標focusIn的時候將指示對象指針賦值。因此咱們只能在類定義外面對對象指針進行賦值。咱們對每一個textEdit塊 connect 一個槽函數,在focusIn的時候 emit 一個信號。
focusIn的函數見上,其中,能夠看到emit 一個 cursorPositionChanged() 信號,以後觸發槽函數,槽函數得到調用者,對指針進行賦值

MyTextEdit* temp = qobject_cast<MyTextEdit*>(sender());
if (!temp->isReadOnly())
{
    ui.focusIn = temp;
}

另外還作得一些工做就是美工,這部分比較複雜,字體,背景,邊框等等...每一個控件用的方法也不同,不過大致上使用 palette和setStyleSheet兩種方法居多。
使用palette示例:

//整個窗口的背景
QPixmap pixmap = QPixmap("background.jpg").scaled(GUITestClass->size());
QPalette palette(GUITestClass->palette());
palette.setBrush(QPalette::Background, QBrush(pixmap));
GUITestClass->setPalette(palette);
//最下面三個按鈕的樣式
QString button_style = "QPushButton{font-family:Comic Sans MS;font-size:16pt;background-image:url(button1.jpg); color:white; border-radius:10px;border-style: outset;}"
            "QPushButton:pressed{background-image:url(pressed1.jpg);border-style:inset;}";
pushButton_3->setStyleSheet(button_style);
pushButton_4->setStyleSheet(button_style);
pushButton_5->setStyleSheet(button_style);

感覺:一開始覺得加個界面應該很快,後來咱們才發現本身仍是naive...以及在寫界面的過程當中兩個直男因爲審美不一樣還產生了一些分歧..

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

界面的最終效果圖以下:

界面設計和計算模塊之間的聯繫主要是界面使用的數字是從Core模塊中產生出來的,界面調用Core模塊中函數的代碼以下:

int save_sudoku[1][CELL];
    memset(save_sudoku, 0, sizeof(save_sudoku));

    bool choosen[10];
    memset(choosen,0,sizeof(choosen));

    srand(time(0));

    for (int i = 0; i < 5; i++)
    {
    int posi = rand() % 9 + 1;
    while (choosen[posi])
    {
    posi = rand() % 9 + 1;
    }
    choosen[posi] = true;
    save_sudoku[0][i] = posi;
    }

    int reduce;
    int empty;
    switch (m_mode)
    {
    case EASY:
        reduce = 40 + rand() % 8;
        break;
    case MIDDLE:
        reduce = 32 + rand() % 8;
        break;
    case HARD:
        reduce = 31 + rand() % 8;
        break;
    default:
        break;
    }
    empty = CELL - reduce;

    Core temp;
    temp.generate(1, empty, empty, true, save_sudoku);

    memset(m_fillBlack, 0, sizeof(m_fillBlack));
    memset(m_fill,0,sizeof(m_fill));

    for (int i = 0; i < CELL; i++)
    {
        m_fill[i] = save_sudoku[0][i];
        m_fillBlack[i] = save_sudoku[0][i];
        m_backup[i] = save_sudoku[0][i];
    }

    m_hasStarted = true;
    temp.solve(save_sudoku[0], m_result);
    showNumber();

由於在Core模塊中爲了保證生成數獨的速度,因此傳入的result矩陣是空矩陣。可是,由於使用的是回溯法,這樣就會形成每兩個相鄰的矩陣十分類似,可想而知,這樣會嚴重影響用戶的體驗,因此,咱們在GUI模塊裏添加了對result二維數組的初始化,隨機填了五個數字。

12)描述結對的過程

13)結對編程的優勢和缺點&結對的每個人的優勢和缺點

結對編程:

  • 優勢:1.在設計時,兩我的的思惟更加開闊,設計的比一我的設計時更全面;2.不間斷的交流與複審,能夠在開發和寫代碼的過程當中就減小大量bug;3.在遇到問題時,兩人合做解決問題的能力更強;
  • 缺點:1.在遇到一些細節問題(好比界面用哪一種字體哪張圖片)時,兩我的容易發生分歧,須要一方退讓;2.兩我的的思惟有時可能不在同一個點上,致使討論了半天發現講的不是同一個問題,總之就是很難保證結對雙方思惟的一致性;3.一我的在寫代碼時,時間一長邊上的另外一我的可能注意力會愈來愈不集中;4.每一個人對另外一我的寫的代碼印象不深入,致使在出現問題時大部分時間只能靠寫那段代碼的人分析;

本人:

  • 優勢:考慮的比較多比較全面;在遇到問題時思惟比較開闊;容易溝通;
  • 缺點:在一旁複審代碼時注意力容易不集中;對於結對夥伴忽略個人建議而本身思考會有些不耐煩;對項目進度一直不夠樂觀;

結對夥伴:

  • 優勢:脾氣好,容易溝通;對工具的使用很是優秀;學習能力強;
  • 缺點:常常當前階段的bug還沒解決就直接想着後面的階段了;

附加題部分

【第四部分】

咱們測試的小組是15061187竇鑫澤 + 15061189李欣澤,測試咱們的小組是15061199李奕君 + 14011100趙奕
咱們找到的錯誤Issue到了對應小組的github項目地址
咱們使用他們的Core模塊發現不能捕捉到異常,也就是說他們的異常拋出是在他們項目的main函數裏面。
咱們被找的錯誤 Github
其中一個問題是咱們的solve函數的問題,由於solve函數用的是回溯法來解,只會判斷每一個位置是否是知足數獨對這個位置的要求,可是沒有考慮到總體的要求。
最終致使那個錯誤的發生,因此,咱們在求解完以後又對求解的數獨進行了一次檢驗。

for (int i = 0; i < GRIDSIZE; i++)
    {
        for (int j = 0; j < GRIDSIZE; j++)
        {
            m_grid[i][j] = puzzle[i*GRIDSIZE + j];
        }
    }

    if (TraceBackSolve(0))
    {
        CopySudoku(solution, m_grid);
        if (valid(m_grid))
        {
            return true;
        }
    }
    throw NoSolutionException("The sudoku has no solution.\n\n");

valid就是對數獨進行有效性檢驗的函數。

針對另一個問題,由於咱們用回溯法生成數獨終盤以後挖空,並且傳入的數組是空數組,因此就會從第一個位置開始回溯,這樣致使每兩個數獨之間的類似性很大,
設計遊戲的時候咱們也考慮到了這個問題,因此在GUI工程裏面調用generate函數以前先對矩陣進行一些初始化,因此,這就致使咱們的模塊不具有隨機化的功能。
根據趙奕、李奕君小組提出的問題,咱們把那個初始化放到了core模塊裏面。

bool choosen[10];
    memset(choosen, 0, sizeof(choosen));
    srand(time(0));
    for (int i = 0; i < 5; i++)
    {
        int posi = rand() % 9 + 1;
        while (choosen[posi])
        {
            posi = rand() % 9 + 1;
        }
        choosen[posi] = true;
        m_grid[0][i] = posi;
    }

針對遇到異常時的反饋不明確,咱們又對這一部分進行了細化。

if ((number < 1))
    {
        throw NumberOutOfBoundException("The number after -n is smaller than minimum 1.\n\n");
    }
    if ((number < 1) || (number > MAX_N))
    {
        throw NumberOutOfBoundException("The number after -n is bigger than maximum 1000000.\n\n");
    }

    if ((upper > EMPTY_UPPER))
    {
        throw NumberOutOfBoundException("The number of upper is bigger than maximum 50.\n\n");
    }
    if ((upper < EMPTY_LOWER))
    {
        throw NumberOutOfBoundException("The number of upper is smaller than minimum 20.\n\n");
    }

    if ((lower > EMPTY_UPPER))
    {
        throw NumberOutOfBoundException("The number of lower is bigger than maximum 50.\n\n");
    }
    if ((lower < EMPTY_LOWER))
    {
        throw NumberOutOfBoundException("The number of lower is smaller than minimum 20.\n\n");
    }

【第五部分】

下載地址:

https://github.com/Issac-Newton/Sudoku_extend

用戶反饋

User One:
和通常的軟件認知不同,不能將單獨的exe文件拷貝到桌面上。
不一樣電腦上字符有差別。

User Two:
沒有說明;
Hint的功能對新手不是特別容易使用;
界面過於單調,作對作錯的彈窗差異不是特別明顯。

User Three:
界面對新手不是很友好。
用戶提出了新的需求(添加回退功能)。

User Four:
但願可以添加一個保存功能,保存上次未作完的遊戲。

User Five:
但願能夠有幫助菜單提供數獨規則。

User Six:
不一樣電腦上顯示的兼容性有差別。
但願提示功能作得更加智能一些,不要只是簡單的顯示答案。

User Seven:
亮點在於:遊戲有暫停功能,方便用戶使用;數獨支持鍵盤填寫,有必定便捷性。
不足在於:在未完成的時候,check應該顯示未完成,而不是錯誤答案;界面的佈局不夠美觀,如計時功能不夠居中,右下方存在必定的蜜汁空白;對用戶的提示過於簡單,用戶只能靠我的去摸索須要用鍵盤輸入。

User Eight:
暫停功能是亮點,感受打開gui直接進入到遊戲頁面有些突兀。界面右側的說明引導步驟必要,可是有些過於簡略。數獨按鈕的風格不知能不能在優美一點?

User Nine:

  1. exe標題的sudoku.exe多了個點.
  2. 上面的menu的「Personal Best」中間的空格不建議,給人一種2個menu的錯覺。建議用一個單詞或去掉空格
  3. 當暫停的時,再點Hint會出現一個格子的值。

User Ten:
我對這款軟件有幾點建議:
首先,我建議增長一個幫助菜單或幫助按鈕。由於軟件的界面雖然簡單,可是對於那幾個按鈕都沒有功能介紹,在詢問開發者以前我都不知道Hint按鈕是須要先選中一個輸入框再點擊Hint按鈕的。
其次,我建議增長一個Clear按鈕,改變Restart按鈕的功能。界面中Restart按鈕的功能是從新開始本局遊戲,數獨是不會改變的,每次改變數獨須要在Mode菜單中從新選擇難易度,我認爲不如增長一個Clear按鈕實現目前Restart按鈕實現的清空已輸入的功能,Restart按鈕的功能改變爲從新生成一個新的當前難易度下的數獨。
第三,我建議增長一個保存功能,能夠保存當前正在作的數獨,下次打開軟件能夠繼續上次的遊戲。

改進:
關於發佈的目錄:如今發佈時將全部的依賴項都放到了一個文件夾下,而後將快捷方式放到了和文件夾同目錄下。
關於幫助:如今提供了help功能,如圖:

添加了這個圖片同時解決了關於右下角空白的問題。 關於不一樣電腦上各類圖標大小顯示比例的問題,通過更改界面,咱們已經可以支持在 100%和125%上界面是沒問題的,可是若是這個比例更大會有些問題。 其餘關於GUI的美化問題,作了一些修改,可是...讓全部人都滿意好難... 保存功能和其餘一些功能因爲時間緣由,未添加。

相關文章
相關標籤/搜索