4

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

 2 years ago
source link: https://blog.51cto.com/harmonyos/5515139
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

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

#夏日挑战赛# 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();

    },

移动图如下

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

蛇的死亡判定

当蛇头的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 重新开始游戏
<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>
.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;
}
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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK