一、游戲簡介:
2048是一款休閑益智類的數字疊加小游戲
二、 游戲玩法:
在4*4的16宮格中,您可以選擇上、下、左、右四個方向進行操作,數字會按方向移動,相鄰的兩個數字相同就會合並,組成更大的數字,每次移動或合並后會增加一個數字。
當16宮格中沒有空格子,且四個方向都無法操作時,游戲結束。
三、 游戲目的:
目的是合並出2048這個數字,獲得更高的分數。
四、 游戲截圖:
五、先來玩一下:
六、游戲實現原理:
使用js、jQuery實現了PC版及手機版,實現原理是一樣的,只是移動端的一些樣式和上、下、左、右滑動的事件與PC端不一樣
1. 首先,把16宮格看成是矩陣的形式
2. 在html中給每個格子添加類名及屬性,來記錄每個格子的位置
類名item是每個格子的類名,emptyItem是空格子的類名,nonEmptyItem是非空格式的類名
3. 游戲開始時,隨機生成兩個數字,2或者4,出現在矩陣中任意位置
這部分是通過類名emptyItem及nonEmptyItem來實現的。
①. 隨機生成一個數字2或者4
②. 獲取所有空元素(類名emptyItem)
③. 隨機選擇一個空元素 將這個數字填充到空元素中,並將類名emptyItem移除,添加類名nonEmptyItem,即非空元素
④. 重復①、②、③步,再隨機生成一個數字填充一到隨機的位置
4. 游戲的核心在於移動
移動有四個方向:上、下、左、右,實現思路如下:
向左移動
移動或合並
遍歷所有非空元素
如果當前元素在第一個位置 則不動
如果當前元素不在每一個位置
當前元素左側是空元素 向左移動
當前元素左側是非空元素
左側元素和當前元素的內容不同 不動
左側元素和當前元素的內容相同 向左合並
是否產生新元素
所有非空元素中,有移動的 產生新元素
所有非空元素中,有合並的 產生新元素
向右移動
移動或合並
遍歷所有非空元素
如果當前元素在最后一個位置 則不動
如果當前元素不在最后一個位置
當前元素右側是空元素 向右移動
當前元素右側是非空元素
右側元素和當前元素的內容不同 不動
右側元素和當前元素的內容相同 向右合並
是否產生新元素
所有非空元素中,有移動的 產生新元素
所有非空元素中,有合並的 產生新元素
向上移動、向下移動 。。。。思路同上
5. 判斷游戲是否結束:
獲取所有元素
獲取所有非空元素
如果所有元素的個數 == 所有非空元素的個數
循環遍歷所有非空元素
上面元素存在 && 當前元素的內容 == 上面元素的內容 return
下面元素存在 && 當前元素的內容 == 下面元素的內容 return
左邊元素存在 && 當前元素的內容 == 左邊元素的內容 return
右邊元素存在 && 當前元素的內容 == 右邊元素的內容 return
所有元素,以上條件都不滿足 gameover
七、代碼實現
1. html部分
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> <head> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> <title>2048小游戲</title> <link rel="stylesheet" type="text/css" href="css/bootstrap.css"> <script type="text/javascript" src="js/jquery-2.1.1.min.js"></script> <script type="text/javascript" src="js/bootstrap.js"></script> <script type="text/javascript" src="js/game.js"></script> </head> <body> <div class="container"> <div class="main"> <!--《2048》一款益智休閑小游戲,聽說能拼出2048的全世界不超過3%哦!--> <div class="gameName">2048小游戲</div> <div class="maxScore">最高分: <span id="maxScore">1345612</span> </div> <div class="gameBody"> <div class="row"> <div class="item emptyItem x0y0" x="0" y="0"></div> <div class="item emptyItem x0y1" x="0" y="1"></div> <div class="item emptyItem x0y2" x="0" y="2"></div> <div class="item emptyItem x0y3" x="0" y="3"></div> </div> <div class="row"> <div class="item emptyItem x1y0" x="1" y="0"></div> <div class="item emptyItem x1y1" x="1" y="1"></div> <div class="item emptyItem x1y2" x="1" y="2"></div> <div class="item emptyItem x1y3" x="1" y="3"></div> </div> <div class="row"> <div class="item emptyItem x2y0" x="2" y="0"></div> <div class="item emptyItem x2y1" x="2" y="1"></div> <div class="item emptyItem x2y2" x="2" y="2"></div> <div class="item emptyItem x2y3" x="2" y="3"></div> </div> <div class="row"> <div class="item emptyItem x3y0" x="3" y="0"></div> <div class="item emptyItem x3y1" x="3" y="1"></div> <div class="item emptyItem x3y2" x="3" y="2"></div> <div class="item emptyItem x3y3" x="3" y="3"></div> </div> </div> <div class="gameRule">請按上、下、左、右鍵進行操作</div> <div class="scoreAndRefresh"> <div class="gameScore">得分:<span id="gameScore">0</span> 分</div> <button type="button" class="btn btn-danger refreshBtn"> <span class="glyphicon glyphicon-repeat"></span> </button> </div> <div class="modal fade" id="gameOverModal" aria-labelledby="myModalLabel" aria-hidden="true" data-backdrop="static"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"> × </button> <h4 class="modal-tittle" id="myModalLabel">2048小游戲</h4> </div> <div class="modal-body"> Game Over! </div> <div class="modal-footer"> <button type="button" class="btn btn-info" data-dismiss="modal">關閉</button> <button type="button" class="btn btn-danger refreshBtn">再玩一次</button> </div> </div> </div> </div> </div> </div> </body> </html>
2. css部分
<style type="text/css"> * { margin: 0; padding: 0; font-family: "YouYuan"; } body { background: lavenderblush; } .container { margin-top: 30px; } .main { width: 1000px; height: 100%; margin: 0 auto; overflow: hidden; text-align: center; } .main .gameName { font-size: 35px; font-weight: bold; } .main .maxScore { font-size: 20px; } .main .maxScore span { color: red; font-weight: bold; } .main .gameBody { width: 400px; height: 400px; margin: 0 auto; display: flex; flex-direction: column; justify-content: space-between; padding: 15px; background: #999; border-radius: 8px; } .main .gameBody .row { display: flex; justify-content: space-between; } .main .gameBody .row .item { width: 80px; height: 80px; border-radius: 10px; background: #fff; text-align: center; line-height: 80px; font-size: 30px; font-weight: bold; color: #666; font-family: "microsoft yahei"; } .main .gameRule { font-size: 20px; font-weight: bold; margin-top: 15px; } .main .gameScore { font-size: 20px; font-weight: bold; margin-top: 15px; } .main .gameScore span { color: red; font-size: 30px; } .main .scoreAndRefresh { display: flex; justify-content: space-around; width: 280px; margin: 0 auto; } .main .scoreAndRefresh .refreshBtn { height: 30px; margin-top: 22px; } .modal { margin-top: 7%; } .modal .modal-header h4 { text-align: left; font-weight: bold; } .modal .modal-dialog { width: 300px; margin: 0 auto; } .modal .modal-body { font-size: 18px; font-weight: bold; color: red; } #resetMaxScore { color: #fff; height: 30px; } </style>
3. js部分
$(function(){ //是否產生新元素 var isNewRndItme = false; var gameScore = 0; //最高分 var maxScore = 0; if(localStorage.maxScore) { maxScore = localStorage.maxScore - 0; }else { maxScore = 0; } //游戲初始化 gameInit(); //上、下、左、右監聽事件 $('body').keydown(function(e) { switch (e.keyCode) { case 37: // left console.log('left'); isNewRndItme = false; move('left'); isGameOver(); break; case 38: // up console.log('up'); isNewRndItme = false; move('up'); isGameOver(); break; case 39: // right console.log('right'); isNewRndItme = false; move('right'); isGameOver(); break; case 40: // down console.log('down'); isNewRndItme = false; move('down'); isGameOver(); break; } }); function refreshGame(){ var items = $('.gameBody .row .item'); for(var i = 0; i < items.length; i++) { items.eq(i).html('').removeClass('nonEmptyItem').addClass('emptyItem'); } gameScore = 0; //分數清零 $('#gameScore').html(gameScore); //隨機生成兩個新元素 newRndItem(); newRndItem(); //刷新顏色 refreshColor(); $('#gameOverModal').modal('hide'); } function getSideItem(currentItem, direction) { //當前元素的位置 var currentItemX = currentItem.attr('x') - 0; var currentItemY = currentItem.attr('y') - 0; //根據方向獲取旁邊元素的位置 switch (direction) { case 'left': var sideItemX = currentItemX; var sideItemY = currentItemY - 1; break; case 'right': var sideItemX = currentItemX; var sideItemY = currentItemY + 1; break; case 'up': var sideItemX = currentItemX - 1; var sideItemY = currentItemY; break; case 'down': var sideItemX = currentItemX + 1; var sideItemY = currentItemY; break; } //旁邊元素 var sideItem = $('.gameBody .row .x' + sideItemX + 'y' + sideItemY); return sideItem; } function itemMove(currentItem, direction) { var sideItem = getSideItem(currentItem, direction); if(sideItem.length == 0) {//當前元素在最邊上 //不動 }else if(sideItem.html() == '') { //當前元素不在最后一個且左(右、上、下)側元素是空元素 sideItem.html(currentItem.html()).removeClass('emptyItem').addClass('nonEmptyItem'); currentItem.html('').removeClass('nonEmptyItem').addClass('emptyItem'); itemMove(sideItem, direction); isNewRndItme = true; }else if(sideItem.html() != currentItem.html()) {//左(右、上、下)側元素和當前元素內容不同 //不動 }else {//左(右、上、下)側元素和當前元素內容相同 //向右合並 sideItem.html((sideItem.html() - 0) * 2); currentItem.html('').removeClass('nonEmptyItem').addClass('emptyItem'); gameScore += (sideItem.text() - 0) * 10; $('#gameScore').html(gameScore); // itemMove(sideItem, direction); maxScore = maxScore < gameScore ? gameScore : maxScore; $('#maxScore').html(maxScore); localStorage.maxScore = maxScore; isNewRndItme = true; return; } } function move(direction){ //獲取所有非空元素 var nonEmptyItems = $('.gameBody .row .nonEmptyItem'); //如果按下的方向是左或上,則正向遍歷非空元素 if(direction == 'left' || direction == 'up') { for(var i = 0; i < nonEmptyItems.length; i++) { var currentItem = nonEmptyItems.eq(i); itemMove(currentItem, direction); } }else if(direction == 'right' || direction == 'down') {//如果按下的方向是右或下,則反向遍歷非空元素 for(var i = nonEmptyItems.length -1; i >= 0; i--) { var currentItem = nonEmptyItems.eq(i); itemMove(currentItem, direction); } } //是否產生新元素 if(isNewRndItme) { newRndItem(); refreshColor(); } } function isGameOver(){ //獲取所有元素 var items = $('.gameBody .row .item'); //獲取所有非空元素 var nonEmptyItems = $('.gameBody .row .nonEmptyItem'); if(items.length == nonEmptyItems.length) {//所有元素的個數 == 所有非空元素的個數 即沒有空元素 //遍歷所有非空元素 for(var i = 0; i < nonEmptyItems.length; i++) { var currentItem = nonEmptyItems.eq(i); if(getSideItem(currentItem, 'up').length != 0 && currentItem.html() == getSideItem(currentItem, 'up').html()) { //上邊元素存在 且 當前元素中的內容等於上邊元素中的內容 return; }else if(getSideItem(currentItem, 'down').length != 0 && currentItem.html() == getSideItem(currentItem, 'down').html()) { //下邊元素存在 且 當前元素中的內容等於下邊元素中的內容 return; }else if(getSideItem(currentItem, 'left').length != 0 && currentItem.html() == getSideItem(currentItem, 'left').html()) { //左邊元素存在 且 當前元素中的內容等於左邊元素中的內容 return; }else if(getSideItem(currentItem, 'right').length != 0 && currentItem.html() == getSideItem(currentItem, 'right').html()) { //右邊元素存在 且 當前元素中的內容等於右邊元素中的內容 return; } } }else { return; } $('#gameOverModal').modal('show'); } //游戲初始化 function gameInit(){ //初始化分數 $('#gameScore').html(gameScore); //最大分值 $('#maxScore').html(maxScore); //為刷新按鈕綁定事件 $('.refreshBtn').click(refreshGame); //隨機生成兩個新元素 newRndItem(); newRndItem(); //刷新顏色 refreshColor(); } //隨機生成新元素 function newRndItem(){ //隨機生成新數字 var newRndArr = [2, 2, 4]; var newRndNum = newRndArr[getRandom(0, 2)]; console.log('newRndNum: ' + newRndNum); //隨機生成新數字的位置 var emptyItems = $('.gameBody .row .emptyItem'); var newRndSite = getRandom(0, emptyItems.length - 1); emptyItems.eq(newRndSite).html(newRndNum).removeClass('emptyItem').addClass('nonEmptyItem'); } //產生隨機數,包括min、max function getRandom(min, max){ return min + Math.floor(Math.random() * (max - min + 1)); } //刷新顏色 function refreshColor(){ var items = $('.gameBody .item'); for(var i = 0; i < items.length; i++) { // console.log(items.eq(i).parent().index()); switch (items.eq(i).html()) { case '': items.eq(i).css('background', ''); break; case '2': items.eq(i).css('background', 'rgb(250, 225, 188)'); break; case '4': items.eq(i).css('background', 'rgb(202, 240, 240)'); break; case '8': items.eq(i).css('background', 'rgb(117, 231, 193)'); break; case '16': items.eq(i).css('background', 'rgb(240, 132, 132)'); break; case '32': items.eq(i).css('background', 'rgb(181, 240, 181)'); break; case '64': items.eq(i).css('background', 'rgb(182, 210, 246)'); break; case '128': items.eq(i).css('background', 'rgb(255, 207, 126)'); break; case '256': items.eq(i).css('background', 'rgb(250, 216, 216)'); break; case '512': items.eq(i).css('background', 'rgb(124, 183, 231)'); break; case '1024': items.eq(i).css('background', 'rgb(225, 219, 215)'); break; case '2048': items.eq(i).css('background', 'rgb(221, 160, 221)'); break; case '4096': items.eq(i).css('background', 'rgb(250, 139, 176)'); break; } } } });
八、手機版2048
預覽:http://sandbox.runjs.cn/show/v9tribf5
源碼:http://runjs.cn/code/v9tribf5
九、移動端滑動事件
PC端2048和手機端2048的不同之處就是一些樣式和上、下、左、右滑動事件
移動端的滑動事件如下:
(function (){ mobilwmtouch(document.getElementById("gameBody")) document.getElementById("gameBody").addEventListener('touright',function (e){ e.preventDefault(); alert("方向向右"); }); document.getElementById("gameBody").addEventListener('touleft',function (e){ alert("方向向左"); }); document.getElementById("gameBody").addEventListener('toudown',function (e){ alert("方向向下"); }); document.getElementById("gameBody").addEventListener('touup',function (e){ alert("方向向上"); }); function mobilwmtouch(obj){ var stoux,stouy; var etoux,etouy; var xdire,ydire; obj.addEventListener("touchstart",function (e){ stoux=e.targetTouches[0].clientX; stouy=e.targetTouches[0].clientY; //console.log(stoux); },false); obj.addEventListener("touchend",function (e){ etoux=e.changedTouches[0].clientX; etouy=e.changedTouches[0].clientY; xdire=etoux-stoux; ydire=etouy-stouy; chazhi=Math.abs(xdire)-Math.abs(ydire); //console.log(ydire); if(xdire>0&&chazhi>0){ console.log("right"); //alert(evenzc('touright',alerts)); obj.dispatchEvent(evenzc('touright')); }else if(ydire>0&&chazhi<0){ console.log("down"); obj.dispatchEvent(evenzc('toudown')); }else if(xdire<0&&chazhi>0){ console.log("left"); obj.dispatchEvent(evenzc('touleft')); }else if(ydire<0&&chazhi<0){ console.log("up"); obj.dispatchEvent(evenzc('touup')); } },false); function evenzc(eve){ if (typeof document.CustomEvent === 'function') { this.event = new document.CustomEvent(eve, {//自定義事件名稱 bubbles: false,//是否冒泡 cancelable: false//是否可以停止捕獲 }); if(!document["evetself"+eve]){ document["evetself"+eve]=this.event; } } else if (typeof document.createEvent === 'function') { this.event = document.createEvent('HTMLEvents'); this.event.initEvent(eve, false, false); if(!document["evetself"+eve]){ document["evetself"+eve]=this.event; } } else { return false; } return document["evetself"+eve]; } } })()