SpringBoot项目——创建菜单与游戏页面

SpringBoot项目——vue 实现游戏页面

回顾:

SpringBoot项目——配置git环境与项目创建


vue 实现前端页面——Web

呈现效果如下:

在这里插入图片描述

一、导航栏功能+PK地图的实现
  • 导航栏组件及所路由跳转各组件代码如下:
// NavBar.vue
<template>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <router-link class="navbar-brand" :to="{name: 'home'}">King of Bots</router-link>
            <div class="collapse navbar-collapse" id="navbarText">
                <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                    <li class="nav-item">
                        <!-- 加冒号使得属性可以赋值为一个表达式,实现点击active -->
                        <router-link :class="route_name == 'pk_index' ? 'nav-link active' : 'nav-link'" :to="{name: 'pk_index'}">对战</router-link>
                    </li>
                    <li class="nav-item">
                        <router-link :class="route_name == 'record_index' ? 'nav-link active' : 'nav-link'" :to="{name: 'record_index'}">对局列表</router-link>
                    </li>
                    <li class="nav-item">
                        <router-link :class="route_name == 'ranklist_index' ? 'nav-link active' : 'nav-link'" :to="{name: 'ranklist_index'}">排行榜</router-link>
                    </li>
                </ul>
                <ul class="navbar-nav">
                    <li class="nav-item dropdown">
                        <a class="nav-link dropdown-toggle active" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
                            Lijiao
                        </a>
                        <ul class="dropdown-menu" aria-labelledby="navbarDropdown">
                            <li><router-link class="dropdown-item" :to="{name: 'user_bot_index'}">我的Bot</router-link></li>
                            <li><a class="dropdown-item" href="#">退出</a></li>
                            <li><hr class="dropdown-divider"></li>
                            <li><a class="dropdown-item" href="#">Something else here</a></li>
                        </ul>
                    </li>
                </ul>
            </div>
        </div>
    </nav>

</template>

<script>
// 为了判断当前url在哪个页面
import {
    
     useRoute } from 'vue-router'
import {
    
     computed } from 'vue'

export default {
    
    
    name: 'NavBar',
    setup() {
    
    
        const route = useRoute();
        let route_name = computed(() => route.name);

        return{
    
    
            route_name,
        }
    },
}
</script>

<style scoped>
</style>

路由跳转到的组件PKIndexViewRecordIndexViewRanklistIndexViewUserBotIndexViewNotFound

// PKIndexView.vue
<template>
    <PKPlayGround/>
</template>

<script>
import PKPlayGround from '@/components/PKPlayGround.vue'

export default {
    
    
    name: 'PKIndexView',
    components: {
    
    
        PKPlayGround,
    },
    setup() {
    
    
        
    },
}
</script>

<style scoped>

</style>

路由操作:

// router/index.js
import {
    
     createRouter, createWebHistory } from 'vue-router'
import PKIndexView from '../views/pk/PKIndexView.vue'
import RecordIndexView from '@/views/record/RecordIndexView'
import RanklistIndexView from '@/views/ranklist/RanklistIndexView'
import UserBotIndexView from '@/views/user/bot/UserBotIndexView'
import NotFound from '@/views/error/NotFound'

const routes = [  
  {
    
    
    path: "/",
    name: "home",
    redirect: "/pk/",
  },
  {
    
    
    path: "/pk/",
    name: "pk_index",
    component: PKIndexView,
  },
  {
    
    
    path: "/record/",
    name: "record_index",
    component: RecordIndexView,
  },
  {
    
    
    path: "/ranklist/",
    name: "ranklist_index",
    component: RanklistIndexView,
  },
  {
    
    
    path: "/user/bot/",
    name: "user_bot_index",
    component: UserBotIndexView,
  },
  {
    
    
    path: "/404/",
    name: "404",
    component: NotFound,
  },
  {
    
    
    path: "/:catchAll(.*)",
    name: "404",
    component: NotFound,
  },
  
]

const router = createRouter({
    
    
  history: createWebHistory(),
  routes
})

export default router
二、js 控制 html、css 实现 PK 地图
  • AcGameObject.js 作为总父类 js 控制 每秒60次 执行GameMap.jsWall.js 的 updata;

    注意:只要new了某个js类,就会执行其所在的整个js文件

  • js 控制GameMap.vue组件中地图canvas随PKPlayGround动态改变大小;

  • js 控制GameMap.vue组件中墙canvas的构建。

