Hi, I’m The Bold Tomato. This is the summary and sharing of minesweeper, the second game of “Challenge one game a month”.
Demo address: wanghaida.com/demo/2202-m…
Github Repository: github.com/wanghaida/g…
In order for a game to be fun, in addition to gameplay, design is also necessary. But what if I can’t design? So download the Microsoft Minesweeper from the Microsoft Store and go through the directory, and you find this:
Ho, isn’t this the Sprite we’re familiar with? Not all of them, but the basics. Love love ~
The map
Each square in Sprite is exactly 84×84 pixels, divided by 2 for hd, 42×42 for each square. The game is available in 9×9, 16×16, and 30×16 difficulty levels. Based on 30×16, the size of the game area reaches 1260×672, including some small spacing, the small screen is slightly difficult, so in addition to 9×9 using 84/2 = 42 pixels, Others use 84/2.4 = 35 pixels.
Simply set up the shelves with a grid:
:root {
--base: 2;
--row: 9;
--col: 9;
}
/ / play area
#game {
display: grid;
grid-template-rows: repeat(var(--row), calc(84px / var(--base)));
grid-template-columns: repeat(var(--col), calc(84px / var(--base)));
gap: 2px;
div {
background: url('./images/sprite_state.png') calc(-84px / var(--base)) 0 / calc(1344px / var(--base)) calc(420px/ var(--base)) no-repeat; }}Copy the code
// The game area
const oGame = document.getElementById('game');
const minesweeper = {
/** * Initialize the map */
initMap(row = 9, col = 9, mines = 10) {
// Clear the original map
oGame.innerHTML = ' ';
// Adjust the map layout
document.documentElement.style.setProperty('--base', row > 9 ? '2.4' : '2');
document.documentElement.style.setProperty('--row', row);
document.documentElement.style.setProperty('--col', col);
// Virtual nodes are used to host DOM nodes, which can be added at once
const oFragment = document.createDocumentFragment();
for (let i = 0; i < row; i++) {
for (let j = 0; j < col; j++) {
// Create a coordinate node
const oDiv = document.createElement('div');
// Put coordinate nodes into virtual nodesoFragment.appendChild(oDiv); }}// Put the virtual node into the game areaoGame.appendChild(oFragment); }};// Start 9x9 games by default
minesweeper.initMap();
Copy the code
The following graph is obtained:
CSS uses three variables –base to scale each square, –row to represent rows, and –col to represent columns. The familiar grid system uses the repeat() function to draw trips and columns.
I’m going to add a keyframe animation and timer:
#game {
.state-loading {
background-position: calc(-84px / var(--base)) 0;
animation: state-loading 0.2 s steps(1);
}
.state-closed {
background-position: calc(-84px / var(--base)) 0; }}@keyframes state-loading {
0% {
background-position: calc(-504px / var(--base)) calc(-84px / var(--base));
}
25% {
background-position: calc(-672px / var(--base)) calc(-84px / var(--base));
}
50% {
background-position: calc(-840px / var(--base)) calc(-84px / var(--base));
}
75% {
background-position: calc(-924px / var(--base)) calc(-84px / var(--base));
}
100% {
background-position: calc(-1260px / var(--base)) calc(-84px/ var(--base)); }}Copy the code
const minesweeper = {
mapCount: 0.mapTimer: null.initMap(row = 9, col = 9, mines = 10){...document.documentElement.style.setProperty('--col', col);
// Virtual nodes are used to host DOM nodes, which can be added at once
const oFragment = document.createDocumentFragment();
for (let i = 0; i < row; i++) {
for (let j = 0; j < col; j++) {
// Create a coordinate node
const oDiv = document.createElement('div');
// Put coordinate nodes into virtual nodesoFragment.appendChild(oDiv); }}// Put the virtual node into the game area
oGame.appendChild(oFragment);
// Load the animation
clearInterval(this.mapTimer);
this.mapCount = 0;
this.mapTimer = setInterval(() = > {
// All lines have been traversed
if (this.mapCount >= row) {
// Clear the timer
return clearInterval(this.mapTimer);
}
for (let i = 0; i < col; i++) {
oDiv = oGame.children[this.mapCount * col + i];
// Load the animation style
oDiv.className = 'state-loading';
oDiv.addEventListener('animationend'.function fn() {
oDiv.className = 'state-closed';
oDiv.removeEventListener('animationend', fn);
});
}
// Increase the number of rows traversed
this.mapCount++;
}, 100); }};Copy the code
Theoretically, there is no problem, but maybe because of DOM, when the number of blocks is too many, there will be an obvious delay in loading from left to right, so I changed from adding divs into the game area first and then adding styles to each line to adding styles at the same time:
const minesweeper = {
mapCount: 0.mapTimer: null.initMap(row = 9, col = 9, mines = 10){...document.documentElement.style.setProperty('--col', col);
// Load the animation
clearInterval(this.mapTimer);
this.mapCount = 0;
this.mapTimer = setInterval(() = > {
// All lines have been traversed
if (this.mapCount >= row) {
// Clear the timer
return clearInterval(this.mapTimer);
}
// Virtual nodes are used to host DOM nodes, which can be added at once
const oFragment = document.createDocumentFragment();
for (let i = 0; i < col; i++) {
// Create a coordinate node
const oDiv = document.createElement('div');
// Load the animation style
oDiv.className = 'state-loading';
oDiv.addEventListener('animationend'.function fn() {
oDiv.className = 'state-closed';
oDiv.removeEventListener('animationend', fn);
});
// Put coordinate nodes into virtual nodes
oFragment.appendChild(oDiv);
}
// Put the virtual node into the game area
oGame.appendChild(oFragment);
// Increase the number of rows traversed
this.mapCount++;
}, 100); }};Copy the code
For the convenience of manipulating squares, and for a slightly faster performance, let’s use a map to store the corresponding states of squares. Dom only stores the corresponding coordinates of data:
const minesweeper = {
/** * Game data **@desc Represent the properties * of each square by a two-dimensional array@example* [* [item, item, item], * [item, item, item], * [item, item, item], *] * * item = {* // Whether to open * isOpen: Boolean, * // explode: Boolean, * // explode: Boolean * // explode: Boolean, * // explode: Boolean, */ / flag normal question * sign: string, */ / Type 0: blank, 1-8: number, 9: mine * type: number, *} */
map: []./** ** game status ** loaded: finished, loading: ongoing, over: ongoing */
state: 'loading'./** * Initialize the map */
row: 9.col: 9.mines: 10.initMap(row = 9, col = 9, mines = 10) {
// Game map size
this.map = [];
this.row = row;
this.col = col;
this.mines = row * col === 256 ? 40 : row * col === 480 ? 99 : mines; // 16x16 ? 40 : 30x16 ? 99 : mines;
// Change the game state
this.state = 'loading';
// Number of landmines
document.getElementById('mines').innerHTML = this.mines;
// Game time (simple timer, not shown here).this.mapTimer = setInterval(() = > {
// All lines have been traversed
if (this.mapCount >= row) {
// Status changes
this.state = 'loaded';
// Clear the timer
return clearInterval(this.mapTimer);
}
// Virtual nodes are used to host DOM nodes, which can be added at once
const oFragment = document.createDocumentFragment();
const mapTemp = [];
for (let i = 0; i < col; i++) {
// Create a coordinate node
const oDiv = document.createElement('div'); ./ / coordinates
oDiv.pos = [this.mapCount, i];
/ / square
mapTemp.push({
// Check whether it has been opened
isOpen: false.// Do I recurse
isCheck: false.// Whether there was an explosion
isExplode: false.// flag normal question
sign: 'normal'.// Type 0: blank, 1-8: numbers, 9: mines
type: 0});// Put coordinate nodes into virtual nodes
oFragment.appendChild(oDiv);
}
this.map.push(mapTemp);
// Put the virtual node into the game area
oGame.appendChild(oFragment);
// Increase the number of rows traversed
this.mapCount++;
}, 100); }};Copy the code
Generally ok:
Based on dynamic effect
Add the basic Spaces, numbers, flags and question marks:
.state-flag-down {
background-position: calc(-1260px / var(--base)) calc(-168px / var(--base));
animation: state-flag-down 0.1 s steps(1);
}
.state-flag-up {
background-position: calc(-252px / var(--base)) calc(-252px / var(--base));
animation: state-flag-up 0.1 s steps(1);
}
.state-normal-down {
background-position: calc(-588px / var(--base)) calc(-168px / var(--base));
animation: state-normal-down 0.1 s steps(1);
}
.state-normal-up {
background-position: calc(-924px / var(--base)) calc(-168px / var(--base));
animation: state-normal-up 0.1 s steps(1);
}
.state-question-down {
background-position: calc(-588px / var(--base)) calc(-252px / var(--base));
animation: state-question-down 0.1 s steps(1);
}
.state-question-up {
background-position: calc(-924px / var(--base)) calc(-252px / var(--base));
animation: state-question-up 0.1 s steps(1);
}
.state-0 {
background-position: 0 0;
}
.state-1 {
background-position: calc(-504px / var(--base)) 0;
}
.state-2 {
background-position: calc(-588px / var(--base)) 0;
}
.state-3 {
background-position: calc(-672px / var(--base)) 0;
}
.state-4 {
background-position: calc(-756px / var(--base)) 0;
}
.state-5 {
background-position: calc(-840px / var(--base)) 0;
}
.state-6 {
background-position: calc(-924px / var(--base)) 0;
}
.state-7 {
background-position: calc(-1008px / var(--base)) 0;
}
.state-8 {
background-position: calc(-1092px / var(--base)) 0;
}
.state-9 {
background-position: calc(-336px / var(--base)) 0;
}
@keyframes state-flag-down {
0% {
background-position: calc(-1008px / var(--base)) calc(-168px / var(--base));
}
3333.% {
background-position: calc(-1092px / var(--base)) calc(-168px / var(--base));
}
6666.% {
background-position: calc(-1176px / var(--base)) calc(-168px / var(--base));
}
100% {
background-position: calc(-1260px / var(--base)) calc(-168px/ var(--base)); }}@keyframes state-flag-up {
0% {
background-position: 0 calc(-252px / var(--base));
}
3333.% {
background-position: calc(-84px / var(--base)) calc(-252px / var(--base));
}
6666.% {
background-position: calc(-168px / var(--base)) calc(-252px / var(--base));
}
100% {
background-position: calc(-252px / var(--base)) calc(-252px/ var(--base)); }}@keyframes state-normal-down {
0% {
background-position: calc(-336px / var(--base)) calc(-168px / var(--base));
}
3333.% {
background-position: calc(-420px / var(--base)) calc(-168px / var(--base));
}
6666.% {
background-position: calc(-504px / var(--base)) calc(-168px / var(--base));
}
100% {
background-position: calc(-588px / var(--base)) calc(-168px/ var(--base)); }}@keyframes state-normal-up {
0% {
background-position: calc(-672px / var(--base)) calc(-168px / var(--base));
}
3333.% {
background-position: calc(-756px / var(--base)) calc(-168px / var(--base));
}
6666.% {
background-position: calc(-840px / var(--base)) calc(-168px / var(--base));
}
100% {
background-position: calc(-924px / var(--base)) calc(-168px/ var(--base)); }}@keyframes state-question-down {
0% {
background-position: calc(-336px / var(--base)) calc(-252px / var(--base));
}
3333.% {
background-position: calc(-420px / var(--base)) calc(-252px / var(--base));
}
6666.% {
background-position: calc(-504px / var(--base)) calc(-252px / var(--base));
}
100% {
background-position: calc(-588px / var(--base)) calc(-252px/ var(--base)); }}@keyframes state-question-up {
0% {
background-position: calc(-672px / var(--base)) calc(-252px / var(--base));
}
3333.% {
background-position: calc(-756px / var(--base)) calc(-252px / var(--base));
}
6666.% {
background-position: calc(-840px / var(--base)) calc(-252px / var(--base));
}
100% {
background-position: calc(-924px / var(--base)) calc(-252px/ var(--base)); }}Copy the code
The effect is as follows:
Randomly generated mine
Random generation of mines is relatively simple, generate N non-repeating numbers in 0-row * col, and then convert them into two-dimensional coordinates:
const minesweeper = {
initMap(row = 9, col = 9, mines = 10){...this.mapTimer = setInterval(() = > {
// All lines have been traversed
if (this.mapCount >= row) {
// Status changes
this.state = 'loaded';
// Generate mines
this.generateMines();
// Clear the timer
return clearInterval(this.mapTimer); }},100);
},
/** * Generates a mine */
generateMines() {
// Enter N non-repeating numbers
let pos = [];
while(pos.length ! = =this.mines) {
pos = [...new Set([...pos, Math.floor(Math.random() * this.row * this.col)])];
}
// Convert a number to a coordinate array
for (let i = 0; i < pos.length; i++) {
const x = Math.floor(pos[i] / this.col);
const y = pos[i] % this.col;
pos[i] = [x, y];
// Change the corresponding data type to 9 (mine)
this.map[x][y].type = 9;
}
// Calculate the coordinates around the mine
for (let i = 0; i < pos.length; i++) {
// Find the surrounding coordinates
const around = this.findPos(pos[i]);
for (let j = 0; j < around.length; j++) {
const grid = this.map[around[j][0]][around[j][1]].// If it is not a mine, add 1
if(grid.type ! = =9) { grid.type++; }}}},};Copy the code
Find the surrounding coordinates
The findPos method is used to return the surrounding coordinates:
const minesweeper = {
/** * find the surrounding coordinates and remove the boundary value **@example* Assuming the coordinates are [x, y], then the surrounding coordinates are: * [ * [x - 1, y - 1], [x - 1, y], [x - 1, y + 1], * [x, y - 1], ..., [x, y + 1], * [x + 1, y - 1], [x + 1, y], [x + 1, y + 1], * ] */
findPos([x, y]) {
// Ambient coordinates
const pos = [
[x - 1, y - 1], [x - 1, y], [x - 1, y + 1],
[x, y - 1], [x, y + 1],
[x + 1, y - 1], [x + 1, y], [x + 1, y + 1]];// Simple collision detection removes boundary values
return pos.filter(([x, y]) = >! (x <0 || y < 0 || x >= this.row || y >= this.col));
},
findPosUDLR([x, y]) {
// Ambient coordinates
const pos = [
[x - 1, y], / /
[x + 1, y], / /
[x, y - 1]./ / left
[x, y + 1]./ / right
];
// Simple collision detection removes boundary values
return pos.filter(([x, y]) = >! (x <0 || y < 0 || x >= this.row || y >= this.col)); }};Copy the code
FindPosUDLR, on the other hand, is used to return about 4 Spaces, mainly for the explosion effect after the game is over, so as not to look a little dull.
Game Logic analysis
Mouse events are down, up, move, and double click.
Why not combine press and lift as click events here? Because the mouse is pressed in a square left button does not let go, move out of the current square needs to be restored, mainly to prevent the point of error. And right click and then lift, need to mark transformation (normal -> flag -> question mark -> normal), so can not be simply processed into a click event, but also have to cache the pressed box, after the move, lift to determine or not the original box.
As for the double – click event, mainly exists in the double – click number to quickly open the unmarked box operation.
The mouse click
// Block cache
let oTemp = null;
// Mouse down from the block
oGame.addEventListener('mousedown'.(ev) = > {
// The game is loaded/the game is over
if (oGame === ev.target || ['loading'.'over'].includes(minesweeper.state)) return;
const [x, y] = ev.target.pos;
if (false === minesweeper.map[x][y].isOpen) {
// Cache presses the element
oTemp = ev.target;
// Add a press style to the cached element
oTemp.className = 'state-' + minesweeper.map[x][y].sign + '-down'; }});Copy the code
If there are no squares, or the game is loading/the game is over, the logic is not processed. Loading is completed by clicking on the initMap timer, and then loading the div.state-closed with mapCount.
First determine that the current square is not open, and then cache the currently clicked square, add a press effect to the square.
The mouse moves
// Mouse movement
oGame.addEventListener('mousemove'.(ev) = > {
// The cached oTemp is inconsistent with the current element
if(oTemp && oTemp ! == ev.target) {// If the cached element is a press style
if (oTemp.className.match(/state\-.+\-down/)) {
// Add lift styles to cached elements
oTemp.className = oTemp.className.replace('-down'.'-up');
}
// Delete cached elements
oTemp = null; }});Copy the code
To move is to determine that the cached DOM exists and is not equal to the current element, and that it has a pushdown style, so the pushdown style changes to lift.
The mouse is raised
// Mouse up
oGame.addEventListener('mouseup'.(ev) = > {
// The cached oTemp is inconsistent with the current element
if(oTemp ! == ev.target) {// Delete cached elements
oTemp = null;
return;
}
const [x, y] = ev.target.pos;
/ / click
if (ev.button === 0) {
// No markup
if (minesweeper.map[x][y].sign === 'normal') {
// Handle the click event
minesweeper.handleClick(oTemp);
} else {
// Add lift styles to cached elements
oTemp.className = 'state-' + minesweeper.map[x][y].sign + '-up'; }}// Right click and do not open
if (ev.button === 2) {
// Change the state of the tag
minesweeper.map[x][y].sign = {
flag: 'question'.normal: 'flag'.question: 'normal',
}[minesweeper.map[x][y].sign];
// Number of landmines
if (minesweeper.map[x][y].sign === 'flag') {
minesweeper.mines -= 1;
}
if (minesweeper.map[x][y].sign === 'question') {
minesweeper.mines += 1;
}
document.getElementById('mines').innerHTML = minesweeper.mines;
// Add lift styles to cached elements
oTemp.className = 'state-' + minesweeper.map[x][y].sign + '-up';
}
// Delete cached elements
oTemp = null;
});
Copy the code
If the click event is marked as Normal, the event is logically processed. If the event is flag or question, the event is added and no processing is performed.
Right-click on the event to mark the change of state of the box and add a lift style. If it turns into a flag, subtract one from the number of mines, and if it turns into something else, add one.
Mouse click
/ / double
oGame.addEventListener('dblclick'.(ev) = > {
// Do not click the center box
if (oGame === ev.target) return;
const [x, y] = ev.target.pos;
const grid = minesweeper.map[x][y];
// Open and a number
if (grid.isOpen && grid.type > 0 && grid.type < 9) {
minesweeper.handleNumber([x, y], grid.type);
// Judge game winsminesweeper.judgeVictory(); }});Copy the code
Game logic processing
Here we look at the handleClick and handleNumber methods in detail.
const minesweeper = {
/** * handle the click event */
handleClick(dom) {
// Change the status
if (this.state ! = ='ongoing') {
this.state = 'ongoing';
this.startTime = +new Date(a);this.startInterval();
}
const grid = this.map[dom.pos[0]][dom.pos[1]].// Change the open state
grid.isOpen = true;
// Modify the recursive state
grid.isCheck = true;
// Modify the block style
dom.className = 'state-' + grid.type;
// Handle blank squares
if (grid.type === 0) {
this.handleSpace(dom.pos);
}
// Handle mine blocks
else if (grid.type === 9) {
this.handleMines([dom.pos]);
}
// // Handle number blocks (here changed to double click trigger)
// else if (grid.type > 0 && grid.type < 9) {
// this.handleNumber(dom.pos, grid.type);
// }
// Judge game wins
this.judgeVictory(); }};Copy the code
The first step is to determine the state. If it is not currently in the game state, modify the state and turn on the timer.
Once you have the grid data, change its open state, recursive state, and block style.
Working with blank squares
If it is a blank square, then in addition to its own changes, it expands outward to show all blank squares and adjacent numeric squares.
After clicking the red dot, judge all the squares in the red box, skip the number (1/2/3/9), and recurse the blank (4/6/7).
const minesweeper = {
/** * handles blank squares */
handleSpace(pos) {
// Find the surrounding coordinates
const around = this.findPos(pos);
for (let i = 0; i < around.length; i++) {
/ / coordinates
const [x, y] = around[i];
// correspond to square
const grid = this.map[x][y];
// Not recursive and marked normal
if (false === grid.isCheck && 'normal' === grid.sign) {
// Change the open state
grid.isOpen = true;
// Modify the recursive state
grid.isCheck = true;
// Load the animation style
const oDiv = oGame.children[x * this.col + y];
oDiv.className = 'state-' + grid.sign + '-down';
oDiv.addEventListener('animationend'.function fn() {
oDiv.className = 'state-' + grid.type;
oDiv.removeEventListener('animationend', fn);
});
// If it is a number, skip it
if (grid.type > 0 && grid.type < 9) {
continue;
}
// If it is blank, recurse
if (grid.type === 0) {
this.handleSpace(around[i]); }}}},};Copy the code
Dealing with mine blocks
const minesweeper = {
/** * Handle mine blocks */
handleMines(pos) {
// Change the status
this.state = 'over';
oGame.className = 'fail';
// Clear time
clearInterval(this.startTimer);
// Flag all mines and false flags
for (let i = 0; i < this.map.length; i++) {
for (let j = 0; j < this.map[i].length; j++) {
// It is a mine and not a flag
if (this.map[i][j].type === 9 && this.map[i][j].sign ! = ='flag') {
oGame.children[i * this.col + j].className = 'state-9';
}
// It is not a mine but a flag
if (this.map[i][j].type ! = =9 && this.map[i][j].sign === 'flag') {
oGame.children[i * this.col + j].className = 'state-flag-error'; }}}for (let i = 0; i < pos.length; i++) {
/ / coordinates
const [x, y] = pos[i];
// Current box
const grid = this.map[x][y];
// Change the open state
grid.isOpen = true;
// Change the explosion state
grid.isExplode = true;
// Load the animation style
const oDiv = oGame.children[x * this.col + y];
oDiv.className = 'state-over';
oDiv.addEventListener('animationend'.function fn() {
// End of game animation
minesweeper.explodeMines(pos[i]);
oDiv.removeEventListener('animationend', fn); }); }}};Copy the code
If you touch a mine, it’s over. Mark all unflagged mines and all wrong flags first. This method passes an array of coordinates, and since double clicking on the number triggers the possibility of guessing multiple mines wrong, it cycles through the positions, adding open, exploding states to the mines, and adding an explosion animation.
There’s no explosion animation in Sprite, so where did you get the material? MSN has a canvas version of the game 😉
I saved the material myself and photoshopped a Sprite:
Execute the end of game animation explodeMines method after the explosion animation ends:
const minesweeper = {
/** * Handle mine explosions */
explodeMines(pos) {
setTimeout(() = > {
// Find the surrounding coordinates
const around = this.findPosUDLR(pos);
for (let i = 0; i < around.length; i++) {
/ / coordinates
const [x, y] = around[i];
// correspond to square
const grid = this.map[x][y];
// There is no explosion
if (grid.isExplode === false&& grid.sign ! = ='flag') {
// Change the explosion state
grid.isExplode = true;
const oDiv = oGame.children[x * this.col + y];
// If it is a mine
if (grid.type === 9) {
// Change the open state
grid.isOpen = true;
// Load the animation style
oDiv.className = 'state-over';
}
// If it is not opened
else if(! grid.isOpen) {// Load the animation style
oDiv.className = 'state-explode';
oDiv.addEventListener('animationend'.function fn() {
oDiv.className = 'state-closed';
oDiv.removeEventListener('animationend', fn);
});
}
// Explosion recursion
this.explodeMines(around[i]); }}},100); }};Copy the code
FindPosUDLR is used to carry out diamond diffusion, turn red when meeting the normal mark, execute the explosion effect when meeting the mine, and recursively complete the traversal of all squares.
The stupid findPos method:
Working with number blocks
Remember the number squares in double click? Originally I also put it in handleClick, but it is obviously a bug when it is opened.
What does the digital module deal with? Let’s start with the code:
const minesweeper = {
/** ** handles number blocks */
handleNumber(pos, type) {
// Find the surrounding coordinates
const around = this.findPos(pos);
If flag >= number (type), the remaining normal flags can be clicked
let flag = 0;
// flag box
const flags = [];
// Mine cube
const mines = [];
for (let i = 0; i < around.length; i++) {
/ / coordinates
const [x, y] = around[i];
// correspond to square
const grid = this.map[x][y];
/ / the flag
if (grid.sign === 'flag') { flag++; flags.push({ ... grid,pos: around[i] });
}
/ / mines
if (grid.type === 9) { mines.push({ ... grid,pos: around[i] }); }}if (flag >= type) {
// Check whether the tags are correct, because it is a sequential push, so it is easy to rotate the string and compare
if (JSON.stringify(flags) === JSON.stringify(mines)) {
this.handleSpace(pos);
}
// Mark error
else {
// Handle error flags
for (let i = 0; i < flags.length; i++) {
if (flags[i].type === 9) continue;
oGame.children[flags[i].pos[0] * this.col + flags[i].pos[1]].className = 'state-flag-error';
}
// Handle the wrong mine
this.handleMines(mines.filter((item) = >item.sign ! = ='flag').map((item) = >item.pos)); }}}};Copy the code
First, get the coordinates of the number pos and the content type. In fact, it is ok to get the content from the map through POS, but I found it outside, so I easily passed it in.
Cycle to find the number of tags. Only when flag >= number type can the remaining normal tags be clicked.
First, check whether the marks are correct. Because they are all in a sequence of push, we can simply turn the string and compare. If the comparison is successful, the current number square will be treated as a blank square.
The wrong flag will be displayed, and the wrong mine coordinates will be thrown into the logic that deals with mine blocks.
Judge game wins
The condition for victory is the number of unopened blocks === the number of flags + the number of mines, so both clicking on a block and double-clicking on a number block should determine victory in the game:
const minesweeper = {
/**
* 判断游戏胜利
*
* 未打开的方块 === 旗子的数量 + 地雷的数量
*/
judgeVictory() {
let count = 0;
let flags = 0;
for (let i = 0; i < this.map.length; i++) {
for (let j = 0; j < this.map[i].length; j++) {
// Open the box
if (this.map[i][j].isOpen === false) {
count++;
}
// The number of flags
if (this.map[i][j].sign === 'flag') { flags++; }}}if (count === flags + this.mines) {
// Change the status
this.state = 'over';
oGame.className = 'success';
// Clear time
clearInterval(this.startTimer); }}};Copy the code
At the end
In fact, there are hundreds of millions of details I feel simple not shown in the article, interested can go to Github to look at the source code, compared to see a better understanding.
Including the garden theme and all the audio is actually in the game folder, you can add it yourself. Here’s another garden-themed Sprite:
The method is the same, write more keyframes for CSS.
The above.