How to quickly write a minesweeper game using native JS

1. Draw the game area


First create a 16*16 two-dimensional array. After double-layer traversal, the first layer creates the ul label, and the second layer creates the button label. Why use the button tag, because the button tag has its own click effect, and no additional settings are required. After rendering, add CSS styles and the game area is ready. Using an array to render the game area, it's easy to identify, search, and modify.
//Quickly generate a 16*16 two-dimensional array
let buttonArr = Array(16).fill('').map(()=>Array(16).fill({}))
// render game area function
const renderDiv = () => {
document.querySelector('div').innerHTML = ''
buttonArr.forEach((item, index) => {
let ul = document.createElement('ul')
//Add custom attribute y to ul tag
ul.dataset.y = index
item.forEach((item2, index2) => {
let button = document.createElement('button')//traverse the array and draw the chessboard
//Add a custom attribute x to the button label, which is used as a coordinate
button.dataset.x = index2
if (item2.num === 10) {
//Add a custom attribute to the mine element for easy identification
button.dataset.z = 10
//When writing, you can render the mine first, and then comment it out after writing
// button.classList.add('active')
} else {
item2.num = 0
}
ul.appendChild(button)
})
document.querySelector('div').appendChild(ul)
})
}
renderDiv()

CSS
* {
margin: 0;
padding: 0;
box-sizing: border-box;
list-style: none;
}
div {
width: 500px;
margin: 50px auto;
padding: 5px;
border: 5px solid black;
}
ul {
display: flex;
height: 30px;
}
button {
width: 30px;
height: 30px;
background-color: #c0c0c0;
}
.bgc1 {
background-color: white;
}
.bgc2 {
background-color: black;
}
.bgc3 {
background: url(./pngsucai_1307487_8c9867.png)no-repeat;
background-color: #c0c0c0;
background-size: 100%;
}
.active {
background: url(./Snipaste_2022-06-12_16-24-48.jpg) no-repeat;
background-size: 100%;
}
p {
position: absolute;
top: 200px;
right: 200px;
font-size: 20px;
}



Left mouse button click

Right mouse button tag

Click on the blank grid to quickly clear mines






2. Generate mines


There are a total of 40 mines. The random numbers X and Y are obtained by the Math method, and these two numbers are stored in the array as coordinates. When the length of the array is 40, the duplicates are removed, and then the coordinates are obtained. Until the 40 coordinates are not repeated, it jumps out. cycle. The number of mines can also be adjusted at will. Remember to add a custom attribute to the grid with mines, so that the page labels corresponding to the elements in the array can be easily linked.
//generate mines
const lei = () => {
let leiArr = []
function fn() {
//get random coordinates
const x = Math.floor(Math.random() * 16)
const y = Math. floor(Math. random() * 16)
leiArr.push([y, x])
if (leiArr.length == 40) {
// deduplicate the array
let obj = {}
leiArr.forEach(item => obj[item] = item)
leiArr = Object.values(obj)
}
if (leiArr.length == 40) {
return
}
fn()
}
fn()
return leiArr
}
// render mines
const renderLei = (arr) => {
//Add a data to the object corresponding to the mine for easy identification
arr.forEach(item => {
//Assignment in this place requires a deep copy of the objects in the array, otherwise it will affect the values ​​in other objects
buttonArr = JSON.parse(JSON.stringify(buttonArr))
buttonArr[item[0]][item[1]].num = 10
})
}
renderLei(lei())
renderDiv()


3. Add numbers to each grid, if there are a few mines around it is a few, if there are no mines it is empty.


Traverse all the grids that are not mines, and then get all the grids around each grid, and then determine how many mines are in this grid, and the number of the current grid is the number.
Obtaining a circle of grids is a bit complicated. The logic of obtaining grids is the grids whose X-coordinates differ by 1 or 0 within 1 row above and below the current grid. The grids in the middle area are obtained from the grids in the upper, middle and lower rows. The grids of the first and last row only need to get two rows.
Because the default num value of each grid is 0, the numbers in the grid can be traversed by traversing the surrounding grids, and then if there are mines, the num value is +1, and the traversal accumulation method is used, so that the num value is the corresponding number of mines. . When rendering, just display the num value greater than 0.
// render grid numbers
const renderNum = () => {
buttonArr.forEach((item, i) => {
item.forEach((item02, i02) => {
if (item02.num == 0) {
//Get the tags that are not mines
let ul = document.querySelectorAll('ul')[i]
let btn = ul.querySelectorAll('button')[i02]
//Call the function to get the grid number, pass in 3 parameters, the array element corresponding to the current grid, the current row, and the current grid
getNum(item02, ul, btn)
//Determine whether this grid contains numbers
if (item02.num < 10 && item02.num > 0) {
//Add a custom attribute to the grid with numbers for easy identification
btn.dataset.z = 1
const span = document.createElement('span')
// render numbers into grid
span.innerText = item02.num
span.style.display = 'none'
btn.appendChild(span)
}
}
})
})

}
//get the grid number
const allUl = document.querySelectorAll('ul')
let getNumArr = []
////Get the grid number function
const getNum = (item02, ul, btn) => {
//Create an array to store a circle of grids around the target grid
getNumArr = []
const y = ul.dataset.y
const x = btn.dataset.x
//Call the function to get a circle of grids around the grid, and pass the coordinates of the grid into the parameters
getBox(x, y)
// Traverse the grid in a circle around this grid, if there is a mine, num will be +1
getNumArr.forEach(item1 => {
if (item1.dataset.z == 10) {
item02.num++
}
})
}
//Function to get a circle of grids around the grid
function getBox(x, y) {
//The grid in the first row only needs to select the first two rows
if (y == 0) {
for (let i = 0; i < 2; i++) {
//Select the first two rows of grids
const allButton01 = allUl[i].querySelectorAll('button')
//If the absolute value of the subtraction of the x-coordinates of the two grids is less than or equal to 1, then the two grids are adjacent
let getNumArr02 = Array.from(allButton01).filter(item => Math.abs(item.dataset.x - x) <= 1)
// join the array
getNumArr.push(...getNumArr02)
}
}
//From the second row to the penultimate row, you need to select the upper, middle and lower rows of the grid
else if (y >= 1 && y < 15) {
for (let i = +y - 1; i < +y + 2; i++) {
const allButton02 = allUl[i].querySelectorAll('button')
let getNumArr02 = Array.from(allButton02).filter(item => Math.abs(item.dataset.x - x) <= 1)
getNumArr.push(...getNumArr02)
}
} else { //The last row, select two rows of grids to traverse
for (let i = 14; i < 16; i++) {
const allButton03 = allUl[i].querySelectorAll('button')
let getNumArr02 = Array.from(allButton03).filter(item => Math.abs(item.dataset.x - x) <= 1)
getNumArr.push(...getNumArr02)
}
}
}
renderNum()


