#夏日挑战赛# OpenHarmony基于JS实现的贪吃蛇

Posted 开源基础软件社区官方

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了#夏日挑战赛# OpenHarmony基于JS实现的贪吃蛇相关的知识,希望对你有一定的参考价值。

本文正在参加星光计划3.0–夏日挑战赛

前言

不知道干啥,那就敲代码吧,写个贪吃蛇,很显然,被自己菜哭了,自己写的贪吃蛇自己都不会玩(ps:我曾经可是在不会死亡的情况下完了好长时间>_<)
实现效果如下:
::: hljs-center

:::

具体实现思路

容器初始化

  1. 在onShow钩子函数那里获取到游戏容器的宽高,其实我是不想在这里获取的,但没办法,好像getBoundingClientRect()需要挂载后才能拿到值,在这之前的钩子函数中都拿不到具体的属性值(ps:这是我猜的,因为文档写的很...我找不到只能挨个试)
  2. 拿到容器宽高后,根据蛇的身体长度(就是每个小圆点)来确定要划分多少个格子,形成一个坐标轴,后面食物,蛇的移动都根据这坐标轴来确定
  onShow() // 第一次初始化
        this.conH = this.$refs["con"].getBoundingClientRect().height ;
        this.conW = this.$refs["con"].getBoundingClientRect().width ;
        this.h = Math.floor(this.conH / this.snakeBody);
        this.w = Math.floor(this.conW / this.snakeBody);
        for (var i = 0; i < this.w; i++)  //绘制网格
            this.grid.push([])
            for (var j = 0; j < this.h; j++) 
                this.grid[i].push(
                    x: i,
                    y: j
                );
            
        
        this.init(); //初始化函数

    

用一个数组实现,数组索引0为蛇的尾巴,索引length-1为头

 init()
        const snakePos = [ //蛇的初始化的身体
            
                x : 0,
                y : 0,
                flag : b,
                id : Math.random()
            ,
            
                x : 1,
                y : 0,
                flag : b,
                id : Math.random()
            ,
            
                x : 2,
                y : 0,
                flag : h,
                id : Math.random()
            
        ];
        this.snake.snakePos = snakePos; //把初始化的身体赋给蛇
        this.randomFood(); //随机生成食物
    

食物

食物随机生成,位置在网格中任意位置,但不能生成在蛇的身体位置中

 randomFood() //随机生成食物
        while(true)
            let x = Math.floor(Math.random() * this.conW);
            let y = Math.floor(Math.random() * this.conH);
            x = x - (x % this.snakeBody); //x,y化为和蛇身体倍数的坐标
            y = y - (y % this.snakeBody);
            let is = this.snake.snakePos.find((item)=>
                return item.x == x && item.y == y;
            )
            this.food.x = x;
            this.food.y = y;
            if(!is)  //当食物的位置不为蛇不和蛇的位置重叠就跳出结束死循环
                break;
            
        
    

蛇的移动

蛇的移动是通过对数组的push和shift实现,蛇有移动的方向,根据方向来修改新增蛇头的x和y的值

des://蛇的方向
          "-20": // 向上移动一位
              x:0,
              y:-1,
              flag: 
          ,
          "20"://向下
              x:0,
              y:1,
              flag: 
          ,
          "10": //右
              x:1,
              y:0,
              flag: 
          ,
          "-10": //左
              x:-1,
              y:0,
              flag: 
          
        ,
 addHead(des) 
       //添加蛇头 des为蛇的方向,一共有四个方向上下左右,每次移动是都会传一个方向过来
        const oHead = this.snake.snakePos[this.snake.snakePos.length -1];
        const newHead =
            x : oHead.x + des.x,
            y : oHead.y + des.y,
            flag : h,
            id : Math.random()
        
        this.isEnd(newHead);
        this.snake.snakePos.push(newHead);

        oHead.flag = b;
    ,
    move(des) // 蛇移动时,原头变身体,原尾巴去掉,也就是push一个头,shift一个尾巴
        this.addHead(des);
        this.snake.snakePos.shift();

    ,

移动图如下
::: hljs-center