GameMap 组件代码

// GameMap.vue
<template>
    <div ref="parent" class="gamemap">
        <canvas ref="canvas">
        </canvas>
    </div>   
</template>

<script>
// 引入自己写的 js
import {
    
    GameMap} from "@/assets/scripts/GameMap.js"
import {
    
    ref} from 'vue'
// 组件挂载完以后需要执行的操作
import {
    
    onMounted} from 'vue'

export default {
    
    
    setup:() => {
    
    
        let parent = ref(null);
        let canvas = ref(null);

        // 组件挂载完以后需要执行的操作:这里是js控制canvas
        onMounted(() => {
    
    
            // 通过 js 控制 html 显示
            // 传入画布和父组件的值
            // getContext('2d') 获取这个元素的 context,由 CanvasRenderingContext2D 接口完成实际的绘制。
            // 用 js 求出动态长方形 PKPlayGround(w * L)的 最大网格(row*col)正方形,可求内部小正方形的边长
            new GameMap(canvas.value.getContext('2d'),parent.value);

        });

        return {
    
    
            parent,
            canvas
        }
    }
}
</script>

<style scoped>
div.gamemap{
    
    
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
}
</style>

GameMap.js

// GameMap.js
import {
    
     AcGameObject } from "./AcGameObject";
import {
    
     Wall } from "./Wall";

export class GameMap extends AcGameObject {
    
    
    // 构造函数传入画布和画布的父元素
    constructor(ctx, parent) {
    
    
        super();
        this.ctx = ctx;
        this.parent = parent;

        // 存画布格子的绝对距离
        this.L = 0;
        this.rows = 13;
        this.cols = 13;

        // 存内部障碍物数量
        this.inner_walls_count = 15;
        // 画墙
        this.walls = [];
    }
	
	// 检查左下和右上是否联通
    check_connectivity(g, sx, sy, ex, ey){
    
    
        if (sx == ex && sy == ey) return true;
        g[sx][sy] = true;

        // 上下左右四个方向
        let dx = [-1, 0, 1, 0], dy = [0, 1, 0, -1];
        for (let i = 0; i < 4; i++){
    
    
            let x = sx + dx[i], y = sy + dy[i];
            if (!g[x][y] && this.check_connectivity(g, x, y, ex, ey)) return true;
            
        }
        return false;
    }

    create_walls() {
    
    
        const g = [];
        for (let r = 0;r < this.rows; r++ ) {
    
    
            g[r] = [];
            for ( let c = 0; c < this.cols; c++) {
    
    
                g[r][c] = false;
            }
        }

        // 给四周加墙
        for (let r = 0; r < this.rows; r++) {
    
    
            // 左右两边加墙
            g[r][0] = g[r][this.cols - 1] = true;
        }
        for (let c = 0; c < this.cols; c++) {
    
    
            // 上下两边加墙
            g[0][c] = g[this.rows-1][c] = true;
        }

        // 创建随机障碍物
        for (let i = 0; i < this.inner_walls_count; i++){
    
    
            for (let j = 0; j<1000 ; j++){
    
    
                // js 如何产生随机数
                // Math.random: 产生[0,1)之间随机浮点数
                // 即产生0-行数之间的随机浮点值后取整。
                let r = parseInt(Math.random() * this.rows);
                let c = parseInt(Math.random() * this.cols);
                if (g[r][c] || g[c][r] ) continue;
                if (c == 1 && r == this.rows-2 || c == this.cols-2 && r == 1) continue;
                g[r][c] = g[c][r] = true;
                break;
            }
        }

        // js 实现深度复制一个对象
        // 先转化成JSON, 再把JSON解析出来
        const copy_g =JSON.parse(JSON.stringify(g));
        if (!this.check_connectivity(copy_g, this.rows - 2, 1, 1, this.cols - 2)) return false;


        // 真正加
        for (let r = 0; r < this.rows; r++) {
    
    
            for (let c = 0; c < this.cols; c++) {
    
    
                if (g[r][c] == true) {
    
     
                    new Wall(r, c, this);
                }
            }
        }

        // 如果联通,返回true
        return true;
    }

    start() {
    
    
        for (let i = 0; i < 1000; i++){
    
    
            if (this.create_walls()){
    
    
                break;
            }
        }
    }

