以前在慕課網看了幾集Canvas的視頻,一直想着寫點東西練練手。感受貪吃蛇算是比較簡單的了,當年大學的時候還寫過C語言字符版的,沒想到仍是遇到了不少問題。html
最終效果以下(圖太大的話 時間太長 錄製gif的軟件有時限…)git
首先定義遊戲區域。貪吃蛇的屏幕上只有蛇身和蘋果兩種元素,而這兩個均可以用正方形格子構成。正方形之間添加縫隙。爲何要添加縫隙?你能夠想象當你成功填滿全部格子的時候,若是沒有縫隙,就是一個實心的大正方形……你根本不知道蛇身什麼樣。github
畫了一個圖。canvas
格子是左上角的座標是(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) 。dom
假設如今蛇身是由三個藍色的格子組成的,咱們不能只繪製三個格子,兩個紫色的空隙也必定要繪製,不然,仍是以前說的,你根本不知道蛇身什麼樣。以下圖,不畫縫隙雖然也能玩,可是體驗確定不同。動畫
繪製相鄰格子之間間隙
不繪製間隙spa
如今咱們能夠嘗試着畫一條蛇了。蛇身其實就是一個格子的集合,每一個格子用包含兩個位置信息的數組表示,整條蛇能夠用二維數組表示。3d
<!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>
我初始化了一條蛇,看起來是符合預期的。code
接下來要作的是讓蛇動起來。蛇動起來這事很簡單,蛇向着當前運動的方向前進一格,刪掉蛇尾,也就是最後一個格子就能夠了。以前說的二維數組表示一條蛇,如今規定其中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了。。。頭疼。。。
隨便看了幾個文章,部分思想是相同的。(http://www.waitingfy.com/archives/95一、https://github.com/chuyangliu/Snake)
最終我選取的方案是:
if 存在蛇頭到蘋果的路徑 and 蛇身長度小於整個地圖的一半 虛擬蛇去嘗試吃蘋果 if 吃完蘋果後能找到蛇頭到蛇尾的路徑 BFS到蛇尾 else if 存在蛇頭到蛇尾的路徑 走蛇頭到蛇尾的最長路徑 else 隨機一個方向
我只是想練習Canvas而已…因此就沒有好好寫。代碼有點長就不貼了。
(由於個人蛇很蠢。。是真的蠢。。。
完整代碼可見github --> https://github.com/G-lory/front-end-practice/blob/master/canvas/blog_snack.html
此次寫完感受個人代碼能力實在是太差了,寫了兩遍仍是很亂。 之後仍是要多練習。
反正沒有bug是不可能的,這輩子是不可能的。