20行js代碼實(shí)現(xiàn)的貪吃蛇小游戲
前言
最近在csdn上看到一位大神用20行代碼就寫(xiě)出了一個(gè)貪吃蛇的小游戲,感覺(jué)被驚艷到了,就試著讀了一下這段代碼,閱讀過(guò)程中不斷為作者寫(xiě)法的巧妙而叫絕,其中我發(fā)現(xiàn)自己對(duì)運(yùn)算符優(yōu)先級(jí)和一些js的技巧不是很清楚,所以看完之后決定把思路分享出來(lái),方便和我一樣的小白學(xué)習(xí)。
我對(duì)代碼稍稍做了些修改,并添加了一些注釋,方便理解。
示例代碼
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>貪吃蛇重構(gòu)</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">對(duì)不起,您的瀏覽器不支持canvas</canvas> <script> var snake = [41, 40], //snake隊(duì)列表示蛇身,初始節(jié)點(diǎn)存在但不顯示 direction = 1, //1表示向右,-1表示向左,20表示向下,-20表示向上 food = 43, //食物的位置 n, //與下次移動(dòng)的位置有關(guān) box = document.getElementById('can').getContext('2d'); //從0到399表示box里[0~19]*[0~19]的所有節(jié)點(diǎn),每20px一個(gè)節(jié)點(diǎn) function draw(seat, color) { box.fillStyle = color; box.fillRect(seat % 20 *20 + 1, ~~(seat / 20) * 20 + 1, 18, 18); //用color填充一個(gè)矩形,以前兩個(gè)參數(shù)為x,y坐標(biāo),后兩個(gè)參數(shù)為寬和高。 } document.onkeydown = function(evt) { //當(dāng)鍵盤(pán)上下左右鍵摁下的時(shí)候改變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); //此時(shí)的n為下次蛇頭出現(xiàn)的位置,n進(jìn)入隊(duì)列 if(snake.indexOf(n, 1) > 0 || n < 0 || n > 399 || direction == 1 && n % 20 == 0 || direction == -1 && n % 20 == 19) { //if語(yǔ)句判斷貪吃蛇是否撞到自己或者墻壁,碰到時(shí)返回,結(jié)束程序 return alert("GAME OVER!"); } draw(n, "lime"); //畫(huà)出蛇頭下次出現(xiàn)的位置 if(n == food) { //如果吃到食物時(shí),產(chǎn)生一個(gè)蛇身以外的隨機(jī)的點(diǎn),不會(huì)去掉蛇尾 while (snake.indexOf(food = ~~(Math.random() * 400)) > 0); draw(food, "yellow"); } else { //沒(méi)有吃到食物時(shí)正常移動(dòng),蛇尾出隊(duì)列 draw(snake.pop(),"black"); } setTimeout(arguments.callee, 150); //每隔0.15秒執(zhí)行函數(shù)一次,可以調(diào)節(jié)蛇的速度 }(); </script> </body> </html>
首先,我們要知道做一個(gè)貪吃蛇最主要的是什么,是做出蛇活動(dòng)的場(chǎng)所和如何使蛇動(dòng)起來(lái)。
我們先看蛇活動(dòng)的場(chǎng)所:
<!-- html --> <canvas id="can" width="400" height="400" style="background-color: black"> 對(duì)不起,您的瀏覽器不支持canvas </canvas> <!-- js --> box = document.getElementById('can').getContext('2d');
這是一個(gè)400px*400px的canvas,思路是以20px*20px為一個(gè)方格,組成20行20列的方陣,總共400格,然后綠色填充的格子表示蛇身,用黃色表示食物。這400個(gè)格子和數(shù)字0~399一一對(duì)應(yīng),對(duì)應(yīng)的方式就是以20作為基數(shù),n / 20再取整表示第幾行,n % 20表示第幾列。行數(shù)和列數(shù)都用0~19表示。
蛇用一個(gè)一維數(shù)組表示,每個(gè)值都是這400個(gè)數(shù)中的一個(gè),用var snake = [41, 40];
初始化這條蛇,索引0為蛇頭。food表示食物的位置,direction表示蛇頭下一次運(yùn)動(dòng)的轉(zhuǎn)向。蛇的運(yùn)動(dòng)就用添加和刪除數(shù)組元素來(lái)實(shí)現(xiàn),每次執(zhí)行繪制蛇頭,去掉蛇尾,循環(huán)執(zhí)行使蛇運(yùn)動(dòng)。
下邊從函數(shù)運(yùn)行的起始處(39行)開(kāi)始看:
!function() {}();
什么鬼?這其實(shí)是立即執(zhí)行函數(shù)IIFE的另一種寫(xiě)法。關(guān)于IIFE,這篇文章講的挺不錯(cuò)的。繼續(xù)往下看,給蛇頭添加一個(gè)節(jié)點(diǎn)n,其值為當(dāng)前蛇頭的值加direction的值,如此一來(lái)就能理解為什么要用20表示向下,-20表示向上了。再下一行是一個(gè)if語(yǔ)句,其中值得提醒的是&&的優(yōu)先級(jí)高于||,這個(gè)語(yǔ)句就是判斷即將出現(xiàn)的蛇頭是不是屬于蛇身,或者跑到box外邊去了。如果沒(méi)有死亡,就把這個(gè)蛇頭繪制出來(lái),下邊就看看繪制的代碼:
function draw(seat, color) { box.fillStyle = color; box.fillRect(seat % 20 *20 + 1, ~~(seat / 20) * 20 + 1, 18, 18); }
填充時(shí)填充18*18的像素,留1px邊框。 .fillRect()
中第一個(gè)參數(shù)就是要繪制的矩形的x坐標(biāo)seat % 20 *20 + 1,即先得到所要繪制的矩形塊在方陣中的位置:第~~(seat / 20)行,第seat % 20列,再* 20 + 1具體到像素點(diǎn)??赡苓@個(gè)~~有點(diǎn)難理解,我感覺(jué)在這里的用處應(yīng)該和Math.floor()
差不多,對(duì)一個(gè)浮點(diǎn)型的數(shù)取反再取反,得到的數(shù)就是去掉小數(shù)位的整數(shù)了。
回到47行,又是一個(gè)判斷語(yǔ)句,判斷下次蛇頭出現(xiàn)的位置是不是和當(dāng)前的食物的位置相同,如果相同,生成下一個(gè)食物,食物的位置為一個(gè)隨機(jī)數(shù),但是要判斷這個(gè)點(diǎn)不是出現(xiàn)在當(dāng)前的蛇身上,繪制食物。如果沒(méi)有吃到食物,即蛇在正常運(yùn)動(dòng)時(shí),每向前一次,將蛇尾彈出,并利用其返回值將這個(gè)點(diǎn)重新繪制為黑色。
最后的setTimeout,循環(huán)執(zhí)行當(dāng)前函數(shù),設(shè)置執(zhí)行周期來(lái)調(diào)蛇的移動(dòng)速度。
到了這里,我們發(fā)現(xiàn)這條蛇已經(jīng)可以動(dòng)了,加上鍵盤(pán)的操作就完成了:
document.onkeydown = function(evt) { direction = snake[1] - snake[0] == (n = [-1, -20, 1, 20][(evt || event).keyCode - 37] || direction) ? direction : n; };
將這個(gè)函數(shù)綁定到鍵盤(pán)事件上,evt || event用法的原因這里有詳細(xì)的解釋,是為了兼容ie。
三目運(yùn)算符?前邊的判斷語(yǔ)句又可分為兩部分:
snake[1] - snake[0]
的值應(yīng)該就是-direction,按理說(shuō)此處寫(xiě)成-direction應(yīng)該和原來(lái)是一個(gè)效果,那為什么沒(méi)有這么做呢,因?yàn)槿绻@樣寫(xiě),玩家可能在一個(gè)函數(shù)周期中多次改變direction的值,最后使得direction和當(dāng)前真正的運(yùn)動(dòng)方向不一致,導(dǎo)致游戲崩潰。- 在==后邊,
[-1, -20, 1, 20][(evt || event).keyCode - 37]
中前邊的[]是一個(gè)數(shù)組,后邊的[]是取索引,左上右下四個(gè)鍵的keyCode分別為37, 38, 39, 40,計(jì)算后的索引為0, 1, 2, 3,使方向鍵與direction的取值對(duì)應(yīng)起來(lái)。這里的巧妙之處在于如果按下的按鍵不是方向鍵,在數(shù)組中將得不到對(duì)應(yīng)的值,返回undefine。此時(shí),由于之后的||運(yùn)算符,n會(huì)取到direction原來(lái)的值。
再用三目運(yùn)算符來(lái)判斷,如果按鍵方向不是反方向,就更新direction的值。
以上就是本篇的全部?jī)?nèi)容啦,雖然都是一些基礎(chǔ)的東西,但是感覺(jué)還是挺好玩的。要是哪里理解的不對(duì)還希望指證出來(lái),共同進(jìn)步。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
讓div層隨鼠標(biāo)移動(dòng)的實(shí)現(xiàn)代碼 ie ff
隨鼠標(biāo)移動(dòng)的div層使用ie ff ,大家可以注意下兼容性的問(wèn)題。2009-12-12javascript 網(wǎng)站常用的iframe分割
就是一個(gè)頁(yè)面使用兩個(gè)iframe來(lái)調(diào)用內(nèi)容,實(shí)現(xiàn)頁(yè)面導(dǎo)航,更容易控制,可控制性好2008-06-06Bootstrap學(xué)習(xí)系列之使用 Bootstrap Typeahead 組件實(shí)現(xiàn)百度下拉效果
這篇文章主要介紹了Bootstrap學(xué)習(xí)系列之使用 Bootstrap Typeahead 組件實(shí)現(xiàn)百度下拉效果的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07js判斷請(qǐng)求的url是否可訪問(wèn),支持跨域判斷的實(shí)現(xiàn)方法
下面小編就為大家?guī)?lái)一篇js判斷請(qǐng)求的url是否可訪問(wèn),支持跨域判斷的實(shí)現(xiàn)方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-09-09JS獲取屏幕,瀏覽器窗口大小,網(wǎng)頁(yè)高度寬度(實(shí)現(xiàn)代碼)
本篇文章主要介紹了JS獲取屏幕,瀏覽器窗口大小,網(wǎng)頁(yè)高度寬度的實(shí)現(xiàn)代碼。需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2013-12-12THREE.JS使用TransformControls對(duì)模型拖拽的代碼實(shí)例
拖拽是前端實(shí)現(xiàn)中比較常用的一種效果,下面這篇文章主要給大家介紹了關(guān)于THREE.JS使用TransformControls對(duì)模型拖拽的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-03-03javascript實(shí)現(xiàn)漢字轉(zhuǎn)拼音代碼分享
這篇文章主要介紹了javascript實(shí)現(xiàn)漢字轉(zhuǎn)拼音代碼分享,非常的實(shí)用,從項(xiàng)目中分離出來(lái)的,這里分享給大家,有需要的小伙伴可以參考下。2015-04-04