Home >Web Front-end >JS Tutorial >JavaScript writing Lianliankan mini game_javascript skills
I see other people playing Lianliankan every day, but they say they haven’t played it seriously. Why don’t they just connect two identical pictures together? I can write one myself.
I wrote one using Javascript and hosted it on github. View the online DEMO address: Open
Final rendering:
What should you consider before writing Lianliankan?
1: How to judge whether two elements can be connected? I was also confused at the beginning. You can refer to here: Open;
2: How to choose a template engine? I used the template of the bottom line library because the syntax is simple. I originally wanted to use Handlebars, but this is a bit big, and the bottom line library also provides many common tools and methods ( •̀ ω •́ )y;
3: How to lay out the layout? Use table, td and border. There is a div inside the border. The div is the chess piece of Lianliankan. The interface is more refreshing and simple. In fact, you can also write directly in canvas. I have not studied canvas seriously;
4: How do we achieve the connection effect when two elements are connected? If we use DOM to implement it, we need to use pictures. When the elements are connected, we need to position the pictures to the path of the connection. Or use canvas to draw the connection effect directly. I choose the latter;
Because I don’t consider low-end browsers, I used the zeptoJS library, and based on habit, I also referenced bootstrap;
Three main constructors are used, including Data, View, Score;
The structure of View is as follows, with relatively few things including event binding, interface generation, and the drawing effect when two identical elements disappear:
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>
The getImg method in the above code will call the getImg method of the global window. This method generates a picture string based on the data and is an auxiliary function:
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' />"; } };
Because Lianliankan’s data is a two-dimensional array, two for loops must be used in the template. The loops generate HTML strings. If the data and template are combined, the DOM structure in the figure below will be generated:
Score module constructor function Score, that’s all the code related to the score (pass the element in, directly call the addScore method of the generated instance, and the DOM will be automatically rendered). The reason for writing a separate constructor for the score is for decoupling:
Score = function(el) { this.el = $(el); this.score = 0; }; $.extend( Score.prototype , { /** * @desc 改变元素的HTML,递增分数; * @param * */ addScore : function() { this.el.html(++this.score); } });
Constructor Data, the main structure is as follows. Although there are relatively few methods, the code of Data actually occupies 300 lines.... To determine whether an element can be connected, use the canConnect method, and the canConnect method will call the dirConnect method. Calculate It’s quite complicated. If you want to know more, it’s best to write it yourself:
//新建初始化 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
All codes are as follows, for reference:
link 得分0
<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-4 result[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>
Online DEMO address view: Open
Found a Lianliankan written by someone else, with very little code. Let’s use it as a reference:
<!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>
The above is the entire content of this article, I hope you all like it.