js贪吃蛇详解

js贪吃蛇详解

终于写了心心念念的贪吃蛇 ,很早就听说过它的大名,一直没有去实现它,这次借着学习jq和面向对象的机会去实现了它 。 js写了注释 后面也有分析哦,不是很难一起看看吧~…
https://github.com/xingxinglieo/-demo

html和css代码

html css 先设置两个div是蛇头(蛇头一定要设z-index 否则可能被食物覆盖就不好了)和一个长度?身体 ;一个长度?身体被定在height:20px width:20px;游戏界面默认200px,300px 可调用时传参设置

<!DOCTYPE html>
<html lang="zh">
	<head>
		<meta charset="UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1.0">
		<meta http-equiv="X-UA-Compatible" content="ie=edge">
		<title>贪吃蛇小demo</title>
		<style type="text/css">
			* {
				padding: 0px;
				margin: 0px;
			}

			#contain {
				position: absolute;
				background-color: black;
				top: 50%;
				left: 50%;
				transform: translate(-50%,-50%);
			}

			.snakebody {
				height: 20px;
				width: 20px;
				position: absolute;
				background-color: white;
			}
			#food{
				height: 20px;
				width:20px;
				position: absolute;
				top: 40px;
				left: 220px;
				background-color: white;
			}
		</style>
	</head>
	<body>
		<div id="contain">
			<div id="food">
			</div>
			<div class="snakebody" style="z-index: 3;">
			</div>
			<div class="snakebody">
			</div>
		</div>
	</body>
	<script src="js/jquery-3.4.0.min.js" type="text/javascript" charset="utf-8"></script>
	<script src="js/main.js" type="text/javascript" charset="utf-8"></script>
	<script type="text/javascript">
		main(20,20,3);
	</script>
</html>

js代码

function main(wids, heit, speed) { //三个参数 游戏界面的宽度 高度 还有?的速度 
	window.direction = 1;//保存用户按下方向键 以控制?方向
    document.onkeydown = function(e) {
	switch (e.keyCode) {//选择判断防止调头
		case 37:
			direction = direction == 1 ? 1 : -1;
			break;
		case 38: //向上键
			direction = direction == wid ? wid : -wid;
			break;
		case 39: //右键
			direction = direction == -1 ? -1 : 1;
			break;
		case 40: //向下键
			direction = direction == -wid ? -wid : wid;
			break;
	}
}
	window.wid = wids;
	window.hei = heit; 
    //用户不传参 默认 一行30个 一列20个 宽高最小 15 速度提供1-5的选择 小数或超出 会判断
	window.speed = Math.floor(window.speed = speed ? speed : 3) > 5 ? 5 : speed < 1 ? 1 : speed;
	total = (wid = (wid = wid || 30) < 15 ? 15 : wid) * (hei = (hei = hei || 20) < 15 ? 15 : hei); //保证最低为15*15
	window.positions = 2 * wid + 10;
	window.markPosition = [2 * wid + 5, 2 * wid + 4]; //初始两个方块 在第三行 第5个第6个
	$('#contain').height(20 * hei).width(20 * wid).children('.snakebody').eq(0).css({//设置基础位置
		top: '40px',
		left: '100px',
		backgroundColor: 'skyblue'
	}).next().css({
		top: '40px',
		left: '80px'
	});
	snake.move();
};
game = {
	crashWall: function() {
		switch (direction) { //即将撞墙且没有改变撞墙的方向 返回ture
			case 1:
				if (markPosition[0] % wid == wid - 1) return true;
				break;
			case -1:
				if (markPosition[0] % wid == 0) return true;
				break;
			case wid:
				if (Math.floor(markPosition[0] / wid) == hei - 1) return true;
				break;
			case -wid:
				if (Math.floor(markPosition[0] / wid) == 0) return true;
				break;
		}
	},
	eatItselt: function() {//判断身体是否与头部位置相等
		if (markPosition.indexOf(markPosition[0], 1) != -1) return true;
	},
	eatFood: function() {
	//判断头部是否与食物位置相等 true 则数组增加一个数 增加一个蛇节
	//并重设食物位置和颜色
		if (markPosition[0] == positions) { //创建增加数组末尾一个数据等待在move
			markPosition[markPosition.length] = null;
			$('#contain').append('<div class="snakebody"></div>')
			while (markPosition.indexOf(positions = Math.floor(Math.random() * total)) != -1); //防止食物与?重叠
			$('#food').css({
				top: snake.toTop(positions),
				left: snake.toLeft(positions),
				backgroundColor: ('rgb(' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ',' + Math
					.floor(
						Math.random() * 255) + ')')
			})
			//食物的位置就是除和取余 背景颜色就是随机rgb ;
		}
	}


}
snake = {
	toTop: function(pos) {//返回设置top/left字符串值 用于处理markposition和positions中的数据
		return Math.floor(pos / wid) * 20 + 'px'
	},
	toLeft: function(pos) {
		return pos % wid * 20 + 'px'
	},
	move: function() {
		timeId = setInterval(function() {
			if (game.crashWall() || game.eatItselt()) {//判断是否撞墙或吃到自己
				clearInterval(timeId);
				alert('游戏结束了');
				return;
			}
			game.eatFood();//判断是否吃到食物
			var record = JSON.parse(JSON.stringify(markPosition)); //深拷贝数组
			markPosition[0] += direction; //第一个蛇节增加方向的值(上下为一行方块个数 左右为1),方向会改变
			$('.snakebody').eq(0).css({
				top: snake.toTop(markPosition[0]),
				left: snake.toLeft(markPosition[0])
			})
			for (let i = 1; i < markPosition.length; i++) {//让后面的蛇节的位置值等于前一个蛇节的位置值 实现移动
				markPosition[i] = record[i - 1];
				$('.snakebody').eq(i).css({
					top: snake.toTop(markPosition[i]),
					left: snake.toLeft(markPosition[i])
				})
			}
		}, (6 - speed) * 50)
	},
}
主体思路