    updata_size() {
    
    
        // 用 js 求出动态长方形 PKPlayGround(w * L)的 最大网格(row*col)正方形,可求内部小正方形的边长
        // clientWidth/clientHeight:  API,求div的长宽
        // why parseInt()? 这里算得为浮点型,而canvas渲染为整型,渲染会有空隙
        this.L = parseInt(Math.min(this.parent.clientWidth / this.cols, this.parent.clientHeight / this.rows));
        this.ctx.canvas.width = this.L * this.cols;
        this.ctx.canvas.height = this.L * this.rows;
    }

    updata() {
    
    
        // 每帧都更新下边长
        this.updata_size();
        this.render();
    }

    render() {
    
    

        // 画基数偶数格颜色分离
        // #424242
        // fillStyle 属性赋值颜色。fillRect(坐标,宽,高)
        const color_even = "#D8D8D8", color_odd = "#E7E7E7";
        for (let r = 0; r < this.rows; r ++) {
    
    
            for ( let c = 0; c < this.cols; c ++) {
    
    
                if ((r+c) % 2 == 0) {
    
    
                    this.ctx.fillStyle = color_odd;
                } else {
    
    
                    this.ctx.fillStyle = color_even;
                }
                // canvas 往右是x正方向,往下是y正方向
                // 因此第 r 行 c 列坐标是(c,r)
                this.ctx.fillRect(c * this.L, r * this.L, this.L, this.L);
            }
        }
    }
}

Wall.js

import {
    
     AcGameObject } from "./AcGameObject";

export class Wall extends AcGameObject {
    
    
    // r,c 为画墙的起始坐标,gamemap 为canvas画布
    constructor(r, c, gamemap) {
    
    
        super();
        this.r = r;
        this.c = c;
        this.gamemap = gamemap;
        this.color = "#424242"
    }

    updata() {
    
    
        this.render();
    }

    render() {
    
    
        const L = this.gamemap.L;
        const ctx = this.gamemap.ctx;

        ctx.fillStyle = this.color;
        // 
        ctx.fillRect(this.c * L,this.r * L, L, L);
    }
}

效果如下:
在这里插入图片描述

三、js 控制 html、css 实现蛇各项操作

GameMap.js 实现如下:

import {
    
     AcGameObject } from "./AcGameObject";
import {
    
     Wall } from "./Wall";
import {
    
     Snake } from './Snake';

export class GameMap extends AcGameObject {
    
    
    // 构造函数传入画布和画布的父元素
    constructor(ctx, parent) {
    
    
        super();
        this.ctx = ctx;
        this.parent = parent;

        // 存画布格子的绝对距离
        this.L = 0;
        // 为避免两蛇可能在同一时刻进入同一个格子对优势者不公平,设置地图列数行数奇偶性不同
        // 蛇的走路路程坐标x和y的和总是奇偶交替的,让其初始的和奇偶不同即保证同时刻不可能走入同格子
        // 后续需要将加墙坐标改为中心对称
        this.rows = 13;
        this.cols = 14;

        // 存内部障碍物数量
        this.inner_walls_count = 30;
        // 画墙,存Wall对象
        this.walls = [];

        // 画蛇
        this.snakes = [
            new Snake({
    
    id:0, color: "#4876EC", r: this.rows-2, c: 1}, this),
            new Snake({
    
    id:1, color: "#F94848", r: 1, c: this.cols-2}, this),
        ];
    }

    check_connectivity(g, sx, sy, ex, ey){
    
    
        if (sx == ex && sy == ey) return true;
        g[sx][sy] = true;

        // 上下左右四个方向
        let dx = [-1, 0, 1, 0], dy = [0, 1, 0, -1];
        for (let i = 0; i < 4; i++){
    
    
            let x = sx + dx[i], y = sy + dy[i];
            if (!g[x][y] && this.check_connectivity(g, x, y, ex, ey)) return true;
            
        }
        return false;
    }

