站長資訊網
        最全最豐富的資訊網站

        實例分享之JavaScript實現貪吃蛇小游戲

        本篇文章給大家帶來了利用JavaScript實現貪吃蛇小游戲的實例,希望對大家有幫助。

        實例分享之JavaScript實現貪吃蛇小游戲

        JavaScript實現貪吃蛇小游戲

        功能概述

        本程序實現了如下功能:

        1. 貪吃蛇的基本功能

        2. 統計得分

        3. 開始與暫停

        4. 選擇難度等級

        5. 設置快捷鍵

          5.1 通過ijkl,wsad也能實現方向的切換

          5.2 通過“P” 表示暫停,“C”表示開始或繼續,"R"表示重新開始

        實現過程

        最開始的實現原理其實是參考的csdn的一位大神,他用JavaScript20行就實現了貪吃蛇的基本功能,難等可貴的是還沒有bug,鏈接在此

        要實現貪吃蛇大概有以下幾個步驟:

        • 畫一個蛇的移動區域

        • 畫一條蛇

        • 畫食物

        • 讓蛇動起來

        • 設定游戲規則

        • 設置難度等級

        • 設置開始與暫停

        • 設置游戲結束后續操作

        • 實現人機交互頁面

        注:下面的過程講解部分只是講述了部分原理與實現,建議一邊看最后的完整代碼,一邊看下面的講解,更容易理解每一部分的原理與實現

        畫蛇的活動區域

        首先我們畫蛇的活動區域,我們采用JavaScript的canvas進行繪制

        我們用一個400 × 400 400times 400400×400的區域作為蛇的活動區域

        <canvas id="canvas" width="400" height="400"></canvas>

        同時通過CSS設置一個邊界線

        #canvas {     border: 1px solid #000000; /* 設置邊框線 */}

        畫蛇和食物

        效果如下:

        實例分享之JavaScript實現貪吃蛇小游戲

        在畫蛇前我們需要想下蛇的數據結構,在這里我們采取最簡單的隊列表示蛇

        • 隊首表示蛇頭位置,隊尾表示蛇尾位置

        • 我們將之前畫的 400 × 400 400times 400 400×400區域劃分為400個 20 × 20 20times 20 20×20的方塊,用這些方塊組成蛇,那么蛇所在方塊的位置的取值范圍就是0~399

          舉個例子:

          var snake=[42,41,40];

          上述代碼表示蛇所在的位置為42,41,40三個方塊,其中蛇頭為42,蛇尾為40

        對于食物,我們可以用一個變量food存儲食物的位置即可,食物的取值范圍為0~399,且不包括蛇的部分,由于游戲中需要隨機產生食物,隨機函數實現如下:

        // 產生min~max的隨機整數,用于隨機產生食物的位置function random(min, max) {     const num = Math.floor(Math.random() * (max - min)) + min;     return num;}

        當食物被蛇吃掉后就需要重新刷新食物,由于食物不能出現在蛇所在的位置,我們用一個while循環,當食物的位置不在蛇的數組中則跳出循環

        while (snake.indexOf((food = random(0, 400))) >= 0); // 重新刷新食物,注意食物應不在蛇內部

        我們接下來通過canvas進行繪制

        首先在js中獲取canvas組件

        const canvas = document.getElementById("canvas");const ctx = canvas.getContext("2d");

        然后寫繪制函數用于繪制方格,繪制方格的時候注意我們預留1px作為邊框,即我們所畫的方格的邊長為18,我們實際填充的是18 × 18 18times 1818×18的方塊,方塊的x、y坐標(方塊的左上角)的計算也需要注意加上1px

        注:canvas的原點坐標在左上角,往右為x軸正方向,往下為y軸正方向

        // 用于繪制蛇或者是食物代表的方塊,seat為方塊位置,取值為0~399,color為顏色function draw(seat, color) {     ctx.fillStyle = color; // 填充顏色     // fillRect的四個參數分別表示要繪制方塊的x坐標,y坐標,長,寬,這里為了美觀留了1px用于邊框     ctx.fillRect(         (seat % 20) * 20 + 1,         Math.floor(seat / 20) * 20 + 1,         18,         18     );}

        讓蛇動起來

        我們要想讓蛇動起來,首先要規定蛇運動的方向,我們用一個變量direction來表示蛇運動的方向,其取值范圍為{1,-1,20,-20},1 表示向右,-1 表示向左,20 表示向下,-20 表示向上,運動時只需要將蛇頭的位置加上direction就可以表示新蛇頭的位置,這樣我們就可以表示蛇的運動了。

        那么如何讓蛇動起來呢,對于蛇的每次移動,我們需要完成下面幾個基本操作:

        1. 將蛇運動的下一個位置變成新蛇頭
          • 將下一個位置加入蛇隊列
          • 繪制下一個方塊為淺藍色
        2. 把舊蛇頭變成蛇身
          • 繪制舊蛇頭為淺灰色
        3. 刪除舊蛇尾
          • 將舊蛇尾彈出蛇隊列
          • 繪制舊蛇尾位置為白色

        當蛇吃掉食物時(蛇的下一個位置為食物所在位置)則需更新食物的位置,并繪制新食物為黃色,此時則不需要刪除舊蛇尾,這樣可以實現蛇吃完食物后長度的增加功能

        我們需要寫一個函數實現上述操作,并且要不斷地調用這個函數,從而實現頁面的刷新,即我們所說的動態效果

        n = snake[0] + direction; // 找到新蛇頭坐標snake.unshift(n); // 添加新蛇頭draw(n, "#1a8dcc"); // 繪制新蛇頭為淺藍色draw(snake[1], "#cececc"); // 將原來的蛇頭(淺藍色)變成蛇身(淺灰色)if (n == food) {     while (snake.indexOf((food = random(0, 400))) >= 0); // 重新刷新食物,注意食物應不在蛇內部     draw(food, "Yellow"); // 繪制食物} else {     draw(snake.pop(), "White"); // 將原來的蛇尾繪制成白色}

        接下來,我們需要實現通過鍵盤控制蛇的運動

        我們需要獲取鍵盤的key值,然后通過一個監聽函數去監聽鍵盤按下的操作,我們這里通過上下左右鍵(還拓展了WSAD鍵和IJKL鍵控制上下左右方向),同時設置一個變量n表示下一步的方向

        // 用于綁定鍵盤上下左右事件,上下左右方向鍵,代表上下左右方向document.onkeydown = function (event) {     const keycode = event.keyCode;     if (keycode <= 40) {         // 上 38 下 40 左 37 右 39         n = [-1, -20, 1, 20][keycode - 37] || direction; // 若keycode為其他值,即表示按了沒用的鍵,則方向不變     } else if (keycode <= 76 && keycode >= 73) {         // i 73 j 74 k 75 l 76         n = [-20, -1, 20, 1][keycode - 73] || direction;     } else {         switch (keycode) {             case 87: //w                 n = -20;                 break;             case 83: //s                 n = 20;                 break;             case 65: //a                 n = -1;                 break;             case 68: //d                 n = 1;                 break;             default:                 n = direction;         }     }     direction = snake[1] - snake[0] == n ? direction : n; // 若方向與原方向相反,則方向不變};

        設定游戲規則

        貪吃蛇的最基礎的游戲規則如下:

        1. 蛇如果撞到墻或者蛇的身體或尾巴則游戲結束
        2. 蛇如果吃掉食物則蛇的長度會增加(上一步已經實現)且得分會增加

        先實現第一個,具體如下:

        注:下面的一段代碼中的n即為新蛇頭的位置

        // 判斷蛇頭是否撞到自己或者是否超出邊界if (     snake.indexOf(n, 1) > 0 ||     n < 0 ||     n > 399 ||     (direction == 1 && n % 20 == 0) ||     (direction == -1 && n % 20 == 19)) {     game_over();}

        接下來我們實現得分統計,對于得分的計算我們只需要設置一個變量score,用于統計得分,然后每吃一個食物,該變量加一,然后將得分信息更新到網頁相應位置

        score = score + 1;score_cal.innerText = "目前得分: " + score; // 更新得分

        設置難度等級

        我們在網頁上設置一個單選框,用于設置難度等級

        <form action="" id="mode_form">     難度等級:      <input type="radio" name="mode" id="simply" value="simply" checked />     <label for="simply">簡單</label>     <input type="radio" name="mode" id="middle" value="middle" />     <label for="middle">中級</label>     <input type="radio" name="mode" id="hard" value="hard" />     <label for="hard">困難</label></form>

        效果如下:

        實例分享之JavaScript實現貪吃蛇小游戲

        那么我們后臺具體如何設置難度等級的功能呢?

        我們采取調用蛇運動的函數的時間間隔來代替難度,時間間隔越小則難度越大,我們分三級:簡單、中級、困難

        我們創建一個時間間隔變量time_internal,然后用一個函數獲取單選框的取值,并將相應模式的時間間隔賦值給time_internal

        // 用刷新間隔代表蛇的速度,刷新間隔越長,則蛇的速度越慢const simply_mode = 200;const middle_mode = 100;const hard_mode = 50;var time_internal = simply_mode; // 刷新時間間隔,用于調整蛇的速度,默認為簡單模式// 同步難度等級function syncMode() {     var mode_value = "";     for (var i = 0; i < mode_item.length; i++) {         if (mode_item[i].checked) {             mode_value = mode_item[i].value;         }     }     switch (mode_value) {         case "simply":             time_internal = simply_mode;             break;         case "middle":             time_internal = middle_mode;             break;         case "hard":             time_internal = hard_mode;             break;     }}

        最后只需要在蛇每次移動前調用一次上述函數syncMode()就可以實現難度切換,至于蛇的速度的具體調節且看下面如何講解

        設置開始與暫停

        如何實現蛇的移動動態效果,如何暫停,如何繼續,速度如何調節,這就涉及到JavaScript的動畫的部分了,建議看下《JavaScript高級程序設計(第4版)》第18章的部分,講的很詳細。

        在最初的“20行JavaScript實現貪吃蛇”中并沒有實現開始與暫停,其實現動態效果的方法為設置一個立即執行函數!function() {}();,然后在該函數中使用setTimeout(arguments.callee, 150);,每隔0.15秒調用此函數,從而實現了網頁的不斷刷新,也就是所謂的動態效果。

        后來,我通過web課程老師的案例(彈球游戲)中了解到requestAnimationFrame方法可以實現動畫效果,于是我便百度查詢,最后在翻書《JavaScript高級程序設計(第4版)》第18章動畫與Canvas圖形中得到啟發–如何實現開始與取消,如何自定義時間間隔(實現難度調節,蛇的速度)

        書中給出的開始動畫與取消動畫的方法如下:

        注:為了便于理解,自己修改過原方法

        var requestID; // 用于標記請求ID與取消動畫 function updateProgress() {  	// do something...     requestID = requestAnimationFrame(updateProgress); // 調用后在函數中反復調用該函數 }  id = requestAnimationFrame(updateProgress); // 第一次調用(即開始動畫)  cancelAnimationFrame(requestID); // 取消動畫

        書中講述道:

        requestAnimationFrame()已經解決了瀏覽器不知道 JavaScript 動畫何時開始的問題, 以及最佳間隔是多少的問題?!ぁぁぁぁぁ?/p>

        傳給 requestAnimationFrame()的函數實際上可以接收一個參數,此參數是一個 DOMHighResTimeStamp 的實例(比如 performance.now()返回的值),表示下次重繪的時間。這一點非常重要: requestAnimationFrame()實際上把重繪任務安排在了未來一個已知的時間點上,而且通過這個參數 告訴了開發者。基于這個參數,就可以更好地決定如何調優動畫了。

        requestAnimationFrame()返回一個請求 ID,可以用于通過另一個 方法 cancelAnimationFrame()來取消重繪任務

        書中同樣給出了如何控制時間間隔的方法:

        書中講述道:

        配合使用一個計時器來限制重繪操作執行的頻率。這樣,計時器可以限制實際的操作執行間隔,而 requestAnimationFrame 控制在瀏覽器的哪個渲染周期中執行。下面的例子可以將回調限制為不超過 50 毫秒執行一次

        具體方法如下:

        let enabled = true; function expensiveOperation() {  	console.log('Invoked at', Date.now()); } window.addEventListener('scroll', () => {   if (enabled) {       enabled = false;       requestAnimationFrame(expensiveOperation);       setTimeout(() => enabled = true, 50);   } });

        由上面的方法我得到啟發,在此處我們可以設置一個控制函數,用于控制隔一定的時間調用一次蛇運動的函數,實現如下:

        // 控制游戲的刷新頻率,每隔time_internal時間間隔刷新一次function game_control(){     if(enabled){         enabled = false;         requestAnimationFrame(run_game);         setTimeout(() => enabled = true, time_internal);     }     run_id = requestAnimationFrame(game_control);}// 啟動或繼續游戲function run_game() {     syncMode(); // 同步難度等級     n = snake[0] + direction; // 找到新蛇頭坐標     snake.unshift(n); // 添加新蛇頭     // 判斷蛇頭是否撞到自己或者是否超出邊界     if (         snake.indexOf(n, 1) > 0 ||         n < 0 ||         n > 399 ||         (direction == 1 && n % 20 == 0) ||         (direction == -1 && n % 20 == 19)     ) {         game_over();     }     draw(n, "#1a8dcc"); // 繪制新蛇頭為淺藍色     draw(snake[1], "#cececc"); // 將原來的蛇頭(淺藍色)變成蛇身(淺灰色)     if (n == food) {         score = score + 1;         score_cal.innerText = "目前得分: " + score; // 更新得分         while (snake.indexOf((food = random(0, 400))) >= 0); // 重新刷新食物,注意食物應不在蛇內部         draw(food, "Yellow"); // 繪制食物     } else {         draw(snake.pop(), "White"); // 將原來的蛇尾繪制成白色     }     // setTimeout(arguments.callee, time_internal); //之前的方案,無法實現暫停和游戲的繼續}

        至于暫停只需要在特定的位置調用cancelAnimationFrame(run_id);就可以了

        設置游戲結束后續操作

        我想的是在游戲結束后出現一個“彈窗”,顯示最終得分和是否再來一把

        效果如下:

        實例分享之JavaScript實現貪吃蛇小游戲

        首先,我們實現網頁的彈窗,通過調研發現JavaScript的彈窗可以通過alert()的方法實現,不過在網頁上直接彈窗感覺不太美觀,而且影響體驗,于是我想了一下,可以采用一個p標簽實現偽彈窗,在需要顯示的時候設置其display屬性為block,不需要顯示的時候設置其display屬性為none,就類似于Photoshop里面的圖層概念,這樣我們就可以在平常的時候設置其display屬性為none觸發game over時設置其display屬性為block,實現如下:

        <p id="game_over">     <h3 id="game_over_text" align="center">游戲結束!</h3>     <h3 id="game_over_score" align="center">您的最終得分為: 0分</h3>     <button id="once_again">再來一把</button>     <button id="cancel">取消</button></p>

        其CSS部分如下:

        #game_over {     display: none; /* 設置game over 窗口不可見 */     position: fixed;     top: 190px;     left: 65px;     width: 280px;     height: 160px;     background-color: aliceblue;     border-radius: 5px;     border: 1px solid #000; /* 設置邊框線 */}#once_again {     position: relative;     left: 20px;}#cancel {     position: relative;     left: 50px;}

        接下來,我們需要實現game over的后續操作:暫停動畫,顯示得分,顯示“彈窗”

        function game_over(){     cancelAnimationFrame(run_id);     game_over_score.innerText = "您的最終得分為: " + score + "分";     game_over_p.style.display = "block";}

        實現人機交互頁面

        接下來的部分就是提高用戶體驗的部分,具體實現下列功能/操作

        1. 游戲說明
        2. 人機交互按鈕:開始/繼續,暫停,重新開始
        3. 快捷鍵
          • 由于在游戲過程中通過鼠標移動到暫停鍵暫停,時間上太慢,可能造成游戲終止,故應該設置開始/繼續(C)、暫停(P)、重新開始(R)的快捷鍵
          • 有些電腦鍵盤的上下左右鍵較小,操作起來不太方便,可以添加WSAD或者IJKL擴展,用于控制上下左右方向

        效果如下:

        實例分享之JavaScript實現貪吃蛇小游戲

        至于寫界面的代碼,可以看文末的完整代碼,這里就稍微講解下綁定按鍵點擊事件與綁定快捷鍵

        我們首先看下綁定按鍵點擊事件,點擊”開始/繼續“只需要調用requestAnimationFrame(game_control);,點擊”暫停“只需要調用cancelAnimationFrame(run_id);

        // 綁定開始按鈕點擊事件start_btn.onclick = function () {     run_id = requestAnimationFrame(game_control);};// 綁定暫停按鈕點擊事件pause_btn.onclick = function () {     cancelAnimationFrame(run_id);};

        點擊“重新開始”的話,則需要先暫停動畫,然后刪除畫面上的蛇和食物,初始化所有設置,然后再調用requestAnimationFrame(game_control);開始游戲

        注:初始化時需要初始化得分與難度等級,這里解釋下為什么要將第一個食物設置為蛇頭下一個位置,因為這樣的話蛇會自動先吃一個食物,繼而可以通過“開始 / 繼續” 一個按鈕實現開始和繼續操作,同時run_game()函數中的食物繪制是在蛇吃到食物之后,保證第一個食物順利繪制,這樣的話score就需要初始化為-1

        // 用于初始化游戲各項參數function init_game() {     snake = [41, 40];      direction = 1;      food = 42;     score = -1;      time_internal = simply_mode;     enabled = true;     score_cal.innerText = "目前得分: 0分"; // 更新得分     mode_item[0].checked = true; // 重置難度等級為簡單}// 綁定重新開始按鈕點擊事件restart_btn.onclick = function () {     cancelAnimationFrame(run_id);     // 將原有的食物和蛇的方塊都繪制成白色     for(var i = 0; i < snake.length; i++){         draw(snake[i], "White");     }     draw(food, "White");     // 初始化游戲各項參數     init_game();     run_id = requestAnimationFrame(game_control);			};

        接下來,我們綁定game over中的兩個按鍵”再來一把“和”取消“

        ”再來一把“只需要完成“重新開始”里面的事件即可,”取消“只需要完成”重新開始“點擊操作中除了開始游戲的部分,即除了run_id = requestAnimationFrame(game_control);

        這兩個按鈕都需要設置”彈窗“的display屬性為none

        具體實現如下:

        // 綁定游戲結束時的取消按鈕點擊事件cancel_btn.onclick = function () {     for(var i = 0; i < snake.length; i++){         draw(snake[i], "White");     }     draw(food, "White");     init_game();     game_over_p.style.display = "none";}// 綁定游戲結束時的再來一把按鈕點擊事件once_again_btn.onclick = function () {     for(var i = 0; i < snake.length; i++){         draw(snake[i], "White");     }     draw(food, "White");     init_game();     game_over_p.style.display = "none";     run_id = requestAnimationFrame(game_control);}

        最后,我們來講解下如何設置快捷鍵,快捷鍵只需要用JavaScript模擬點擊對應的按鈕即可,實現如下:

        // 同時綁定R 重啟,P 暫停,C 繼續document.onkeydown = function (event) {     const keycode = event.keyCode;     if(keycode == 82){         // R 重啟         restart_btn.onclick();     } else if(keycode == 80){         // P 暫停         pause_btn.onclick();     } else if(keycode == 67){         // C 繼續         start_btn.onclick();     } };

        問題、調試與解決

        注: 此部分為本人在實現過程中出現的bug、調試過程以及解決方法,感興趣的可以看看,不感興趣的也可以跳過此部分,直接看文末的完整代碼

        問題1:點擊暫停和開始,游戲正常開始,按P也可以實現暫停,按C則畫面出現蛇所在的方格亂跳,無法正常開始,但是按C的操作中只模擬了”開始 / 繼續“按鈕的點擊?

        效果如下:

        實例分享之JavaScript實現貪吃蛇小游戲

        調試過程:因為蛇頭的位置是由direction控制的,故想到設置斷點,同時監測這個變量的值的變化,發現這個值在按完P和C時被更新成很大的數,進而去找direction在哪里被更新,發現點擊P或C后還需要執行下面這一行代碼,而實際上是不需要的

        direction = snake[1] - snake[0] == n ? direction : n; // 若方向與原方向相反,則方向不變

        解決方法:只需要執行完對應的模擬鼠標點擊相應按鈕事件之后就直接return就可以了

        原代碼與修改后的代碼如下:

        document.onkeydown = function (event) {     const keycode = event.keyCode;     if(keycode == 82){         // R 重啟         restart_btn.onclick();         return; // 后來加上的     } else if(keycode == 80){         // P 暫停         pause_btn.onclick();         return; // 后來加上的     } else if(keycode == 67){         // C 繼續         start_btn.onclick();         return; // 后來加上的     } else if (keycode <= 40) {         // 上 38 下 40 左 37 右 39         n = [-1, -20, 1, 20][keycode - 37] || direction; // 若keycode為其他值,則方向不變     } else if (keycode <= 76 && keycode >= 73) {         // i 73 j 74 k 75 l 76         n = [-20, -1, 20, 1][keycode - 73] || direction;     } else {         switch (keycode) {             case 87: //w                 n = -20;                 break;             case 83: //s                 n = 20;                 break;             case 65: //a                 n = -1;                 break;             case 68: //d                 n = 1;                 break;             default:                 n = direction;         }     }     direction = snake[1] - snake[0] == n ? direction : n; // 若方向與原方向相反,則方向不變};

        問題2:調整難度等級后,蛇的速度并沒有發生改變,但是通過console.log()發現確實調用了同步難度模式的函數?

        調試過程:在同步難度等級的函數中設置console.log()方法,輸出time_internal變量,同時設斷點調試,發現time_internal變量不發生變化,mode_value變量始終為undefined,最后發現應該是值傳遞時的錯誤mode_value = mode_item.value;

        解決方法:修改值傳遞的方法,加上索引,改為mode_value = mode_item[i].value;

        原代碼和修改后的代碼如下:

        // 同步難度等級function syncMode() {     var mode_value = "";     for (var i = 0; i < mode_item.length; i++) {         if (mode_item[i].checked) {             mode_value = mode_item[i].value;//原來是mode_item.value         }     }     switch (mode_value) {         case "simply":             time_internal = simply_mode;             break;         case "middle":             time_internal = middle_mode;             break;         case "hard":             time_internal = hard_mode;             break;     }}

        完整代碼

        <!DOCTYPE html><html lang="en">   <head>     <meta charset="UTF-8" />     <meta http-equiv="X-UA-Compatible" content="IE=edge" />     <meta name="viewport" content="width=device-width, initial-scale=1.0" />     <title>貪吃蛇小游戲</title>     <style> 		button { 		  width: 100px; 		  height: 40px; 		  font-weight: bold; 		} 		#game_title { 		  margin-left: 95px; 		} 		#canvas { 		  border: 1px solid #000000; /* 設置邊框線 */ 		} 		#score { 		  font-weight: bold; 		} 		#mode_form { 		  font-weight: bold; 		} 		#game_over { 		  display: none; /* 設置game over 窗口不可見 */ 		  position: fixed; 		  top: 190px; 		  left: 65px; 		  width: 280px; 		  height: 160px; 		  background-color: aliceblue; 		  border-radius: 5px; 		  border: 1px solid #000; /* 設置邊框線 */ 		} 		#once_again { 		  position: relative; 		  left: 20px; 		} 		#cancel { 		  position: relative; 		  left: 50px; 		}     </style>   </head>   <body>     <h1 id="game_title">貪吃蛇小游戲</h1>     <canvas id="canvas" width="400" height="400"></canvas> 	<p id="game_over"> 		<h3 id="game_over_text" align="center">游戲結束!</h3> 		<h3 id="game_over_score" align="center">您的最終得分為: 0分</h3> 		<button id="once_again">再來一把</button> 		<button id="cancel">取消</button> 	</p> 	<br> 	<p id="game_info"> 		<p><b>游戲說明:</b></p> 		<p> 			<b>1</b>. 用鍵盤上下左右鍵(或者IJKL鍵,或者WSAD鍵)控制蛇的方向,尋找吃的東西		<br><b>2</b>. 每吃一口就能得到一定的積分,同時蛇的身子會越吃越長		<br><b>3</b>. 不能碰墻,不能咬到自己的身體,更不能咬自己的尾巴		<br><b>4</b>. 在下方單選框中選擇難度等級,點擊"<b>開始 / 繼續</b>"即開始游戲,點擊"<b>暫停</b>"則暫停游戲,			<br>&nbsp;&nbsp;&nbsp;&nbsp;再點擊"<b>開始 / 繼續</b>"繼續游戲,點擊"重新開始"則重新開始游戲		<br><b>5</b>. <b>快捷鍵</b>: "<b>C</b>"表示開始或繼續,"<b>P</b>"表示暫停,"<b>R</b>"表示重新開始		</p> 	</p>          <p id="score">目前得分: 0分</p>     <form action="" id="mode_form">       難度等級:        <input type="radio" name="mode" id="simply" value="simply" checked />       <label for="simply">簡單</label>       <input type="radio" name="mode" id="middle" value="middle" />       <label for="middle">中級</label>       <input type="radio" name="mode" id="hard" value="hard" />       <label for="hard">困難</label>     </form>     <br />     <button id="startButton">開始 / 繼續</button>     <button id="pauseButton">暫停</button>     <button id="restartButton">重新開始</button>      <script> 		const canvas = document.getElementById("canvas"); 		const ctx = canvas.getContext("2d");  		const start_btn = document.getElementById("startButton"); 		const pause_btn = document.getElementById("pauseButton"); 		const restart_btn = document.getElementById("restartButton"); 		const once_again_btn = document.getElementById("once_again"); 		const cancel_btn = document.getElementById("cancel"); 		const game_over_p = document.getElementById("game_over"); 		const game_over_score = document.getElementById("game_over_score");  		const score_cal = document.getElementById("score"); 		const mode_item = document.getElementsByName("mode");  		// 用刷新間隔代表蛇的速度,刷新間隔越長,則蛇的速度越慢 		const simply_mode = 200; 		const middle_mode = 100; 		const hard_mode = 50;  		//注意要改為var const是不會修改的 		var snake = [41, 40]; // 蛇身體隊列 		var direction = 1; // 方向:1為向右,-1為向左,20為向下,-20為向上 		var food = 42; // 食物位置,取值為0~399 		var n; // 蛇的下一步的方向(由鍵盤和蛇的原方向決定) 		var score = -1; // 得分 		var time_internal = simply_mode; // 刷新時間間隔,用于調整蛇的速度,默認為簡單模式  		let enabled = true; // 用于控制是否刷新,實現通過一定頻率刷新 		let run_id; // 請求ID,用于暫停功能  		// 產生min~max的隨機整數,用于隨機產生食物的位置 		function random(min, max) { 			const num = Math.floor(Math.random() * (max - min)) + min; 			return num; 		}  		// 用于繪制蛇或者是食物代表的方塊,seat為方塊位置,取值為0~399,color為顏色 		function draw(seat, color) { 			ctx.fillStyle = color; // 填充顏色 			// fillRect的四個參數分別表示要繪制方塊的x坐標,y坐標,長,寬,這里為了美觀留了1px用于邊框 			ctx.fillRect( 				(seat % 20) * 20 + 1, 				Math.floor(seat / 20) * 20 + 1, 				18, 				18 			); 		}  		// 同步難度等級 		function syncMode() { 			var mode_value = ""; 			for (var i = 0; i < mode_item.length; i++) { 				if (mode_item[i].checked) { 					mode_value = mode_item[i].value;//原來是mode_item.value 				} 			} 			switch (mode_value) { 				case "simply": 					time_internal = simply_mode; 					break; 				case "middle": 					time_internal = middle_mode; 					break; 				case "hard": 					time_internal = hard_mode; 					break; 			} 		}  		// 用于綁定鍵盤上下左右事件,我設置了wsad,或者ijkl,或者上下左右方向鍵,代表上下左右方向 		// 同時綁定R 重啟,P 暫停,C 繼續,注意:若是這幾個鍵則不需要更新direction的值,操作結束后直接返回即可 		document.onkeydown = function (event) { 			const keycode = event.keyCode; 			if(keycode == 82){ 				// R 重啟 				restart_btn.onclick(); 				return; 			} else if(keycode == 80){ 				// P 暫停 				pause_btn.onclick(); 				return; 			} else if(keycode == 67){ 				// C 繼續 				start_btn.onclick(); 				return; 			} else if (keycode <= 40) { 				// 上 38 下 40 左 37 右 39 				n = [-1, -20, 1, 20][keycode - 37] || direction; // 若keycode為其他值,則方向不變 			} else if (keycode <= 76 && keycode >= 73) { 				// i 73 j 74 k 75 l 76 				n = [-20, -1, 20, 1][keycode - 73] || direction; 			} else { 				switch (keycode) { 					case 87: //w 						n = -20; 						break; 					case 83: //s 						n = 20; 						break; 					case 65: //a 						n = -1; 						break; 					case 68: //d 						n = 1; 						break; 					default: 						n = direction; 				} 			} 			direction = snake[1] - snake[0] == n ? direction : n; // 若方向與原方向相反,則方向不變 		};  		// 用于初始化游戲各項參數 		function init_game() { 			snake = [41, 40];  			direction = 1;  			food = 42; 			score = -1;  			time_internal = simply_mode; 			enabled = true; 			score_cal.innerText = "目前得分: 0分"; // 更新得分 			mode_item[0].checked = true; // 重置難度等級為簡單 		}  		function game_over(){ 			cancelAnimationFrame(run_id); 			game_over_score.innerText = "您的最終得分為: " + score + "分"; 			game_over_p.style.display = "block"; 		}  		// 啟動或繼續游戲 		function run_game() { 			syncMode(); // 同步難度等級 			n = snake[0] + direction; // 找到新蛇頭坐標 			snake.unshift(n); // 添加新蛇頭 			// 判斷蛇頭是否撞到自己或者是否超出邊界 			if ( 				snake.indexOf(n, 1) > 0 || 				n < 0 || 				n > 399 || 				(direction == 1 && n % 20 == 0) || 				(direction == -1 && n % 20 == 19) 			) { 				game_over(); 			} 			draw(n, "#1a8dcc"); // 繪制新蛇頭為淺藍色 			draw(snake[1], "#cececc"); // 將原來的蛇頭(淺藍色)變成蛇身(淺灰色) 			if (n == food) { 				score = score + 1; 				score_cal.innerText = "目前得分: " + score; // 更新得分 				while (snake.indexOf((food = random(0, 400))) >= 0); // 重新刷新食物,注意食物應不在蛇內部 				draw(food, "Yellow"); // 繪制食物 			} else { 				draw(snake.pop(), "White"); // 將原來的蛇尾繪制成白色 			} 			// setTimeout(arguments.callee, time_internal); //之前的方案,無法實現暫停和游戲的繼續 		}  		// 控制游戲的刷新頻率,每隔time_internal時間間隔刷新一次 		function game_control(){ 			if(enabled){ 				enabled = false; 				requestAnimationFrame(run_game); 				setTimeout(() => enabled = true, time_internal); 			} 			run_id = requestAnimationFrame(game_control); 		}  		// 綁定開始按鈕點擊事件 		start_btn.onclick = function () { 			run_id = requestAnimationFrame(game_control); 		};  		// 綁定暫停按鈕點擊事件 		pause_btn.onclick = function () { 			cancelAnimationFrame(run_id); 		};  		// 綁定重新開始按鈕點擊事件 		restart_btn.onclick = function () { 			cancelAnimationFrame(run_id); 			// 將原有的食物和蛇的方塊都繪制成白色 			for(var i = 0; i < snake.length; i++){ 				draw(snake[i], "White"); 			} 			draw(food, "White"); 			// 初始化游戲各項參數 			init_game(); 			run_id = requestAnimationFrame(game_control);			 		};  		// 綁定游戲結束時的取消按鈕點擊事件 		cancel_btn.onclick = function () { 			for(var i = 0; i < snake.length; i++){ 				draw(snake[i], "White"); 			} 			draw(food, "White"); 			init_game(); 			game_over_p.style.display = "none"; 		}  		// 綁定游戲結束時的再來一把按鈕點擊事件 		once_again_btn.onclick = function () { 			for(var i = 0; i < snake.length; i++){ 				draw(snake[i], "White"); 			} 			draw(food, "White"); 			init_game(); 			game_over_p.style.display = "none"; 			run_id = requestAnimationFrame(game_control); 		}     </script>   </body></html>

        贊(0)
        分享到: 更多 (0)
        網站地圖   滬ICP備18035694號-2    滬公網安備31011702889846號
        主站蜘蛛池模板: 正在播放酒店精品少妇约| 国产精品亚洲A∨天堂不卡| 亚洲精品成人久久久| 51久久夜色精品国产| 精品久久久噜噜噜久久久| 亚洲精品麻豆av| 久久精品国产99久久久香蕉| 亚洲一二成人精品区| 精品一区二区三区中文字幕| 精品一区二区三区在线成人 | 熟女精品视频一区二区三区 | 欧美精品一区二区久久| 国产精品欧美亚洲韩国日本久久| 丝袜美腿国产精品视频一区 | 亚洲av午夜成人片精品电影| 国产精品 码ls字幕影视| 99久久免费国产精精品| 国产精品高清一区二区三区| 少妇伦子伦精品无码STYLES| 合区精品中文字幕| 欧美成人精品网站播放 | 久久无码人妻精品一区二区三区 | 国产一精品一av一免费爽爽| 99精品免费视品| 国产成人精品亚洲日本在线| 99久久精品毛片免费播放| www国产精品| 97热久久免费频精品99| 91久久精品91久久性色| 2020亚洲男人天堂精品| 2020国产精品永久在线| jiucao在线观看精品| 久久精品成人国产午夜| 久久99精品国产麻豆宅宅| 欧美精品亚洲精品日韩1818| 日韩一级精品视频在线观看| 91探花国产综合在线精品| 国产成人毛片亚洲精品| 黄床大片免费30分钟国产精品| 久久www免费人成精品香蕉| 日本加勒比久久精品|