用原生JavaScript寫一個貪吃蛇

前言

我的博客

看到掘金上有這樣一種效果,感覺很好看,就是那種毛玻璃效果,於是想試試寫一個登錄頁面並且實現遮罩,但是寫成瞭開始遊戲,可是光一個開始遊戲也沒意思,幹脆寫一個小遊戲吧,直接試試貪吃蛇。

如何實現

                                開始遊戲                            

這是我HTMLbody部分的代碼,main是主體,也就是遊戲場地。

beginBox是開始遊戲的界面,我再這個盒子裡面實現瞭毛玻璃遮罩,還不錯。

然後下面那個盒子就是蛇瞭。

如果你也想試試毛玻璃遮罩效果,可以看看我的css

直接看js代碼吧。

首先,我們先定義好全局變量,做好準備。

// 蛇的速度,即計時器的間隔時間var SnakeTime = 200;// 蛇的身體var map = document.getElementById('map');

速度是計時器控制的。

接下來,我們創建一個方法,Snake(),這是蛇整個的構造方法。

我再這個方法裡面寫瞭蛇的一些東西。

我的蛇初始是3個10*10的正方形拼成的。

// 設置蛇的寬、高、默認走的方向    this.width = 10;    this.height = 10;    this.direction = 'right';

所以方法裡面,我首先確定瞭寬高,以及使用direction屬性確定方向。

然後,我們這個蛇的三個點,需要按照規律排好,我這裡使用瞭一個數組。

