鸿蒙HarmonyOS NEXT开发:简易贪吃蛇小游戏的实现(撞墙、吃到自己、方向控制、吃到东西长大判断等)

效果图

一、全局变量和状态变量定义

  1. 游戏区域表示(@State area:number [][])

    • area是一个二维数组,它在整个贪吃蛇游戏中扮演着至关重要的角色,代表着游戏的运动区域。这个二维数组中的每个元素都存储着一个数字,这些数字代表着不同的游戏元素状态。
    • 初始状态下,这个数组是空的,通过initArea函数进行初始化。初始化后,它将变成一个 25x25 的二维数组,其中每个位置的值初始为 0。这个 0 值在游戏中代表着空白区域,即没有蛇的身体部分也没有能量(食物)的位置。
  2. 方向控制(@State directions 和 @State tryDirections)

    • directions表示当前运动方向,它可以取值为 'up'(上)、'down'(下)、'left'(左)或 'right'(右)。初始时被设置为 'right',这意味着蛇在游戏开始时将向右移动。
    • tryDirections用于存储玩家尝试改变的方向,同样可以取上述四个方向的值。它的作用是当玩家按下方向控制按钮时,这个变量会被更新为玩家想要尝试的方向。但是,蛇不能立即向相反的方向移动,所以在更新实际的移动方向时,需要检查tryDirections与当前的directions是否冲突。如果不冲突,才会更新directionstryDirections的值。
  3. 历史位置记录(@State historyPosition:number [][])

    • historyPosition是一个数组,其中的每个元素也是一个数组,存储着蛇在游戏过程中经过的位置。这个数组在蛇移动时起着关键作用,用于更新蛇头和蛇尾的位置。
    • 当蛇移动时,蛇头的新位置会被添加到这个数组中。如果蛇没有吃到能量,那么蛇尾的位置会从这个数组中移除,以实现蛇的移动效果。通过记录蛇的历史位置,可以准确地确定蛇的长度和形状,以及在需要时更新蛇的外观。
  4. 头部坐标和长度(@State headX、@State headY 和 @State length)

    • headXheadY分别表示蛇头的横坐标和纵坐标。它们确定了蛇在游戏区域中的具体位置。初始时,蛇头的位置是通过initArea函数设置的,通常在游戏区域的特定位置。
    • length表示蛇的长度。这个变量在游戏开始时根据初始蛇的长度进行设置,并且会随着蛇吃到能量而增加。蛇的长度是衡量游戏进程的一个重要指标,它不仅影响着游戏的难度,还决定了蛇是否占满全屏从而导致游戏结束。
  5. 颜色定义(@State colors:ResourceColor [])

    • colors是一个数组,定义了不同状态所对应的颜色。这个数组中的元素分别对应着游戏中的不同元素。例如,数组中的第一个元素通常是透明颜色,代表空白区域;第二个元素可能是黑色,代表蛇的身体部分;第三个元素可能是橙色,代表能量的颜色。
    • 通过使用这些颜色,可以在游戏界面中清晰地区分不同的元素,使玩家能够直观地看到蛇、能量和空白区域。这样的颜色定义增强了游戏的视觉效果,提高了游戏的可玩性。
  6. 结束提示(@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数组中,以便在游戏过程中能够跟踪蛇的移动。
  • 初始化其他状态变量:除了设置蛇的初始位置,函数还初始化了其他重要的状态变量。它将头部坐标headXheadY设置为蛇头的初始位置,确保蛇头在游戏开始时位于正确的位置。同时,初始化蛇的长度length为初始蛇的长度,通常是根据初始蛇的身体部分数量来确定。此外,清空结束提示信息end,将其设置为空字符串,表示游戏正在进行中。最后,将方向directionstryDirections都设置为右,确定蛇的初始移动方向为向右。
  // 初始化
  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函数进行初始化,确保游戏以初始状态开始。然后,它会随机生成能量(食物)的位置。通过生成两个随机数foodXfoodY,分别代表能量在游戏区域中的横坐标和纵坐标。这两个随机数的范围是游戏区域的宽度和高度,确保能量可以出现在游戏区域的任何位置。
  • 定时更新逻辑:使用setInterval实现游戏的定时更新,每隔 200 毫秒执行一次更新操作。这个定时更新是游戏的核心逻辑之一,它确保了游戏的持续进行和动态变化。
  • 判断是否生成新能量:在每次更新中,如果游戏未结束(即end为空字符串),首先判断是否需要生成新的能量。如果当前位置的能量未被吃到(即area[foodY][foodX]!=2)且蛇的长度未占满全屏(即this.length<25*25),则进入生成新能量的逻辑。通过不断随机生成坐标,直到找到一个值为 0 的位置(即空白位置),将该位置的值设置为 2,表示能量的位置。这个过程确保了在游戏过程中,始终有能量可供蛇吃到,从而增加游戏的趣味性和挑战性。
  • 计算蛇头新位置:根据当前的控制方向和移动方向,计算蛇头的新位置。首先判断尝试改变的方向tryDirections是否与当前移动方向directions不冲突,如果不冲突,则更新当前移动方向为尝试改变的方向。然后根据当前移动方向计算蛇头的新坐标。例如,如果方向为向右,则新的横坐标newHeadX为当前横坐标headX + 1,纵坐标newHeadY保持不变。这个计算过程确保了蛇的移动是符合逻辑的,并且不会出现不合理的移动方向。
  • 判断是否撞墙或撞到自己:检查蛇头的新位置是否在游戏区域内。如果新位置的坐标超出了游戏区域的范围,则视为撞墙,设置结束提示信息为 “撞墙了 游戏结束!”。如果新位置在游戏区域内,进一步检查该位置是否为蛇的身体部分(值为 1)。如果是,则表示蛇吃到了自己,设置结束提示信息为 “吃到自己了 游戏结束!”。如果新位置既不是游戏区域外也不是蛇的身体部分,则进行下一步操作。这个判断过程是游戏的关键逻辑之一,它决定了游戏是否结束以及结束的原因。
  • 处理吃到能量或正常移动:如果新位置的值为 2,表示蛇吃到了能量。将该位置的值设置为 1,将蛇头的位置添加到历史位置数组historyPosition中,并更新蛇头的坐标headXheadY。同时,蛇的长度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)

