Javascript实现俄罗斯方块

前文

完成俄罗斯方块所需要完成的流程想起来比较简单,但是要考虑的事情并不算很少,边界的判断,到达底部的判断,消除的判断,消除后的处理都需要考虑在里面,一开始自己写的时候其实思维是很容易混乱的,所以这时候你要自己去捋顺了思路,画一个简易的流程图是很有必要的,同时要保证能够实现函数之间的低耦合高内聚,通俗的说,你要让自己的函数尽量只实现一个功能.

实现的功能截图

常规运行截图
在这里插入图片描述

流程图

说明:

这里每个生成的模型的移动是通过16宫格位置和模型相对16宫格位置重新定位实现的,里面有些英语单词拼错了,罪恶罪恶,sublime text3确实在代码补全方面挺头疼的

主流程图
主流程图
制作模型流程(createModel())
在这里插入图片描述

生成模型完成定位流程图(localtionBlocks())
在这里插入图片描述
定位完成的模型开始移动(autoDown())
在这里插入图片描述
onKeyDown

这里就不用流程图进行说明了,无法向上移动,向左是move(-1,0),向右是move(1,0),向下就是move(0,1),向上是变化就是函数rotate(),该函数的判断逻辑是生成一个新的模型对象,形状位置是即将变化后的形状位置,利用函数isMeet()判断,如果重叠了则无变化,没重叠则当前的模型位置和形状就是变化后的

模型样式

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码

整个代码的运行逻辑就是前面所放置的逻辑流程图,耐心看的话很容易理解。有关于一些常量没有说明,但是代码的注释说明清楚了,看看就行。