:::

蛇的死亡判定

当蛇头的x >= 地图的x最大值 || x < 0 || 蛇头的Y >= 地图的Y最大值 || Y < 0 || 蛇头的(x,y) == 蛇身体任意一个 (x,y)

isEnd(newHead) // 判定蛇是是否死亡
        if(newHead.x >= this.w || newHead.x < 0 || newHead.y >= this.h || newHead.y < 0)
            this.setIsEnd();
        

        let is = this.snake.snakePos.find((item)=> //循环查询是否撞到自己
            return item.x == newHead.x && item.y == newHead.y;
        )
        if(is)
            this.setIsEnd(); //结束游戏
        
    ,
setIsEnd()
        clearInterval(this.timeId); //清除蛇的移动定时器
        this.isEndP = true; //这个属性是用来是否显示游戏结果界面
    

操作蛇的移动

-20,20,10,-10,原本是一开用来判定是否当前移动的方向是否和原来的方向冲突,后来发现还是用坐标轴香,也就懒得改了

    intervalMove(d) // 自动跑
        if(!this.isStart) return;//判定是否开始
        clearInterval(this.timeId); //清除以前的定时时器
        this.timeId = setInterval(()=>
            const head = this.snake.snakePos[this.snake.snakePos.length - 1];
            this.move(d);
            if(this.snakeBody * head.x == this.food.x && this.food.y == this.snakeBody * head.y ) //蛇吃到食物
               this.addHead(d); //新增蛇头,这个不去除尾巴
               this.randomFood(); //再次重新生成食物
                this.result++; //分数
            
        ,1000/this.level); //this.level级别,决定蛇移动的速度
    ,
    isCuurDes(value = ,x1,x2)
        // 判断当前蛇的方向,x1 为新方向,x2为以前的方向,主要是判断点击的按钮是否左右,上下冲突
        if((+x1 + +x2) == 0 ) return false; //这里+x1,+x2 是用来把字符串转成数字
        if(this.isEndP) return;//当游戏结束无法再修改方向 
        this.currDes = value; //存下方向
        return true;
    ,
clickBut(m)// 点击按钮
        const value = m.target.dataSet.value;
        switch(value)
            case "-20": //上
              //判断方向是否相反,如果相反则不切换方向
              this.isCuurDes(this.des[value],this.des[value].y,this.currDes.y)
                && this.intervalMove(this.des[value]);
              break;
            
            case "20":// 下
                this.isCuurDes(this.des[value],this.des[value].y,this.currDes.y)
                  && this.intervalMove(this.des[value]);
                break;
            
            case "-10": //左
                this.isCuurDes(this.des[value],this.des[value].x,this.currDes.x)
                  && this.intervalMove(this.des[value]);
                break;
            
            case "10": // 右
                this.isCuurDes(this.des[value],this.des[value].x,this.currDes.x)
                  && this.intervalMove(this.des[value]);
                break;
            
            case "1":  //开始或暂停
                if(this.isEndP) return
                this.isStart = !this.isStart;
                if(this.isStart && !this.isEndP)
                    this.intervalMove(this.currDes);
                else
                    clearInterval(this.timeId);
                
                break;
            
        
    

具体属性和方法

属性

名称 类型 备注
result number 分数
conW number 容器宽度
conH number 容器高度
snakeBody number 蛇身体单位
h number 网格的y长度
w number 网格的x长度
grid Array<Array> 网格地图
food object 食物
timeId number 定时器id
level number 游戏难度级别
des Object<Object> 蛇的四个方向
isStart Boolean 判断是否开始
snake Object<Object>
currDes object 当前蛇前进的方向
isEndP Boolean 判断游戏是否结束

方法

名称 参数 备注
init 初始化函数
onShow 框架生命周期钩子函数
isEnd newHead : object 判断游戏是否结束
setIsEnd 设置游戏结束相关数据
randomFood 随机生成食物
addHead des : object 增加新头
move des : object 蛇的移动
intervalMove d :object 蛇自动移动
isCuurDes value:object ,x1:string,x2:string 定时器id
clickBut m:object 操作蛇的移动的点击事件
reInit 重新开始游戏

