20行代碼的貪喫蛇

原文地址

  在csdn上看到一位大神用20行代碼就寫出了一個貪喫蛇的小遊戲,鏈接請點這裏,感覺被驚豔到了,就試着讀了一下這段代碼,閱讀過程中不斷爲作者寫法的巧妙而叫絕,其中我發現自己對運算符優先級和一些js的技巧不是很清楚,所以看完之後決定把思路分享出來,方便和我一樣的小白學習。

  我對代碼稍稍做了些修改,並添加了一些註釋,方便理解。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>貪喫蛇重構</title>
    <style>
        body {
            display: flex;
            height: 100vh;
            margin: 0;
            padding: 0;
            justify-content: center;
            align-items: center;
        }
    </style>
</head>
<body>
    <canvas id="can" width="400" height="400" style="background-color: black">對不起,您的瀏覽器不支持canvas</canvas>
    <script>
        var snake = [41, 40],       //snake隊列表示蛇身,初始節點存在但不顯示
            direction = 1,          //1表示向右,-1表示向左,20表示向下,-20表示向上
            food = 43,              //食物的位置
            n,                      //與下次移動的位置有關
            box = document.getElementById('can').getContext('2d');
                                    //從0到399表示box裏[0~19]*[0~19]的所有節點,每20px一個節點

        function draw(seat, color) {
            box.fillStyle = color;
            box.fillRect(seat % 20 *20 + 1, ~~(seat / 20) * 20 + 1, 18, 18);
                                    //用color填充一個矩形,以前兩個參數爲x,y座標,後兩個參數爲寬和高。
        }

        document.onkeydown = function(evt) {    
                                    //當鍵盤上下左右鍵摁下的時候改變direction
            direction = snake[1] - snake[0] == (n = [-1, -20, 1, 20][(evt || event).keyCode - 37] || direction) ? direction : n;
        };

        !function() {
            snake.unshift(n = snake[0] + direction);    
                                    //此時的n爲下次蛇頭出現的位置,n進入隊列
            if(snake.indexOf(n, 1) > 0 || n < 0 || n > 399 || direction == 1 && n % 20 == 0 || direction == -1 && n % 20 == 19) {
                                    //if語句判斷貪喫蛇是否撞到自己或者牆壁,碰到時返回,結束程序
                return alert("GAME OVER!");
            }
            draw(n, "lime");        //畫出蛇頭下次出現的位置
            if(n == food) {         //如果喫到食物時,產生一個蛇身以外的隨機的點,不會去掉蛇尾
                while (snake.indexOf(food = ~~(Math.random() * 400)) >= 0);
                draw(food, "yellow");
            } else {                //沒有喫到食物時正常移動,蛇尾出隊列
                draw(snake.pop(),"black");
            }
            setTimeout(arguments.callee, 150);      
                                    //每隔0.15秒執行函數一次,可以調節蛇的速度
        }();
    </script>
</body>
</html>

  首先,我們要知道做一個貪喫蛇最主要的是什麼,是做出蛇活動的場所和如何使蛇動起來。

  我們先看蛇活動的場所:

    <!-- html -->
    <canvas id="can" width="400" height="400" style="background-color: black">
        對不起,您的瀏覽器不支持canvas
    </canvas>
    <!-- js -->
    box = document.getElementById('can').getContext('2d');

  這是一個400px*400pxcanvas,思路是以20px*20px爲一個方格,組成2020列的方陣,總共400格,然後綠色填充的格子表示蛇身,用黃色表示食物。這400個格子和數字0~399一一對應,對應的方式就是以20作爲基數,n / 20再取整表示第幾行,n % 20表示第幾列。行數和列數都用0~19表示。
  蛇用一個一維數組表示,每個值都是這400個數中的一個,用var snake = [41, 40];初始化這條蛇,索引0爲蛇頭。food表示食物的位置,direction表示蛇頭下一次運動的轉向。蛇的運動就用添加和刪除數組元素來實現,每次執行繪製蛇頭,去掉蛇尾,循環執行使蛇運動。
  下邊從函數運行的起始處(39行)開始看:

!function() {}();

  什麼鬼?這其實是立即執行函數IIFE的另一種寫法。關於IIFE這篇文章講的挺不錯的。繼續往下看,給蛇頭添加一個節點n,其值爲當前蛇頭的值加direction的值,如此一來就能理解爲什麼要用20表示向下,-20表示向上了。再下一行是一個if語句,其中值得提醒的是&&的優先級高於||,這個語句就是判斷即將出現的蛇頭是不是屬於蛇身,或者跑到box外邊去了。如果沒有死亡,就把這個蛇頭繪製出來,下邊就看看繪製的代碼:

function draw(seat, color) {
    box.fillStyle = color;
    box.fillRect(seat % 20 *20 + 1, ~~(seat / 20) * 20 + 1, 18, 18);
}

  填充時填充18*18的像素,留1px邊框。.fillRect()中第一個參數就是要繪製的矩形的x座標seat % 20 *20 + 1,即先得到所要繪製的矩形塊在方陣中的位置:第(seat / 20)行,第seat % 20列,再* 20 + 1具體到像素點。可能這個有點難理解,我感覺在這裏的用處應該和Math.floor()差不多,可以用來截除小數,兩者具體還有一些區別,可以去看《你不知道的js(中卷)》62頁。
  回到47行,又是一個判斷語句,判斷下次蛇頭出現的位置是不是和當前的食物的位置相同,如果相同,生成下一個食物,食物的位置爲一個隨機數,但是要判斷這個點不是出現在當前的蛇身上,繪製食物。如果沒有喫到食物,即蛇在正常運動時,每向前一次,將蛇尾彈出,並利用其返回值將這個點重新繪製爲黑色。
  最後的setTimeout,循環執行當前函數,設置執行週期來調蛇的移動速度。
  到了這裏,我們發現這條蛇已經可以動了,加上鍵盤的操作就完成了:

document.onkeydown = function(evt) {    
    direction = snake[1] - snake[0] == (n = [-1, -20, 1, 20][(evt || event).keyCode - 37] || direction) ? direction : n;
};

  將這個函數綁定到鍵盤事件上,evt || event用法的原因這裏有詳細的解釋,是爲了兼容ie
  三目運算符?前邊的判斷語句又可分爲兩部分:
1. snake[1] - snake[0]的值應該就是-direction,按理說此處寫成-direction應該和原來是一個效果,那爲什麼沒有這麼做呢,因爲如果這樣寫,玩家可能在一個函數週期中多次改變direction的值,最後使得direction和當前真正的運動方向不一致,導致遊戲崩潰。
2. 在==後邊,[-1, -20, 1, 20][(evt || event).keyCode - 37]中前邊的[]是一個數組,後邊的[]是取索引,左上右下四個鍵的keyCode分別爲37, 38, 39, 40,計算後的索引爲0, 1, 2, 3,使方向鍵與direction的取值對應起來。這裏的巧妙之處在於如果按下的按鍵不是方向鍵,在數組中將得不到對應的值,返回undefine。此時,由於之後的||運算符,n會取到direction原來的值。

  再用三目運算符來判斷,如果按鍵方向不是反方向,就更新direction的值。

以上就是本篇的全部內容啦,雖然都是一些基礎的東西,但是感覺還是挺好玩的。要是哪裏理解的不對還希望指證出來,共同進步。這裏是我的博客以及github,喜歡的可以點個星。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章