1. 程式人生 > >用JS實現的貪吃蛇遊戲

用JS實現的貪吃蛇遊戲

JavaScript 小遊戲

需要用到html、css、javascript 和 DOM 這些知識點就可以了。主要是js,其他只是一些基本的知識。js貌似也不是很難。但是問題就在這裏,即使知識點都會了,但是還是無法綜合運用把東西做出來

遊戲界面

先把整個遊戲界面做出來:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>貪吃蛇</title>
    <style>
        *{padding: 0; margin: 0;}
        .title{text-align: center; margin: 10px 0;}
        #main{width: 800px; height: 600px; border:1px solid red; margin: 0 auto;}
        #main .left{width: 600px; height: 600px; float: left;
            position: relative;}
        /*隨機的食物通過position了定位的,所以父標簽要加上position: relative*/
        #main .right{width: 200px; height: 100%; float: left; border-left: 1px solid red;
            box-sizing: border-box; text-align: center}
        /*整體寬度包括left和right的以及內部元素的邊框,這樣寬度不夠,會跑到下一行。這裏用了box-sizing解決問題*/
        #main .right h2{margin: 10px auto; text-align: center;}
        #main .right h2 #score{color: red;}
        #main .right button{width: 100px; height: 30px; font-size: 20px; margin-top: 30px;
            border: 0; border-radius: 5px;
            background-color: pink; color: green;}
        .food{background-color: black;}
        .snake{background-color: darkgreen}
    </style>
</head>
<body>
<h2 class="title">貪吃蛇</h2>
<div id="main">
    <div class="left"></div>
    <div class="right">
        <h2 class="status">請點擊開始</h2>
        <h2>分數:<span id="score">0</span></h2>
        <button id="btn">開始</button>
    </div>
</div>
<script>
    // 先空著
</script>
</body>
</html>

一個標題,然後下面是遊戲界面。界面分2部分,左邊是600*600的正方形,右邊寬度200,可以顯示一些遊戲信息。
這裏有一個問題,就是界面寬800,左邊寬600,右邊寬200。不過中間還有個邊框至少還要占1的寬度。所以右邊的部分會被擠到下一行。把800的總寬度加一點可以解決,也可以像這裏這樣,使用 box-sizing 屬性。
box-sizing:border-box; :為元素設定的寬度和高度決定了元素的邊框盒。就是說,為元素指定的任何內邊距和邊框都將在已設定的寬度和高度內進行繪制。通過從已設定的寬度和高度分別減去邊框和內邊距才能得到內容的寬度和高度。

初始化遊戲

上面的界面還缺少蛇和食物。這裏就是一個一個的小方塊。食物是一個方塊,蛇初始是連續的3個方塊。小方塊就是帶背景色的div,背景色已經在上面的css裏寫上了。

另外蛇和食物的位置都要用 position: absolute; 來做定位。已經提前在他們的父元素就是left界面裏設置好了 position: relative;

初始化食物

直接調用一個初始化的方法 init() ,然後是定義這個初始化方法。下面只是定義了食物的一些屬性,最後調用了一個生成食物的方法 food() :

    // 初始化
    init();
    // 初始化方法
    function init() {
        // 獲取地圖的跨度和高度
        this.map_width = parseInt(getComputedStyle(map).width);
        this.map_height = parseInt(getComputedStyle(map).height);

        // 食物的高度和寬度
        this.food_width = 20;
        this.food_height = 20;
        // 食物的位置,先定義在左上角看看樣子,之後再搞隨機位置
        this.food_X = 0;
        this.food_Y = 0;
        food();  // 生成食物
    }

