user7393973
user7393973

Reputation: 2440

Minesweeper expansion algorithm

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.

Upvotes: 3

Views: 1287

Answers (1)

Kirill Simonov
Kirill Simonov

Reputation: 8481

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);

})();

Upvotes: 2

Related Questions