原生js实现别踩白块小游戏

原生js实现别踩白块小游戏

本文采用原生js及一小部分es6新语法实现简单的别踩白块小游戏

别踩白块演示

思路分析

古人云:工欲善其事,必先利其器

写代码也是一样的,写之前要先捋清楚实现的思路:

  1. 先在页面上准备好一个要显示的区域,并根据自己的想法写好一些基本的css来美化
  2. 点击开始游戏后《开始游戏》消失,并开始创建每一行并从上往下移动
  3. 每一行有一个不同颜色的可以点击,并且为之是随机的
  4. 点击方块后变色并加分,点击错误结束游戏

基本结构

HTML基本结构

<!-- 游戏容器 -->
<div class="box">
  <!-- 开始按钮 -->
  <div class="go"><a href="#">开始游戏</a></div>
  <!-- 游戏主体区域 -->
  <table></table>
</div>

CSS美化

* {
	margin: 0;
	padding: 0;
	text-decoration: none;
}
.box {
	position: relative;
	width: 400px;
	height: 600px;
	margin: 150px auto;
	border: 1px solid black;
	overflow: hidden;
}

.box .go {
	position: absolute;
	z-index: 9999;
	width: 100%;
	height: 100%;
	text-align: center;
	line-height: 600px;
}
.box .go a {
	font-size: 60px;
	color: black;
}
.box table {
	position: relative;
  /* 让游戏主体区域与容器一样大,并让主体区域向上走一个块的高度 */
	top: -150px;
	width: 100%;
	height: 100%;
	border-collapse: collapse;
}
.box table tr {
	display: flex;
	width: 100%;
	height: 150px;
}
.box table td {
  /* 一个块宽100px,高150px */
	width: 100px;
	height: 150px;
	box-sizing: border-box;
	border-left: 1px solid black;
	border-bottom: 1px solid black;
}
.box table td:first-child {
	border-left: none;
}

JavaScript基础结构获取

// 获取随机数
function getRandom(n, m) {
	return Math.floor(Math.random() * (m - n + 1)) + n;
}
// 获取游戏开始按键
const go = document.querySelector(".go");
// 获取游戏主体
const table = document.querySelector("table");
// 创建一个用来接收定时器id的变量
let timer;
// 分数
let score = 0;

开始游戏事件

// 获取游戏开始按键
const go = document.querySelector(".go");
// 游戏开始
function start() {
	// 游戏开始按钮添加事件
	go.addEventListener("click", () => {
		// 点击后隐藏按钮
		go.style.display = "none";
		// 调用移动函数让table动起来
		move(); // 这个函数下面会定义,可以先写上
	});
}
// 执行游戏函数
start();

游戏主体移动事件

移动思路分析

根据上面的基础结构,让游戏主体区域和容器一样大,让游戏主体区域先往上移动一个钢琴块的距离

调整游戏区域的top值,当他回到原来的位置时,让他重新往上移动一个钢琴块的距离

如下图所示,图片比较简略,可以反复观看

移动思路分析演示图

封装函数

根据上面的解析,我们封装一个move函数

// 移动
function move() {
	// 创建定时器
	timer = setInterval(() => {
		// 获取当前的top值并加上速度值
		table.style.top = parseInt(table.offsetTop) + 5 + "px";
		// 如果回到原位就重新再返回-150px的地方
		if (parseInt(table.offsetTop) >= 0) {
			table.style.top = "-150px";
			// 回到-150px位置之后才创建标签
			createTr(); // 这个函数下面会定义,可以先写上
		}
	}, 20);
}

当调用这个函数的时候,开启一个定时器,在定时器里写代码,每次执行的时候,获取当前游戏主体距离顶部的值,然后➕5,让他每次执行就让游戏主体往下移动5px,然后赋值给游戏主体的top属性,判断一下,如果他的顶部距离大于或等于0的时候,说明已经回到了原位,那么就让主体区域回到-150px的地方重新向下移动。至此,游戏区域的移动已经完成了,接下来就是让主体移动的时候添加一行要点击的黑块

