0. 前言
整理一下之前做的一个 2048 游戏,使用 C#实现的。后续也整理出了核心代码,是使用 ts 实现的。
原本想做小程序的,但小程序限制太多而且前端三板斧也没学到位,就搁置了小程序版,就也先把 ts 版的核心逻辑代码都一起放上来了。具体项目实现就不讲太多了,主要讲一下核心的实现,实例代码也就用 ts 展示了,毕竟也整理出来了。如果需要 C#的代码可以看项目,会略有差异,但想法都是一样的。
项目链接如下:
链接:https://pan.baidu.com/s/1VfH4CEXEE9C6zM9tV2N0Pg
提取码:wsad
1. 2048 核心逻辑
2048 的逻辑流程也是很清晰的,主要就是:生成数字,判断能否移动,移动数字。不能再移动也就意味着游戏结束。所以抽像成 3 个函数为 GenNewNum(),GetMoveAble(dir: Dir),Move(dir: Dir)
(1)GenNewNum
生成数字比较简单,找到空位置并尝试生成就可以了,可以直接随机位置,然后尝试生成,这个位置已经有数字了再随机一次。或者把所
有的空位置都找出来,然后再随机,这也是下面使用的。
/**
* 生成新数字
*/
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
判断是否可以移动,有 4 个方向,每个方向都可以独立做处理,但其实每个方向的判断都是类似的。我们可以先考虑第一行情况怎么判断的,只考虑这一行是否可以向左移。那这个判断就可以抽象为 2 个条件,判断及可以如下:
- 1.是否有 0
- 2.是否有相同的数字相邻(隔着 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
}
那做完这一步的之后,就可以,向左方向的其他的行都可以,通过这个函数来判断。那这个时候其他方向的处理,我们做一下处理就好了。
/**
* 检查能否移动
* @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
这里其实也是和上面类似,我们先考虑一行向左移动的处理。首先一行的话,可以按 3 个步骤。
- (*1) moveZero, 我们先把 0 都移动到一边,把有数字的都凑到一块。
- (*2) combine, 然后再把相邻的数合并起来。
- (*3) moveZero, 考虑到数合并之后有空缺,所以最后要再把 0 移动到一边
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]
}
}
移动一行做好了之后那么所有方向的也都可以类似处理了,不过和 GetMoveAble 不同的是,还需要把数再赋值回去。不用太担心赋值还有新建数组的开销,每一步才新建一点位置,问题不大,而这样处理,代码维修起来要容易得多。
/**
* 往这个方向移动
* @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 版
这个是之前在学做游戏的时候做的一个小项目,就不再做更改和讲述了,具体内容就看项目里面了。
3. 结束
到这里也就讲完咯,希望能够提供一个参考。