生成食物的方法。這裏主要是生成一個div,然後把這個div添加到map元素裏。位置的話使用相對定位,關鍵是計算出橫坐標和縱坐標:

    // 獲取變量
    var map = document.getElementsByClassName(‘left‘)[0];
    // 生成食物
    function food() {
        // 創建食物
        var foodBox = document.createElement(‘div‘);
        foodBox.style.width = this.food_width+"px";
        foodBox.style.height = this.food_height+"px";
        // 食物的位置
        this.food_X = Math.floor(Math.random()*this.map_width/this.food_width);  // 0~29之間
        this.food_Y = Math.floor(Math.random()*this.map_height/this.food_height);
        foodBox.style.position = ‘absolute‘;
        foodBox.style.top = this.food_Y*this.food_height+"px";  // 隨機數乘以寬度
        foodBox.style.left = this.food_X*this.food_width+"px";
        // 設置一個類名,然後css給這個類定義樣式
        foodBox.className = "food";
        // 將食物追加到map中
        map.appendChild(foodBox);
    }

初始化蛇

首先在初始化方法裏追加蛇的屬性。這裏蛇是由連續的小方塊組成。那麽蛇就是一個數組,數組裏的每個元素就是組成蛇的一個一個的小方塊。有3個參數:水平位置、垂直位置、是否是蛇頭:

    // 初始化方法
    function init() {
        // 省略之前的代碼
        // 食物的位置,先定義在左上角看看樣子,之後再搞隨機位置
        // this.food_X = 0;
        // this.food_Y = 0;
        food();  // 生成食物

        // 初始化蛇身
        this.snake_width = 20;
        this.snake_height = 20;
        // 下面是一個3段的蛇,第一個參數是水平位置,第二個參數是垂直位置,但三個參數是是否是頭部
        this.snake_body = [[2, 0, true], [1, 0, false], [0, 0, false]];
        snake(); // 生成蛇身
    }

上面調用了snake()方法來生成蛇。初始化的時候,蛇始終是生成在左上角。蛇的每一段其實都是獨立的,這裏用一個for循環,把snake_body的每一段都添加到了map中。這裏是根據snake_boy來添加蛇,也就是說之後,任何時候只要修改了snake_body這個數組,然後調用snake()方法,就是生成一個蛇了:

    // 生成蛇身
    function snake() {
        // 用for循環遍歷數組,將每一段作為一個div然後添加
        for(var i=0; i<this.snake_body.length; i++){
            // 創建蛇身
            var snakeBox = document.createElement(‘div‘);
            // 高度和寬度
            snakeBox.style.width = this.snake_width+"px";
            snakeBox.style.height = this.snake_height+"px";
            // 定位
            snakeBox.style.position = ‘absolute‘;
            // 位置
            snakeBox.style.top = this.snake_body[i][1]*this.snake_width+"px";
            snakeBox.style.left = this.snake_body[i][0]*this.snake_height+"px";
            // 設置一個類名,然後css給這個類定義樣式
            snakeBox.className = "snake";
            // 追加到map中
            map.appendChild(snakeBox);
        }
    }

小結

上面已經生成了完整的遊戲初始化的界面,包括隨機生成的一個食物,以及左上角的長度為3段的蛇。完整的js代碼如下:

<script>
    // 獲取變量
    var map = document.getElementsByClassName(‘left‘)[0];
    // 初始化
    init();
    // 初始化方法
    function init() {
        // 獲取地圖的跨度和高度
        this.map_width = parseInt(getComputedStyle(map).width);
        this.map_height = parseInt(getComputedStyle(map).height);

        // 食物的高度和寬度
        this.food_width = 20;
        this.food_height = 20;
        // 食物的位置,先定義在左上角看看樣子,之後再搞隨機位置
        // this.food_X = 0;
        // this.food_Y = 0;
        food();  // 生成食物

        // 初始化蛇身
        this.snake_width = 20;
        this.snake_height = 20;
        // 下面是一個3段的蛇,第一個參數是水平位置,第二個參數是垂直位置,但三個參數是是否是頭部
        this.snake_body = [[2, 0, true], [1, 0, false], [0, 0, false]];
        snake(); // 生成蛇身
    }

    // 生成蛇身
    function snake() {
        // 用for循環遍歷數組,將每一段作為一個div然後添加
        for(var i=0; i<this.snake_body.length; i++){
            // 創建蛇身
            var snakeBox = document.createElement(‘div‘);
            // 高度和寬度
            snakeBox.style.width = this.snake_width+"px";
            snakeBox.style.height = this.snake_height+"px";
            // 定位
            snakeBox.style.position = ‘absolute‘;
            // 位置
            snakeBox.style.top = this.snake_body[i][1]*this.snake_width+"px";
            snakeBox.style.left = this.snake_body[i][0]*this.snake_height+"px";
            // 設置一個類名,然後css給這個類定義樣式
            snakeBox.className = "snake";
            // 追加到map中
            map.appendChild(snakeBox);
        }
    }

    // 生成食物
    function food() {
        // 創建食物
        var foodBox = document.createElement(‘div‘);
        foodBox.style.width = this.food_width+"px";
        foodBox.style.height = this.food_height+"px";
        // 食物的位置
        this.food_X = Math.floor(Math.random()*this.map_width/this.food_width);  // 0~29之間
        this.food_Y = Math.floor(Math.random()*this.map_height/this.food_height);
        foodBox.style.position = ‘absolute‘;
        foodBox.style.top = this.food_X*this.food_width+"px";  // 隨機數乘以寬度
        foodBox.style.left = this.food_Y*this.food_height+"px";
        // 設置一個類名,然後css給這個類定義樣式
        foodBox.className = "food";
        // 將食物追加到map中
        map.appendChild(foodBox);
    }
