教你怎样1步1步用Canvas写1个嘴馋蛇

日期:2021-01-20 类型:科技新闻 

关键词:抠图软件电脑版,在线图片加水印,在线 抠图,在线画图网站,淘宝做图片用什么软件好

以前在慕课网看了几集Canvas的视頻,1直想着写点物品练练手。觉得嘴馋蛇算是较为简易的了,当年大学的情况下还写过C語言标识符版的,想不到還是遇到了许多难题。

最后实际效果以下(图太大的话 時间过长 录制gif的手机软件有时限…)

最先界定手机游戏地区。嘴馋蛇的显示屏上仅有蛇身和iPhone两种元素,而这两个都可以以用正方形格子组成。正方形之间加上间隙。为何要加上间隙?你能够想像当你取得成功铺满全部格子的情况下,假如沒有间隙,便是1个实心的大正方形……你压根不知道道蛇身甚么样。

画了1个图。

 

格子是左上角的座标是(0, 0),向右是横座标提升,向下是纵座标提升。这个方位和Canvas同样。

每次画1个格子的情况下,要从左上角刚开始,大家直了解Canvas的左上角座标是(0, 0),假定格子的边长是 GRID_WIDTH 间隙的宽度是  GAP_WIDTH ,能够获得第(i, j)个格子的左上角座标  (i*(GRID_WIDTH+GAP_WIDTH)+GAP_WIDTH, j*(GRID_WIDTH+GAP_WIDTH)+GAP_WIDTH) 。

假定如今蛇身是由3个蓝色的格子构成的,大家不可以只绘图3个格子,两个紫色的间隙也1定要绘图,不然,還是以前说的,你压根不知道道蛇身甚么样。以下图,不画间隙尽管也能玩,可是体验毫无疑问不1样。

绘图邻近格子之间空隙 不绘图空隙

如今大家能够尝试着画1条蛇了。蛇身实际上便是1个格子的结合,每一个格子用包括两个部位信息内容的数字能量数组表明,整条蛇能够用2维数字能量数组表明。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF⑻">
    <title>blog_snack</title>
    <style>
        #canvas {
             background-color: #000;
        }
    </style>
</head>
<body>
    <canvas id="canvas"></canvas>
    <script>
        const GRID_WIDTH = 10;  // 格子的边长
        const GAP_WIDTH = 2;    // 间隙的边长
        const ROW = 10;         // 1共有是多少行格子&每行有是多少个格子

        let canvas = document.getElementById('canvas');
        canvas.height = GRID_WIDTH * ROW + GAP_WIDTH * (ROW + 1);
        canvas.width = GRID_WIDTH * ROW + GAP_WIDTH * (ROW + 1);
        let ctx = canvas.getContext('2d');

        let snack = [ [2, 3], [2, 4], [2, 5], [3, 5], [4, 5], [4, 4], [5, 4], [5, 5] ]; // 原始化1条🐍

        drawSnack(ctx, snack, '#fff');

        function drawSnack(ctx, snack, color) {
            ctx.fillStyle = color;
            for (let i = 0; i < snack.length; i++) {
                ctx.fillRect(...getGridULCoordinate(snack[i]), GRID_WIDTH, GRID_WIDTH);
                if (i) {
                    ctx.fillRect(...getBetweenTwoGridGap(snack[i], snack[i - 1]));
                }
            }
        }
        // 传入1个格子 回到左上角座标
        function getGridULCoordinate(g) {
            return [g[0] * (GRID_WIDTH + GAP_WIDTH) + GAP_WIDTH, g[1] * (GRID_WIDTH + GAP_WIDTH) + GAP_WIDTH];
        }
        // 传入两个格子 回到两个格子之间的矩形框间隙
        // 这里传入的两个格子务必是邻近的
        // 回到1个数字能量数组 各自是这个矩形框间隙的 左上角横座标 左上角纵座标 宽 高
        function getBetweenTwoGridGap(g1, g2) {
            let width = GRID_WIDTH + GAP_WIDTH;
            if (g1[0] === g2[0]) { // 横座标同样 是纵向邻近的两个格子
                let x = g1[0] * width + GAP_WIDTH;
                let y = Math.min(g1[1], g2[1]) * width + width;
                return [x, y, GRID_WIDTH, GAP_WIDTH];
            } else { // 纵座标同样 是横向邻近的两个格子
                let x = Math.min(g1[0], g2[0]) * width + width;
                let y = g1[1] * width + GAP_WIDTH;
                return [x, y, GAP_WIDTH, GRID_WIDTH];
            }
        }
    </script>