    create_walls() {
    
    
        const g = [];
        for (let r = 0;r < this.rows; r++ ) {
    
    
            g[r] = [];
            for ( let c = 0; c < this.cols; c++) {
    
    
                g[r][c] = false;
            }
        }

        // 给四周加墙
        for (let r = 0; r < this.rows; r++) {
    
    
            // 左右两边加墙
            g[r][0] = g[r][this.cols - 1] = true;
        }
        for (let c = 0; c < this.cols; c++) {
    
    
            // 上下两边加墙
            g[0][c] = g[this.rows-1][c] = true;
        }

        // 创建随机障碍物
        for (let i = 0; i < this.inner_walls_count / 2; i++){
    
    
            for (let j = 0; j<1000 ; j++){
    
    
                // js 如何产生随机数
                // Math.random: 产生[0,1)之间随机浮点数
                // 即产生0-行数之间的随机浮点值后取整。
                let r = parseInt(Math.random() * this.rows);
                let c = parseInt(Math.random() * this.cols);
                if (g[r][c] || g[this.rows-1-r][this.cols-1-c] ) continue;
                if (c == 1 && r == this.rows-2 || c == this.cols-2 && r == 1) continue;
                g[r][c] = g[this.rows-1-r][this.cols-1-c] = true;
                break;
            }
        }

        // js 实现深度复制一个对象
        // 先转化成JSON, 再把JSON解析出来
        const copy_g =JSON.parse(JSON.stringify(g));
        if (!this.check_connectivity(copy_g, this.rows - 2, 1, 1, this.cols - 2)) return false;

        // 真正加墙
        for (let r = 0; r < this.rows; r++) {
    
    
            for (let c = 0; c < this.cols; c++) {
    
    
                if (g[r][c]) {
    
     
                    this.walls.push(new Wall(r, c, this));
                }
            }
        }

        // 如果联通,返回true
        return true;
    }

    // 写键盘监听事件
    add_listening_events() {
    
    
        // 聚焦canvas
        this.ctx.canvas.focus();
        console.log("聚焦");
        const [snake0, snake1] = this.snakes;
    
        // 获取用户事件
        this.ctx.canvas.addEventListener("keydown", e => {
    
    
            if (e.key === 'w') snake0.set_direction(0);
            else if (e.key === "d") snake0.set_direction(1);
            else if (e.key === "s") snake0.set_direction(2);
            else if (e.key === "a") snake0.set_direction(3);
            else if (e.key === 'ArrowUp') snake1.set_direction(0);
            else if (e.key === "ArrowRight") snake1.set_direction(1);
            else if (e.key === 'ArrowDown') snake1.set_direction(2);
            else if (e.key === "ArrowLeft") snake1.set_direction(3);
            // console.log("键盘呀");
        });
    }

    start() {
    
    
        for (let i = 0; i < 1000; i++){
    
    
            if (this.create_walls()){
    
    
                break;
            }
        }
        this.add_listening_events();
    }

    updata_size() {
    
    
        // 用 js 求出动态长方形 PKPlayGround(w * L)的 最大网格(row*col)正方形,可求内部小正方形的边长
        // clientWidth/clientHeight:  API,求div的长宽
        // why parseInt()? 这里算得为浮点型,而canvas渲染为整型,渲染会有空隙
        this.L = parseInt(Math.min(this.parent.clientWidth / this.cols, this.parent.clientHeight / this.rows));
        this.ctx.canvas.width = this.L * this.cols;
        this.ctx.canvas.height = this.L * this.rows;
    }

    // 检查两蛇是否准备好下一步操作
    check_ready() {
    
    
        // 目前未移动且有指令,表示准备好
        for (const snake of this.snakes) {
    
    
            if ( snake.status !== "idle" ) return false;
            if ( snake.direction === -1) return false;
        }
        return true;
    }  

    // 让两条蛇进入下一回合
    next_step(){
    
    
        for (const snake of this.snakes ) {
    
    
            snake.next_step();
        }
    }

    // 检测加的蛇头是否合法
    // 检测目标位置是否合法:没有撞到两条蛇的身体和障碍物
    check_valid(cell) {
    
      
        for (const wall of this.walls) {
    
    
            if (wall.r === cell.r && wall.c === cell.c)
            {
    
        
                console.log("撞墙");return false;
            }
        }

        for (const snake of this.snakes) {
    
    
            let k = snake.cells.length;
            // 当蛇尾会前进的时候,蛇尾不要判断
            if (!snake.check_tail_increasing()) {
    
      
                k -- ;
            }
            for (let i = 0; i < k; i ++ ) {
    
    
                if (snake.cells[i].r === cell.r && snake.cells[i].c === cell.c)
                    return false;
            }
        }
        return true;
    }

