├── .gitignore ├── img ├── Snake.png ├── cancel.png └── index.png ├── .idea └── vcs.xml ├── old_version.html ├── README.md ├── app.js ├── js ├── index.js ├── snake_2017.js ├── snake_2018.js ├── messageBox.js └── snake.js ├── index.html └── css └── tetris.css /.gitignore: -------------------------------------------------------------------------------- 1 | # .gitignore 2 | sftp.json -------------------------------------------------------------------------------- /img/Snake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SunQQQ/snake/HEAD/img/Snake.png -------------------------------------------------------------------------------- /img/cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SunQQQ/snake/HEAD/img/cancel.png -------------------------------------------------------------------------------- /img/index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SunQQQ/snake/HEAD/img/index.png -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /old_version.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 贪吃蛇 6 | 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## snake 2 | >此实例为贪吃蛇小游戏,演示地址 https://codinglife.online/snake 3 | 4 | 本实例由原生JS + HTML + CSS实现,因为功能简单未使用任何框架纯手工打造 5 | 6 | js文件有三个版本,第一版是17年刚学js时写的,第二版是18年时整理了下代码, 第三版是2021年又整理的。 7 | 8 | 对我来说也是个成长的见证,特意留下了之前的版本,我会一直更新。 9 | 10 | ## Build Setup 11 | 直接在浏览器里打开index.html即可; 12 | 13 | 或者node执行app.js,然后访问http://localhost:70 14 | 15 | ## About Star 16 | 最后请您给俺点个小星星![star.png](https://res.wx.qq.com/mpres/htmledition/images/icon/emotion/21.gif),您的鼓励是俺不断更新的动力![fighting.png](https://res.wx.qq.com/mpres/htmledition/images/icon/emotion/100.gif), 17 | 手动笔芯![Rose.png](https://res.wx.qq.com/mpres/htmledition/images/icon/emotion/63.gif)![Kiss.png](https://res.wx.qq.com/mpres/htmledition/images/icon/emotion/65.gif)![Heart.png](https://res.wx.qq.com/mpres/htmledition/images/icon/emotion/66.gif) 18 | 19 | ## About Me 20 | 我还有一个自己开发的博客,欢迎光临。 地址是:https://codinglife.online 21 | 22 | 如果有吐槽或者建议、或者技术上的交流,可在博客留言板联系我。 23 | 24 | 我的邮箱地址:1585437938@qq.com,也欢迎交流。 25 | 26 | ## 预览 27 | ![index](https://github.com/SunQQQ/snake/blob/master/img/index.png) -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author:sunquan 2021/08/01 3 | * 4 | * 本文件实现一个静态服务器,对于文件类请求直接响应展示,对于Ajax类请求转发并响应对应数据; 5 | * 使用方法:到本文件的地址下执行node app.js,如果是本地访问localhost:对应端口号(如localhost:70),如果部署在服务器则访问对应ip和对应端口号 6 | */ 7 | 8 | let http = require('http'); 9 | let url = require('url'); 10 | let util = require('util'); 11 | let fs = require('fs'); 12 | let mime = require('mime'); 13 | let path = require('path'); 14 | let axios = require('axios'); 15 | let querystring = require('querystring'); 16 | 17 | let server = http.createServer((req, res) => { 18 | let pathname = url.parse(req.url).pathname, 19 | UrlType = pathname.split('/'), //辨别请求是不是ajax,本应用ajax请求都有aqi标记,具体使用时需要根据情况修改此处 20 | ProxyedUrl = pathname.replace(/\/api/, 'http://39.104.22.73:8888'), //指向后后的url 21 | ext = path.parse(pathname).ext, 22 | mimeType = mime.getType(ext); 23 | 24 | if (UrlType[1] == 'api') { 25 | //我的前端请求接口url需要代理,url会添加api,需要将url替换并转发 26 | let post = ''; 27 | req.on('data', function (chunk) { 28 | post += chunk; 29 | }); 30 | 31 | req.on('end', function () { 32 | axios.post( 33 | pathname.replace(/\/api/, 'http://39.104.22.73:8888'), 34 | JSON.parse(post) 35 | ).then(function (response) { 36 | res.end(JSON.stringify(response.data)); 37 | }).catch(function (e) { 38 | console.log('ajax error'); 39 | }); 40 | }); 41 | } else if (UrlType[1] == '') { 42 | ReadAndResponse('index.html',res,mimeType); 43 | } else { 44 | ReadAndResponse(pathname.substring(1),res,mimeType); 45 | } 46 | }); 47 | 48 | server.listen(70); 49 | 50 | /** 51 | * 读取文件,并在服务中响应对应的文件 52 | * @param filePath 文件路径 53 | * @param response 响应对象 54 | * @param fileType 文件类型 55 | * @constructor 56 | */ 57 | function ReadAndResponse(filePath,response,fileType){ 58 | fs.readFile(filePath, (err, data) => { 59 | if (err) { 60 | // 错误就返回404状态码 61 | response.writeHead(404, { 62 | 'Content-Type': fileType 63 | }) 64 | } else { 65 | // 成功读取文件 66 | response.writeHead(200, { 67 | 'Content-Type': fileType 68 | }) 69 | // 展示文件数据 70 | response.write(data); 71 | } 72 | // 注意,这个end 一定要放在读取文件的内部使用 73 | response.end(); 74 | }); 75 | } 76 | -------------------------------------------------------------------------------- /js/index.js: -------------------------------------------------------------------------------- 1 | var messageBox = new MessageBox(); 2 | 3 | // 游戏结束后的处理逻辑 4 | var OverDeal = function (gameData) { 5 | var userName = localStorage.getItem("SunqBlog") ? JSON.parse(localStorage.getItem("SunqBlog")).userInfo.name : document.getElementsByClassName('name')[0].innerHTML; 6 | 7 | var para = JSON.stringify({ 8 | userName: userName, 9 | score: gameData.score - 10, 10 | gameTime: gameData.time, 11 | userId: 1 12 | }); 13 | 14 | messageBox.reloadGame(gameData.score - 10, gameData.overReason); 15 | 16 | messageBox.myAjax({ 17 | url: 'https://codinglife.online/api/createScore', 18 | data: para, 19 | success: function (data) { 20 | // 重载英雄榜 21 | messageBox.reloadScoreList(); 22 | // 弹出游戏结束弹框 23 | messageBox.reloadGame(gameData.score - 10, gameData.overReason); 24 | } 25 | }); 26 | 27 | messageBox.myAjax({ 28 | url: 'https://codinglife.online/api/createLog', 29 | data: JSON.stringify({ 30 | platformType: "贪吃蛇", 31 | page: "贪吃蛇", 32 | action: "体验", 33 | actionObject: "贪吃蛇", 34 | actionDesc: ": 玩了" + gameData.time + "s,得了" + (gameData.score - 10) + "分", 35 | }), 36 | Success: function () { 37 | } 38 | }); 39 | }; 40 | 41 | var snakeObject = new Snake({ 42 | gameSpeed: 500, 43 | gameOver: OverDeal 44 | }); 45 | 46 | init(); 47 | 48 | function init() { 49 | snakeObject.drawChessBoard();// 画棋盘 50 | 51 | messageBox.reloadScoreList(); // 渲染榜单记录 52 | 53 | snakeObject.upDownAnimation(function () { // 欢迎动画 54 | 55 | // 开始游戏 56 | snakeObject.beginGame(); 57 | 58 | snakeObject.listenKeyDown(); //监听上下左右按钮 59 | var firstBody = snakeObject.createRandomBlock('black', true);// 创建蛇身 60 | 61 | snakeObject.headerTr = firstBody.horizon;// 初始化蛇头位置 62 | snakeObject.headertd = firstBody.vertical; 63 | snakeObject.snakeBody.push(snakeObject.tds[snakeObject.headerTr][snakeObject.headertd]);//将蛇头放入蛇身 64 | 65 | snakeObject.createEgg(true);// 创建一个蛋 66 | }); 67 | } 68 | 69 | document.getElementsByClassName('edit')[0].addEventListener('click', function () { 70 | messageBox.getUserInfo(); 71 | }); -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 贪吃蛇 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 |
17 | 用户名: 18 | 编辑 19 |
20 |
0
21 |
22 |
23 |
得分:
24 |
0
25 |
26 |
27 |
28 | 用时: 29 | (秒) 30 |
31 |
0
32 |
33 |
34 |
游戏规则:
35 | 36 |

