效果图
一、全局变量和状态变量定义
-
游戏区域表示(@State area:number [][]):
area
是一个二维数组,它在整个贪吃蛇游戏中扮演着至关重要的角色,代表着游戏的运动区域。这个二维数组中的每个元素都存储着一个数字,这些数字代表着不同的游戏元素状态。- 初始状态下,这个数组是空的,通过
initArea
函数进行初始化。初始化后,它将变成一个 25x25 的二维数组,其中每个位置的值初始为 0。这个 0 值在游戏中代表着空白区域,即没有蛇的身体部分也没有能量(食物)的位置。
-
方向控制(@State directions 和 @State tryDirections):
directions
表示当前运动方向,它可以取值为 'up'(上)、'down'(下)、'left'(左)或 'right'(右)。初始时被设置为 'right',这意味着蛇在游戏开始时将向右移动。tryDirections
用于存储玩家尝试改变的方向,同样可以取上述四个方向的值。它的作用是当玩家按下方向控制按钮时,这个变量会被更新为玩家想要尝试的方向。但是,蛇不能立即向相反的方向移动,所以在更新实际的移动方向时,需要检查tryDirections
与当前的directions
是否冲突。如果不冲突,才会更新directions
为tryDirections
的值。
-
历史位置记录(@State historyPosition:number [][]):
historyPosition
是一个数组,其中的每个元素也是一个数组,存储着蛇在游戏过程中经过的位置。这个数组在蛇移动时起着关键作用,用于更新蛇头和蛇尾的位置。- 当蛇移动时,蛇头的新位置会被添加到这个数组中。如果蛇没有吃到能量,那么蛇尾的位置会从这个数组中移除,以实现蛇的移动效果。通过记录蛇的历史位置,可以准确地确定蛇的长度和形状,以及在需要时更新蛇的外观。
-
头部坐标和长度(@State headX、@State headY 和 @State length):
headX
和headY
分别表示蛇头的横坐标和纵坐标。它们确定了蛇在游戏区域中的具体位置。初始时,蛇头的位置是通过initArea
函数设置的,通常在游戏区域的特定位置。length
表示蛇的长度。这个变量在游戏开始时根据初始蛇的长度进行设置,并且会随着蛇吃到能量而增加。蛇的长度是衡量游戏进程的一个重要指标,它不仅影响着游戏的难度,还决定了蛇是否占满全屏从而导致游戏结束。
-
颜色定义(@State colors:ResourceColor []):
colors
是一个数组,定义了不同状态所对应的颜色。这个数组中的元素分别对应着游戏中的不同元素。例如,数组中的第一个元素通常是透明颜色,代表空白区域;第二个元素可能是黑色,代表蛇的身体部分;第三个元素可能是橙色,代表能量的颜色。- 通过使用这些颜色,可以在游戏界面中清晰地区分不同的元素,使玩家能够直观地看到蛇、能量和空白区域。这样的颜色定义增强了游戏的视觉效果,提高了游戏的可玩性。
-
结束提示(@State end:string):
end
是一个字符串变量,用于存储游戏结束的提示信息。初始状态下,它是空字符串,表示游戏正在进行中。- 当游戏结束时,根据结束的原因,这个变量会被设置为相应的提示信息。例如,如果蛇吃到了自己,
end
将被设置为 “吃到自己了 游戏结束!”;如果蛇撞墙了,end
将被设置为 “撞墙了 游戏结束!”。这个提示信息可以在游戏界面中显示给玩家,让他们知道游戏已经结束以及结束的原因。
// 二维数组 表示运动区域
@State area:number[][] = []
// 当前运动方向
@State directions: 'up'| 'down'| 'left'| 'right' = 'right'
// 控制方向
@State tryDirections: 'up'| 'down'| 'left'| 'right' = 'right'
// 历史位置 用于蛇移动时更新蛇头和蛇尾
@State historyPosition:number[][]=[]
// 头部坐标
@State headX:number = 0
@State headY:number = 0
// 蛇的长度
@State length:number = 0
// 颜色 不同的表示渲染不同等等颜色
@State colors:ResourceColor[]=[Color.Transparent, Color.Black, Color.Orange]
//结束提示
@State end:string=''
二、函数定义模块
初始化函数:initArea:
- 重置游戏区域:这个函数首先负责重置游戏区域
area
。它通过两个嵌套的循环,将area
初始化为一个 25x25 的二维数组,每个位置的值都设置为 0。这一步确保了游戏在每次开始或重新开始时,游戏区域都是干净的,没有任何蛇的身体部分或能量。 - 初始化蛇的位置:接着,函数在特定的位置设置蛇的初始位置。例如,将
area[4][6]
到area[4][10]
的值设置为 1,表示蛇的身体部分。这意味着在游戏开始时,蛇的身体占据了这些位置。同时,将这些位置添加到historyPosition
数组中,以便在游戏过程中能够跟踪蛇的移动。 - 初始化其他状态变量:除了设置蛇的初始位置,函数还初始化了其他重要的状态变量。它将头部坐标
headX
和headY
设置为蛇头的初始位置,确保蛇头在游戏开始时位于正确的位置。同时,初始化蛇的长度length
为初始蛇的长度,通常是根据初始蛇的身体部分数量来确定。此外,清空结束提示信息end
,将其设置为空字符串,表示游戏正在进行中。最后,将方向directions
和tryDirections
都设置为右,确定蛇的初始移动方向为向右。
// 初始化
initArea(){
// 25 * 25 初始化地图
this.area = []
for(let i=0;i<25;i++){
this.area.push([])
for(let j=0;j<25;j++){
this.area[i].push(0)
}
}
// 对蛇初始化
this.area[4][6]=1
this.area[4][7]=1
this.area[4][8]=1
this.area[4][9]=1
this.area[4][10]=1
// 初始化历史位置
this.historyPosition=[]
this.historyPosition.push([6,4])
this.historyPosition.push([7,4])
this.historyPosition.push([8,4])
this.historyPosition.push([9,4])
this.historyPosition.push([10,4])
// 初始化头部位置
this.headX = 10
this.headY = 4
// 初始化蛇的长度
this.length = 5
// 初始化 结束提示
this.end=''
// 初始化方向以及想尝试的方向 为右
this.directions = 'right'
this.tryDirections = 'right'
}
游戏逻辑更新函数:aboutToAppear(生命周期函数):
- 初始化游戏状态:这个函数在游戏开始时被调用,首先它会调用
initArea
函数进行初始化,确保游戏以初始状态开始。然后,它会随机生成能量(食物)的位置。通过生成两个随机数foodX
和foodY
,分别代表能量在游戏区域中的横坐标和纵坐标。这两个随机数的范围是游戏区域的宽度和高度,确保能量可以出现在游戏区域的任何位置。 - 定时更新逻辑:使用
setInterval
实现游戏的定时更新,每隔 200 毫秒执行一次更新操作。这个定时更新是游戏的核心逻辑之一,它确保了游戏的持续进行和动态变化。 - 判断是否生成新能量:在每次更新中,如果游戏未结束(即
end
为空字符串),首先判断是否需要生成新的能量。如果当前位置的能量未被吃到(即area[foodY][foodX]!=2
)且蛇的长度未占满全屏(即this.length<25*25
),则进入生成新能量的逻辑。通过不断随机生成坐标,直到找到一个值为 0 的位置(即空白位置),将该位置的值设置为 2,表示能量的位置。这个过程确保了在游戏过程中,始终有能量可供蛇吃到,从而增加游戏的趣味性和挑战性。 - 计算蛇头新位置:根据当前的控制方向和移动方向,计算蛇头的新位置。首先判断尝试改变的方向
tryDirections
是否与当前移动方向directions
不冲突,如果不冲突,则更新当前移动方向为尝试改变的方向。然后根据当前移动方向计算蛇头的新坐标。例如,如果方向为向右,则新的横坐标newHeadX
为当前横坐标headX + 1
,纵坐标newHeadY
保持不变。这个计算过程确保了蛇的移动是符合逻辑的,并且不会出现不合理的移动方向。 - 判断是否撞墙或撞到自己:检查蛇头的新位置是否在游戏区域内。如果新位置的坐标超出了游戏区域的范围,则视为撞墙,设置结束提示信息为 “撞墙了 游戏结束!”。如果新位置在游戏区域内,进一步检查该位置是否为蛇的身体部分(值为 1)。如果是,则表示蛇吃到了自己,设置结束提示信息为 “吃到自己了 游戏结束!”。如果新位置既不是游戏区域外也不是蛇的身体部分,则进行下一步操作。这个判断过程是游戏的关键逻辑之一,它决定了游戏是否结束以及结束的原因。
- 处理吃到能量或正常移动:如果新位置的值为 2,表示蛇吃到了能量。将该位置的值设置为 1,将蛇头的位置添加到历史位置数组
historyPosition
中,并更新蛇头的坐标headX
和headY
。同时,蛇的长度length
加一。如果新位置不是能量,则需要更新蛇尾的位置。获取尾巴的位置(即历史位置数组的第一个元素),将旧尾巴的位置在游戏区域中的值设置为 0,表示尾巴移动。然后从历史位置数组中移除尾巴的位置。这个处理过程实现了蛇的移动和生长,根据蛇是否吃到能量来决定如何更新蛇的位置和长度。
aboutToAppear(): void {
// 初始化
this.initArea()
// 随机生成能量的位置
let foodX:number = Math.floor(Math.random()*this.area[0].length)
let foodY:number = Math.floor(Math.random()*this.area[0].length)
// 控制蛇的移动(更新坐标)
setInterval(()=>{
// 游戏结束后不做操作
if(!this.end){
// 上一个还没吃到 或 蛇的长度占满全屏 不用生成
if(this.area[foodY][foodX]!=2&&this.length<25*25){
while(true){
// 生成的随机坐标为0时即为空位置时才生成能量退出循环
if(this.area[foodY][foodX]==0){
this.area[foodY][foodX]=2
break
}
// 继续生成
foodX = Math.floor(Math.random()*this.area[0].length)
foodY = Math.floor(Math.random()*this.area[0].length)
}
}
// 移动后头部的新位置
let newHeadY:number
let newHeadX:number
// 当控制方向和当前移动的方向满足条件时 才改变当前位置 避免蛇头往身后走
if(this.tryDirections == 'right' && this.directions != 'left') this.directions = 'right'
else if(this.tryDirections == 'left' && this.directions != 'right') this.directions = 'left'
else if(this.tryDirections == 'up' && this.directions != 'down') this.directions = 'up'
else if(this.tryDirections == 'down' && this.directions != 'up') this.directions = 'down'
// 不同的方向更新不同的头部
if(this.directions == 'right'){
newHeadY = this.headY
newHeadX = this.headX+1
}else if(this.directions == 'up'){
newHeadY = this.headY-1
newHeadX = this.headX
}else if(this.directions == 'down'){
newHeadY = this.headY+1
newHeadX = this.headX
}else{
newHeadY = this.headY
newHeadX = this.headX-1
}
// 判断是否撞墙
if(newHeadY>=0 && newHeadY<this.area.length && newHeadX>=0 && newHeadX<this.area[0].length){
// 未撞墙
// 头部撞到自己
if(this.area[newHeadY][newHeadX]==1){
this.end = '吃到自己了 游戏结束!'
} // 未撞到
else{
// 是否吃到能量
let eat:boolean = false
// 如果头碰到2 则视为吃到能量
if(this.area[newHeadY][newHeadX] == 2) eat = true
// 将头的位置放入历史位置中
this.historyPosition.push([newHeadX,newHeadY])
// 获取头部的那一行
let newRowHead = this.area[newHeadY]
// 将头部的位置置为1 即变为蛇的一部分
newRowHead[newHeadX] = 1
// 更新蛇头的位置
this.headY = newHeadY
this.headX = newHeadX
// 更新蛇头的那一行
this.area.splice(newHeadY,1,newRowHead)
// 吃到了能量 无需更新尾巴 长度加一
if(!eat){
// 没吃到能量 更新尾巴的那一行
// 获取尾巴的位置
let tailX = this.historyPosition[0][0]
let tailY = this.historyPosition[0][1]
// 移除尾巴
this.historyPosition.shift()
// 获取旧的尾巴的那一行
let pastRowTail = this.area[tailY]
// 将旧尾巴的位置置为0 表示尾巴移动
pastRowTail[tailX] = 0
// 更新旧的尾巴的那一行
this.area.splice(tailY,1,pastRowTail)
}else {
// 吃到了 长度加一即可
this.length++
}
}
} //跑出边界 即为撞墙
else this.end='撞墙了 游戏结束!'
}
},200) //200ms动一次
}
三、构建界面模块
在 build
方法中,整体构建了贪吃蛇游戏的界面和交互逻辑。
- 创建了一个
Column
布局,作为游戏的主要容器。 - 游戏区域和结束提示:
- 第一个
Stack
布局包含游戏区域和结束提示。- 游戏区域通过嵌套的
Column
和ForEach
循环遍历area
数组,对每个位置进行渲染。对于每个位置,根据位置是否为蛇头、是否为能量以及是否为蛇的身体部分,设置不同的背景颜色和显示内容。如果位置是蛇头,则显示一个特定的字符(如 “・”),并设置为白色、加粗字体。如果位置是蛇的身体部分,则根据历史位置数组判断该位置是否为蛇尾,如果是蛇尾,则设置为粉色背景,否则根据颜色数组设置为蛇的身体颜色。如果位置是能量,则设置为橙色背景。 - 结束提示部分在游戏结束时显示相应的提示信息和重新开始按钮。当
end
不为空字符串时,显示结束提示信息,并显示重新开始按钮。重新开始按钮点击时调用initArea
函数重新初始化游戏。
- 游戏区域通过嵌套的
- 第一个
Stack(){
// 区域
Column(){
// 遍历每一行
ForEach(this.area,(itemR:number[], indexR:number)=>{
Row(){
// 遍历每行的每一个位置
ForEach(itemR,(itemC:number, indexC:number)=>{
// 对每一个位置进行渲染 0:空位置 1:蛇的部位 2:能量 以及是否为蛇头
Text(indexR == this.headY && indexC == this.headX?'·' : '')
.textAlign(TextAlign.Center)
.fontSize(12)
.fontWeight(900)
.fontColor(Color.White)
.width(12)
.aspectRatio(1)
.backgroundColor(this.historyPosition[this.historyPosition.length-1][0] == indexC &&
this.historyPosition[this.historyPosition.length-1][1] == indexR?
Color.Pink : this.colors[this.area[indexR][indexC]])
})
}
})
}
.borderWidth(2)
.borderColor(Color.Orange)
// 结束时展示提示
Column({space:10}){
Text(this.end)
.fontSize(20)
.fontColor(Color.Red)
Text(this.end?'重新开始':'')
.fontSize(22)
.fontColor(Color.Red)
.onClick(()=>{
this.initArea()
})
}
.zIndex(this.end?1:-1) //结束时置z轴为1 展示出来
}
- 控制按钮:
- 第二个
Stack
布局包含上、下、左、右四个方向的控制按钮。每个按钮设置了不同的属性,如字体大小、高度、宽度、位置等。点击每个按钮时,更新tryDirections
的值,以尝试改变蛇的移动方向。当游戏结束时(即end
不为空字符串),控制按钮被禁用,通过设置enabled
属性为 false 实现。
- 第二个
// 控制键
Stack(){
// 上、下、左、右 逻辑一样 以"上"操作为例
Button('上')
.fontSize(20)
.height(50)
.width(80)
.position({top:0, left:'50%'})
.translate({x:'-50%'})
.onClick(()=>{
this.tryDirections = 'up'
})
Button('下')
.fontSize(20)
.height(50)
.width(80)
.position({bottom:0, left:'50%'})
.translate({x:'-50%'})
.onClick(()=>{
this.tryDirections = 'down'
})
Button('左')
.fontSize(20)
.height(50)
.width(70)
.position({left:0, top:'50%'})
.translate({y:'-50%'})
.offset({left:0})
.onClick(()=>{
this.tryDirections = 'left'
})
Button('右')
.fontSize(20)
.height(50)
.width(70)
.position({right:0, top:'50%'})
.translate({y:'-50%'})
.onClick(()=>{
this.tryDirections = 'right'
})
}
.enabled(this.end?false:true) // 游戏结束时为禁用态 按钮失效
.width('45%')
.aspectRatio(1)
四、思路总结
一、状态变量的作用和重要性
-
游戏区域表示(@State area:number [][]):
area
作为游戏区域的抽象表示,为游戏的进行提供了一个可视化的基础。通过这个二维数组,游戏可以清晰地展示蛇的位置、能量的位置以及空白区域,使玩家能够直观地了解游戏的状态。- 其初始化为 25x25 的二维数组,为游戏提供了一个固定大小的空间,让玩家在这个范围内进行游戏。同时,每个位置的初始值为 0,为游戏的开始提供了一个干净的画布,使得游戏的起始状态清晰明了。
-
方向控制(@State directions 和 @State tryDirections):
directions
和tryDirections
这两个变量共同控制着蛇的移动方向。它们的存在使得玩家可以通过控制按钮来尝试改变蛇的移动方向,增加了游戏的互动性和趣味性。- 同时,通过对这两个变量的检查和更新,游戏确保了蛇不能立即向相反的方向移动,避免了不合理的移动情况,增加了游戏的逻辑性和可玩性。
-
历史位置记录(@State historyPosition:number [][]):
historyPosition
记录了蛇在游戏过程中的历史位置,这对于更新蛇的外观和判断游戏是否结束至关重要。- 通过记录蛇头经过的位置,游戏可以在需要时准确地更新蛇尾的位置,实现蛇的移动效果。同时,这个数组也可以用于判断蛇是否吃到了自己,增加了游戏的挑战性和紧张感。
-
头部坐标和长度(@State headX、@State headY 和 @State length):
headX
和headY
确定了蛇头在游戏区域中的具体位置,是蛇在游戏中的关键部分。它们的变化直接影响着游戏的进程和结果。length
表示蛇的长度,随着游戏的进行而变化。它不仅是衡量游戏进程的重要指标,还决定了游戏的难度和挑战程度。当蛇的长度增加时,游戏的难度也会相应增加,因为蛇更容易撞到自己或撞墙。
-
颜色定义(@State colors:ResourceColor []):
colors
数组为游戏中的不同元素提供了特定的颜色,增强了游戏的视觉效果。通过不同的颜色区分空白区域、蛇的身体部分和能量,玩家可以更轻松地识别游戏中的不同元素,提高了游戏的可玩性和趣味性。
-
结束提示(@State end:string):
end
变量用于存储游戏结束的提示信息,为玩家提供了明确的游戏结果反馈。当游戏结束时,这个变量会被设置为相应的提示信息,让玩家知道游戏已经结束以及结束的原因。这有助于玩家更好地理解游戏规则和自己的游戏表现。
二、函数定义模块的详细分析
-
初始化函数:initArea:
- 这个函数的作用不仅仅是重置游戏的状态,更是为游戏的开始或重新开始提供了一个标准化的起点。
- 通过重置游戏区域
area
,它确保了每次游戏开始时,游戏区域都是干净的,没有任何残留的蛇的身体部分或能量。这为玩家提供了一个公平的游戏环境,让他们可以从相同的起点开始游戏。 - 初始化蛇的位置和历史位置记录,使得游戏在开始时就有一个明确的蛇的形状和位置。这不仅为游戏的进行提供了基础,还让玩家能够清楚地看到蛇的初始状态,更好地理解游戏的规则和操作。
- 初始化其他状态变量,如头部坐标、长度和方向,为游戏的开始提供了必要的参数设置。这些变量的初始化确保了游戏在开始时处于一个稳定的状态,避免了不必要的错误和混乱。
-
游戏逻辑更新函数:aboutToAppear:
- 这个函数是游戏的核心逻辑所在,它负责在游戏进行过程中不断更新游戏的状态,确保游戏的持续进行和动态变化。
- 初始化游戏状态部分,调用
initArea
函数进行初始化,为游戏的开始做好准备。然后随机生成能量的位置,为蛇提供了一个目标,增加了游戏的趣味性和挑战性。 - 定时更新逻辑部分,使用
setInterval
实现了游戏的定时更新,确保了游戏的流畅性和动态性。每隔 200 毫秒执行一次更新操作,使得游戏的变化不会过于迅速,让玩家有足够的时间做出反应。 - 判断是否生成新能量部分,根据游戏的当前状态,决定是否需要生成新的能量。如果当前位置的能量未被吃到且蛇的长度未占满全屏,就会进入生成新能量的逻辑。这确保了在游戏过程中,始终有能量可供蛇吃到,保持了游戏的吸引力和挑战性。
- 计算蛇头新位置部分,根据当前的控制方向和移动方向,准确地计算出蛇头的新位置。这个过程考虑了蛇不能立即向相反方向移动的规则,增加了游戏的逻辑性和可玩性。
- 判断是否撞墙或撞到自己部分,是游戏结束的关键判断部分。通过检查蛇头的新位置是否在游戏区域内以及是否为蛇的身体部分,决定了游戏是否结束以及结束的原因。这为游戏增加了紧张感和挑战性,让玩家需要时刻注意蛇的位置,避免撞墙或吃到自己。
- 处理吃到能量或正常移动部分,根据蛇头的新位置是否为能量,决定了蛇的移动和生长方式。如果吃到了能量,蛇的长度会增加,并且蛇头的位置会被更新。如果没有吃到能量,就需要更新蛇尾的位置,实现蛇的移动效果。
三、构建界面模块的深入解读
- 在
build
方法中,整体构建了贪吃蛇游戏的界面和交互逻辑。- 创建的
Column
布局作为游戏的主要容器,为游戏的各个元素提供了一个统一的布局框架。 - 游戏区域和结束提示:
- 第一个
Stack
布局将游戏区域和结束提示整合在一起,为玩家提供了一个直观的游戏界面。- 游戏区域通过嵌套的
Column
和ForEach
循环遍历area
数组,对每个位置进行渲染。这种方式使得游戏区域的显示非常灵活,可以根据游戏的状态动态地更新每个位置的显示内容。 - 对于蛇头、蛇的身体部分和能量的不同显示方式,增强了游戏的视觉效果,让玩家能够清晰地分辨不同的游戏元素。同时,根据历史位置数组判断蛇尾的位置并设置不同的背景颜色,进一步增加了游戏的可视化效果。
- 结束提示部分在游戏结束时显示相应的提示信息和重新开始按钮,为玩家提供了明确的游戏结果反馈和继续游戏的机会。重新开始按钮的点击事件调用
initArea
函数重新初始化游戏,使得玩家可以方便地重新开始游戏,提高了游戏的可玩性。
- 游戏区域通过嵌套的
- 第一个
- 控制按钮:
- 第二个
Stack
布局包含上、下、左、右四个方向的控制按钮,为玩家提供了操作游戏的方式。每个按钮设置了不同的属性,使得它们在游戏界面中易于识别和操作。 - 点击每个按钮时,更新
tryDirections
的值,以尝试改变蛇的移动方向。这种交互方式使得玩家可以通过简单的点击操作来控制蛇的移动,增加了游戏的互动性和趣味性。 - 当游戏结束时,控制按钮被禁用,避免了玩家在游戏结束后进行不必要的操作。这不仅提高了游戏的稳定性,还为玩家提供了一个明确的游戏结束状态。
- 第二个
- 创建的
完整代码:
@Entry
@Component
struct Page2 {
// 二维数组 表示运动区域
@State area:number[][] = []
// 当前运动方向
@State directions: 'up'| 'down'| 'left'| 'right' = 'right'
// 控制方向
@State tryDirections: 'up'| 'down'| 'left'| 'right' = 'right'
// 历史位置 用于蛇移动时更新蛇头和蛇尾
@State historyPosition:number[][]=[]
// 头部坐标
@State headX:number = 0
@State headY:number = 0
// 蛇的长度
@State length:number = 0
// 颜色 不同的表示渲染不同等等颜色
@State colors:ResourceColor[]=[Color.Transparent, Color.Black, Color.Orange]
//结束提示
@State end:string=''
// 初始化
initArea(){
// 25 * 25 初始化地图
this.area = []
for(let i=0;i<25;i++){
this.area.push([])
for(let j=0;j<25;j++){
this.area[i].push(0)
}
}
// 对蛇初始化
this.area[4][6]=1
this.area[4][7]=1
this.area[4][8]=1
this.area[4][9]=1
this.area[4][10]=1
// 初始化历史位置
this.historyPosition=[]
this.historyPosition.push([6,4])
this.historyPosition.push([7,4])
this.historyPosition.push([8,4])
this.historyPosition.push([9,4])
this.historyPosition.push([10,4])
// 初始化头部位置
this.headX = 10
this.headY = 4
// 初始化蛇的长度
this.length = 5
// 初始化 结束提示
this.end=''
// 初始化方向以及想尝试的方向 为右
this.directions = 'right'
this.tryDirections = 'right'
}
aboutToAppear(): void {
// 初始化
this.initArea()
// 随机生成能量的位置
let foodX:number = Math.floor(Math.random()*this.area[0].length)
let foodY:number = Math.floor(Math.random()*this.area[0].length)
// 控制蛇的移动(更新坐标)
setInterval(()=>{
// 游戏结束后不做操作
if(!this.end){
// 上一个还没吃到 或 蛇的长度占满全屏 不用生成
if(this.area[foodY][foodX]!=2&&this.length<25*25){
while(true){
// 生成的随机坐标为0时即为空位置时才生成能量退出循环
if(this.area[foodY][foodX]==0){
this.area[foodY][foodX]=2
break
}
// 继续生成
foodX = Math.floor(Math.random()*this.area[0].length)
foodY = Math.floor(Math.random()*this.area[0].length)
}
}
// 移动后头部的新位置
let newHeadY:number
let newHeadX:number
// 当控制方向和当前移动的方向满足条件时 才改变当前位置 避免蛇头往身后走
if(this.tryDirections == 'right' && this.directions != 'left') this.directions = 'right'
else if(this.tryDirections == 'left' && this.directions != 'right') this.directions = 'left'
else if(this.tryDirections == 'up' && this.directions != 'down') this.directions = 'up'
else if(this.tryDirections == 'down' && this.directions != 'up') this.directions = 'down'
// 不同的方向更新不同的头部
if(this.directions == 'right'){
newHeadY = this.headY
newHeadX = this.headX+1
}else if(this.directions == 'up'){
newHeadY = this.headY-1
newHeadX = this.headX
}else if(this.directions == 'down'){
newHeadY = this.headY+1
newHeadX = this.headX
}else{
newHeadY = this.headY
newHeadX = this.headX-1
}
// 判断是否撞墙
if(newHeadY>=0 && newHeadY<this.area.length && newHeadX>=0 && newHeadX<this.area[0].length){
// 未撞墙
// 头部撞到自己
if(this.area[newHeadY][newHeadX]==1){
this.end = '吃到自己了 游戏结束!'
} // 未撞到
else{
// 是否吃到能量
let eat:boolean = false
// 如果头碰到2 则视为吃到能量
if(this.area[newHeadY][newHeadX] == 2) eat = true
// 将头的位置放入历史位置中
this.historyPosition.push([newHeadX,newHeadY])
// 获取头部的那一行
let newRowHead = this.area[newHeadY]
// 将头部的位置置为1 即变为蛇的一部分
newRowHead[newHeadX] = 1
// 更新蛇头的位置
this.headY = newHeadY
this.headX = newHeadX
// 更新蛇头的那一行
this.area.splice(newHeadY,1,newRowHead)
// 吃到了能量 无需更新尾巴 长度加一
if(!eat){
// 没吃到能量 更新尾巴的那一行
// 获取尾巴的位置
let tailX = this.historyPosition[0][0]
let tailY = this.historyPosition[0][1]
// 移除尾巴
this.historyPosition.shift()
// 获取旧的尾巴的那一行
let pastRowTail = this.area[tailY]
// 将旧尾巴的位置置为0 表示尾巴移动
pastRowTail[tailX] = 0
// 更新旧的尾巴的那一行
this.area.splice(tailY,1,pastRowTail)
}else {
// 吃到了 长度加一即可
this.length++
}
}
} //跑出边界 即为撞墙
else this.end='撞墙了 游戏结束!'
}
},200) //200ms动一次
}
build() {
Column() {
Stack(){
// 区域
Column(){
// 遍历每一行
ForEach(this.area,(itemR:number[], indexR:number)=>{
Row(){
// 遍历每行的每一个位置
ForEach(itemR,(itemC:number, indexC:number)=>{
// 对每一个位置进行渲染 0:空位置 1:蛇的部位 2:能量 以及是否为蛇头
Text(indexR == this.headY && indexC == this.headX?'·' : '')
.textAlign(TextAlign.Center)
.fontSize(12)
.fontWeight(900)
.fontColor(Color.White)
.width(12)
.aspectRatio(1)
.backgroundColor(this.historyPosition[this.historyPosition.length-1][0] == indexC &&
this.historyPosition[this.historyPosition.length-1][1] == indexR?
Color.Pink : this.colors[this.area[indexR][indexC]])
})
}
})
}
.borderWidth(2)
.borderColor(Color.Orange)
// 结束时展示提示
Column({space:10}){
Text(this.end)
.fontSize(20)
.fontColor(Color.Red)
Text(this.end?'重新开始':'')
.fontSize(22)
.fontColor(Color.Red)
.onClick(()=>{
this.initArea()
})
}
.zIndex(this.end?1:-1) //结束时置z轴为1 展示出来
}
// 控制键
Stack(){
// 上、下、左、右 逻辑一样 以"上"操作为例
Button('上')
.fontSize(20)
.height(50)
.width(80)
.position({top:0, left:'50%'})
.translate({x:'-50%'})
.onClick(()=>{
this.tryDirections = 'up'
})
Button('下')
.fontSize(20)
.height(50)
.width(80)
.position({bottom:0, left:'50%'})
.translate({x:'-50%'})
.onClick(()=>{
this.tryDirections = 'down'
})
Button('左')
.fontSize(20)
.height(50)
.width(70)
.position({left:0, top:'50%'})
.translate({y:'-50%'})
.offset({left:0})
.onClick(()=>{
this.tryDirections = 'left'
})
Button('右')
.fontSize(20)
.height(50)
.width(70)
.position({right:0, top:'50%'})
.translate({y:'-50%'})
.onClick(()=>{
this.tryDirections = 'right'
})
}
.enabled(this.end?false:true) // 游戏结束时为禁用态 按钮失效
.width('45%')
.aspectRatio(1)
}
.height('100%')
.width('100%')
}
}