vue 數獨

ni742017發表於2017-12-25

最近看到有朋友發了一篇類似的文章用vue開發一個所謂的數獨 想起自己幾個月前學學習vue的時候也做過一個類似的,跟上篇文章中實現的思路有些不同,遂起興拿出來分享一下github

前言:

前段時間在學習vue框架時看到官網示例中有這麼個栗子

數獨

咋樣是不是很酷炫,看到這個頓時按耐不住一顆程式設計師的心,然後花了2、3個晚上的時間寫了個大概。。

數獨

怎麼樣是不是感覺(完全沒有)掌握到精髓。。。

恩好吧,雖然我們長得醜但基本功能還是有的,在資料生成這塊也是有些值得思考的地方。

規則

在分析具體程式碼之前先明確一下數獨的規則:

數獨是源自18世紀瑞士的一種數學遊戲。是一種運用紙、筆進行演算的邏輯遊戲。玩家需要根據9×9盤面上的已知數字,推理出所有剩餘空格的數字,並滿足每一行、每一列、每一個粗線宮(3*3)內的數字均含1-9,不重複。

思路

要實現這個遊戲,我的想法是先生成一個完整的符合規則的陣列再將其中的一些值隱去。在驗證時把填入的值和隱去的值作比較,如果相同就算正確,如果不一樣在按規則做一次校驗(考慮到可能有多種解法)。