用一个markposition 存储所有蛇节的位置,位置的数据本例是采用蛇节在界面的总位置(也可以采取存储x,y轴数据可能判断时候更方便一些),用direction 设置移动方向,判断?是否吃到自己 判断是否撞墙 判断是否吃到食物 ,吃到食物后生成新食物 蛇节增长

初始设置:main函数
function main(wids, heit, speed) { //三个参数 游戏界面的宽度 高度 还有?的速度 
	window.direction = 1;//保存用户按下方向键 以控制?方向
    document.onkeydown = function(e) {
	switch (e.keyCode) {//选择判断防止调头
		case 37:
			direction = direction == 1 ? 1 : -1;
			break;
		case 38: //向上键
			direction = direction == wid ? wid : -wid;
			break;
		case 39: //右键
			direction = direction == -1 ? -1 : 1;
			break;
		case 40: //向下键
			direction = direction == -wid ? -wid : wid;
			break;
	}
}
	window.wid = wids;
	window.hei = heit; 
    //用户不传参 默认 一行30个 一列20个 宽高最小 15 速度提供1-5的选择 小数或超出 会判断
	window.speed = Math.floor(window.speed = speed ? speed : 3) > 5 ? 5 : speed < 1 ? 1 : speed;
	total = (wid = (wid = wid || 30) < 15 ? 15 : wid) * (hei = (hei = hei || 20) < 15 ? 15 : hei); //保证最低为15*15
	window.positions = 2 * wid + 10;
	window.markPosition = [2 * wid + 5, 2 * wid + 4]; //初始两个方块 在第三行 第5个第6个
	$('#contain').height(20 * hei).width(20 * wid).children('.snakebody').eq(0).css({//设置基础位置
		top: '40px',
		left: '100px',
		backgroundColor: 'skyblue'
	}).next().css({
		top: '40px',
		left: '80px'
	});
	snake.move();
};

将传进来的参数设置在window里暴露给其他函数

其中

wid = (wid = wid || 30) < 15 ? 15 : wid

首先进行 括号内的|| 若用户传入参数 wids 上面window.wid 就被赋一个具体的值 否则是undefined若用户传参 则为参数值 若为传则默认30(这里忘了取整了) 为了防止用户传参过小 导致界面过小 影响体验 这里再进行一次选择判断 如果参数小于15 则为15 否则是原参数

direction 为方向标志 默认为1(向右) direction 赋值时判断 因为不能回头移动

如:

direction = direction == 1 ? 1 : -1;

设置位置标记数组默认有两个初始方块的数据

