热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

用原生JS写一个网页版的2048小游戏(兼容移动端)

这个游戏JS部分全都是用原生JS代码写的,加有少量的CSS3动画,并简单的兼容了一下移动端。先看一下在线的demo:https:yuan-yiming.github.io2048-onli

 这个游戏JS部分全都是用原生JS代码写的,加有少量的CSS3动画,并简单的兼容了一下移动端。

先看一下在线的demo:https://yuan-yiming.github.io/2048-online-game/

github地址:https://github.com/Yuan-Yiming/2048-online-game

 

 ====================================================================

 

下面简单分析一下JS代码:

1.游戏中包含的主要事件:

new game按钮的click事件:点击后重新开始一次新的游戏;

game over后重新开始按钮的click事件:点击后重新开始一次新的游戏;

桌面端键盘上的方向键keydown的事件:按下方向键后数字会向相应的方向移动;

移动端的touch事件:通过计算touchstart和touchend的坐标变化,判断手指在移动端屏幕的滑动方向;

 

2.业务逻辑

2.1 重新开始一次游戏:

需要清空游戏盘,并且随机给一个格子生成一个数字2,每次生成不同数字都会给格子配相应的背景颜色;

2.2 按下方向盘或滑动屏幕时格子移动情况(核心部分

游戏面板有4 x 4的格子,遍历每一行的每一个格子,若为空格则不作处理,非空格则要判断其下一步(注意每次只判断一步的动作,方法howToGo())的运动情况,即:

a:空格,不作处理

b:非空格,进一步判断下一步动作,有3种情况

  b1:若需要移动的格子前面是没有数字的空格,则格子往前移动一个,即将数字插入到前面格子内,原本格子中的数字销毁,然后从b开始循环再次判断;

  b2:若需要移动的格子前面不是空格,且与前面格子包含的数字不相等,则格子无需移动,该格子运动判断完毕,进行下一个格子的运动判断;

  b3:若需要移动的格子前面不是空格,且与前面格子包含的数字相等,则将前面格子内的数字 x 2,原本格子中的数字销毁,该格子运动判断完毕,进行下一个格子的运动判断;

注意:由于是每次运动都是由运动方向的最前面格子开始运动的,即b2,b3两种情况碰到前面有格子时,说明前面的格子已经完成运动,前面没有空位了,所以当前格子运动完成,继续移动后面的格子。

 

以下使用图片来说明格子运动的情况:

上面有两行格子,如果是向左运动,首先遍历第1行,对1~4号格子依次判断其运动情况,首先1号格子先运动,由于是1号格子靠边,可认为其前面是包含不同数字的格子,运动情况为b2,无需运动;2号格子为空格,运动情况为a;3号格子前面为空格,首先运动情况为b1,运动后3号格子的数字插到2号格子,3号格子数字销毁成为空格,然后需要对2号格子再次判断,再进行一次运动,即b3,使得1号格子数字变成4,2号格子成为空格;后面4号格子为空格不作处理。遍历第2行,对5~8号格子依次判断其运动情况,5号格子与1号格子情况相同,6、7、8号格子为空格,无需处理。

向左运动后的格子:

 

如果是向右运动,遍历第1行要从右边开始,4~1号格子依次运动,首先4号格子空格无需处理;然后3号格子运动情况b1,向前移动一格,数字2插入到4号格子内;2号空格无需处理;1号空格先连续进行两次b1运动,再进行一次b3运动,碰到4号格子内数字相同,最终使得4号格子内数字变成4。遍历第2行,8~5号格子依次运动,8、7、6号为空格均无需处理,5号格子连续3次b1运动,使得8号格子内数字变成2

向右运动后的格子:

 

向上或者向下运动同理。

 

=====================================================================

 

下面废话不说,直接上代码。

HTML代码:index.html



	
	
	
	
	
	


	
	
2048

Play 2048 Game Online!

Score
0+12
Best
1024+12
New Game

  

CSS桌面版代码:desktop.css

* {
	list-style: none;
	color: #8f7a66;
	padding: 0;
	margin: 0;
	box-sizing: border-box;
}
body {
	background-color: #ffffe0;
}

.wrapper {
	position: relative;
	width: 400px;
	height: 540px;
	/*border: 1px solid red;*/
	margin: 0 auto;
}
/*头部*/
.header {
	width: 400px;
	height: 140px;
	/*border: 1px solid green;*/
	position: relative;
	/*opacity: 0.4;*/
}
.title, .slogan, .score, .best, .new-game, .github>a {
	position: absolute;
}
.title strong {
	display: inline-block;
	width: 260px;
	height: 100px;
	font-size: 78px;
	line-height: 100px;
	/*text-align: center;*/
	padding: 0 5px;
	/*border: 1px solid black;*/
}
.slogan {
	padding: 0 5px;
	top: 85px;
	/*border: 1px solid black;*/
}
.github>a {
	display: inline-block;
	width: 50px;
	height: 50px;
	/*border: 1px solid red;*/
	top: 8%;
	right: 5%;
	/*margin: 5px;*/
	background: url("Git.png") no-repeat 0 0;
	border-radius: 50%;
	background-size: 100%;
}
.github>span>span {
	display: inline-block;
	width: 100px;
	height: 16px;
	line-height: 16px;
	position: absolute;
	bottom: -24px;
	left: -25px;
	text-align: center;
	color: #2c2c2c;
}


/*分数*/

/* 分数动画 */
.score-animation, .best-animation{
	display: none;
	position: absolute;
	top: 25px;
	left: 10px;
	width: 65px;
	height: 30px;
	font-size: 24px;
	font-weight: bold;
}
.score {
	left: 220px;
}
.best {
	left: 315px;
}
.score, .best {
	position: absolute;
	width: 85px;
	height: 60px;
	line-height: 28px;
	top: 20px;
	background-color: #bbada0;
}
.score span, .best span, .new-game span {
	color: #ffffff;
}
.score, .best, .new-game, .game-board, .grid {
	text-align: center;
	border-radius: 5px;
} 
.best .number, .score .number, .new-game {
	font-size: 22px;
	font-weight: bold;
}
.new-game {
	width: 180px;
	height: 40px;
	line-height: 40px;
	left: 220px;
	top: 90px;
	text-align: center;
	background-color: #8e7963;
	cursor: pointer;
}
.new-game:hover {
	width: 182px;
	height: 42px;
	line-height: 42px;
	left: 219px;
	top: 89px;
	font-size: 24px;
}



/*游戏主面板*/
.game-board {
	width: 400px;
	height: 400px;
	padding: 5px 5px;
	background-color: #bbada0;
	/*opacity: 0.4;*/
}
.grid {
	position: relative;
	float: left;
	width: 87.5px;
	height: 87.5px;
	line-height: 87.5px;
	/*font-size: 48px;*/
	font-weight: bold;
	margin: 5px;
	background-color: #b0c4de;
}
.game-board .grid span {
	/*color: */
	
}
/*game over or win the game弹出页面*/
.popup .game-over, .popup .win {
	position: absolute;
	left: 60px;
	text-align: center;
	width: 280px;
	height: 160px;
	border-radius: 5px;
	/*border: 1px solid red;*/
	opacity: 1.0;
}
.popup p {
	color: #8f7a66;
}
.popup .game-over {
	display: none;
	top: 230px;
	font-size: 36px;
	font-weight: bold;
}

.popup .win {
	display: none;
	top: 220px;
	font-size: 28px;
	font-weight: bold;
}

p.try-again {
	color: #fff;
	font-size: 22px;
	width: 150px;
	height: 50px;
	line-height: 50px;
	border-radius: 5px;
	margin: 0 auto;
	background-color: #8f7a66;
	cursor: pointer;
}
.header, .game-board {
	/*opacity: 0.4;*/
}

.new-grid {
	background-color: #b0c4de !important;
}

/* 生成新格子动画 */
@keyframes tempgrid {
	from {width: 45px; height: 45px; top: 24px;left: 24px; font-size: 18px; line-height: 45px;display: block;}
	to {width: 87.5px; height: 87.5px; top: 0px;left: 0px; font-size: 48px; line-height: 87.5px;display: none;}
}
@-webkit-keyframes tempgrid {
	from {width: 45px; height: 45px; top: 24px;left: 24px; font-size: 18px; line-height: 45px;display: block;}
	to {width: 87.5px; height: 87.5px; top: 0px;left: 0px; font-size: 48px; line-height: 87.5px;display: none;}
}
@-moz-keyframes tempgrid {
	from {width: 45px; height: 45px; top: 24px;left: 24px; font-size: 18px; line-height: 45px;display: block;}
	to {width: 87.5px; height: 87.5px; top: 0px;left: 0px; font-size: 48px; line-height: 87.5px;display: none;}
}
.temp-grid {
	animation: tempgrid 0.15s ease-in forwards;
	-webkit-animation: tempgrid 0.15s ease-in forwards;
	-ms-animation: tempgrid 0.15s ease-out forwards;
	-moz-animation: tempgrid 0.15s ease-out forwards;
}

  

CSS移动端兼容代码:mobile.css

body {
	background-color: #fffadd;
}

.wrapper {
	width: 100%;
	/*height: 540px;*/
	/*border: 1px solid red;*/
	/*margin: 0 auto;*/
}

/*头部*/
.header {
	width: 100%;
	/*height: 140px;*/
	position: relative;
}
.title, .slogan, .score, .best, .new-game {
	position: absolute;
	float: left;
	text-align: center;
}


.title, .slogan {
	width: 100%;
	
}

.title strong {
	display: inline-block;
	width: 100%;
	/*height: 260px;*/
	font-size: 487%;  /* 100% 16px */
	/*line-height: 192%;*/
	text-align: center;
	/*padding: 0 5px;*/
	/*border: 1px solid black;*/
}
.slogan {
	/*margin-top: 10px;*/
	top: 65%;
}

/* github */
/*.github>a {
	top: 4%;
	right: 6.25%;
}*/


/*分数*/
.score, .best, .new-game {
	width: 25%;
	/*border: 1px solid green;*/
}

/* 分数动画 */
.score-animation, .best-animation{
	display: none;
	/*position: absolute;*/
	top: 25px;
	left: 10px;
	width: 65px;
	height: 30px;
	font-size: 24px;
	font-weight: bold;
}

.score, .best {
	/*position: absolute;*/
	line-height: 28px;
	top: 90%;
	background-color: #bbada0;
}
.score {
	left: 47.5%;
}
.best {
	left: 75%;
}
.new-game {
	width: 45%;
	height: 60px;
	line-height: 60px;
	left: 0;
	top: 90%;
	text-align: center;
	background-color: #8e7963;
	cursor: pointer;
	/*padding-bottom: 2em;*/
	font-size: 28px;
}
.new-game:hover {
	width: 45%;
	/*height: 42px;*/
	/*line-height: 42px;*/
	height: 60px;
	line-height: 60px;
	left: 0;
	top: 90%;
	/*line-height: 2e;*/
	font-size: 28px;
}

.score span, .best span, .new-game span {
	color: #ffffff;
}
.score, .best, .new-game, .game-board, .grid {
	text-align: center;
	border-radius: 5px;
} 
.best .number, .score .number {
	font-size: 22px;
	font-weight: bold;
}

/*游戏主面板*/
.game-board {
	position: absolute;
	/*display: none;*/
	width: 100%;
	height: auto;
	/*height: 400px;*/
	/*padding: 10px 10px;*/
	background-color: #bbada0;
	/*opacity: 0.4;*/
	top: 36%;
}
.grid {
	/*position: relative;*/
	float: left;
	width: 22%;
	/*height: */
	/*padding-bottom: 21.95%;*/
	/*height: 100%;*/
	line-height: 80px;
	/*font-size: 48px;*/
	font-weight: bold;
	/*padding-bottom: 410px;*/
	padding: 1.5%;
	background-color: #b0c4de;
}

/* 生成新格子动画 */
@keyframes tempgrid {
	from {width: 50%; height: 50%; top: 25%;left: 25%; font-size: 24px; line-height: 192%;display: block;}
	to {width: 100%; height: 100%; top: 0px;left: 0px; font-size: 48px; line-height: 192%;display: none;}
}
@-webkit-keyframes tempgrid {
	from {width: 50%; height: 50%; top: 25%;left: 25%; font-size: 24px; line-height: 192%;display: block;}
	to {width: 100%; height: 100%; top: 0px;left: 0px; font-size: 48px; line-height: 192%;display: none;}
}
@-moz-keyframes tempgrid {
	from {width: 50%; height: 50%; top: 25%;left: 25%; font-size: 24px; line-height: 192%;display: block;}
	to {width: 100%; height: 100%; top: 0px;left: 0px; font-size: 48px; line-height: 192%;display: none;}
}

  

js代码:2048.js

// 页面加载时
window.Onload= function () {
	// var temp = tempGrid(2);
	giveNumber(2);
	newGameBotton();
	getReady();
	backgroundColorToNumber();
	scaleWidth();
	touch();
}


// 在移动端使得格子宽高比例1:1
function scaleWidth() {
	// 获取格子的宽度
	var grid = document.getElementsByClassName("grid"),
		width = window.getComputedStyle(grid[0], null)["width"];
		// width = grid[0].style.width;
	//给格子高度赋值
	for (var i = 0; i <16; i++) {
		grid[i].style.height = width;
	}	
}

// 创建一个临时的格子
function createTempGrid(num) {
		var temp = document.createElement("div");
		temp.innerHTML = "" + num + "";	
		temp.style.position = "absolute";
		temp.style.backgroundColor = "#fff8dc";
		temp.style.width = "87.5px";
		temp.style.height = "87.5px";
		temp.style.lineHeight = "87.5px";
		temp.style.fOntWeight= "bold";
		temp.style.fOntSize= "48px";
		temp.style.borderRadius = "5px";
		temp.style.top = "0";
		temp.style.left = "0";
		// temp.style.display = "none";
		// console.log(temp);
		temp.classList.add("temp-grid");
		return temp;
	};

// 删除临时格子
function deleteTempGrid() {
	var temp = document.getElementsByClassName("temp-grid");
	for (var i = 0; i ") {
			newGrid.classList.add("new-grid");
			newGrid.innerHTML = "" + num + "";
			newGrid.appendChild(tempGrid);
			break;
		}
	}
	// return blankGrid;
}

