JavaScript編寫(xiě)連連看小游戲
天天看到別人玩連連看, 表示沒(méi)有認(rèn)真玩過(guò), 不就把兩個(gè)一樣的圖片連接在一起么, 我自己寫(xiě)一個(gè)都可以呢。
使用Javascript寫(xiě)了一個(gè), 托管到github, 在線DEMO地址查看:打開(kāi)
最終的效果圖:
寫(xiě)連連看之前要先考慮哪些呢?
1:如何判斷兩個(gè)元素可以連接呢, 剛剛開(kāi)始的時(shí)候我也納悶, 可以參考這里:打開(kāi);
2:模板引擎怎么選擇呢, 我用了底線庫(kù)的template,因?yàn)檎Z(yǔ)法簡(jiǎn)單。 本來(lái)想用Handlebars,但是這個(gè)有點(diǎn)大啊, 而且底線庫(kù)也提供很多常用工具方法( •̀ ω •́ )y;
3:布局如何布局呢, 用table, td加上邊框, 邊框內(nèi)部一個(gè)div,div就是連連看的棋子, 界面更清爽, 簡(jiǎn)單, 其實(shí)直接用canvas寫(xiě)也行, 沒(méi)認(rèn)真研究過(guò)canvas;
4:兩個(gè)元素連接時(shí)連線的效果我們要怎么實(shí)現(xiàn)呢,如果用dom實(shí)現(xiàn)那么需要用到圖片,元素連接時(shí)候把圖片定位到連接的路徑。 或者用canvas, 直接用canvas把連接的效果畫(huà)出來(lái), 我選擇后者;
因?yàn)槲也豢紤]低瀏覽器, 使用了zeptoJS庫(kù), 基于習(xí)慣,把bootstrap也引用了;
使用了三個(gè)主要構(gòu)造函數(shù), 包括Data, View, Score;
View的結(jié)構(gòu)如下, 東西比較少 包括事件綁定, 界面生成, 以及當(dāng)兩個(gè)相同元素消失時(shí)的 繪圖效果:
View
/** * @desc 根據(jù)數(shù)據(jù)生成map * */ renderHTML : function /** * @desc 界面的主要事件綁定 * @return this; * */ bindEvents : function /** * @desc 工具方法,在canvas上面進(jìn)行繪圖; * @param [{x:0,y:0},{x:1,y:1},{x:2,y:2},{x:3,y:3}]一個(gè)數(shù)組, 會(huì)自動(dòng)重繪; * */ showSparkLine : function tbody內(nèi)部元素的模板是這樣的: <script type="text/template" id="tr-td-tpl"> <% for(var i=0; i<data.length; i++) {%> <tr> <% for(var j=0; j< data[i].length; j++ ) { %> <td id="<%=i%><%=j%>" class="bg<%=data[i][j]%>" data-x="<%=j%>" data-y="<%=i%>" data-data="<%=data[i][j]%>" data-info='{"x":<%=[j]%>,"y":<%=[i]%>}'> <div> <%=getImg(data[i][j])%> </div> </td> <% } %> </tr> <% } %> </script>
上面代碼的getImg方法會(huì)調(diào)用全局window的getImg方法,這個(gè)方法是根據(jù)數(shù)據(jù)生成圖片字符串, 是一個(gè)輔助的函數(shù):
window.getImg = function( num ) { switch(num){ case 1: return "<img src='imgs/ani (1).gif' />"; case 2: return "<img src='imgs/ani (2).gif' />"; case 3: return "<img src='imgs/ani (3).gif' />"; case 4: return "<img src='imgs/ani (4).gif' />"; case 5: return "<img src='imgs/ani (5).gif' />"; case 6: return "<img src='imgs/ani (6).gif' />"; } };
因?yàn)檫B連看的數(shù)據(jù)是個(gè)二維的數(shù)組, 所以模板中必須使用兩個(gè)for循環(huán), 循環(huán)產(chǎn)生HTML字符串, 如果把數(shù)據(jù)和模板合在一起, 會(huì)生成下圖的DOM結(jié)構(gòu):
分?jǐn)?shù)模塊構(gòu)造函數(shù)Score, 所有有關(guān)得分的代碼就這些了 (把元素傳進(jìn)去, 直接調(diào)用生成實(shí)例的addScore方法, 會(huì)自動(dòng)渲染DOM), 為分?jǐn)?shù)單獨(dú)寫(xiě)一個(gè)構(gòu)造函數(shù)是因?yàn)闉榱私怦睿?/p>
Score = function(el) { this.el = $(el); this.score = 0; }; $.extend( Score.prototype , { /** * @desc 改變?cè)氐腍TML,遞增分?jǐn)?shù); * @param * */ addScore : function() { this.el.html(++this.score); } });
構(gòu)造函數(shù)Data, 主要的結(jié)構(gòu)如下 , 雖然方法比較少, 實(shí)際上Data這塊代碼占了300行.... 要判斷元素是否可以連接用canConnect方法,canConnect方法又會(huì)調(diào)用dirConnect方法, 計(jì)算比較繁瑣, 想了解的話最好自己寫(xiě)寫(xiě):
//新建初始化 newData : function //工具方法,隨機(jī)混肴數(shù)組; suffer : function /** * @desc set值,把地圖中對(duì)應(yīng)的數(shù)據(jù)清空或者設(shè)置,兩用接口 * @param x, y * @return chain * */ set : function /** * @desc 判斷兩個(gè)元素之間是否可以連接 * @param [{x:1,y:1},{x:1,y:1}] * @return false || [] * */ canConnect : function /** * @desc 判斷元素是否可以直連 * @param [{x:1,y:1},{x:1,y:1}] * @return false || true * */ dirConnect
所有所有代碼如下, 作為參考:
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <!-- 新 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" > <title>link</title> <script src="js/zepto.js"></script> <script src="js/underscore1.8.js"></script> <style> table{ border-collapse: collapse; } td{ border:1px solid #f5f5f5; text-align: center; line-height: 40px; cursor: pointer; } td.active{ opacity: 0.7; } td div{ width:40px; height:40px; } .bg1{ /*background: #2ECC71;*/ } .bg2{ /*background: #E67E22;*/ } .bg3{ /*background: #34495E;*/ } .bg4{ /*background: #1ABC9C;*/ } .relative{ position: relative; } .absolute{ position: absolute; left:0; top:0; } </style> </head> <body> <div class="container "> <div class="row" style="width:80%;margin:0 auto;"> <h3>得分<span class="label label-default" id="score">0</span></h3> </div> </div> <div class="container"> <div class="row relative"> <table class="absolute"> <thead></thead> <tbody id="tbody"> </tbody> </table> <canvas id="canvas"> <p>Your browserdoes not support the canvas element.</p> </canvas> </div> </div> <script type="text/template" id="tr-td-tpl"> <% for(var i=0; i<data.length; i++) {%> <tr> <% for(var j=0; j< data[i].length; j++ ) { %> <td id="<%=i%><%=j%>" class="bg<%=data[i][j]%>" data-x="<%=j%>" data-y="<%=i%>" data-data="<%=data[i][j]%>" data-info='{"x":<%=[j]%>,"y":<%=[i]%>}'> <div> <%=getImg(data[i][j])%> </div> </td> <% } %> </tr> <% } %> </script> <script> var el = document.getElementById("tbody"); var elCan = document.getElementById("canvas"); var tpl = document.getElementById("tr-td-tpl"); var cfg = { width : 8, height : 8 }; window.getImg = function( num ) { switch(num){ case 1: return "<img src='imgs/ani (1).gif' />"; case 2: return "<img src='imgs/ani (2).gif' />"; case 3: return "<img src='imgs/ani (3).gif' />"; case 4: return "<img src='imgs/ani (4).gif' />"; case 5: return "<img src='imgs/ani (5).gif' />"; case 6: return "<img src='imgs/ani (6).gif' />"; } }; var View = function(data, score) { this.data = data; this.score = score; }, Data = function(cfg) { this.cfg = { width : cfg.width+2, height : cfg.height+2 }; this.getRandom = this.getRandom(); }, Score = function(el) { this.el = $(el); this.score = 0; }; $.extend( Data.prototype, { /** * @desc 把兩個(gè) * @param HTMLELEMENT * @return true || false * */ clear : function(obj, target) { }, /** * @desc 根據(jù)this.cfg新建數(shù)據(jù)到this.map * @param void * @return void * */ newData : function() { var result = []; for(var i=0; i<=this.cfg.height+1; i++ ) { result[i] = result[i] || []; for(var j = 0; j<= this.cfg.width+1; j++) { if(i === 0 || j===0 || (i===this.cfg.height+1) || j === (this.cfg.width+1) ) { result[i][j] = 0; }else{ //1-4 result[i][j] = this.getRandom(); } }; }; this.map = result; return this; }, //隨機(jī)混肴數(shù)組; suffer : function(obj) { function random(min, max) { if (max == null) { max = min; min = 0; } return min + Math.floor(Math.random() * (max - min + 1)); }; var set = obj; var length = set.length; var shuffled = Array(length); for (var index = 0, rand; index < length; index++) { rand = random(0, index); if (rand !== index) shuffled[index] = shuffled[rand]; shuffled[rand] = set[index]; } return shuffled; }, /** * @return 返回值必須是成雙的, 消除到最后尼瑪,發(fā)現(xiàn)有一堆不匹配的,玩?zhèn)€球; * */ getRandom : function() { //如果消消樂(lè)是3*3, 那么你告訴我....最后一個(gè)和誰(shuí)消, 所以要做的就是把所有的元素生成變成一半,然后返回; var arr = new Array( (this.cfg.height) * (this.cfg.width) / 2 ); var result = []; for(var i=0; i<arr.length; i++ ) { arr[i] = (Math.floor( Math.random()*6 ) + 1); }; result = Array.prototype.concat.call( [] , arr, arr); result = this.suffer( result ); return function( ) { return result.pop(); }; }, /** * @desc set值 * @param x, y * @return chain * */ set : function( x, y) { this.map[y][x] = 0; return this; }, /** * @desc 判斷元素是否可以連接 * @param [{x:1,y:1},{x:1,y:1}] * @return false || true * */ canConnect : function(obj,target) { var map = this.map; //循環(huán)obj的y軸相等 , obj.x旁邊所有數(shù)據(jù)為0的元素;; var getX = function( obj ) { var result = []; //循環(huán)找出在X附近為0的元素; for(var i=obj.x+1; i< map[0].length; i++) { if( map[obj.y][i] == 0 ) { result.push( {x:i, y:obj.y} ); }else{ break; }; }; for(var i=obj.x-1; i>=0; i--) { if( map[obj.y][i] == 0 ) { result.push( {x:i,y:obj.y} ); }else{ break; }; }; return result; }; //循環(huán)obj的x軸相等, obj.y旁邊所有數(shù)據(jù)為0的元素; var getY = function(obj) { var result = []; for(var i=obj.y+1; i<map.length; i++) { if( map[i][obj.x] == 0) { result.push( { x : obj.x ,y : i} ); }else{ break; }; }; for(var i=obj.y-1; i>=0; i--) { if( map[i][obj.x] == 0 ) { result.push( { x : obj.x ,y : i} ); }else{ break; }; }; return result; }; var arr0 = Array.prototype.concat.call( [], getX(obj), obj, getY(obj)).filter(function(obj) { return !!obj; }); var arr1 = Array.prototype.concat.call( [], getX(target), target, getY(target) ).filter(function(obj) { return !!obj; }); for(i = 0; i<arr0.length; i++) { for(var j = 0; j<arr1.length; j++) { //只要有一個(gè)連接就返回true; if( this.dirConnect(arr0[i],arr1[j]) ) { return [obj, arr0[i], arr1[j], target]; }; }; }; return false; }, /** * @desc 判斷元素是否可以直接連接 * @param [{x:1,y:1},{x:1,y:1}] * @return false || true * */ dirConnect : function(obj, target) { var map = this.map; //row是x軸 列 //col是y軸 行 var min = 0, max = 0, sum = 0; if(obj.y === target.y) { if(obj.x < target.x) { min = obj.x; max = target.x; }else{ min = target.x; max = obj.x; }; for(var i=min; i<=max; i++) { sum += map[obj.y][i]; }; if(sum === (map[obj.y][obj.x] + map[target.y][target.x])) { return true; }else{ return false; }; }; if(obj.x === target.x) { if(obj.y < target.y) { min = obj.y; max = target.y; }else{ min = target.x; max = obj.y; }; for( i=min; i<=max; i++) { sum += map[i][obj.x]; }; if( sum === (map[obj.y][obj.x] + map[target.y][target.x])) { return true; }else{ return false; }; }; } }); $.extend( View.prototype, { /** * @desc 為view添加視圖的主元素 * @return void * */ setEL : function(el) { this.el = el; return this; }, setTpl : function(tpl) { this.tpl = _.template( tpl.innerHTML ); return this; }, /** * @desc 根據(jù)數(shù)據(jù)生成map * */ renderHTML : function() { $(this.el).html( this.tpl( {data : this.data.map} ) ); return this; }, /** * @desc 界面的主要事件綁定 * @return this; * */ bindEvents : function() { $(this.el).delegate("td", "click", this.click.bind(this)); return this; }, /** * @desc click事件, 單獨(dú)抽出來(lái)的; * */ click : function(ev) { //修改樣式; $("td.active").removeClass("active"); var target = $(ev.target).closest("td"); target.addClass("active"); //第一次點(diǎn)擊我們做的特殊處理; var prev = this.prev; if( !prev || target[0] === prev[0]){ this.prev = target; return; }; if( prev.attr("data-data") === target.attr("data-data")) { var xy = JSON.parse( prev.attr("data-info") ); var xxyy = JSON.parse( target.attr("data-info") ); //保存了連接的數(shù)組信息 var connectionInfo = [] || false; if( connectionInfo = this.data.canConnect( xy, xxyy) ) { this.showSparkLine( connectionInfo ); this.prev = undefined; this.data.set(xy.x, xy.y); this.data.set(xxyy.x, xxyy.y); this.score.addScore(); var _this = this; setTimeout(function() { _this.renderHTML(); },2000); }; prev.attr("data-data", ""); target.attr("data-data","") }else{ this.prev = target; }; }, /** * @desc 工具方法,在canvas上面進(jìn)行繪圖; * @param [{x:0,y:0},{x:1,y:1},{x:2,y:2},{x:3,y:3}]一個(gè)數(shù)組, 會(huì)自動(dòng)重繪; * */ showSparkLine : function( arr ) { arr = arr.map(function(xy) { return { x : (xy.x)*40 + 20, y : (xy.y)*40 + 20 } }); var elCan = document.getElementById("canvas"); function spark(ctx) { function showAndClear(arr, lineWidth) { ctx.clearRect(0,0,elCan.width,elCan.height); ctx.beginPath(); ctx.lineJoin = "round"; ctx.lineWidth = lineWidth; ctx.shadowColor = "rgba(241, 196, 15, 0.41)"; ctx.shadowOffsetX = 1; ctx.shadowOffsetY = 1; ctx.shadowBlur = 1; for(var i=0; i<arr.length-1; i++) { var xy = arr[i]; var nextXY = arr[i+1] ctx.moveTo(xy.x, xy.y); ctx.lineTo(nextXY.x, nextXY.y); }; ctx.stroke(); }; var ctx = elCan.getContext("2d"); ctx.strokeStyle = "#F1C40F"; var lineWidthArr = [1,2,1,2,1,3,1,0]; var len = lineWidthArr.length; var times = 400, addTimes = 200; while(len--) { (function(len){ setTimeout(function() { showAndClear(arr, lineWidthArr[len]); if(len==0) { ctx.clearRect(0,0,elCan.width,elCan.height); } }, times); times += addTimes; })(len) }; }; spark( elCan ); } }); $.extend( Score.prototype , { /** * @desc 改變?cè)氐腍TML,遞增分?jǐn)?shù); * @param * */ addScore : function() { this.el.html(++this.score); } }); $(function() { var score = new Score( document.getElementById("score") ); var data = new Data(cfg).newData(); var view = new View(data, score); view.setEL( el ).setTpl( tpl).renderHTML().bindEvents(); (function init() { //如果通過(guò)style屬性添加width或者h(yuǎn)eight,會(huì)根據(jù)原來(lái)的寬和高度自動(dòng)伸縮的 elCan.width = el.offsetWidth; elCan.height = el.offsetHeight; })(); }); </script> </body> </html>
在線DEMO地址查看:打開(kāi)
找到了一個(gè)別人寫(xiě)的連連看, 代碼極少, 作為參考吧:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title> 連連看 </title> <meta name="Generator" content="EditPlus"> <meta name="Author" content=""> <meta name="Keywords" content=""> <meta name="Description" content=""> <style type="text/css"> #board{width:508px; height:500px; margin: 30px auto 0px; overflow: hidden; position: relative; background-color: #999999;} #board span{display: block; position: absolute; width: 30px; height: 30px; } </style> </head> <body> <div id="board" > </div> </body> <!-- js --> <script src="http://cdn.bootcss.com/jquery/2.1.4/jquery.min.js"></script> <script type="text/javascript" > $(function(){ var cont=$("#board"); var colors=["#ff0000","#00ff00","#0000ff","#ffcc33","#000000","#00ffcc","#ffffff"]; var pos=[]; var click=0; var firstSpan; var fx; var fy; var arr=[]; arr=[0,0,0,0,0,0,0,0]; pos.push(arr); for(var i=0;i<8;i++){ new creSpan(i,cont,0,i*40,colors[6],0); } for(var i=1;i<=6;i++){ m=new creSpan(i,cont,i*40,0,"#ffffff"); arr=[0]; for(var j=0;j<6;j++){ var color=Math.floor(Math.random()*6); new creSpan(i,cont,i*40,(j+1)*40,colors[color],(color+1)); arr.push(1); } m=new creSpan(i,cont,i*40,(j+1)*40,"#ffffff",0); arr.push(0); pos.push(arr); } for(var i=0;i<8;i++){ m=new creSpan(i,cont,7*40,i*40,"#ffffff",0); } arr=[0,0,0,0,0,0,0,0]; pos.push(arr); function clear(c1,c2,x,y){ if(c1!=null)c1.style.background="#ffffff"; if(c2!=null){ c2.style.background="#ffffff"; pos[x-1][y-1]=0; pos[fx-1][fy-1]=0; } fx=0; fy=0; click=0; } $.each($("#board span"),function(index,mSpan){ $(this).click(function(){ var x=Math.floor(index/8); var y=Math.floor(index%8); if(click==0){ click=1; firstSpan=mSpan; fx=x; fy=y; return; } if(firstSpan.id!=mSpan.id||(x==fx&&fy==y)){ clear(null,null,0,0); return; } var col=6; var row=6; for(var i=0;i<row+2;i++){ var step=i-x>0?1:-1; var count=0; for(var j=x;j!=i;j+=step){ count+=pos[j][y]; } step=y>fy?-1:1; for(j=y;j!=fy;j+=step){ count+=pos[i][j]; } step=i>fx?-1:1; for(j=i;j!=fx;j+=step){ count+=pos[j][fy]; } if(count==1){ clear(firstSpan,mSpan,x,y); return; } } for(i=0;i<col+2;i++){ step=i-y>0?1:-1; count=0; for(j=y;j!=i;j+=step){ count+=pos[x][j]; } step=x>fx?-1:1; for(j=x;j!=fx;j+=step){ count+=pos[i][j]; } step=i<fy?1:-1; for(j=i;j!=fy;j+=step){ count+=pos[fx][j]; } if(count==1){ clear(firstSpan,mSpan,x,y); return; } } clear(null,null,0,0); }); }); }); function creSpan(n,cont,mtop,mleft,mcolor,idstr){ var mSpan=document.createElement("span"); cont[0].appendChild(mSpan); mSpan.id=idstr; with(mSpan.style){ top=mtop+"px"; left=mleft+"px"; background=mcolor; } }; </script> </html>
以上所述 就是本文的全部?jī)?nèi)容了,希望大家能夠喜歡。
相關(guān)文章
javascript基于原型鏈的繼承及call和apply函數(shù)用法分析
這篇文章主要介紹了javascript基于原型鏈的繼承及call和apply函數(shù)用法,結(jié)合實(shí)例形式較為詳細(xì)的分析了javascript中繼承的概念、創(chuàng)建方法以及call和apply函數(shù)的功能與使用技巧,需要的朋友可以參考下2016-12-12javascript學(xué)習(xí)筆記--數(shù)字格式類(lèi)型
很多人也許只知道 123,123.456,0xff 之類(lèi)的數(shù)字格式。 其實(shí) js 格式還有很多數(shù)字格式類(lèi)型,比如 1., .1 這樣的,也有 .1e2 這樣的。2014-05-05javascript上下方向鍵控制表格行選中并高亮顯示的方法
這篇文章主要介紹了javascript上下方向鍵控制表格行選中并高亮顯示的方法,涉及javascript針對(duì)鍵盤(pán)按鍵操作相應(yīng)的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-02-02JavaScript實(shí)現(xiàn)隨機(jī)替換圖片的方法
這篇文章主要介紹了JavaScript實(shí)現(xiàn)隨機(jī)替換圖片的方法,涉及javascript中Math.random方法的靈活運(yùn)用,需要的朋友可以參考下2015-04-04JavaScript學(xué)習(xí)筆記之函數(shù)記憶
這篇文章主要介紹了JavaScript學(xué)習(xí)筆記之函數(shù)記憶,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-09-09return false,對(duì)阻止事件默認(rèn)動(dòng)作的一些測(cè)試代碼
很明顯我們每個(gè)函數(shù)都返回false,如果返回值可以阻止事件默認(rèn)動(dòng)作,那么文本框?qū)o(wú)法輸入任何內(nèi)容。 看下面我測(cè)試的結(jié)果,注意紅的部分。2010-11-11