[原生JS][程式導向]貪吃蛇

weixin_34214500發表於2017-07-16

您好,本文主要描述如何用原生JS程式導向的思想來編寫一個貪吃蛇小遊戲。


1. 準備工作

首先,我們需要新建一個資料夾,資料夾裡建立3個檔案,分別是 snake.html來存放html程式碼,snake.css存放css樣式表程式碼,snake.js存放js程式碼。

1.1 編寫snake.html

首先我們在body標籤裡面建立一個地圖div,在地圖模型中嵌入蛇模型(包括蛇頭與蛇身),並賦予蛇頭id名方便我們後面JS操控蛇頭div,而一條蛇有很多個蛇身模型div,所以我們給所有蛇身賦予相同的class名,最後還有食物模型div也是內嵌在地圖裡的。

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>貪吃蛇</title>
        <!-- 引入snake.css檔案 -->
        <link rel="stylesheet" type="text/css" href="snake.css"/>
    </head>
    <body>
        <!-- 地圖 -->
        <div id="wrap">
            <!-- 蛇 -->
            <div id="head"></div>
            <div class="body" style="left:25px;top:0;">1</div>
            <div class="body" style="left:50px;top:0;">2</div>
            <!-- 食物 -->
            <div class="food"></div>
        </div>
        <!-- 引入snake.js檔案 -->
        <script src="snake.js" type="text/javascript"></script>
    </body>
</html>

1.2 編寫snake.css

現在我們在瀏覽器中開啟snake.html檔案是一片空白的,因為還沒有給各元素賦予樣式。那麼事不宜遲,趕緊開始css樣式表的編寫吧!首先,給各元素定位設定,如地圖設定相對定位relative,蛇與食物設定絕對定位absolute。由於邊幅有限,這裡我只設定比較簡單的樣式,各位有興趣的話可以給各個元素賦予背景圖片,讓遊戲更真實(笑。

    #wrap{
        width: 500px;
        height: 500px;
        border: 1px solid #000;
        position: relative;
        margin: auto
    }
    #head{
        width: 25px;
        height: 25px;
        background: red;
        position: absolute;
        top: 0;
        left: 50px;
        z-index: 1;
    }
    .body{
        width: 25px;
        height: 25px;
        position: absolute;
        background: forestgreen;
    }
    .food{
        width: 25px;
        height: 25px;
        background: yellow;
        position: absolute;
    }

2. 編寫snake.js

經過一番準備後,我們現在開始進行js的編寫。

2.1 獲取所有元素

我們先要獲取元素,才能操作元素。

//以id獲取地圖元素
var wrap = document.getElementById('wrap');
//以id獲取蛇頭元素
var head = document.getElementById('head');
//以class名獲取所有蛇身元素(是一個陣列)
var body = document.getElementsByClassName('body');
//以class名獲取食物元素(只有一個)
var food = document.getElementsByClassName('food')[0];

2.2 設定所需變數

var box = 25;//蛇的移動步長
var boombol = true;//用於判斷是否吃到食物
var timer = null;//讓蛇不斷移動的定時器
var runbol = true;//判斷短時間內的多次按鍵
// 原始方向為向右
var l = 1;
var t = 0;

2.3 繫結鍵盤事件

利用鍵盤方向鍵改變蛇的移動方向,通過檢測keyCode的值來確定按鍵,如 37代表 、38代表 、39代表、40代表
需要達到的要求如下:
1️⃣蛇在向右走的時候,按左方向鍵或按右方向鍵時依舊往右走,其他三個按鍵同理
2️⃣多次快速按鍵無效

document.onkeyup = function(e){
        var ee = e||window.event;
        // 當移動函式沒有執行完畢時按下鍵盤直接返回,使按鍵無效
        if(!runbol){
            return;
        }
        // 把 l改變為負值 移動時相乘就會為負則向左走
        // 同時 t要置為0 不然會斜著走
        // 其他按鍵也是同理
        switch(e.keyCode){
            case 37:{
                // 把移動布林值置否
                runbol = false;
                // 如果蛇正在往右走時按下左鍵 break接著無效
                if(l == 1){break;}
                l = -1;t = 0;break;
            }
            case 38:{
                runbol = false;
                if(t == 1){break;}
                l = 0;t = -1;break;
            }
            case 39:{
                runbol = false;
                if(l == -1){break;}
                l = 1;t = 0;break;
            }
            case 40:{
                runbol = false;
                if(t == -1){break;}
                l = 0;t = 1;break;
            }
        }
    }

2.4 設定蛇的移動函式

