精华内容
下载资源
问答
  • 拼图游戏

    2019-12-29 19:21:27
    学习前端也有一段时间了,今天做一个前端的综合小游戏–拼图游戏。 首先展示下已完成的效果图: 下方有两个按钮,分别是用于选择本地图片和打乱图片开始游戏。 这里给出可体验的网址:拼图游戏 开始正式写代码之前...

    学习前端也有一段时间了,今天做一个前端的综合小游戏–拼图游戏。
    首先展示下已完成的效果图:
    在这里插入图片描述

    下方有两个按钮,分别是用于选择本地图片和打乱图片开始游戏。
    这里给出可体验的网址:拼图游戏
    开始正式写代码之前,首先得明白此案例需要实现哪些功能。
    需要实现的功能:

    1. 两个按钮的功能
    2. 鼠标拖拽的功能
    3. 拖拽后可以交换的功能
    4. 检验是否成功完成游戏的功能

    明确了需要实现的功能代码实现就简单多了。
    HTML代码:

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
        <link rel="stylesheet" href="puzzle.css">
    </head>
    
    <body>
        <table>
            <tr>
                <td>
                    <!-- 便于拖拽 -->
                    <div id="f-1">
                        <div class="picture" id="z-1"></div>
                    </div>
                </td>
                <td>
                    <div id="f-2">
                        <div class="picture" id="z-2"></div>
                    </div>
                </td>
                <td>
                    <div id="f-3">
                        <div class="picture" id="z-3"></div>
                    </div>
                </td>
            </tr>
            <tr>
                <td>
                    <div id="f-4">
                        <div class="picture" id="z-4"></div>
                    </div>
                </td>
                <td>
                    <div id="f-5">
                        <div class="picture" id="z-5"></div>
                    </div>
                </td>
                <td>
                    <div id="f-6">
                        <div class="picture" id="z-6"></div>
                    </div>
                </td>
            </tr>
            <tr>
                <td>
                    <div id="f-7">
                        <div class="picture" id="z-7"></div>
                    </div>
                </td>
                <td>
                    <div id="f-8">
                        <div class="picture" id="z-8"></div>
                    </div>
                </td>
                <td>
                    <div id="f-9">
                        <div class="picture" id="z-9"></div>
                    </div>
                </td>
            </tr>
        </table>
        <form name="form0" id="form0">
            <a href="javascript:;" class="file">选择图片<input type="file" name="file0" id="file0"/></a>
        </form>
        <input type="button" value="开始游戏" onclick="disorder()" class="start">
        <script src="https://cdn.bootcss.com/jquery/2.2.4/jquery.min.js"></script>
        <script src="puzzle.js"></script>
    </body>
    
    </html>
    

    CSS代码:

    table{
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%,-50%);
    }
    table,tr,td{
        border: 1px solid rgb(6, 213, 228);
    }
    div{
        width: 100px;
        height: 100px;
    }
    
    .picture{
        background-size: 300px 300px;/* 改变原背景图片大小 */
    }
    
    form{ 
        position: absolute;
        top: 72%;
        left: 47%;
        margin-left: -113px;
        margin-top: 15px;
    }
    /* 美化按钮 */
    .file {
        position: relative;
        display: inline-block;
        background: #D0EEFF;
        border: 1px solid #99D3F5;
        border-radius: 4px;
        padding: 4px 12px;
        overflow: hidden;
        color: #1E88C7;
        text-decoration: none;
        text-indent: 0;
        line-height: 20px;
    }
    .file input {
        position: absolute;
        font-size: 100px;
        cursor: pointer;
        right: 0;
        top: 0;
        opacity: 0;
    }
    .file:hover {
        background: #AADFFD;
        border-color: #78C3F3;
        color: #004974;
        text-decoration: none;
    }
    
    .start{
        width: 85px;
        height: 30px;
        font-size: 15px;
        line-height: 30px;
        border: 1px solid #99D3F5;
        border-radius: 4px;
        background-color: #D0EEFF;
        color: #1E88C7;
        position: absolute;
        top: 72%;
        left: 68%;
        margin-left: -200px;
        margin-top: 14px;
        cursor: pointer;
    }
    .start:hover {
        background: #AADFFD;
        border-color: #78C3F3;
        color: #004974;
    }
    
    #z-1{
        background-position: 0 0;
    }
    #z-2{
        background-position: -100px 0;
    }
    #z-3{
        background-position: -200px 0;
    }
    #z-4{
        background-position: 0 -100px;
    }
    #z-5{
        background-position: -100px -100px;
    }
    #z-6{
        background-position: -200px -100px;
    }
    #z-7{
        background-position: 0 -200px;
    }
    #z-8{
        background-position: -100px -200px;
    }
    #z-9{
        background-position: -200px -200px;
    }
    

    js代码:

    function my$(id) {
        return document.getElementById(id);
    }
    function over(e) {
        e.preventDefault();
    }
    //抓起,记录被抓起元素的id
    function drag(e) {
        //获取id
        var id = e.target.id;
        //传输数据
        e.dataTransfer.setData("id", id);
    }
    
    //放下
    function drop(e) {
        //var ev = window.event;
        var dragId = e.dataTransfer.getData("id");
        var dropId = e.target.id;
        var dragE = document.getElementById(dragId);
        var dropE = document.getElementById(dropId);
        var dragParent = dragE.parentNode;
        var dropParent = dropE.parentNode;
        //互换
        dragParent.appendChild(dropE);
        dropParent.appendChild(dragE);
        checked();
    }
    
    function checked() {
        var count = 0;
        var pis = document.getElementsByClassName("picture");
        for (var i = 0; i < pis.length; i++) {
            var pi = pis[i];
            var fi = pi.parentNode;
            var piId = pi.getAttribute("id");
            var fiId = fi.getAttribute("id");
            if (piId.replace("z-", "") == fiId.replace("f-", "")) {
                count++;
            } else {
                return;
            }
            if (count == 9) {
                alert("congratulate you!");
            }
        }
    }
    // 一旦打乱后各个div的位置都变了,重新选择文件后也是乱的
    function disorder() {
        var pis = document.getElementsByClassName("picture");
        for (var i = 0; i < 100; i++) {
            var r1 = parseInt(Math.random() * 9);
            var r2 = parseInt(Math.random() * 9);
            //得到随机父节点
            var pir1 = pis[r1].parentNode;
            var pir2 = pis[r2].parentNode;
            //互换
            pir1.appendChild(pis[r2]);
            pir2.appendChild(pis[r1]);
        }
    }
    
    //给父子div绑定相应的事件
    var idf = [];
    var idz = [];
    // var idf = new Array(9);
    // var idz = new Array(9);
    for (var i = 0; i < 9; i++) {
        // idf[i] = "f-" + (1 + i);
        // idz[i] = "z-" + (1 + i);
        idf.push("f-" + (1 + i));
        idz.push("z-" + (1 + i));
    }
    for (var i = 0; i < 9; i++) {
        my$(idf[i]).ondrop = drop;
        my$(idf[i]).ondragover = over;
        my$(idz[i]).draggable = true;
        my$(idz[i]).ondragstart = drag;
    }
    
    //获取被点击的图片的url
    $("#file0").change(function () {
        var objUrl = getObjectURL(this.files[0]);//获取文件信息  
        if (objUrl) {
            var pis = document.getElementsByClassName("picture");
            for (var i = 0; i < pis.length; i++) {
                pis[i].style.backgroundImage = "url(" + objUrl + ")";
            }
        }
    });
    
    
    function getObjectURL(file) {
        var url = null;
        if (window.createObjectURL != undefined) {
            url = window.createObjectURL(file);
        } else if (window.URL != undefined) { // mozilla(firefox)  
            url = window.URL.createObjectURL(file);
        } else if (window.webkitURL != undefined) { // webkit or chrome  
            url = window.webkitURL.createObjectURL(file);
        }
        return url;
    };
    

    有4个需要注意的地方:

    1. 此案例核心代码的实现,我参考了B站鼻孔哥的视频,链接:拼图案例详解
    2. 这个游戏的的如此设计造成一个bug:由于每次交换的是div,所以一旦打乱后哪怕更换图片,更新后的图片也是乱的。
    3. 实现本地图片上传功能参考此博主的文章:图片上传
    4. 在CSS按钮美化中,巧用覆盖包裹的方式美化上传图片的input标签,参考链接:美化input
    展开全文
  • 拼图游戏

    千次阅读 2009-08-20 16:52:00
    拼图游戏 本文讨论如何判断拼图游戏中图形是否可以还原。例1:下图是一个3X3的数字拼图。1326 5478图1它要还原成图212345

    拼图游戏

     

          本文讨论如何判断拼图游戏中图形是否可以还原。

    1是一个3X3的数字拼图

    1

    3

    2

    6

     

    5

    4

    7

    8

    1

    要还原成图2

    1

    2

    3

    4

    5

    6

    7

    8

     

    2

          将问题一般化,在M*N的方格里有M*N-1个不同元素和一个空元素,只有空元素可以与上下左右相邻的元素交换位置。M*N方格中M*N-1个元素和一个空元素的位置确定一个图形。拼图游戏的问题是:一个图形经过一连串的交换能否得到另一个图形,如何得到。从交换方式的可逆性看出这种关系满足等价三性质,如果图形A通过交换变成图形B我们则称它们是等价的。把M*N-1个元素用1M*N-1编号,空元素编号0。然后展成一个排列。每个图形对应一个排列。确定了展开方式,图形和排列是一一对应的。这里用到的展开方式是行优先的顺序(其他方式展开也能到相应的结果)。将例1的两个图形展开有:图1对应1 3 2 6 0 5 4 7 8,图2对应1 2 3 4 5 6 7 8 0

          定理1图形A与图形B等价的充要条件图形A的排列的逆序数加上0元素行号和列号的奇偶性等于图形B的排列的逆序数加上0元素行号和列号的奇偶性。为方便表述,把图形排列的逆序数加上0元素行号和列号的奇偶性称为图形的奇偶性。

          先看定理1如何起作用,图1:展开的排列 1 3 2 6 0 5 4 7 8,它的逆序数为80元素行号为2,列号为2。逆序数加行号,列号的奇偶性为偶。图2:展开的排列 1 2 3 4 5 6 7 8 0,它的逆序数为80元素行号为3,列号为3。逆序数加行号,列号的奇偶性为偶。两个图形的奇偶性相同,根据定理1判断它们等价。

          首先证明必要性,即如果图形A图形B等价,则图形A的奇偶性等于图形B奇偶性。

                  0元素和某个元素交换位置,则排列的逆序数的奇偶性就改变一次。交换后0元素的行号或者列号会加1或减1,即行号,列号之和的奇偶性也改变一次。这说明拼图的交换方式不改变图形的奇偶性,也说明拼图中至少有两组等价类,奇偶性不同的图形不等价。

          下面证明充分性,如果图形A的奇偶性等于图形B的奇偶性,则图形AB等价

          如果证明了拼图只有两组等价类,从必要性的证明过程可知,奇性图形是一组等价类,偶性是一组。从而证明了充分性。

          先考虑一般的排列1 2 3 ... N。某个元素连续与后面M相邻的元素交换位置,称为向后M步移动。如排列:1 2 3 4 5 6。元素2向后3步移动,排列变成1 3 4 5 2 6。同样的方式定义向前M步移动。如果排列A能够通过有限向前M步移动和向后M步移动变成排列B,称排列A与排列B M步等价。容易看出这也是等价关系。

          引理1任何一个1N的排列M步等价于1 2 ... N-M...)。括号里是N-M+1N某个排列

    证明:如果N=M,这显然成立。

    假设N=k时成立,下面证明k+1的情况。

    1元素的位置记为i

    情况1:假设i=1,显然,余下的元素减1,就变成N=k的境况,得证。

    情况2:如果1<i<=M,则元素1前面的元素向后M移动,变为情况1

    情况3如果i>M,则元素1有限次向前M步移动,使i1<=i<=M,可变成情况12

    从而得证。
    M=2时,只有两组等价类。由于移动不改变排列的奇偶性,从而奇排列是一组等价类,偶排列是一组等价类。


    考虑N*M拼图
    N=M=2,穷举法可证明只有两组等价类。

    NM不同时为2时,设N不等于2(如果N等于2M不等于2可颠倒行列讨论)。

    只考虑第二行最后一个元素是空元素的情形,因为空元素在其他位置总可以等价某个空元素在第二行最后一个元素的图形。不考虑空元素以之字形方式展开图形,即第一行最后一个数字和第二行倒数第二个数字相连。如:

    1

    2

    4

    3

    5

     

    3

    展开成12453

    下面证明两行拼图的交换方式可以实现排列的向前2向后2移动。

    要实现元素a向前2步移动,则可顺着展开的方式循环移动拼图,使a在第一行第二列的位置,使空元素在第二行第二列的位置,此时可把元素i可与空元素对换。然后再沿着展开的顺序还原拼图。

    例如:3的元素4向前2步移动。可以如下操作,

    2

    4

    5

    1

     

    3

    4

    2

     

    5

    1

    4

    3

    5

    4

    1

    2

    3

    5

     

    6

    展开41253。实现了向前2步移动。

    使i在第二行第二列的位置,使空元素在第一行第二列的位置可以实现向后2步移动。根据引理1及,两行拼图可以分成两组等价类。

    假设M=k图形可以分成两组等价类,下面证明M=k+1

    只需要证明任何M=k+1图形总等价于第一行元素为1 2 ... N的某图形即可。

    如果这N个元素都在第一行,把空元素移到第二行,从上面的证明可知,交换两个不同的非空元素,图形的奇偶性改变,属于不同的等价类。N大于2,第二行就有两个非空元素可供交换。所以两行图形可以等价与第一行为1 2 ... N的某个图形。

    如果1N的某个a元素不在第一行,设它在第i行。把空元素移动到i行,这样第i行和第i-1行可以看成M=2的图形。可以把a移动到第i-1行,并保证第i行和i-1行中1N的元素的行号不增加。有限步移动可以使1N元素全部在第一行。

    显然M=k+1图形的等价类数目为2

    充分性得证。

          拼图游戏的随机离散中加入定理1的判断可以保证游戏有意义,不会出现无解的情况。

    展开全文
  • 用C++写的拼图游戏,仅供参考.如果有什么好的建议请给我留言。用C++写的拼图游戏,仅供参考.如果有什么好的建议请给我留言。用C++写的拼图游戏,仅供参考.如果有什么好的建议请给我留言。
  • 拼图游戏-易语言

    2021-06-13 14:30:07
    拼图游戏
  • 拼图游戏-源码

    2021-03-02 04:18:01
    拼图游戏
  • 基于MATLAB的拼图游戏设计 内容摘要:MATLAB强大的运算和图形展示功能,使图像处理变得更加的简单和直观。本博文基于MATLAB编程语言,详细介绍了如何利用MATLAB及其图像处理函数进行经典拼图游戏设计,并通过...

                                                                               基于MATLAB的拼图游戏设计

    内容摘要:MATLAB强大的运算和图形展示功能,使图像处理变得更加的简单和直观。本博文基于MATLAB编程语言,详细介绍了如何利用MATLAB及其图像处理函数进行经典拼图游戏设计,并通过具体方法步骤及相应代码逐步实现游戏的完美运行。(有详细步骤及代码解释,适合新手以及进阶的朋友参考)

    关键词:拼图游戏;MATLAB;数字图像处理
    本文为博主原创,引用请注明出处http://blog.csdn.net/qq_32892383

     1.前言 

          Matlab是MathWork公司推出的一套高性能的数值计算和可视化软件。它是一个高度集成的系统,集科学计算、图像处理、声音处理于一体,具有极高的编程效率。数字图像处理是一种通过计算机采用一定的算法对图形图像进行处理的技术。数字图像处理技术已经在各个领域上都有了比较广泛的应用。MATLAB强大的运算和图形展示功能,使图像处理变得更加的简单和直观。本文介绍了如何利用MATLAB进行经典拼图游戏设计。拼图游戏是一款非常经典的小游戏,因为它比较简单有趣,可变性很高且耐玩,有利于开发智力,帮助提高动手解决问题的能力。 

          拼图游戏的设计对一个MATLAB语言设计者进行语言提高和进阶都是一个很好的锻炼机会,说起来这个程序是笔者当年大学时完全自己构思摸索的第一个长的完整程序,作为一名刚接触编程语言的菜鸟,在痛苦与激动中整整花了一个星期才完成。如今我已是一名研究生,转眼再看过去写的那些程序,可能诸多不足但那些编程的锻炼确实让我感获良多,面对如今众多的研究课题时,入门编程让我可以从容地用编程语言去释放自己的思想,不必在有好想法时因为编程问题而浇灭灵感的火花。临近年关,辞旧迎新之际,外面的世界熙来攘往,早已沉浸着春节的热闹与喜悦中,我仍要挑个安静的时间总结反思过去一年各方面的得与失。一个想法划过脑海想把前面做过的程序做个整理和改进,分享出来希望能够给刚接触编程的朋友们一点帮助和启发,对于我也是一个总结与提高,期待我们今后一起能够继续学习和进步。

    2.MATLAB的图像处理 

          MATLAB的基本数据单位是矩阵,它的指令表达式与数学,工程中常用的形式十分相似,故用MATLAB来解算问题要比用C、FORTRAN等语言完相同的事情简捷得多。MATLAB支持五种图像类型,即索引图像、灰度图像、二值图像、RGB图像和多帧图像阵列。MATLAB中,一幅图像包含一个数据矩阵,RGB图像分别用红,绿,蓝三个亮度值为一组,代表每个像素的颜色。图像数组为M*N*3,M,N表示图像像素的行列数。MATLAB图像处理工具箱是由一系列支持图像处理操作的函数组成,可以进行诸如几何操作、线性滤波和滤波器设计、图像变换、图像分析与图像增强、二值图像操作以及形态学处理等图像处理操作。

          这里拼图游戏中的图像处理主要是对图像数组的操作,涉及矩阵分割、重组等步骤。本文将在后面几节结合代码进行详细介绍。

    3.设计步骤

          到此为止,前面的内容稍作了解即可,接下来的主要设计思路无需细看但了解大概的流程却是有必要的,毕竟编写程序前还是得清楚到底要做什么的,简洁清晰的设计方案能起到提纲挈领的作用。

          拼图游戏的主要思想是先读入准备好的图片,将存储图像的数据矩阵平均分为9个小的矩阵块,并用一个矩阵元素为1,2,3,4,5,6,7,8,0的3*3的数组矩阵标记每一个小矩阵块。仿照人工随机打乱拼图的方法,将标记矩阵打乱,根据标记矩阵拼接相应的拼图块并显示整个图像,通过设置图形窗口鼠标点击事件的回调函数的方式获得鼠标点击位置的坐标值,判断点击位置并移动相应的拼图块,当拼图顺序完全正确时提示游戏完成。图3.1和图3.2是拼图游戏的程序流程图。

                                                    

                                             图3.1 总体程序流程图(左图)               图3.2 回调函数程序流程图(右图)

          上面流程图的方式可能有点学术,但确实不失为一种好的叙述整个程序设计思想的表示方法。解释一下吧,为了使整个程序整洁清晰,图1中“打乱拼图矩阵”以及”按照标记矩阵显示拼图“步骤都写成了自定义函数,分别为disrupt( )、dramap( ),其功能及具体细节会在后面章节展开。接着需要得到点击鼠标的位置坐标,这里利用figure的WindowButtonDownFcn属性自行定义一个回调函数。此时每次当你在图上按下鼠标的时候,就会转而执行回调函数,整个程序处于监听鼠标事件与调用一次回调函数的循环之中,回调函数的流程如图2中所示。在回调函数中,计算鼠标点击坐标后,根据坐标移动拼图标记矩阵中的元素,这一步由自定义的函数movejig( )完成。然后根据标记矩阵刷新拼图图像,即调用dramap( )函数。最后判断拼图的标记矩阵中元素是否排列正确,如果顺序完全正确则提示游戏完成并结束程序,而不正确时则结束本次的回调函数继续等待鼠标点击。

    3.1 开始程序设计

          首先需要准备一张彩色图片,图片的尺寸最好为正方形(否则不易于后面的处理,而且可能不好看),这里我选择的图片是一张经过裁剪的长宽为300*300的图片,如下文件'jigsawImage.jpeg'

                                                                                         jigsawImage.jpeg

            在matlab中新建一个m文件,这里我命名的文件名为'jigsaw.m',为了方便两个文件放在同一文件夹下,如下图所示。

                                                                                         图3.1.1 文件存放情况

            准备好这些我们就可以正式开始编写程序了,所有代码编写在jigsaw.m文件中,下面将逐个介绍其中的代码及设计细节。

    3.2 分割拼图

          这里我们设计的是一个九宫格的拼图游戏,因此在打乱拼图前需要先将图片平均切割为相等的九份。首先我们来看看MATLAB中图片的储存与表示,执行命令”image=imread('jigsawImage.jpeg');“读入jigsawImage图片(若图片与m文件不在同一位置需输入绝对路径,如image=imread('C:\Users\Administrator\Desktop\jigsaw puzzle\jigsawImage.jpeg');)在MATLAB中的存储情况如图3.2.1所示。

                                                                                       图3.2.1 原图存放情况

          可以看出MATLAB中图片的存储是以矩阵的形式进行,image是个300*300*3的矩阵,300、300分别是矩阵的横纵尺寸。平均分割image矩阵,则第一块小拼图的数据矩阵可以为x1=image(1:100,1:100,:);这行代码的意思是选取image矩阵中横坐标从1到100,纵坐标也从1到100的那部分矩阵数据。可通过下面的代码验证,帮助简单理解

    image=imread('jigsawImage.jpeg'); %读入图片
    figure %产生一个图形窗口
    imshow(image) %显示原图
    axis on %显示坐标轴
    figure
    x1=image(1:100,1:100,:);%分割一个拼图块
    imshow(x1);
    axis on

    在命令窗口执行以上代码,可以看到如下运行结果

                                          

                                                                             图3.2.2 分割演示结果图

          将整幅图像平均分割成9块,为了方便每一块用一个数字表示,从左到右,从上到下开始将9个小拼图块分别标记为1,2,3,4,5,6,7,8,0;其位置及坐标系如图3.2.3所示

                                                                                              图3.2.3 拼图标记
          
          从上图中可以看到每块拼图在原图中的坐标位置,其实也是矩阵的索引范围,因此同样可以通过上面的方式引用标号为2,3,4,5,6,7,8,0的拼图块,代码如下
    x2=image(1:100,101:200,:);%拼图块2矩阵数据
    x3=image(1:100,201:300,:);%拼图块3矩阵数据
    x4=image(101:200,1:100,:);%拼图块4矩阵数据
    x5=image(101:200,101:200,:);%拼图块5矩阵数据
    x6=image(101:200,201:300,:);%拼图块6矩阵数据
    x7=image(201:300,1:100,:);%拼图块7矩阵数据
    x8=image(201:300,101:200,:);%拼图块8矩阵数据
    x0=image(201:300,201:300,:);%拼图块0矩阵数据
    

    按照上面的思路,现在自定义一个从原图中分割拼图并按照相应拼图块的标记截取小拼图的函数choose( ),输入参数为image,index,其中image为原图的数据矩阵,index为要选择的拼图块的标记;输出为x,x是标记为index的小拼图块的数据矩阵。代码如下

    function x = choose(image,index)
    %% 根据索引选择对应位置上的拼图块
    if index > 0 %标记为1,2,3,4,5,6,7,8的拼图块
        % 计算出行数row以及列数column
        row=fix((index-1)/3);
        column=mod(index-1,3);
        % 分割出对应拼图块数据
        x=image(1+row*100:100*(row+1),1+column*100:100*(column+1),:);
    else
        x=uint8(255*ones(100,100,3));%拼图块0矩阵数据
    end
          首先第3到第6行中当index为1,2,3,...,8时先计算出行数row以及列数column,行数可通过求除以3后的商确定,fix( )是MATLAB中的取整函数,而列数可通过求余数确定,mod( )是求余函数,返回index-1除以3的余数。为了便于按照上面的方法分割矩阵,这里的row、column相当于一个倍数,如x=image(1+row*100:100*(row+1),1+column*100:100*(column+1),:);则将行列数乘分割间隔100(因为选择的图片大小是300*300的,分成3*3的拼图块所以索引间隔为100,若为其他尺寸的图片应为长宽分别除以3后的结果)即可根据行列数row,column引用相应位置上的小拼图块的数据矩阵了。例如index=4时,经过第5,6行代码计算出的row=1,column=0,代入第8行代码中,则实际x=image(1+1*100:100*2,1+0*100:100*1,:);这与前面代码中写出的”x4=image(101:200,1:100,:);%拼图块4矩阵数据“作用一致。
          当index=0时则执行第9行else下的代码部分,你可能会奇怪,为什么拼图块0不是从imge矩阵中分割而是放到else中了?这是因为0这个位置比较特殊,留作空白处,拼图游戏进行时当空白处附近某个拼图块被点击时,该拼图块就会移动到这个空白位置。这里ones( )函数产生一个100*100*3的全1矩阵,乘255则像素灰度值最亮,显示为全白,最后uint8( )统一数据类型。因此当index=0时,执行else中的代码,输出x为一块全白的拼图块。
     

    3.3 按标记序号矩阵显示拼图

          前面一节介绍了可以通过标记序号分割原图获得小拼图块数据矩阵的方法,一幅拼图可由9块小拼图块拼接而成,因此用一个9个元素的标记矩阵,每个位置上的元素值作为一个标记序号表示相应位置的拼图块,就可以通过这个标记矩阵表示整个的拼图了。          

          这样原图就可以用矩阵 表示,而任意打乱的拼图也可以通过改变矩阵相应位置上的元素值表示了。现在我们编写一个根据标记矩阵显示对应拼图的函数drawmap( ),输入参数为A,A为一幅拼图的标记矩阵,A大小为3*3。代码如下

    function drawmap(A)
    %% 将运算数字与对应拼图对应显示图片
    origin=imread('jigsawImage.jpeg');
    image=origin;
    
    % 对要显示的拼图进行赋值
    for row=1:3
        for col=1:3
        image(1+(row-1)*100:100*row,1+(col-1)*100:100*col,:)=choose(origin,A(row,col));
        end
    end
    
    imshow(image)%显示拼图

          第3行代码读入图片并将原图数据矩阵存储在origin中;第4行中image是要显示的拼图数据矩阵,先预定义与origin相同。接下来对image进行逐个赋值,image每一块数据的赋值通过调用前面编写的choose( )函数完成;第9行中”choose(image,A(row,col))“返回标记矩阵A中第row行第col列的元素表示的那一块拼图块的数据矩阵,而前面的一半image(1+(row-1)*100:100*row,1+(col-1)*100:100*col,:)与3.2节中选取每个拼图块的作用类似,这里用于对行数row从1到3,列数col从1到3的拼图块矩阵上的元素逐个赋值。经过9次循环9处拼图块的数据赋值完成,最终显示图像image。可以通过下面的代码简单测试一下drawmap( )函数,设置一个标记矩阵Tag_A=[8 4 5;6 0 3;1 7 2],即Tag_A= ,按照该矩阵显示这幅拼图。

    function jigsaw()
    %% 主函数
    Tag_A=[8 4 5; 6 0 3; 1 7 2];
    drawmap(Tag_A);%按照标记矩阵显示拼图
    
    
    function x = choose(image,index)
    %% 根据索引选择对应位置上的拼图块
    if index>0 %标记为1,2,3,4,5,6,7,8的拼图块
        % 计算出行数row以及列数column
        row=fix((index-1)/3);
        column=mod(index-1,3);
        % 分割出对应拼图块数据
        x=image(1+row*100:100*(row+1),1+column*100:100*(column+1),:);
    else
        x=uint8(255*ones(100,100,3));%拼图块0矩阵数据
    end
    
    
    function drawmap(A)
    %% 将运算数字与对应拼图对应显示图片
    origin=imread('jigsawImage.jpeg');
    image=origin;
    % 对要显示的拼图进行赋值
    for row=1:3
        for col=1:3
        image(1+(row-1)*100:100*row,1+(col-1)*100:100*col,:)=choose(origin,A(row,col));
        end
    end
    imshow(image)%显示拼图

          运行结果如下图所示,对比每个拼图块的标记可以看出函数能实现根据矩阵的值显示拼图了,距离成功又近了一步。

                                                                                         图3.3.1 测试结果

    3.4 移动拼图

          这部分我们要解决一个逻辑问题,那就是怎么移动拼图。例如,在图3.4.1的拼图中,如果鼠标在空白块的左侧一个拼图块内点击一次,此时左侧的那个拼图块应该往空白处移动,但要怎样才能实现这一过程?

                                                          

                                                       图3.4.1 移动拼图过程(左图)                 图3.4.2 拼图行列数(右图)

          为了实现这一过程,这里同样定义一个函数,实现在当前的拼图中根据鼠标所在的行列数移动拼图块,命名为movejig( ) 。输入参数:tag(当前拼图的标记矩阵)、row(鼠标点击位置的行数)、column(鼠标点击位置的列数);输出参数:tag(移动后得到的标记矩阵)。行列数的规定按照图3.4.2的坐标系确定,图3.4.3简单示出了movejig( )函数的大致运行方式。

                                                                                        图3.4.3 函数功能示意图

          这里默认已经知道了鼠标坐标并经过计算得出了鼠标点击处的行列数row,col,至于如何计算将在后面介绍。 movejig( )函数的具体代码如下

    function tag=movejig(tag,row,col)
     %% 4个if分4种情况对不同位置处的点坐标与矩阵行列式统一
        num = tag(row,col);%鼠标位置与号码牌一致
        if (row > 1)&&(tag(row-1,col)==0)%点击位置在第二或第三行,空白块在点击位置的上一行
            tag(row-1,col) = num;%交换两个位置上的值
            tag(row,col) = 0;
        end
        if (row < 3)&&(tag(row+1,col)==0)%点击位置在第一或第二行,空白块在点击位置的下一行
            tag(row+1,col) = num;
            tag(row,col) = 0;
        end
        if (col > 1)&&(tag(row,col-1)==0)%点击位置在第二或第三列,空白块在点击位置的左边一列
            tag(row,col-1) = num;
            tag(row,col) = 0;
        end
        if (col < 3)&&(tag(row,col+1)==0)%点击位置在第二或第三列,空白块在点击位置的右边一列
            tag(row,col+1) = num;
            tag(row,col) = 0;
        end
          第3行根据点击处的行列号可取出点击位置处拼图块的标记,存为num。第4行至第19行分四种情况考虑可能出现的点击情况,第4行if中的条件row>1说明点击位置在第2行或第3行,并且要求tag(row-1,col)==0即点击处的上一行位置上的标记是0(也就是表示空白拼图块),这两个条件同时满足就是表示鼠标点击的拼图块上面一个拼图块是空白块。条件满足后第5,6行就是将点击处的拼图块和上面的空白块的标记值互换,表示点击拼图块上移。同理,后面三种情况空白处分别出现在点击下方、左边、右边同样交换两个拼图块实现移动。其他情况如点击了某个拼图块而这个拼图块相邻位置上没有空白则不满足条件是不会进行任何操作的。
          其实,上面的四个if条件在拼图时是不会有同时满足的情况的,上述代码中的形式需要判断四次条件虽然没错但显得有点多余,在编程时写成嵌套的if...elseif...end形式则更加合理,这里不这么写是为了防止可能有初学的朋友容易混淆而出错,熟悉的朋友可以自行改写。

          movejig( )函数实现的是标记矩阵中相应元素的移动,结合前面编写的显示函数drawmap( )将得到的移动后的标记作为输入就可以显示移动后的拼图了。

    3.5 打乱拼图

          游戏开始时需要将一幅完整的图片打乱,那又要怎么实现呢?读到这里您可能就知道了,整个拼图游戏的实现主要是通过标记矩阵进行操作的,即改变标记矩阵,然后按标记矩阵将拼图显示出来。对此前面已多有铺垫,通过打乱标记矩阵的元素的方法打乱拼图自然也就水到渠成。

          一个简单的想法是,生成一个由0-8的数字随机排列构成的3*3的矩阵作为标记矩阵如下面的矩阵Tag1

          然而这样存在的bug是随机产生的矩阵其实大多通过移动拼图是不能完成正确排序的,这在数学上有相关理论研究可得出结论,不研究数学的笔者就不多说了。这里我的想法是模仿人手动打乱拼图的方式,不断随机移动拼图直至拼图顺序完全打乱。前面介绍了移动拼图的函数,这里随机产生点击的行列数然后调用移动拼图函数movejig( ),重复一定次数则可完成拼图打乱。定义一个打乱拼图函数Disrupt( ),返回一个仿手动打乱之后得到的标记矩阵,MATLAB代码如下
    function y = Disrupt()
    %% 随机打乱原拼图排列顺序
    y =[1,2,3;4,5,6;7,8,0];
    for i = 1:360
        row=randi([1,3]);%产生一个范围在1到3的整数
        col=randi([1,3]);
        y=movejig(y,row,col);%按随机产生的动作打乱拼图
    end
          代码第3行,默认y是一个顺序正确的3*3的矩阵,在第5行for循环中设置移动的次数为360次,每次都产生一个行数row和列数col,其值为1,2,3中随机的一个数,然后第8行调用movejig( )移动拼图。这就仿佛一个人不断在拼图上随机地点击很多次,由于点击拼图块,相应位置上的拼图块就会不断移动。这样做虽然每次盲目产生的row和col不一定都有效,但次数多了无疑会起到打乱拼图的作用。
          接下来就可以测试一下了,新建一个m文件,命名为jigsaw.m,在文件中输入如下代码
    function jigsaw()
    %% 主函数
    Tag_A= Disrupt()%将图像的排列顺序打乱
    drawmap(Tag_A);%按照标记矩阵显示拼图
    
    
    function tag=movejig(tag,row,col)
     %% 4个if分4种情况对不同位置处的点坐标与矩阵行列式统一
        num = tag(row,col);%鼠标位置与号码牌一致
        if (row > 1)&&(tag(row-1,col)==0)%点击位置在第二或第三行,空白块在点击位置的上一行
            tag(row-1,col) = num;%交换两个位置上的值
            tag(row,col) = 0;
        end
        if (row < 3)&&(tag(row+1,col)==0)%点击位置在第一或第二行,空白块在点击位置的下一行
            tag(row+1,col) = num;
            tag(row,col) = 0;
        end
        if (col > 1)&&(tag(row,col-1)==0)%点击位置在第二或第三列,空白块在点击位置的左边一列
            tag(row,col-1) = num;
            tag(row,col) = 0;
        end
        if (col < 3)&&(tag(row,col+1)==0)%点击位置在第二或第三列,空白块在点击位置的右边一列
            tag(row,col+1) = num;
            tag(row,col) = 0;
        end
       
    
    function y = Disrupt()
    %% 随机打乱原拼图排列顺序
    y =[1,2,3;4,5,6;7,8,0];
    
    for i = 1:360
        row=randi([1,3]);%产生一个范围在1到3的整数
        col=randi([1,3]);
        y=movejig(y,row,col);%按随机产生的动作打乱拼图
    end
    
    
    
    function x = choose(image,index)
    %% 根据索引选择对应位置上的拼图块
    if index>0 %标记为1,2,3,4,5,6,7,8的拼图块
        % 计算出行数row以及列数column
        row=fix((index-1)/3);
        column=mod(index-1,3);
        % 分割出对应拼图块数据
        x=image(1+row*100:100*(row+1),1+column*100:100*(column+1),:);
    else
        x=uint8(255*ones(100,100,3));%拼图块0矩阵数据
    end
    
    
    function drawmap(A)
    %% 将运算数字与对应拼图对应显示图片
    origin=imread('jigsawImage.jpeg');
    image=origin;
    % 对要显示的拼图进行赋值
    for row=1:3
        for col=1:3
        image(1+(row-1)*100:100*row,1+(col-1)*100:100*col,:)=choose(origin,A(row,col));
        end
    end
    imshow(image)%显示拼图
        运行结果如图3.5.1所示

                                                     

                                                                                图3.5.1 打乱拼图测试

    3.6 拼图主函数

          主函数是完成拼图任务的核心,主函数的设计思路是首先将标记矩阵打乱,并按照标记矩阵中的排列显示拼图块,然后需要获得鼠标点击处的位置坐标以移动拼图,每次移动后判断拼图顺序是否已经正确,顺序正确后结束游戏。前面已经完成了标记矩阵的打乱和显示以及根据鼠标位置移动拼图的函数,那么现在的问题就剩下鼠标位置的获取了。

          MATLAB中获取鼠标坐标值有两种途径。第一种是利用ginput( )函数,该函数提供一个十字光标帮助更精确选择所需要的位置并返回坐标值,函数调用形式如[x y]=ginput(1),x,y分别为横纵坐标。这确实为一个简单实用的方法,开始时我就是采用的这种方式,在主函数中利用while循环重复调用ginput( )函数获取每次点击处的坐标值,结果证明是可行的,效果如下图3.6.1所示。

    图3.6.1 利用ginput( )的方式实现效果

          这样的类似方法网上提到很多,让我感觉不太好的一点是这个十字光标精确是精确,但是在这个拼图中显得有些多余,怎么删去这个光标寻找半天,未果。还有就是用while循环编写的代码每次关掉这个图形窗口都会甩出一个大大的类似下图那样的错误,这就让有点小小处女座的我不能忍了,转而编写了另外一个版本。

          第二种方法是利用figure的WindowButtonDownFcn属性定义一个坐标获取的回调函数。当在图上按下鼠标的时候,就会自动执行回调函数来获取坐标值。主函数命名为jigsaw( ),与文件名一致,其代码如下

    function jigsaw()
    %% 主函数
    Tag_A= Disrupt();%将标记矩阵的排列顺序打乱
    drawmap(Tag_A);%按照标记矩阵显示拼图
    
    global Tag;%Tag是标记矩阵,定义成全局变量,方便传递参数
    Tag=Tag_A;
    set(gcf,'windowButtonDownFcn',@ButtonDownFcn);%点击鼠标时调用ButtonDownFcn函数

          代码第3,4行调用前面的函数打乱和显示,不必多说;第6,7行意在将标记矩阵定义成全局变量并赋值为Tag_A,其目的在于在后面的回调函数中需要用到标记矩阵,这样方便传递参数,这里可以不必深究;第8行就是设置windowButtonDownFcn属性的回调函数,gcf表示当前图形窗口句柄,ButtonDownFcn是回调函数名,@ButtonDownFcn表示其函数句柄,整条代码就是设置当在当前图形窗口中点击鼠标时就会转而执行ButtonDownFcn函数。

    3.7 回调函数

          根据上一节的设置每次点击鼠标时就会执行一次回调函数,因此可以在回调函数中编写程序获取当前鼠标位置并据此移动一次拼图,然后判断拼图是否完成。定义回调函数ButtonDownFcn( ),输入参数src、event为系统约定变量,函数代码如下

    function ButtonDownFcn(src,event)
    %% 回调函数,鼠标点击事件发生时调用
    pt=get(gca,'CurrentPoint');%获取当前鼠标点击位置坐标
    xpos=pt(1,1);%鼠标点击处的横坐标实际值
    ypos=pt(1,2);%鼠标点击处的纵坐标实际值
       
    col = ceil(xpos/100);%将横坐标值转换为列数
    row = ceil(ypos/100);%将纵坐标值转换为行数
    
    global Tag; %全局变量声明
    
    if(col<=3&&col>0)&&(row<=3&&row>0)%鼠标点击位置在有效范围内    
        Tag=movejig(Tag,row,col);%按点击位置移动拼图
        
        drawmap(Tag)%显示拼图
        
        order = [1 2 3;4 5 6;7 8 0];%顺序矩阵
        zt = abs(Tag-order);%比较两个矩阵
        if sum(zt(:))==0 %顺序已经完全吻合
            image=imread('jigsawImage.jpeg');
            imshow(image) %游戏完成,补全拼图
            msgbox('You did a good job ,恭喜完成!!!') %提示完成信息
            pause(0.5);%延迟半秒
            close all %游戏结束,关闭所有图像窗口
        end
        
    else
        return
        
    end

          代码第3行利用get( )获取鼠标位置坐标,gca表示获取当前坐标系句柄即在当前坐标系中获取坐标值,CurrentPoint是当前点属性值,返回的pt为一行两列的数组,其元素分别为横纵坐标值。第4,5行是分别取出横纵坐标值,第7,8行代码求出行列数,因为图片的尺寸为300*300,分成3行3列,所以将鼠标坐标值除以100即可得出所在的行列数,如果选取的图片为其他尺寸应除以其他相应的数字。

          第10行声明全局变量Tag,在3.6节中定义过了,这里再度声明表示与前面定义的一致,Tag共享其数值,前面的标记矩阵这里就能使用了。

          第12行中,判断点击鼠标的位置是不是在拼图中,因为在图形窗口中点击时若点击位置不在图片上时,返回的坐标值会出现异常值,避免的方法是只有行列数在1到3内的点击才进行处理,不满足条件时跳至第27行返回,结束本次回调函数的运行。

          第13,15行按照点击的行列数移动拼图的标记矩阵,然后按照标记矩阵显示拼图。第17行定义一个顺序矩阵用于标记矩阵的比较,第18行将移动后的标记矩阵与顺序矩阵相减取绝对值,abs( )为取绝对值函数,可以知道如果两个矩阵完全一致则相减之后的结果每个位置上的元素都为0,反之不然。

          第19行中,if sum(zt(:))==0即如果zt中所有元素的和等于0,满足这个条件时表示游戏完成了,此时将拼图空白的那块补全,第20,21行读取原图然后显示,这一过程连续起来的瞬间就表现为拼图空白处被补全了。

          第22行弹出一个提示窗口,展示的信息为“You did a good job ,恭喜完成!!!”。第23行暂停0.5秒,第24行在暂停半秒之后关闭所有图形窗口,游戏结束。

    4.完整代码

          所有工作完成,完整的程序m文件以及图片文件已经上传大家可以点击链接下载基于MATLAB的拼图游戏,直接打开jigsaw.m文件即可运行程序。下面是完整的MATLAB代码,大家可以自行新建jigsaw.m文件,复制以下代码至文件中同样可以运行拼图程序。注意下载jigsawImage.jpeg图片文件,与jigsaw.m文件放在同一文件夹下。注意,如更换图片需要适当修改程序。

    %% 制作人:吴限
    % 2018年2月14日
    function jigsaw()
    %% 主函数
    Tag_A= Disrupt();%将标记矩阵的排列顺序打乱
    drawmap(Tag_A);%按照标记矩阵显示拼图
    
    global Tag;%Tag是标记矩阵,定义成全局变量,方便传递参数
    Tag=Tag_A;
    set(gcf,'windowButtonDownFcn',@ButtonDownFcn);%点击鼠标时调用ButtonDownFcn函数
    
    
    
    function ButtonDownFcn(src,event)
    %% 回调函数,鼠标点击事件发生时调用
    pt=get(gca,'CurrentPoint');%获取当前鼠标点击位置坐标
    xpos=pt(1,1);%鼠标点击处的横坐标实际值
    ypos=pt(1,2);%鼠标点击处的纵坐标实际值
       
    col = ceil(xpos/100);%将横坐标值转换为列数
    row = ceil(ypos/100);%将纵坐标值转换为行数
    
    global Tag; %全局变量声明
    
    if(col <= 3 && col >0)&&(row <= 3&&row > 0)%鼠标点击位置在有效范围内    
        Tag=movejig(Tag,row,col);%按点击位置移动拼图
        
        drawmap(Tag)%显示拼图
        
        order = [1 2 3;4 5 6;7 8 0];%顺序矩阵
        zt = abs(Tag-order);%比较两个矩阵
        if sum(zt(:))==0 %顺序已经完全吻合
            image=imread('jigsawImage.jpeg');
            imshow(image) %游戏完成,补全拼图
            msgbox('You did a good job ,恭喜完成!!!') %提示完成信息
            pause(0.5);%延迟半秒
            close all %游戏结束,关闭所有图像窗口
        end
        
    else
        return
        
    end
    
    
    
    
    function tag=movejig(tag,row,col)
     %% 4个if分4种情况对不同位置处的点坐标与矩阵行列式统一
        num = tag(row,col);%鼠标位置与号码牌一致
        if (row > 1)&&(tag(row-1,col)==0)%点击位置在第二或第三行,空白块在点击位置的上一行
            tag(row-1,col) = num;%交换两个位置上的值
            tag(row,col) = 0;
        end
        if (row < 3)&&(tag(row+1,col)==0)%点击位置在第一或第二行,空白块在点击位置的下一行
            tag(row+1,col) = num;
            tag(row,col) = 0;
        end
        if (col > 1)&&(tag(row,col-1)==0)%点击位置在第二或第三列,空白块在点击位置的左边一列
            tag(row,col-1) = num;
            tag(row,col) = 0;
        end
        if (col < 3)&&(tag(row,col+1)==0)%点击位置在第二或第三列,空白块在点击位置的右边一列
            tag(row,col+1) = num;
            tag(row,col) = 0;
        end
       
    
    
    function y = Disrupt()
    %% 随机打乱原拼图排列顺序
    y =[1,2,3;4,5,6;7,8,0];
    
    for i = 1:360
        row=randi([1,3]);%产生一个范围在1到3的整数
        col=randi([1,3]);
        y=movejig(y,row,col);%按随机产生的动作打乱拼图
    end
    
    
    
    function x = choose(image,index)
    %% 根据索引选择对应位置上的拼图块
    if index > 0 %标记为1,2,3,4,5,6,7,8的拼图块
        % 计算出行数row以及列数column
        row=fix((index-1)/3);
        column=mod(index-1,3);
        % 分割出对应拼图块数据
        x=image(1+row*100:100*(row+1),1+column*100:100*(column+1),:);
    else
        x=uint8(255*ones(100,100,3));%拼图块0矩阵数据
    end
    
    function drawmap(A)
    %% 将运算数字与对应拼图对应显示图片
    origin=imread('jigsawImage.jpeg');
    image=origin;
    
    % 对要显示的拼图进行赋值
    for row=1:3
        for col=1:3
        image(1+(row-1)*100:100*row,1+(col-1)*100:100*col,:)=choose(origin,A(row,col));
        end
    end
    
    imshow(image)%显示拼图

     

    5.反思与总结

     

          MATLAB大多用于数据处理以及工程计算,几乎很少有用MATLAB编写游戏的,这也符合实际情况因为编写游戏毕竟不是MATLAB的专长,其实MATLAB发展至今已经成为一个足够完善的编程语言许多java,c中的功能在MATLAB中同样也能实现。例如MATLAB具有高级图形处理功能,可以通过图形对象的属性完成许多复杂工作,前面鼠标位置的获取用的就是这一功能。

          整个拼图游戏的设计其实可以看成一个数学建模过程,代表拼图块的标记矩阵就是我们建立的模型,通过对这个模型矩阵元素排序问题的求解、分析最终完成这个拼图任务。更多数学建模的知识大家可以自行上网搜索。

          纵观整个拼图游戏的编写,在基本功能上可以改进与提高的地方如下

    一、增加自行挑选设置拼图所用图片的功能。

    二、增加拼图的难度,设计4*4或5*5甚至更复杂的拼图。

    三、利用MATLAB的GUI功能,为游戏设计一个好看的用户图形界面。

    四、设计一个一键完成拼图的算法,让程序以最优步数自行移动拼图完成拼图。实现上可采用原始的方法或者机器学习的算法。

    五、拼图块的形状与移动方式上也可不必局限这一种,稍作创新兴许一个新型的游戏就会被创造出来。

          关于上述的改进之处,有机会将在后面的博文中介绍。

     

    6. 结束语

          这就是利用MATLAB进行拼图游戏编写的全部内容了,虽然拼图游戏本身不足为奇,但利用MATLAB编写的完整程序不多,而且或多或少会有一点小bug,本博文介绍的方法在多次修改之下,其程序严谨没有错误。由于编者能力有限,代码即使经过了多次校对,也难免会有疏漏之处。希望您能热心指出其中的错误,以便下次修改时能以一个更完美更严谨的样子,呈现在大家面前。同时如果有更好的实现方法也请您不吝赐教。

    【公众号获取】
    本人微信公众号已创建,扫描以下二维码并关注公众号“AI技术研究与分享”,后台回复“JP20180210”即可获取全部资源文件。

     

    展开全文
  • QT 拼图游戏

    2021-01-17 20:30:52
    QT 拼图游戏
  • jquery拼图游戏简单的鼠标拖拽移动拼图游戏源码下载 jquery拼图游戏简单的鼠标拖拽移动拼图游戏源码下载
  • 易语言拼图游戏源码,拼图游戏,子画板初始,刷新列表,开始,缩放参照图,画图片,片24,片48,分割,判断成败,加密,解密
  • 地图拼图游戏

    2020-12-02 03:43:04
    地图拼图游戏是一款基于jQuery实现的简单鼠标拖拽移动拼图游戏源码。 游戏介绍: 1、点击游戏难度以更改 2、点击开始游戏,打乱图片 3、交换图片位置,复原图片

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,351
精华内容 2,140
关键字:

拼图游戏