    updata() {
    
    
        // 每帧都更新下边长
        this.updata_size();
        if (this.check_ready()) {
    
    
            this.next_step();
        }
        this.render();
    }

    render() {
    
    

        // 画基数偶数格颜色分离
        // #424242
        // fillStyle 属性赋值颜色。fillRect(坐标,宽,高)
        const color_even = "#D8D8D8", color_odd = "#E7E7E7";
        for (let r = 0; r < this.rows; r ++) {
    
    
            for ( let c = 0; c < this.cols; c ++) {
    
    
                if ((r+c) % 2 == 0) {
    
    
                    this.ctx.fillStyle = color_odd;
                } else {
    
    
                    this.ctx.fillStyle = color_even;
                }
                // canvas 往右是x正方向,往下是y正方向
                // 因此第 r 行 c 列坐标是(c,r)
                this.ctx.fillRect(c * this.L, r * this.L, this.L, this.L);
            }
        }
    }
}

Snake.js 蛇整体实现如下:

import {
    
     AcGameObject } from "./AcGameObject";
import {
    
     Cell } from "./Cell";

export class Snake extends AcGameObject {
    
    
    // 传入蛇信息以及gamemap
    constructor(info, gamemap) {
    
    
        super();
        
        this.id = info.id;
        this.color = info.color;
        this.gamemap = gamemap;

        // 存放蛇身体,cells[0]存放蛇头。
        this.cells = [new Cell(info.r, info.c)];

        // 蛇每秒走5个格子
        this.speed = 5; 

        // -1表示没有指令,0、1、2、3表示上右下左
        this.direction = -1

        // 蛇状态:idle表示静止,move 表示正在移动,die表示死亡
        this.status = 'idle';

        // 蛇走下一步的目标位置
        this.next_cell = null;

        // move上右下左的偏移量
        this.dr = [-1, 0, 1, 0];
        this.dc = [0, 1, 0, -1];

        // 存当前蛇的回合数(判断蛇是否要边长)
        this.count = 0;

        // 允许的误差0.01
        this.eps = 1e-2; 

        // 蛇眼睛
        // 1.移动方向
        this.eye_direction = 0;
        // 左下角的蛇眼睛移动方向初始朝上,右上角的蛇朝下
        if (this.id === 1) this.eye_direction = 2; 
        // 2. 眼睛距离蛇头位置,即圆心不同方向偏移量x
        this.eye_dx = [
            [-1,1], // 向上
            [1,1], // 向右
            [1,-1], // 向下
            [-1,-1] // 向左
        ]
        // 眼睛距离圆心不同方向偏移量y
        this.eye_dy = [
            [-1,-1],
            [-1,1],
            [1,1],
            [1,-1]
        ]
    }

    start() {
    
    
       
    }

    // 统一接口用来设置方向,方便后来除键盘外的后端控制方向
    set_direction(d) {
    
    
        this.direction = d;
    }

    // 真正移动
    updata_move() {
    
    
        const dx = this.next_cell.x - this.cells[0].x;
        const dy = this.next_cell.y - this.cells[0].y;
        const distance = Math.sqrt(dx * dx + dy *dy);

        // (距离小于0.01表示走完一格了停下的状态:)
        if (distance < this.eps) {
    
    
        	// 1.1:蛇头停止态:—→停下时直接按新蛇头坐标渲染新头
            this.cells[0] = this.next_cell; // 目标结点做新蛇头(放入新蛇头)
            this.next_cell = null;  // 目标清空
            this.status = 'idle';  // 状态改为停止
            
            // 2.2:尾巴停止态:—→移动停下后删除尾巴
            //(蛇没有增加长的时候,即蛇不加尾巴的时候:每次添加新头,连续移动尾巴到前一个位置,停下时直接把尾巴删掉)            
            if(!this.check_tail_increasing()) {
    
    
                this.cells.pop();
                console.log("删除蛇尾");
            }
        } else {
    
    
       		// (距离不小于0.01表示移动的状态:)
        	// 1.1:蛇头移动态:—→当前放入的新蛇头动态移动一个格子
            // 每两帧之间走过的距离(目的使得看起来是动态的不是直接移动一顿一顿的)
            const move_distance = this.speed * this.timedelta / 1000;
            this.cells[0].x += move_distance * dx / distance; 
            this.cells[0].y += move_distance * dy / distance;

            // 2.1:尾巴移动态:—→蛇尾连续动态移动到前一个(使得蛇尾是移动消失而不是凭空消失)
            //(蛇没有增加长的时候,即蛇不加尾巴的时候:每次添加新头,连续移动尾巴到前一个位置,停下时直接把尾巴删掉)                
            if (!this.check_tail_increasing()) {
    
    
                const k = this.cells.length;
                const tail = this.cells[k - 1], tail_target = this.cells[k - 2];
                const tail_dx = tail_target.x - tail.x;
                const tail_dy = tail_target.y - tail.y;
                tail.x += move_distance * tail_dx / distance;
                tail.y += move_distance * tail_dy / distance;
                // console.log("不 %3 移动蛇尾");
            }

        }
    }