代码

html

```html/xml
<div class="container">
<div class="game-container" id="con1" ref="con" >
<div class="food" style="left: food.x px ;top: food.y px;" ></div>
<div for="snake.snakePos" tid="id"
class=" $item.flag == b ?snake-body : snake-head "
style="left: $item.x snakeBodypx;top:$item.y snakeBody;"

           左

    </button>
    <button data-value="1" class="but" @click="clickBut">

            isStart ? 暂停 : 开始 

    </button>
    <button data-value="10" class="but" @click="clickBut">

           右

    </button>
</div>
<div class="center">
    <button data-value="20" class="but" @click="clickBut">

           下
       </button>
    </div>

</div>
<div class="end" if="isEndP == true">
    <div>
        <text>
                 游戏结束!
        </text>
    </div>
    <div>
        <text>
        当前得分:result 分
        </text>
    </div>
    <div @click="reInit" 
         style="width: 120px;margin: 10px 0 0 30px;
                 border-radius: 5px;">
        <text>
            点击重开
        </text>
    </div>
</div>
<div class="result">
      <text>
          分数:result
      </text>
</div>

</div>

## css
```html/xml
.container
    height: 100%;
    width: 100%;

.container,.buts, .end

    display: flex;
    flex-direction: column;
    position: relative;


.game-container
    width: 100%;
    height: 540px;
    position: relative;
    background-color: #85ce61;


.snake-head
    height: 20px;
    width: 20px;
    position: absolute;
    border-radius: 10px;
    background-color: red;

.snake-body
    height: 20px;
    width: 20px;
    position: absolute;
    border-radius: 10px;
    background-color: white;


.food
    height: 20px;
    width: 20px;
    position: absolute;
    border-radius: 10px;
    background-color: black;

.center
    display: flex;
    justify-content:center;
    height: 50px;
    margin:2px 0;

.but
    width: 50px;
    border-radius: 5px;
    font-weight: 700;
    margin: 0 2px ;
    background-color: #85ce61;

.buts
    margin-top:10px ;

.end
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-25%,-100%);

.result
    position: fixed;
    top: 0;
    left: 260px;

JS

