Creating Minesweeper using only JavaScript

huangapple go评论67阅读模式
英文:

Creating Minesweeper using only JavaScript

问题

I'll provide translations for the code sections you provided:

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?
英文:

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?

答案1

得分: 2

这是你的翻译:

设计你的数据结构

你有一个二维数组,接下来怎么做?数组中的元素代表什么?想象一下扫雷游戏中,每个单元格可以是什么状态?每个单元格可以是地雷或不是地雷,可以是未打开、已打开或已标记。因此,每个单元格可以存储像这样的对象(这里使用 TypeScript 语法来描述对象结构):

{
   isMine: boolean
   state: "未打开" | "已打开" | "已标记"
}

游戏状态可以用这些对象的二维数组来表示。

初始化游戏状态

你已经完成了这一步的一部分,即生成二维数组。要进一步进行,当你生成数组时,用上面提到的对象初始化每个数组元素,所有元素的state都为"未打开",而isMine都为isMine()

提示:实现isMine()的一个更简单的方法是:

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

这样你就可以直接得到一个真正的布尔值,而不是一个数字。

渲染游戏状态

现在你已经有了游戏的模型,需要将它转化为可读的形式。实现一个接受对象的二维数组并返回一个字符串的函数,然后可以通过console.log()打印到控制台上。

const render = (grid) => {
    let output = "";
    //进行你的操作
    return output;
}

识别用户输入

你已经初始化了游戏状态并能够进行渲染。下一步是让玩家与之交互。想想在扫雷游戏中玩家可以做什么:

  1. 打开一个单元格
  2. 标记一个单元格

就是这样。现在为每个动作编写一个函数。

const open = (grid,row,column) => {
    //根据扫雷逻辑更改此单元格的状态,可能还需要更改其他单元格的状态
}

const flag = (grid,row,column) => {
   //将此单元格的状态更改为"已标记"
}

识别结束条件

由于游戏无限循环运行,你需要实现一个函数来检查游戏是否结束(输或赢),例如:

const checkEnd = (grid) => {
    //遍历每个单元格,根据扫雷逻辑确定游戏输赢
    //返回"赢"、"输"或false
}

构建主函数

最后一步是编写集成所有部分的主函数。在这里完成读取用户输入的操作。你关于使用readline的想法是正确的。你可以使用rl.question的异步版本来提示用户输入,以便在异步函数内部轻松使用它进行循环。目标是识别动作、行和列,然后调用相应的函数。

const main = async () => {
    //提示输入网格大小
    //生成网格
    console.log(render(grid));
    let endState = false;
    while(!endState)
    {

        //提示玩家动作
        //解析用户输入以提取动作、行和列
        //调用相应的函数
        endState = checkEnd(grid);
        console.log(render(grid));
    }
    if(endState === "赢")
    {
        //打印赢消息
    }
    else
    {
        //打印输消息
    }
};

最后调用main();来启动游戏!

错误处理

恭喜你。你的游戏正在运行并且可玩!但是让我们再考虑一下。如果用户打开了一个已打开或已标记的单元格会怎样?如果用户标记了一个已打开的单元格会怎样?如果输入的坐标超出了范围会怎样?等等。你必须考虑到程序可能出错的每种可能性,并在你的逻辑中处理它们。

接下来做什么

这只是构建命令行游戏的基本、系统化方法。有许多地方可以进行优化。

例如,在checkEnd中执行O(N^2)检查是低效的。你可以创建一些额外的变量来跟踪游戏状态(例如旗帜数量、已打开数量等),并在open()中直接确定胜负。还有许多其他方法可以提高效率,通常涉及在时间复杂度和空间复杂度之间进行权衡(利用更多变量以减少计算时间)。

英文:

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: &quot;unopened&quot; | &quot;opened&quot; | &quot;flagged&quot;
}

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: &quot;unopened&quot; and isMine: isMine().

Tips: a simpler way to implement isMine() is

const isMine = () =&gt; Math.random() &lt; 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) =&gt; {
    let output = &quot;&quot;;
    //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) =&gt; {
    //change the state of this cell and potentially other cells according to minesweeper logic
}

const flag = (grid,row,column) =&gt; {
   //Change the state of this cell to &quot;flagged&quot;
}

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) =&gt; {
    //Go through each cells to determine if the game is win or lose according to minesweeper logic
    //return &quot;win&quot; or &quot;lose&quot; 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 () =&gt; {
    //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 === &quot;win&quot;)
    {
        //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).

huangapple
  • 本文由 发表于 2023年5月18日 08:24:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/76276987.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定