實現步驟

  1. 資料結構

    實現這個9*9宮格應該大家想的都差不多,用一個二維陣列去對應每一格的資料。每個資料中包含一些屬性用來表識具體的狀態(正確、錯誤等)具體可在class檔案中檢視

    initGridList() {
        this.gridList = [];
    
        //生成大體佈局
        for (let i = 0; i < this.ROWS; i++) {
            let rowList = [];
            for (let j = 0; j < this.COLS; j++) {
                let data = new Grid();
                rowList.push(data);
            }
            this.gridList.push(rowList);
        }
    
        return this.gridList;
    
    };
    複製程式碼
  2. 向這個二維陣列中插值(重點)

    在我們想這個二維陣列中填值時要考慮數獨的3個規則:

    1. 行不重複
    2. 列不重複
    3. 每個小9宮格不重複

    我們可以按照這3個要求來生成3個陣列分別滿足對應的要求的陣列即

    let unUseRowList = this.getNoRepeatData(row, col, 1); //1.行不能重複
    let unUseColList = this.getNoRepeatData(row, col, 2); //2.列不能重複
    let unUseGroupList = this.getNoRepeatData(row, col, 3); //3.9宮格不能重複
    複製程式碼

    然後取這三個陣列的交集,即是可插入的值的陣列,然後從這陣列中隨機取個值插入即可。

    相關生成陣列的程式碼:

    getNoRepeatData(row, col, type) {
    
        let baseDataList = this.baseDataList;
    
        //1.每行不能重複
        //2.每列不能重複
        //3.每個9宮格不能重複
        if (type === 1) {
            //篩選出有值的物件
            // debugger
            let exitDataList = this.gridList[row].filter((item, i) => {
                if (item && i !== col)
                    return item["text"];
            });
    
            return this.getUnUseDataList(exitDataList, baseDataList);
        } else if (type === 2) {
            let colDataList = this.gridList.map(item => {
                return (item.filter((subItem, index) => {
                    return index === col;
                }))[0]
            });
            //篩選出有值的物件
            let exitDataList = colDataList.filter((item, i) => {
                if (item && i !== row)
                    return item["text"];
            });
    
            return this.getUnUseDataList(exitDataList, baseDataList);
        } else if (type === 3) {
            let rowRange = [(row / 3 | 0) * 3, (row / 3 | 0) * 3 + 3];
            let colRange = [(col / 3 | 0) * 3, (col / 3 | 0) * 3 + 3];
            // console.log(rowRange, colRange);
    
            let exitDataList = [];
    
            for (let i = rowRange[0]; i < rowRange[1]; i++) {
                for (let j = colRange[0]; j < colRange[1]; j++) {
                    let item = this.gridList[i][j];
                    if (item && (i !== row || j !== col))
                        exitDataList.push(item);
                }
            }
            // console.log(exitDataList);
    
            return this.getUnUseDataList(exitDataList, baseDataList);
    
        }
    
    };
    
    複製程式碼

    ok接下來我們就可以試著生成標準答案了。

    ...loading...

    DuangDuangDuang啥只生成了兩行資料就結束了。 再試一次變3行了,再試還是兩行。。

    仔細研究下發現生成了一定資料之後,確實會出現沒有資料可生成的情況。。 例如:

    數獨

    恩,我試著換一種生成順序:按9宮格來生成看看能不能提高成功率

    數獨

    恩,數字是多了點,試著迴圈了100次成功了1次,成功率1%。。(中間還試了下先從四個角上和中間的9宮格開始生成,再生成其他格子的,雖然生成的數字會多一點但成功率還是差不多)

    看來還是得從根源上解決問題。

    回想我們生成值的時候是從多個可選值中選取一個插入,這個可選值會逐漸減少,直到一個值都沒有。 所以當我們沒有值可取的時候可以考慮做一個回退,退到上一步去選擇一另一個值,那我們這一步的可選值就有可能增加。(有點像蝴蝶效應)

    addCacheData(i, j, validData) {
        this.beforeGridDataList.unshift({row: i, col: j, validData});
        if (this.beforeGridDataList.length > this.cacheLength) {
            this.beforeGridDataList.length = this.cacheLength;
    
        }
        // console.log("this.beforeGridDataList", JSON.parse(JSON.stringify(this.beforeGridDataList)))
    
    };
    
    rollBack() {
    
        if (this.beforeGridDataList.length > 0) {
            // console.warn("rollBack", JSON.parse(JSON.stringify(this.beforeGridDataList)))
            // 從上一步的可選值裡取一個
            let beforeGridData = this.beforeGridDataList.shift();
    
            let beforeValidList = beforeGridData.validData.list;
    
            if (beforeValidList.length > 0) {
                let data = beforeValidList[common.getRandom(beforeValidList.length)];
                beforeValidList.splice(beforeValidList.indexOf(data), 1);
                return {
                    row: beforeGridData.row,
                    col: beforeGridData.col,
                    validData: {data, list: beforeValidList}
                }
    
            } else {
                return this.rollBack();
    
            }
        } else {
            return null;
        }
    };
    複製程式碼

    上述程式碼中建立了一個cache陣列儲存上次的可選值,如果哪一步沒有值可以生成了就去上一步找,再沒有就再向上,直到找到後從那一步重新開始生成。

    我們如果把這個cache陣列的長度設為3,試一下成功率

    數獨

    測試10次去掉最高最低值,生成一張地圖平均20.75次

    如果設定成6

    數獨

    恩平均6.125次,已經是可接受的值了(在往上設定的話發現影響並不大,而且對效能的影響可能遠大過成功率的提升,所以這個長度預設設了6)

    至此生成資料已經差不多了,但考慮到可能有些特殊情況還是會生成超過20次,所以我預先存了幾個生成成功的資料,當失敗次數大於20次的時候會直接去取這些資料。

  3. 隱藏掉一些資料

    這步比較簡單直接貼程式碼

    _hideSomeGridData(){
        for (let i = 0; i < 9; i++) {
            let rowList = [];
            let hideCount = common.getRandom(...this.level.hideGridRange);
            for (let j = 0; j < hideCount; j++) {
                let unAvailableList = this.gridList[i].filter((item, index) => {
                    return !item.isAvailable;
    
                });
                unAvailableList[common.getRandom(unAvailableList.length)].isAvailable = true;
    
            }
            this.gridList.push(rowList);
        }
    },
    複製程式碼

以後要加的功能

第一點當然是改UI啦(苦笑),畢竟我要不說誰能知道當初我是像照著那個動效做的。。 其他的話應該會考慮增加一些難度級別,其實現在已經有了,就是還沒加資料測試,可能會想做點不一樣的。

比如說用圖片做9宮格。。。

數獨

結語

恩套路一波:本人技術水平有限,時間也比較倉促,寫得不好的地方歡迎指出。程式碼已經上傳到github了:有需要的可以star一下!

相關文章