export default 
    data: 
        result:0,
        conW : 0,// 游戏界面宽
        conH : 0,
        snakeBody: 20,
        h:0,
        w:0,
        grid:[],
        food:
            x:0,
            y:0
        ,
        timeId:null, // 计时器id
        level:5,//档位
        des://蛇的方向
          "-20": // 向上移动一位
              x:0,
              y:-1,
              flag: 
          ,
          "20"://向下
              x:0,
              y:1,
              flag: 
          ,
          "10": //右
              x:1,
              y:0,
              flag: 
          ,
          "-10": //左
              x:-1,
              y:0,
              flag: 
          
        ,
        isStart:false,
        snake:
            snakePos:null
        ,
        currDes:x:1,y:0,
        isEndP:false

    ,
    init()
        const snakePos = [
            
                x : 0,
                y : 0,
                flag : b,
                id : Math.random()
            ,
            
                x : 1,
                y : 0,
                flag : b,
                id : Math.random()
            ,
            
                x : 2,
                y : 0,
                flag : h,
                id : Math.random()
            
        ];
        this.snake.snakePos = snakePos
        this.randomFood()

    ,
    onShow() // 第一次初始化
        this.conH = this.$refs["con"].getBoundingClientRect().height ;
        this.conW = this.$refs["con"].getBoundingClientRect().width ;
        this.h = Math.floor(this.conH / this.snakeBody);
        this.w = Math.floor(this.conW / this.snakeBody);
        for (var i = 0; i < this.w; i++) 
            this.grid.push([])
            for (var j = 0; j < this.h; j++) 
                this.grid[i].push(
                    x: i,
                    y: j
                );
            
        
        this.init();

    ,
    isEnd(newHead)
        if(newHead.x >= this.w || newHead.x < 0 || newHead.y >= this.h || newHead.y < 0)
            this.setIsEnd();
        

        let is = this.snake.snakePos.find((item)=>
            return item.x == newHead.x && item.y == newHead.y
        )
        if(is)
            this.setIsEnd();
        

    ,
    setIsEnd()
        clearInterval(this.timeId)
        this.isEndP = true
    
    ,
    randomFood() //随机生成食物
        while(true)
            let x = Math.floor(Math.random() * this.conW);
            let y = Math.floor(Math.random() * this.conH);
            x = x - (x % this.snakeBody);
            y = y - (y % this.snakeBody);
            let is = this.snake.snakePos.find((item)=>
                return item.x == x && item.y == y
            )
            this.food.x = x;
            this.food.y = y;
            if(!is) 
                break;
            
        
    ,
    addHead(des)
        const oHead = this.snake.snakePos[this.snake.snakePos.length -1];
        const newHead =
            x : oHead.x + des.x,
            y : oHead.y + des.y,
            flag : h,
            id : Math.random()
        
        this.isEnd(newHead);
        this.snake.snakePos.push(newHead);

        oHead.flag = b;
    ,
    move(des) // 蛇移动时,原头变身体,原尾巴去掉,也就是push一个头,shift一个尾巴
        this.addHead(des);
        this.snake.snakePos.shift();

    ,
    intervalMove(d) // 自动跑
        if(!this.isStart) return
        clearInterval(this.timeId);
        this.timeId = setInterval(()=>
            const head = this.snake.snakePos[this.snake.snakePos.length - 1];
            this.move(d);
            if(this.snakeBody * head.x == this.food.x && this.food.y == this.snakeBody * head.y )
               this.addHead(d);
               this.randomFood();
                this.result++;
            
        ,1000/this.level);
    ,
    isCuurDes(value = ,x1,x2)
        // 判断当前蛇的方向,x1 为新方向,x2为以前的方向,主要是判断点击的按钮是否左右,上下冲突
        if((+x1 + + x2) == 0 ) return false;
        if(this.isEndP) return ;
        this.currDes = value; //存下方向

        return true;
    ,
    clickBut(m)// 点击按钮
        const value = m.target.dataSet.value;
        switch(value)
            case "-20": //上
              //判断方向是否相反,如果相反则不切换方向
              this.isCuurDes(this.des[value],this.des[value].y,this.currDes.y)
                && this.intervalMove(this.des[value]);
              break;
            
            case "20":// 下
                this.isCuurDes(this.des[value],this.des[value].y,this.currDes.y)
                  && this.intervalMove(this.des[value]);
                break;
            
            case "-10": //左
                this.isCuurDes(this.des[value],this.des[value].x,this.currDes.x)
                  && this.intervalMove(this.des[value]);
                break;
            
            case "10": // 右
                this.isCuurDes(this.des[value],this.des[value].x,this.currDes.x)
                  && this.intervalMove(this.des[value]);
                break;
            
            case "1":  //开始或暂停
                if(this.isEndP) return
                this.isStart = !this.isStart;
                if(this.isStart && !this.isEndP)
                    this.intervalMove(this.currDes);
                else
                    clearInterval(this.timeId);
                
                break;
            
        
    ,
    reInit()
        this.init();
        this.isEndP = false;
        this.isStart = false;
        this.currDes=x:1,y:0;
    

最后

嗯...问就是要优化

附件链接:https://ost.51cto.com/resource/2199

想了解更多关于开源的内容,请访问:

51CTO 开源基础软件社区

https://ost.51cto.com/#bkwz

以上是关于#夏日挑战赛# OpenHarmony基于JS实现的贪吃蛇的主要内容,如果未能解决你的问题,请参考以下文章

#夏日挑战赛# OpenHarmony - ArkUI(TS)开发翻页时钟

#夏日挑战赛# 用OpenHarmony eTS 实现一个Huawei app标准布局

#夏日挑战赛# HarmonyOS 实现一个手绘板

#夏日挑战赛# OpenHarmony HiSysEvent打点调用实践(L2)

#夏日挑战赛# HarmonyOS - 基于ArkUI(JS)实现打地鼠游戏

#夏日挑战赛#OHOS构建自定义服务实战