成人性生交大片免费看视频r_亚洲综合极品香蕉久久网_在线视频免费观看一区_亚洲精品亚洲人成人网在线播放_国产精品毛片av_久久久久国产精品www_亚洲国产一区二区三区在线播_日韩一区二区三区四区区区_亚洲精品国产无套在线观_国产免费www

主頁 > 知識庫 > 教你如何一步一步用Canvas寫一個貪吃蛇

教你如何一步一步用Canvas寫一個貪吃蛇

熱門標(biāo)簽:威海營銷外呼系統(tǒng)招商 農(nóng)村住宅地圖標(biāo)注 漳州人工外呼系統(tǒng)排名 濟南辦理400電話 鄭州電銷外呼系統(tǒng)違法嗎 ai電銷機器人連接網(wǎng)關(guān) 中紳電銷智能機器人 跟電銷機器人做同事 鶴壁手機自動外呼系統(tǒng)怎么安裝

之前在慕課網(wǎng)看了幾集Canvas的視頻,一直想著寫點東西練練手。感覺貪吃蛇算是比較簡單的了,當(dāng)年大學(xué)的時候還寫過C語言字符版的,沒想到還是遇到了很多問題。

最終效果如下(圖太大的話 時間太長 錄制gif的軟件有時限…)

首先定義游戲區(qū)域。貪吃蛇的屏幕上只有蛇身和蘋果兩種元素,而這兩個都可以用正方形格子構(gòu)成。正方形之間添加縫隙。為什么要添加縫隙?你可以想象當(dāng)你成功填滿所有格子的時候,如果沒有縫隙,就是一個實心的大正方形……你根本不知道蛇身什么樣。

畫了一個圖。

 

格子是左上角的坐標(biāo)是(0, 0),向右是橫坐標(biāo)增加,向下是縱坐標(biāo)增加。這個方向和Canvas相同。

每次畫一個格子的時候,要從左上角開始,我們直知道Canvas的左上角坐標(biāo)是(0, 0),假設(shè)格子的邊長是 GRID_WIDTH 縫隙的寬度是  GAP_WIDTH ,可以得到第(i, j)個格子的左上角坐標(biāo)  (i*(GRID_WIDTH+GAP_WIDTH)+GAP_WIDTH, j*(GRID_WIDTH+GAP_WIDTH)+GAP_WIDTH) 。

假設(shè)現(xiàn)在蛇身是由三個藍(lán)色的格子組成的,我們不能只繪制三個格子,兩個紫色的空隙也一定要繪制,否則,還是之前說的,你根本不知道蛇身什么樣。如下圖,不畫縫隙雖然也能玩,但是體驗肯定不一樣。

繪制相鄰格子之間間隙 不繪制間隙

