拼图游戏 玩法介绍及其代码实现(有意思的JS 一)

我是你们的索儿呀,很幸运我的文章能与你相见
愿萌新能直观的感受到Javascript的趣味性,愿有一定基础者有所收获,愿大佬不吝赐教

拼图游戏是一张图片分为若干块,打乱次序,将其中一块变为空白块,其只能与相邻块互换,发挥你的聪明才智来将其复原

而我本篇文章的目的是,介绍拼图游戏的玩法、技巧使用JavaScript语言实现拼图游戏

就算是萌新,也是可以阅读前半篇文章的,若能勾起你的兴趣将整篇文章看完那就更棒了

效果展览

  • 原图、2 × 2、3 × 3、6 × 6
  • 甚至还可以4 × 3、3 × 6
  • 图片也是可以更换的,其原图、4 × 4

思路及代码实现

若你对于JavaScript才初窥门径,我建议你使用电脑下载代码(文末提供的核心代码),然后配合本篇文章食用更佳

我先以 3 × 3 的九宫格来讲解拼图游戏的思路,实际上我提供的代码是很灵活的,可以更换图片和改变分割方式

补充一嘴:本人没有对打乱拼图的算法做逻辑处理,显示出来的拼图不一定可以复原哦~~

html代码只需要一行即可:

<div id="game"></div>

其余内容皆由JavaScript完成

一:游戏整体配置

在这里插入图片描述

此时,摆在我们眼前的有两种做法:

  1. div中放九个img作为拼图块(这个做法太死板了,若我要改变图片、分割方式,改动起来会非常麻烦琐碎)
  2. div只需要一个背景图片,里面生成九个div作为拼图块,即将其分割为3 × 3 的九宫格(将图片看作spirit图,然后配合background-position属性即可做到)
  • 根据图片的分辨率( 900 × 900 ),确定<div id="game"></div>的宽高
  • 拼图游戏( 3 × 3 ),确定每一行、每一列有多少个拼图块
  • 图片路径,相对的是页面路径

以上三个方面的配置是最基本的,可以通过更改以上配置来更改拼图游戏的图片或者分割方式

/**
 * 游戏配置
 */
var config = {
    dom: document.getElementById("game"),  //游戏的dom对象
    width: 900,
    height: 900,
    rows: 3,  //行数
    cols: 3,  //列数
    url: "img/Ahri.png",  //图片路径
};
  • 每一块拼图块的宽高取决于<div id="game"></div>的宽高和行列数
  • 拼图块的数量取决于div的行列数
//每一个拼图块的宽高
config.blockWidth = config.width / config.cols;
config.blockHeight = config.height / config.rows;

//拼图块的数量
config.blockNumber = config.rows * config.cols;

二:初始化游戏

1. 初始化游戏面板

  • config.dom获取的是<div id="game"></div>的dom对象
  • 然后使用这个dom对象设置其css属性(宽高、边框)
  • 为什么要设置为相对定位呢?因为拼图游戏中的拼图是可以移动的,它们的位置需要使用绝对定位
/**
 * 初始化游戏容器
 */
function initGameDom(){
    config.dom.style.width = config.width + "px";
    config.dom.style.height = config.height + "px";
    config.dom.style.border = "2px solid #ccc";
    config.dom.style.position = "relative";
}

2. 初始化拼图块

一个拼图块就是一个div,

需要在<div id="game"></div>中创建九个div(都设置为绝对定位),

此时需要一个数组,数组的每一项是一个对象,存放每一个div(拼图块)的信息

//存放拼图块信息
var blocks = [];
  • i 对应行号,j 对应列号,left、top为定位元素
  • left = j × blockWidth
  • top = i × blockHeight

此时,<div id="game"></div>中的九个div(拼图块)的位置就安排好了

/**
 * 初始化拼图块的数组
 */
function initBlocks(){
    for(var i = 0; i < config.rows; i++){
        for(var j = 0; j < config.cols; j++){
            var isVisible = true;
            if(i === config.rows-1 && j === config.cols-1){
                isVisible = false;
            }
            blocks.push(new Block(j * config.blockWidth, i * config.blockHeight, isVisible));
        }
    }
}

同时,每个div(拼图块)对应的正确的背景图片的位置correctBgX、correctBgY与left、top其实是一一对应的

  • correctBgX = -left;
  • correctBgY = -top;

Block构造函数有三个参数:;
以下函数的第三个参数isVisible控制拼图块是否可见(最后一个拼图块是不可见的)

关键参数为前两个参数:定位元素left、right,

在构造函数Block中先生成div的dom元素,然后定义其必须要的属性:width、height、border、background、position,

box-sizing: border-box 决定div的宽高包括边框的宽度,这样不会影响到页面布局
cursor: pointer 鼠标移动到该标签上时变为手指
transition : .5s css属性变化的时候,在0.5秒内完成(达到拼图块交互时的拖拽效果)

