簡體   English   中英

如何修復掃雷艇中超出的最大調用堆棧大小

[英]How to fix Maximum call stack size exceeded in minesweeper

我今天使用純 js 和 css 制作了一個掃雷艇。 單擊一個塊時,使用遞歸打開其他塊。 首先,我將它用於10x10板。 它工作得很好。 但是現在當我制作一個50x50板子時。 它給出了錯誤

未捕獲的 RangeError:超出最大調用堆棧大小。

這是我的完整代碼。 它很多,但您只需專注於遞歸調用的openBlock函數。 50x50棋盤上只有 10 個地雷。 因此,在幾乎所有情況下,除了地雷之外,所有區塊都應該開放。 但是由於錯誤,某些塊沒有打開。

 // -1 => mine // 0 => a block without touching any mine // 1 => block touching 1 mine // etc //Helper Methods //Short for querySelector and querySelectorAll const qs = str => document.querySelector(str); const qsa = str => document.querySelectorAll(str); //To create an element const ce = ({ tag, style, ...rest }) => { const element = document.createElement(tag); if (rest) { for (let k in rest) { element[k] = rest[k]; } } return element; }; const main = qs("#main"); //Main variables let len, wid, mines, blocks; let isPlaying = true; //Helping Data let touchingBlockArr = [ [1, 0], [0, 1], [1, 1], [1, -1] ]; touchingBlockArr = touchingBlockArr.concat( touchingBlockArr.map(x => x.map(a => -a)) ); //Object to assign colors for different numbers. const colorObj = { "-1": "red", 0: "gray", 1: "blue", 2: "orange", 3: "green", 4: "purple", 5: "maroon" }; //Function to create new game. function newGame(l, w, m = 10) { len = l; wid = w; mines = m; main.innerHTML = ""; game = []; blocks = []; createBoard(); } //Create board function createBoard() { for (let i = 0; i < len; i++) { let tr = ce({ tag: "tr" }); blocks.push([]); for (let j = 0; j < len; j++) { let td = ce({ className: "block", tag: "td", onclick: onBlockClick, id: `${i},${j}` }); tr.appendChild(td); td.id = `${i},${j}`; blocks[blocks.length - 1].push(td); } main.appendChild(tr); } addMines(); setValues(); } //Adding Mines function addMines() { let addedMines = []; for (let i = 0; i < mines; i++) { let str, randX, randY; //To avoid repition of mines on same block. do { randX = Math.floor(Math.random() * wid); randY = Math.floor(Math.random() * len); str = `${randX},${randY}`; } while (addedMines.includes(str)); addedMines.push(str); blocks[randX][randY].classList.add("mine"); blocks[randX][randY].setAttribute("data-value", -1); } } //Set Numbers for each block function setValues() { for (let i = 0; i < len; i++) { for (let j = 0; j < len; j++) { let val; let tile = +blocks[i][j].dataset.value; if (tile !== -1) { val = touchingBlockArr.filter(([y, x]) => { if (blocks[i + y] && blocks[i + y][j + x]) { return +blocks[i + y][j + x].dataset.value === -1; } }).length; } val = val === undefined ? -1 : val; blocks[i][j].setAttribute("data-value", val); blocks[i][j].style.color = colorObj[val]; } } } function openSingleBlock(td) { let val = +td.dataset.value; if (val === -1) { } else { td.innerHTML = val || ""; } td.classList.add("opened"); } //When a left mouse button is clicked function onBlockClick() { if (this.classList.contains("flagged")) return false; let val = +this.dataset.value; //If mine is clicked. if (val === -1) { openSingleBlock(this); } //Empty block else if (val === 0) { openBlock(this); openSingleBlock(this); } //For blocks touching mines. else { openSingleBlock(this); } } //A function which open the blocks recursively function openBlock(td) { const [x, y] = td.id.split(",").map(Number); //If the block is not empty then don't proceed further. if (+td.dataset.value !== 0) return false; let touchingBlocks = touchingBlockArr.map(([dx, dy]) => [x + dx, dy + y]); openSingleBlock(td); touchingBlocks.forEach(([x, y]) => { //To checks if blocks are not out of range if (blocks[x] === undefined) return false; if (blocks[x][y] === undefined) return false; let val = +blocks[x][y].dataset.value; let td = blocks[x][y]; //Not a mine if (val !== -1) { //Not touching mine and not opened and not flagged. if ( val === 0 && !td.classList.contains("opened") ) { openBlock(td); } //Touching a mine else { openSingleBlock(td); } } }); } newGame(50, 50);
 body { font-family: cursive; } .block { height: 10px; width: 10px; text-align: center; border: 1px solid black; background-color: lightgray; filter: brightness(0.8); cursor: pointer; font-size: 0.25rem; box-shadow: 1px 1px c10px black; background-size: contain; } .block:hover { filter: brightness(1); } .opened { background-color: rgb(255, 255, 255); filter: brightness(1); } #main { border-collapse: collapse; } .opened.mine { background-image: url(mine.jpg); background-size: contain; } .flagged { background-image: url(flag.png); background-size: contain; }
 <table id="main"></table>

如果您有任何提高代碼性能的技巧,請將其添加到您的答案中。

通常,解決由於遞歸引起的堆棧溢出的最簡單方法是不使用遞歸。

在這種情況下,您可以使用以下算法:

當用戶點擊一個空塊時(這里,“空塊”是指沒有地雷和相鄰地雷的塊):

  1. 將塊推送到空堆棧
  2. 當堆棧非空時:
    1. 從堆棧中彈出頂部項目
    2. 如果該項目尚未打開:
      1. 將項目標記為打開
      2. 檢查物品的鄰居 - 將任何空的、未打開的鄰居推入堆棧,並將任何具有相鄰地雷的非地雷鄰居標記為打開