</body>
</html>

我原始化了1条蛇,看起来是合乎预期的。

接下来要做的是让蛇动起来。蛇动起来这事很简易,蛇向着当今健身运动的方位前行1格,删除蛇尾,也便是最终1个格子便可以了。以前说的2维数字能量数组表明1条蛇, 如今要求在其中snack[0]表明蛇尾,snack[snack.length⑴]表明蛇头。 动漫就简易的用setInterval完成了。

const GRID_WIDTH = 10;  // 格子的边长
const GAP_WIDTH = 2;    // 间隙的边长
const ROW = 10;         // 1共有是多少行格子&每行有是多少个格子
const COLOR = '#fff';   // 蛇的色调
const BG_COLOR = '#000';// 情况色调

const UP = 0, LEFT = 1, RIGHT = 2, DOWN = 3;    // 界定蛇前行的方位
const CHANGE = [ [0, ⑴], [⑴, 0], [1, 0], [0, 1] ]; // 每一个方位前行时格子座标的转变

let canvas = document.getElementById('canvas');
canvas.height = GRID_WIDTH * ROW + GAP_WIDTH * (ROW + 1);
canvas.width = GRID_WIDTH * ROW + GAP_WIDTH * (ROW + 1);
let ctx = canvas.getContext('2d');

let snack = [ [2, 3], [2, 4], [2, 5], [3, 5], [4, 5], [4, 4], [5, 4], [5, 5] ]; // 原始化1条🐍
let dir = RIGHT; // 原始化1个方位

drawSnack(ctx, snack, COLOR);

let timer = setInterval(() => {
    // 每隔1段時间就更新1次
    let head = snack[snack.length - 1]; // 蛇头
    let change = CHANGE[dir];           // 下1个格子前行部位
    let newGrid = [head[0] + change[0], head[1] + change[1]]; // 新格子的部位
    snack.push(newGrid);    // 新格子添加蛇身的数字能量数组中
    ctx.fillStyle = COLOR;
    ctx.fillRect(...getGridULCoordinate(newGrid), GRID_WIDTH, GRID_WIDTH); // 画新格子
    ctx.fillRect(...getBetweenTwoGridGap(head, newGrid)); // 新蛇头和旧蛇头之间的间隙
    ctx.fillStyle = BG_COLOR;
    let delGrid = snack.shift();    // 删掉蛇尾-最终1个元素
    ctx.fillRect(...getGridULCoordinate(delGrid), GRID_WIDTH, GRID_WIDTH); // 擦除删掉元素
    ctx.fillRect(...getBetweenTwoGridGap(delGrid, snack[0])); // 擦除删掉元素和当今最终1个元素之间的间隙
}, 1000);

..... // 和以前同样

如今蛇早已能够动起来了。

但这毫无疑问并不是我要想的实际效果——它的挪动是1顿1顿的,而我要想丝滑的。

如今每次转变全是立即挪动1个格子边长的间距,确保蛇挪动速率不会改变的状况下,动漫是不能能变得丝滑的。因此要想挪动变得丝滑,1种可行的方式是,挪动1个格子的间距的全过程分数次绘图。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF⑻">
    <title>blog_snack</title>
    <style>
        #canvas {
             background-color: #000;
        }
    </style>
