2

I have made a simple game of Minesweeper with JavaScript and it's working fine except that when I click on the middle of a big area without mines it doesn't clear that whole area, only the position where I clicked.

There are already other questions for that but the way I made mine check to generate the numbers is (I believe) different so the solution should be made for it more specifically instead of changing the code to look more like what others did.

Here's an image which explains better the situation (with additional colors that don't show in the actual code):

Minesweeper

Blue is where the user clicked and then it should check both vertically and horizontally (dark green) if those positions have 0 mines around to expand (green) until the border (yellow) where the mines (orange) are close enough.

I tried to make the code as readable and easy to understand:

(function() {

  var minesweeper = document.createElement('div');
  var positions = [];
  var playing = true;

  function random(min, max) {
    return Math.floor(Math.random() * (max - min + 1) + min);
  }

  function end() {
    this.onclick = null;
    if ( playing ) {
      playing = false;
      this.style.backgroundColor = 'rgb(255, 0, 0)';
      alert('Game over.');
    }
  }

  function update() {
    this.onclick = null;
    if ( playing ) {
      this.style.backgroundColor = 'rgb(0, 255, 0)';
      this.className = 'safe';
      let mines = 0;
      let element = this.previousElementSibling;
      if ( element ) {
        if ( element.className == 'mine' && this.style.top == element.style.top ) mines++;
        for ( let i = 0; i < 8; i++ ) {
          element = element.previousElementSibling;
          if ( !element ) break;
        }
        if ( element ) {
          if ( element.className == 'mine' && this.style.top != element.style.top ) mines++;
          element = element.previousElementSibling;
          if ( element ) {
            if ( element.className == 'mine' ) mines++;
            element = element.previousElementSibling;
            if ( element )
              if ( element.className == 'mine' && (parseInt(this.style.top) - parseInt(element.style.top)) == 9 ) mines++;
          }
        }
      }
      element = this.nextElementSibling;
      if ( element ) {
        if ( element.className == 'mine' && this.style.top == element.style.top ) mines++;
        for ( let i = 0; i < 8; i++ ) {
          element = element.nextElementSibling;
          if ( !element ) break;
        }
        if ( element ) {
          if ( element.className == 'mine' && this.style.top != element.style.top ) mines++;
          element = element.nextElementSibling;
          if ( element ) {
            if ( element.className == 'mine' ) mines++;
            element = element.nextElementSibling;
            if ( element )
              if ( element.className == 'mine' && (parseInt(element.style.top) - parseInt(this.style.top)) == 9 ) mines++;
          }
        }
      }
      this.innerText = mines;
      if ( minesweeper.querySelectorAll('div.safe').length == 90 ) {
        playing = false;
        alert('Victory.');
      }
    }
  }

  minesweeper.style.backgroundColor = 'rgb(0, 0, 0)';
  minesweeper.style.fontSize = '7vmin';
  minesweeper.style.textAlign = 'center';
  minesweeper.style.userSelect = 'none';
  minesweeper.style.position = 'absolute';
  minesweeper.style.left = 'calc(50vw - 45.5vmin)';
  minesweeper.style.top = 'calc(50vh - 45.5vmin)';
  minesweeper.style.width = '91vmin';
  minesweeper.style.height = '91vmin';

  for ( let i = 0; i < 10; i++ ) {
    for ( let j = 0; j < 10; j++ ) {
      const n = i * 10 + j;
      positions[n] = document.createElement('div');
      positions[n].style.backgroundColor = 'rgb(255, 255, 255)';
      positions[n].style.position = 'absolute';
      positions[n].style.left = (j * 8 + j + 1) + 'vmin';
      positions[n].style.top = (i * 8 + i + 1) + 'vmin';
      positions[n].style.width = '8vmin';
      positions[n].style.height = '8vmin';
      minesweeper.appendChild(positions[n]);
    }
  }

  for ( let i = 0; i < 11; i++ ) {
    const empty = minesweeper.querySelectorAll('div:not(.mine)');
    if ( i == 10 ) {
      for ( let j = 0; j < 90; j++ ) {
        empty[j].onclick = update;
      }
      break;
    }
    const n = random(0, (empty.length - 1));
    empty[n].className = 'mine';
    empty[n].onclick = end;
  }

  document.body.style.margin = '0px';
  document.body.appendChild(minesweeper);

})();

The way I do the checks for the positions around the one where the number goes is with previousElementSibling and nextElementSibling.

phihag
  • 278,196
  • 72
  • 453
  • 469