<!DOCTYPE html>
<html>
<head>
	<title>俄罗斯方块</title>
	<meta charset="utf-8">
	<style type="text/css">
		.container{
    
    
			left: 50px;
			height: 360px;
			width:200px;
			background-color: #000000;
			position:relative;

		}
		.activity_model{
    
    
			height: 20px;
			width: 20px;
			top:0px;
			left:0px;
			border-style:solid;
			border-width: 1px;
			border-color: #ffffff;
			background-color: #0000FE;
			position: absolute;
		}
		.fixed_model{
    
    
			height: 20px;
			width: 20px;
			top:0px;
			left:0px;
			border-style:solid;
			border-width: 1px;
			border-color: #333333;
			background-color: #fefefe;
			position: absolute;
		}
		.score{
    
    
			height: 100px;
			width: 300px;
			display: flex;
			flex-direction: column;
			justify-content: center;
			align-items: center;
		}
		.score_score{
    
    
			height: 50px;
			width: 300px;
			display: flex;
			flex-direction: row;
			justify-content: center;
			align-items: center;
		}
	</style>
	<script type="text/javascript" src="http://cdn.bootcss.com/lodash.js/4.16.6/lodash.min.js"></script>
	<script type="text/javascript">
		//分数
		var GAME_SCORE = 0;
		//暂停状态
		var CANCEL_STATE = false;
		//游戏速度
		var LEVEL = 1;
		//游戏默认得分
		var SCORE = 100;
		//常数值
		const STEP = 20;
		const ROW_COUNT = 18;
		const COL_COUNT = 10;

		//定时器
		var mInterval = null;

		//模型类
		const MODELS = [
			//L
			{
    
    
				0:{
    
    
					row:2,
					col:0
				},
				1:{
    
    
					row:2,
					col:1
				},
				2:{
    
    
					row:2,
					col:2
				},
				3:{
    
    
					row:1,
					col:2
				}
			},
			//凸形
			{
    
    
				0:{
    
    
					row:1,
					col:1
				},
				1:{
    
    
					row:0,
					col:0
				},
				2:{
    
    
					row:1,
					col:0
				},
				3:{
    
    
					row:2,
					col:0
				}
			},
			//田
			{
    
    
				0:{
    
    
					row:1,
					col:1
				},
				1:{
    
    
					row:2,
					col:1
				},
				2:{
    
    
					row:1,
					col:2
				},
				3:{
    
    
					row:2,
					col:2
				}
			},
			//一 形
			{
    
    
				0:{
    
    
					row:0,
					col:0
				},
				1:{
    
    
					row:0,
					col:1
				},
				2:{
    
    
					row:0,
					col:2
				},
				3:{
    
    
					row:0,
					col:3
				}
			},
			//Z形状
			{
    
    
				0:{
    
    
					row:1,
					col:1
				},
				1:{
    
    
					row:1,
					col:2
				},
				2:{
    
    
					row:2,
					col:2
				},
				3:{
    
    
					row:2,
					col:3
				}
			}
		]


		//当前使用的模型
		var currentModel = {
    
    }

		//16宫格的位置
		var currentX = 0;
		var currentY = 0;

		//所有块元素的位置 行_列:块元素
		var fixedBlocks = {
    
    }


		//初始化
		function init() {
    
    
			// body...
			createModel();
			onKeyDown();
		}

		//键盘事件监听
		function onKeyDown(){
    
    
			document.onkeydown = function (event){
    
    
			//console.log(event.keyCode)
				switch(event.keyCode){
    
    
					case 37:
						console.log("左");
						move(-1,0)
						break;
					case 38:
						console.log("上");
						rotate()
						break;
					case 39:
						console.log("右");
						move(1,0)
						break;
					case 40:
						console.log("下");
						move(0,1)
						break;
				}
			}
		}


		//创建所使用的模型
		function createModel(){
    
    
			//先判断游戏是否结束,结束了才能生成新的块元素进行使用
			if(isGameOver()){
    
    
				GameOver();
				return ;
			}

			//初始化16宫格
			currentX = 0;
			currentY = 0;
			//确定使用的模型类型
			currentModel = MODELS[_.random(0,MODELS.length - 1)]
			//生成对应数量的块元素
			for(let key in currentModel){
    
    
				var divEle = document.createElement("div");
				divEle.className = "activity_model"
				document.getElementById("container").appendChild(divEle);
			}
			//将生成的块元素进行定位
			localtionBlocks();
			//开始下落
			autoDown();
		}

		//根据模型来定位块元素的位置
		//完成定位的功能-移动模型就是进行不断的定位
		function localtionBlocks(){
    
    
			//检查一下当前的模型位置是否合理
			checkBound();
			//拿到所有的块元素
			let eles = document.getElementsByClassName("activity_model");
			for (var i = 0; i < eles.length; i++) {
    
    
				//单个块元素
				let activityModelEles = eles[i];
				//每个块元素对应的数据
				
				let blockmodel = currentModel[i]
				//每个块元素的位置由16宫格和自己相对的位置共同确定
				activityModelEles.style.top = (blockmodel.row + currentY) * STEP + "px";
				activityModelEles.style.left = (blockmodel.col + currentX) * STEP + "px";
			}
			// 找到每个块元素对应的数据
			// 根据数据指定位置
			
		}

		//移动
		function move(x,y){
    
    
			// let activity = document.getElementsByClassName("activity_model")[0]
			// //需要检查一下有没有越界,再者不能进行向上的移动
			// activity.style.top = parseInt(activity.style.top||0) + y*STEP + "px";
			// activity.style.left = parseInt(activity.style.left||0) + x*STEP + "px";
			// 进行移动的其实是整个模型-16宫格移动
			
			//移动前检查下下一个动作是否会导致无法移动
			
			if(isMeet(currentX + x,currentY+y,currentModel)){
    
    
				if(y!==0){
    
    
					fixedBottomModel();
				}
				return;
			}
			currentX+=x;
			currentY+=y;
			//根据16宫格位置定位块元素位置
			localtionBlocks();
		}

		//旋转
		function rotate(){
    
    
			//旋转后的行=旋转前的列
			//旋转后的列=3-旋转前的行
			//遍历当前使用的模型
			
			//旋转时也检查下每个元素是否会相撞
			//利用外置库创建一个新的模型对象
			let clonecurrentModel = _.cloneDeep(currentModel);



			for(let key in clonecurrentModel){
    
    
				//获取这个模型中的每一块
				let blockmodel = clonecurrentModel[key];
				let temp = blockmodel.row;
				blockmodel.row = blockmodel.col;
				blockmodel.col = 3 - temp;
			}

			if(isMeet(currentX,currentY,clonecurrentModel)){
    
    
				return;
			}

			currentModel = clonecurrentModel;
			//console.log(currentModel === clonecurrentModel);
			localtionBlocks();
		}

		//边界检测-控制模型在容器中
		function checkBound(){
    
    
			//定义模型可以活动的边界
			let left = 0;
			let right = COL_COUNT;
			let bottom = ROW_COUNT;
			//如果当前的模型超过了边界,所在的16宫格对应的后退一步
			for(let key in currentModel){
    
    
				let blockmodel = currentModel[key];
				//左
				if((blockmodel.col + currentX) < left){
    
    
					currentX++;
				}
				//右
				if((blockmodel.col + currentX) >= right){
    
    
					currentX--;
				}
				//底部
				if((blockmodel.row + currentY) >= bottom){
    
    
					currentY--;
					//然后模型不能动了
					fixedBottomModel();
				}
			}
		}

		//抵达底部的模型就固定
		function fixedBottomModel(){
    
    
			//改变模型的样式-改颜色
			//让模型无法移动-让16宫格也不动
			let eles = document.getElementsByClassName("activity_model");
			for (var i = eles.length - 1; i >= 0; i--) {
    
    
				//单个块元素
				let activityModelEles = eles[i];
				//每个块元素类名
				activityModelEles.className = "fixed_model";
				let blockmodel = currentModel[i];
				//将固定了的块元素进行记录
				fixedBlocks[(currentY + blockmodel.row)+"_"+(currentX + blockmodel.col)] = activityModelEles;
			}
			
			//固定模型后去判断是否有一行铺满了要进行清理
			isRemove();
			//创建新模型
			createModel();
		}

		//判断模型之间受否产生了碰撞
		function isMeet(x,y,model){
    
    
			//同一个位置只能有一个块元素
			//x,y:16宫格将要抵达的位置
			//model:模型元素将要完成的变化:旋转
			//查看在当前这个模型的每个位置上是否已经存在了元素
			for (var key in model) {
    
    
				var blockmodel = model[key];
				if(fixedBlocks[(y+blockmodel.row)+"_"+(x+blockmodel.col)]){
    
    
					return true;
				}
				//这里写了return false自然会出问题,因为第一个没有碰到就返回没碰到本身就不对
				//
			}
			return false
		}

		function isRemove(){
    
    
			//判断一行是否已经被铺满了,如果被铺满了就清楚当前行所有的元素
			for(let i = 0; i < ROW_COUNT; i++){
    
    
				let flag = true
				for(let j = 0; j < COL_COUNT; j++){
    
    
					if(!fixedBlocks[i+"_"+j]){
    
    
						flag = false
						break
					}
				}
				//判断当前行是否被铺满
				if(flag == true){
    
    
					//console.log("清除")
					removeline(i)
				}
			}
		}
		
		function removeline(line){
    
    
			for(let i = 0; i < COL_COUNT; i++){
    
    
				//删除改行的块元素
				document.getElementById("container").removeChild(fixedBlocks[line+"_"+i]);
				fixedBlocks[line+"_"+i] = null;
			}
			//清除当前行后,当前后上面的块元素要落到底部
			downline(line);
			//然后总得分增加
			score_count();
		}
		//清理行之上的元素下落
		function downline(line){
    
    
			for (let i = line - 1; i >= 0; i--) {
    
    
				for(let j = 0;j < COL_COUNT; j++){
    
    
					if(!fixedBlocks[i+"_"+j]){
    
    
						continue;
					}
					//对于每个清理行之上的元素增大top然后记录块元素的记录也要更改,改变索引名称  A->B  new C=A 则C->B 让A->null 就变成了C->B  
					//元素位置下落
					fixedBlocks[(i+1)+"_"+j] = fixedBlocks[i+"_"+j];
					//实现元素位置下落
					fixedBlocks[(i+1)+"_"+j].style.top = (i+1)*STEP + "px";
					//清除原来的块元素
					fixedBlocks[i+"_"+j] = null;
				}
			}
		}
		//模型自动降落
		
		function autoDown(){
    
    
			if(mInterval){
    
    
				clearInterval(mInterval);
			}
			mInterval = setInterval(function(){
    
    
				move(0,1);
			},1000/LEVEL)
		}

		//判断游戏结束
		function isGameOver(){
    
    
			//第0行存在块元素就结束了
			for(let i = 0; i < COL_COUNT; i++){
    
    
				if(fixedBlocks["0_"+i]){
    
    
					return true
				}
			}
			return false
		}
		//游戏结束
		function GameOver(){
    
    
			if(mInterval){
    
    
				clearInterval(mInterval);
			}
			alert("GameOver")
		}
		//开始游戏
		function start(){
    
    
			createModel();
			onKeyDown();
		}
		//重新开始游戏
		function restart(){
    
    
			//清楚所有的块元素然后继续
			//暂停状态变为没有暂停
			//计时器取消
			//当前的16宫格位置从开始开始
			window.location.reload();
		}
		//暂停游戏
		function cancel(){
    
    
			if(CANCEL_STATE){
    
    
				//如果暂停了,就继续
				autoDown();
				CANCEL_STATE = false;
				onKeyDown();
			}else{
    
    
				if(mInterval){
    
    
					clearInterval(mInterval);
				}
				CANCEL_STATE = true;

				//鼠标监听事件取消
				document.onkeydown = null;
			}
		}
		//改变游戏的速度
		function levelUP(){
    
    
			if(LEVEL <= 3){
    
    
				LEVEL++;
				autoDown();
				onKeyDown();
			}
		}
		function levelDOWN(){
    
    
			if(LEVEL >= 1){
    
    
				LEVEL--;
				autoDown();
				onKeyDown();
			}
		}
		//计算分数
		function score_count(){
    
    
			let leastscore = parseInt(document.getElementById("score").innerText);
			//console.log(leastscore);
			leastscore += LEVEL * SCORE;
			document.getElementById("score").innerText = leastscore;
		}
</script>
</head>
<body>
<!-- 背景容器 -->
	<div class="container" id="container">
		<!-- 块元素 -->
	</div>
	<div class="score">
		<div class="score_score">
			<p id="score">0</p>
			<p></p>
		</div>
		<div class="score_control">
			<button onclick="start()">开始</button>
			<button onclick="restart()">重新</button>
			<button onclick="cancel()">暂停</button>
			<button onclick="levelUP()">levelUP</button>
			<button onclick="levelDOWN()">levelDOWN</button>
		</div>
	</div>
</body>
</html>

猜你喜欢

转载自blog.csdn.net/qq_41199852/article/details/107721420