使用方向键,控制蛇吃蛋得分。

37 |
38 |
39 |
英雄榜:
40 |
41 |

42 | 比克大魔王: 43 | 230分 44 |

45 |

46 | 张可: 47 | 120分 48 |

49 |

50 | 王耀辉: 51 | 100分 52 |

53 |

54 | 我是第一: 55 | 100分 56 |

57 |

58 | 多啦A梦: 59 | 120分 60 |

61 |
62 |
63 |
64 |
65 | 66 | 67 |
68 |
69 |
70 | 编辑用户名 71 |
72 |
73 | 74 |
75 |
76 | 大侠可留下你的昵称,冲刺最高记录 77 |
78 | 79 |
80 |
81 |
82 | 83 | 84 | 85 | 86 |
87 |
88 |
89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /js/snake_2017.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by OnlyMid_sq on 2017/3/9. 3 | */ 4 | /*画棋盘 把所有td装到一个二维数组里*/ 5 | var chessboard = document.getElementById('chessboard'); 6 | var alltd = []; 7 | for(var i=0;i<30;i++){ 8 | var thistr = chessboard.appendChild(document.createElement('tr')); 9 | var thistds = []; 10 | for(var k=0;k<30;k++){ 11 | thistds[k] = thistr.appendChild(document.createElement('td')); 12 | } 13 | alltd[i] = thistds; 14 | } 15 | 16 | var dir=4;/*水平向右方向*/ 17 | var blackspace=-1;/*空格键*/ 18 | //给键盘上下左右添加方法 19 | document.onkeydown = function (e) { 20 | if (event.keyCode==37){dir=3;}// 左 21 | if (event.keyCode==38){dir=1;} // 上 22 | if (event.keyCode==39){dir=4;}// 右 23 | if (event.keyCode==40){dir=2;}// 下 24 | if (event.keyCode==32){ 25 | blackspace = -blackspace; 26 | // myconsole(blackspace); 27 | } 28 | }; 29 | // 初始化蛇 30 | var snake = []; 31 | var snakelength = 3; /*蛇身的初始长度*/ 32 | var x=0; y=snakelength-1; /*x,y刚开始时蛇头的坐标,*/ 33 | for(var i=0;i30){ 54 | clearInterval(init); 55 | }else { 56 | snake[0].style.backgroundColor = '#ffffff'; 57 | 58 | console.log('一次'); 59 | 60 | if(blackspace==1){ 61 | clearInterval(init); 62 | } 63 | 64 | for(var o=0;o29||headerTr>29||headertd<0||headerTr<0){ 56 | clearInterval(timer); 57 | alert("已撞晕,game over"); 58 | window.location.reload(); 59 | }else if(biteMyself()){ 60 | clearInterval(timer); 61 | alert("咬自己了,game over"); 62 | window.location.reload(); 63 | }else { 64 | if(snake.snakeBody[snake.snakeBody.length-1]==egg){ 65 | makeEgg(); 66 | snake.snakeBody[0].style.backgroundColor = "black"; 67 | }else { 68 | this.snakeBody[0].style.backgroundColor = "#cbcbcb"; 69 | this.snakeBody[0].style.border = "1px solid #cbcbcb"; 70 | this.snakeBody.shift(); 71 | } 72 | 73 | this.snakeBody.push(tds[headerTr][headertd]); 74 | 75 | 76 | this.snakeBody[this.snakeBody.length-1].style.backgroundColor = "black"; 77 | } 78 | timer = setInterval(function () { 79 | snake.turn(direction); 80 | },gameSpeed); 81 | } 82 | } 83 | var snake = new Snake(); 84 | snake.snakeBody.push(tds[0][0]); 85 | snake.snakeBody[0].style.backgroundColor = "black"; 86 | makeEgg(); 87 | 88 | window.addEventListener("keydown",function (e) { 89 | if(e.keyCode=="40"){ 90 | snake.turn("down"); 91 | }else if(e.keyCode=="37"){ 92 | snake.turn("left"); 93 | }else if(e.keyCode=="39"){ 94 | snake.turn("right"); 95 | }else if(e.keyCode=="38"){ 96 | snake.turn("up"); 97 | } 98 | }); 99 | 100 | function makeEgg() { 101 | egg = tds[parseInt(Math.random()*30)][parseInt(Math.random()*30)]; 102 | egg.style.backgroundColor = "red"; 103 | 104 | document.getElementsByClassName("warm")[0].innerHTML = "你吃了"+eggNum+"个蛋"; 105 | 106 | eggNum+=1; 107 | 108 | if(eggNum==2){ 109 | gameSpeed-=30; 110 | }else if(eggNum==4){ 111 | gameSpeed-=30; 112 | }else if(eggNum==6){ 113 | gameSpeed-=30; 114 | }else if(eggNum==8) { 115 | gameSpeed -= 30; 116 | }else if(eggNum==8) { 117 | gameSpeed -= 20; 118 | } 119 | } 120 | 121 | function biteMyself() { 122 | for(var b=0,snakelength=snake.snakeBody.length-1;b' + scoreList[i].userName + 35 | ':' + 36 | scoreList[i].score + '分

