In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-03-26 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)05/31 Report--
This article mainly explains "how to achieve Xiaoxiale game based on JS". The content of the explanation in the article is simple and clear, and it is easy to learn and understand. Please follow the editor's train of thought to study and learn "how to realize Xiaoxiale game based on JS".
The preparation of the game
First of all, let's think about the mechanism of the game: the game has a "chessboard", which is a rectangle of nymphs. There are several colors (or types) of squares in a rectangle, and squares of the same type will be eliminated when there are three or more horizontal or vertical rows.
After some of the squares are eliminated, the squares above these squares will fall and supplement the gaps in these squares, and at the same time, a new square will be generated above to supplement the position of the falling squares. After performing the above steps, a cycle of the game process is completed.
To sum up.
There are three steps that will generate a game cycle: eliminate, fall, and supplement. After replenishment, if the squares cannot be eliminated naturally, the cycle will end. At this point, players are required to return two adjacent squares to artificially create situations that can be eliminated in order to re-enter the cycle.
What if the player's exchange doesn't get you back into the elimination cycle? Then the exchange will return to its original form.
After thinking about the basic mechanism, let's start building the code:
First of all, considering that the squares will do a lot of animation process (mainly four kinds: move, eliminate, drop, pop), so we use absolute positioning to arrange these squares, and add the attribute: transition to their line class style, and use css to animate these squares. The specific implementation is as follows:
Move: control the movement of the square by changing the left and top values.
Elimination: by modifying the transform, modify to scale (0) to achieve the elimination of animation.
Fall: through the change of the top value, move with.
Pop-up: change the transformation value that was originally scale (0) to scale (1) by modifying transform to achieve the pop-up animation.
Considering that these animations are executed one after another, we should need to use async to execute these animations. Of course, we can also use callback functions, but callback functions can be troublesome, so we use Promise objects to solve these problems.
Too much nonsense! Now start writing the code.
Chessboard
The first is the realization of the chessboard, the simple operation defines the overall structure of the chessboard. Of course, it is necessary to add position:relative or position:absolute to # app.
Next, we use the object-oriented idea to construct the specific content of the chessboard:
First, a chessboard has its width and height (x and y), and we also define its square size (size).
Matrix is the matrix that will be used later, storing different type, and types is all kinds of chess pieces.
In addition, there are several attributes, which will be discussed later.
Class GameMap {constructor (x, y, size) {this.x = x; this.y = y; this.size = size; this.matrix = []; this.useSwap = false; this.handleable = true This.types = emojis.length;}}
Let's construct "chess pieces". There are many attributes of chess pieces, so we assign these attributes to chess pieces by only taking options as a parameter and deconstructing options. These attributes are
Class Cell {constructor (options) {const {position, status, type, left, top, right, bottom, instance} = options; this.type = type; this.position = position; this.status = status; this.top = top; this.bottom = bottom This.left = left; this.right = right; this.instance = instance;}}
Type type (color), number type means that the same number is considered the same type.
Position location, stored in a two-dimensional array in the shape of [mrecom n]
Status state, divided into 'common' normal' collapse' collapse 'emerge' emergence, a total of three
The value of the pawn object above the top piece is also a Cell instance. If there is no piece above the piece, it is undefined.
The left pawn object of left chess piece
The right pawn object of the right chess piece
The lower pawn object of the bottom chess piece
Instance depicts the DOM objects of real chess pieces based on the above attributes, and eventually these objects will be displayed in GameMap
Here we use emoji emoticons to represent these pieces, and we define the global variable emojis:
Const emojis = [',']; render the picture
With the chessboard and pieces, we can render the chessboard.
First of all, we define the global variable cells, which is used to store all the pieces in the chessboard and all the Cell classes.
Then we define the method genMatrix () in GameMap to initialize the chessboard. According to the configuration of the width and height of the chessboard (x and y), we have filled in a lattice of xroomy, but there is nothing in it yet.
GenMatrix () {const {x, y} = this; const row = new Array (x) .fill (undefined); const matrix = new Array (y) .fill (undefined) .map (item = > row); this.matrix = matrix; return this;}
The next job is to fill the lattice with random numbers, and we define the method genRandom ().
GenRandom () {const {x, y} = this; this.matrix = this.matrix.map (row = > row.map (item = > Math.floor (Math.random () * this.types); return this;}
A lattice is generated as shown in the figure. We then use these lattices to render the real picture.
Define the method init ()
For now, the way init () is written is somewhat obscure, which is normal, and we'll talk about it later.
Init () {cells = []; const {x, y} = this; for (let I = 0; I
< y; i++) { for (let j = 0; j < x; j++) { const type = this.matrix[i][j]; const random = Math.floor(Math.random() * this.types); cells.push(new Cell({ type: (type == undefined) ? random : type, position: [j, i], status: (type == undefined) ? 'emerge' : 'common', left: undefined, top: undefined, right: undefined, bottom: undefined, instance: undefined })); } } cells.forEach(cell =>{const [row, col] = cell.position; cell.left = cells.find (_ cell = > {const [_ row, _ col] = _ cell.position; return (_ row = = row-1) & & (_ col = = col) }); cell.right = cells.find (_ cell = > {const [_ row, _ col] = _ cell.position; return (_ row = = row + 1) & & (_ col = = col) }); cell.top = cells.find (_ cell = > {const [_ row, _ col] = _ cell.position; return (_ row = = row) & & (_ col = = col-1) }); cell.bottom = cells.find (_ cell = > {const [_ row, _ col] = _ cell.position; return (_ row = = row) & & (_ col = = col + 1) }); cell.genCell ();}); return this;}
The global variable cells defined earlier is used to hold all instances of the Cell class.
For a newly generated chessboard, the status of all Cell is common, and we'll talk about other situations later. Going back to the construction process of the Cell class, we find that this step completes the shaping of the Cell instance, these pieces will be further processed into the final game screen, and the last step cell.genCell () will finally materialize these abstract classes.
The genCell () method is the method we defined in the Cell class, and we use the
GenCell () {const cell = document.createElement ('div'); const size = gameMap.size; const [x, y] = this.position; cell.type = this.type; cell.style.cssText = `width:$ {size} px Height:$ {size} px; left:$ {size * x} px; top:$ {size * y} px; box-sizing:border-box; border:5px solid transparent; transition:0.5s Position:absolute; transform:scale (${this.status = = 'emerge'?' 0':'1'}); display:flex; justify-content:center; align-items:center ` Cell [XSS _ clean] = `${emojis [this.type]}`; this.instance = cell;}
GenCell generates chess pieces according to the definition and calculus of cell data before init (), but at present, the chess pieces have not yet been rendered to the page, only temporarily stored as DOM objects.
Finally, we define the method genCellMap () to generate the real game screen.
GenCellMap () {app [XSS _ clean] ='; cells.forEach (cell = > {app.append (cell.instance);}); return this;}
Iterate through the contents of the previous global variable cells, find the instance in each Cell, mount these instance to # app, and a game board pops up.
This is what a Cell instance looks like, where instance is a real div.
The game screen is the simplest chessboard without any other beautification (in fact, it is because I am too lazy to write). Considering that emoji is not as troublesome as image loading, and there is no distortion, we use emoji to depict these pieces.
A real instance look.
Animation effect
Previously, we have made it clear that there are three things to eliminate, drop, and supplement sent in a cycle of the game, so we describe these three things by defining GameMap in three ways:
GenCollapse () genDownfall () genEmerge ()
The code is as follows
GenCollapse () genCollapse () {return new Promise ((resolve, reject) = > {this.handleable = false; this.markCollapseCells () SetTimeout (() = > {cells.forEach (cell = > {if (cell.status = = 'collapse') {cell.instance.style.transform =' scale (0)' }});}, 0); setTimeout (() = > {resolve ('ok');}, 500);});}
There is also a step in the genCollapse process called markCollapseCells (), which marks the pieces that will collapse. The code for this method is as follows:
MarkCollapseCells () {cells.forEach ((cell) = > {const {left, right, top, bottom, type} = cell; if (left?.type = = type & & right?.type = = type) {left.status = "collapse"; cell.status = "collapse"; right.status = "collapse" } if (top?.type = = type & & bottom?.type = = type) {top.status = "collapse"; cell.status = "collapse"; bottom.status = "collapse";}}); return this;}
Traversing the entire cells, if the left and right side of a piece is of the same type as itself, then all three of their states will be marked as' collapse',', and if the top and bottom of the piece are of the same type as themselves. We are not afraid of repetition, because it doesn't matter if the pieces that have already been marked are marked again.
After the markup is complete, we add these tagged Cell objects, their instance style, to a transform:scale (0), and under the influence of transition, they gradually (and actually very quickly) shrink to an invisible state, and in fact they do not disappear. In this gradual shrinking process, we use the nature of Promise to block the implementation of this method, in order to wait for the pieces to shrink and then move on to the next process: falling. Throw the resolve after 0.5s, complete the release, and proceed to the next step smoothly.
GenDownfall () genDownfall () {return new Promise ((resolve, reject) = > {setTimeout () = > {cells.forEach (cell = > {if (cell.status! = 'collapse') {let downfallRange = 0) Let bottom = cell.bottom; while (bottom) {if (bottom.status = = 'collapse') {downfallRange + = 1 } bottom = bottom.bottom;} cell.instance.style.top = (parseInt (cell.instance.style.top) + gameMap.size * downfallRange) + 'px' }});}, 0); setTimeout (() = > {resolve ('ok');}, 500);});}
The key to genDownfall () is that we need to know which pieces will fall and how far they should fall. After we know the fall distance, we set these new top values in Cell, which are also animated by the effect of transition.
First of all, identify what pieces may fall:
In fact, it is very simple, with the exception of status for collapse pieces may fall, but not necessarily, such as the bottom row of pieces will not fall anyway.
So the next step is to calculate the distance they fall:
This step is not complicated. In the previous Cell class, we have defined the bottom attribute of the chess piece in advance. We only need to know how many pieces with a state of collapse under the piece, and we will know how far the piece will fall. The distance = the number of pieces with the status of collapse under the size* of the chess piece.
Through the while method, you can get the answer by inquiring about the status of the pieces under the chess piece one by one.
We did it again, blocking the whole process for 0.5s with the nature of Promise, and then releasing it to the next step.
GenEmerge ()
The process of genEmerge () is much more complicated because at this time the chessboard has been disrupted and the entire chessboard needs to be reshaped and the missing chessboard will be added after it has been reshaped. The supplementary method has also been mentioned before, that is, scale (0)-> scale (1), in order to get a vibrant, competitive effect of all things.
The code is as follows:
GenEmerge () {return new Promise ((resolve, reject) = > {this.regenCellMap (); this.genCellMap () SetTimeout (() = > {cells.forEach (cell = > {if (cell.status = = 'emerge') {cell.instance.style.transform =' scale (1)';}}) }, 0); setTimeout (() = > {resolve ('ok');}, 500);});}
One of the steps is called regenCellMap (), and the code is as follows
RegenCellMap () {const size = gameMap.size; const findInstance = (x, y) = > {return cells.find (item = > {const {offsetLeft, offsetTop} = item.instance; return (item.status! = 'collapse' & & (x = = offsetLeft / size) & & (y = = offsetTop / size));})? .instance }; this.genMatrix (); this.matrix = this.matrix.map ((row, rowIndex) = > row.map ((item, itemIndex) = > findInstance (itemIndex, rowIndex)? .type); this.init ();}
One of the key steps is findInstance. We need to find out what their position is after performing downfall, and correspond their type to the position after displacement. We use these one-to-one corresponding information to reconstruct matrix to complete the reconstruction of the whole chessboard. Notice that the last step of the method, init (), means that we have reinitialized the chessboard. Let's take a look at the code of init (), and maybe you can understand why init () wrote this.
The reshaped matrix looks like this.
The falling piece fills the hole to eliminate the piece, and the gap above appears. The find method of the array returns undefined if it cannot find the content, so findInstance () returns undefined when it cannot find the piece, thus applying this result to the reshaped matrix.
Let's look at init () again.
Init () {cells = []; const {x, y} = this; for (let I = 0; I
< y; i++) { for (let j = 0; j < x; j++) { const type = this.matrix[i][j]; const random = Math.floor(Math.random() * this.types); cells.push(new Cell({ type: (type == undefined) ? random : type, position: [j, i], status: (type == undefined) ? 'emerge' : 'common', left: undefined, top: undefined, right: undefined, bottom: undefined, instance: undefined })); } } cells.forEach(cell =>{const [row, col] = cell.position; cell.left = cells.find (_ cell = > {const [_ row, _ col] = _ cell.position; return (_ row = = row-1) & & (_ col = = col) }); cell.right = cells.find (_ cell = > {const [_ row, _ col] = _ cell.position; return (_ row = = row + 1) & & (_ col = = col) }); cell.top = cells.find (_ cell = > {const [_ row, _ col] = _ cell.position; return (_ row = = row) & & (_ col = = col-1) }); cell.bottom = cells.find (_ cell = > {const [_ row, _ col] = _ cell.position; return (_ row = = row) & & (_ col = = col + 1) }); cell.genCell ();}); return this;}
Notice these two lines of code:
Type: (type = = undefined)? Random: type
Status: (type = = undefined)? 'emerge': 'common'
After reshaping the chessboard, the gap parts can be added: first, their type is randomly generated, and their state is different from common, but emerge, and they will appear in the genEmerge () process.
Integration effect
The most complex work has been done so far, we have depicted the core cycle of the game, and now we use the genLoop method to integrate this series of processes, the code is as follows:
GenLoop () async genLoop () {await gameMap.genCollapse (); let status = cells.some (cell = > cell.status = = 'collapse'); while (cells.some (cell = > cell.status = =' collapse')) {await gameMap.genDownfall (); await gameMap.genEmerge (); await gameMap.genCollapse ();} gameMap.handleable = true; return status }
Considering that we are using Promise, we make genLoop an asynchronous function. The while loop will cycle through these processes until we cannot mark the state of any pieces as collapse. One variable we didn't mention before is handleable, which determines whether we can interact with the chessboard or not.
GenSwap ()
Finally, we think about the process of manipulating the chessboard. In fact, in Xiaoxiaole game, there are very few scenes that require us to interact with each other, most of the time is watching animation, genSwap is the process of exchanging two pieces.
GenSwap (firstCell, secondCell) {return new Promise ((resolve, reject) = > {const {instance: C1, type: T1} = firstCell; const {instance: C2, type: T2} = secondCell; const {left: x1, top: y1} = c1.style; const {left: x2, top: y2} = c2.style SetTimeout (() = > {c1.style.left = x2; c1.style.top = y2; c2.style.left = x1; c2.style.top = y1;}, 0) SetTimeout (() = > {firstCell.instance = c2; firstCell.type = T2; secondCell.instance = C1; secondCell.type = T1; resolve ('ok');});};}
The routine is very clear, first change the left and top values of the two pieces to get the animation effect, and then reconstruct the Cell object after 0.5s.
Finally, there is the execution code of the game:
Const app = document.getElementById ('app'); const btn = document.getElementById (' btn'); const emojis = [',']; let cells = []; let gameMap = new GameMap (6,10,50); gameMap.genMatrix (). GenRandom (); gameMap.init (). GenCellMap (); gameMap.genLoop (); let cell1 = null;let cell2 = null App.onclick = () = > {if (gameMap.handleable) {const target = event.target [XSS _ clean]; const {left: X, top: y} = target.style; const _ cell = cells.find (item = > item.instance = = target); if (! gameMap.useSwap) {target.className = 'active' Cell1 = _ cell;} else {cell2 = _ cell; cell1.instance.className ='' If (['left',' top', 'bottom',' right'] .some (item = > cell1 [item] = = cell2)) {(async () = > {await gameMap.genSwap (cell1, cell2); let res = await gameMap.genLoop () If (! res) {await gameMap.genSwap (cell1, cell2);}}) () }} gameMap.useSwap =! gameMap.useSwap;}}
In fact, the main work is spent on constructing the GameMap and Cell classes, and after constructing the two classes clearly, the rest of the work is not so complicated.
The click event here is a little more complicated, because in fact, only two adjacent cells can be exchanged, and after the exchange, it is necessary to determine whether any cells will collapse, and if not, the exchange will be reset. This effect is depicted in app.onclick.
Thank you for your reading, the above is the content of "how to achieve Xiaoxue game based on JS". After the study of this article, I believe you have a deeper understanding of how to achieve Xiaoxiao game based on JS, and the specific use needs to be verified in practice. Here is, the editor will push for you more related knowledge points of the article, welcome to follow!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.