JavaScript写一个拼图游戏
拼图游戏的代码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 = [,,,]; //工具方法
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
JavaScript写一个拼图游戏的更多相关文章
- 用javascript实现一个2048游戏
早就想自己写一个2048游戏了,昨晚闲着没事,终于写了一个 如下图,按方向键开始玩吧. 如果觉得操作不方便,请直接打开链接玩吧: http://gujianbo.1kapp.com/2048/2048 ...
- 怎么用JavaScript写一个区块链?
几乎所有语言都可以编写区块链开发程序.那么如何用JavaScript写一个区块链?以下我将要用JavaScript来创建1个简单的区块链来演示它们的内部到底是怎样工作的.我将会称作SavjeeCoin ...
- Python 写一个俄罗斯方块游戏
使用 Python 的 PyGame 库写一个俄罗斯方块游戏的逐步指南 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手.很多已经做案例的人 ...
- JavaScript写一个连连看的游戏
天天看到别人玩连连看, 表示没有认真玩过, 不就把两个一样的图片连接在一起么, 我自己写一个都可以呢. 使用Javascript写了一个, 托管到github, 在线DEMO地址查看:打开 最终的效果 ...
- 用 JavaScript 实现简单拼图游戏
本篇主要讲解,如何利用原生的 JavaScript 来实现一个简单的拼图小游戏. 线上体验地址:拼图 一.游戏的基础逻辑 想用一门语言来开发游戏,必须先了解如何使用这门语言来实现一些基础逻辑,比如图像 ...
- 用JavaScript写一个区块链
几乎每个人都听说过像比特币和以太币这样的加密货币,但是只有极少数人懂得隐藏在它们背后的技术.在这篇博客中,我将会用JavaScript来创建一个简单的区块链来演示它们的内部究竟是如何工作的.我将会称之 ...
- 利用C# 窗体设计 写一个抽奖游戏
老师布置了一个任务,要求我们做一个抽奖游戏,以下是我个人制作的一个作品与写项目的过程. 我们用到了8个pictureBox控件和一个button,设置好大小,并且编排成一个九宫个形状 添加窗体的背景图 ...
- 用javascript写一个前端等待控件
前端等待控件有啥新奇的?什么jquery啦,第三方控件啦,好多好多,信手拈来. 因为项目使用了bootstrap的原因,不想轻易使用第三方,怕不兼容.自己写一个. 技术点包括动态加载CSS,javas ...
- 用javascript写一个emoji表情插件
概述 以我们写的这个emoji插件为例,网上已经有一些相关的插件了,但你总感觉有些部分的需求不能被满足(如:可以自行添加新的表情包而不用去改源代码等等) 详细 代码下载:http://www.demo ...
随机推荐
- HDU 5155 Harry And Magic Box --DP
题意:nxm的棋盘,要求每行每列至少放一个棋子的方法数. 解法:首先可以明确是DP,这种行和列的DP很多时候都要一行一行的推过去,即至少枚举此行和前一行. dp[i][j]表示前 i 行有 j 列都有 ...
- AC日记——单词翻转 1.7 27
27:单词翻转 总时间限制: 1000ms 内存限制: 65536kB 描述 输入一个句子(一行),将句子中的每一个单词翻转后输出. 输入 只有一行,为一个字符串,不超过500个字符.单词之间以空 ...
- Location 对象
Location 对象 Location 对象包含有关当前 URL 的信息. Location 对象是 window 对象的一部分,可通过 window.Location 属性对其进行访问. 注意: ...
- Python的高级特性8:你真的了解类,对象,实例,方法吗
Python的高级特性1-7系列是本人从Python2过渡3时写下的一些个人见解(不敢说一定对),接下来的系列主要会以类级为主. 类,对象,实例,方法是几个面向对象的几个基本概念,其实我觉得很多人并不 ...
- django整合原有的mysql数据库
虽然django适合从零开始构建一个项目,但有时候整合原有的数据库也在所难免,下面以django整合我的mysql作说明. mysql数据是我从京东上抓取的数据,数据表名为jd,演示如图 下面将jd整 ...
- JVM监测&工具[转]
通过工具及Java api来监测JVM的运行状态, 需要监测的数据:(内存使用情况 谁使用了内存 GC的状况) 内存使用情况--heap&PermGen @ 表示通过jmap –heap pi ...
- php常用函数(不定时更新)
一,数据操作类 1,天花板函数(ceil)和地板函数(floor) 这两个函数是对数据进行向上取整和向下取整的操作的,比如说 echo ceil("3.01"); //这里输出的是 ...
- ASP.NET 单点登陆
第一种:同主域但不同子域之间实现单点登陆 Form验证其实是基于身份cookie的验证.客户登陆后,生成一个包含用户身份信息(包含一个ticket)的cookie,这个cookie的名字就是在web. ...
- Linux Linux下特殊的printf函数和fputs函数
Linux下,printf函数必须以'\n'结尾才会立刻输出到屏幕,如果没有'\n'直到输出缓冲区满了以后才会打印到屏幕上(敲击换行也算),如果需要不换行的输出,一般可以使用write函数代替.'\n ...
- &12 二叉搜索树
#1,定义 二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的 ...