</script>

移動蛇

首先來實現點擊開始按鈕,開始遊戲

開始按鈕

點擊開始按鈕,調用start_game()方法,觸發遊戲運行,並且變成暫停按鈕。點擊暫停按鈕,調用pause_game()方法,暫停遊戲,並且再變為開始按鈕。這裏還獲取了一個status變量,可以隨時改變它的innerHtml的內容,在頁面上顯示一些信息,調試的時候一些中間過程也可以實現在這裏:

    var status = document.getElementsByClassName(‘status‘)[0];
    // 點擊按鈕
    var btn = document.getElementById(‘btn‘);
    btn.onclick = function btn() {
        if (this.innerHTML==="開始"){
            start_game();
            this.innerHTML = "暫停";
        } else {
            pause_game();
            this.innerHTML = "開始";
        }
    };

開始遊戲

開始遊戲就是啟動一個定時器。這裏首先修改了status裏顯示的內容,然後創建了一個定時器:

    // 開始遊戲
    function start_game() {
        status.innerHTML = "遊戲運行中";
        this.snakeMove = setInterval(move, 100);
    }

上面的定時器調用了move,下面就來寫這個move方法。move方法就是讓蛇移動,這裏先讓蛇一直往左移動。具體的做法就是設置snake_body數組,然後調用snake()方法,生成一個新的蛇。把之前的蛇清除了,然後把新的蛇添加進去,看上去取出蛇在移動的效果了:

    // 蛇移動的方法
    function move() {
        // 從尾端開始,數組裏的後一個值用數組的前一個值替代
        for(var i=this.snake_body.length-1; i>0; i--){
            this.snake_body[i][0] = this.snake_body[i-1][0];
            this.snake_body[i][1] = this.snake_body[i-1][1];
        }
        // 數組最前的那個值,就是蛇頭
        this.snake_body[0][0] += 1;

        // 重新生成蛇,才能在頁面上有變化
        // 先移除原有的蛇身體
        clearBox(‘snake‘);  // 這個方法可以復用,只有要清除食物的時候也能用到
        // 然後繪制新的蛇身
        snake();
    }

    // 清除Box
    function clearBox(class_name) {
        var box = document.getElementsByClassName(class_name);
        while(box.length){
            map.removeChild(box[0]);
        }
    }

這裏移除蛇身體的方法,之後要移除食物的時候也能用。只要傳入不同的類名,就能把這個類的元素都清除了。

暫停遊戲

暫停遊戲就很簡單了,就是清除掉計時器就好了。再點開始又會重新生成新的計時器:

    // 暫停遊戲
    function pause_game() {
        // 暫停遊戲就是清除定時器
        status.innerHTML = "遊戲暫停...";
        clearInterval(this.snakeMove)
    }

鍵盤事件