/**
 * 拼图块的构造函数
 * @param {*} left 
 * @param {*} top 
 * @param {*} isVisible 是否可见
 */
function Block(left, top, isVisible){
    this.left = left;
    this.top = top;
    this.correctBgX = -this.left;
    this.correctBgY = -this.top;
    this.isVisible = isVisible;//是否可见

    this.dom = document.createElement("div");
    this.dom.style.width = config.blockWidth + "px";
    this.dom.style.height = config.blockHeight + "px";
    this.dom.style.boxSizing = "border-box";
    this.dom.style.border = "2px solid #fff";
    this.dom.style.background = 'url("'+ config.url + '") ' + this.correctBgX + "px " + this.correctBgY + "px";
    this.dom.style.cursor = "pointer";
    this.dom.style.transition = ".5s";//css属性变化的时候,在0.5秒内完成
    if(!isVisible){
        this.dom.style.display = "none";
    }
    
    this.dom.style.position = "absolute";
    /**
     * 根据当前的left、top,显示div的位置
     */
    this.show = function(){
        this.dom.style.left = this.left + "px";
        this.dom.style.top = this.top + "px";
    }

    this.show();
    config.dom.appendChild(this.dom);
}

此时,拼图的大概样子是做出来了

3. 打乱拼图块

将存放拼图块的数组blocks重新排序,需要循环数组重新给其left、top值赋值

最后一个拼图块是空白的,它的位置是确定的(不参与重新洗牌)

/**
 * 给blocks数组从新排序
 */
function shuffle(){
    for(var i = 0; i < blocks.length-1; i++){
    	//随机产生一个下标
        var index = getRandom(0, blocks.length-2);
        //交换left、top
        exchangeBlock(blocks[i], blocks[index]);  
    }
}

/**
 * 生成[min, max]范围内的随机数
 * @param {*} min 
 * @param {*} max 
 */
function getRandom(min, max){
    return Math.floor(Math.random() * (max +1 -min) + min);
}

/**
 * 交换两个拼图块的top、left,并在页面上重新显示
 * 参数都为拼图块对象
 * @param {*} x 
 * @param {*} y 
 */
function exchangeBlock(x, y){
    //交换left
    var temp = x.left;
    x.left = y.left;
    y.left = temp;
    //交换top
    var temp = x.top;
    x.top = y.top;
    y.top = temp;
    //在页面重新显示
    x.show();
    y.show();
}

4. 给拼图块注册点击事件

交换看的见的拼图块与空白拼图块的坐标位置,

并且它们必须要挨着一起

  • 同一行,它们的left的差值的绝对值,等于拼图块的宽度
  • 同一列,它们的top的差值的绝对值,等于拼图块的高度
/**
 * 给拼图块注册点击事件
 */
function regEvent(){
    //找到空白拼图块
    var inVisibleBlock = blocks.find(function(b){
        return !b.isVisible;
    });
    
    blocks.forEach(function(b){
        b.dom.onclick = function(){
            if((isEqual(b.top, inVisibleBlock.top) && 
                isEqual(Math.abs(b.left - inVisibleBlock.left), config.blockWidth))
                ||
                (isEqual(b.left, inVisibleBlock.left) &&
                isEqual(Math.abs(b.top - inVisibleBlock.top), config.blockHeight))){
                    //交换看的见的拼图块与空白拼图块的坐标位置
                    exchangeBlock(b, inVisibleBlock);
                    //游戏结束判定
                    此处有一个函数
                }
        }
    });
}

/**
 * 避免小数的不精确,将两数化整比较
 * @param {*} x 
 * @param {*} y 
 */
function isEqual(x, y){
    return parseInt(x) === parseInt(y);
}

5. 游戏结束判定

关键:判断每一个拼图块是否都在正确的位置上

/**
 * 游戏结束判定
 */
function isWin(){
    //过滤不在正确位置上的拼图块
    var mistakenBlocks = blocks.filter(function(b){
        return !(isEqual(b.left, -b.correctBgX) && isEqual(b.top, -b.correctBgY));
    });
    if(mistakenBlocks.length === 0){
        //游戏结束
        blocks.forEach(function(b){
            b.dom.style.border = "none";
            b.dom.style.display = "block";//将最后一个拼图块也显示在页面上
        });
    }
}

此时还没有完成哦~

拼图完成后,要将每一个拼图块设置为都不能点击,有兴趣的可以阅读文末提供的完整核心代码哟

部分JS知识点陈列

为了阅读的流畅性,我先陈列一些后文我将用到的JS函数、dom元素的玩法(大致瞟一眼,留个印象就好了,阅读到后面可以返回来看)

