亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > HTML5 > 正文

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

2024-09-05 07:19:18
字體:
來源:轉載
供稿:網友

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

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

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

畫了一個圖。

 

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

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

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

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

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

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

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

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

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;    // 定義蛇前進的方向const CHANGE = [ [0, -1], [-1, 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] ]; // 初始化一條🐍let dir = RIGHT; // 初始化一個方向drawSnack(ctx, snack, COLOR);let timer = setInterval(() => {    // 每隔一段時間就刷新一次    let head = snack[snack.length - 1]; // 蛇頭    let change = CHANGE[dir];           // 下一個格子前進位置    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();    // 刪除蛇尾-最后一個元素    ctx.fillRect(...getGridULCoordinate(delGrid), GRID_WIDTH, GRID_WIDTH); // 擦除刪除元素    ctx.fillRect(...getBetweenTwoGridGap(delGrid, snack[0])); // 擦除刪除元素和當前最后一個元素之間的縫隙}, 1000);..... // 和之前相同

現在蛇已經可以動起來了。

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

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

<!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;    // 定義蛇前進的方向        const CHANGE = [ [0, -1], [-1, 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] ]; // 初始化一條🐍        let dir = RIGHT; // 初始化一個方向        drawSnack(ctx, snack, COLOR);        let timer = setInterval(() => {            // 每隔一段時間就刷新一次            let head = snack[snack.length - 1]; // 蛇頭            let change = CHANGE[dir];           // 下一個格子前進位置            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();    // 刪除蛇尾-最后一個元素            gradientRect(ctx, ...getUniteRect(delGrid, getBetweenTwoGridGap(delGrid, snack[0])),                 getDirection(delGrid, snack[0]), BG_COLOR, INTERVAL);        }, INTERVAL);        // 給定一個格子的坐標和一個格子間隙的矩形(左上角,寬,高) 返回兩個合并的矩形 的左上角、右下角 坐標        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;        }        // 慢慢的填充一個矩形 (真的不知道則怎么寫 瞎寫...動畫的執行時間可能不等于duration 但一定要保證<=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);        }        // 根據snack二維數組畫一條蛇        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]));                }            }        }        // 傳入一個格子 返回左上角坐標        function getGridULCoordinate(g) {            return [g[0] * (GRID_WIDTH + GAP_WIDTH) + GAP_WIDTH, g[1] * (GRID_WIDTH + GAP_WIDTH) + GAP_WIDTH];        }        // 傳入兩個格子 返回兩個格子之間的矩形縫隙        // 這里傳入的兩個格子必須是相鄰的        // 返回一個數組 分別是這個矩形縫隙的 左上角橫坐標 左上角縱坐標 寬 高        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>

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

反正現在蛇可以緩慢順滑的移動了。

接下來要做的是判斷是否觸碰到邊緣或者觸碰到自身導致游戲結束,以及響應鍵盤事件。

