这里有新鲜出炉的 Javascript 教程, 程序狗速度看过来!
JavaScript 客户端脚本语言
Javascript 是一种由 Netscape 的 LiveScript 发展而来的原型化继承的基于对象的动态类型的区分大小写的客户端脚本语言, 主要目的是为了解决服务器端语言, 比如 Perl, 遗留的速度问题, 为客户提供更流畅的浏览效果.
这篇文章主要介绍了 JavaScript 编写连连看小游戏的相关资料, 需要的朋友可以参考下
天天看到别人玩连连看, 表示没有认真玩过, 不就把两个一样的图片连接在一起么, 我自己写一个都可以呢.
使用 Javascript 写了一个, 托管到 github, 在线 DEMO 地址查看: http://sqqihao.github.io/games/link/index.html
最终的效果图:
写连连看之前要先考虑哪些呢?
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-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>
- </body>
- </html>
在线 DEMO 地址查看: http://sqqihao.github.io/games/link/index.html
找到了一个别人写的连连看, 代码极少, 作为参考吧:
- <!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>
来源: http://www.phperz.com/article/17/0411/270495.html