這是該算法的核心部分:

function openBlocks(startingBlock) {
    let blocksToOpen = [startingBlock];

    while (blocksToOpen.length) {
        let nextBlock = blocksToOpen.pop();

        if (!nextBlock.classList.contains("opened")) {
            // openBlock returns an array of empty neighbors that are not
            // yet open
            let additionalBlocksToOpen = openBlock(nextBlock);

            if (additionalBlocksToOpen.length) {
                blocksToOpen = [...blocksToOpen, ...additionalBlocksToOpen];
            }
        }
    }
}

請參閱下面的完整解決方案。

僅供參考,我認為如果您使用普通對象來表示游戲數據並且僅在需要更改其中的一部分(顯示塊等)時才觸摸 DOM,那么運行速度會快得多。 由於各種原因,DOM 是出了名的慢。

 // -1 => mine // 0 => a block without touching any mine // 1 => block touching 1 mine // etc //Helper Methods //Short for querySelector and querySelectorAll const qs = str => document.querySelector(str); const qsa = str => document.querySelectorAll(str); //To create an element const ce = ({ tag, style, ...rest }) => { const element = document.createElement(tag); if (rest) { for (let k in rest) { element[k] = rest[k]; } } return element; }; const main = qs("#main"); //Main variables let len, wid, mines, blocks; let isPlaying = true; //Helping Data let touchingBlockArr = [ [1, 0], [0, 1], [1, 1], [1, -1] ]; touchingBlockArr = touchingBlockArr.concat( touchingBlockArr.map(x => x.map(a => -a)) ); //Object to assign colors for different numbers. const colorObj = { "-1": "red", 0: "gray", 1: "blue", 2: "orange", 3: "green", 4: "purple", 5: "maroon" }; //Function to create new game. function newGame(l, w, m = 10) { len = l; wid = w; mines = m; main.innerHTML = ""; game = []; blocks = []; createBoard(); } //Create board function createBoard() { for (let i = 0; i < len; i++) { let tr = ce({ tag: "tr" }); blocks.push([]); for (let j = 0; j < len; j++) { let td = ce({ className: "block", tag: "td", onclick: onBlockClick, id: `${i},${j}` }); tr.appendChild(td); td.id = `${i},${j}`; blocks[blocks.length - 1].push(td); } main.appendChild(tr); } addMines(); setValues(); } //Adding Mines function addMines() { let addedMines = []; for (let i = 0; i < mines; i++) { let str, randX, randY; //To avoid repition of mines on same block. do { randX = Math.floor(Math.random() * wid); randY = Math.floor(Math.random() * len); str = `${randX},${randY}`; } while (addedMines.includes(str)); addedMines.push(str); blocks[randX][randY].classList.add("mine"); blocks[randX][randY].setAttribute("data-value", -1); } } //Set Numbers for each block function setValues() { for (let i = 0; i < len; i++) { for (let j = 0; j < len; j++) { let val; let tile = +blocks[i][j].dataset.value; if (tile !== -1) { val = touchingBlockArr.filter(([y, x]) => { if (blocks[i + y] && blocks[i + y][j + x]) { return +blocks[i + y][j + x].dataset.value === -1; } }).length; } val = val === undefined ? -1 : val; blocks[i][j].setAttribute("data-value", val); blocks[i][j].style.color = colorObj[val]; } } } function openSingleBlock(td) { let val = +td.dataset.value; if (val === -1) { } else { td.innerHTML = val || ""; } td.classList.add("opened"); } function openBlocks(startingBlock) { let blocksToOpen = [startingBlock]; while (blocksToOpen.length) { let nextBlock = blocksToOpen.pop(); if (!nextBlock.classList.contains("opened")) { // openBlock returns an array of empty neighbors that are not // yet open let additionalBlocksToOpen = openBlock(nextBlock); if (additionalBlocksToOpen.length) { blocksToOpen = [...blocksToOpen, ...additionalBlocksToOpen]; } } } } //When a left mouse button is clicked function onBlockClick() { if (this.classList.contains("flagged")) return false; let val = +this.dataset.value; //If mine is clicked. if (val === -1) { openSingleBlock(this); } //Empty block else if (val === 0) { openBlocks(this); } //For blocks touching mines. else { openSingleBlock(this); } } function alreadyOpened(td) { return td.classList.contains('opened'); } //A function which open the blocks recursively function openBlock(td) { let blocksToOpen = []; const [x, y] = td.id.split(",").map(Number); //If the block is not empty then don't proceed further. if (+td.dataset.value !== 0) return false; let touchingBlocks = touchingBlockArr.map(([dx, dy]) => [x + dx, dy + y]); openSingleBlock(td); touchingBlocks.forEach(([x, y]) => { //To checks if blocks are not out of range if (blocks[x] === undefined) return false; if (blocks[x][y] === undefined) return false; let val = +blocks[x][y].dataset.value; let td = blocks[x][y]; //Not a mine if (val !== -1) { //Not touching mine and not opened and not flagged. if ( val === 0 && !alreadyOpened(td) ) { blocksToOpen.push(td); } //Touching a mine else { openSingleBlock(td); } } }); return blocksToOpen; } newGame(50, 50, 20);
 body { font-family: cursive; } .block { height: 10px; width: 10px; text-align: center; border: 1px solid black; background-color: lightgray; filter: brightness(0.8); cursor: pointer; font-size: 0.25rem; box-shadow: 1px 1px c10px black; background-size: contain; } .block:hover { filter: brightness(1); } .opened { background-color: rgb(255, 255, 255); filter: brightness(1); } #main { border-collapse: collapse; } .opened.mine { background-image: url(mine.jpg); background-size: contain; } .flagged { background-image: url(flag.png); background-size: contain; }
 <table id="main"></table>

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM