Let’s take a look at the renderings first:
Functional Analysis
Of course, it’s all about playing. As a Vue enthusiast, we should go deep into the game and explore the implementation of the code. Next, let’s analyze what functions are mainly needed to complete such a game. Below I will directly list the functional points of this example:
1. Randomly generate digit grids of 1 to 15. Each number must appear and only appear once.
2. After clicking on a digital square, if there is one place above, bottom, left and right, the two exchange positions
3. Every time the grid moves, we need to verify whether it has successfully passed the level.
4. After clicking the Reset Game button, you need to reorder the puzzles
The above are the main functional points of this example. It can be seen that the game functions are not complicated. We just need to break them one by one and it will be OK. Next, I will show the Vue codes of each functional point.
Build a game panel
As a data-driven JS framework, Vue's HTML templates should often be bound to data. For example, the block grid of such a game must not be written here. The code is as follows:
<template> <div> <ul> <li :class="{'puzzle': true, 'puzzle-empty': !puzzle}" v-for="puzzle in puzzles" v-text="puzzle" ></li> </ul> </div></template><script>export default { data () { return { puzzles: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] } }}</script>I have omitted the css style part here, so you don’t have to worry about it for now. In the above code, we wrote the numbers from 1 to 15 in an array. This is obviously not randomly sorted, so we will implement the function of random sorting.
Random sorting of numbers
<template> <div> <ul> <li :class="{'puzzle': true, 'puzzle-empty': !puzzle}" v-for="puzzle in puzzles" v-text="puzzle" ></li> </ul> </div></template><script>export default { data () { return { puzzles: [] } }, methods: { // Reset render () { let puzzleArr = [], i = 1 // Generate an array of 1 to 15 numbers for (i; i < 16; i++) { puzzleArr.push(i) } // Randomly disrupt the array puzzleArr = puzzleArr.sort(() => { return Math.random() - 0.5 }); // The page shows this.puzzles = puzzleArr this.puzzles.push('') }, }, ready () { this.render() }} In the above code, we use the for loop to generate an ordered array of 1~15. Then we use the native JS sort method to randomly disrupt the numbers. This also contains a knowledge point, which is Math.random() method.
To use sort() method for custom sorting, we need to provide a comparison function, and then return a number used to illustrate the relative order of these two values, and its return value is as follows:
1. Return a value less than 0, indicating that a is less than b
2. Return 0, indicating that a is equal to b
3. Return a value greater than 0, indicating that a is greater than b
Here, Math.random() is used to generate a random number between 0 and 1, and then subtract 0.5. In this way, half of the probability will return a value less than 0 and half of the probability will return a value greater than 0, which ensures the randomness of the generated array and realizes the function of dynamic random generation of numeric grids.
It should be noted that we also inserted an empty string at the end of the array to generate a unique blank grid.
Switch block position
<template> <div> <ul> <li :class="{'puzzle': true, 'puzzle-empty': !puzzle}" v-for="puzzle in puzzles" v-text="puzzle" @click="moveFn($index)" ></li> </ul> </div></template><script>export default { data () { return { puzzles: [] } }, methods: { // Reset render () { let puzzleArr = [], i = 1 // Generate an array of 1 to 15 numbers for (i; i < 16; i++) { puzzleArr.push(i) } // Random to disrupt the array puzzleArr = puzzleArr.sort(() => { return Math.random() - 0.5 }); // The page shows this.puzzles = puzzleArr this.puzzles.push('') }, // Click the block moveFn (index) { // Get the click position and its values up and down and left let curNum = this.puzzles[index], leftNum = this.puzzles[index - 1], rightNum = this.puzzles[index + 1], topNum = this.puzzles[index - 4], bottomNum = this.puzzles[index + 4] // Exchange the value with the empty position if (leftNum === '') { this.puzzles.$set(index - 1, curNum) this.puzzles.$set(index, '') } else if (rightNum === '') { this.puzzles.$set(index + 1, curNum) this.puzzles.$set(index, '') } else if (topNum === '') { this.puzzles.$set(index - 4, curNum) this.puzzles.$set(index, '') } else if (bottomNum === '') { this.puzzles.$set(index + 4, curNum) this.puzzles.$set(index, '') } } } }, ready () { this.render() }}</script>1. Here we first add the click event @click="moveFn($index)" to the li of each grid, and use the $index parameter to get the position of the click block in the array.
2. Secondly, get the index values of the numbers up, down, left and right in the array, as index - 4, index + 4, index - 1, index + 1
3. When we find a place that is empty on the upper, lower, left and right, we assign the empty position to the number of the currently clicked grid on the null position and set the current clicked position to empty
Note: Why do we use the $set method instead of directly assigning values? This contains knowledge points on the principle of Vue responsiveness.
// Due to JavaScript limitations, Vue.js cannot detect the following array changes: // 1. Set elements directly with indexes, such as vm.items[0] = {}; // 2. Modify the length of the data, such as vm.items.length = 0. // To solve the problem (1), Vue.js extends the observation array and adds a $set() method to it: // Same as `example1.items[0] = ...`, but can trigger the view update example1.items.$set(0, { childMsg: 'Changed!'})Check whether the test is successful
<template> <div> <ul> <li :class="{'puzzle': true, 'puzzle-empty': !puzzle}" v-for="puzzle in puzzles" v-text="puzzle" @click="moveFn($index)" ></li> </ul> </div></template><script>export default { data () { return { puzzles: [] } }, methods: { // Reset render () { let puzzleArr = [], i = 1 // Generate an array of 1 to 15 numbers for (i; i < 16; i++) { puzzleArr.push(i) } // Random to disrupt the array puzzleArr = puzzleArr.sort(() => { return Math.random() - 0.5 }); // The page shows this.puzzles = puzzleArr this.puzzles.push('') }, // Click the block moveFn (index) { // Get the click position and its values up and down and left let curNum = this.puzzles[index], leftNum = this.puzzles[index - 1], rightNum = this.puzzles[index + 1], topNum = this.puzzles[index - 4], bottomNum = this.puzzles[index + 4] // Exchange the value with the empty position if (leftNum === '') { this.puzzles.$set(index - 1, curNum) this.puzzles.$set(index, '') } else if (rightNum === '') { this.puzzles.$set(index + 1, curNum) this.puzzles.$set(index, '') } else if (topNum === '') { this.puzzles.$set(index - 4, curNum) this.puzzles.$set(index, '') } else if (bottomNum === '') { this.puzzles.$set(index + 4, curNum) this.puzzles.$set(index, '') } this.passFn() }, // Verify whether it passes the passFn () { if (this.puzzles[15] === '') { const newPuzzles = this.puzzles.slice(0, 15) const isPass = newPuzzles.every((e, i) => e === i + 1) if (isPass) { alert ('Congratulations, I successfully passed the level!') } } } } } }, ready () { this.render() }}</script> We call the passFn method in the moveFn method to detect, and passFn method involves two knowledge points:
(1) slice method
Through the slice method, we intercept the first 15 elements of the array to generate a new array, of course, the following element of the array is empty.
(2) Every method
Through the every method, we loop whether each element of the intercepted array is equal to its index+1 value. If all are equal, it returns true. If there is one that is not equal, it returns false.
If you successfully pass the level, then the value of isPass is true, and you will alert "Congratulations, you successfully pass the level!" prompt window. If not, you will not prompt.
Reset the game
Resetting the game is actually very simple, just add the reset button and call render method on it:
<template> <div> <ul> <li :class="{'puzzle': true, 'puzzle-empty': !puzzle}" v-for="puzzle in puzzles" v-text="puzzle" @click="moveFn($index)" > </li> </ul> <button @click="render">Reset the game</button> </div></template><script>export default { data () { return { puzzles: [] } }, methods: { // Reset render () { let puzzleArr = [], i = 1 // Generate an array containing 1 to 15 numbers for (i; i < 16; i++) { puzzleArr.push(i) } // Random to disrupt the array puzzleArr = puzzleArr.sort(() => { return Math.random() - 0.5 }); // The page shows this.puzzles = puzzleArr this.puzzles.push('') }, // Click the block moveFn (index) { // Get the click position and its values up and down and left let curNum = this.puzzles[index], leftNum = this.puzzles[index - 1], rightNum = this.puzzles[index + 1], topNum = this.puzzles[index - 4], bottomNum = this.puzzles[index + 4] // Exchange the value with the empty position if (leftNum === '') { this.puzzles.$set(index - 1, curNum) this.puzzles.$set(index, '') } else if (rightNum === '') { this.puzzles.$set(index + 1, curNum) this.puzzles.$set(index, '') } else if (topNum === '') { this.puzzles.$set(index - 4, curNum) this.puzzles.$set(index, '') } else if (bottomNum === '') { this.puzzles.$set(index + 4, curNum) this.puzzles.$set(index, '') } this.passFn() }, // Verify whether it passes the passFn () { if (this.puzzles[15] === '') { const newPuzzles = this.puzzles.slice(0, 15) const isPass = newPuzzles.every((e, i) => e === i + 1) if (isPass) { alert ('Congratulations, I successfully passed the level!') } } } } } }, ready () { this.render() }}</script><style>@import url('./assets/css/bootstrap.min.css');body { font-family: Arial, "Microsoft YaHei"; }.box { width: 400px; margin: 50px auto 0;}.puzzle-wrap { width: 400px; height: 400px; margin-bottom: 40px; padding: 0; background: #ccc; list-style: none;}.puzzle { float: left; width: 100px; height: 100px; font-size: 20px; background: #f90; text-align: center; line-height: 100px; border: 1px solid #ccc; box-shadow: 1px 1px 4px; text-shadow: 1px 1px 1px #B9B4B4; cursor: pointer;}.puzzle-empty { background: #ccc; box-shadow: inset 2px 2px 18px;}.btn-reset { box-shadow: inset 2px 2px 18px;}</style>Here I added the css code.
Summarize
The above is all about this article. In fact, the code volume of this game is not much, and the function points are not very complicated. However, writing such a game through Vue will help us understand the responsive principle of Vue driven by data. While simplifying the amount of code, it also increases the readability of the code. I hope this article will be helpful to everyone to learn some Vue.