1

I am trying to build Minesweeper with Javascript. For that, I need a function that will return an array of "cells" that I then can use as ID's to declare as "mined". It should end up looking like this: ["cell-4-27", "cell-14-18", "cell-9-13", ...]. The first random number is the row where the cell is and the second one is the column. There's only 16 rows and 30 columns, that's the why of the numbers.

The array needs to have a length of 99 and not contain any duplicates. However, this function returns some duplicate values purely due to randomness. I have tried deleting the duplicates but that reduces the number of mines on the board.

How can I make it so that "minedCells" is pushed values without duplicates until it reaches a number of 99?

function assignMines() {
    let randoms = {
        rows: [],
        columns: []
    };

    let minedCells = [];

    for (let i = 0; i < 99; i++) {
        randoms.rows.push(Math.floor((Math.random() * 16) + 1));
        randoms.columns.push(Math.floor((Math.random() * 30) + 1));
        minedCells.push("cell-" + randoms.rows[i] + "-" + randoms.columns[i])
    }

    return minedCells;

}
Alberto Vilches
  • 303
  • 1
  • 5
  • 16

3 Answers3

3

You could check if the value is already in the array before pushing it:

function assignMines() {
  let randoms = {
    rows: [],
    columns: []
  };
    
  let minedCells = [];

  for (let i = 0; i < 99; i++) {
    let el = "cell-" + Math.floor((Math.random() * 16) + 1) + "-" + Math.floor((Math.random() * 30) + 1);
    if (minedCells.includes(el)) {
   i--;
    } else {
      minedCells.push(el);
    }
  }
    console.log(minedCells);
}
assignMines();
dikuw
  • 1,134
  • 13
  • 22
  • 1
    Nice answer, but shuffling could be a better solution: this snippet has a theoretical possibility of creating an *infinite* (more accurately, *infinitely* long) loop... Consider that the random generator returns `0` 10000 times, even when the probability of such scenario is astronomically low. But again, nice answer & +1, though. – FZs Sep 26 '19 at 20:21
1

I think you are starting this off on the wrong foot.

  1. Make sure you can define all the states of a cell:

    • mined
    • flagged
    • revealed
    • hidden
  2. Make sure you can display these states (use manually defined maps to test)

  3. Make sure you can display correct numbers on the revealed squares that are near a mined square

  4. Just after you have solved first three problems, start thinking of how to generate a map with mined cells.

At the moment, the data structure you are using is not even close to optimal for solving the first three problems, especially the 3rd problem.

I would make the data structure as a two-dimensional array/collection of cell objects, each cell object will have an attribute that defines its state (later on you may want to optimise this to just using numbers that represent the states of the cell).

Still if you insist to use the data structure you have chosen so far:

let randoms = {
    rows: [],
    columns: []
};
let minedCells = [];

function isMined(row, column) {
    return minedCells.indexOf("cell-"+ row + "-" + column)>-1;
}

function assignMine(row, column) {
    randoms.rows.push(row);
    randoms.columns.push(column);
    minedCells.push("cell-" + row + "-" + column);
}

function assignMines(rows, columns, minesCount) {
    var row, column;
    let i = 0;
    while (i<minesCount) {
        row = Math.floor((Math.random() * rows) + 1);
        column = Math.floor((Math.random() * columns) + 1);
        if (!isMined(row, column)) {
            assignMine(row, column)
            i++;
        }
    }
}

assignMines(16, 30, 99);
console.log(minedCells);
console.log(randoms);
Zlatin Zlatev
  • 3,034
  • 1
  • 24
  • 32
1

As said in the comments, create an array of all values, then shuffle it

First, see this answer to another question about shuffling an array.

...All read up? Cool. You can change your data structure to be a single array (instead of separate rows and columns arrays) so one of those shuffle() functions can be easily plugged in.

Also mentioned by others here, you will want to support multiple cell states. You don't need to define them all in the beginning, but you'll need some grouping of data that represents a cell - preferably all in one place in your code. For example, see createCell() in the snippet below...

I don't see an immediate need for it, but you might want to identify a cell using its row and/or column number, so I've included an extra example function called buildTable() to demonstrate how to do that (and to see the cells), just in case.

// Mostly from https://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array
const shuffle = (a) => {
  for (let i = a.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1))
    ;[a[i], a[j]] = [a[j], a[i]]
  }
  return a
}

const createCell = (isMine = false) => ({
  isMine,
  flagged: false,
  revealed: false,
})

const buildCells = (width, height, mineCount = 1) => {

  // Create a single array
  const isMineArray = new Array(width * height)

  // Place "mines" at the start of the cells array
  for (let i=0; i<mineCount; i++) isMineArray[i] = true

  // Randomize mines
  shuffle(isMineArray)

  // Map booleans to fully-fledged cell objects
  return isMineArray.map(createCell)
}

// ---

// Example usage - Visualize the random cell data by building a <table>
const buildTable = (tableSelector, width, height, mineCount) => {

  // Run our function from above to make the cells array
  const cells = buildCells(width, height, mineCount)

  // Get (and empty) the table
  const tableElement = document.querySelector(tableSelector)
  while (tableElement.firstChild) {
    tableElement.firstChild.remove()
  }

  // Put cells (and rows) into the table
  let rowElement
  cells.forEach((cell, idx) => {

    // Here's how to get col and row as separate indexes, if you need them
    const colIdx = idx % width
    const rowIdx = (idx - colIdx) / width

    // If first cell in the row, add a new <tr>
    if (colIdx === 0) {
      rowElement = document.createElement('tr')
      tableElement.appendChild(rowElement)
    }
    
    // Add a <td> w/ classes for visual effect
    const tdElement = document.createElement('td')
    tdElement.classList.add('cell')
    tdElement.textContent = `${colIdx}, ${rowIdx}`
    if (cell.isMine) tdElement.classList.add('mine')
    rowElement.appendChild(tdElement)
  })
}

buildTable('.js-table', 4, 5, 6)
.cell {
  border: 1px solid black;
}

.mine {
  background: red;
}
<table class="js-table"></table>
BCDeWitt
  • 4,540
  • 2
  • 21
  • 34