四、思路总结

一、状态变量的作用和重要性
  1. 游戏区域表示(@State area:number [][])

    • area作为游戏区域的抽象表示,为游戏的进行提供了一个可视化的基础。通过这个二维数组,游戏可以清晰地展示蛇的位置、能量的位置以及空白区域,使玩家能够直观地了解游戏的状态。
    • 其初始化为 25x25 的二维数组,为游戏提供了一个固定大小的空间,让玩家在这个范围内进行游戏。同时,每个位置的初始值为 0,为游戏的开始提供了一个干净的画布,使得游戏的起始状态清晰明了。
  2. 方向控制(@State directions 和 @State tryDirections)

    • directionstryDirections这两个变量共同控制着蛇的移动方向。它们的存在使得玩家可以通过控制按钮来尝试改变蛇的移动方向,增加了游戏的互动性和趣味性。
    • 同时,通过对这两个变量的检查和更新,游戏确保了蛇不能立即向相反的方向移动,避免了不合理的移动情况,增加了游戏的逻辑性和可玩性。
  3. 历史位置记录(@State historyPosition:number [][])

    • historyPosition记录了蛇在游戏过程中的历史位置,这对于更新蛇的外观和判断游戏是否结束至关重要。
    • 通过记录蛇头经过的位置,游戏可以在需要时准确地更新蛇尾的位置,实现蛇的移动效果。同时,这个数组也可以用于判断蛇是否吃到了自己,增加了游戏的挑战性和紧张感。
  4. 头部坐标和长度(@State headX、@State headY 和 @State length)

    • headXheadY确定了蛇头在游戏区域中的具体位置,是蛇在游戏中的关键部分。它们的变化直接影响着游戏的进程和结果。
    • length表示蛇的长度,随着游戏的进行而变化。它不仅是衡量游戏进程的重要指标,还决定了游戏的难度和挑战程度。当蛇的长度增加时,游戏的难度也会相应增加,因为蛇更容易撞到自己或撞墙。
  5. 颜色定义(@State colors:ResourceColor [])

    • colors数组为游戏中的不同元素提供了特定的颜色,增强了游戏的视觉效果。通过不同的颜色区分空白区域、蛇的身体部分和能量,玩家可以更轻松地识别游戏中的不同元素,提高了游戏的可玩性和趣味性。
  6. 结束提示(@State end:string)

    • end变量用于存储游戏结束的提示信息,为玩家提供了明确的游戏结果反馈。当游戏结束时,这个变量会被设置为相应的提示信息,让玩家知道游戏已经结束以及结束的原因。这有助于玩家更好地理解游戏规则和自己的游戏表现。

