I am participating in the nuggets Community game creative submission Contest. For details, please see: Game Creative Submission Contest
Hello everyone, I am a bowl week, a front end that does not want to be drunk (inrolled). If I am lucky enough to write an article that you like, I am very lucky
Writing in the front
2048 this game was first released in 2014, the gameplay is relatively simple, is through the direction of the key to slide the page, and then the number of the page to the specified direction, if the number is the same, the number of the merger.
I recently recreated the game using Vue3+TS+Tailwind, demonstrating it as follows:
This game also made a response type, can be used in mobile phone barrier-free, try to play the address 👉 dig gold 2048.
Note: Minerals obtained in the game are for entertainment only! The template used for this project is vue-template.
Why Vue3
First, the game is not very complex and does not require the use of a game engine, such as Cocos; Then I didn’t want to play with the native DOM, so I chose the data-driven framework Vue3; That way I don’t have to worry about how to manipulate DOM elements, just the data.
The setup syntax is also very sweet. Here are two articles of my own:
- There are seven ways to communicate with Vue3 components, not to mention that you can’t communicate with them
- Get started with Composition API in 10 minutes
The game is material
All the materials in the game come from articles published by gold Digging account or pictures published by boiling point. Photoshop is used for matting, as shown below:
If you need the PSD source, you can go to GitHub and get it at the end of this article.
The page layout
For this page layout, I used Grid layout, TailwindCSS, and wrote almost no CSS code. The
code is as follows:
<! -- App.vue -->
<template>
<div
class="warp h-screen lg:w-[480px] md:w-screen m-auto flex flex-col justify-center items-center select-none"
>
<div>
<h4
class="Font -bold text-2XL MB-5 flex bg-[rgba(30,128,255,.8)] Rounded -full text-white Pt-3 PR-10 Pb-2 PL-6"
>
<! -- Ore icon -->
<OreIcon />: {{with}}</h4>
</div>
<div
class="container bg-[#D6E8FF] w-full lg:h-[680px] md:h-[520px] grid grid-cols-4 grid-rows-4 gap-4 p-4 rounded-lg"
@touchstart="handleTouchStart"
@touchend="handleTouchEnd"
>
<span v-for="(item, index) in dataArray" :key="index" class="grid-item">
<div v-for="inx in n2048" :key="inx">
<img
v-if="item === inx"
:src="getImageUrl(item)"
class="w-full he-full user-drag animate-scale"
/>
</div>
</span>
</div>
<div class="mt-2 text-left w-full p-4 text-white">
<p>Note: the ore exchange with the screenshots to find Tony teacher, do not exchange blame me oh ~</p>
<p>Gameplay: PC side press on the left and right, H5 you on the phone to pull back and forth on good ~</p>
</div>
</div>
</template>
<style>
@keyframes scale {
0% {
transform: scale(0.5);
}
100% {
transform: scale(1); }}.animate-scale {
animation: scale 0.2 s ease;
}
</style>
Copy the code
The above code is a simple Grid layout, if I write any questions welcome comment.
The core code
First, we implement the core of the whole game, which is the function of the combined card. First, we only consider one line, and can only slide left. The effect to be achieved is shown in the picture below:
By analyzing the above figure, the following ideas can be sorted out:
- By default, an array of length 4 is received, and no data is filled with zeros;
- Define an empty array to store processed data and return it;
- Iterates through four values in a row. If the current value is 0, the loop is broken.
- The next one in the record index, which compares two values to see if they are equal, if they are
push
Double values,i++
Skip the next one, because the next one is already added to the current onepush
The current value. - So far the basic functionality has been achieved, but if it is the third case in the figure above, the result will be
[2, 4]
It’s clear that the outcome is not what we want, the outcome that we need is[2, 4]
The reason for this problem is that the null value we use is 0, which is not true at the first judgment, so ifj
When the value of is 0, we need to putj
Point to the next one until it’s non-zero; - As a final step, we need to return an array of length 4 to replace the previous array.
The implementation code is as follows:
const _2048 = (arr: number[], boo = true) = > {
const newArr: number[] = []
for (let i = 0; i < arr.length; i++) {
// If the current value is 0, jump out of the loop
if(! arr[i])continue
// When 32768 appears, the game is over (because there is no bigger graph than 32768)
if (arr[i] === 32768) {
// TODO game over win
gameOverBox()
return[]}let j = i + 1
for (; j < arr.length; j++) {
if(arr[j] ! = =0) break
}
// Compare for equality and push the result into the new array
if (arr[i] === arr[j]) {
// add extra points
newArr.push(arr[i] + arr[j])
i++
} else {
newArr.push(arr[i])
}
}
// 补0
return [0.0.0.0].map((v, i) = > newArr[i] || v)
}
Copy the code
So now we have our core function written, and in fact, it works in all directions, and we just need to pass the arguments in different order.
Let’s take the first row and the first example, left slide is [0, 1, 2, 3], right slide is [3, 2, 1, 0], up slide is [0, 4, 8, 12], slide is [12, 8, 4, 0];
We define the order as a constant that is passed as an argument when used:
const DATA_PROPS = {
ArrowLeft: [[0.1.2.3],
[4.5.6.7],
[8.9.10.11],
[12.13.14.15]],ArrowUp: [[0.4.8.12],
[1.5.9.13],
[2.6.10.14],
[3.7.11.15]],ArrowRight: [[3.2.1.0],
[7.6.5.4],
[11.10.9.8],
[15.14.13.12]],ArrowDown: [[12.8.4.0],
[13.9.5.1],
[14.10.6.2],
[15.11.7.3]],}Copy the code
At this point you should know that the core of the game is an array of length 16, and the empty space displayed on the page has a value of 0 in the array.
Create a new card
Now let’s write a function to create a new card, which goes like this:
- Get the 0 index of an array of length 16 and save it as an array;
- Get a random value from this array as our index to add a new card;
- Add a random array to the values you want to add.
The implementation code is as follows:
/ / create
const create = (arr: number[]) = > {
// Find the index that is not 0 in the array and save it to an array
const val0Arr = findIndexAll(arr, 0)
const random = Math.floor(Math.random() * val0Arr.length)
const index = val0Arr[random]
const newArr = [2.4.8]
arr[index] = newArr[Math.floor(Math.random() * newArr.length)]
}
// Looks for all occurrences of x in the array and returns an array containing the matching index
const findIndexAll = (arr: number[], x: number) = > {
const results = [],
len = arr.length
let pos = 0
while (pos < len) {
pos = arr.indexOf(x, pos)
if (pos === -1) {
// Exit the loop to complete the search
break
}
results.push(pos) // Store index when found
pos += 1 // And start searching from the next location
}
return results
}
Copy the code
The game start
Tool function
Before the game starts, we need to define two utility functions, one to update the returned array into the array, and one to pass four index sequences in one direction, each calling the update function. The implementation code is as follows:
const setArrayVal = (arr: number[], index: number[], value: number[]) = > {
index.forEach((val, index) = > {
arr[val] = value[index]
})
}
const ArrComputed = (arr: number[], direction: DirectionType, bool = true) = > {
DATA_PROPS[direction].forEach(_= > {
const newArr = _2048([arr[_[0]], arr[_[1]], arr[_[2]], arr[_[3]]], bool)
setArrayVal(arr, _, newArr)
})
}
Copy the code
Define the game start function
This function is relatively simple, just need to call the first two functions, again before deciding whether the game is over, example code is as follows:
const run = (direction: DirectionType) = > {
// over is used to record whether the game is over
if (over.value) return
ArrComputed(dataArray.value, direction)
// TODO:Determine if the game is over
create(dataArray.value)
}
Copy the code
Listen for PC events
PC speakers simply listen for keyboard events and call our run function, as shown in the following example:
const DirectionArr: DirectionType[] = [
'ArrowLeft'.'ArrowRight'.'ArrowUp'.'ArrowDown',]document.addEventListener('keyup'.(e: KeyboardEvent) = > {
if (DirectionArr.find(item= > item === e.key)) {
run(e.key as unknown as DirectionType)
}
})
Copy the code
Listen for mobile events
On the mobile side, we need to monitor the touch event and determine the sliding direction. Here, we use TouchStart to record the start position and touchEnd to record the end position.
Example code is as follows:
const moveXY = {
startX: 0.startY: 0.endX: 0.endY: 0,}const handleTouchStart = (e: TouchEvent) = > {
e.preventDefault()
// Get the starting positionmoveXY.startX = e.touches? .0].pageX moveXY.startY = e.touches? .0].pageY
}
const handleTouchEnd = (e: TouchEvent) = > {
e.preventDefault()
// Get the end positionmoveXY.endX = e.changedTouches? .0].pageX moveXY.endY = e.changedTouches? .0].pageY
// Get the sliding distance
const distanceX = moveXY.endX - moveXY.startX
const distanceY = moveXY.endY - moveXY.startY
// Determine the sliding direction
if (Math.abs(distanceX) > Math.abs(distanceY) && distanceX < 0) {
run('ArrowLeft')}else if (Math.abs(distanceX) > Math.abs(distanceY) && distanceX > 0) {
run('ArrowRight')}else if (Math.abs(distanceX) < Math.abs(distanceY) && distanceY < 0) {
run('ArrowUp')}else if (Math.abs(distanceX) < Math.abs(distanceY) && distanceY > 0) {
run('ArrowDown')}}Copy the code
At this point, we can see that the game is ready to run and mobile compatible.
Game over
End of the game box component
First we write a game end of the box component, the example code is as follows:
<script setup lang="ts">
import { withDefaults } from 'vue'
import { useVModel } from '@vueuse/core'
import OreIcon from './OreIcon.vue'
interface Props {
modelValue: boolean
score: number
}
const props = withDefaults(defineProps<Props>(), {
modelValue: false.score: 0,})const emits = defineEmits(['update:modelValue'.'restart'])
const show = useVModel(props, 'modelValue', emits)
const handleReStart = () = > {
emits('restart')}</script>
<template>
<div v-show="show" class="w-screen h-screen fixed inset-0">
<div class="mask bg-black opacity-50 absolute inset-0"></div>
<div
class="h-60 w-[420px] bg-white rounded-3xl absolute inset-0 m-auto p-6 flex flex-col"
>
<div class="text-center text-xl">Game over</div>
<span class="text-2xl flex justify-center mt-3 flex-grow items-center"
>Get {{props. Score}}<OreIcon />
</span>
<el-button
type="primary"
size="large"
round
class="w-full mt-2"
@click="handleReStart"
></el-button ></div>
</div>
</template>
Copy the code
Bidirectional data binding is implemented via Vueuse’s useVModel, and I have to say that this hooks library really works.
How to tell when a game is over
Judge the end of the game here I stole a little lazy, directly through the function to perform sliding, after the execution of the four directions of the value and the current consistency, indicating that can not slide, so the game is over.
It is important to note that the array to be tested must be a deep copy of the original array.
The implementation code is as follows:
// Game over judgment
const gameOver = (arr: number[]) = > {
const oldArr = JSON.stringify(arr)
const testArr = JSON.parse(JSON.stringify(arr))
// Compute four directions
const _res: string[] = []
DirectionArr.forEach(item= > {
ArrComputed(testArr, item, false)
_res.push(JSON.stringify(testArr))
})
if (_res.filter(i= > i === oldArr).length == 4) {
// Game over
over.value = true
// TODO: Opens the end-of-game popbox component}}Copy the code
Now we call this function in the function that started the game, and we can determine the end of the game.
Start all over again
It is easier to start again, just reset the data, as shown in the following code:
// Array reset function
const restart = () = > {
const arr = Array.apply(null.Array(16)).map(Number.prototype.valueOf, 0)
const random1 = Math.floor(Math.random() * arr.length)
const random2 = Math.floor(Math.random() * arr.length)
arr[random1] = 2
arr[random2] = 4
return arr
}
// Start over
const handleReStart = () = > {
show.value = false
dataArray.value = restart()
score.value = 0
over.value = false
}
Copy the code
End scatter flower ~~~
Write in the last
The source code for this game is open source on GitHub (welcome to ⭐), and if you have a good idea, you can fork out the repository, or even clone the core code, and make it whatever you like.
If this article is helpful to you, please like it and comment on it. Thank you
If there are errors in the code, please correct them. If you have a good idea to put forward, I meet ~