在開始遊戲的時候,為鍵盤綁定事件:

    // 開始遊戲
    function start_game() {
        status.innerHTML = "遊戲運行中";
        this.snakeMove = setInterval(move, 100);
        // 綁定鍵盤按下的事件
        bindKeyDown();
    }

    // 鍵盤按下的事件
    function bindKeyDown() {
        window.onkeydown = function (ev) {
            status.innerHTML = ev.keyCode;
        }
    }

這裏先看看keyCode這個屬性的值。按下不同的按鈕,值是不同的,根據這個值來判斷按的是哪個按鈕,然後觸發響應的方法。
左37、上38、右39、下40。還有空格是32。

根據方向來移動蛇

下面就來為這些事件寫上不同的處理方法:

    // 鍵盤按下的事件
    function bindKeyDown() {
        window.onkeydown = function (ev) {
            // status.innerHTML = ev.keyCode;
            var code = ev.keyCode;  // 獲取按鍵
            switch (code){
                case 37:
                    this.direction = ‘left‘;
                    break;
                case 38:
                    this.direction = ‘up‘;
                    break;
                case 39:
                    this.direction = ‘right‘;
                    break;
                case 40:
                    this.direction = ‘down‘;
                    break;
                case 32:
                    // 我這裏還希望開始遊戲後,可以用空格控制暫停和開始
                    btn();  // 前面的按鈕事件沒寫
                    break;
            }
        }
    }

這裏只是修改了this.direction這個屬性的值。接下來要修改之前寫的move()方法,根據這個屬性的值,給出朝不同方法移動的效果。這裏有了方向這個概念,在初始化的時候也要把方向屬性進行初始化。把下面這句添加到初始化函數init()中去:

this.direction = ‘right‘;

最後來修改move()方法,之前是默認向右移動的,現在要根據this.direction的值來確定向哪裏移動:

    // 蛇移動的方法
    function move() {
        // 從尾端開始,數組裏的後一個值用數組的前一個值替代
        for(var i=this.snake_body.length-1; i>0; i--){
            this.snake_body[i][0] = this.snake_body[i-1][0];
            this.snake_body[i][1] = this.snake_body[i-1][1];
        }

        // 根據方法來操作
        switch (this.direction){
            case ‘left‘:
                this.snake_body[0][0] -= 1;
                break;
            case ‘right‘:
                this.snake_body[0][0] += 1;
                break;
            case ‘up‘:
                this.snake_body[0][1] -= 1;
                break;
            case ‘down‘:
                this.snake_body[0][1] += 1;
                break;
        }
        // 數組最前的那個值,就是蛇頭
        // this.snake_body[0][0] += 1;

        // 重新生成蛇,才能在頁面上有變化
        // 先移除原有的蛇身體
        clearBox(‘snake‘);  // 這個方法可以復用,只有要清除食物的時候也能用到
        // 然後繪制新的蛇身
        snake();
    }

優化移動方向

現在的蛇是自由移動的,按照遊戲規則,蛇不不能直接掉頭的。所以要修改一個按鍵事件的case出的內容,只有在特定的條件下才響應鍵盤方向的事件:

    // 鍵盤按下的事件
    function bindKeyDown() {
        window.onkeydown = function (ev) {
            // status.innerHTML = ev.keyCode;
            var code = ev.keyCode;  // 獲取按鍵
            switch (code){
                case 37:
                    if (this.direction === ‘up‘ ||  this.direction === ‘down‘) {
                        this.direction = ‘left‘;
                    }
                    break;
                case 38:
                    if (this.direction === ‘left‘ ||  this.direction === ‘right‘){
                        this.direction = ‘up‘;
                    }
                    break;
                case 39:
                    if (this.direction === ‘up‘ ||  this.direction === ‘down‘){
                        this.direction = ‘right‘;
                    }
                    break;
                case 40:
                    if (this.direction === ‘left‘ ||  this.direction === ‘right‘){
                        this.direction = ‘down‘;
                    }
                    break;
                case 32:
                    // 我這裏還希望開始遊戲後,可以用空格控制暫停和開始
                    btn();  // 前面的按鈕事件沒寫
                    break;
            }
        }
    }

