这篇文章是关于使用H5 canvas、css和JavaScript技术实现滑动拼图游戏。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<link rel="stylesheet" href="css/game.css" />
<script type="text/javascript" src="js/game.js" ></script>
</head>
<body>
<div id="container">
<div id="toolbar">
<input type="button" name="" id="" value="简单"/>
<input type="button" name="" id="" value="复杂"/>
<img src="img/1.jpg" />
<img src="img/2.jpg" />
<img src="img/3.jpg" />
</div>
<div id="game_left">
<canvas id="puzzle" width="600" height="600"></canvas>
</div>
<div id="game_right">
<img id="cankao" src="img/1.jpg"/>
</div>
</div>
</body>
</html>
game.css
#toolbar{
height: 80px;
border: 1px solid blue;
}
#toolbar img{
width: 50px;
height: 50px;
margin-top: 10px;
}
#game_left{
width: 60%;
float: left;
border: 1px solid blue;
height: 600px;
}
#game_right{
width: 39%;
float: left;
border: 1px solid blue;
height: 600px;
}
以上就做好了前台的页面。
当网页刷新后,要初始化绘图环境。即准好参考图片和画布、画笔。
注册页面加载完毕事件。
<body onload="ready()">
在游戏中需要将参考图片分为n个切片,以分为4个切片为例。
将图片分为2*2的图片,用二维数组存储图片。例:[[img1,img2],[img3,img4]]
其中img1里有两个属性,分别为图片的x轴坐标和y轴坐标来标识切片。
在JavaScript里定义全局变量和ready()函数。
var tileArray=new Array(); //存储切片的二维数组
var img=new Image();//在画布中要绘制的图片
var imgSize=600; //图片大小
var ctx;//画笔
var emptyObj=new Object();//空白图片
//游戏准备
function ready(){
var puzzle=document.getElementById("puzzle"); //获得canvas标签
ctx=puzzle.getContext("2d");
img.src=document.getElementById("cankao").src; //获取参考图片
}
实现的效果为点击上方缩略图,然后显示下方参考图片。
先在html里注册图片的onclick事件
<img src="img/1.jpg" onclick="changePic(this)"/>
<img src="img/2.jpg" onclick="changePic(this)"/>
<img src="img/3.jpg" onclick="changePic(this)"/>
js定义changePic()函数
//改变参考图片
function changePic(ele){
document.getElementById("cankao").src=ele.src;
}
点击简单或复杂按钮,初始化canvas图片分割。
首先给游戏级别按钮注册initGame(num)点击事件,根据传入的num对原图片进行num*num的分割,将原图片每个切片的坐标按顺序存入到tileArray二维数组中,然后将数组洗牌打乱,空白图片设置为第0行第0列。
这里面涉及到两套坐标系,第一套是原图片的坐标系,第二套是画布的坐标系。
先将图片切割,按顺序将参考图片画到画布上。
给按钮注册onclick事件
<input type="button" name="" id="" value="简单" onclick="initGame(2)"/> //分割为2×2的图片
<input type="button" name="" id="" value="复杂" onclick="initGame(4)"/>//分割为4×4的图片
js定义initGame()函数
//根据游戏级别初始化
function initGame(num){
//将原图片坐标存入二维数组
for (var i = 0; i < num; i++) {
tileArray[i]=new Array(); //行
for(var j = 0; j < num; j++){
var obj=new Object(); //定义一个对象
obj.x=i;
obj.y=j;
tileArray[i][j]=obj; //列
}
}
//按照二维数组绘制图片到画布
redraw();
}
需定义每个切片的大小
var tilelen;//切片大小
//根据游戏级别初始化
function initGame(num){
//得到切片大小
tilelen=imgSize/num;
}
当更改图片时,img的src路径会改变
//改变图片
function changePic(ele){
document.getElementById("cankao").src=ele.src;
img.src=ele.src;
}
js定义redraw()函数,在画布中绘制图片
//重新绘制图片
function redraw(){
var num=tileArray.length; //二维数组的长度
for (var i = 0; i < num; i++) {
for(var j = 0; j < num; j++){
//原图片的坐标
var curimg=tileArray[i][j];//得到真实图片的坐标
//绘制一个小切片
ctx.drawImage(img,curimg.x*tilelen,curimg.y*tilelen,tilelen,tilelen,i*tilelen,j*tilelen,tilelen,tilelen);
}
}
}
canvas绘制图像说明:
剪切图像,并在画布上定位被剪切的部分:
context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height);
参数值
参数 | 描述 |
---|---|
img | 规定要使用的图像、画布或视频。 |
sx | 可选。开始剪切的 x 坐标位置。 |
sy | 可选。开始剪切的 y 坐标位置。 |
swidth | 可选。被剪切图像的宽度。 |
sheight | 可选。被剪切图像的高度。 |
x | 在画布上放置图像的 x 坐标位置。 |
y | 在画布上放置图像的 y 坐标位置。 |
width | 可选。要使用的图像的宽度。(伸展或缩小图像) |
height | 可选。要使用的图像的高度。(伸展或缩小图像) |
在初始化游戏还没有重新绘制之前,对二维数组切片随机排列
//根据游戏级别初始化
function initGame(num){
//对二维数组切片进行随机排列
shuffle();
//按照二维数组绘制图片到画布
redraw();
}
js定义shuffle()函数对二维数组随机排列
//随机排列二维数组
function shuffle(){
var num=tileArray.length; //二维数组的长度
for (var i = 0; i < num; i++) {
for(var j = 0; j < num; j++){
//获取随机位置
var ri=Math.floor(Math.random()*num);
var rj=Math.floor(Math.random()*num);
//当前ij元素交换位置
var t=tileArray[i][j];
tileArray[i][j]=tileArray[ri][rj];
tileArray[ri][rj]=t;
}
}
}
图片完成随机排列后,将之前绘制的图片清空,
function redraw(){
ctx.clearRect(0,0,imgSize,imgSize);
}
由于完全随机排列,拼图可能无解,将参考图片的左上角搬到画布的左上角置为空白,保证左上角为正确的位置,其他图片打乱。
//找到左上角(0,0)的位置设为空白,而且保证在正确的位置上
//随机排列二维数组
function shuffle(){
//找到左上角(0,0)的位置设为空白,而且保证切片在正确的位置上
for (var i = 0; i < num; i++) {
for(var j = 0; j < num; j++){
//如果横纵坐标都为0,则与数组里的(0,0)交换位置,确保左上角对应左上角
if(tileArray[i][j].x==0 && tileArray[i][j].y==0){
var t=tileArray[i][j];
tileArray[i][j]=tileArray[0][0];
tileArray[0][0]=t;
break;
}
}
}
//标记(0,0)为空白图片,画布中(i,j)坐标
emptyObj.i=0;
emptyObj.j=0;
}
在绘制图片中将空白图片画出来
//重新绘制图片
function redraw(){
for (var i = 0; i < num; i++) {
for(var j = 0; j < num; j++){
//原图片的坐标
var curimg=tileArray[i][j];//得到真实图片的坐标
//设置当前图片如果为空白的话就不绘制
if(i==emptyObj.i && j==emptyObj.j){
}else{
//绘制一个小切片
ctx.drawImage(img,curimg.x*tilelen,curimg.y*tilelen,tilelen,tilelen,i*tilelen,j*tilelen,tilelen,tilelen);
}
}
}
}
给图片注册一个onclick事件
<canvas id="puzzle" width="600" height="600" onclick="move(event)"></canvas>
js定义move()函数
function move(e){
//获得点击的位置,获得下标
var ci=Math.floor(e.offsetX/tilelen);
var cj=Math.floor(e.offsetY/tilelen);
//如果点击的切片与空白相邻,则与空白交换位置。
//要么横坐标一样要么纵坐标一样,两坐标分别相减等于1则相邻
if(Math.abs(ci-emptyObj.i)+Math.abs(cj-emptyObj.j)==1){
var t=tileArray[ci][cj];
tileArray[ci][cj]=tileArray[emptyObj.i][emptyObj.j];
tileArray[emptyObj.i][emptyObj.j]=t;
//修改空白图片的坐标
emptyObj.i=ci;
emptyObj.j=cj;
//重新绘图
redraw();
}
}
我们认为,除了空白图片之外原图的坐标xy与画布的坐标ij重合时就完成。
每一次移动后判断是否完成
function move(e){
//判断游戏是否完成
var success=isSuccess();
//如果success为true则重新绘制整张图
if(success){
ctx.drawImage(img,0,0,imgSize,imgSize,0,0,imgSize,imgSize);
//加文字
ctx.font='bold 50px 宋体'; //字体样式
ctx.fillStyle='green'; //字体颜色
ctx.fillText("游戏完成!",imgSize/2,imgSize/2); //文字内容,位置
}
}
js定义isSuccess()函数
//判断游戏是否完成
function isSuccess(){
var num=tileArray.length;
var success=true;
for (var i = 0; i < num; i++) {
for(var j = 0; j < num; j++){
//当前ij与tileArray[i][j]存储元素完全相同,则完成;如果有一个不一样则返回false
if(tileArray[i][j].x!=i||tileArray[i][j].y!=j){
success=false;
break;
}
}
}
return success;
}
空白经过偶数次移动才能结束。 在数学中有一个逆序数的概念,若有一列数字为0123,则逆序数为0;若有一列数字为3210,则逆序数为6。
若逆序数为偶数,则为偶排列,若逆序数为奇数,则为奇排列。 打乱后只要为偶排列就有解,否则为奇排列。
在随机之后判断是否有解
function initGame(num){
//对二维数组切片进行洗牌
shuffle();
//判断随机之后的拼图是否有解,如果有解就绘制,如果无解重新排列
var solved=workabel();
if(solved){
//按照二维数组绘制图片到画布
redraw();
}else{
initGame(num);
}
}
js定义workabel()函数
//判断拼图是否有解
function workabel(){
var num=tileArray.length;
var arr1=new Array();
//将二维数组坐标转换为数字存入一维数组
for (var i = 0; i < num; i++) {
for(var j = 0; j < num; j++){
var ri=tileArray[i][j].x;
var rj=tileArray[i][j].y;
arr1.push(ri*num+rj);
}
}
//求arr1的逆序数
var total=0;
for (var i = 0; i < num*num; i++) {
for(var j = i+1; j < num*num; j++){
if(arr1[i]>arr1[j]){total++;}
}
}
if(total%2==0){
return true;
}else{
return false;
}
}