Search code examples
javascriptrecursionminesweeper

javaScript minesweeper empty fields reveal


I am trying to create a minesweeper game using html css and js. I created the board, the fields(div elements), planted the bombs, every field "knows" his position (x,y) (the x and y coordinates are saved as data attributes of the div), "knows" if he is a bomb and the number of bombs nearby. I managed to create onclick event to reveal if the tile is a bomb or to show the number of bombs nearby. Now my problem is that if i click on a tile which has no bombs nearby (empty tile) i want to reveal all empty tiles which are in the surrounding. So the next function is called if i click on an empty field:

function zero(x, y) {
    for (var i = x - 1; i <= x + 1; i++) {
        for (var j = y - 1; j <= y + 1; j++) {
            $('.tile[row-d=' + i + '][col-d=' + j + ']').text($('.tile[row-d=' + i + '][col-d=' + j + ']').attr('bomb_number')); // set the field text = bomb -number nearby
        }
    }
}

With this i reveal all the surrounding fields near the clicked empty field, so i reveal just 9 fields. I want to reveal all empty fields nearby not just 9. I tried to use recursive function but the page crashed. So anybody can help me with a recursive function which could solve this. Thank you. (As you can see i am also using jQuery)


Solution

  • The basic rule is:

    • If you find a neighboring field that is next to a bomb, stop searching
    • If a neighboring field has no bomb, keep expanding to the new field's neighbors

    In code:

    function zero(x, y) {
        for (var i = x - 1; i <= x + 1; i++) {
            for (var j = y - 1; j <= y + 1; j++) {
                var neighbor = $('.tile[row-d=' + i + '][col-d=' + j + ']');
                var bombNr = neighbor.attr('bomb_number');
    
                neighbor.text(bombNr);
    
                if (bombNr === "0") zero(i, j);
            }
        }
    }
    

    Edit: a quick & dirty example of the recursion pattern:

    // The recursive function
    const handleInput = i => {
      if (tapped.has(i)) return;
      
      // Count the bombs in neighboring tiles
      const bombs = neighbors(i)
        .map(j => board[j])
        .filter(v => v === x)
        .length;
    
      // Store the count so we can render it
      tapped.set(i, bombs);
      
      // If there are no bombs, handle all the neighbors'
      // as well.
      if (bombs === 0) {
        neighbors(i).forEach(handleInput);
      }
    };
    
    // Game state
    const x = "x";
    const board = [
      x, 0, 0, 0, 0,
      x, 0, 0, 0, 0,
      x, x, x, 0, 0,
      0, 0, 0, 0, 0,
      0, 0, x, 0, 0
    ];
    
    const tapped = new Map();
    
    // Quick & dirty render method to visualize what happens
    const render = board => {
      const el = document.querySelector(".board")
      
      el.innerHTML =
        board
          .map((v, i) => tapped.has(i) ? tapped.get(i) : v)
          .map(txt => `<div>${txt}</div>`)
          .join("");
        
      Array.from(el.children).forEach((c, i) => {
        c.addEventListener("click", () => {
          
          if (board[i] === x) return console.log("Boom!");
          
          handleInput(i);
          
          render(board);
          
        });
        
        c.classList.toggle("tapped", tapped.has(i));
      });
    }
    
    const row = i => i / 5 << 0;
    const col = i => i % 5;
    const neighbors = i => {
      const top =    [ -6, -5, -4 ];
      const right =  [ -4,  1,  6 ];
      const bottom = [  4,  5,  6 ];
      const left =   [ -6, -1,  4 ];
      
      const ds = new Set([...top, ...right, ...bottom, ...left]);
      const remove = d => ds.delete(d);
      
      if (row(i) === 0) top.forEach(remove);
      if (row(i) === 4) bottom.forEach(remove);
      if (col(i) === 0) left.forEach(remove);
      if (col(i) === 4) right.forEach(remove);
    
      return [...ds].map(d => d + i);
    };
    
    render(board);
    .board {
     width: 100px;
     height: 100px;
    }
    
    .board > div {
      width: 20px;
      height: 20px;
      border: 1px solid black;
      box-sizing: border-box;
      display: inline-block;
      text-align: center;
    }
    
    .board > div.tapped {
      background: yellow;
    }
    <div class="board"></div>