Welcome 微信登录

首页 / 脚本样式 / JavaScript / JavaScript编写连连看小游戏

天天看到别人玩连连看, 表示没有认真玩过, 不就把两个一样的图片连接在一起么, 我自己写一个都可以呢。
使用Javascript写了一个, 托管到github, 在线DEMO地址查看:打开
最终的效果图:


写连连看之前要先考虑哪些呢?
1:如何判断两个元素可以连接呢, 刚刚开始的时候我也纳闷, 可以参考这里:打开;
2:模板引擎怎么选择呢, 我用了底线库的template,因为语法简单。 本来想用Handlebars,但是这个有点大啊, 而且底线库也提供很多常用工具方法( •̀ ω •́ )y;
3:布局如何布局呢, 用table, td加上边框, 边框内部一个div,div就是连连看的棋子, 界面更清爽, 简单, 其实直接用canvas写也行, 没认真研究过canvas;
4:两个元素连接时连线的效果我们要怎么实现呢,如果用dom实现那么需要用到图片,元素连接时候把图片定位到连接的路径。 或者用canvas, 直接用canvas把连接的效果画出来, 我选择后者;
因为我不考虑低浏览器, 使用了zeptoJS库, 基于习惯,把bootstrap也引用了;
使用了三个主要构造函数, 包括Data, View, Score;
View的结构如下, 东西比较少 包括事件绑定, 界面生成, 以及当两个相同元素消失时的 绘图效果:
View

/** * @desc 根据数据生成map * */ renderHTML : function/*** @desc 界面的主要事件绑定* @return this;* */ bindEvents : function/*** @desc 工具方法,在canvas上面进行绘图;* @param [{x:0,y:0},{x:1,y:1},{x:2,y:2},{x:3,y:3}]一个数组, 会自动重绘;* */showSparkLine : function  tbody内部元素的模板是这样的:<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方法会调用全局window的getImg方法,这个方法是根据数据生成图片字符串, 是一个辅助的函数:
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" />";}};
因为连连看的数据是个二维的数组, 所以模板中必须使用两个for循环, 循环产生HTML字符串, 如果把数据和模板合在一起, 会生成下图的DOM结构:

分数模块构造函数Score,  所有有关得分的代码就这些了  (把元素传进去, 直接调用生成实例的addScore方法, 会自动渲染DOM), 为分数单独写一个构造函数是因为为了解耦:
 Score = function(el) { this.el = $(el); this.score = 0; };$.extend( Score.prototype , {/** * @desc 改变元素的HTML,递增分数; * @param * */addScore : function() {this.el.html(++this.score);}});
构造函数Data, 主要的结构如下 , 虽然方法比较少, 实际上Data这块代码占了300行.... 要判断元素是否可以连接用canConnect方法,canConnect方法又会调用dirConnect方法, 计算比较繁琐, 想了解的话最好自己写写:
//新建初始化newData : function//工具方法,随机混肴数组;suffer : function /*** @desc set值,把地图中对应的数据清空或者设置,两用接口 * @param x, y* @return chain* */set : function/** * @desc 判断两个元素之间是否可以连接* @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" href="http://cdn.bootcss.com/bootstrap/3.3.4/css/bootstrap.min.css"><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 把两个 * @param HTMLELEMENT * @return true || false * */clear : function(obj, target) {},/** * @desc 根据this.cfg新建数据到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-4result[i][j] = this.getRandom();}};};this.map = result;return this;},//随机混肴数组;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 返回值必须是成双的, 消除到最后尼玛,发现有一堆不匹配的,玩个球; * */getRandom : function() {//如果消消乐是3*3, 那么你告诉我....最后一个和谁消, 所以要做的就是把所有的元素生成变成一半,然后返回;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;//循环obj的y轴相等 , obj.x旁边所有数据为0的元素;;var getX = function( obj ) {var result = [];//循环找出在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;};//循环obj的x轴相等, obj.y旁边所有数据为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++) {//只要有一个连接就返回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 根据数据生成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事件, 单独抽出来的; * */click : function(ev) {//修改样式;$("td.active").removeClass("active");var target = $(ev.target).closest("td");target.addClass("active");//第一次点击我们做的特殊处理;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") );//保存了连接的数组信息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上面进行绘图; * @param [{x:0,y:0},{x:1,y:1},{x:2,y:2},{x:3,y:3}]一个数组, 会自动重绘;* */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 改变元素的HTML,递增分数; * @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() {//如果通过style属性添加width或者height,会根据原来的宽和高度自动伸缩的elCan.width = el.offsetWidth;elCan.height = el.offsetHeight;})();});</script></body></html>
在线DEMO地址查看:打开
找到了一个别人写的连连看, 代码极少, 作为参考吧:
<!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><scripttype="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>
以上所述 就是本文的全部内容了,希望大家能够喜欢。