</head>
<body>
    <canvas id="canvas"></canvas>
    <script>
        const GRID_WIDTH = 10;  // 格子的边长
        const GAP_WIDTH = 2;    // 间隙的边长
        const ROW = 10;         // 1共有是多少行格子&每行有是多少个格子
        const COLOR = '#fff';   // 蛇的色调
        const BG_COLOR = '#000';// 情况色调
        const INTERVAL = 1000;

        const UP = 0, LEFT = 1, RIGHT = 2, DOWN = 3;    // 界定蛇前行的方位
        const CHANGE = [ [0, ⑴], [⑴, 0], [1, 0], [0, 1] ]; // 每一个方位前行时格子座标的转变

        let canvas = document.getElementById('canvas');
        canvas.height = GRID_WIDTH * ROW + GAP_WIDTH * (ROW + 1);
        canvas.width = GRID_WIDTH * ROW + GAP_WIDTH * (ROW + 1);
        let ctx = canvas.getContext('2d');

        let snack = [ [2, 3], [2, 4], [2, 5], [3, 5], [4, 5], [4, 4], [5, 4], [5, 5] ]; // 原始化1条🐍
        let dir = RIGHT; // 原始化1个方位

        drawSnack(ctx, snack, COLOR);

        let timer = setInterval(() => {
            // 每隔1段時间就更新1次
            let head = snack[snack.length - 1]; // 蛇头
            let change = CHANGE[dir];           // 下1个格子前行部位
            let newGrid = [head[0] + change[0], head[1] + change[1]]; // 新格子的部位
            snack.push(newGrid);    // 新格子添加蛇身的数字能量数组中
            gradientRect(ctx, ...getUniteRect(newGrid, getBetweenTwoGridGap(head, newGrid)), dir, COLOR, INTERVAL);
            let delGrid = snack.shift();    // 删掉蛇尾-最终1个元素
            gradientRect(ctx, ...getUniteRect(delGrid, getBetweenTwoGridGap(delGrid, snack[0])), 
                getDirection(delGrid, snack[0]), BG_COLOR, INTERVAL);
        }, INTERVAL);

        // 给定1个格子的座标和1个格子空隙的矩形框(左上角,宽,高) 回到两个合拼的矩形框 的左上角、右下角 座标
        function getUniteRect(g, rect) {
            let p = getGridULCoordinate(g);
            if (p[0] === rect[0] && p[1] < rect[1] ||   // 矩形框是在格子下方
                p[1] === rect[1] && p[0] < rect[0]) {   // 矩形框在格子的正右方
                return [p[0], p[1], rect[0] + rect[2], rect[1] + rect[3]];
            } else if (p[0] === rect[0] && p[1] > rect[1] || // 矩形框是在格子正上方
                p[1] === rect[1] && p[0] > rect[0]) { // 矩形框在格子的正左方
                return [rect[0], rect[1], p[0] + GRID_WIDTH, p[1] + GRID_WIDTH];
            }
        }
        // 从格子1 挪动到格子2 的方位
        function getDirection(g1, g2) {
            if (g1[0] === g2[0] && g1[1] < g2[1]) return DOWN;
            if (g1[0] === g2[0] && g1[1] > g2[1]) return UP;
            if (g1[1] === g2[1] && g1[0] < g2[0]) return RIGHT;
            if (g1[1] === g2[1] && g1[0] > g2[0]) return LEFT;
        }

        // 渐渐地的填充1个矩形框 (真的不知道道则如何写 瞎写...动漫的实行時间将会不等于duration 但1定要确保<=duration
        // 传入的是矩形框左上角和右下角的座标 和渐变色的方位
        function gradientRect(ctx, x1, y1, x2, y2, dir, color, duration) {
            let dur = 20;
            let times = Math.floor(duration / dur); // 升级次数
            let nowX1 = x1, nowY1 = y1, nowX2 = x2, nowY2 = y2;
            let dx1 = 0, dy1 = 0, dx2 = 0, dy2 = 0;
            if (dir === UP) { dy1 = (y1 - y2) / times; nowY1 = y2; }
            if (dir === DOWN) { dy2 = (y2 - y1) / times; nowY2 = y1; }
            if (dir === LEFT) { dx1 = (x1 - x2) / times; nowX1 = x2; }
            if (dir === RIGHT) { dx2 = (x2 - x1) / times; nowX2 = x1; }
            let startTime = Date.now();
            let timer = setInterval(() => {
                nowX1 += dx1, nowX2 += dx2, nowY1 += dy1, nowY2 += dy2; // 升级
                let runTime = Date.now() - startTime;
                if (nowX1 < x1 || nowX2 > x2 || nowY1 < y1 || nowY2 > y2 || runTime >= duration - dur) {
                    nowX1 = x1, nowX2 = x2, nowY1 = y1, nowY2 = y2;
                    clearInterval(timer);
                }
                ctx.fillStyle = color;
                ctx.fillRect(nowX1, nowY1, nowX2 - nowX1, nowY2 - nowY1);
            }, dur);
        }
        // 依据snack2维数字能量数组画1条蛇
        function drawSnack(ctx, snack, color) {
            ctx.fillStyle = color;
            for (let i = 0; i < snack.length; i++) {
                ctx.fillRect(...getGridULCoordinate(snack[i]), GRID_WIDTH, GRID_WIDTH);
                if (i) {
                    ctx.fillRect(...getBetweenTwoGridGap(snack[i], snack[i - 1]));
                }
            }
        }
        // 传入1个格子 回到左上角座标
        function getGridULCoordinate(g) {
            return [g[0] * (GRID_WIDTH + GAP_WIDTH) + GAP_WIDTH, g[1] * (GRID_WIDTH + GAP_WIDTH) + GAP_WIDTH];
        }
        // 传入两个格子 回到两个格子之间的矩形框间隙
        // 这里传入的两个格子务必是邻近的
        // 回到1个数字能量数组 各自是这个矩形框间隙的 左上角横座标 左上角纵座标 宽 高
        function getBetweenTwoGridGap(g1, g2) {
            let width = GRID_WIDTH + GAP_WIDTH;
            if (g1[0] === g2[0]) { // 横座标同样 是纵向邻近的两个格子
                let x = g1[0] * width + GAP_WIDTH;
                let y = Math.min(g1[1], g2[1]) * width + width;
                return [x, y, GRID_WIDTH, GAP_WIDTH];
            } else { // 纵座标同样 是横向邻近的两个格子
                let x = Math.min(g1[0], g2[0]) * width + width;
                let y = g1[1] * width + GAP_WIDTH;
                return [x, y, GAP_WIDTH, GRID_WIDTH];
            }
        }
    </script>
