2

We've been told to build a Minesweeper game on a 3x3 grid using only JavaScript that can be played in the VS studio console.

With some help from a higher level student, I have a dynamic 2d array, I kind of understand how it works up until the line

for (let j = 0; j < gridSize; j++)

I get lost there:

const generateGrid = (gridSize) => { //NOTE - Creates a 2D array of objects
    let grid = []; //Empty array
    for (let i = 0; i < gridSize; i++) {
        grid.push([]); //Pushes to empty array
        for (let j = 0; j < gridSize; j++) {
            grid[i][j] = [
            ];
       }
    }    
     return grid;
};
let grid = generateGrid(9); //Sets grid size i.e. 3 = 3x3 Grid, 9 = 9x9 Grid etc

I need a boolean function for my random mine generator so I've come up with below:

const isMine = () => Math.round(Math.random());//Boolean, Math.random returns number between 0.0 and 1.0, Math.round rounds 0.5 > to 0 or 0.5 < to 1
let placeMine = isMine();
console.log(placeMine)//NOTE - Testing Boolean returns 0 or 1

Someone had mentioned flattening the array before the placing of the mines so I think I figured out how to do that but I don't understand why I want to do this:

const flatten = (arr) => { //Flatten 2d array to 1d
    let flattenArr = [];
    for (let i = 0; i < arr.length; i++) {
      if (Array.isArray(arr[i])) {
        flattenArr = flattenArr.concat(flatten(arr[i]));
      } else {
        flattenArr.push(arr[i]);
      }
    }
    return flattenArr;
  };

console.log(flatten([grid]))

console.table(grid) //Prints 2D array to table

The dynamic board works fine, it prints to console.table a square array of [] whichever size you set it to as expected, the boolean function works as expected, I think the flatten function works? but its hard to tell, it turns the array into a single [].

Basically where do I go next? I've got a board, do I need to create cells as well? I know I need to push mines to the array but I have no clue how, someone mentioned you can create a list to store the mines locations and a function checks the list whenever the user input coordinates? I've been told to use Readline to prompt the user with a rl.question for entering user input i.e. the grid coordinates of cell they wish to reveal?

David Makogon
  • 69,407
  • 21
  • 141
  • 189
dkeppel
  • 23
  • 3
  • Does this answer your question? [Minesweeper solving algorithm](https://stackoverflow.com/questions/1738128/minesweeper-solving-algorithm) – Shayan May 18 '23 at 05:28

1 Answers1

2

Apparently this is a homework so let's not give you the solution but some guide.

Design your data structure

You got a 2D-array, what next? What do the elements in the arrays represent? Think about in a minesweeper game, what states each cell could be? Each cell can be either mine or not mine, and either unopened, opened, or flagged. So each cell could store an object like this (using typescript syntax here to describe the object structure):

{
   isMine: boolean
   state: "unopened" | "opened" | "flagged"
}

The game state could be represent by a 2D-array of these objects

Initialise the game state

You have already done part of this step, i.e. generate the 2D-array. To go further, when you generate the array, initialise each array element with the above-mentioned object, all with state: "unopened" and isMine: isMine().

Tips: a simpler way to implement isMine() is

const isMine = () => Math.random() < 0.5;

This way you get a real boolean out of the box instead of a number.

Render the game state

Now you have got a model of the game, you need to turn it into human-readable form. That is to implement a function that accept the 2D-array of objects and return a string, which can then be printed to the console by console.log()

const render = (grid) => {
    let output = "";
    //Do your magic
    return output;
}

Identify user input

You've got your game state initialised and able to get rendered. The next step is to let the player interact with it. Think about what a player can do in a minesweeper game:

  1. open a cell
  2. flag a cell

That's it. Now write a function for each action.

const open = (grid,row,column) => {
    //change the state of this cell and potentially other cells according to minesweeper logic
}

const flag = (grid,row,column) => {
   //Change the state of this cell to "flagged"
}

Identify ending condition

Since the game runs in a loop indefinitely, you need to implement a function to check if the game is end (lose or win), like:

const checkEnd = (grid) => {
    //Go through each cells to determine if the game is win or lose according to minesweeper logic
    //return "win" or "lose" or false
}

Construct the main function

The final step is to write the main function that integrate every pieces together. Reading user input is also done here. You are right about using readline. You can use the async version of rl.question to prompt for user input so it can be easily used in a loop within a async function. The goal is to identify the action, row and column, then call the corresponding function.

const main = async () => {
    //prompt for grid size
    //generate grid
    console.log(render(grid));
    let endState = false;
    while(!endState)
    {

        //prompt for player action
        //parse user input to extract the action, row, column
        //call the corresponding function
        endState = checkEnd(grid);
        console.log(render(grid));
    }
    if(endState === "win")
    {
        //print win message
    }
    else
    {
        //print lose message
    }
};

Finally call main(); to start the game!

Error Handling

Congratulations. Your game is running and playable! But let's think more. What if the user open an opened or flagged cell? What if the user flag an opened cell? What if the input coordinates are out of range? etc. You have to think of every possibility that the program could mess up and handle them in your logic.

What's next

This is just a basic, systematic way to build a cli game. There are many places which can be optimised.

For example, doing an O(N^2) check in checkEnd is inefficient. You can make some extra variables to keep track of the game state (e.g. flag counts, opened counts, etc.) and determine win/lose right within open().

One obvious improvement, for example, when the player open a cell which isMine is true, you can immediately mark the game is lose and break the loop. Many more stuff can be done to improve the efficiency, usually involves trading time complexity with space-complexity (make use of more variables to reduce computational time).

Ricky Mo
  • 6,285
  • 1
  • 14
  • 30