一:dom元素的玩法

  • document.getElementById(“id名”)
    通过id获取对应id的元素
  • document.createElement(“元素名”)
    创建一个dom元素对象(此时不影响文本结构)
  • this关键字在事件处理程序中指代当前发生的事件元素
  • dom.style.样式名 = 值
    设置该dom元素的行内样式
  • dom.appendChild(dom元素)
    在某个元素末尾加入一个子元素
  • dom.onclick() = function(){函数体}
    为dom元素注册点击事件,当鼠标在页面上点击该dom元素时,执行函数体内容

二:JS函数

  • push()
    可向数组的末尾添加一个或多个元素,并返回新的长度
  • parseInt(浮点数)
    返回整数,即将浮点数的小数点即其后的数去掉
  • Math.floor(数)
    向下取整,比如12.1~12.9都返回12
  • Math.random()
    返回介于 0 ~ 1 之间的一个随机数(包括0,不包括1)
  • Math.abs(数)
    返回该数的绝对值
  • 数组.find()
    为数组中的每个元素都调用一次函数执行:当数组中的元素在测试条件时返回 true 时, find() 返回符合条件的元素,之后的值不会再调用执行函数。
  • 数组.filter()
    创建一个新的数组,新数组中的元素是通过调用数组中的每个元素并检查,返回满足条件的元素到新数组中
  • 数组.forEach()
    用于调用数组的每个元素,并将元素传递给回调函数。

个人所虑,必有不足。若有疑问,欢迎在评论区提问。

游戏玩法及技巧

既然决定写这篇文章,我花了一个上午研究拼图游戏,也某度、某乎查看了一些技巧,遂整体成文

本人建议,先到文末将核心代码下载下来,边玩边看玩法技巧,食用更佳

如果你是萌新,推荐使用百度网盘下载代码、查看运行哟

下载到电脑上后,只需要鼠标左键双击game.html文件,即可用浏览器打开,可以尝试玩一下哟(图片尺寸较大,缩放页面视觉效果更佳)

查看代码,就使用编辑器(或者电脑自带的记事本:鼠标右键该文件,打开方式,记事本)打开script文件夹下的game.js文件,就可以开始玩了(重新开一局也很简单:刷新页面)

若拼图是完全随机排序的,不一定可以复原,
拼图的复原在于旋转,旋转是受限制的(可以这么理解:把复原好的任意阶拼图,任意互换两块的位置,都不可能复原)

  • 2 × 2 比如下图是复原不了的:
  • 3 × 3 是拼图游戏的基础,因为它大概率可以复原(只有一种情况是无法复原的,后文会讲到);可以引申为一句话,无论是?× ?的拼图,只要包含3 × 3,就可以简化为3 × 3的拼图

只要会复原3 × 3,就可以玩转拼图游戏了(若它能够复原)

以可复原为基础,某乎高赞答案 给的复原3 × 3技巧,我发现非常好用:

  • 复原顺序分为两种:纵向和横向
    假设以一列一列的方式复原
  • 准则一:当你复原一列的时候,你的眼中只有这一列的块,其他列都不属于你的考虑范围。
  • 准则二:当你复原完一列的时候,就再不要管它,不要动它,把它给忘掉。
  • 注意:不要被伪复原迷惑(复原每一列的时候,一定也要遵循从上往下或从下往上一块块复原,不连贯就是伪复原)

除了以上技巧,还要会借位技巧(你就已经是拼图大神了)

我将情况尽量简化,来讲解借位技巧,给下图(1、4、7已经排好了,这一列不看了):

而且上图恰好是伪复原,2、8看似复原了,实际上这样想会进入死胡同的,实际上只复原了2或8

我就以2复原了作为基础,来讲解怎么将第二列(2、5、8)复原:
先将2、5复原(不用思考就做得到),

此时摆在我们面前的问题是,如何将8复原,此处需要借位技巧了

先将2移到上图3处,5移到上图2处(眼中只做到这两步即可)

然后就是最后一个难点了,将8移到上图6处,空白格再移回上图空白格处

接位的目标已达成,将空白格上移、右移,第二列复原完成

于是,你发现了一个问题…嘿嘿…我给的这个3 × 3 拼图是不可能复原的

代码获取

拼图游戏的完整核心代码,有两个方式获取

所谓核心代码,即本篇文章带你实现的拼图游戏的完整代码,没有加入许许多多花里胡哨的东西,以免文章无法连贯讲解

  1. 百度网盘
    文件夹链接:https://pan.baidu.com/s/1bFiosDk2uXjvOHuVYsI2IQ
    提取码:r1ua
    压缩包链接:https://pan.baidu.com/s/1nvYilW7vYErJZWQzXER6DQ
    提取码:zr7k
  2. github:https://github.com/Zhangguohao666/demo-front-end/tree/master/jigsaw

拼图游戏的完整提高版代码

未完待续…

本文代码思路参考自:渡一,袁进老师


发布了146 篇原创文章 · 获赞 287 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/Zhangguohao666/article/details/104408504