this.body = [    { x: 2, y: 0 },   // 蛇頭,第一個點    { x: 1, y: 0 },   // 蛇脖子,第二個點    { x: 0, y: 0 }    // 蛇尾,第三個點];

這還隻是蛇的初始化狀態哈!蛇還沒創建。

然後我們來創建蛇。

定義一個方法。這個方法在snake方法裡面。

// 顯示蛇    this.display = function () {        // 創建蛇        for (var i = 0; i < this.body.length; i++) {            if (this.body[i].x != null) {                   // 當吃到食物時,x==null,不能新建,不然會在0,0處新建一個                var s = document.createElement('div');                // 將節點保存到狀態中,以便於後面刪除                this.body[i].flag = s;                // 設置寬高                s.style.width = this.width + 'px';                s.style.height = this.height + 'px';                //設置顏色                s.style.backgroundColor = 'yellow';                // 設置位置                s.style.position = 'absolute';                s.style.left = this.body[i].x * this.width + 'px';                s.style.top = this.body[i].y * this.height + 'px';                // 添加進去                map.appendChild(s);            }        }        //設置蛇頭的顏色        this.body[0].flag.style.backgroundColor = 'orange';    };

在這個方法裡面,s就是一個div,而body數組的長度是3,我們循環3此,依次追加,就拼成瞭,頭、身、尾。

但是,此時蛇,是出來瞭,但是不能動啊….

所以在定義一個方法,也是在snake方法裡面。

this.run = function () {        // 後一個元素到前一個元素的位置        for (var i = this.body.length - 1; i > 0; i--) {            this.body[i].x = this.body[i - 1].x;            this.body[i].y = this.body[i - 1].y;        }        // 根據方向處理蛇頭        switch (this.direction) {            case "left":                this.body[0].x -= 1;                break;            case "right":                this.body[0].x += 1;                break;            case "up":                this.body[0].y -= 1;                break;            case "down":                this.body[0].y += 1;                break;        }        // 判斷是否出界,根據蛇頭判斷        if (this.body[0].x  150 || this.body[0].y  60) {            clearInterval(timer);              // 清除定時器            alert("出界啦,遊戲結束!");            document.getElementById('beginBox').style.display = 'block';            // 刪除舊的            for (var i = 0; i < this.body.length; i++) {                if (this.body[i].flag != null) {                       // 如果剛吃完就死掉,會加一個值為null的                    map.removeChild(this.body[i].flag);                }            }            this.body = [   // 回到初始狀態,                { x: 2, y: 0 },                { x: 1, y: 0 },                { x: 0, y: 0 }            ];            this.direction = 'right';            this.display();   // 顯示初始狀態            return false;   // 結束        }        // 判斷蛇頭吃到食物,xy坐標重合,        if (this.body[0].x == food.x && this.body[0].y == food.y) {            // 蛇加一節,因為根據最後節點定,下面display時,會自動賦值的            this.body.push({ x: null, y: null, flag: null });            // 獲取蛇的長度            var len = this.body.length;            // 根據蛇的長度,設置定時器頻率SnakeTime            SnakeTime = SnakeTime - (len - 3) * 5;            // SnakeTime最低不能小於40            if (SnakeTime < 40) {                SnakeTime = 40;            }            refresh();            // 清除食物,重新生成食物            map.removeChild(food.flag);            food.display();        }        // 吃到自己死亡,從第五個開始與頭判斷,因為前四個永遠撞不到        for (var i = 4; i < this.body.length; i++) {            if (this.body[0].x == this.body[i].x && this.body[0].y == this.body[i].y) {                clearInterval(timer);   // 清除定時器,                alert("你咬到瞭自己,遊戲結束!");                // 顯示id為beginBox的毛玻璃遮罩盒子                document.getElementById('beginBox').style.display = 'block';                // 刪除舊的                for (var i = 0; i < this.body.length; i++) {                    if (this.body[i].flag != null) {                           // 如果剛吃完就死掉,會加一個值為null的                        map.removeChild(this.body[i].flag);                    }                }                this.body = [   // 回到初始狀態,                    { x: 2, y: 0 },                    { x: 1, y: 0 },                    { x: 0, y: 0 }                ];                this.direction = 'right';                this.display();   // 顯示初始狀態                return false;   // 結束            }        }        // 先刪掉初始的蛇,在顯示新蛇        for (var i = 0; i < this.body.length; i++) {            if (this.body[i].flag != null) {                   // 當吃到食物時,flag是等於null,且不能刪除                map.removeChild(this.body[i].flag);            }        }        // 重新顯示蛇        this.display();    }}

這段代碼有點多哈。我們拆開看。

// 後一個元素到前一個元素的位置for (var i = this.body.length - 1; i > 0; i--) {      this.body[i].x = this.body[i - 1].x;      this.body[i].y = this.body[i - 1].y;}

首先,蛇是一節節動的,所以我們使用循環,讓他後一個替代前一個的位置。

然後,根據direction屬性來判斷方向。

// 根據方向處理蛇頭        switch (this.direction) {            case "left":                this.body[0].x -= 1;                break;            case "right":                this.body[0].x += 1;                break;            case "up":                this.body[0].y -= 1;                break;            case "down":                this.body[0].y += 1;                break;        }

然後,我們就要定義出界後遊戲結束瞭,這個就不多說瞭。

// 判斷是否出界,根據蛇頭判斷        if (this.body[0].x  150 || this.body[0].y  60) {            clearInterval(timer);              // 清除定時器            alert("出界啦,遊戲結束!");            document.getElementById('beginBox').style.display = 'block';            // 刪除舊的            for (var i = 0; i < this.body.length; i++) {                if (this.body[i].flag != null) {                       // 如果剛吃完就死掉,會加一個值為null的                    map.removeChild(this.body[i].flag);                }            }            this.body = [   // 回到初始狀態,                { x: 2, y: 0 },                { x: 1, y: 0 },                { x: 0, y: 0 }            ];            this.direction = 'right';            this.display();   // 顯示初始狀態            return false;   // 結束        }

這裡的x和y都是整體的px/蛇的盒子高寬,也就是除10,1 = 10px

然後,就是吃食物瞭。

當蛇頭與食物相遇,我們就認為它吃瞭食物,於是我們通過位置來判斷吃東西。

        // 判斷蛇頭吃到食物,xy坐標重合,        if (this.body[0].x == food.x && this.body[0].y == food.y) {            // 蛇加一節,因為根據最後節點定,下面display時,會自動賦值的            this.body.push({ x: null, y: null, flag: null });            // 獲取蛇的長度            var len = this.body.length;            // 根據蛇的長度,設置定時器頻率SnakeTime            SnakeTime = SnakeTime - (len - 3) * 5;            // SnakeTime最低不能小於40            if (SnakeTime < 40) {                SnakeTime = 40;            }            refresh();            // 清除食物,重新生成食物            map.removeChild(food.flag);            food.display();        }

說明一下:這個flag是當時創建食物時留下的一個對象。

創建食物方法我寫在瞭後面,一步步看吧。

而下面這部分代碼:

// 獲取蛇的長度            var len = this.body.length;            // 根據蛇的長度,設置定時器頻率SnakeTime            SnakeTime = SnakeTime - (len - 3) * 5;            // SnakeTime最低不能小於40            if (SnakeTime < 40) {                SnakeTime = 40;            }

是為瞭可以動態的實現蛇吃到食物後,速度加快。

這裡,我有一個refresh();這個後面再看。

然後就是咬到自己,遊戲結束,這個不多說。

現在就到瞭構造食物瞭。

// 構造食物function Food() {    this.width = 10;    this.height = 10;    this.display = function () {        // 創建一個div(一節蛇身)        var f = document.createElement('div');        this.flag = f;        f.style.width = this.width + 'px';        f.style.height = this.height + 'px';        f.style.background = 'red';        f.style.position = 'absolute';        this.x = Math.floor(Math.random() * 80);        this.y = Math.floor(Math.random() * 40);        f.style.left = this.x * this.width + 'px';        f.style.top = this.y * this.height + 'px';        map.appendChild(f);    }}

實際上,這個“食物”就是創建瞭蛇的一節身體。

後面也可以看見,有一個追加到蛇身。

 map.appendChild(f);

看到這,你可能還疑惑,不應該啊,這也無法分辨出明確的蛇和食物啊,也就是說,很抽象啊。

因為我最後面,還有一個創建對象過程。

var snake = new Snake();var food = new Food();// 初始化顯示snake.display();   food.display();

將方法作為瞭一個對象。

而我們為瞭控制蛇的方向,我們需要使用鍵盤事件來改變蛇的屬性。

// 給body加按鍵事件,上下左右document.body.onkeydown = function (e) {    // 有事件對象就用事件對象,沒有就自己創建一個,兼容低版本瀏覽器    var ev = e || window.event;    switch (ev.keyCode) {        case 38:            if (snake.direction != 'down') {   // 不允許返回,向上的時候不能向下                snake.direction = "up";            }            break;        case 40:            if (snake.direction != "up") {                snake.direction = "down";            }            break;        case 37:            if (snake.direction != "right") {                snake.direction = "left";            }            break;        case 39:            if (snake.direction != "left") {                snake.direction = "right";            }            break;        // 兼容WASD鍵            case 87:            if (snake.direction != "down") {                snake.direction = "up";            }            break;        case 83:            if (snake.direction != "up") {                snake.direction = "down";            }            break;        case 65:            if (snake.direction != "right") {                snake.direction = "left";            }            break;        case 68:            if (snake.direction != "left") {                snake.direction = "right";            }            break;    }    };

當然,我這裡做瞭兼容,WASD和上下左右鍵都通用控制。

最後就是點擊開始遊戲的事件瞭。

// 獲取開始按鈕var btn = document.getElementById('begin');// 點擊開始遊戲事件btn.onclick = function () {    // 開始按鈕毛玻璃幕佈    var parent = this.parentNode;    // 隱藏開始按鈕    parent.style.display = 'none';    // 獲取定時器時間    let time = SnakeTime;    timer = setInterval(function () {        snake.run();    }, time);}

我們這裡面是使用瞭setInterval來實現不斷的前進走動。

    timer = setInterval(function () {        snake.run();    }, time);

但是啊,因為這個計時器他是不刷新的,也就是說啟動時,time = 200,然後你改變time的值。

此時time值確實變瞭,但是,這個setInterval它隻認定第一次的設置,它不會動態改變。

那怎麼辦呢?首先,分析,他要什麼時候做出time值的刷新,肯定是吃到食物的時候對吧。

於是,我們寫一個刷新函數。

// 定義刷新定時器方法function refresh() {    // 停止定時器    clearInterval(timer);    // 刷新定時器    timer = setInterval(function () {        snake.run();        console.log(SnakeTime);    }, SnakeTime);}

然後,你們就知道我上面說的refresh()方法是什麼瞭吧?就是用於動態刷新setInterval的。

這樣,這個貪吃蛇就寫好瞭。

效果

開始頁面

遊戲界面

完整源碼

Github:JanYork/Snake

Gitee:janyork/Snake

本文來自網絡,不代表程式碼花園立場,如有侵權,請聯系管理員。https://www.codegarden.cn/article/31307/
返回顶部