至此,已经完成主体区域持续滚动

移动事件演示

动态创建方块

思路解析

别踩白块这个游戏是一行当中有很多个方块,然后其中一块是需要点击的黑块,那么我们需要做到的就是让js动态生成每一行,一行里面有n个方块,并让其中一个方块变色

封装函数

// 创建方块
function createTr() {
	// 创建行
	let tr = document.createElement("tr");
	// 创建随机数,随机让一个成为点击对象
	const random = getRandom(0, 3); // 这个随机数函数在最开始已经封装好了
	// 循环4次让一行有4个方块
	for (let i = 0; i < 4; i++) {
		// 一次循环创建一个td
		let td = document.createElement("td");
		// 将td插入tr
		tr.appendChild(td);
	}
	// 让随机一个方块变成黑色
	tr.children[random].style.backgroundColor = "black";
	// 给要点击的方块设置一个index
	tr.children[random].dataset.index = 1;
	// 循环完后在table的最前面插入
	table.insertBefore(tr, table.childNodes[0]);
}

每调用一次函数,就会动态生成一行,然后获取一个随机数备用,然后循环4次,创建4个td并插入tr里,插入后再通过上面获取的随机数,随机让一个方块变成黑色,并添加一个自定义属性标记一下这个需要点击的方块,方便后面的点击事件,所有属性添加完之后通过insertBefore插入到table的最前面,因为这个游戏是从顶部往下运动,新添加的永远都是在最上面,所以要用insertBefore

至此,已经完成每次滚动返回顶部时会插入一行,并添加一个随机的需要点击的方块

随机方块函数封装完成

为黑块添加事件

之前在生成方块的时候已经为一个随机的方块添加了一个自定义属性,判断点击的是否是那个自定义属性的方块,如果点击的方块上有那个自定义属性就改变颜色,并将自定义的属性值修改掉,并让分数++

// 点击方块事件
function clickFk(e) {
	// 使用事件委托
	// 判断点击的方块index是否为1
	if (e.target.dataset.index == 1) {
		// 点击后改变颜色
		e.target.style.backgroundColor = "gray";
		// 将index设置为0
		e.target.dataset.index = 0;
		// 分数+1
		score++;
	}
}

封装后回到move函数中,需要在move启动时调用这个函数

// 移动
function move() {
	// 创建定时器
	timer = setInterval(() => {
		// 获取当前的top值并加上速度值
		table.style.top = parseInt(table.offsetTop) + 5 + "px";
		// 如果回到原位就重新再返回-150px的地方
		if (parseInt(table.offsetTop) >= 0) {
			table.style.top = "-150px";
			// 回到-150px位置之后才创建标签
			createTr(); // 这个函数下面会定义,可以先写上
		}
	}, 20);
	// 添加事件委托
	table.addEventListener("click", clickFk); // 添加这行!!!!!!!!!
}

在原有的基础上为table添加事件监听

至此,持续滚动、随机创建需要点击的方块,点击变色也完成

点击事件

超出区域移除

可以发现,上面的代码虽然做到了持续滚动,但方块超出游戏主体区域后不会移除,所以我们还需要改造代码

根据上面的所有流程写下来,可以发现整个游戏主体是600px,然后一个方块是150px,一整个主体只能放4行,

每次生成的时候也需要一行,待删除的一行也算一行,所以一共是6行

// 移动
function move() {
	// 清除定时器
	clearInterval(timer);
	// 创建定时器
	timer = setInterval(() => {
		// 获取当前的top值并加上速度值
		table.style.top = parseInt(table.offsetTop) + 5 + "px";
		// 如果回到原位就重新再返回-150px的地方
		if (parseInt(table.offsetTop) >= 0) {
			table.style.top = "-150px";
			// 之后返回顶部后才会创建标签
			createTr();
		}
		// 如果当前容器里面达到6个就删除最后一个
		if (table.children.length >= 6) {
			// 删除最后一个元素
			table.children[table.children.length - 1].remove();
		}
	}, 20);
	// 添加事件委托
	table.addEventListener("click", clickFk);
}

