7

OpenHarmony基于JS实现的贪吃蛇

 2 years ago
source link: https://os.51cto.com/article/714691.html
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
89e15bb773ed0e9929d01629abb95e7b810a96.png

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

​51CTO 开源基础软件社区​

​https://ost.51cto.com​

不知道干啥,那就敲代码吧,写个贪吃蛇,很显然,被自己菜哭了,自己写的贪吃蛇自己都不会玩(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实现的贪吃蛇-开源基础软件社区

蛇的死亡判定

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

number

number

snakeBody

number

蛇身体单位

number

网格的y长度

number

网格的x长度

Array<Array>

object

timeId

number

定时器id

level

number

游戏难度级别

Object<Object>

蛇的四个方向

isStart

Boolean

判断是否开始

snake

Object<Object>

currDes

object

当前蛇前进的方向

isEndP

Boolean

判断游戏是否结束

初始化函数

onShow

框架生命周期钩子函数

isEnd

newHead : object

判断游戏是否结束

setIsEnd

设置游戏结束相关数据

randomFood

随机生成食物

addHead

des : object

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​​。


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK