Web开发项目 # 2048小游戏

游戏规则

每次控制所有方块向同一个方向运动,两个相同数字的方块撞在一起之后合并成为他们的和,每次操作之后会在空白的方格处随机生成一个2或者4,最终得到一个“2048”的方块就算胜利了。如果16个格子全部填满并且相邻的格子都不相同也就是无法移动的话,那么恭喜你gameover。

*上面似乎也很多细节没有提到(这个给用户看足够了,对开发者来说还有一些小细节)
比方说3个方块2,2,4,0要连续合并吗?最终结果是0,0,0,8?还是0,0,4,4(我们假定不连续合并,因为这样用户决策空间更大)
每次是只要有效滑动就生成新的方块吗?还是得发生碰撞消除才产生新的方块?(我假定有效滑动就生成新的方块)[不是按键就生成,有效滑动是指至少有一个方块移动了位置]
积分规则?假设产生合并出一个数值为n的方格则积分+n。(假设)


界面


index.html - 主页面
<header></header>之前的为

<section></section>之间的为

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title>2048</title>
	<link rel="stylesheet" type="text/css" href="./css/style.css">
	<script type="text/javascript" src="./js/jquery.js"></script>
	<script type="text/javascript" src="./js/init.js"></script>
	<script type="text/javascript" src="./js/active.js"></script>
</head>
<body>
	<header>
		<h1>2048</h1>
		<input type="button" value="New Game" id="start" class="btn">
		<form>
			<label>score:<input type="text" value="0"></label>
		</form>
	</header>
	<section id="container">
		<table class="board">
			<tr>
				<td></td>
				<td></td>
				<td></td>
				<td></td>
			</tr>
			<tr>
				<td></td>
				<td></td>
				<td></td>
				<td></td>
			</tr>
			<tr>
				<td></td>
				<td></td>
				<td></td>
				<td></td>
			</tr>
			<tr>
				<td></td>
				<td></td>
				<td></td>
				<td></td>
			</tr>
		</table>
	</section>
</body>
</html>

style.css - 页面样式
注意:

table{
	...
	position: relative;
}
td p{
	...
	position: absolute;
}

这里是为了后面JS实现的滑动动画效果(删去不会影响页面布局,但会影响后面的JS动画(失效)
这一部分代码是为了让中间的方块刚好填到对应位置

table{
	/*这里很重要*/
	border-spacing: 0;
}
td{
	width: 100px;
	height: 100px;
    padding: 0;
}
td p{
	margin: 0;
	padding: 0;
	width: 100px;
	height: 100px;
}

完整代码

body{
	margin: 0 auto;
	width: 100%;
	text-align: center;
}
header>input{
    background-color:#605546;
	font-size: 20px;
	color: white;
}
header>h1{
	margin: 10px 0px;
}
header form{
	margin-left: 0px;
}
header label{
	font-size: 25px;
}
header label>input{
	text-align: center;
	border: 0;
	width: 60px;
	font-size: 25px;
}
.btn{
	display: inline-block;
    padding: 6px 12px;
    font-size: 14px;
    line-height: 1.42;
    border: 1px solid transparent;
    border-radius: 8px;
}
table{
	margin: 0 auto;
	background-color:#bbada0;
	border-radius: 10px;
	position: relative;
	border-spacing: 0;
}
td{
	width: 100px;
	height: 100px;
    border-radius: 10px;
	float: left;
	background-color: #ccc0b3;
    text-align: center;
    font-size: 30px;
    margin: 10px;
    padding: 0;
}
td p{
	margin: 0;
	padding: 0;
	width: 100px;
	height: 100px;
	line-height: 90px;
	border-radius: 10px;
	position: absolute;
}

init.js - 初始化脚本
由于我们假设每次移动不能连续合并所以记一个has_conflicted数组标记当前位置方块是否是本次移动中已经合并过了。

var board = new Array(),
	score = 0,
	has_conflicted = new Array(),
	successString = "Success",
	gameOverString = "GameOver";

$(newGame); // 加载完成之后开始游戏
$(document).ready(function(){
	$("#start").click(function(){
		newGame();
	});
})

function newGame(){
	for(var i=0;i<4;i++){
		board[i] = new Array();
		has_conflicted[i] = new Array();
		for(var j=0;j<4;j++){
			board[i][j] = 0;
			has_conflicted[i][j] = false;
		}
	}
	updateBoardView(); //更新棋盘
	score = 0;
	updateScore(score);//更新分数
	//产生两个方格
	generate();
	generate();
}
function generate(){
	if(isNoSpace())return false;
	var empty = new Array();
	for(var i=0;i<4;i++){
		for(var j=0;j<4;j++)if(!board[i][j]){
			empty.push(i*4+j);
		}
	}
	var randNum = empty[Math.floor(Math.random()*empty.length)];
	var x = parseInt(randNum/4),y = parseInt(randNum%4);
	board[x][y] = Math.random()>0.5?4:2;
	showNewNum(x,y,board[x][y]);
	return true;
}
function showNewNum(i,j,num){
	var item = $("tr:eq("+i+")").children("td:eq("+j+")").append("<p>"+num+"</p>").children("p");
	item.css("background-color",getBackgroundColor(num));
	item.css("color",getColor(num));
	item.fadeIn("normal");
}
function updateBoardView(){
	$("td").empty();
	for(var i=0;i<4;i++){
		for(var j=0;j<4;j++){
			if(board[i][j]){
				var item = $("tr:eq("+i+")").children("td:eq("+j+")").append("<p>"+board[i][j]+"</p>").children("p");
				item.css("background-color",getBackgroundColor(board[i][j]));
				item.css("color",getColor(board[i][j]));
				item.show();
			}
			has_conflicted[i][j] = false;
		}
	}
}
function updateScore(){
	$("label>input").attr("value",""+score);
}
function isNoSpace(){
	for(var i=0;i<4;i++){
		for(var j=0;j<4;j++)
			if(!board[i][j])return false;
	}
	return true;
}
function getColor(num){
	if(num>4){
        return "snow";
    }
    else{
        return "#776e65";
    }
}
function getBackgroundColor(num){
	switch(num){
		case 2:return "#eee4da";break;
        case 4:return "#eee0c8";break;
        case 8: return '#f2b179'; break;
        case 16: return '#f59563'; break;
        case 32: return '#f67c5f'; break;
        case 64: return '#f65e3b'; break;
        case 128: return '#edcf72'; break;
        case 256: return '#edcc61'; break;
        case 512: return '#9c0'; break;
        case 1024: return '#33b5e5'; break;
        case 2048: return '#09c'; break;
 	}
 	return "black";
}

active.js - 游戏事件
$(document).keydown这里捕获了按键事件,拦截默认行为(只放行了F5便于测试)

var flag = true;
$(document).keydown(function(e){
    if($("label>input").text()==successString){newGame(); return;}
    e.preventDefault();
    if(e.keyCode == 116)window.location.href = window.location.href;//恢复F5便于测试
    if(move(e.keyCode)){
        setTimeout("generate()",200);
        setTimeout("checkGameStatus()",400);
    }
});
function move(dir){
    if(!isCanMove(dir))return false;
    var a,b,c,d;//start,end,step,enum
    if(dir==39||dir==40)a=2,b=-1,c=-1,d=3;//→/↓
    else a=1,b=4,c=1,d=0;//←/↑
    for(var i=0;i<4;i++){//行or列(对下面第一组case是行对第二组是列)
        for(var j=a;j!=b;j+=c)switch(dir){
            case 37:case 39:///if(board[i][j])for(var k=d;k!=j;k+=c)if(isNoBlockBtw(i,j,i,k)){
                if(!board[i][k]){
                    showMoveAnimation(i,j,i,k);
                    board[i][k]=board[i][j],board[i][j]=0;
                    break;
                }else if(board[i][k]==board[i][j]&&!has_conflicted[i][k]){
                    showMoveAnimation(i,j,i,k);
                    board[i][k]+=board[i][j],board[i][j]=0;
                    score+=board[i][k];
                    updateScore(score);
                    has_conflicted[i][k] = true;
                    break;
                }
            }
            break;
            case 38:case 40:///if(board[j][i])for(var k=d;k!=j;k+=c)if(isNoBlockBtw(j,i,k,i)){
                if(!board[k][i]){
                    showMoveAnimation(j,i,k,i);
                    board[k][i]=board[j][i],board[j][i]=0;
                    break;
                }else if(board[k][i]==board[j][i]&&!has_conflicted[k][i]){
                    showMoveAnimation(j,i,k,i);
                    board[k][i]+=board[j][i],board[j][i]=0;
                    score+=board[k][i];
                    updateScore(score);
                    has_conflicted[k][i] = true;
                    break;
                }
            }
            break;
        }
    }
    setTimeout("updateBoardView()",200);
    return true;
}

function isNoBlockBtw(x1,y1,x2,y2){
    if(x1 == x2){//row
        if(y1>y2){var tmp = y1;y1 = y2; y2 = tmp;}
        for(var i=y1+1;i<y2;i++)if(board[x1][i])return false;
    }else{//col
        if(x1>x2){var tmp = x1;x1 = x2; x2 = tmp;}
        for(var i=x1+1;i<x2;i++)if(board[i][y1])return false;
    }
    return true;
}
function showMoveAnimation(fromX,fromY,toX,toY){
    var item = $("tr:eq("+fromX+")").children("td:eq("+fromY+")").children("p");
    item.animate({top:(toX*(100+20)+12)+"px",left:(toY*(100+20)+12)+"px"},100);
}
function isCanMove(dir){
    var a,b,c;
    if(dir==39||dir==40)a=2,b=-1,c=-1;else a=1,b=4,c=1;
    for(var i=0;i<4;i++)for(var j=a;j!=b;j+=c){
        switch(dir){
            case 37:case 39:
            if(board[i][j]&&(board[i][j-c]==0||board[i][j]==board[i][j-c]))return true;
            break;
            case 38:case 40:
            if(board[j][i]&&(board[j-c][i]==0||board[j][i]==board[j-c][i]))return true;
            break;
        }
    }
    return false;
}
function checkGameStatus(){
    for(var i=0;i<4;i++)for(var j=0;j<4;j++)if(board[i][j]==2048){
        alert(successString);   newGame();  return;
    }
    if(isNoMove())gameOver();
}
function gameOver(){
    alert(gameOverString+"\n你的分数是:"+$("label>input").val());    newGame();
}
function isNoMove(){
    if(isCanMove(37)||isCanMove(38)||isCanMove(39)||isCanMove(40))return false;
    else return true;
}

Notice

1.<button> 标签定义一个按钮。在 button 元素内部,您可以放置内容,比如文本或图像。这是该元素与使用 input 元素创建的按钮之间的不同之处。<button>控件 与 <input type="button"> 相比,提供了更为强大的功能和更丰富的内容。<button></button>标签之间的所有内容都是按钮的内容,其中包括任何可接受的正文内容,比如文本或多媒体内容。例如,我们可以在按钮中包括一个图像和相关的文本,用它们在按钮中创建一个吸引人的标记图像。唯一禁止使用的元素是图像映射,因为它对鼠标和键盘敏感的动作会干扰表单按钮的行为。
请始终为按钮规定 type 属性。Internet Explorer 的默认类型是 “button”,而其他浏览器中(包括 W3C 规范)的默认值是 “submit”。
如果在 HTML 表单中使用 button 元素,不同的浏览器会提交不同的值。Internet Explorer 将提交<button></button> 之间的文本,而其他浏览器将提交 value 属性的内容。请在 HTML 表单中使用 input 元素来创建按钮。 (Ref.1~2)

2.jQuery $()函数传入回调函数时在document对象上绑定一个ready事件监听函数,当DOM结构加载完成的时候执行。(Ref.3)

3.$(…).empty() 删除匹配的元素集合中所有的子节点。

4.$("tr:eq("+i+")").children("td:eq("+j+")")这里是选中第i行第j列的单元格(Ref.5)

Reference

1.https://www.w3school.com.cn/tags/tag_button.asp
2.https://www.w3school.com.cn/tags/tag_input.asp
3.https://blog.csdn.net/qq_28775437/article/details/80321711
4.https://www.runoob.com/jquery/jquery-tutorial.html
5.https://www.w3school.com.cn/jquery/traversing_eq.asp
6.https://www.cnblogs.com/daysme/p/6272570.html
7.https://www.w3school.com.cn/cssref/pr_tab_border-spacing.asp


Copyright © 2019-2030 skysys All Rights Reserved. [转载需授权且标注出处]

发布了634 篇原创文章 · 获赞 579 · 访问量 35万+

猜你喜欢

转载自blog.csdn.net/qq_33583069/article/details/102963979