移動方法

之前的move()方法只是讓蛇動起來。每次移動後,當要判斷一下當前的位置。比如:吃到食物了、撞墻了或者撞到蛇身了。

吃食物

吃食物的邏輯放在蛇移動的move()方法裏。在計算好新的蛇身數組後,增加判斷蛇頭位置的邏輯:

    // 蛇移動的方法
    function move() {
        // 前面是生成新的蛇身數組的代碼

        // 判斷蛇頭吃食物
        if (this.snake_body[0][0]===this.food_X && this.snake_body[0][1]===this.food_Y){
            // status.innerHTML = "吃到食物了"
            clearBox("food");  // 移除食物
            food();  // 生成新的食物
            // 增加分數,先把下面這句加到最上面的獲取變量裏
            // var scoreBox = document.getElementById(‘score‘);
            // 在去初始化函數init()裏,加一條初始化分數變量
            // this.score = 0;
            this.score += 1;
            scoreBox.innerHTML = this.score;
            // 增加蛇身長度
            // var snake_end = this.snake_body[this.snake_body.length-1];  // 這個是錯誤的
            var snake_end = Array.from(this.snake_body[this.snake_body.length-1]);  // 這裏需要深copy
            this.snake_body.push(snake_end);
        }

        // 後面是移除原有蛇身,然後繪制新蛇身的方法
    }

這裏增加蛇蛇身長度的需要註意一下。
可以這麽做,把新生成的身體的一段放到一個臨時的位置,等移動一次以後,就會變成正常的位置:

var snake_end = [0, 0, false]
this.snake_body.push(snake_end);

上面那麽做肯定不好看,最好是追加到生當前身體最後一段的位置上,但是不能簡單的向下面這麽做:

 var snake_end = this.snake_body[this.snake_body.length-1];  // 這個是錯誤的
 this.snake_body.push(snake_end);

由於 this.snake_body[this.snake_body.length-1] 本身也是一個數組,如果直接賦值給snake_end的話,就是一個地址引用(淺copy)。這裏要麽獲取到具體的值,生成snake_end這個數組,比如下面這樣:

            var snake_last = this.snake_body[this.snake_body.length-1];
            var snake_end = [snake_last[0], snake_last[1], false];
            this.snake_body.push(snake_end);

或者就是像例子中那樣要做一個數組的深copy,把數組裏的值賦值給snake_end。而不是直接的賦值操作。

判斷撞墻

我先在開是的位置增加一個變量wall,用來開啟撞墻判斷的功能,這樣如果只有不想撞墻的話,還能方便的關掉:

    // 設置自定義的遊戲參數
    var wall = true;  // 開啟撞墻

繼續在move()方法裏,在判斷吃食物的後面添加判斷撞墻的邏輯。撞墻之後,就是

    // 蛇移動的方法
    function move() {

        // 判斷蛇頭吃食物

        // 判斷撞墻
        wall && (
            this.snake_body[0][0]<0 || this.snake_body[0][0]>=this.map_width/this.snake_width ||
            this.snake_body[0][1]<0 || this.snake_body[0][1]>=this.map_height/this.snake_height
        ) && game_over();

        // 後面是移除原有蛇身,然後繪制新蛇身的方法

    }

    // 遊戲結束的方法
    function game_over() {
        status.innerHTML = "Game Over";
        clearInterval(this.snakeMove);
        alert("遊戲結束\n分數:"+this.score);
        clearBox(‘snake‘);
        clearBox(‘food‘);
        init();
    }

上面的move()函數裏只復雜判斷是否撞墻,如果撞墻就調用game_over()方法。

撞到蛇身

先手動設置一個初始的長蛇身,避免測試撞擊蛇身之前要先玩一段時間的尷尬。還是把這些自定義參數放到開頭部分:

    // 設置自定義的遊戲參數
    var snake_length = 15;  // 正整數,增加額外的蛇身體。0就是最小的3段,1就是額外增加1段。
    var wall_snake = true;  // 開啟撞擊蛇身
    var wall = true;  // 開啟撞墻