</body>
</html>

说实话,编码写的十分不尽人意……我也很无可奈何……

总之如今蛇能够迟缓丝滑的挪动了。

接下来要做的是分辨是不是碰触到边沿或碰触到本身致使手机游戏完毕,和回应电脑键盘恶性事件。

这里的修改很简易。用1个map标识每个格子是不是被占。每个格子(i, j)能够被序号i*row+j。

const GRID_WIDTH = 10;  // 格子的边长
const GAP_WIDTH = 2;    // 间隙的边长
const ROW = 10;         // 1共有是多少行格子&每行有是多少个格子
const COLOR = '#fff';   // 蛇的色调
const BG_COLOR = '#000';// 情况色调
const INTERVAL = 300;

const UP = 0, LEFT = 1, RIGHT = 2, DOWN = 3;    // 界定蛇前行的方位
const CHANGE = [ [0, ⑴], [⑴, 0], [1, 0], [0, 1] ]; // 每一个方位前行时格子座标的转变

let canvas = document.getElementById('canvas');
canvas.height = GRID_WIDTH * ROW + GAP_WIDTH * (ROW + 1);
canvas.width = GRID_WIDTH * ROW + GAP_WIDTH * (ROW + 1);
let ctx = canvas.getContext('2d');

let snack, dir, map, nextDir;

function initialize() {
    snack = [ [2, 3], [2, 4], [2, 5], [3, 5], [4, 5], [4, 4], [5, 4], [5, 5] ]; // 原始化1条🐍
    nextDir = dir = RIGHT; // 原始化1个方位
    map = [];
    for (let i = 0; i < ROW * ROW; i++) map[i] = 0;
    for (let i = 0; i < snack.length; i++) map[ getGridNumber(snack[i]) ] = 1;
    window.onkeydown = function(e) {
        // e.preventDefault();
        if (e.key === 'ArrowUp') nextDir = UP;
        if (e.key === 'ArrowDown') nextDir = DOWN;
        if (e.key === 'ArrowRight') nextDir = RIGHT;
        if (e.key === 'ArrowLeft') nextDir = LEFT;
    }
    drawSnack(ctx, snack, COLOR);
}