根据上面描述,一共需要6行,那么只需要判断table下面是不是有6个子元素,当超过6个子元素时删除最后一个子元素

超过6个子元素,删除最后一个子元素

游戏终止条件

游戏终止有两种情况

  1. 当最后一行删除时,没被点击(自定义属性还在),则游戏结束
  2. 点击到没有自定义属性的方块上,也就是点错方块

没点方块情况

// 移动
function move() {
	// 清除定时器
	clearInterval(timer);
	// 创建定时器
	timer = setInterval(() => {
		// 获取当前的top值并加上速度值
		table.style.top = parseInt(table.offsetTop) + 5 + "px";
		// 如果回到原位就重新再返回-150px的地方
		if (parseInt(table.offsetTop) >= 0) {
			table.style.top = "-150px";
			// 之后返回顶部后才会创建标签
			createTr();
		}
		// 如果当前容器里面达到6个就删除最后一个
		if (table.children.length >= 6) {
			// 如果要删除的行中有一个没点击的,游戏结束
			if (Array.from(table.lastElementChild.children).some((ele) => +ele.dataset.index == 1)) {
				// 清除定时器
				clearInterval(timer);
				// 弹出游戏结束
				alert(`游戏结束,您的分数是:${score}`);
				// 清空整个table
				table.innerHTML = "";
				// 移除事件委托
				table.removeEventListener("click", clickFk);
				// 重新让开始游戏显示出来
				go.style.display = "block";
			}
      // 由于我在上方加了结束游戏后清空table
      // 清空后table没有东西,还运行下面代码的话会报错,所以用if判断table里是否还有东西
			if (table.children.length > 0) {
				// 删除最后一个元素
				table.children[table.children.length - 1].remove();
			}
		}
	}, 20);
	// 添加事件委托
	table.addEventListener("click", clickFk);
}

点错方块情况

// 点击方块事件
function clickFk(e) {
	// 使用事件委托
	// 判断点击的方块index是否为1
	if (e.target.dataset.index == 1) {
		// 点击后改变颜色
		e.target.style.backgroundColor = "gray";
		// 将index设置为0
		e.target.dataset.index = 0;
		// 分数+1
		score++;
	} else {
		// 如果点击的不是需要点击的方块,那么游戏直接结束
		// 清除定时器
		clearInterval(timer);
		// 弹出游戏结束
		alert(`游戏结束,您的分数是:${score}`);
		// 清空整个table
		table.innerHTML = "";
		// 移除事件委托
		table.removeEventListener("click", clickFk);
		// 重新让开始游戏显示出来
		go.style.display = "block";
	}
}

隐藏超出的区域

在css给最外层的盒子加一条overflow:hidden

将table的边框注释掉

此处就不演示了

完整代码

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
		<link rel="stylesheet" href="css/index.css" />
	</head>
	<body>
		<!-- 游戏容器 -->
		<div class="box">
			<!-- 开始按钮 -->
			<div class="go"><a href="#">开始游戏</a></div>
			<!-- 游戏主体区域 -->
			<table></table>
		</div>
		<script src="js/index.js"></script>
	</body>
</html>
* {
	margin: 0;
	padding: 0;
	text-decoration: none;
}
.box {
	position: relative;
	width: 400px;
	height: 600px;
	margin: 150px auto;
	border: 1px solid black;
	overflow: hidden;
}