修改一下初始化方法,在初始化的時候就生成一個很長的蛇身體,完整的初始化init()方法:

    // 初始化方法
    function init() {
        // 獲取地圖的跨度和高度
        this.map_width = parseInt(getComputedStyle(map).width);
        this.map_height = parseInt(getComputedStyle(map).height);

        // 初始化成績和界面
        this.score = 0;
        scoreBox.innerHTML = this.score;
        btn.innerHTML = "開始";

        // 食物的高度和寬度
        this.food_width = 20;
        this.food_height = 20;
        // 食物的位置,先定義在左上角看看樣子,之後再搞隨機位置
        // this.food_X = 0;
        // this.food_Y = 0;
        food();  // 生成食物

        // 初始化蛇身
        this.snake_width = 20;
        this.snake_height = 20;
        // 下面是一個3段的蛇,第一個參數是水平位置,第二個參數是垂直位置,但三個參數是是否是頭部
        // 最後發現第三個參數並沒有用
        this.snake_body = [[2, 0, true], [1, 0, false], [0, 0, false]];
        // 追加額外的蛇身
        if (typeof snake_length === ‘number‘ && snake_length%1 === 0) {
            // 綜合上面的2個條件,可以判斷snake_length是一個整數
            for (var i=0; i<snake_length; i++) {
                this.snake_body.push([0, 0, false])
            }
        }
        // 蛇移動的方向
        this.direction = ‘right‘;
        snake(); // 生成蛇身
    }

好了,現在不用玩了,直接可以鉆出一條大長龍。
然後是撞擊蛇身的判斷,還是move()函數裏,放到之前撞墻的後面

    // 蛇移動的方法
    function move() {

        // 判斷蛇頭吃食物

        // 判斷撞墻
        wall && (
            this.snake_body[0][0]<0 || this.snake_body[0][0]>=this.map_width/this.snake_width ||
            this.snake_body[0][1]<0 || this.snake_body[0][1]>=this.map_height/this.snake_height
        ) && game_over();

        // 判斷撞到蛇身
        if (wall_snake) {
            for (var i=1; i<this.snake_body.length; i++) {
                this.snake_body[i][0] === this.snake_body[0][0] &&
                this.snake_body[i][1] === this.snake_body[0][1] &&
                game_over();
            }
        }

        // 後面是移除原有蛇身,然後繪制新蛇身的方法

    }

這裏沒有什麽難點。可以去試著撞一下自己試試。

總結

上面也只是有個大概的樣子。基本的東西都搞出來了。還有優化空間,另外也還有些小BUG其實。下面跳上最終我自己的完整的代碼,有興趣的話自己修改:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>貪吃蛇</title>
    <style>
        *{padding: 0; margin: 0;}
        .title{text-align: center; margin: 10px 0;}
        #main{width: 800px; height: 600px; border:1px solid red; margin: 0 auto;}
        #main .left{width: 600px; height: 600px; float: left;
            position: relative;}
        /*隨機的食物通過position了定位的,所以父標簽要加上position: relative*/
        #main .right{width: 200px; height: 100%; float: left; border-left: 1px solid red;
            box-sizing: border-box; text-align: center}
        /*整體寬度包括left和right的以及內部元素的邊框,這樣寬度不夠,會跑到下一行。這裏用了box-sizing解決問題*/
        #main .right h2{margin: 10px auto; text-align: center;}
        #main .right h2 #score{color: red;}
        #main .right button{width: 100px; height: 30px; font-size: 20px; margin-top: 30px;
            border: 0; border-radius: 5px;
            background-color: pink; color: green;}
        .food{background-color: black;}
        .snake{background-color: darkgreen}
    </style>
</head>
<body>
<h2 class="title">貪吃蛇</h2>
<div id="main">
    <div class="left"></div>
    <div class="right">
        <h2 class="status">請點擊開始</h2>
        <h2>分數:<span id="score">0</span></h2>
        <button id="btn">開始</button>
    </div>