user7393973
  • 2,270
  • 1
  • 20
  • 58
  • take a look at this [Mouse program in Turbo CPP](https://stackoverflow.com/a/45561564/2521214) it is my ancient attempt for mine sweep under VGA MS-DOS and Turbo C++ so ignore the graphics and mouse stuff ... It solves what it can on its own so human just click only few times to decide what is based on chance. It might inspire you with some ideas ... – Spektre Feb 18 '18 at 09:02

1 Answers1

2

Nice job! I'm hanging for half an hour testing this game :)

To clear the area around the clicked position (if this position has 0 mines around) you could recursively call the update function for all its neighbors.

I've slightly modified your code: created the negihbors array of all non-mine neighbors and called update.call(neighbor) for each of them.

(function() {

    var minesweeper = document.createElement('div');
    var positions = [];
    var playing = true;

    function random(min, max) {
        return Math.floor(Math.random() * (max - min + 1) + min);
    }

    function end() {
        this.onclick = null;
        if (playing) {
            playing = false;
            const mines = minesweeper.querySelectorAll('div.mine');
            for (let mine of mines) mine.style.backgroundColor = 'rgb(255, 0, 0)';
            alert('Game over.');
        }
    }

    function update() {
        this.onclick = null;
        if (playing && !this.className.length) {
            this.style.backgroundColor = 'rgb(0, 255, 0)';
            this.className = 'safe';
            let neighbors = [];
            let mines = 0;
            let element = this.previousElementSibling;
            if (element) {
                if (this.style.top === element.style.top) {
                    if (element.className === 'mine') mines++;
                    else neighbors.push(element);
                }
                for (let i = 0; i < 8; i++) {
                    element = element.previousElementSibling;
                    if (!element) break;
                }
                if (element) {
                    if (this.style.top !== element.style.top) {
                        if (element.className === 'mine') mines++;
                        else neighbors.push(element);
                    }
                    element = element.previousElementSibling;
                    if (element) {
                        if (element.className === 'mine') mines++; else neighbors.push(element);
                        element = element.previousElementSibling;
                        if (element) {
                            if (parseInt(this.style.top) - parseInt(element.style.top) === 9 ) {
                                if (element.className === 'mine') mines++;
                                else neighbors.push(element);
                            }
                        }
                    }
                }
            }
            element = this.nextElementSibling;
            if (element) {
                if (this.style.top === element.style.top) {
                    if (element.className === 'mine') mines++;
                    else neighbors.push(element);
                }
                for (let i = 0; i < 8; i++) {
                    element = element.nextElementSibling;
                    if (!element) break;
                }
                if (element) {
                    if (this.style.top !== element.style.top) {
                        if (element.className === 'mine') mines++;
                        else neighbors.push(element);
                    }
                    element = element.nextElementSibling;
                    if (element) {
                        if (element.className === 'mine') mines++; else neighbors.push(element);
                        element = element.nextElementSibling;
                        if (element) {
                            if (parseInt(element.style.top) - parseInt(this.style.top) === 9 ) {
                                if (element.className === 'mine') mines++;
                                else neighbors.push(element);
                            }
                        }
                    }
                }
            }
            this.innerText = mines;
            if (mines === 0) {
                for (let neighbor of neighbors) update.call(neighbor);
            }
            if (minesweeper.querySelectorAll('div.safe').length === 90) {
                playing = false;
                alert('Victory.');
            }
        }
    }

    minesweeper.style.backgroundColor = 'rgb(0, 0, 0)';
    minesweeper.style.fontSize = '7vmin';
    minesweeper.style.textAlign = 'center';
    minesweeper.style.userSelect = 'none';
    minesweeper.style.position = 'absolute';
    minesweeper.style.left = 'calc(50vw - 45.5vmin)';
    minesweeper.style.top = 'calc(50vh - 45.5vmin)';
    minesweeper.style.width = '91vmin';
    minesweeper.style.height = '91vmin';

    for (let i = 0; i < 10; i++) {
        for (let j = 0; j < 10; j++) {
            const n = i * 10 + j;
            positions[n] = document.createElement('div');
            positions[n].style.backgroundColor = 'rgb(255, 255, 255)';
            positions[n].style.position = 'absolute';
            positions[n].style.left = (j * 8 + j + 1) + 'vmin';
            positions[n].style.top = (i * 8 + i + 1) + 'vmin';
            positions[n].style.width = '8vmin';
            positions[n].style.height = '8vmin';
            minesweeper.appendChild(positions[n]);
        }
    }

    for (let i = 0; i < 11; i++) {
        const empty = minesweeper.querySelectorAll('div:not(.mine)');
        if (i === 10) {
            for (let j = 0; j < 90; j++) {
                empty[j].onclick = update;
            }
            break;
        }
        const n = random(0, (empty.length - 1));
        empty[n].className = 'mine';
        empty[n].onclick = end;
    }

    document.body.style.margin = '0px';
    document.body.appendChild(minesweeper);

})();
Kirill Simonov
  • 8,257
  • 3
  • 18
  • 42
  • Thank you for taking the time on it. That looks to be what I wanted. I haven't analised the code yet but I will look at it better later when I get time. I do understand what you did from your explanation. It's been a long time since I played the real minesweeper but I have a tiny feeling that it shouldn't expand diagonally like [this](https://i.imgur.com/CNGPx4j.png) (not sure). – user7393973 Feb 17 '18 at 14:46
  • @user7393973 yes, I had some doubts about diagonals too. But it seems that in real game it does [extend diagonally](https://imgur.com/VUuPSQC). Anyway, you can simply avoid it by not adding the diagonal neighbors to the `neighbors` array. – Kirill Simonov Feb 17 '18 at 17:01