這里的改動很簡單。用一個map標記每一個格子是否被占。每一個格子(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;    // 定義蛇前進的方向const CHANGE = [ [0, -1], [-1, 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] ]; // 初始化一條🐍    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(() => {    // 每隔一段時間就刷新一次    // 只有轉頭方向與當前方向垂直的時候 才改變方向    if (nextDir !== dir && nextDir + dir !== 3) dir = nextDir;    let head = snack[snack.length - 1]; // 蛇頭    let change = CHANGE[dir];           // 下一個格子前進位置    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();    // 刪除蛇尾-最后一個元素    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];}// 給定一個格子的坐標和一個格子間隙的矩形(左上角,寬,高) 返回兩個合并的矩形 的左上角、右下角 坐標function getUniteRect(g, rect) {/// ... 后面代碼不改變 略....

這時已經可以控制蛇的移動了。

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

<!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;    // 定義蛇前進的方向        const CHANGE = [ [0, -1], [-1, 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] ]; // 初始化一條🐍            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(() => {            // 每隔一段時間就刷新一次            // 只有轉頭方向與當前方向垂直的時候 才改變方向            if (nextDir !== dir && nextDir + dir !== 3) dir = nextDir;            let head = snack[snack.length - 1]; // 蛇頭            let change = CHANGE[dir];           // 下一個格子前進位置            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();    // 刪除蛇尾-最后一個元素            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)); // 隨機獲取一個數字 數字范圍和剩余的格子數相同            for (let i = 0; ; i++) {    // 只有遇到空位的時候 計數君 r 才減一                if (!map[i] && --r < 0) return [Math.floor(i / ROW), i % ROW];            }        }        // 給定一個格子的坐標和一個格子間隙的矩形(左上角,寬,高) 返回兩個合并的矩形 的左上角、右下角 坐標        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;        }        // 慢慢的填充一個矩形 (真的不知道則怎么寫 瞎寫...動畫的執行時間可能不等于duration 但一定要保證<=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);        }        // 根據snack二維數組畫一條蛇        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]));                }            }        }        // 傳入一個格子 返回左上角坐標        function getGridULCoordinate(g) {            return [g[0] * (GRID_WIDTH + GAP_WIDTH) + GAP_WIDTH, g[1] * (GRID_WIDTH + GAP_WIDTH) + GAP_WIDTH];        }        // 傳入兩個格子 返回兩個格子之間的矩形縫隙        // 這里傳入的兩個格子必須是相鄰的        // 返回一個數組 分別是這個矩形縫隙的 左上角橫坐標 左上角縱坐標 寬 高        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>

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

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

最終我選取的方案是:

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

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

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

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

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

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

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
亚洲区一区二区| 亚洲午夜精品久久久久久久久久久久| 国产香蕉精品视频一区二区三区| 中文字幕久热精品在线视频| 国产中文字幕日韩| 国产精品女人久久久久久| 日韩美女视频中文字幕| 亚洲a∨日韩av高清在线观看| 黑人与娇小精品av专区| 久久精品国产清自在天天线| 一区二区三区回区在观看免费视频| 欧美老女人www| 亚洲精品日韩av| 国产mv免费观看入口亚洲| 亚洲理论片在线观看| 亚洲国产一区二区三区在线观看| 91久久国产婷婷一区二区| 欧美极品少妇xxxxⅹ裸体艺术| 国产精品免费久久久久影院| 亚洲视频在线观看网站| 日韩精品在线私人| 欧美中文字幕在线播放| 欧美极品少妇与黑人| 国产视频一区在线| 欧美精品www| 国产91久久婷婷一区二区| 一区二区三区动漫| 亚洲自拍欧美色图| 亚洲欧美成人网| 国产成人拍精品视频午夜网站| 欧美日韩亚洲一区二区| 国产这里只有精品| 日韩av日韩在线观看| 黑人极品videos精品欧美裸| 亚洲香蕉伊综合在人在线视看| 国产综合在线看| 欧美精品电影免费在线观看| 日韩大片免费观看视频播放| 日韩免费av一区二区| 隔壁老王国产在线精品| 国内精品一区二区三区四区| 国产69久久精品成人看| 亚洲精品欧美日韩专区| 奇米一区二区三区四区久久| 精品亚洲国产视频| 一本一本久久a久久精品牛牛影视| 91精品国产高清久久久久久91| 91色p视频在线| 亚洲欧洲成视频免费观看| 97在线视频精品| 亚洲欧美另类自拍| 欧美极品少妇全裸体| 91国偷自产一区二区三区的观看方式| 性欧美在线看片a免费观看| 亚洲精品在线看| 色综合男人天堂| 国产一区二区三区在线视频| 日韩免费高清在线观看| 久久久999精品视频| 91成人在线观看国产| 欧美丰满片xxx777| 欧美激情va永久在线播放| 亚洲成人av片在线观看| 精品久久久久人成| 丝袜亚洲另类欧美重口| 91精品国产网站| 国产精品久久久久久久久免费| 久久久久亚洲精品成人网小说| 亚洲国产精品热久久| 午夜精品一区二区三区在线| 日韩av观看网址| 91手机视频在线观看| 狠狠干狠狠久久| 欧美另类第一页| 国产精品亚洲一区二区三区| 国产精品久久综合av爱欲tv| 亚洲一级片在线看| 精品无人国产偷自产在线| 米奇精品一区二区三区在线观看| 在线日韩av观看| 国产精品扒开腿做爽爽爽男男| 亚洲欧美在线免费| 欧美在线视频a| 久久亚洲精品毛片| 欧美xxxx做受欧美.88| 亚洲第一男人天堂| 成人性生交大片免费看小说| 亚洲剧情一区二区| 韩曰欧美视频免费观看| 国产mv免费观看入口亚洲| 亚洲天堂av高清| 亚洲精品美女在线观看| 亚洲第一中文字幕| 欧美人在线视频| 欧美乱大交xxxxx另类电影| 亚洲欧美日韩精品久久亚洲区| 久久精品国产欧美激情| 在线看国产精品| 91久久久久久| 亚洲性日韩精品一区二区| 欧美黑人极品猛少妇色xxxxx| 在线视频欧美性高潮| 国内精品久久久久影院 日本资源| 亚洲欧美日韩在线一区| 日韩av综合网| 欧美中文在线视频| 欧美中文字幕第一页| 国产亚洲成av人片在线观看桃| 日本a级片电影一区二区| 亚洲亚裔videos黑人hd| 中文字幕亚洲欧美| 国产一区二区在线播放| 欧美性猛交xxxx乱大交极品| 国产精品久久久久久久久久久久| 国产精品成人久久久久| 国产精品一久久香蕉国产线看观看| 久久高清视频免费| 色偷偷91综合久久噜噜| 91夜夜揉人人捏人人添红杏| 亚洲天堂精品在线| 亚洲欧美国产另类| 亚洲精品久久久久中文字幕欢迎你| 欧美精品一区二区三区国产精品| 亚洲在线免费观看| 亚洲网在线观看| 亚洲无亚洲人成网站77777| 亚洲最新视频在线| 欧美成人四级hd版| 日韩在线欧美在线国产在线| 亚洲精品一区在线观看香蕉| 日本三级久久久| 不用播放器成人网| 亚洲高清一区二| 欧美一区二区三区精品电影| 亚洲娇小xxxx欧美娇小| 亚洲成人网久久久| 97视频在线观看免费高清完整版在线观看| 最近中文字幕日韩精品| 亚洲欧美激情在线视频| 成人国产精品久久久久久亚洲| 国产精品aaa| 97久久伊人激情网| 亚洲免费伊人电影在线观看av| 欧美日韩性生活视频| 午夜精品蜜臀一区二区三区免费| 欧美日韩精品在线| 色综合老司机第九色激情| y97精品国产97久久久久久| 国产美女久久精品香蕉69| 久久国产精品久久久久久| 日韩在线免费视频观看| 国产性色av一区二区| 欧美日韩加勒比精品一区| 久久香蕉精品香蕉| 精品福利樱桃av导航| 色哟哟网站入口亚洲精品| 欧美成人激情图片网| 亚洲精品美女免费| 国产精品久久久久久久久久小说| 中文字幕av一区| 精品国产电影一区| 精品国产自在精品国产浪潮| 亚洲成人激情图| 国产91久久婷婷一区二区|