设置两个初始方块的位置(真是没事找事在js里面设置

最后调用?移动的方法

游戏逻辑
game = {
	crashWall: function() {
		switch (direction) { //即将撞墙且没有改变撞墙的方向 返回ture
			case 1:
				if (markPosition[0] % wid == wid - 1) return true;
				break;
			case -1:
				if (markPosition[0] % wid == 0) return true;
				break;
			case wid:
				if (Math.floor(markPosition[0] / wid) == hei - 1) return true;
				break;
			case -wid:
				if (Math.floor(markPosition[0] / wid) == 0) return true;
				break;
		}
	},
	eatItselt: function() {//判断身体是否与头部位置相等
		if (markPosition.indexOf(markPosition[0], 1) != -1) return true;
	},
	eatFood: function() {
	//判断头部是否与食物位置相等 true 则数组增加一个数 增加一个蛇节
	//并重设食物位置和颜色
		if (markPosition[0] == positions) { //创建增加数组末尾一个数据等待在move
			markPosition[markPosition.length] = null;
			$('#contain').append('<div class="snakebody"></div>')
			while (markPosition.indexOf(positions = Math.floor(Math.random() * total)) != -1); //防止食物与?重叠
			$('#food').css({
				top: snake.toTop(positions),
				left: snake.toLeft(positions),
				backgroundColor: ('rgb(' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ',' + Math
					.floor(
						Math.random() * 255) + ')')
			})
			//食物的位置就是除和取余 背景颜色就是随机rgb ;
		}
	}


}
是否over了判断

crashWall: 是否撞墙判断 如果用户在判断时处于墙边缘未改变要撞墙的方向 游戏结束

eatItselt:使用indexof方法从第二个数据检查数组有没有数据与第一个数据(头)相同的
相同则是头与身体重合 即吃到自己

吃到食物判断

蛇头位置等于食物位置(每生成一次食物需记录其位置)

生成食物

本质是吃到食物后改变原食物的坐标和颜色
需注意 食物不能与身体重叠 (本例有小概率与新增加的最后一节蛇尾重合bug 但是因为下一次移动?就移动开了 所以基本无法发现)

while 
(
markPosition.indexOf(positions = Math.floor(Math.random() * total))
 != -1); //防止食物与?重叠

while循环直至indexOf 不匹配数组中任何一个数据

?方法
snake = {
	toTop: function(pos) {//返回设置top/left字符串值 用于处理markposition和positions中的数据
		return Math.floor(pos / wid) * 20 + 'px'
	},
	toLeft: function(pos) {
		return pos % wid * 20 + 'px'
	},
	move: function() {
		timeId = setInterval(function() {
			if (game.crashWall() || game.eatItselt()) {//判断是否撞墙或吃到自己
				clearInterval(timeId);
				alert('游戏结束了');
				return;
			}
			game.eatFood();//判断是否吃到食物
			var record = JSON.parse(JSON.stringify(markPosition)); //深拷贝数组
			markPosition[0] += direction; //第一个蛇节增加方向的值(上下为一行方块个数 左右为1),方向会改变
			$('.snakebody').eq(0).css({
				top: snake.toTop(markPosition[0]),
				left: snake.toLeft(markPosition[0])
			})
			for (let i = 1; i < markPosition.length; i++) {//让后面的蛇节的位置值等于前一个蛇节的位置值 实现移动
				markPosition[i] = record[i - 1];
				$('.snakebody').eq(i).css({
					top: snake.toTop(markPosition[i]),
					left: snake.toLeft(markPosition[i])
				})
			}
		}, (6 - speed) * 50)
	},
}

totop/left 处理markposition 返回设置top/left所需字符串

重点
markPosition[0] += direction; //方向会改变
			$('.snakebody').eq(0).css({
				top: snake.toTop(markPosition[0]),
				left: snake.toLeft(markPosition[0])
			})
for (let i = 1; i < markPosition.length; i++) {
				markPosition[i] = record[i - 1];
				$('.snakebody').eq(i).css({
					top: snake.toTop(markPosition[i]),
					left: snake.toLeft(markPosition[i])
				})
			}

头增加direction值

?身体开始 后面的数值等于原数组前一个的数值(模拟移动)

需要一个新数组存储原数组数值

注意
1.var record = markPosition; 是不可以的 这个是浅拷贝数组 只是拷贝了数组的指向 如果markPosition[i] 中数据则也改变 record[i] 数据数据(他们是同一个数据

var record = JSON.parse(JSON.stringify(markPosition)); //深拷贝数组,参考自 [遇见小美好] (https://me.csdn.net/qq_37268201)

这样子才能新创一个数组完全拷贝其数据;

2.判断over 或者eat必须在前面 否则会出现慢半拍的bug

缩减没有帮助阅读的换行和坐标更换为x,y之后可以40行js(不传参可以30行)js代码内完成这个小游戏,只要肯多缩减,20多行的大佬级别也并不是遥不可及(毕竟思路是一样的,这个游戏挺适合新手上手的,我也是小菜鸟一枚,第一次就写写自己理解哦~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值