現(xiàn)在我們可以嘗試著畫一條蛇了。蛇身其實就是一個格子的集合,每個格子用包含兩個位置信息的數(shù)組表示,整條蛇可以用二維數(shù)組表示。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <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;         // 一共有多少行格子&每行有多少個格子

        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] ]; // 初始化一條🐍

        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]));
                }
            }
        }
        // 傳入一個格子 返回左上角坐標(biāo)
        function getGridULCoordinate(g) {
            return [g[0] * (GRID_WIDTH + GAP_WIDTH) + GAP_WIDTH, g[1] * (GRID_WIDTH + GAP_WIDTH) + GAP_WIDTH];
        }
        // 傳入兩個格子 返回兩個格子之間的矩形縫隙
        // 這里傳入的兩個格子必須是相鄰的
        // 返回一個數(shù)組 分別是這個矩形縫隙的 左上角橫坐標(biāo) 左上角縱坐標(biāo) 寬 高
        function getBetweenTwoGridGap(g1, g2) {
            let width = GRID_WIDTH + GAP_WIDTH;
            if (g1[0] === g2[0]) { // 橫坐標(biāo)相同 是縱向相鄰的兩個格子
                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 { // 縱坐標(biāo)相同 是橫向相鄰的兩個格子
                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>

我初始化了一條蛇,看起來是符合預(yù)期的。

接下來要做的是讓蛇動起來。蛇動起來這事很簡單,蛇向著當(dāng)前運動的方向前進(jìn)一格,刪掉蛇尾,也就是最后一個格子就可以了。之前說的二維數(shù)組表示一條蛇, 現(xiàn)在規(guī)定其中snack[0]表示蛇尾,snack[snack.length-1]表示蛇頭。 動畫就簡單的用setInterval實現(xiàn)了。

const GRID_WIDTH = 10;  // 格子的邊長
const GAP_WIDTH = 2;    // 空隙的邊長
const ROW = 10;         // 一共有多少行格子&每行有多少個格子
const COLOR = '#fff';   // 蛇的顏色
const BG_COLOR = '#000';// 背景顏色

const UP = 0, LEFT = 1, RIGHT = 2, DOWN = 3;    // 定義蛇前進(jìn)的方向
const CHANGE = [ [0, -1], [-1, 0], [1, 0], [0, 1] ]; // 每個方向前進(jìn)時格子坐標(biāo)的變化

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] ]; // 初始化一條🐍
let dir = RIGHT; // 初始化一個方向

drawSnack(ctx, snack, COLOR);

let timer = setInterval(() => {
    // 每隔一段時間就刷新一次
    let head = snack[snack.length - 1]; // 蛇頭
    let change = CHANGE[dir];           // 下一個格子前進(jìn)位置
    let newGrid = [head[0] + change[0], head[1] + change[1]]; // 新格子的位置
    snack.push(newGrid);    // 新格子加入蛇身的數(shù)組中
    ctx.fillStyle = COLOR;
    ctx.fillRect(...getGridULCoordinate(newGrid), GRID_WIDTH, GRID_WIDTH); // 畫新格子
    ctx.fillRect(...getBetweenTwoGridGap(head, newGrid)); // 新蛇頭和舊蛇頭之間的縫隙
    ctx.fillStyle = BG_COLOR;
    let delGrid = snack.shift();    // 刪除蛇尾-最后一個元素
    ctx.fillRect(...getGridULCoordinate(delGrid), GRID_WIDTH, GRID_WIDTH); // 擦除刪除元素
    ctx.fillRect(...getBetweenTwoGridGap(delGrid, snack[0])); // 擦除刪除元素和當(dāng)前最后一個元素之間的縫隙
}, 1000);

..... // 和之前相同

現(xiàn)在蛇已經(jīng)可以動起來了。

但這肯定不是我想要的效果——它的移動是一頓一頓的,而我想要順滑的。

現(xiàn)在每一次變化都是直接移動一個格子邊長的距離,保證蛇移動速度不變的情況下,動畫是不可能變得順滑的。所以想要移動變得順滑,一種可行的方法是,移動一個格子的距離的過程分多次繪制。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <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;         // 一共有多少行格子&每行有多少個格子
        const COLOR = '#fff';   // 蛇的顏色
        const BG_COLOR = '#000';// 背景顏色
        const INTERVAL = 1000;

        const UP = 0, LEFT = 1, RIGHT = 2, DOWN = 3;    // 定義蛇前進(jìn)的方向
        const CHANGE = [ [0, -1], [-1, 0], [1, 0], [0, 1] ]; // 每個方向前進(jìn)時格子坐標(biāo)的變化

        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] ]; // 初始化一條🐍
        let dir = RIGHT; // 初始化一個方向

        drawSnack(ctx, snack, COLOR);

        let timer = setInterval(() => {
            // 每隔一段時間就刷新一次
            let head = snack[snack.length - 1]; // 蛇頭
            let change = CHANGE[dir];           // 下一個格子前進(jìn)位置
            let newGrid = [head[0] + change[0], head[1] + change[1]]; // 新格子的位置
            snack.push(newGrid);    // 新格子加入蛇身的數(shù)組中
            gradientRect(ctx, ...getUniteRect(newGrid, getBetweenTwoGridGap(head, newGrid)), dir, COLOR, INTERVAL);
            let delGrid = snack.shift();    // 刪除蛇尾-最后一個元素
            gradientRect(ctx, ...getUniteRect(delGrid, getBetweenTwoGridGap(delGrid, snack[0])), 
                getDirection(delGrid, snack[0]), BG_COLOR, INTERVAL);
        }, INTERVAL);

        // 給定一個格子的坐標(biāo)和一個格子間隙的矩形(左上角,寬,高) 返回兩個合并的矩形 的左上角、右下角 坐標(biāo)
        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;
        }

        // 慢慢的填充一個矩形 (真的不知道則怎么寫 瞎寫...動畫的執(zhí)行時間可能不等于duration 但一定要保證<=duration
        // 傳入的是矩形左上角和右下角的坐標(biāo) 以及漸變的方向
        function gradientRect(ctx, x1, y1, x2, y2, dir, color, duration) {
            let dur = 20;
            let times = Math.floor(duration / dur); // 更新次數(shù)
            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);
        }
        // 根據(jù)snack二維數(shù)組畫一條蛇
        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]));
                }
            }
        }
        // 傳入一個格子 返回左上角坐標(biāo)
        function getGridULCoordinate(g) {
            return [g[0] * (GRID_WIDTH + GAP_WIDTH) + GAP_WIDTH, g[1] * (GRID_WIDTH + GAP_WIDTH) + GAP_WIDTH];
        }
        // 傳入兩個格子 返回兩個格子之間的矩形縫隙
        // 這里傳入的兩個格子必須是相鄰的
        // 返回一個數(shù)組 分別是這個矩形縫隙的 左上角橫坐標(biāo) 左上角縱坐標(biāo) 寬 高
        function getBetweenTwoGridGap(g1, g2) {
            let width = GRID_WIDTH + GAP_WIDTH;
            if (g1[0] === g2[0]) { // 橫坐標(biāo)相同 是縱向相鄰的兩個格子
                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 { // 縱坐標(biāo)相同 是橫向相鄰的兩個格子
                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>

實話,代碼寫的非常糟糕……我也很無奈……

反正現(xiàn)在蛇可以緩慢順滑的移動了。

接下來要做的是判斷是否觸碰到邊緣或者觸碰到自身導(dǎo)致游戲結(jié)束,以及響應(yīng)鍵盤事件。

這里的改動很簡單。用一個map標(biāo)記每一個格子是否被占。每一個格子(i, j)可以被編號i*row+j。

const GRID_WIDTH = 10;  // 格子的邊長
const GAP_WIDTH = 2;    // 空隙的邊長
const ROW = 10;         // 一共有多少行格子&每行有多少個格子
const COLOR = '#fff';   // 蛇的顏色
const BG_COLOR = '#000';// 背景顏色
const INTERVAL = 300;

const UP = 0, LEFT = 1, RIGHT = 2, DOWN = 3;    // 定義蛇前進(jìn)的方向
const CHANGE = [ [0, -1], [-1, 0], [1, 0], [0, 1] ]; // 每個方向前進(jìn)時格子坐標(biāo)的變化

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] ]; // 初始化一條🐍
    nextDir = dir = RIGHT; // 初始化一個方向
    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(() => {
    // 每隔一段時間就刷新一次
    // 只有轉(zhuǎn)頭方向與當(dāng)前方向垂直的時候 才改變方向
    if (nextDir !== dir && nextDir + dir !== 3) dir = nextDir;
    let head = snack[snack.length - 1]; // 蛇頭
    let change = CHANGE[dir];           // 下一個格子前進(jìn)位置
    let newGrid = [head[0] + change[0], head[1] + change[1]]; // 新格子的位置
    if (!isValidPosition(newGrid)) { // 新位置不合法 游戲結(jié)束
        clearInterval(timer);
        return;
    }
    snack.push(newGrid);    // 新格子加入蛇身的數(shù)組中
    map[getGridNumber(newGrid)] = 1;
    gradientRect(ctx, ...getUniteRect(newGrid, getBetweenTwoGridGap(head, newGrid)), dir, COLOR, INTERVAL);
    let delGrid = snack.shift();    // 刪除蛇尾-最后一個元素
    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;
}
// 獲取一個格子的編號
function getGridNumber(g) {
    return g[0] * ROW + g[1];
}
// 給定一個格子的坐標(biāo)和一個格子間隙的矩形(左上角,寬,高) 返回兩個合并的矩形 的左上角、右下角 坐標(biāo)
function getUniteRect(g, rect) {
/// ... 后面代碼不改變 略....

這時已經(jīng)可以控制蛇的移動了。

最后一個步驟了,畫蘋果。蘋果的位置應(yīng)該是隨機的,且不與蛇身重疊,另外蛇吃到蘋果的時候,長度會加一。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <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;         // 一共有多少行格子&每行有多少個格子
        const COLOR = '#fff';   // 蛇的顏色
        const BG_COLOR = '#000';// 背景顏色
        const FOOD_COLOR = 'red'; // 食物顏色
        const INTERVAL = 300;

        const UP = 0, LEFT = 1, RIGHT = 2, DOWN = 3;    // 定義蛇前進(jìn)的方向
        const CHANGE = [ [0, -1], [-1, 0], [1, 0], [0, 1] ]; // 每個方向前進(jìn)時格子坐標(biāo)的變化

        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] ]; // 初始化一條🐍
            nextDir = dir = RIGHT; // 初始化一個方向
            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(() => {
            // 每隔一段時間就刷新一次
            // 只有轉(zhuǎn)頭方向與當(dāng)前方向垂直的時候 才改變方向
            if (nextDir !== dir && nextDir + dir !== 3) dir = nextDir;
            let head = snack[snack.length - 1]; // 蛇頭
            let change = CHANGE[dir];           // 下一個格子前進(jìn)位置
            let newGrid = [head[0] + change[0], head[1] + change[1]]; // 新格子的位置
            if (!isValidPosition(newGrid)) { // 新位置不合法 游戲結(jié)束
                clearInterval(timer);
                return;
            }
            snack.push(newGrid);    // 新格子加入蛇身的數(shù)組中
            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();    // 刪除蛇尾-最后一個元素
            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);
        }
        // 判斷一個新生成的格子位置是否合法
        function isValidPosition(g) {
            if (g[0] >= 0 && g[0] < ROW && g[1] >= 0 && g[1] < ROW && !map[getGridNumber(g)]) return true;
            return false;
        }
        // 獲取一個格子的編號
        function getGridNumber(g) {
            return g[0] * ROW + g[1];
        }
        function getFoodPosition() {
            let r = Math.floor(Math.random() * (ROW * ROW - snack.length)); // 隨機獲取一個數(shù)字 數(shù)字范圍和剩余的格子數(shù)相同
            for (let i = 0; ; i++) {    // 只有遇到空位的時候 計數(shù)君 r 才減一
                if (!map[i] && --r < 0) return [Math.floor(i / ROW), i % ROW];
            }
        }
        // 給定一個格子的坐標(biāo)和一個格子間隙的矩形(左上角,寬,高) 返回兩個合并的矩形 的左上角、右下角 坐標(biāo)
        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;
        }

        // 慢慢的填充一個矩形 (真的不知道則怎么寫 瞎寫...動畫的執(zhí)行時間可能不等于duration 但一定要保證<=duration
        // 傳入的是矩形左上角和右下角的坐標(biāo) 以及漸變的方向
        function gradientRect(ctx, x1, y1, x2, y2, dir, color, duration) {
            let dur = 20;
            let times = Math.floor(duration / dur); // 更新次數(shù)
            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);
        }
        // 根據(jù)snack二維數(shù)組畫一條蛇
        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]));
                }
            }
        }
        // 傳入一個格子 返回左上角坐標(biāo)
        function getGridULCoordinate(g) {
            return [g[0] * (GRID_WIDTH + GAP_WIDTH) + GAP_WIDTH, g[1] * (GRID_WIDTH + GAP_WIDTH) + GAP_WIDTH];
        }
        // 傳入兩個格子 返回兩個格子之間的矩形縫隙
        // 這里傳入的兩個格子必須是相鄰的
        // 返回一個數(shù)組 分別是這個矩形縫隙的 左上角橫坐標(biāo) 左上角縱坐標(biāo) 寬 高
        function getBetweenTwoGridGap(g1, g2) {
            let width = GRID_WIDTH + GAP_WIDTH;
            if (g1[0] === g2[0]) { // 橫坐標(biāo)相同 是縱向相鄰的兩個格子
                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 { // 縱坐標(biāo)相同 是橫向相鄰的兩個格子
                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>

我不管 我寫完了 我的代碼最棒了(口區(qū)

如果蛇能自己動就好了。。。我的想法很單純。。。但是想了很久沒結(jié)果的時候,Google一下才發(fā)現(xiàn)這好像涉及到AI了。。。頭疼。。。

最終我選取的方案是:

if 存在蛇頭到蘋果的路徑 and 蛇身長度小于整個地圖的一半
    虛擬蛇去嘗試吃蘋果
    if 吃完蘋果后能找到蛇頭到蛇尾的路徑
        BFS到蛇尾
else if 存在蛇頭到蛇尾的路徑
    走蛇頭到蛇尾的最長路徑
else
    隨機一個方向

我只是想練習(xí)Canvas而已…所以就沒有好好寫。代碼有點長就不貼了。

(因為我的蛇很蠢。。是真的蠢。。。

完整代碼可見github --> https://github.com/G-lory/front-end-practice/blob/master/canvas/blog_snack.html

這次寫完感覺我的代碼能力實在是太差了,寫了兩遍還是很亂。 以后還是要多練習(xí)。

反正沒有bug是不可能的,這輩子是不可能的。

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

標(biāo)簽:惠州 蘇州 紅河 文山 營口 萍鄉(xiāng) 甘南 咸陽

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《教你如何一步一步用Canvas寫一個貪吃蛇》,本文關(guān)鍵詞  教你,如何,一步,用,Canvas,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請?zhí)峁┫嚓P(guān)信息告之我們,我們將及時溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。
  • 相關(guān)文章
  • 下面列出與本文章《教你如何一步一步用Canvas寫一個貪吃蛇》相關(guān)的同類信息!
  • 本頁收集關(guān)于教你如何一步一步用Canvas寫一個貪吃蛇的相關(guān)信息資訊供網(wǎng)民參考!
  • 推薦文章
    九色精品美女在线| 亚洲精品在线观看www| 国产一区二区在线播放视频| 欧美日韩国产片| 久久成年人视频| 青青草视频成人| 日韩成人a**站| 久久69精品久久久久久久电影好| 五月婷六月丁香| 岛国91视频| 亚洲欧美激情小说另类| 奇米影视777在线欧美电影观看| 久草在线视频精品| 99久久婷婷国产综合精品电影√| 一区二区精品视频在线观看| 澳门av一区二区三区| 人妻丰满熟妇av无码区app| 欧美不卡视频一区发布| 亚洲精选一区| 国产成人精品日本亚洲专区61| 热re99久久精品国产66热| 性色视频在线观看| 色婷婷国产精品| 天堂色在线视频| 国产99视频精品免视看7| 中文在线资源| 精品69视频一区二区三区Q| 三级影片在线看| 一区二区精品免费视频| 欧美日韩国产页| 香蕉视频网站入口| 国产伦精品一区二区三区在线观看| 日日夜夜狠狠操| 日本亚洲不卡| 美日韩一二三区| 中国china体内裑精亚洲片| 久久欧美一区二区| 精品国产一二三区| 91啪亚洲精品| 黄色录像1级片| 欧美亚洲日本| 成人精品3d动漫| 永久免费看av| 国色天香久久精品国产一区| 成人福利小视频| 天天综合网91| 黄色直播在线| 欧美激情视频给我| 日韩激情电影免费看| 9久久婷婷国产综合精品性色| 一区二区三区在线视频免费观看| 免费人成在线观看播放视频| 乌克兰美女av| 久久综合亚洲社区| 福利片在线看| 国产精品v欧美精品v日韩| 孩娇小videos精品| 久久婷婷国产综合尤物精品| 国产成人精品男人的天堂538| www.九九热.com| 91网站黄www| 中文字幕av久久爽av| 亚洲综合激情在线| 激情成人四房播| 国产91色在线|免| 在线观看www91| 色狠狠一区二区三区| 91中文在线观看| 精品在线播放午夜| 国产91精品视频在线观看| 欧美男人天堂| 91一区二区三区四区| 日韩欧美在线观看一区二区三区| 欧美视频日韩视频| 日本女优在线视频一区二区| 欧美在线观看18| 国产成人av一区二区三区不卡| 国产欧美日韩在线视频| 国产尤物在线观看| 全免费a级毛片| 亚洲一区二区三区四区电影| 国产视频99| 欧美在线观看视频网站| 亚洲激情自拍图| 久久er精品视频| 国产亚洲一区二区三区啪| 中文字幕在线免费视频| 亚洲国产一区二区三区在线播放| 欧美另类在线视频| 久久久亚洲欧洲日产国码αv| 97在线视频国产| 久久视频在线观看免费| 伊人精品在线| 激情五月婷婷综合网| 亚洲tv在线观看| 污网站在线观看视频| 欧美日韩精品专区| 精品无码人妻一区二区三区| 国产老女人精品毛片久久| 国产91精品入口| 国产在线观看欧美| 久久精品国产美女| 能直接看的av| 在线观看一区二区三区三州| 亚洲精品第二页| 天堂在线观看视频观看www| 精品国产电影| 伊人精品一区二区三区| 国产视频亚洲精品| 亚州福利视频| 一个人看免费www视频有多爽| 欧美一区二区三区系列电影| 成人一区二区三区视频在线观看| h视频免费在线观看| 中文在线不卡| 国产老肥熟一区二区三区| 农村少妇一区二区三区四区五区| 99视频一区二区| 久热中文字幕在线观看| 久久五月天小说| 91久久精品国产91久久| 丁香花五月婷婷| 欧美日韩精品欧美日韩精品一综合| 亚洲www色| 国产精品天天干| 两个人hd高清在线观看| 一本大道久久加勒比香蕉| 五月天av在线播放| 欧美大片一区二区| 欧美精彩视频一区二区三区| 国产精品99久久久久久董美香| 在线视频欧美日韩精品| 成人综合在线网站| 9191国语精品高清在线| 爱草在线视频| 国产精品福利一区| 色琪琪原网站亚洲香蕉| 国产激情久久久久久熟女老人av| 无人码人妻一区二区三区免费| 日韩欧美国产综合在线一区二区三区| 四虎精品影院在线观看视频| 久久久久久国产精品视频| 欧美激情一区二区三级高清视频| 成人网址大全| 热久久美女精品天天吊色| 欧美在线视频免费观看| 国产欧美精品区一区二区三区| 亚洲熟女乱色一区二区三区久久久| 手机福利小视频在线播放| av大片免费在线观看| 欧洲黄色一区| 精品国产一区二区三区小蝌蚪| 成人女保姆的销魂服务| 香蕉国产精品| 色播五月激情综合网| 不卡视频免费播放| 高清国产在线一区| 福利视频网址导航| 免费av在线一区二区| 黄色三级在线| 欧美国产一区二区在线| 老司机在线免费视频| 水蜜桃久久夜色精品一区| 久久精品亚洲国产| 波多野结衣中文字幕在线播放| 成年人的黄色片| 久久久亚洲精品一区二区三区| 午夜视频在线| 最新91在线| 日韩成人一区二区三区| 91亚洲国产成人久久精品| 韩国三级电影在线观看婷婷| 欧美视频免费在线观看| 欧美视频在线免费看| 色大18成网站www在线观看| 精品成人av一区二区三区| 狠狠色丁香婷综合久久| 川上优的av在线一区二区| 牛人国产偷窥女洗浴在线观看| 天然素人一区二区视频| 久久久亚洲精选| 成人免费高清完整版在线观看| 麻豆av在线免费观看| 亚洲高清在线免费| 欧美日韩专区在线| 欧美aaa级| 日产精品久久久| 91美女片黄在线观看游戏| 国产乱淫a∨片免费视频| 97人人在线| 波多野结衣一区二区三区在线| 无码任你躁久久久久久久| 99久热这里只有精品视频免费观看| 豆花视频一区二区| 亚洲另类色综合网站| av大全在线观看| 亚洲国产成人精品视频| 国产一区二区三区在线看| 杨钰莹一级淫片aaaaaa播放| 另类成人小视频在线| 日本免费网站| 国产丝袜一区二区三区免费视频| 成年人视频观看| 久久xxx视频| 日韩一级中文字幕| 欧美日韩一区二区三区在线电影| 高清乱码免费看污| 日本视频中文字幕一区二区三区| 亚洲美女在线国产| 国产综合色香蕉精品| 欧美电影在线观看一区| 欧美三级小视频| 亚洲精品xxx| 国产日韩一区二区在线| 男生女生差差差的视频在线观看| 黄页在线观看免费| 国产亚洲情侣一区二区无| 欧美爱爱视频| 色悠悠久久88| 中文字幕第315页| 欧美 国产 综合| 尤物国产精品| 国产毛片毛片毛片毛片毛片毛片| 不卡视频一二三四| av日韩久久| 久一视频在线观看| 一级片在线免费播放| 欧美日韩日本国产亚洲在线| 国产一级在线| 大片免费在线观看| 国产成人一区二| 午夜精品一区二区三区免费视频| 91免费精品国自产拍在线不卡| 免费a漫导航| 97精品人妻一区二区三区| 日本激情视频在线观看| 五月婷婷伊人网| 裸体大乳女做爰69| 思思99re6国产在线播放| 姬川优奈av一区二区在线电影| 久久这里只有精品6| 中文字幕久久av| 中文字幕无线码一区| www.黄色国产| 久久天天躁狠狠躁夜夜躁2014| 精品人妻一区二区三区含羞草| ady日本映画久久精品一区二区| 欧美日韩亚洲综合在线 欧美亚洲特黄一级| 波多野结衣在线免费观看| 国产精品第七十二页| 99视频精品视频高清免费| 人妻一区二区视频| 久久久天堂国产精品女人| 26uuu色噜噜精品一区| 波多野结衣国产精品| 综合色一区二区| 国产无套精品一区二区| 综合操久久久| 麻豆av免费在线观看| 国产色在线 com| 欧美h在线观看| 欧洲日本不卡| 免费av大全| 99re8这里有精品热视频8在线| 国产又色又爽又黄刺激在线视频| 无码日韩精品一区二区| 日韩av网站在线观看| 亚洲欧洲中文日韩久久av乱码| 一个色综合网| 老汉色影院首页| 美女一区二区视频| 亚洲精品欧美二区三区中文字幕| 五月丁香综合缴情六月小说| 青草影院在线观看| 在线成人小视频| 一二三区不卡| 国产精品va在线观看无码| 大又大又粗又硬又爽少妇毛片| 一区二区三区在线视频看| 九九热最新视频//这里只有精品| 国产真实精品久久二三区| 91国语精品自产拍在线观看性色| 精品动漫3d一区二区三区免费| 精品国产免费一区二区三区| 未满十八勿进黄网站一区不卡| 免费中国女人69xxxxx视频| 91精品国产综合久久久蜜臀九色| 日韩激情欧美| 91香蕉视频网| 在线视频成人| 秋霞a级毛片在线看| 国产成人精品免费看在线播放| 毛片a片免费观看| 国产美女被遭强高潮免费网站| 国产三级日本三级在线播放| 精品久久久久久久久久久| 日韩精选在线观看| 亚洲人成伊人成综合网久久久| 国产一区二区视频网站| www.av视频在线观看| 日韩av一区二区三区在线| 久久本道综合色狠狠五月| 一区二区三区国产在线观看| 日韩一区二区三免费高清在线观看| 国产成人亚洲综合青青| 3d性欧美动漫精品xxxx软件| 第一视频专区在线| 中文字幕亚洲一区二区va在线| 国产寡妇树林野战在线播放| 国产精品久久久久久影院8一贰佰| 欧美日韩精品一区二区三区视频| 精品久久久亚洲| 同性恋视频一区| 美女性感视频久久久| 三级欧美在线一区| 在线激情视频| 黄色片在线免费观看| 一区二区成人在线| 91手机视频在线| 亚洲精品国产av| 嫩草影院永久一二三入口| 日韩欧美一区二区三区视频| 国产三级国产精品国产国在线观看| 天天操天天曰| 在线看视频你懂得| 超碰最新在线| 在线观看亚洲一区二区| 欧美日韩中文字幕综合视频|