我也来写一个贪吃蛇
最近工作量好大,好忙,趁周末練練手,花了近3小時寫了一個貪吃蛇。(后續會把自己的一些想法繼續更新,目前是1.3版本~更新過4次,文末有更新說明)。
這是實踐操作的地址:http://iforj.com:8000/tool/snake/
實現貪吃蛇的功能很簡單。
我就分享一下我實現貪吃蛇看起來在界面上移動并且吃食物長大的原理。
我建了一個數組list_arr[]來保存貪吃蛇所在的每個格子的id,并建了2個全局變量x和y,監聽貪吃蛇頭的位置,當然x和y也是貪吃蛇的起始位置。
那么貪吃蛇移動實際上就是每次從list_arr[]里取出第一個元素(list_arr.shift()方法),取出的第一個元素也可以認為是貪吃蛇的尾巴,然后把這個元素(也就是格子的id)代表的格子的css中的貪吃蛇的樣式去掉。然后,把[x, y]這組坐標所代表的格子的id扔進list_arr[]里(list_arr.push()),并給這個格子添加上貪吃蛇的樣式。所以實際上每次就操作了2個格子的樣式。
如果貪吃蛇吃食物([x, y]與食物所在坐標相同),那么把食物那個格子的css去掉代表食物的樣式并加上貪吃蛇的樣式,并且不去掉尾巴,也就是不去掉list_arr[]里的第一個元素所在格子的樣式。
?
<!doctype html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<style>body {position: relative;margin: 0;width: 100vw;height: 100vh;}.wrapper {padding: 40px 0;}.snake-box {margin: 0 auto;border-collapse: collapse; border:solid #ddd; border-width:1px 0px 0px 1px;}.snake-box td {padding: 10px;border: solid #ddd; border-width: 0px 1px 1px 0px; }.snake-body {background-color: #8078f3;}.snake-initial-right, .snake-right {position: relative;border-top-right-radius: 12px;border-bottom-right-radius: 12px;background-color: #8078f3; }.snake-right {/*-webkit-animation: moveright .2s linear;-moz-animation: moveright .2s linear;*/animation: moveright .2s linear;}@keyframes moveright {0% {transform: translate(-10px, 0);}100% {transform: translate(0, 0);}}.snake-initial-right:before,.snake-right:before {content: '';position: absolute;top: 4px;right: 6px;width: 3px;height: 3px;-webkit-border-radius: 50%;-moz-border-radius: 50%;border-radius: 50%;background-color: #000;}.snake-initial-right:after,.snake-right:after {content: '';position: absolute;bottom: 4px;right: 6px;width: 3px;height: 3px;-webkit-border-radius: 50%;-moz-border-radius: 50%;border-radius: 50%;background-color: #000;}.snake-left {position: relative;border-top-left-radius: 12px;border-bottom-left-radius: 12px;background-color: #8078f3;animation: moveleft .2s linear;}@keyframes moveleft {0% {transform: translate(10px, 0);}100% {transform: translate(0, 0);}}.snake-left:before {content: '';position: absolute;top: 4px;left: 6px;width: 3px;height: 3px;-webkit-border-radius: 50%;-moz-border-radius: 50%;border-radius: 50%;background-color: #000;}.snake-left:after {content: '';position: absolute;bottom: 4px;left: 6px;width: 3px;height: 3px;-webkit-border-radius: 50%;-moz-border-radius: 50%;border-radius: 50%;background-color: #000;}.snake-top {position: relative;border-top-left-radius: 12px;border-top-right-radius: 12px;background-color: #8078f3;animation: movetop .2s linear;}@keyframes movetop {0% {transform: translate(0, 10px);}100% {transform: translate(0, 0);}}.snake-top:before {content: '';position: absolute;top: 6px;left: 4px;width: 3px;height: 3px;-webkit-border-radius: 50%;-moz-border-radius: 50%;border-radius: 50%;background-color: #000;}.snake-top:after {content: '';position: absolute;top: 6px;right: 4px;width: 3px;height: 3px;-webkit-border-radius: 50%;-moz-border-radius: 50%;border-radius: 50%;background-color: #000;}.snake-bottom {position: relative;border-bottom-left-radius: 12px;border-bottom-right-radius: 12px;background-color: #8078f3;animation: movebottom .2s linear;}@keyframes movebottom {0% {transform: translate(0, -10px);}100% {transform: translate(0, 0);}}.snake-bottom:before {content: '';position: absolute;bottom: 6px;left: 4px;width: 3px;height: 3px;-webkit-border-radius: 50%;-moz-border-radius: 50%;border-radius: 50%;background-color: #000;}.snake-bottom:after {content: '';position: absolute;bottom: 6px;right: 4px;width: 3px;height: 3px;-webkit-border-radius: 50%;-moz-border-radius: 50%;border-radius: 50%;background-color: #000;}.food {position: relative;background-color: #008000;}.food:after {content: 'f';position: absolute;top: 0;left: 0;width: 100%;height: 100%;text-align: center;line-height: 20px;color: #fff;font-weight: 700;}.food-fast {background-color: #e81616;}.food-convert {background-color: #00f;}.btn-box {text-align: center;margin-bottom: 30px;}
</style>
</head>
<body>
<div class="wrapper"><div class="btn-box"><p>可以用空格鍵控制開始、暫停、繼續</p><button id="btn_control">開始</button></div><table id="snake_box" class="snake-box"></table>
</div>
<script>
//Array擴展indexOf
Array.prototype.indexOf = function(val) {for (var i = 0; i < this.length; i++) {if (this[i] == val) return i;}return -1;
};(function() {function option() {var option = {};option.width = 20; //x軸數量option.height = 20; //y軸數量option.speed = 600; //setInterval的執行周期option.fastSpeed = 150; //加速周期option.fastStep = 20; //加速步長return option;}var o = new option();var snakeBox = document.getElementById('snake_box');snakeBox.innerHTML = null;var fragment = document.createDocumentFragment();var snakeBoxArray = new Array();for (var i = 0; i < o.height; i++) {var sTr = document.createElement('tr');sTr.setAttribute('class', 'row');var cellFragment = document.createDocumentFragment();snakeBoxArray[i]= new Array(); for (var j = 0; j < o.width; j++) {var sTd = document.createElement('td');var sId = j + '_' + i;sTd.setAttribute('id', sId);sTd.setAttribute('class', 'cell');cellFragment.appendChild(sTd);snakeBoxArray[i][j] = j + '_' + i;}fragment.appendChild(sTr).appendChild(cellFragment);}snakeBox.appendChild(fragment);var x = Math.floor(o.width / 2),y = Math.floor(o.height / 2),list_arr = [], //記錄蛇的cellfast_arr = [], //記錄蛇加速的格子數dir_arr = []; //記錄蛇身的方向document.getElementById(snakeBoxArray[x][y]).classList.add('snake-initial-right');list_arr.push(x + '_' + y);/*實現鍵盤方向事件,在非同軸方向移動時,會提前移動一格,以解決快速按下非同軸方向鍵時,setInterval延遲觸發問題,能實現空格事件控制按鈕*/var lastDir = 39; //記錄上一次方向document.onkeydown = control;function control(e) {var dir,btn = document.getElementById('btn_control');if (e.keyCode == 32) {if (btn.innerHTML == '開始') {moveEvent = setInterval(move.bind(this, 39), o.speed);btn.innerHTML = '暫停';} else if (btn.innerHTML == '暫停') {btn.innerHTML = '繼續';clearInterval(moveEvent);} else if (btn.innerHTML == '繼續') {btn.innerHTML = '暫停';moveEvent = setInterval(move.bind(this, lastDir), o.speed); //移動}return false; //一定要false,否則會觸發btn的click監聽事件}if (btn.innerHTML == '開始' || btn.innerHTML == '繼續') {return true; //設置為false,會關閉所有鍵盤觸發事件}switch(e.keyCode) {case 37: //左 if (lastDir == 37 || (list_arr.length > 1 && lastDir == 39)) {return true;} else {document.getElementById(snakeBoxArray[y][x]).setAttribute('class', 'cell snake-body');x--;snakeHeadStyle = 'snake-left';lastDir = dir = 37; }break;case 38: //上if (lastDir == 38 || (list_arr.length > 1 && lastDir == 40)) {return true;} else {document.getElementById(snakeBoxArray[y][x]).setAttribute('class', 'cell snake-body');y--;snakeHeadStyle = 'snake-top';lastDir = dir = 38;}break;case 39: //右 if (lastDir == 39 || (list_arr.length > 1 && lastDir == 37)) {return true;} else {document.getElementById(snakeBoxArray[y][x]).setAttribute('class', 'cell snake-body');x++;snakeHeadStyle = 'snake-right';lastDir = dir = 39;}break;case 40: //下 if (lastDir == 40 || (list_arr.length > 1 && lastDir == 38)) {return true;} else {document.getElementById(snakeBoxArray[y][x]).setAttribute('class', 'cell snake-body');y++;snakeHeadStyle = 'snake-bottom';lastDir = dir = 40;}break; }death();list_arr.push(x + '_' + y);fast_arr.push('');dir_arr.push(dir)if (document.getElementById(snakeBoxArray[y][x]).classList.contains('food')) {if (document.getElementById(snakeBoxArray[y][x]).classList.contains('food-fast')) {o.speed = option().fastSpeed;fast_arr.length = 0; //重置加速步長}document.getElementById(snakeBoxArray[y][x]).setAttribute('class', 'cell');document.getElementById(snakeBoxArray[y][x]).classList.add(snakeHeadStyle);food(); } else {if (fast_arr.length >= o.fastStep) {o.speed = option().speed;}var tail = list_arr.shift();dir_arr.shift();document.getElementById(tail).setAttribute('class', 'cell')document.getElementById(snakeBoxArray[y][x]).classList.add(snakeHeadStyle);}clearInterval(moveEvent);moveEvent = setInterval(move.bind(this, dir), o.speed); //移動}/*移動函數*/var moveEvent;function move(direction) {document.getElementById(snakeBoxArray[y][x]).setAttribute('class', 'cell snake-body');switch(direction) {case 37: //左x--;snakeHeadStyle = 'snake-left';convertHeadStyle = 'snake-right';break;case 38: //上y--;snakeHeadStyle = 'snake-top';convertHeadStyle = 'snake-bottom';break;case 39: //右x++;snakeHeadStyle = 'snake-right';convertHeadStyle = 'snake-left';break;case 40: //下 y++;snakeHeadStyle = 'snake-bottom';convertHeadStyle = 'snake-top';break;}death();list_arr.push(x + '_' + y);dir_arr.push(direction)fast_arr.push('');if (document.getElementById(snakeBoxArray[y][x]).classList.contains('food')) { if (document.getElementById(snakeBoxArray[y][x]).classList.contains('food-fast')) {o.speed = option().fastSpeed;fast_arr.length = 0; //重置加速步長clearInterval(moveEvent);moveEvent = setInterval(move.bind(this, direction), o.speed); //移動}document.getElementById(snakeBoxArray[y][x]).setAttribute('class', 'cell');document.getElementById(snakeBoxArray[y][x]).classList.add(snakeHeadStyle);food();} else {if(fast_arr.length >= o.fastStep) {o.speed = option().speed;clearInterval(moveEvent);moveEvent = setInterval(move.bind(this, direction), o.speed); //移動 }var tail = list_arr.shift();dir_arr.shift();document.getElementById(tail).setAttribute('class', 'cell')document.getElementById(snakeBoxArray[y][x]).classList.add(snakeHeadStyle);} }/*死亡判定*/function death() {var flag = list_arr.indexOf(x + '_' + y)if (x < 0 || x >= o.width || y < 0 || y >= o.height || flag != -1) {location.reload();}}/*食物*/var foodCell; //食物位置function food() {var foodX = Math.ceil(Math.random() * o.width) - 1,foodY = Math.ceil(Math.random() * o.height) - 1;foodCell = foodX + '_' + foodY;if (list_arr.indexOf(foodCell) == -1) {n = Math.round(Math.random() * 100);document.getElementById(foodCell).classList.add('food');if (n >=0 && n < 60) {return true; //普通食物} else if (n >=60 && n < 85) {document.getElementById(foodCell).classList.add('food-fast');} else if (n >= 85 && n <= 100) {document.getElementById(foodCell).classList.add('food-convert');}// document.getElementById(foodCell).classList.add('food-fast');} else {return food();}}food();/*按鈕控制*/document.getElementById('btn_control').addEventListener('click', function(e) {if (e.target.innerHTML == '開始') {moveEvent = setInterval(move.bind(this, 39), o.speed);e.target.innerHTML = '暫停';} else if (e.target.innerHTML == '暫停') {e.target.innerHTML = '繼續';clearInterval(moveEvent);} else if (e.target.innerHTML == '繼續') {e.target.innerHTML = '暫停';moveEvent = setInterval(move.bind(this, lastDir), o.speed); //移動}})
})(); </script>
</body>
</html>
?
更新說明:
1.0版本:實現最基本的功能,貪吃蛇移動,吃食物和死亡判定;
1.1版本:加入了控制按鈕,支持空格控制按鈕;解決快速按下非同軸方向鍵時,setInterval延遲觸發問題;在貪吃蛇長度為1時,可以往4個方向移動,當大于2時,不能往移動方向反方向移動;
1.2版本:美化UI(小蛇有蛇頭和眼睛了,有興趣的可以加個蛇尾,原理和蛇頭類似);食物加入了顏色和對應效果(目前只有紅色的加速效果有效,加速周期和加速步長都在option里配置);o現在是繼承option;
其實還寫了一個藍色食物的換向效果:
if (document.getElementById(snakeBoxArray[y][x]).classList.contains('food-convert')) {document.getElementById(snakeBoxArray[y][x]).setAttribute('class', 'cell snake-body');list_arr.reverse();dir_arr.reverse();document.getElementById(list_arr[list_arr.length - 1]).setAttribute('class', 'cell');document.getElementById(list_arr[list_arr.length - 1]).classList.add(convertHeadStyle);x = list_arr[list_arr.length - 1].split('_')[0];y = list_arr[list_arr.length - 1].split('_')[1];clearInterval(moveEvent);var newDir;if (dir_arr[dir_arr.length - 1] == 37) {lastDir = newDir = 39;} else if (dir_arr[dir_arr.length - 1] == 38) {lastDir = newDir = 40;} else if (dir_arr[dir_arr.length - 1] == 39) {lastDir = newDir = 37;} else if (dir_arr[dir_arr.length - 1] == 40) {lastDir = newDir = 38;} moveEvent = setInterval(move.bind(this, newDir, o.speed)); //移動
}
建了一個數組dir_arr來監聽蛇身體每個格子的方向(就是蛇頭每次進入一個新格子的方向),然后轉向也就是把監聽小蛇身體位置的list_arr和監聽方向的dir_arr均顛倒(reverse()),然后重置蛇頭坐標(x,y),list_arr和dir_arr里的最后一個元素就是轉向后的小蛇新的頭部位置和方向,最后重置setInterval就可以了。原理就是如上。該特殊食物由于效果觸發后,轉向是往之前蛇頭第一次進入當前的蛇尾所在的格子的方向的反方向進行的,所以在小蛇較長或者貼邊緣移動時,很容易觸發死亡判定,所以最后把這個特殊事務移除了。
1.3版本:美化UI,新增了蛇頭進入新格子的動畫(如果蛇身也要,原理一樣;animation沒有對瀏覽器作適配,需要的自己適配);
2016年11月23日發現一個bug:
在小蛇長度為4時,繞一個2*2的正方形格子轉圈為直接觸發死亡條件,發現是代碼334行先觸發死亡判定,而后在350行才將尾巴從小蛇身體內去掉,最簡單的改法應該就是下面這樣了:
?
轉載于:https://www.cnblogs.com/youyouluo/p/6057132.html
總結
- 上一篇: 磨颧骨多少钱啊?
- 下一篇: 求一个好听的猪宝宝名字