二、函数定义模块的详细分析
  1. 初始化函数:initArea

    • 这个函数的作用不仅仅是重置游戏的状态,更是为游戏的开始或重新开始提供了一个标准化的起点。
    • 通过重置游戏区域area,它确保了每次游戏开始时,游戏区域都是干净的,没有任何残留的蛇的身体部分或能量。这为玩家提供了一个公平的游戏环境,让他们可以从相同的起点开始游戏。
    • 初始化蛇的位置和历史位置记录,使得游戏在开始时就有一个明确的蛇的形状和位置。这不仅为游戏的进行提供了基础,还让玩家能够清楚地看到蛇的初始状态,更好地理解游戏的规则和操作。
    • 初始化其他状态变量,如头部坐标、长度和方向,为游戏的开始提供了必要的参数设置。这些变量的初始化确保了游戏在开始时处于一个稳定的状态,避免了不必要的错误和混乱。
  2. 游戏逻辑更新函数:aboutToAppear

    • 这个函数是游戏的核心逻辑所在,它负责在游戏进行过程中不断更新游戏的状态,确保游戏的持续进行和动态变化。
    • 初始化游戏状态部分,调用initArea函数进行初始化,为游戏的开始做好准备。然后随机生成能量的位置,为蛇提供了一个目标,增加了游戏的趣味性和挑战性。
    • 定时更新逻辑部分,使用setInterval实现了游戏的定时更新,确保了游戏的流畅性和动态性。每隔 200 毫秒执行一次更新操作,使得游戏的变化不会过于迅速,让玩家有足够的时间做出反应。
    • 判断是否生成新能量部分,根据游戏的当前状态,决定是否需要生成新的能量。如果当前位置的能量未被吃到且蛇的长度未占满全屏,就会进入生成新能量的逻辑。这确保了在游戏过程中,始终有能量可供蛇吃到,保持了游戏的吸引力和挑战性。
    • 计算蛇头新位置部分,根据当前的控制方向和移动方向,准确地计算出蛇头的新位置。这个过程考虑了蛇不能立即向相反方向移动的规则,增加了游戏的逻辑性和可玩性。
    • 判断是否撞墙或撞到自己部分,是游戏结束的关键判断部分。通过检查蛇头的新位置是否在游戏区域内以及是否为蛇的身体部分,决定了游戏是否结束以及结束的原因。这为游戏增加了紧张感和挑战性,让玩家需要时刻注意蛇的位置,避免撞墙或吃到自己。
    • 处理吃到能量或正常移动部分,根据蛇头的新位置是否为能量,决定了蛇的移动和生长方式。如果吃到了能量,蛇的长度会增加,并且蛇头的位置会被更新。如果没有吃到能量,就需要更新蛇尾的位置,实现蛇的移动效果。

三、构建界面模块的深入解读
  1. 在 build 方法中,整体构建了贪吃蛇游戏的界面和交互逻辑。
    • 创建的Column布局作为游戏的主要容器,为游戏的各个元素提供了一个统一的布局框架。
    • 游戏区域和结束提示
      • 第一个Stack布局将游戏区域和结束提示整合在一起,为玩家提供了一个直观的游戏界面。
        • 游戏区域通过嵌套的ColumnForEach循环遍历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%')
  }
}

  • 12
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值