</div>
<script>
    // 設置自定義的遊戲參數
    var snake_length = 15;  // 正整數,增加額外的蛇身體。0就是最小的3段,1就是額外增加1段。
    var wall_snake = true;  // 開啟撞擊蛇身
    var wall = true;  // 開啟撞墻
    // 獲取變量
    var map = document.getElementsByClassName(‘left‘)[0];
    var status = document.getElementsByClassName(‘status‘)[0];
    var scoreBox = document.getElementById(‘score‘);
    var btn = document.getElementById(‘btn‘);
    // 初始化
    init();
    // 初始化方法
    function init() {
        // 獲取地圖的跨度和高度
        this.map_width = parseInt(getComputedStyle(map).width);
        this.map_height = parseInt(getComputedStyle(map).height);

        // 初始化成績和界面
        this.score = 0;
        scoreBox.innerHTML = this.score;
        btn.innerHTML = "開始";

        // 食物的高度和寬度
        this.food_width = 20;
        this.food_height = 20;
        // 食物的位置,先定義在左上角看看樣子,之後再搞隨機位置
        // this.food_X = 0;
        // this.food_Y = 0;
        food();  // 生成食物

        // 初始化蛇身
        this.snake_width = 20;
        this.snake_height = 20;
        // 下面是一個3段的蛇,第一個參數是水平位置,第二個參數是垂直位置,但三個參數是是否是頭部
        // 最後發現第三個參數並沒有用
        this.snake_body = [[2, 0, true], [1, 0, false], [0, 0, false]];
        // 追加額外的蛇身
        if (typeof snake_length === ‘number‘ && snake_length%1 === 0) {
            // 綜合上面的2個條件,可以判斷snake_length是一個整數
            for (var i=0; i<snake_length; i++) {
                this.snake_body.push([0, 0, false])
            }
        }
        // 蛇移動的方向
        this.direction = ‘right‘;
        snake(); // 生成蛇身
    }

    // 點擊按鈕
    btn.onclick = function () {
        if (this.innerHTML==="開始"){
            start_game();
            this.innerHTML = "暫停";
        } else {
            pause_game();
            this.innerHTML = "開始";
        }
    };

    // 開始遊戲
    function start_game() {
        status.innerHTML = "遊戲運行中";
        this.snakeMove = setInterval(move, 100);
        // 綁定鍵盤按下的事件
        bindKeyDown();
    }

    // 鍵盤按下的事件
    function bindKeyDown() {
        window.onkeydown = function (ev) {
            // status.innerHTML = ev.keyCode;
            var code = ev.keyCode;  // 獲取按鍵
            switch (code){
                case 37:
                    if (this.direction === ‘up‘ ||  this.direction === ‘down‘) {
                        this.direction = ‘left‘;
                    }
                    break;
                case 38:
                    if (this.direction === ‘left‘ ||  this.direction === ‘right‘){
                        this.direction = ‘up‘;
                    }
                    break;
                case 39:
                    if (this.direction === ‘up‘ ||  this.direction === ‘down‘){
                        this.direction = ‘right‘;
                    }
                    break;
                case 40:
                    if (this.direction === ‘left‘ ||  this.direction === ‘right‘){
                        this.direction = ‘down‘;
                    }
                    break;
                case 32:
                    // 我這裏還希望開始遊戲後,可以用空格控制暫停和開始
                    btn();  // 前面的按鈕事件沒寫
                    break;
            }
        }
    }

    // 蛇移動的方法
    function move() {
        // 從尾端開始,數組裏的後一個值用數組的前一個值替代
        for(var i=this.snake_body.length-1; i>0; i--){
            this.snake_body[i][0] = this.snake_body[i-1][0];
            this.snake_body[i][1] = this.snake_body[i-1][1];
        }

        // 根據方法來操作
        switch (this.direction){
            case ‘left‘:
                this.snake_body[0][0] -= 1;
                break;
            case ‘right‘:
                this.snake_body[0][0] += 1;
                break;
            case ‘up‘:
                this.snake_body[0][1] -= 1;
                break;
            case ‘down‘:
                this.snake_body[0][1] += 1;
                break;
        }
        // 數組最前的那個值,就是蛇頭
        // this.snake_body[0][0] += 1;

        // 判斷蛇頭吃食物
        if (this.snake_body[0][0]===this.food_X && this.snake_body[0][1]===this.food_Y){
            // status.innerHTML = "吃到食物了"
            clearBox("food");  // 移除食物
            food();  // 生成新的食物
            // 增加分數,先把下面這句加到最上面的獲取變量裏
            // var scoreBox = document.getElementById(‘score‘);
            // 在去初始化函數init()裏,加一條初始化分數變量
            // this.score = 0;
            this.score += 1;
            scoreBox.innerHTML = this.score;
            // 增加蛇身長度
            // var snake_end = this.snake_body[this.snake_body.length-1];  // 這個是錯誤的
            var snake_end = Array.from(this.snake_body[this.snake_body.length-1]);  // 這裏需要深copy
            this.snake_body.push(snake_end);
        }

        // 判斷撞墻
        wall && (
            this.snake_body[0][0]<0 || this.snake_body[0][0]>=this.map_width/this.snake_width ||
            this.snake_body[0][1]<0 || this.snake_body[0][1]>=this.map_height/this.snake_height
        ) && game_over();

        // 判斷撞到蛇身
        if (wall_snake) {
            for (var i=1; i<this.snake_body.length; i++) {
                this.snake_body[i][0] === this.snake_body[0][0] &&
                this.snake_body[i][1] === this.snake_body[0][1] &&
                game_over();
            }
        }

        // 重新生成蛇,才能在頁面上有變化
        // 先移除原有的蛇身體
        clearBox(‘snake‘);  // 這個方法可以復用,只有要清除食物的時候也能用到
        // 然後繪制新的蛇身
        snake();
    }

    // 遊戲結束的方法
    function game_over() {
        status.innerHTML = "Game Over";
        clearInterval(this.snakeMove);
        alert("遊戲結束\n分數:"+this.score);
        clearBox(‘snake‘);
        clearBox(‘food‘);
        init();
    }

    // 清除Box
    function clearBox(class_name) {
        var box = document.getElementsByClassName(class_name);
        while(box.length){
            map.removeChild(box[0]);
        }
    }

    // 暫停遊戲
    function pause_game() {
        // 暫停遊戲就是清除定時器
        status.innerHTML = "遊戲暫停...";
        clearInterval(this.snakeMove)
    }

    // 生成蛇身
    function snake() {
        // 用for循環遍歷數組,將每一段作為一個div然後添加
        for(var i=0; i<this.snake_body.length; i++){
            // 創建蛇身
            var snakeBox = document.createElement(‘div‘);
            // 高度和寬度
            snakeBox.style.width = this.snake_width+"px";
            snakeBox.style.height = this.snake_height+"px";
            // 定位
            snakeBox.style.position = ‘absolute‘;
            // 位置
            snakeBox.style.top = this.snake_body[i][1]*this.snake_width+"px";
            snakeBox.style.left = this.snake_body[i][0]*this.snake_height+"px";
            // 設置一個類名,然後css給這個類定義樣式
            snakeBox.className = "snake";
            // 追加到map中
            map.appendChild(snakeBox);
        }
    }

    // 生成食物
    function food() {
        // 創建食物
        var foodBox = document.createElement(‘div‘);
        foodBox.style.width = this.food_width+"px";
        foodBox.style.height = this.food_height+"px";
        // 食物的位置
        this.food_X = Math.floor(Math.random()*this.map_width/this.food_width);  // 0~29之間
        this.food_Y = Math.floor(Math.random()*this.map_height/this.food_height);
        foodBox.style.position = ‘absolute‘;
        foodBox.style.top = this.food_Y*this.food_height+"px";  // 隨機數乘以寬度
        foodBox.style.left = this.food_X*this.food_width+"px";
        // 設置一個類名,然後css給這個類定義樣式
        foodBox.className = "food";
        // 將食物追加到map中
        map.appendChild(foodBox);
    }
</script>
</body>
</html>

用JS實現的貪吃蛇遊戲