需要用到 html,css,javascript 和 DOM 这些知识点就可以了. 主要是 js, 其他只是一些基本的知识. js 貌似也不是很难. 但是问题就在这里, 即使知识点都会了, 但是还是无法综合运用把东西做出来
游戏界面
先把整个游戏界面做出来:
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title > 贪吃蛇 </title>
- <style>
- *{padding: 0; margin: 0;}
- .title{text-align: center; margin: 10px 0;}
- #main{width: 800px; height: 600px; border:1px solid red; margin: 0 auto;}
- #main .left{width: 600px; height: 600px; float: left;
- position: relative;}
- /* 随机的食物通过 position 了定位的, 所以父标签要加上 position: relative*/
- #main .right{width: 200px; height: 100%; float: left; border-left: 1px solid red;
- box-sizing: border-box; text-align: center}
- /* 整体宽度包括 left 和 right 的以及内部元素的边框, 这样宽度不够, 会跑到下一行. 这里用了 box-sizing 解决问题 */
- #main .right h2{margin: 10px auto; text-align: center;}
- #main .right h2 #score{color: red;}
- #main .right button{width: 100px; height: 30px; font-size: 20px; margin-top: 30px;
- border: 0; border-radius: 5px;
- background-color: pink; color: green;}
- .food{background-color: black;}
- .snake{background-color: darkgreen}
- </style>
- </head>
- <body>
- <h2 class="title"> 贪吃蛇 </h2>
- <div id="main">
- <div class="left"></div>
- <div class="right">
- <h2 class="status"> 请点击开始 </h2>
- <h2 > 分数:<span id="score">0</span></h2>
- <button id="btn"> 开始 </button>
- </div>
- </div>
- <script>
- // 先空着
- </script>
- </body>
- </html>
一个标题, 然后下面是游戏界面. 界面分 2 部分, 左边是 600*600 的正方形, 右边宽度 200, 可以显示一些游戏信息.
这里有一个问题, 就是界面宽 800, 左边宽 600, 右边宽 200. 不过中间还有个边框至少还要占 1 的宽度. 所以右边的部分会被挤到下一行. 把 800 的总宽度加一点可以解决, 也可以像这里这样, 使用 box-sizing 属性.
box-sizing:border-box;
: 为元素设定的宽度和高度决定了元素的边框盒. 就是说, 为元素指定的任何内边距和边框都将在已设定的宽度和高度内进行绘制. 通过从已设定的宽度和高度分别减去边框和内边距才能得到内容的宽度和高度.
初始化游戏
上面的界面还缺少蛇和食物. 这里就是一个一个的小方块. 食物是一个方块, 蛇初始是连续的 3 个方块. 小方块就是带背景色的 div, 背景色已经在上面的 css 里写上了.
另外蛇和食物的位置都要用 position: absolute; 来做定位. 已经提前在他们的父元素就是 left 界面里设置好了 position: relative;.
初始化食物
直接调用一个初始化的方法 init() , 然后是定义这个初始化方法. 下面只是定义了食物的一些属性, 最后调用了一个生成食物的方法 food() :
- // 初始化
- init();
- // 初始化方法
- function init() {
- // 获取地图的跨度和高度
- this.map_width = parseInt(getComputedStyle(map).width);
- this.map_height = parseInt(getComputedStyle(map).height);
- // 食物的高度和宽度
- this.food_width = 20;
- this.food_height = 20;
- // 食物的位置, 先定义在左上角看看样子, 之后再搞随机位置
- this.food_X = 0;
- this.food_Y = 0;
- food(); // 生成食物
- }
生成食物的方法. 这里主要是生成一个 div, 然后把这个 div 添加到 map 元素里. 位置的话使用相对定位, 关键是计算出横坐标和纵坐标:
- // 获取变量
- var map = document.getElementsByClassName('left')[0];
- // 生成食物
- function food() {
- // 创建食物
- var foodBox = document.createElement('div');
- foodBox.style.width = this.food_width+"px";
- foodBox.style.height = this.food_height+"px";
- // 食物的位置
- this.food_X = Math.floor(Math.random()*this.map_width/this.food_width); // 0~29 之间
- this.food_Y = Math.floor(Math.random()*this.map_height/this.food_height);
- foodBox.style.position = 'absolute';
- foodBox.style.top = this.food_Y*this.food_height+"px"; // 随机数乘以宽度
- foodBox.style.left = this.food_X*this.food_width+"px";
- // 设置一个类名, 然后 css 给这个类定义样式
- foodBox.className = "food";
- // 将食物追加到 map 中
- map.appendChild(foodBox);
- }
初始化蛇
首先在初始化方法里追加蛇的属性. 这里蛇是由连续的小方块组成. 那么蛇就是一个数组, 数组里的每个元素就是组成蛇的一个一个的小方块. 有 3 个参数: 水平位置, 垂直位置, 是否是蛇头:
- // 初始化方法
- function init() {
- // 省略之前的代码
- // 食物的位置, 先定义在左上角看看样子, 之后再搞随机位置
- // this.food_X = 0;
- // this.food_Y = 0;
- food(); // 生成食物
- // 初始化蛇身
- this.snake_width = 20;
- this.snake_height = 20;
- // 下面是一个 3 段的蛇, 第一个参数是水平位置, 第二个参数是垂直位置, 但三个参数是是否是头部
- this.snake_body = [[2, 0, true], [1, 0, false], [0, 0, false]];
- snake(); // 生成蛇身
- }
上面调用了 snake() 方法来生成蛇. 初始化的时候, 蛇始终是生成在左上角. 蛇的每一段其实都是独立的, 这里用一个 for 循环, 把 snake_body 的每一段都添加到了 map 中. 这里是根据 snake_boy 来添加蛇, 也就是说之后, 任何时候只要修改了 snake_body 这个数组, 然后调用 snake() 方法, 就是生成一个蛇了:
- // 生成蛇身
- function snake() {
- // 用 for 循环遍历数组, 将每一段作为一个 div 然后添加
- for(var i=0; i<this.snake_body.length; i++){
- // 创建蛇身
- var snakeBox = document.createElement('div');
- // 高度和宽度
- snakeBox.style.width = this.snake_width+"px";
- snakeBox.style.height = this.snake_height+"px";
- // 定位
- snakeBox.style.position = 'absolute';
- // 位置
- snakeBox.style.top = this.snake_body[i][1]*this.snake_width+"px";
- snakeBox.style.left = this.snake_body[i][0]*this.snake_height+"px";
- // 设置一个类名, 然后 css 给这个类定义样式
- snakeBox.className = "snake";
- // 追加到 map 中
- map.appendChild(snakeBox);
- }
- }
小结
上面已经生成了完整的游戏初始化的界面, 包括随机生成的一个食物, 以及左上角的长度为 3 段的蛇. 完整的 js 代码如下:
- <script>
- // 获取变量
- var map = document.getElementsByClassName('left')[0];
- // 初始化
- init();
- // 初始化方法
- function init() {
- // 获取地图的跨度和高度
- this.map_width = parseInt(getComputedStyle(map).width);
- this.map_height = parseInt(getComputedStyle(map).height);
- // 食物的高度和宽度
- this.food_width = 20;
- this.food_height = 20;
- // 食物的位置, 先定义在左上角看看样子, 之后再搞随机位置
- // this.food_X = 0;
- // this.food_Y = 0;
- food(); // 生成食物
- // 初始化蛇身
- this.snake_width = 20;
- this.snake_height = 20;
- // 下面是一个 3 段的蛇, 第一个参数是水平位置, 第二个参数是垂直位置, 但三个参数是是否是头部
- this.snake_body = [[2, 0, true], [1, 0, false], [0, 0, false]];
- snake(); // 生成蛇身
- }
- // 生成蛇身
- function snake() {
- // 用 for 循环遍历数组, 将每一段作为一个 div 然后添加
- for(var i=0; i<this.snake_body.length; i++){
- // 创建蛇身
- var snakeBox = document.createElement('div');
- // 高度和宽度
- snakeBox.style.width = this.snake_width+"px";
- snakeBox.style.height = this.snake_height+"px";
- // 定位
- snakeBox.style.position = 'absolute';
- // 位置
- snakeBox.style.top = this.snake_body[i][1]*this.snake_width+"px";
- snakeBox.style.left = this.snake_body[i][0]*this.snake_height+"px";
- // 设置一个类名, 然后 css 给这个类定义样式
- snakeBox.className = "snake";
- // 追加到 map 中
- map.appendChild(snakeBox);
- }
- }
- // 生成食物
- function food() {
- // 创建食物
- var foodBox = document.createElement('div');
- foodBox.style.width = this.food_width+"px";
- foodBox.style.height = this.food_height+"px";
- // 食物的位置
- this.food_X = Math.floor(Math.random()*this.map_width/this.food_width); // 0~29 之间
- this.food_Y = Math.floor(Math.random()*this.map_height/this.food_height);
- foodBox.style.position = 'absolute';
- foodBox.style.top = this.food_X*this.food_width+"px"; // 随机数乘以宽度
- foodBox.style.left = this.food_Y*this.food_height+"px";
- // 设置一个类名, 然后 css 给这个类定义样式
- foodBox.className = "food";
- // 将食物追加到 map 中
- map.appendChild(foodBox);
- }
- </script>
移动蛇
首先来实现点击开始按钮, 开始游戏
开始按钮
点击开始按钮, 调用 start_game() 方法, 触发游戏运行, 并且变成暂停按钮. 点击暂停按钮, 调用 pause_game() 方法, 暂停游戏, 并且再变为开始按钮. 这里还获取了一个 status 变量, 可以随时改变它的 innerHtml 的内容, 在页面上显示一些信息, 调试的时候一些中间过程也可以实现在这里:
- var status = document.getElementsByClassName('status')[0];
- // 点击按钮
- var btn = document.getElementById('btn');
- btn.onclick = function btn() {
- if (this.innerHTML==="开始"){
- start_game();
- this.innerHTML = "暂停";
- } else {
- pause_game();
- this.innerHTML = "开始";
- }
- };
开始游戏
开始游戏就是启动一个定时器. 这里首先修改了 status 里显示的内容, 然后创建了一个定时器:
- // 开始游戏
- function start_game() {
- status.innerHTML = "游戏运行中";
- this.snakeMove = setInterval(move, 100);
- }
上面的定时器调用了 move, 下面就来写这个 move 方法. move 方法就是让蛇移动, 这里先让蛇一直往左移动. 具体的做法就是设置 snake_body 数组, 然后调用 snake() 方法, 生成一个新的蛇. 把之前的蛇清除了, 然后把新的蛇添加进去, 看上去取出蛇在移动的效果了:
- // 蛇移动的方法
- function move() {
- // 从尾端开始, 数组里的后一个值用数组的前一个值替代
- for(var i=this.snake_body.length-1; i>0; i--){
- this.snake_body[i][0] = this.snake_body[i-1][0];
- this.snake_body[i][1] = this.snake_body[i-1][1];
- }
- // 数组最前的那个值, 就是蛇头
- this.snake_body[0][0] += 1;
- // 重新生成蛇, 才能在页面上有变化
- // 先移除原有的蛇身体
- clearBox('snake'); // 这个方法可以复用, 只有要清除食物的时候也能用到
- // 然后绘制新的蛇身
- snake();
- }
- // 清除 Box
- function clearBox(class_name) {
- var box = document.getElementsByClassName(class_name);
- while(box.length){
- map.removeChild(box[0]);
- }
- }
这里移除蛇身体的方法, 之后要移除食物的时候也能用. 只要传入不同的类名, 就能把这个类的元素都清除了.
暂停游戏
暂停游戏就很简单了, 就是清除掉计时器就好了. 再点开始又会重新生成新的计时器:
- // 暂停游戏
- function pause_game() {
- // 暂停游戏就是清除定时器
- status.innerHTML = "游戏暂停...";
- clearInterval(this.snakeMove)
- }
键盘事件
在开始游戏的时候, 为键盘绑定事件:
- // 开始游戏
- function start_game() {
- status.innerHTML = "游戏运行中";
- this.snakeMove = setInterval(move, 100);
- // 绑定键盘按下的事件
- bindKeyDown();
- }
- // 键盘按下的事件
- function bindKeyDown() {
- window.onkeydown = function (ev) {
- status.innerHTML = ev.keyCode;
- }
- }
这里先看看 keyCode 这个属性的值. 按下不同的按钮, 值是不同的, 根据这个值来判断按的是哪个按钮, 然后触发响应的方法.
左 37, 上 38, 右 39, 下 40. 还有空格是 32.
根据方向来移动蛇
下面就来为这些事件写上不同的处理方法:
- // 键盘按下的事件
- function bindKeyDown() {
- window.onkeydown = function (ev) {
- // status.innerHTML = ev.keyCode;
- var code = ev.keyCode; // 获取按键
- switch (code){
- case 37:
- this.direction = 'left';
- break;
- case 38:
- this.direction = 'up';
- break;
- case 39:
- this.direction = 'right';
- break;
- case 40:
- this.direction = 'down';
- break;
- case 32:
- // 我这里还希望开始游戏后, 可以用空格控制暂停和开始
- btn(); // 前面的按钮事件没写
- break;
- }
- }
- }
这里只是修改了 this.direction 这个属性的值. 接下来要修改之前写的 move() 方法, 根据这个属性的值, 给出朝不同方法移动的效果. 这里有了方向这个概念, 在初始化的时候也要把方向属性进行初始化. 把下面这句添加到初始化函数 init() 中去:
this.direction = 'right';
最后来修改 move() 方法, 之前是默认向右移动的, 现在要根据 this.direction 的值来确定向哪里移动:
- // 蛇移动的方法
- function move() {
- // 从尾端开始, 数组里的后一个值用数组的前一个值替代
- for(var i=this.snake_body.length-1; i>0; i--){
- this.snake_body[i][0] = this.snake_body[i-1][0];
- this.snake_body[i][1] = this.snake_body[i-1][1];
- }
- // 根据方法来操作
- switch (this.direction){
- case 'left':
- this.snake_body[0][0] -= 1;
- break;
- case 'right':
- this.snake_body[0][0] += 1;
- break;
- case 'up':
- this.snake_body[0][1] -= 1;
- break;
- case 'down':
- this.snake_body[0][1] += 1;
- break;
- }
- // 数组最前的那个值, 就是蛇头
- // this.snake_body[0][0] += 1;
- // 重新生成蛇, 才能在页面上有变化
- // 先移除原有的蛇身体
- clearBox('snake'); // 这个方法可以复用, 只有要清除食物的时候也能用到
- // 然后绘制新的蛇身
- snake();
- }
优化移动方向
现在的蛇是自由移动的, 按照游戏规则, 蛇不不能直接掉头的. 所以要修改一个按键事件的 case 出的内容, 只有在特定的条件下才响应键盘方向的事件:
- // 键盘按下的事件
- function bindKeyDown() {
- window.onkeydown = function (ev) {
- // status.innerHTML = ev.keyCode;
- var code = ev.keyCode; // 获取按键
- switch (code){
- case 37:
- if (this.direction === 'up' || this.direction === 'down') {
- this.direction = 'left';
- }
- break;
- case 38:
- if (this.direction === 'left' || this.direction === 'right'){
- this.direction = 'up';
- }
- break;
- case 39:
- if (this.direction === 'up' || this.direction === 'down'){
- this.direction = 'right';
- }
- break;
- case 40:
- if (this.direction === 'left' || this.direction === 'right'){
- this.direction = 'down';
- }
- break;
- case 32:
- // 我这里还希望开始游戏后, 可以用空格控制暂停和开始
- btn(); // 前面的按钮事件没写
- break;
- }
- }
- }
移动方法
之前的 move() 方法只是让蛇动起来. 每次移动后, 当要判断一下当前的位置. 比如: 吃到食物了, 撞墙了或者撞到蛇身了.
吃食物
吃食物的逻辑放在蛇移动的 move() 方法里. 在计算好新的蛇身数组后, 增加判断蛇头位置的逻辑:
- // 蛇移动的方法
- function move() {
- // 前面是生成新的蛇身数组的代码
- // 判断蛇头吃食物
- if (this.snake_body[0][0]===this.food_X && this.snake_body[0][1]===this.food_Y){
- // status.innerHTML = "吃到食物了"
- clearBox("food"); // 移除食物
- food(); // 生成新的食物
- // 增加分数, 先把下面这句加到最上面的获取变量里
- // var scoreBox = document.getElementById('score');
- // 在去初始化函数 init() 里, 加一条初始化分数变量
- // this.score = 0;
- this.score += 1;
- scoreBox.innerHTML = this.score;
- // 增加蛇身长度
- // var snake_end = this.snake_body[this.snake_body.length-1]; // 这个是错误的
- var snake_end = Array.from(this.snake_body[this.snake_body.length-1]); // 这里需要深 copy
- this.snake_body.push(snake_end);
- }
- // 后面是移除原有蛇身, 然后绘制新蛇身的方法
- }
这里增加蛇蛇身长度的需要注意一下.
可以这么做, 把新生成的身体的一段放到一个临时的位置, 等移动一次以后, 就会变成正常的位置:
- var snake_end = [0, 0, false]
- this.snake_body.push(snake_end);
上面那么做肯定不好看, 最好是追加到生当前身体最后一段的位置上, 但是不能简单的向下面这么做:
- var snake_end = this.snake_body[this.snake_body.length-1]; // 这个是错误的
- this.snake_body.push(snake_end);
由于
this.snake_body[this.snake_body.length-1]
本身也是一个数组, 如果直接赋值给 snake_end 的话, 就是一个地址引用 (浅 copy). 这里要么获取到具体的值, 生成 snake_end 这个数组, 比如下面这样:
- var snake_last = this.snake_body[this.snake_body.length-1];
- var snake_end = [snake_last[0], snake_last[1], false];
- this.snake_body.push(snake_end);
或者就是像例子中那样要做一个数组的深 copy, 把数组里的值赋值给 snake_end. 而不是直接的赋值操作.
判断撞墙
我先在开是的位置增加一个变量 wall, 用来开启撞墙判断的功能, 这样如果只有不想撞墙的话, 还能方便的关掉:
- // 设置自定义的游戏参数
- var wall = true; // 开启撞墙
继续在 move() 方法里, 在判断吃食物的后面添加判断撞墙的逻辑. 撞墙之后, 就是
- // 蛇移动的方法
- function move() {
- // 判断蛇头吃食物
- // 判断撞墙
- wall && (
- this.snake_body[0][0]<0 || this.snake_body[0][0]>=this.map_width/this.snake_width ||
- this.snake_body[0][1]<0 || this.snake_body[0][1]>=this.map_height/this.snake_height
- ) && game_over();
- // 后面是移除原有蛇身, 然后绘制新蛇身的方法
- }
- // 游戏结束的方法
- function game_over() {
- status.innerHTML = "Game Over";
- clearInterval(this.snakeMove);
- alert("游戏结束 \ n 分数:"+this.score);
- clearBox('snake');
- clearBox('food');
- init();
- }
上面的 move() 函数里只复杂判断是否撞墙, 如果撞墙就调用 game_over() 方法.
撞到蛇身
先手动设置一个初始的长蛇身, 避免测试撞击蛇身之前要先玩一段时间的尴尬. 还是把这些自定义参数放到开头部分:
- // 设置自定义的游戏参数
- var snake_length = 15; // 正整数, 增加额外的蛇身体. 0 就是最小的 3 段, 1 就是额外增加 1 段.
- var wall_snake = true; // 开启撞击蛇身
- var wall = true; // 开启撞墙
修改一下初始化方法, 在初始化的时候就生成一个很长的蛇身体, 完整的初始化 init() 方法:
- // 初始化方法
- function init() {
- // 获取地图的跨度和高度
- this.map_width = parseInt(getComputedStyle(map).width);
- this.map_height = parseInt(getComputedStyle(map).height);
- // 初始化成绩和界面
- this.score = 0;
- scoreBox.innerHTML = this.score;
- btn.innerHTML = "开始";
- // 食物的高度和宽度
- this.food_width = 20;
- this.food_height = 20;
- // 食物的位置, 先定义在左上角看看样子, 之后再搞随机位置
- // this.food_X = 0;
- // this.food_Y = 0;
- food(); // 生成食物
- // 初始化蛇身
- this.snake_width = 20;
- this.snake_height = 20;
- // 下面是一个 3 段的蛇, 第一个参数是水平位置, 第二个参数是垂直位置, 但三个参数是是否是头部
- // 最后发现第三个参数并没有用
- this.snake_body = [[2, 0, true], [1, 0, false], [0, 0, false]];
- // 追加额外的蛇身
- if (typeof snake_length === 'number' && snake_length%1 === 0) {
- // 综合上面的 2 个条件, 可以判断 snake_length 是一个整数
- for (var i=0; i<snake_length; i++) {
- this.snake_body.push([0, 0, false])
- }
- }
- // 蛇移动的方向
- this.direction = 'right';
- snake(); // 生成蛇身
- }
好了, 现在不用玩了, 直接可以钻出一条大长龙.
然后是撞击蛇身的判断, 还是 move() 函数里, 放到之前撞墙的后面
- // 蛇移动的方法
- function move() {
- // 判断蛇头吃食物
- // 判断撞墙
- wall && (
- this.snake_body[0][0]<0 || this.snake_body[0][0]>=this.map_width/this.snake_width ||
- this.snake_body[0][1]<0 || this.snake_body[0][1]>=this.map_height/this.snake_height
- ) && game_over();
- // 判断撞到蛇身
- if (wall_snake) {
- for (var i=1; i<this.snake_body.length; i++) {
- this.snake_body[i][0] === this.snake_body[0][0] &&
- this.snake_body[i][1] === this.snake_body[0][1] &&
- game_over();
- }
- }
- // 后面是移除原有蛇身, 然后绘制新蛇身的方法
- }
这里没有什么难点. 可以去试着撞一下自己试试.
总结
上面也只是有个大概的样子. 基本的东西都搞出来了. 还有优化空间, 另外也还有些小 BUG 其实. 下面跳上最终我自己的完整的代码, 有兴趣的话自己修改:
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title > 贪吃蛇 </title>
- <style>
- *{padding: 0; margin: 0;}
- .title{text-align: center; margin: 10px 0;}
- #main{width: 800px; height: 600px; border:1px solid red; margin: 0 auto;}
- #main .left{width: 600px; height: 600px; float: left;
- position: relative;}
- /* 随机的食物通过 position 了定位的, 所以父标签要加上 position: relative*/
- #main .right{width: 200px; height: 100%; float: left; border-left: 1px solid red;
- box-sizing: border-box; text-align: center}
- /* 整体宽度包括 left 和 right 的以及内部元素的边框, 这样宽度不够, 会跑到下一行. 这里用了 box-sizing 解决问题 */
- #main .right h2{margin: 10px auto; text-align: center;}
- #main .right h2 #score{color: red;}
- #main .right button{width: 100px; height: 30px; font-size: 20px; margin-top: 30px;
- border: 0; border-radius: 5px;
- background-color: pink; color: green;}
- .food{background-color: black;}
- .snake{background-color: darkgreen}
- </style>
- </head>
- <body>
- <h2 class="title"> 贪吃蛇 </h2>
- <div id="main">
- <div class="left"></div>
- <div class="right">
- <h2 class="status"> 请点击开始 </h2>
- <h2 > 分数:<span id="score">0</span></h2>
- <button id="btn"> 开始 </button>
- </div>
- </div>
- <script>
- // 设置自定义的游戏参数
- var snake_length = 15; // 正整数, 增加额外的蛇身体. 0 就是最小的 3 段, 1 就是额外增加 1 段.
- var wall_snake = true; // 开启撞击蛇身
- var wall = true; // 开启撞墙
- // 获取变量
- var map = document.getElementsByClassName('left')[0];
- var status = document.getElementsByClassName('status')[0];
- var scoreBox = document.getElementById('score');
- var btn = document.getElementById('btn');
- // 初始化
- init();
- // 初始化方法
- function init() {
- // 获取地图的跨度和高度
- this.map_width = parseInt(getComputedStyle(map).width);
- this.map_height = parseInt(getComputedStyle(map).height);
- // 初始化成绩和界面
- this.score = 0;
- scoreBox.innerHTML = this.score;
- btn.innerHTML = "开始";
- // 食物的高度和宽度
- this.food_width = 20;
- this.food_height = 20;
- // 食物的位置, 先定义在左上角看看样子, 之后再搞随机位置
- // this.food_X = 0;
- // this.food_Y = 0;
- food(); // 生成食物
- // 初始化蛇身
- this.snake_width = 20;
- this.snake_height = 20;
- // 下面是一个 3 段的蛇, 第一个参数是水平位置, 第二个参数是垂直位置, 但三个参数是是否是头部
- // 最后发现第三个参数并没有用
- this.snake_body = [[2, 0, true], [1, 0, false], [0, 0, false]];
- // 追加额外的蛇身
- if (typeof snake_length === 'number' && snake_length%1 === 0) {
- // 综合上面的 2 个条件, 可以判断 snake_length 是一个整数
- for (var i=0; i<snake_length; i++) {
- this.snake_body.push([0, 0, false])
- }
- }
- // 蛇移动的方向
- this.direction = 'right';
- snake(); // 生成蛇身
- }
- // 点击按钮
- btn.onclick = function () {
- if (this.innerHTML==="开始"){
- start_game();
- this.innerHTML = "暂停";
- } else {
- pause_game();
- this.innerHTML = "开始";
- }
- };
- // 开始游戏
- function start_game() {
- status.innerHTML = "游戏运行中";
- this.snakeMove = setInterval(move, 100);
- // 绑定键盘按下的事件
- bindKeyDown();
- }
- // 键盘按下的事件
- function bindKeyDown() {
- window.onkeydown = function (ev) {
- // status.innerHTML = ev.keyCode;
- var code = ev.keyCode; // 获取按键
- switch (code){
- case 37:
- if (this.direction === 'up' || this.direction === 'down') {
- this.direction = 'left';
- }
- break;
- case 38:
- if (this.direction === 'left' || this.direction === 'right'){
- this.direction = 'up';
- }
- break;
- case 39:
- if (this.direction === 'up' || this.direction === 'down'){
- this.direction = 'right';
- }
- break;
- case 40:
- if (this.direction === 'left' || this.direction === 'right'){
- this.direction = 'down';
- }
- break;
- case 32:
- // 我这里还希望开始游戏后, 可以用空格控制暂停和开始
- btn(); // 前面的按钮事件没写
- break;
- }
- }
- }
- // 蛇移动的方法
- function move() {
- // 从尾端开始, 数组里的后一个值用数组的前一个值替代
- for(var i=this.snake_body.length-1; i>0; i--){
- this.snake_body[i][0] = this.snake_body[i-1][0];
- this.snake_body[i][1] = this.snake_body[i-1][1];
- }
- // 根据方法来操作
- switch (this.direction){
- case 'left':
- this.snake_body[0][0] -= 1;
- break;
- case 'right':
- this.snake_body[0][0] += 1;
- break;
- case 'up':
- this.snake_body[0][1] -= 1;
- break;
- case 'down':
- this.snake_body[0][1] += 1;
- break;
- }
- // 数组最前的那个值, 就是蛇头
- // this.snake_body[0][0] += 1;
- // 判断蛇头吃食物
- if (this.snake_body[0][0]===this.food_X && this.snake_body[0][1]===this.food_Y){
- // status.innerHTML = "吃到食物了"
- clearBox("food"); // 移除食物
- food(); // 生成新的食物
- // 增加分数, 先把下面这句加到最上面的获取变量里
- // var scoreBox = document.getElementById('score');
- // 在去初始化函数 init() 里, 加一条初始化分数变量
- // this.score = 0;
- this.score += 1;
- scoreBox.innerHTML = this.score;
- // 增加蛇身长度
- // var snake_end = this.snake_body[this.snake_body.length-1]; // 这个是错误的
- var snake_end = Array.from(this.snake_body[this.snake_body.length-1]); // 这里需要深 copy
- this.snake_body.push(snake_end);
- }
- // 判断撞墙
- wall && (
- this.snake_body[0][0]<0 || this.snake_body[0][0]>=this.map_width/this.snake_width ||
- this.snake_body[0][1]<0 || this.snake_body[0][1]>=this.map_height/this.snake_height
- ) && game_over();
- // 判断撞到蛇身
- if (wall_snake) {
- for (var i=1; i<this.snake_body.length; i++) {
- this.snake_body[i][0] === this.snake_body[0][0] &&
- this.snake_body[i][1] === this.snake_body[0][1] &&
- game_over();
- }
- }
- // 重新生成蛇, 才能在页面上有变化
- // 先移除原有的蛇身体
- clearBox('snake'); // 这个方法可以复用, 只有要清除食物的时候也能用到
- // 然后绘制新的蛇身
- snake();
- }
- // 游戏结束的方法
- function game_over() {
- status.innerHTML = "Game Over";
- clearInterval(this.snakeMove);
- alert("游戏结束 \ n 分数:"+this.score);
- clearBox('snake');
- clearBox('food');
- init();
- }
- // 清除 Box
- function clearBox(class_name) {
- var box = document.getElementsByClassName(class_name);
- while(box.length){
- map.removeChild(box[0]);
- }
- }
- // 暂停游戏
- function pause_game() {
- // 暂停游戏就是清除定时器
- status.innerHTML = "游戏暂停...";
- clearInterval(this.snakeMove)
- }
- // 生成蛇身
- function snake() {
- // 用 for 循环遍历数组, 将每一段作为一个 div 然后添加
- for(var i=0; i<this.snake_body.length; i++){
- // 创建蛇身
- var snakeBox = document.createElement('div');
- // 高度和宽度
- snakeBox.style.width = this.snake_width+"px";
- snakeBox.style.height = this.snake_height+"px";
- // 定位
- snakeBox.style.position = 'absolute';
- // 位置
- snakeBox.style.top = this.snake_body[i][1]*this.snake_width+"px";
- snakeBox.style.left = this.snake_body[i][0]*this.snake_height+"px";
- // 设置一个类名, 然后 css 给这个类定义样式
- snakeBox.className = "snake";
- // 追加到 map 中
- map.appendChild(snakeBox);
- }
- }
- // 生成食物
- function food() {
- // 创建食物
- var foodBox = document.createElement('div');
- foodBox.style.width = this.food_width+"px";
- foodBox.style.height = this.food_height+"px";
- // 食物的位置
- this.food_X = Math.floor(Math.random()*this.map_width/this.food_width); // 0~29 之间
- this.food_Y = Math.floor(Math.random()*this.map_height/this.food_height);
- foodBox.style.position = 'absolute';
- foodBox.style.top = this.food_Y*this.food_height+"px"; // 随机数乘以宽度
- foodBox.style.left = this.food_X*this.food_width+"px";
- // 设置一个类名, 然后 css 给这个类定义样式
- foodBox.className = "food";
- // 将食物追加到 map 中
- map.appendChild(foodBox);
- }
- </script>
- </body>
- </html>
来源: http://www.bubuko.com/infodetail-2637411.html