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

阿毛0920
发布于 2022-7-21 10:19
浏览
3收藏

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

前言

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

 #夏日挑战赛# OpenHarmony基于JS实现的贪吃蛇-鸿蒙开发者社区

:::

具体实现思路

容器初始化

  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

 #夏日挑战赛# OpenHarmony基于JS实现的贪吃蛇-鸿蒙开发者社区

:::

蛇的死亡判定

当蛇头的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

<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 * snakeBody}}px;top:{{$item.y * snakeBody}};"
             >
        </div>
    </div>
    <div class="buts">
    <div class="center">
        <button data-value="-20" class="but" @click="clickBut">
               上
        </button>
    </div>
    <div class="center" >
        <button data-value="-10" class="but" @click="clickBut">

               左

        </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

.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};
    }


}

最后

嗯…问就是要优化

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
贪吃蛇.rar 2.81K 46次下载
已于2022-11-22 16:25:11修改
9
收藏 3
回复
举报
8条回复
按时间正序
/
按时间倒序
yukoyu
yukoyu

有意思

回复
2022-7-21 10:33:41
阿毛0920
阿毛0920 回复了 yukoyu
有意思

意思意思?

回复
2022-7-21 10:43:55
红叶亦知秋
红叶亦知秋

从getBoundingClientRect()中读到了楼主对文档的无奈。

回复
2022-7-21 11:00:11
lxj29
lxj29

有点东西哦

回复
2022-7-21 11:39:40
Whyalone
Whyalone

原尾不用去掉吧

吃了一个,长度+1

回复
2022-7-21 16:59:22
阿毛0920
阿毛0920 回复了 Whyalone
原尾不用去掉吧 吃了一个,长度+1

去掉尾巴是移动的时候,通过添加删除来实现移动,长度+1是吃到食物的时候

回复
2022-7-21 17:04:41
Whyalone
Whyalone 回复了 阿毛0920
去掉尾巴是移动的时候,通过添加删除来实现移动,长度+1是吃到食物的时候

确实,看这个1是加到头,还是加到尾了

回复
2022-7-21 18:53:38
SummerRic
SummerRic

有点东西哦+1

回复
2022-7-21 18:56:18
回复
    相关推荐