0. Preface
Let me sort out a 2048 game I made before, which was implemented using C#. The core code was also sorted out later, which was implemented using ts.
I originally wanted to make a small program, but the small program has too many restrictions and the front-end three tricks have not been learned, so I put the small program version on hold, and first put the core logic code of the ts version together. I won’t talk too much about the specific project implementation. I will mainly talk about the core implementation, and the example code will be displayed in ts. After all, it has also been sorted out. If you need C# code, you can look at the project, there will be slight differences, but the idea is the same.
The project link is as follows:
链接:https://pan.baidu.com/s/1VfH4CEXEE9C6zM9tV2N0Pg
提取码:wsad
1. 2048 core logic
The logical flow of 2048 is also very clear, mainly: generate numbers, determine whether to move, and move numbers. No more movement means game over. So it is abstracted into 3 functions as GenNewNum(), GetMoveAble(dir: Dir), Move(dir: Dir)
(1)GenNewNum
It is relatively simple to generate a number. Just find an empty position and try to generate it. You can directly randomize the position and then try to generate it. There is already a number in this position and then randomize it again. Or
find out all the empty positions, and then randomize, which is also used below.
/**
* 生成新数字
*/
public GenNewNum() {
let newNumPlace: Array<number> = new Array<number>()
for (let i = 0; i < this.sizeI; i++) {
for (let j = 0; j < this.sizeJ; j++) {
if (this.num[i][j] == 0) {
newNumPlace.push(i + j * this.sizeI)
}
}
}
if (newNumPlace.length > 0) {
let index: number = Random.GetRandomIn(newNumPlace)
let i = index % this.sizeI
let j = (index - i) / this.sizeI
this.num[i][j] = Random.GetRandomIn(this.newNumRange)
}
}
(2) GetMoveAble
There are 4 directions to judge whether it can move, and each direction can be processed independently, but in fact the judgment of each direction is similar. We can first consider how to judge the situation in the first row, and only consider whether this row can be moved to the left. Then this judgment can be abstracted into two conditions, and the judgment can be as follows:
- 1. Whether there are 0
- 2. Whether there are the same numbers next to each other (it can also be separated by 0)
private canMoveOneLine(line: Array<number>): boolean {
let last: number = -1
let can: boolean = false
for (let i = line.length - 1; i >= 0; i--) {
if (last == 0) {
can = true //is zero
break
} else if (line[i] == last) {
can = true //is same
break
} else {
last = line[i]
}
}
return can
}
After finishing this step, you can, and other lines in the left direction can be judged by this function. At this time, for the processing in other directions, we can do it for a while.
/**
* 检查能否移动
* @param dir
* @returns moveAble
*/
public GetMoveAble(dir: Dir): boolean {
let lineCount = this.getLineCount(dir)
let canMove: boolean = false
for (let i = 0; i < lineCount; i++) {
let tempLine = this.getNumOneLine(dir, i)
canMove = this.canMoveOneLine(tempLine)
if (canMove) {
break
}
}
return canMove
}
private getNumOneLine(dir: Dir, lineIndex: number): Array<number> {
let i = lineIndex
let j = lineIndex
let arr_index: number
let arr_length: number = this.getOneLineLength(dir)
let arr: Array<number> = new Array<number>()
for (let k = 0; k < arr_length; k++) {
if (dir == Dir.left || dir == Dir.right) {
i = k
} else {
j = k
}
if (dir == Dir.down || dir == Dir.right) {
arr_index = k
} else {
arr_index = arr_length - 1 - k
}
arr[arr_index] = this.num[i][j]
}
return arr
}
private getLineCount(dir: Dir) {
switch (dir) {
case Dir.up:
case Dir.down:
return this.sizeJ
case Dir.left:
case Dir.right:
return this.sizeI
}
return this.sizeI
}
(3)Move
This is actually similar to the above. Let's first consider the processing of moving a line to the left. For the first line, you can follow 3 steps.
- (*1) moveZero, we first move all the 0s to one side, and put all the ones with numbers together.
- (*2) combine, and then combine adjacent numbers.
- (*3) moveZero, considering that there is a vacancy after the number is merged, so finally move 0 to the side
private MoveOneLine(numOne: Array<number>) {
//console.log("MoveOneLine:" + numOne[0], numOne[1], numOne[2], numOne[3])
this.moveZero(numOne)
this.combine(numOne)
this.moveZero(numOne)
//console.log("MoveOneLine2:" + numOne[0], numOne[1], numOne[2], numOne[3])
}
private moveZero(line: Array<number>) {
let count = 0
for (let i = line.length - 1; i >= 0; i--) {
if (line[i] == 0) {
count++ //zero count
} else if (count > 0) {
//move to zero
line[i + count] = line[i]
line[i] = 0
}
}
}
private combine(line: Array<number>) {
let last: number = -1
for (let i = line.length - 1; i >= 0; i--) {
if (line[i] != 0 && line[i] == last) {
line[i + 1] = last + line[i]
line[i] = 0
}
last = line[i]
}
}
After moving one line is done, all directions can be handled similarly, but the difference from GetMoveAble is that the number needs to be assigned back. Don't worry too much about assignment and the overhead of creating a new array. It's not a big problem to only create a little position every step, and it's much easier to maintain the code in this way.
/**
* 往这个方向移动
* @param dir
*/
public Move(dir: Dir) {
let lineCount = this.getLineCount(dir)
for (let i = 0; i < lineCount; i++) {
let tempLine = this.getNumOneLine(dir, i)
this.MoveOneLine(tempLine)
this.setNumOneLine(tempLine, dir, i)
}
this.GenNewNum()
}
private setNumOneLine(arr: Array<number>, dir: Dir, lineIndex: number) {
let i = lineIndex
let j = lineIndex
let arr_index: number
let arr_length: number = arr.length
for (let k = 0; k < arr_length; k++) {
if (dir == Dir.left || dir == Dir.right) {
i = k
} else {
j = k
}
if (dir == Dir.down || dir == Dir.right) {
arr_index = k
} else {
arr_index = arr_length - 1 - k
}
this.num[i][j] = arr[arr_index]
}
}
2. C# form version
This is a small project I did when I was learning how to make games, so I won’t make any changes and narration, the specific content depends on the project.
3. end
That's all for now, I hope it can provide a reference.