initialize();

let timer = setInterval(() => {
    // 每隔1段時间就更新1次
    // 仅有扭头方位与当今方位竖直的情况下 才更改方位
    if (nextDir !== dir && nextDir + dir !== 3) dir = nextDir;
    let head = snack[snack.length - 1]; // 蛇头
    let change = CHANGE[dir];           // 下1个格子前行部位
    let newGrid = [head[0] + change[0], head[1] + change[1]]; // 新格子的部位
    if (!isValidPosition(newGrid)) { // 新部位不符合法 手机游戏完毕
        clearInterval(timer);
        return;
    }
    snack.push(newGrid);    // 新格子添加蛇身的数字能量数组中
    map[getGridNumber(newGrid)] = 1;
    gradientRect(ctx, ...getUniteRect(newGrid, getBetweenTwoGridGap(head, newGrid)), dir, COLOR, INTERVAL);
    let delGrid = snack.shift();    // 删掉蛇尾-最终1个元素
    map[getGridNumber(delGrid)] = 0;
    gradientRect(ctx, ...getUniteRect(delGrid, getBetweenTwoGridGap(delGrid, snack[0])), 
        getDirection(delGrid, snack[0]), BG_COLOR, INTERVAL);
}, INTERVAL);

function isValidPosition(g) {
    if (g[0] >= 0 && g[0] < ROW && g[1] >= 0 && g[1] < ROW && !map[getGridNumber(g)]) return true;
    return false;
}
// 获得1个格子的序号
function getGridNumber(g) {
    return g[0] * ROW + g[1];
}
// 给定1个格子的座标和1个格子空隙的矩形框(左上角,宽,高) 回到两个合拼的矩形框 的左上角、右下角 座标
function getUniteRect(g, rect) {
/// ... 后边编码不更改 略....

这时候早已能够操纵蛇的挪动了。

最终1个流程了,画iPhone。iPhone的部位应当是任意的,且不与蛇身重合,此外蛇吃到iPhone的情况下,长度会加1。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF⑻">
    <title>blog_snack</title>
    <style>
        #canvas {
             background-color: #000;
        }
    </style>