// clearGrid:清空格子及分数归零
function clearGrid() {
	var grid = document.getElementsByClassName("grid"),
		score = document.getElementsByClassName("score")[0].children[2];
	score.innerText = "0";
	for (var i = 0; i  1) {
					count += go;   // 累计每一次运动的得分
				}
			}
		} else if (dir == "down" || dir == "right") {
			for (var j = 2; j >= 0; j --) {
				max = 3 - j;
				go = howToGo(arr[j], dir, max);
				// gridMove(arr[j], dir, 1);
				// console.log("go:" + go);
				signal += go;
				if (go > 1) {
					count += go;   // 累计每一次运动的得分
				}
			}
		}
		
	}
	// 格子有运动signal > 0
	if (signal > 0) {
		// console.log("signal:" + signal);
		giveNumber(2);
		backgroundColorToNumber();
		testGameOver();
	}
	// 格子移动,且得分>0
	if (count > 0) {
		addScore(count);
	}
	return count;
}

// 移动端使用touch事件来监听滑块移动
function touch() {
	var gameBoard = document.getElementsByClassName("game-board")[0];
	gameBoard.addEventListener("touchstart",function (e) {
		
		// e.preventDefault();
		startX = e.changedTouches[0].pageX;
	    startY = e.changedTouches[0].pageY;
	},false);

	gameBoard.addEventListener('touchend',function(e){
		
		e.preventDefault();  // 阻止浏览器的默认行为,例如滚动、跳转等!!
		//获取滑动屏幕时的X,Y
		endX = e.changedTouches[0].pageX,
		endY = e.changedTouches[0].pageY;
		//获取滑动距离
		distanceX = endX-startX;
		distanceY = endY-startY;
		//判断滑动方向,滑动角度大于15°
		if(Math.abs(distanceX) / Math.abs(distanceY) > 1.73 && distanceX > 0){
		    deleteTempGrid();
		    keyDown(39);
		}else if(Math.abs(distanceX) / Math.abs(distanceY) > 1.73 && distanceX <0){
		    deleteTempGrid();
		    keyDown(37);
		}else if(Math.abs(distanceY) / Math.abs(distanceX) > 1.73 && distanceY <0){
		    deleteTempGrid();
		    keyDown(38);
		}else if(Math.abs(distanceY) / Math.abs(distanceX) > 1.73 && distanceY > 0){
		    deleteTempGrid();
		    keyDown(40);
		}else{
		    // console.log('点击未滑动');
		}
	});
}

