拼圖游戲的代碼400行, 有點多了, 在線DEMO的地址是:打開;
因為使用canvas,所以某些瀏覽器是不支持的: you know;
為什么要用canvas(⊙o⊙)? 因為圖片是一整張jpg或者png, 我們要用把圖片導入到canvas畫布, 然后再調用上下文context的getImageData方法, 把圖片處理成小圖, 這些小圖就作為拼圖的基本單位;
如何判斷游戲是否結束, 或者說如何判斷用戶拼成的大圖是正確的? 我們就要在剛剛生成的小圖上面添加自定義屬性, 后期在小圖被移動后再一個個判斷,如果順序是對的, 那么這張大圖就拼接成功, 允許進入下一關;
游戲一共有四個關卡, 不會有人通關的,真的....因為第四關把圖片的寬高分別切成了6份, 看著都暈好吧(∩_∩);
因為要考慮到移動端的效果, 所以主界面圖片是根據屏幕適配, 拼圖大圖的大小是屏幕寬度和屏幕高度之間最小值的一半, 都是為了不出現滾動條。 比如:用戶的手機是橫屏模式, 這個橫屏的寬度是1000px,高度是300px, 如果我們把主圖片的寬設置為屏幕1000px的一半500, 那么垂直方向就出滾動條了;
用戶的事件只要考慮上下左右四個方向鍵即可, 要判斷圖片是否可以移動, 也要考慮到當圖片移動的時候的動畫效果 ,感興趣的話考慮我的實現, 和我寫的2048是一樣的道理;2048的DEMO;
如果用戶覺得這些圖片不好看, 甚至可以上傳自己手機的圖片, 瀏覽器要支持FileReader的API, 移動是基于webkit的內核,可以不用考慮兼容性;
代碼包含工具方法和一些基本的配置, 比如, 圖片地址的配置, 圖片要切成的塊數 , 加載圖片的工具方法等:
//游戲關卡的圖片和游戲每一個關卡要切成的圖片快個數 var levels = ["lake.jpg","cat.jpg","follower.jpg","view.jpg"]; var numbers = [3,4,5,6]; //工具方法 var util = { /** * @desc 圖片加載成功的話就執行回調函數 * @param 圖片地址 || 圖片的DataUrl數據; */ loadImg : function(e, fn) { var img = new Image; if( typeof e !== "string" ) { img.src = ( e.srcElement || e.target ).result; }else{ img.src = e; }; img.onload = function() {//document.body.appendChild( canvas ); //document.getElementById("content").appendChild( canvas ); fn&&fn(); }; } };
代碼是基于面向對象(oop), 包含了兩個類 :ClipImage 類, Block 類:
ClipImage類
/** * @desc 把圖片通過canvas切成一塊塊; */ function ClipImage(canvas , number) { }; $.extend(ClipImage.prototype, { /** * @desc 根據關卡把圖片canvas切成塊canvas * 然后渲染到DOM; * */ clip : function () { }, /** * @param 把canvas塊混排, 打亂排序; * */ //使用底線庫的方法shuffle打亂排序; random : function( ) { }, /** * @desc 把canvas渲染到DOM; * */ renderToDom : function () { }, updataDom : function(cav, obj) { this.updataMap(); $(cav).animate({top:obj.y*this.avH,left:obj.x*this.avW}); }, updataMap : function () { }, testSuccess : function () { } });
Block類
/** * @desc 對每一個canvas進行包裝; * @param canvas * @param left * @param top * @param avW * @param avH * @constructor Block */ var Block = function(canvas, left, top,avW, avH) { }; $.extend(Block.prototype, { /** * @desc 對每一個canvas進行定位, 然后添加到界面中; * */ init : function () { }, /** * @desc 對每一個canvas進行定位 * */ setPosition : function() { }, /** * @desc 向上移動會執行的函數 ,通過判斷maps下有沒有對應的key值判斷, 界面中的固定位置是否被占用; * */ upF : function(maps,numbers,cb) { }; }, /** * @desc 同上 * */ rightF : function(maps, numbers, cb) { }, /** * @desc 同上 * */ downF : function (maps,numbers,cb) { }, /** * @desc 同上 * */ leftF : function(maps,numbers,cb) { } });
為了考慮移動端,我們使用了zepto封裝的swipe系列事件, 默認并沒有這個模塊, 我們要通過script標簽引用進來, github的地址為 https://github.com/madrobby/zepto/blob/master/src/touch.js#files:
$(document).swipeLeft(function() { run(clipImage,"leftF") }).swipeUp(function() { run(clipImage,"upF") }).swipeRight(function() { run(clipImage,"rightF") }).swipeDown(function() { run(clipImage,"downF") });
雖然是一個小游戲,都是要考慮的東西真的不少,包括動畫效果, 是否可以移動, 更改數據模型, 是否成功進入下一個關卡等, 包含挺多的判斷;
全部代碼, 提供思路, 代碼可以作為參考:

<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css"/> <script src="http://cdn.bootcss.com/zepto/1.0rc1/zepto.min.js"></script> <script src="http://cdn.bootcss.com/underscore.js/1.8.3/underscore.js"></script> <style> body{ margin:0; } #content{ position: relative; margin:40px auto; } canvas{ border:1px solid #f0f0f0; box-shadow: 2px 2px 2px #eee; } </style> </head> <body> <input type="file" name="file" id="file"/> <div class="container"> <div class="row"> <div class="progress"> <div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" style="width: 25%"> <span class>當前是第<span id="now">1</span>關,共4關</span> </div> </div> </div> <div class="row"> <div id="content" class="clearfix "> </div> </div> </div> <script> (function(fn) { fn($); })(function($) { //這個canvas是緩存圖片用的; var canvas = document.createElement("canvas"); var minScreenWidth = Math.min( document.documentElement.clientWidth/ 2, document.documentElement.clientHeight/2 ); canvas.width = minScreenWidth; canvas.height = minScreenWidth; document.getElementById("content").style.width = minScreenWidth + "px"; //保存了所有的block; var blocks = []; //工具方法 var util = { /** * @desc 圖片加載成功的話就執行回調函數 * @param 圖片地址 || 圖片的DataUrl數據; */ loadImg : function(e, fn) { var img = new Image; if( typeof e !== "string" ) { img.src = ( e.srcElement || e.target ).result; }else{ img.src = e; }; img.onload = function() { //canvas.width = img.width; //canvas.height = img.height; canvas.getContext("2d").drawImage( img, 0, 0 ,canvas.width, canvas.height); //document.body.appendChild( canvas ); //document.getElementById("content").appendChild( canvas ); fn&&fn(); }; } }; //綁定事件; function bindEvents () { var file = $("#file"); file.bind("change", function(ev) { var reader = new FileReader; reader.onload = function(e) { util.loadImg(e, function() { window.clipImage = new ClipImage(canvas, numbers[window.lev]); window.clipImage.random(); Controller( window.clipImage, numbers[window.lev]); }); }; reader.readAsDataURL(this.files[0]); }); }; //游戲關卡的圖片和游戲每一個關卡要切成的圖片快個數 var levels = ["http://sqqihao.github.io/games/jigsaw/lake.jpg","http://sqqihao.github.io/games/jigsaw/cat.jpg","http://sqqihao.github.io/games/jigsaw/follower.jpg","http://sqqihao.github.io/games/jigsaw/view.jpg"]; var numbers = [3,4,5,6]; /** * @desc 把圖片通過canvas切成一塊塊; */ function ClipImage(canvas , number) { //blocks是一個二維數組,保存的是所有的canvas方塊; this.blocks = []; //instances是一維數組,保存的是實例化的數組; this.instances = []; this.maps = {}; this.canvas = canvas; this.context = this.canvas.getContext("2d"); this.number = number; this.clip(); }; $.extend(ClipImage.prototype, { /** * @desc 根據關卡把圖片canvas切成塊canvas * 然后渲染到DOM; * */ clip : function () { var avW = this.avW = this.canvas.width/this.number; var avH = this.avH = this.canvas.height/this.number; for(var i=0; i< this.number; i++ ) { for(var j=0; j<this.number; j++ ) { this.blocks[i] = this.blocks[i] || []; var canvas = document.createElement("canvas"); canvas.width = avW; canvas.height = avH; canvas.x = j; canvas.y = i; canvas.map = i+"_"+j; canvas.correctMap = i+"_"+j; var imageData = this.context.getImageData(j*avW, i*avH, avW, avH); canvas.getContext("2d").putImageData( imageData, 0, 0 ); if( i === j && j=== (this.number-1) )break; // 把canvas放到二維數組blocks中; this.blocks[i][j] = canvas; }; }; this.renderToDom(); }, /** * @param 把canvas塊混排, 打亂排序; * */ random : function( ) { var len = this.instances.length; while(len--) { $(this.instances[len].canvas).remove(); }; //使用底線庫的方法shuffle打亂排序; this.blocks = _.shuffle(this.blocks); for(var i=0 ;i <this.blocks.length; i++) { this.blocks[i] = _.shuffle(this.blocks[i]); } this.renderToDom(); }, /** * @desc 把canvas渲染到DOM; * */ renderToDom : function () { document.getElementById("content").innerHTML = ""; this.maps = {}; this.doms = []; this.instances = []; for(var i=0; i<this.blocks.length; i++ ) { for(var j=0; j<this.blocks[i].length; j++) { var instance = new Block( this.blocks[i][j], j, i ,this.avW, this.avH); //把實例化的數據保存到instances this.instances.push( instance ); this.maps[i+"_"+j] = true; }; }; }, updataDom : function(cav, obj) { this.updataMap(); $(cav).animate({top:obj.y*this.avH,left:obj.x*this.avW}); }, updataMap : function () { this.maps = {}; var len = this.instances.length; while(len--) { this.maps[this.instances[len].canvas.y + "_" + this.instances[len].canvas.x] = true; this.instances[len].canvas.map = this.instances[len].canvas.y + "_" + this.instances[len].canvas.x; }; /* for(var i=0; i<this.blocks.length; i++ ) { for (var j = 0; j < this.blocks[i].length; j++) { this.maps[this.blocks[i][j].y + "_" + this.blocks[i][j].x] = true; } }*/ }, testSuccess : function () { var len = this.instances.length; while(len--) { //只要有一個不等就無法成功; if(this.instances[len].canvas.correctMap !== this.instances[len].canvas.map) { return ; }; }; console.log("成功"); if( ++window.lev >=4 ) { alert("已經通關"); return ; } ; $("#now").html( window.lev + 1 ); $(".progress-bar").width( (window.lev+ 1) * 25 + "%" ); init(window.lev); } }); /** * @desc 對每一個canvas進行包裝; * @param canvas * @param left * @param top * @param avW * @param avH * @constructor Block */ var Block = function(canvas, left, top,avW, avH) { this.canvas = canvas; this.left = left; this.top = top; this.avW = avW; this.avH = avH; this.init(); }; $.extend(Block.prototype, { /** * @desc 對每一個canvas進行定位, 然后添加到界面中; * */ init : function () { this.canvas.style.position = "absolute"; this.canvas.style.left = this.avW*this.left +"px"; this.canvas.style.top = this.avH*this.top +"px"; this.canvas.x = this.left; this.canvas.y = this.top; document.getElementById("content").appendChild( this.canvas ); }, /** * @desc 對每一個canvas進行定位 * */ setPosition : function() { this.canvas.style.left = this.avW*this.canvas.x +"px"; this.canvas.style.top = this.avH*this.canvas.y +"px"; }, /** * @desc 向上移動會執行的函數 ,通過判斷maps下有沒有對應的key值判斷, 界面中的固定位置是否被占用; * */ upF : function(maps,numbers,cb) { //如果目標有 var temp = (this.canvas.y>0 ? (this.canvas.y-1) : this.canvas.y); var targetXY = temp+"_"+this.canvas.x; if( !maps[targetXY] ) { this.canvas.y = temp; this.canvas.map = targetXY; //alert("可以走") cb(this.canvas, { x : this.canvas.x, y : this.canvas.y }); return true; }; }, /** * @desc 同上 * */ rightF : function(maps, numbers, cb) { var temp = ((this.canvas.x+1>numbers-1) ? this.canvas.x : this.canvas.x+1); var targetXY = this.canvas.y+"_"+temp; if( !maps[targetXY] ) { this.canvas.x = temp; this.canvas.map = targetXY; //alert("可以走") cb(this.canvas, { x : this.canvas.x, y : this.canvas.y }); return true; }; }, /** * @desc 同上 * */ downF : function (maps,numbers,cb) { var temp = ((this.canvas.y+1>numbers-1) ? this.canvas.y : this.canvas.y+1); var targetXY = temp+"_"+this.canvas.x if( !maps[targetXY] ) { this.canvas.y = temp; this.canvas.map = targetXY; cb(this.canvas, { x : this.canvas.x, y : this.canvas.y }); //alert("可以走"); return true; }; }, /** * @desc 同上 * */ leftF : function(maps,numbers,cb) { var temp = ( (this.canvas.x-1)>=0 ? this.canvas.x-1 : this.canvas.x ); var targetXY = this.canvas.y+"_"+temp; if( !maps[targetXY] ) { this.canvas.x = temp; this.canvas.map = targetXY; //alert("可以走") cb(this.canvas, { x : this.canvas.x, y : this.canvas.y }); return true; }; } }); /** * @desc 主要控制器; * * */ function Controller( clipImage, number) { var run = function( clipImage, name ) { //window.clipImage.doms ,window.clipImage.maps, numbers[level], window.clipImage.updataDom.bind(window.clipImage) for(var i=0; i<clipImage.instances.length; i++ ) { var instance = clipImage.instances[i]; if( instance[name].bind(instance)(clipImage.maps, number, clipImage.updataDom.bind(clipImage)) ) { clipImage.testSuccess(); return }; }; } $(window).unbind("keydown"); $(window).bind("keydown", function(ev) { var name; switch(ev.keyCode) { case 37 : name = "leftF"; break; case 38 : name = "upF"; break; case 39 : name = "rightF"; break; case 40 : name = "downF"; break; default : ev.preventDefault(); return false }; run( clipImage, name ); ev.preventDefault(); }); $(document).swipeLeft(function() { run(clipImage,"leftF") }).swipeUp(function() { run(clipImage,"upF") }).swipeRight(function() { run(clipImage,"rightF") }).swipeDown(function() { run(clipImage,"downF") }); }; function init(level) { util.loadImg( levels[level] ,function() { window.clipImage = new ClipImage(canvas, numbers[level]); window.clipImage.random(); Controller( window.clipImage, numbers[level] || 3); }); }; $(function() { window.lev = 0; init(lev); bindEvents(); }); }); </script> </body> </html>
如果有bug直接評論, 我會修正, 提git的issue也行,
DEMO地址查看:打開
作者: NONO
出處:http://www.cnblogs.com/diligenceday/
QQ:287101329
文章列表