4. Mouse click event


After the numbers are rendered, the next step is the click event. There are two click events, the left mouse button click and the right mouse button to set the flag.
First, the styles of mines and numbers are hidden, and mouse clicks are actually a process of adding styles.
When left-clicking, you must first determine whether the click is a mine. If it is a mine, the game is over. Instead of mine just add a CSS style to it and have the numbers show up.
If you click on a blank grid, you need to display all the non-mine grids connected to this blank grid, which is the effect of clicking on one to display a large area.
If you click on a number, it will display this grid.
The right mouse button is very simple, and you can directly add CSS styles to play the effect of a flag. But to judge, the flags cannot be placed in the checked grids, and only red flags can be added to the unclicked grids.
//click event
let allArr = [] //declare an array to judge the winner
allUl.forEach(buttons => {
buttons.querySelectorAll('button').forEach(item => {
item.addEventListener('click', function () {
//click effect
this.classList.add('bgc1')
//If there is a number in this grid, display it
if (this.querySelector('span')) {
this.querySelector('span').style.display = 'block'
}
allArr.push(this)
//Judgment, if the click is a mine, the game is over
if (item.dataset.z == 10) {
item.classList.add('active')
setTimeout(function () {
alert('game failed')
location.reload()
}, 200)
}
//Only blank grids have no custom z attribute, judge if the clicked blank grid
if (!item.dataset.z) {
const num0Arr = []
num0(item)
//There must be no mines around the blank grid, and directly let these grids display the class content
function num0(item) {
getNumArr = []
const buttons = item.parentNode
const y = buttons.dataset.y
const x = item.dataset.x
//Call the function to get the surrounding grid again
getBox(x, y)
getNumArr.forEach(itemBtn => {
//Click on the blank grid, it will automatically display all the grids around it
if (itemBtn.dataset.z != 10) {
itemBtn.classList.add('bgc1')
}
if (itemBtn.querySelector('span')) {
itemBtn.querySelector('span').style.display = 'block'
}
//Add these grids to the total array
allArr.push(itemBtn)
if (!itemBtn.dataset.z) {
//If there are blank cells in a circle around the blank cells, add them to the num0 array, and loop the click event again later
num0Arr.push(itemBtn)
}
})
}
//Add a function to display the class content to the blank space around the blank space
function clickNum0() {
//num0Arr contains all the blank cells in the 9 cells around the clicked blank cell, newNum0Arr is to remove all the blank cells of itself
const newNum0Arr = num0Arr. filter(item2 => item2 != item)
newNum0Arr.forEach(item02 => {
//Call the function that displays the content of the surrounding circle on other blank cells again, thus forming a click on a blank cell,
//If there is a lot of blank space around this grid, it can display the effect of a large area.
num0(item02)
})
}
clickNum0()
}
// deduplicate the total array
const newAllArr = [...new Set(allArr)]
//Filter, exclude grids that are mines
const newAllArr02 = newAllArr.filter(item => item.dataset.z != 10)
//There are 256 grids and 40 mines in total. If the length of the array reaches 216, the victory can be determined.
if (newAllArr02.length == 216) {
alert('Game victory!')
location.reload()
}
})
//Right mouse click event, used to insert chess pieces
item.addEventListener('contextmenu', function () {
if (!this.classList.contains('bgc3') && !this.classList.contains('bgc1')) {
this.classList.add('bgc3')
} else {
this.classList.remove('bgc3')
}

})
})
})



The click event of the blank squares is where this game is troublesome. But as long as it is clear, there must be no mines in the circle around the blank grid. Clicking on a blank grid is equivalent to clicking on all the 9 surrounding grids. It will not be difficult to write code based on this logic.
- Click the right mouse button in the browser, the page will pop up a menu, we need to block this.
// right mouse button to block the menu
document.oncontextmenu = function (event) {
if (window.event) {
event = window.event;
}
try {
var the = event.srcElement;
if (!((the.tagName == "INPUT" && the.type.toLowerCase() == "text") || the.tagName == "TEXTAREA")) {
return false;
}
return true;
} catch (e) {
return false;
}
}

At this point, the entire game has been written, with a total of more than 200 lines of code.

Related Articles

Explore More Special Offers

  1. Short Message Service(SMS) & Mail Service

    50,000 email package starts as low as USD 1.99, 120 short messages start at only USD 1.00