.box .go {
	position: absolute;
	z-index: 9999;
	width: 100%;
	height: 100%;
	text-align: center;
	line-height: 600px;
}
.box .go a {
	font-size: 60px;
	color: black;
}
.box table {
	position: relative;
	/* 让游戏主体区域与容器一样大,并让主体区域向上走一个块的高度 */
	top: -150px;
	width: 100%;
	height: 100%;
	border-collapse: collapse;
	/* border: 1px solid red; */
}
.box table tr {
	display: flex;
	width: 100%;
	height: 150px;
}
.box table td {
	/* 一个块宽100px,高150px */
	width: 100px;
	height: 150px;
	box-sizing: border-box;
	border-left: 1px solid black;
	border-bottom: 1px solid black;
}
.box table td:first-child {
	border-left: none;
}
// 获取随机数
function getRandom(n, m) {
	return Math.floor(Math.random() * (m - n + 1)) + n;
}
// 获取游戏开始按键
const go = document.querySelector(".go");
// 获取游戏主体
const table = document.querySelector("table");
// 定时器id
let timer;
// 分数
let score = 0;
// 游戏开始
function start() {
	// 游戏开始按钮添加事件
	go.addEventListener("click", () => {
		// 点击后隐藏按钮
		go.style.display = "none";
		// 调用移动函数让table动起来
		move();
	});
}
// 执行游戏函数
start();
// 移动
function move() {
	// 清除定时器
	clearInterval(timer);
	// 创建定时器
	timer = setInterval(() => {
		// 获取当前的top值并加上速度值
		table.style.top = parseInt(table.offsetTop) + 5 + "px";
		// 如果回到原位就重新再返回-150px的地方
		if (parseInt(table.offsetTop) >= 0) {
			table.style.top = "-150px";
			// 之后返回顶部后才会创建标签
			createTr();
		}
		// 如果当前容器里面达到6个就删除最后一个
		if (table.children.length >= 6) {
			// 如果要删除的行中有一个没点击的,游戏结束
			if (Array.from(table.lastElementChild.children).some((ele) => +ele.dataset.index == 1)) {
				// 清除定时器
				clearInterval(timer);
				// 弹出游戏结束
				alert(`游戏结束,您的分数是:${score}`);
				// 清空整个table
				table.innerHTML = "";
				// 移除事件委托
				table.removeEventListener("click", clickFk);
				// 重新让开始游戏显示出来
				go.style.display = "block";
			}
			// 由于我在上方加了结束游戏后清空table
			// 清空后table没有东西,还运行下面代码的话会报错,所以用if判断table里是否还有东西
			if (table.children.length > 0) {
				// 删除最后一个元素
				table.children[table.children.length - 1].remove();
			}
		}
	}, 20);
	// 添加事件委托
	table.addEventListener("click", clickFk);
}
// 创建方块
function createTr() {
	// 创建行
	let tr = document.createElement("tr");
	// 创建随机数,随机让一个成为点击对象
	const random = getRandom(0, 3);
	// 循环4次让一行有4个方块
	for (let i = 0; i < 4; i++) {
		// 一次循环创建一个td
		let td = document.createElement("td");
		// 将td插入tr
		tr.appendChild(td);
	}
	// 让随机一个方块变成黑色
	tr.children[random].style.backgroundColor = "black";
	// 给要点击的方块设置一个index
	tr.children[random].dataset.index = 1;
	// 循环完后在table的最前面插入
	table.insertBefore(tr, table.childNodes[0]);
}
// 点击方块事件
function clickFk(e) {
	// 使用事件委托
	// 判断点击的方块index是否为1
	if (e.target.dataset.index == 1) {
		// 点击后改变颜色
		e.target.style.backgroundColor = "gray";
		// 将index设置为0
		e.target.dataset.index = 0;
		// 分数+1
		score++;
	} else {
		// 如果点击的不是需要点击的方块,那么游戏直接结束
		// 清除定时器
		clearInterval(timer);
		// 弹出游戏结束
		alert(`游戏结束,您的分数是:${score}`);
		// 清空整个table
		table.innerHTML = "";
		// 移除事件委托
		table.removeEventListener("click", clickFk);
		// 重新让开始游戏显示出来
		go.style.display = "block";
	}
}
© 版权声明
THE END
喜欢就支持一下吧
点赞12 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情图片

    暂无评论内容