// 3.记录分数,分数会增加,
function addScore(score) {
	var span = document.getElementsByClassName("number"),
		currentScore = parseInt(span[0].innerText),
		bestScore = parseInt(span[1].innerText);
	span[0].innerText = score + currentScore;
	scoreUpAnimaton("score", score);
	if (span[0].innerText > bestScore) {
		scoreUpAnimaton("best", score);
		span[1].innerText = span[0].innerText;
	}
}

// howToGoLeft(ele, direction, max):该函数判断单个格子怎么移动
function howToGo(ele, direction, max, testMode) {
	var prevGrid,
		prevGridNum,
		gridNum = 0,
		go,
		addNum,
		numLen,
		doubleNumGrid;
		// console.log(prevGrid);

	// 各个方向
	prevGrid = getPrevGrid(ele, direction);
	gridNum = getGridNum(ele);
	if (prevGrid) {
		prevGridNum = getGridNum(prevGrid);
	} else {
		prevGridNum = "null";
	}
	// 前面是空格,要继续判断。。。。。。。。。。。。。。。。。。。。。
	if (gridNum && !prevGridNum) {
		prevGrid.innerHTML = ele.innerHTML;
		ele.children[0].innerText = "";
		max -= 1;
		// gridMove(ele, direction, 1);
		if (max) {
			go = howToGo(prevGrid, direction, max);
			// 0、double、continue
		}
		// 返回1
		// console.log("go:" + (go || 1));
		// if (max == 0) {
		// 	console.log("before:" + typeof(go));
		// 	go = 1;
		// 	console.log("after" + typeof(go));
		// }

		return go || 1; 
		// 若go = 0,返回1;go = double,返回double,go = underfied,返回1

	// 和前面数字相同
	} else if (gridNum == prevGridNum) {
		if (!testMode) {
			gridNum *= 2;
			// addScore(gridNum);
			// gridMove(ele, direction, 1);
			prevGrid.children[0].innerText = gridNum + "";
			
			// 在这里添加数字变大的动画:
			// numLen = (gridNum + "").length;

			ele.children[0].innerText = "";
			// console.log('gridNum:' + gridNum)
			if (gridNum == 2048) {
				popup("win");
			}
			// 如果数字叠加,就返回得分,且得分≥4
		}
		// console.log("gridNum:  " + gridNum);
		return gridNum;
	} else {
		// 格子没动,返回0
		return 0;
	}
}