'); 37 | } 38 | 39 | // 更新榜单内容 40 | document.getElementsByClassName('ranking')[0].innerHTML = rankString; 41 | // input填入默认用户名 42 | document.getElementsByClassName('name')[0].innerHTML = userName; 43 | 44 | if(callback){ 45 | callback(); 46 | } 47 | } 48 | }); 49 | } 50 | 51 | MessageBox.prototype.getUserInfo = function (callback) { 52 | // 弹出输入框 53 | document.getElementsByClassName('wrapper')[0].style.display = 'flex'; 54 | 55 | // 回显input默认值 56 | document.getElementsByTagName('input')[0].value = document.getElementsByClassName('name')[0].innerHTML; 57 | 58 | // 按钮绑定点击方法,会收集用户名称并存入localstorage 59 | document.getElementsByClassName('conform')[0].onclick = this.closeMessageBox; 60 | document.getElementsByClassName('head-button')[0].onclick = this.closeMessageBox; 61 | document.getElementsByClassName('skip')[0].onclick = this.closeMessageBox; 62 | }; 63 | 64 | // 封装请求数据的方法,实现重用 65 | MessageBox.prototype.myAjax = function (para) { 66 | var xhr = new XMLHttpRequest(); 67 | xhr.open("POST", para.url, true); 68 | 69 | // 添加http头,发送信息至服务器时内容编码类型 70 | xhr.setRequestHeader("Content-Type", "application/json"); 71 | xhr.send(para.data ? para.data : ''); 72 | xhr.onreadystatechange = function() { 73 | if (xhr.readyState == 4 && (xhr.status == 200 || xhr.status == 304)) { 74 | para.success(xhr.responseText); 75 | } 76 | }; 77 | }; 78 | 79 | // 游戏结束后的弹框 80 | MessageBox.prototype.reloadGame = function (score,overReason) { 81 | var text = '您的得分是'; 82 | var text2 = ',看下英雄榜有没有你吧'; 83 | 84 | // 显示弹框,隐藏掉输入框,修改掉部分文本 85 | document.getElementsByClassName('wrapper')[0].style.display = 'flex'; 86 | if(document.getElementsByClassName('sq-input')[0]){ 87 | document.getElementsByClassName('sq-input')[0].style.display = 'none'; 88 | } 89 | 90 | document.getElementsByClassName('box-header')[0].innerHTML = overReason; 91 | document.getElementsByClassName('box-content')[0].innerHTML = text + score + text2; 92 | // 添加一个按钮 93 | document.getElementsByClassName('leave-Message')[0].style.display = 'inline'; 94 | // 修改按钮上的文字 95 | document.getElementsByClassName('conform')[0].innerHTML = '查看源码'; 96 | document.getElementsByClassName('skip')[0].innerHTML = '再来一局'; 97 | document.getElementsByClassName('head-button')[0].style.display = 'none'; 98 | 99 | 100 | // 修改按钮绑定的函数 101 | document.getElementsByClassName('leave-Message')[0].onclick = function (){ 102 | window.open('https://codinglife.online/message-board'); 103 | }; 104 | document.getElementsByClassName('conform')[0].onclick = function () { 105 | window.open('https://github.com/SunQQQ/snake'); 106 | }; 107 | document.getElementsByClassName('adminUrl')[0].onclick = function () { 108 | window.open('https://codinglife.online/admin'); 109 | }; 110 | document.getElementsByClassName('skip')[0].onclick = function () { 111 | window.location.reload(); 112 | }; 113 | }; 114 | 115 | -------------------------------------------------------------------------------- /css/tetris.css: -------------------------------------------------------------------------------- 1 | body{ 2 | background-color: rgb(103,120,104); 3 | } 4 | 5 | table{ 6 | background-color: rgb(103,120,104); 7 | border-right: 2px solid black; 8 | } 9 | 10 | tr{ 11 | border:1px solid red; 12 | /*border-collapse:collapse;*/ 13 | } 14 | 15 | td{ 16 | width:10px; 17 | height:10px; 18 | border:3px solid rgb(88, 104, 88); 19 | box-shadow: 0px 0px 0px 3px rgb(103,120,104) inset; 20 | background-color: rgb(88, 104, 88); 21 | } 22 | 23 | /*整体内容*/ 24 | .content{ 25 | vertical-align:middle; 26 | display: flex; 27 | margin: 0 auto; 28 | width: 815px; 29 | border: 2px solid black; 30 | } 31 | 32 | .content table{ 33 | width: 767px; 34 | } 35 | 36 | /*左侧游戏棋盘*/ 37 | .content .left{ 38 | width: 180px; 39 | margin-right: 10px; 40 | } 41 | 42 | /*右侧信息栏*/ 43 | .content .right{ 44 | /*border: 1px solid black;*/ 45 | width: 180px; 46 | } 47 | 48 | .rules{ 49 | padding: 10px; 50 | border-bottom: 2px solid black; 51 | } 52 | 53 | .rules span:first-child{ 54 | font-size: 16px; 55 | } 56 | 57 | .rules .unit{ 58 | font-size: 6px; 59 | } 60 | 61 | .rules .title{ 62 | font-size: 16px; 63 | font-weight: bold; 64 | } 65 | 66 | .rules:last-child{ 67 | border-bottom: 0; 68 | } 69 | 70 | .rules p{ 71 | text-indent: 25px; 72 | } 73 | 74 | .rules .score{ 75 | margin: 0 auto; 76 | display: block; 77 | font-size: 30px; 78 | font-weight: bold; 79 | text-align: center; 80 | } 81 | 82 | .name{ 83 | height: 49px !important; 84 | line-height: 49px !important; 85 | font-size: 20px !important; 86 | } 87 | 88 | .edit{ 89 | display: inline-block; 90 | border-radius: 5px !important; 91 | border: 1px solid black !important; 92 | padding: 0px 15px !important; 93 | font-weight: normal; 94 | font-size: 15px; 95 | cursor: pointer; 96 | } 97 | 98 | /*弹框*/ 99 | .wrapper{ 100 | position: fixed; 101 | left: 0; 102 | right: 0; 103 | top:0; 104 | bottom: 0; 105 | /*background-color: grey;*/ 106 | 107 | display: none; 108 | align-items: center; 109 | } 110 | .wrapper .dialog-box{ 111 | width: 380px; 112 | background-color: #fff; 113 | border: 1px solid #ebeef5; 114 | margin: 0 auto; 115 | border-radius: 4px; 116 | box-shadow: 0px 0px 0px 3px rgb(103,120,104); 117 | padding: 15px; 118 | position: relative; 119 | } 120 | .box-header{ 121 | padding-bottom: 10px; 122 | font-size: 18px; 123 | line-height: 1; 124 | color: #303133; 125 | } 126 | 127 | .head-button{ 128 | position: absolute; 129 | right: 15px; 130 | top: 15px; 131 | cursor:pointer; 132 | } 133 | 134 | .head-button img{ 135 | width: 25px; 136 | height: 25px; 137 | } 138 | 139 | .box-content{ 140 | padding: 10px 0 0; 141 | color: #606266; 142 | font-size: 14px; 143 | min-height: 60px; 144 | } 145 | 146 | .box-content .sq-input{ 147 | padding: 15px 0; 148 | } 149 | .box-content .sq-input input{ 150 | padding: 5px 15px; 151 | border-radius: 4px; 152 | width: 100%; 153 | box-sizing: border-box; 154 | border: 1px solid #dcdfe6; 155 | height: 40px; 156 | line-height: 40px; 157 | outline: none; 158 | } 159 | .box-btns{ 160 | text-align: right; 161 | margin: 15px 0 0; 162 | } 163 | 164 | .box-btns button{ 165 | padding: 9px 15px; 166 | color: #606266; 167 | outline: none; 168 | line-height: 1; 169 | font-size: 12px; 170 | color: #606266; 171 | background-color: #fff; 172 | border: 1px solid #dcdfe6; 173 | border-radius: 3px; 174 | cursor: pointer; 175 | } 176 | 177 | .box-btns .leave-Message{ 178 | display: none; 179 | color: #f56c6c; 180 | background: #fef0f0; 181 | border-color: #fbc4c4; 182 | } 183 | 184 | .box-btns .conform{ 185 | color: #409eff; 186 | background: #ecf5ff; 187 | border-color: #b3d8ff; 188 | } 189 | 190 | .box-btns .adminUrl{ 191 | color: #67c23a; 192 | background: #f0f9eb; 193 | border-color: #c2e7b0; 194 | } 195 | 196 | .box-btns button:focus, .box-btns button:hover { 197 | color: #409eff; 198 | border-color: #c6e2ff; 199 | background-color: #ecf5ff; 200 | } 201 | 202 | .box-btns .leave-Message:focus, .box-btns .leave-Message:hover{ 203 | display: none; 204 | background-color: rgba(245, 108, 108, 0.7); 205 | border-color: rgba(245, 108, 108, 0.7); 206 | color:#fff; 207 | } 208 | 209 | .box-btns .adminUrl:focus, .box-btns .adminUrl:hover{ 210 | color: #fff; 211 | background-color: #67c23a; 212 | border-color: #67c23a; 213 | } 214 | 215 | .box-btns .conform:focus, .box-btns .conform:hover{ 216 | color: #fff; 217 | background-color: #409eff; 218 | border-color: #409eff; 219 | } 220 | 221 | .ranking p{ 222 | display: flex; 223 | height: 16px; 224 | line-height: 16px; 225 | text-indent: 0; 226 | } 227 | 228 | .user-name{ 229 | width: 120px; 230 | overflow: hidden; 231 | vertical-align: middle; 232 | } 233 | .score-num{ 234 | vertical-align: middle; 235 | text-indent: 10px; 236 | width: 85px; 237 | overflow: hidden; 238 | } -------------------------------------------------------------------------------- /js/snake.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: sunquan 2017/3/9. 3 | * 4 | * 本文件用js语言实现一个贪吃蛇类; 5 | */ 6 | function Snake(para) { 7 | // 在新建蛇时可以修改的参数 8 | this.gameSpeed = para.gameSpeed; //游戏开场时的速度 9 | this.gameOver = para.gameOver; 10 | 11 | // 初始化的参数 12 | this.snakeBody = []; // 蛇的身体 13 | this.eggNum = 0; // 蛋的个数 14 | this.headerTr = 0; // 蛇头的横坐标 15 | this.headertd = 0; // 蛇头的纵坐标 16 | this.tds = []; // 装整个棋盘的方块 17 | this.block = ''; // 染了色的方块 18 | this.timer = ''; // 初始一个计时器 19 | this.preDirection; // 第一个蛇头的方向 20 | this.timeBegin = 0; // 游戏计时变量 21 | this.timeWatch = ''; // 统计游戏持续时间 22 | this.direction = 'down'; 23 | this.begin = false; // 游戏是否开始 24 | } 25 | 26 | // 蛇的转向 27 | Snake.prototype.turn = function () { 28 | var that = this; 29 | 30 | that.direction = arguments[0]; 31 | 32 | // 如果是第一次按按钮,开始游戏即时 33 | if(!that.begin && that.direction){ 34 | that.updateTime(true); 35 | that.begin = true; 36 | } 37 | 38 | // 限制回退操作 39 | if ((that.preDirection == "left" && that.direction == "right") || (that.preDirection == "right" && that.direction == "left")) { 40 | return; 41 | } else if ((that.preDirection == "up" && that.direction == "down") || (that.preDirection == "down" && that.direction == "up")) { 42 | return; 43 | } 44 | that.preDirection = that.direction; 45 | 46 | if (that.direction == "left") { 47 | this.headertd -= 1; 48 | } else if (that.direction == "right") { 49 | this.headertd += 1; 50 | } else if (that.direction == "up") { 51 | this.headerTr -= 1; 52 | } else if (that.direction == "down") { 53 | this.headerTr += 1; 54 | } 55 | 56 | if (this.headertd > 29 || this.headerTr > 29 || this.headertd < 0 || this.headerTr < 0) { //判断点击了方向键后,下一个移动的方块,是否碰到墙壁 57 | if (that.timer) { 58 | window.clearInterval(that.timer); 59 | that.timer = null; 60 | console.log('timer', that.timer); 61 | 62 | this.updateTime(false); 63 | 64 | that.gameOver({ 65 | score: that.eggNum * 10, 66 | time: that.timeBegin, 67 | overReason: '撞墙啦' 68 | }); 69 | } else { 70 | return; 71 | } 72 | } else if (this.biteMyself()) { 73 | clearInterval(that.timer); 74 | this.updateTime(false); 75 | 76 | window.clearInterval(that.timer); 77 | that.gameOver({ 78 | score: that.eggNum * 10, 79 | time: that.timeBegin, 80 | overReason: '咬自己啦' 81 | }); 82 | } else { //点击了方向键后,下一个移动的方块,不咬自己也不碰墙的逻辑处理 83 | if (this.snakeBody[this.snakeBody.length - 1] == this.block) { //点击了方向键后,下一个移动的方块,刚好是蛋 84 | this.createEgg(); 85 | this.snakeBody[0].style.backgroundColor = "black"; 86 | } else { //点击了方向键后,下一个移动的方块,不是蛋 87 | this.snakeBody[0].style.backgroundColor = "rgb(88, 104, 88)"; //蛇身体那个数组的第一个元素恢复成棋盘的背景颜色, 88 | this.snakeBody[0].style.border = "3px solid rgb(88, 104, 88)"; 89 | // 如果吃了蛇蛋后,清除最后一个蛇蛋的样式 90 | this.snakeBody[0].style.borderRadius = "0px"; 91 | this.snakeBody[0].style.boxShadow = '0px 0px 0px 3px rgb(103,120,104) inset'; 92 | 93 | this.snakeBody.shift(); //删除蛇身数组第一个元素 94 | } 95 | 96 | //给蛇身数组添加对应方向上的下一个方块。配合上面删除数组第一个元素并恢复背景色的操作,表现出一种往前挪到的动画 97 | this.snakeBody.push(this.tds[this.headerTr][this.headertd]); 98 | 99 | this.snakeBody[this.snakeBody.length - 1].style.backgroundColor = "black"; 100 | this.snakeBody[this.snakeBody.length - 1].style.border = "3px solid black"; 101 | } 102 | }; 103 | 104 | /** 105 | * 随机的给一个方块上色 106 | * color:方块的背景色,和边框颜色 107 | */ 108 | Snake.prototype.createRandomBlock = function (color,init) { 109 | var horizon, vertical; 110 | 111 | // 初始化时随机渲染一个黑色块作为蛇身 112 | if (color == 'black') { 113 | 114 | horizon = init ? 15 : parseInt(Math.random() * 30); 115 | vertical = init ? 15 : parseInt(Math.random() * 30); 116 | this.block = this.tds[horizon][vertical]; 117 | this.block.style.backgroundColor = color; 118 | this.block.style.border = "3px solid " + color; 119 | } else {//之后会持续创建白块作为蛋 120 | 121 | do { 122 | horizon = init ? 20 : parseInt(Math.random() * 30); 123 | vertical = init ? 15 : parseInt(Math.random() * 30); 124 | this.block = this.tds[horizon][vertical]; 125 | console.log('创建一次'); 126 | 127 | this.block.style.backgroundColor = color; 128 | this.block.style.border = "3px solid " + color; 129 | this.block.style.boxShadow = "none"; 130 | this.block.style.borderRadius = "10px"; 131 | } while (this.snakeBody.indexOf(this.block) != -1); // 如果随机生成的蛋刚好在蛇身里,重新生成; 132 | 133 | } 134 | 135 | return { 136 | horizon: horizon, 137 | vertical: vertical 138 | }; 139 | } 140 | 141 | /** 142 | * 创建一个新的蛋,并刷新游戏成绩,提高游戏速度 143 | */ 144 | Snake.prototype.createEgg = function (init) { 145 | if(init){ 146 | this.createRandomBlock('white',init); 147 | }else{ 148 | this.createRandomBlock('white'); 149 | } 150 | 151 | // 更新得分 152 | document.getElementsByClassName("score")[1].innerHTML = this.eggNum * 10; 153 | 154 | this.eggNum += 1; // 已吃鸡蛋计数 155 | 156 | // 更新游戏速度 157 | if (this.eggNum == 1) { 158 | this.gameSpeed -= 150; 159 | } else if (this.eggNum == 3) { 160 | this.gameSpeed -= 120; 161 | } else if (this.eggNum == 5) { 162 | this.gameSpeed -= 100; 163 | } else if (this.eggNum == 7) { 164 | this.gameSpeed -= 80; 165 | } else if (this.eggNum == 9) { 166 | this.gameSpeed -= 20; 167 | } 168 | 169 | this.beginGame(); 170 | } 171 | 172 | // 判断蛇是否咬到自己 173 | Snake.prototype.biteMyself = function () { 174 | for (var b = 0, snakelength = this.snakeBody.length - 1; b < snakelength; b++) { 175 | if (this.snakeBody[snakelength] == this.snakeBody[b]) { 176 | return true; 177 | } 178 | } 179 | return false; 180 | } 181 | 182 | // 画棋盘,即地图 183 | Snake.prototype.drawChessBoard = function () { 184 | /*画棋盘 把所有td装到一个二维数组里*/ 185 | var chessboard = document.getElementById('chessboard'); 186 | for (var i = 0; i < 30; i++) { 187 | // 插入一个行元素,appendChild还返回插入的节点 188 | var thistr = chessboard.appendChild(document.createElement('tr')); // 189 | var thistds = []; 190 | // 给行元素插入30个列元素 191 | for (var k = 0; k < 30; k++) { 192 | thistds[k] = thistr.appendChild(document.createElement('td')); 193 | } 194 | // 这样,数组tds的每一个值,又都是一个数组。通过两层下标就可以准确的取到对应的td块 195 | this.tds[i] = thistds; 196 | } 197 | } 198 | 199 | // 监听上下左右四个按键 200 | Snake.prototype.listenKeyDown = function () { 201 | var that = this; 202 | window.addEventListener("keydown", function (e) { 203 | if (e.keyCode == "40") { 204 | that.turn("down"); 205 | } else if (e.keyCode == "37") { 206 | that.turn("left"); 207 | } else if (e.keyCode == "39") { 208 | that.turn("right"); 209 | } else if (e.keyCode == "38") { 210 | that.turn("up"); 211 | } 212 | }); 213 | }; 214 | 215 | // 游戏计时 216 | Snake.prototype.updateTime = function (close) { 217 | var that = this;//给游戏更新持续时间; 218 | 219 | if (close) { 220 | that.timeWatch = window.setInterval(function () { 221 | that.timeBegin += 1; 222 | document.getElementsByClassName('time')[0].innerHTML = that.timeBegin; 223 | }, 1000); 224 | } else { 225 | console.log('游戏计时停止'); 226 | window.clearInterval(that.timeWatch); 227 | that.timeWatch = 0; 228 | } 229 | } 230 | 231 | // 游戏开场的上下动画 232 | Snake.prototype.upDownAnimation = function (callback) { 233 | var that = this, 234 | trNum = -1, 235 | tdNum = 30 236 | 237 | // 上下动画 238 | var upDownInter = window.setInterval(function () { 239 | trNum += 1; 240 | tdNum -= 1; 241 | 242 | if (trNum < 30) { 243 | for (var i = 0; i < 29; i = i + 2) { 244 | that.tds[trNum][i].style.backgroundColor = 'black'; 245 | that.tds[trNum][i].style.border = '3px solid black'; 246 | } 247 | 248 | for (var t = 29; t > -1; t = t - 2) { 249 | that.tds[tdNum][t].style.backgroundColor = 'black'; 250 | that.tds[tdNum][t].style.border = '3px solid black'; 251 | } 252 | } else { 253 | window.clearInterval(upDownInter); 254 | that.leftRightAnimation(callback); 255 | } 256 | }, 50); 257 | } 258 | 259 | // 游戏开场的左右动画 260 | Snake.prototype.leftRightAnimation = function (callback) { 261 | var that = this, 262 | leftRightTdStart = -1, 263 | leftRightTdEnd = 30; 264 | 265 | // 左右动画 266 | var leftRightInter = window.setInterval(function () { 267 | leftRightTdStart += 1; 268 | leftRightTdEnd -= 1; 269 | 270 | if (leftRightTdStart < 30) { 271 | for (var i = 0; i < 29; i = i + 2) { 272 | that.tds[i][leftRightTdStart].style.backgroundColor = 'rgb(88, 104, 88)'; 273 | that.tds[i][leftRightTdStart].style.border = '3px solid rgb(88, 104, 88)'; 274 | } 275 | 276 | for (var t = 29; t > -1; t = t - 2) { 277 | that.tds[t][leftRightTdEnd].style.backgroundColor = 'rgb(88, 104, 88)'; 278 | that.tds[t][leftRightTdEnd].style.border = '3px solid rgb(88, 104, 88)'; 279 | } 280 | } else { 281 | window.clearInterval(leftRightInter); 282 | callback(); 283 | } 284 | }, 50); 285 | } 286 | 287 | Snake.prototype.beginGame = function () { 288 | var that = this; 289 | 290 | // 如果循环存在,清除掉循环。为创建新循环做准备 291 | if (that.timer) { 292 | clearInterval(that.timer); 293 | that.timer = null; 294 | } 295 | 296 | // 根据吃的蛋个数不同,提升循环间隔时间 297 | that.timer = window.setInterval(function () { 298 | that.turn(that.direction); //如果不按方向键,程序会定时循环调用turn方法,并传入目前的方向。玩家看到的就是不操作键盘时,蛇一直朝着一个方向向前游 299 | }, that.gameSpeed); 300 | } 301 | 302 | 303 | --------------------------------------------------------------------------------