</head>
<body>
    <canvas id="canvas"></canvas>
    <script>
        const GRID_WIDTH = 10;  // 格子的边长
        const GAP_WIDTH = 2;    // 间隙的边长
        const ROW = 10;         // 1共有是多少行格子&每行有是多少个格子
        const COLOR = '#fff';   // 蛇的色调
        const BG_COLOR = '#000';// 情况色调
        const FOOD_COLOR = 'red'; // 食材色调
        const INTERVAL = 300;

        const UP = 0, LEFT = 1, RIGHT = 2, DOWN = 3;    // 界定蛇前行的方位
        const CHANGE = [ [0, ⑴], [⑴, 0], [1, 0], [0, 1] ]; // 每一个方位前行时格子座标的转变

        let canvas = document.getElementById('canvas');
        canvas.height = GRID_WIDTH * ROW + GAP_WIDTH * (ROW + 1);
        canvas.width = GRID_WIDTH * ROW + GAP_WIDTH * (ROW + 1);
        let ctx = canvas.getContext('2d');

        let snack, dir, map, nextDir, food;

        function initialize() {
            snack = [ [2, 3], [2, 4], [2, 5], [3, 5], [4, 5], [4, 4], [5, 4], [5, 5] ]; // 原始化1条🐍
            nextDir = dir = RIGHT; // 原始化1个方位
            map = [];
            for (let i = 0; i < ROW * ROW; i++) map[i] = 0;
            for (let i = 0; i < snack.length; i++) map[ getGridNumber(snack[i]) ] = 1;
            window.onkeydown = function(e) {
                // e.preventDefault();
                if (e.key === 'ArrowUp') nextDir = UP;
                if (e.key === 'ArrowDown') nextDir = DOWN;
                if (e.key === 'ArrowRight') nextDir = RIGHT;
                if (e.key === 'ArrowLeft') nextDir = LEFT;
            }
            drawSnack(ctx, snack, COLOR);
            drawFood();
        }

        initialize();

        let timer = setInterval(() => {
            // 每隔1段時间就更新1次
            // 仅有扭头方位与当今方位竖直的情况下 才更改方位
            if (nextDir !== dir && nextDir + dir !== 3) dir = nextDir;
            let head = snack[snack.length - 1]; // 蛇头
            let change = CHANGE[dir];           // 下1个格子前行部位
            let newGrid = [head[0] + change[0], head[1] + change[1]]; // 新格子的部位
            if (!isValidPosition(newGrid)) { // 新部位不符合法 手机游戏完毕
                clearInterval(timer);
                return;
            }
            snack.push(newGrid);    // 新格子添加蛇身的数字能量数组中
            map[getGridNumber(newGrid)] = 1;
            gradientRect(ctx, ...getUniteRect(newGrid, getBetweenTwoGridGap(head, newGrid)), dir, COLOR, INTERVAL);
            if (newGrid[0] === food[0] && newGrid[1] === food[1]) {
                drawFood();
                return;
            }
            let delGrid = snack.shift();    // 删掉蛇尾-最终1个元素
            map[getGridNumber(delGrid)] = 0;
            gradientRect(ctx, ...getUniteRect(delGrid, getBetweenTwoGridGap(delGrid, snack[0])), 
                getDirection(delGrid, snack[0]), BG_COLOR, INTERVAL);
        }, INTERVAL);
        // 画食材
        function drawFood() {
            food = getFoodPosition();
            ctx.fillStyle = FOOD_COLOR;
            ctx.fillRect(...getGridULCoordinate(food), GRID_WIDTH, GRID_WIDTH);
        }
        // 分辨1个新生儿成的格子部位是不是合理合法
        function isValidPosition(g) {
            if (g[0] >= 0 && g[0] < ROW && g[1] >= 0 && g[1] < ROW && !map[getGridNumber(g)]) return true;
            return false;
        }
        // 获得1个格子的序号
        function getGridNumber(g) {
            return g[0] * ROW + g[1];
        }
        function getFoodPosition() {
            let r = Math.floor(Math.random() * (ROW * ROW - snack.length)); // 任意获得1个数据 数据范畴和剩下的格子数同样
            for (let i = 0; ; i++) {    // 仅有遇到位置的情况下 计数君 r 才减1
                if (!map[i] && --r < 0) return [Math.floor(i / ROW), i % ROW];
            }
        }
        // 给定1个格子的座标和1个格子空隙的矩形框(左上角,宽,高) 回到两个合拼的矩形框 的左上角、右下角 座标
        function getUniteRect(g, rect) {
            let p = getGridULCoordinate(g);
            if (p[0] === rect[0] && p[1] < rect[1] ||   // 矩形框是在格子下方
                p[1] === rect[1] && p[0] < rect[0]) {   // 矩形框在格子的正右方
                return [p[0], p[1], rect[0] + rect[2], rect[1] + rect[3]];
            } else if (p[0] === rect[0] && p[1] > rect[1] || // 矩形框是在格子正上方
                p[1] === rect[1] && p[0] > rect[0]) { // 矩形框在格子的正左方
                return [rect[0], rect[1], p[0] + GRID_WIDTH, p[1] + GRID_WIDTH];
            }
        }
        // 从格子1 挪动到格子2 的方位
        function getDirection(g1, g2) {
            if (g1[0] === g2[0] && g1[1] < g2[1]) return DOWN;
            if (g1[0] === g2[0] && g1[1] > g2[1]) return UP;
            if (g1[1] === g2[1] && g1[0] < g2[0]) return RIGHT;
            if (g1[1] === g2[1] && g1[0] > g2[0]) return LEFT;
        }

        // 渐渐地的填充1个矩形框 (真的不知道道则如何写 瞎写...动漫的实行時间将会不等于duration 但1定要确保<=duration
        // 传入的是矩形框左上角和右下角的座标 和渐变色的方位
        function gradientRect(ctx, x1, y1, x2, y2, dir, color, duration) {
            let dur = 20;
            let times = Math.floor(duration / dur); // 升级次数
            let nowX1 = x1, nowY1 = y1, nowX2 = x2, nowY2 = y2;
            let dx1 = 0, dy1 = 0, dx2 = 0, dy2 = 0;
            if (dir === UP) { dy1 = (y1 - y2) / times; nowY1 = y2; }
            if (dir === DOWN) { dy2 = (y2 - y1) / times; nowY2 = y1; }
            if (dir === LEFT) { dx1 = (x1 - x2) / times; nowX1 = x2; }
            if (dir === RIGHT) { dx2 = (x2 - x1) / times; nowX2 = x1; }
            let startTime = Date.now();
            let timer = setInterval(() => {
                nowX1 += dx1, nowX2 += dx2, nowY1 += dy1, nowY2 += dy2; // 升级
                let runTime = Date.now() - startTime;
                if (nowX1 < x1 || nowX2 > x2 || nowY1 < y1 || nowY2 > y2 || runTime >= duration - dur) {
                    nowX1 = x1, nowX2 = x2, nowY1 = y1, nowY2 = y2;
                    clearInterval(timer);
                }
                ctx.fillStyle = color;
                ctx.fillRect(nowX1, nowY1, nowX2 - nowX1, nowY2 - nowY1);
            }, dur);
        }
        // 依据snack2维数字能量数组画1条蛇
        function drawSnack(ctx, snack, color) {
            ctx.fillStyle = color;
            for (let i = 0; i < snack.length; i++) {
                ctx.fillRect(...getGridULCoordinate(snack[i]), GRID_WIDTH, GRID_WIDTH);
                if (i) {
                    ctx.fillRect(...getBetweenTwoGridGap(snack[i], snack[i - 1]));
                }
            }
        }
        // 传入1个格子 回到左上角座标
        function getGridULCoordinate(g) {
            return [g[0] * (GRID_WIDTH + GAP_WIDTH) + GAP_WIDTH, g[1] * (GRID_WIDTH + GAP_WIDTH) + GAP_WIDTH];
        }
        // 传入两个格子 回到两个格子之间的矩形框间隙
        // 这里传入的两个格子务必是邻近的
        // 回到1个数字能量数组 各自是这个矩形框间隙的 左上角横座标 左上角纵座标 宽 高
        function getBetweenTwoGridGap(g1, g2) {
            let width = GRID_WIDTH + GAP_WIDTH;
            if (g1[0] === g2[0]) { // 横座标同样 是纵向邻近的两个格子
                let x = g1[0] * width + GAP_WIDTH;
                let y = Math.min(g1[1], g2[1]) * width + width;
                return [x, y, GRID_WIDTH, GAP_WIDTH];
            } else { // 纵座标同样 是横向邻近的两个格子
                let x = Math.min(g1[0], g2[0]) * width + width;
                let y = g1[1] * width + GAP_WIDTH;
                return [x, y, GAP_WIDTH, GRID_WIDTH];
            }
        }
    </script>
</body>
</html>

我无论 我写完了 我的编码最棒了(口区

假如蛇能自身动就行了。。。我的念头很单纯性。。。可是想了很久没結果的情况下,Google1下才发现这仿佛涉及到到AI了。。。头疼。。。

最后我选择的计划方案是:

if 存在蛇头到iPhone的相对路径 and 蛇身长度小于全部地形图的1半
    虚似蛇去尝试吃iPhone
    if 吃完iPhone后能寻找蛇头到蛇尾的相对路径
        BFS到蛇尾
else if 存在蛇头到蛇尾的相对路径
    走蛇头到蛇尾的最长相对路径
else
    任意1个方位

我只是想训练Canvas罢了…因此就沒有好好写。编码有点长就不贴了。

(由于我的蛇很蠢。。是真的蠢。。。

详细编码可见github --> https://github.com/G-lory/front-end-practice/blob/master/canvas/blog_snack.html

这次写完觉得我的编码工作能力确实是太差了,写了两遍還是很乱。 之后還是要多训练。

总之沒有bug是不能能的,这辈子是不能能的。

以上便是本文的所有內容,期待对大伙儿的学习培训有一定的协助,也期待大伙儿多多适用脚本制作之家。