// 4.怎么判断game over,或者达到2048为winner
// test geme over
function testGameOver() {
	var content,
		leftTest,
		rightTest,
		upTest,
		downTest,
		count = 0;
		grid = document.getElementsByClassName("grid");
	for (var i = 0; i  

  

  


推荐阅读
  • 如何在HTML中获取鼠标的当前位置
    本文介绍了在HTML中获取鼠标当前位置的三种方法,分别是相对于屏幕的位置、相对于窗口的位置以及考虑了页面滚动因素的位置。通过这些方法可以准确获取鼠标的坐标信息。 ... [详细]
  • 移动端常用单位——rem的使用方法和注意事项
    本文介绍了移动端常用的单位rem的使用方法和注意事项,包括px、%、em、vw、vh等其他常用单位的比较。同时还介绍了如何通过JS获取视口宽度并动态调整rem的值,以适应不同设备的屏幕大小。此外,还提到了rem目前在移动端的主流地位。 ... [详细]
  • STL迭代器的种类及其功能介绍
    本文介绍了标准模板库(STL)定义的五种迭代器的种类和功能。通过图表展示了这几种迭代器之间的关系,并详细描述了各个迭代器的功能和使用方法。其中,输入迭代器用于从容器中读取元素,输出迭代器用于向容器中写入元素,正向迭代器是输入迭代器和输出迭代器的组合。本文的目的是帮助读者更好地理解STL迭代器的使用方法和特点。 ... [详细]
  • 本文介绍了Python字典视图对象的示例和用法。通过对示例代码的解释,展示了字典视图对象的基本操作和特点。字典视图对象可以通过迭代或转换为列表来获取字典的键或值。同时,字典视图对象也是动态的,可以反映字典的变化。通过学习字典视图对象的用法,可以更好地理解和处理字典数据。 ... [详细]
  • 使用nodejs爬取b站番剧数据,计算最佳追番推荐
    本文介绍了如何使用nodejs爬取b站番剧数据,并通过计算得出最佳追番推荐。通过调用相关接口获取番剧数据和评分数据,以及使用相应的算法进行计算。该方法可以帮助用户找到适合自己的番剧进行观看。 ... [详细]
  • 禁止程序接收鼠标事件的工具_VNC Viewer for Mac(远程桌面工具)免费版
    VNCViewerforMac是一款运行在Mac平台上的远程桌面工具,vncviewermac版可以帮助您使用Mac的键盘和鼠标来控制远程计算机,操作简 ... [详细]
  • Google Play推出全新的应用内评价API,帮助开发者获取更多优质用户反馈。用户每天在Google Play上发表数百万条评论,这有助于开发者了解用户喜好和改进需求。开发者可以选择在适当的时间请求用户撰写评论,以获得全面而有用的反馈。全新应用内评价功能让用户无需返回应用详情页面即可发表评论,提升用户体验。 ... [详细]
  • 解决VS写C#项目导入MySQL数据源报错“You have a usable connection already”问题的正确方法
    本文介绍了在VS写C#项目导入MySQL数据源时出现报错“You have a usable connection already”的问题,并给出了正确的解决方法。详细描述了问题的出现情况和报错信息,并提供了解决该问题的步骤和注意事项。 ... [详细]
  • 本文详细介绍了MySQL表分区的创建、增加和删除方法,包括查看分区数据量和全库数据量的方法。欢迎大家阅读并给予点评。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • PHPMailer邮件类邮件发送功能的使用教学及注意事项
    本文介绍了使用国外开源码PHPMailer邮件类实现邮件发送功能的简单教学,同时提供了一些注意事项。文章涵盖了字符集设置、发送HTML格式邮件、群发邮件以及避免类的重定义等方面的内容。此外,还提供了一些与PHP相关的资源和服务,如传奇手游游戏源码下载、vscode字体调整、数据恢复、Ubuntu实验环境搭建、北京爬虫市场、进阶PHP和SEO人员需注意的内容。 ... [详细]
  • ASP.NET2.0数据教程之十四:使用FormView的模板
    本文介绍了在ASP.NET 2.0中使用FormView控件来实现自定义的显示外观,与GridView和DetailsView不同,FormView使用模板来呈现,可以实现不规则的外观呈现。同时还介绍了TemplateField的用法和FormView与DetailsView的区别。 ... [详细]
  • 本文介绍了一些Java开发项目管理工具及其配置教程,包括团队协同工具worktil,版本管理工具GitLab,自动化构建工具Jenkins,项目管理工具Maven和Maven私服Nexus,以及Mybatis的安装和代码自动生成工具。提供了相关链接供读者参考。 ... [详细]
  • 本文介绍了使用Python编写购物程序的实现步骤和代码示例。程序启动后,用户需要输入工资,并打印商品列表。用户可以根据商品编号选择购买商品,程序会检测余额是否充足,如果充足则直接扣款,否则提醒用户。用户可以随时退出程序,在退出时打印已购买商品的数量和余额。附带了完整的代码示例。 ... [详细]
  • JS实现一键分享功能
    本文介绍了如何使用JS实现一键分享功能,并提供了2019独角兽企业招聘Python工程师的标准。同时,给出了分享到QQ空间、新浪微博和人人网的链接。 ... [详细]
author-avatar
Devil灬旋律
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有