蛇移動有兩種情況:
1️⃣蛇吃到食物
2️⃣蛇沒有吃到食物
遊戲結束判定有兩種情況:
1️⃣蛇頭撞到牆壁
2️⃣蛇頭撞到自己的身體
3️⃣整條蛇的長度等於地圖大小的通關慶祝?

function move(){
        
        // 獲取頭部的左和上偏移量
        var headX = head.offsetLeft;
        var headY = head.offsetTop;
        // 直接寫蛇頭下一個移動位置 用於判斷撞牆
        var nextx = headX + box * l;
        var nexty = headY + box * t;
        
        // 判斷撞牆
        if(nextx<0||nextx>475 ||nexty<0||nexty>475){
            gameOver();
        }
        
        // 判斷撞到蛇身 利用迴圈遍歷每一個body 
        // 並判斷當頭的下一次移動位置為其中一個重合時即跟撞牆一樣結束遊戲
        for(var i = 0;i<body.length;++i){
            if(nextx==body[i].offsetLeft && nexty==body[i].offsetTop){
                gameOver();
            }
        }
        
        // 經過判斷後蛇才移動
        head.style.left = nextx + 'px';
        head.style.top = nexty + 'px';
        
        // 如果吃到食物 創造一個蛇身 位置為頭部的偏移量 
        // 陣列中的位置為蛇頭之後
        // 最後把布林值恢復
        if(!boombol){
            var div = document.createElement('div');
            div.className = 'body';
            div.style.left = headX + 'px';
            div.style.top = headY + 'px';
            wrap.insertBefore(div,body[0]);
            boombol = true;
        }
        // 沒有吃到食物
        // 移動最後一個蛇身div為頭部偏移量 並在陣列中把它插入到頭部後面
        else{
            body[body.length-1].style.left = headX + 'px';
            body[body.length-1].style.top = headY + 'px';
            wrap.insertBefore(body[body.length-1],body[0]);
        }
        // 判斷是否吃到食物
        if(head.offsetLeft==food.offsetLeft&&head.offsetTop==food.offsetTop){
            // 吃到就改變食物的位置
            creatFood();
            // 吃到則把布林值置為否,在下次執行移動函式進入if語句
            boombol = false;
        }
        // 移動完應該把布林值置為真 
        // 表示移動函式執行完畢 可以接受下一個按鍵命令(第一次寫錯)
        runbol = true;
    }
    
    function gameOver(){
        // 撞到蛇身或牆壁  清除定時器 並彈窗 return
        clearInterval(timer);
        alert('Game Over');
        return;
    }

2.5 設定隨機食物位置函式

需要達到的要求如下:
1️⃣隨機位置不能在蛇的身上
故遍歷蛇陣列 如食物出現在蛇身上 則再呼叫一次創造食物函式
則產生函式遞迴 利用布林值決定是否移動食物 出口為break
裡面移動了一次 外面的就不移動

2️⃣隨機位置不能越出地圖
因為食物本來就有大小,如果最大值設定500則食物有可能會出現在525,超出了外框,所以位置隨機數最大為地圖大小減去食物大小

function creatFood(){

        var bol = true;

        var x = rnd(475,0);
        var y = rnd(475,0);
        // parseInt(x/25)拿到在隨機數相對於25的位數,保證食物在格子上
        var foodx = parseInt(x/25)*25;
        var foody = parseInt(y/25)*25;
        
        for(var i =0;i<snake.length-1;++i){
            //迴圈判斷食物是否出現在蛇身
            if(foodx==snake[i].offsetLeft&& foody==snake[i].offsetTop){
                creatFood();
                bol = false;
                break;
            }
        }
        if(bol){
            food.style.left = foodx + 'px';
            food.style.top = foody + 'px';
        }
    }
    // 隨機函式
    function rnd(max,min){
        return Math.round(Math.random()*(max-min)+min);
    }

2.6 最後

1️⃣呼叫 creatFood()函式
2️⃣啟動定時器,定時器內呼叫函式為蛇移動函式move(),定時器時間間隔設定為200毫秒**

creatFood();
timer = setInterval(move,200);

3 總結

程式導向式的程式設計如果要加功能或者修改,步驟之間的循序可能都要進行大規模調整,如本例中要加入其他食物,使蛇吃到食物後產生不同的效果(加速,蛇身加兩節等)就比較難做到了。

所以我們此時需要學習物件導向程式設計,因為其結構更加清晰,更容易擴充套件與良好的封裝性。

當我們都兩種程式設計思想都學會了,在面對需求時就可以根據他們的特點選擇更好的解決方案啦。(其實還有其他n個程式設計思想)

最後,感謝您的閱讀!

相關文章