    // 判断需要增加蛇尾
    check_tail_increasing() {
    
    
        if (this.count <= 5) return true;
        if (this.count % 3 === 1) {
    
    
            // console.log("判断是否 % 3");
            return true;}
        return false;
    }

    // 将蛇的状态变为走下一步
    next_step() {
    
    
        const d = this.direction;
        // 下一个头cell的位置,增加新蛇头位置信息
        this.next_cell = new Cell(this.cells[0].r + this.dr[d], this.cells[0].c + this.dc[d]);
        // 用完方向以后清空操作
        this.direction = -1;
        // 蛇眼方向
        this.eye_direction = d;
        // 状态由静止变为移动
        this.status = 'move';
        this.count++;

        // 以前的cells存储位置均完后退一位,以便于空出cells[0]存新蛇头位置信息
        const k = this.cells.length;
        for (let i = k; i > 0; i--){
    
    
            this.cells[i] = JSON.parse(JSON.stringify(this.cells[i-1]));
        }

        // 撞墙/自己不合法不能走
        if (!this.gamemap.check_valid(this.next_cell)) {
    
    
            this.status = "die";
        }        
    }
    
    updata() {
    
    
        if (this.status === 'move') {
    
    
            this.updata_move();
        }
        this.render();
    }
    
    // 渲染画蛇
    render() {
    
    
        const L = this.gamemap.L;
        const ctx = this.gamemap.ctx;

        ctx.fillStyle = this.color;

        // 蛇die颜色变色
        if(this.status === "die") {
    
    
            ctx.fillStyle = "white";
        }

        for (const cell of this.cells) {
    
    
            // canvas画圆开启路径
            ctx.beginPath();
            // 画圆圆心x,圆心y,半径,起始角度,终止角度
            ctx.arc(cell.x * L, cell.y * L, L/2 *0.8, 0, Math.PI * 2);
            // 填充颜色
            ctx.fill();
        }

        // 给蛇身体填充正方形
        for (let i = 1; i < this.cells.length; i ++ ) {
    
    
            const a = this.cells[i - 1], b = this.cells[i];

            // 若相邻两球重合不用填
            if (Math.abs(a.x - b.x) < this.eps && Math.abs(a.y - b.y) < this.eps)
                continue;
            // 若为垂直关系
            if (Math.abs(a.x - b.x) < this.eps) {
    
    
                ctx.fillRect((a.x - 0.4) * L, Math.min(a.y, b.y) * L, 0.8 * L, Math.abs(a.y - b.y) * L);
            } else {
    
    
                // 若为水平关系
                ctx.fillRect(Math.min(a.x, b.x) * L, (a.y - 0.4) * L, Math.abs(a.x - b.x) * L, 0.8 * L );
            }
        }

        // 画眼睛
        ctx.fillStyle = "black";

        for(let i = 0; i < 2; i++){
    
    
            const eye_x = (this.cells[0].x + this.eye_dx[this.eye_direction][i]*0.15)*L;
            const eye_y = (this.cells[0].y + this.eye_dy[this.eye_direction][i]*0.15)*L;
            ctx.beginPath();
            ctx.arc(eye_x, eye_y, 0.05*L, 0, 2 * Math.PI);
            ctx.fill();
        }
    }

}

Cell.js 蛇每一部分子元素如下:

export class Cell {
    
    
    constructor(r, c){
    
    
        this.r = r;
        this.c = c;
        this.x = c + 0.5;
        this.y = r + 0.5;
    }
}

猜你喜欢

转载自blog.csdn.net/qq_46201146/article/details/126070194