• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

基于MATLAB的小游戏(puzzle)

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

游戏画面:

注:文章开始写的时候还没有考虑到高阶的扩展的问题,高阶扩展部分的代码更具有普遍性,可以直接跳过前面的代码设计看高阶扩展部分。

游戏介绍

游戏玩法来源

游戏的玩法规则并非原创,最早看到是多年前百度魔方吧有人制作了可以在网页上玩的版本,同时还制作了4×4版本。网页连接很早之前就失效,作者也不可考。感谢作者制作了这样一个有意思的小游戏。

设计参考

主要来自桥上风景楼上的你的博客,基于MATLAB的拼图游戏设计(图文详解,附完整代码)https://blog.csdn.net/qq_32892383/article/details/79219110

两个游戏有很多相似之处,这篇文章条理很清晰,对于整个编写过程带来了很大帮助。

规则和功能

小游戏的基本规则:如图所示,点击某一个方块自身及相邻的方块数值加一,到达最大值(设定的是9)循环回最小值(1),最小值的索引不是0而是1对应了Matlab的索引下标是1起始。
游戏目标:通过点击方块使所有的方块的值相同。
当玩家完成后显示步数和胜利语句。

一些定义

游戏画面有长宽两个维度,同时还有数字循环的次数。
仿照魔方将维度定义为m×n,可以在后续看到这个游戏的长宽不一定需要相等。
将一个循环中有的数字个数称之为深度。

设计逻辑

仿照桥上风景楼上的你的文章也画了一张流程图。两个游戏的基本逻辑相似。

前期准备


首先是绘制这几张图片(我是用画图软件画的)。
然后用imread()读入Matlab,直接拼接成一长条。

img=[];
for k=1:9
    img=[img,imread(strcat(num2str(k),'.png'))];
end
imshow(img)%显示
imwrite(img,'puzzle1.png');%输出到文件目录


本来打算用小方块来实现,后来发现拼合成长条直接用也很方便。使用长条形的素材,构造一个GetImg函数以获取第k个数字的图片。

function X=GetImg(img,k)
%取出第k张图
  X=img(:,100*(k-1)+1:100*k,:);
end

绘制游戏画面

游戏画面的9个数字可以用一个3×3的矩阵来标记。绘制游戏画面的函数drawmap就依靠得到的状态,也就是3×3的标记矩阵来显示,代码如下

function drawmap(A)
im=imread('puzzle1.png');
img=uint8(zeros(300,300,3));%预分配,且类型需要为unint8
% 对要显示的图片进行赋值
for row=1:3
    for col=1:3
        img(1+(row-1)*100:100*row,1+(col-1)*100:100*col,:)=GetImg(im,A(row,col)); %将A矩阵中的数字和图片对应
    end
end
imshow(img)%显示画面
end

用以下语句测试,矩阵A和显示的每个数字都对应

A=[1,2,3;4,5,6;7,8,9]
drawmap(A);

打乱函数

虽然没有严格的证明,但是经验得出(没有遇到随机的打乱无法复原的情况)任意状态应该都是可以复原的,所以打乱就可以很简单用随机生成的9个数字来实现,并不需要模拟人为打乱。任意状态是否都能复原还有待证明。

function A=Disrupt()
A=unidrnd(9,3,3);随机生成值在19之间的3×3矩阵
drawmap(A);
end

主函数和规则

Matlab提供了两种获取鼠标坐标的途径,一个是ginput()函数,会有一个十字光标进行定位;另一个利用figure的WindowButtonDownFcn属性,在figure界面点击鼠标可以调用回调函数,实现一些功能。游戏的主函数如下

function puzzle()
%% 主函数
loading()%开场动画
global Tag;%Tag是标记矩阵,定义成全局变量,方便传递参数 
Tag=Disrupt();%将标记矩阵的排列顺序打乱Tag_A;
global count;%计算步数,也设为全局变量
count=0;
set(gcf,'windowButtonDownFcn',@ButttonDownFcn);%点击鼠标时调用ButttonDownFcn函数

鼠标点击时调用ButttonDownFcn函数,ButttonDownFcn函数中实现游戏的规则和画面的刷新,比较偷懒没有分模块来写,所有都写在一块了。图像的大小时300×300,所以坐标值/100向上取整(ceil函数)转化为矩阵对应的坐标。用四个if语句来实现了规则。最后检测是否胜利,可以用标记矩阵和k*ones(3,3)比较,也可以逐项比较每个元素是否相等。达成胜利条件,输出包含还原步数的消息。

function ButttonDownFcn(src,event)
pt = get(gca,'CurrentPoint');
y = uint8(ceil(pt(1,1)/100));
x = uint8(ceil(pt(1,2)/100));%获取点击的位置转化为矩阵中的坐标
global Tag
global count%声明全局变量

if x>=1&&x<=3&&y>=1&&y<=3%点击位置满足才执行
    count=count+1;%计算步数
    Tag(x,y)=Tag(x,y)+1;
    if x<3 %四个if实现规则
        Tag(x+1,y)=Tag(x+1,y)+1;
    end
    if x>1
        Tag(x-1,y)=Tag(x-1,y)+1;
    end
    if y<3
        Tag(x,y+1)=Tag(x,y+1)+1;
    end
    if y>1
        Tag(x,y-1)=Tag(x,y-1)+1;
    end
    Tag(Tag==10)=1 ;
end
drawmap(Tag);

%胜利条件
for k=1:9
    if Tag==uint8(k*ones(3,3))
        msgbox(strcat(num2str(count),' !You win!')); %提示完成信息
        pause(5);%延迟
        close all %游戏结束,关闭所有图像窗口

    end
end
end

至此所有的工作都完成了,运行puzzle()函数就能可以玩了。

谜题解法

我的解法利用了它的对称性,优化一下步数大概在40-60之间。欢迎交流解法。
叙述可能有些繁杂,有时间再详写。

扩展

以上整个3×3的拼图就算是完成了,但是功能并不灵活。经过一些小修改可以实现十分丰富的玩法。

更多阶数和深度

在这个代码的基础上制作更高阶的版本也非常容易实现。在puzzle()函数中引入3个全局变量dim_x,dim_y和depth,编写setdim()分别来设置维数。

puzzle.m修改如下:

function puzzle()
%% 主函数
%loading()%开场动画
global Tag;%Tag是标记矩阵,定义成全局变量,方便传递参数 
global count;%计算步数,也设为全局变量
count=0;
global dim_x;
global dim_y;
global depth;%维度
[dim_x,dim_y,depth]=setdim(4,4,6);
Tag=Disrupt();%将标记矩阵的排列顺序打乱Tag_A;
set(gcf,'windowButtonDownFcn',@ButtonDownFcn);%点击鼠标时调用ButttonDownFcn函数

setdim()函数如下:

function [dim_x,dim_y,depth]=setdim(x,y,d)
dim_x=x;
dim_y=y;
depth=d;
end

同时在其他的函数中也需要进行一些修改
绘制画面根据dim_x,dim_y绘制画面,

function drawmap(A)
im=imread('puzzle1.png');
img=uint8(zeros(100,100,3));%预分配,且类型需要为unint8
global dim_x
global dim_y
% 对要显示的拼图进行赋值
for row=1:dim_x
    for col=1:dim_y
        img(1+(row-1)*100:100*row,1+(col-1)*100:100*col,:)=GetImg(im,A(row,col)); 
    end
end
imshow(img)%显示
end

标记矩阵的数值变为1到depth,维度为dim_x,dim_y

function A=Disrupt()
global dim_x;
global dim_y;
global depth
A=unidrnd(depth,dim_x,dim_y);
drawmap(A);
end

回调函数,基本是将3×3的3换成dim_x,dim_y,当数字超过depth变为1,胜利条件变为与1到depth的dim_x×dim_y的矩阵比较。

function ButttonDownFcn(src,event)
pt = get(gca,'CurrentPoint');
y = uint8(ceil(pt(1,1)/100));
x = uint8(ceil(pt(1,2)/100));
global Tag
global count
global dim_x
global dim_y
global depth
if x>=1&&x<=dim_x&&y>=1&&y<=dim_y%点击位置满足才执行
    count=count+1;%计算步数
    Tag(x,y)=Tag(x,y)+1;
    if x<dim_x %四个if实现规则
        Tag(x+1,y)=Tag(x+1,y)+1;
    end
    if x>1
        Tag(x-1,y)=Tag(x-1,y)+1;
    end
    if y<dim_y
        Tag(x,y+1)=Tag(x,y+1)+1;
    end
    if y>1
        Tag(x,y-1)=Tag(x,y-1)+1;
    end
    Tag(Tag==depth+1)=1 ;
end
drawmap(Tag);

%胜利条件
for k=1:depth
    if Tag==uint8(k*ones(dim_x,dim_y))
        msgbox(strcat(num2str(count),' !You win!')); %提示完成信息
        pause(5);%延迟
        close all %游戏结束,关闭所有图像窗口

    end
end
end

只需修改这几个小地方,通过setdim()即可调整设置不同维度

2×2×7

3×3×2

这个换成0和1可能更好一点

更高阶


不等阶

修改规则

经过修改回调函数可以实现别的规则,新规则同样适用于不同的阶数

1.注释掉回调函数的第10行

    %Tag(x,y)=Tag(x,y)+1;

规则就改为被点击的方块数值不发生变化,相邻方块数值加1。


2.更改为被点击的方块所在的行列+1
代码修改为

if x>=1&&x<=dim_x&&y>=1&&y<=dim_y%点击位置满足才执行
    count=count+1;%计算步数
    Tag(x,y)=Tag(x,y)+1;
    if x<dim_x %四个if实现规则
        Tag(x+1:dim_x,y)=Tag(x+1:dim_x,y)+1;
    end
    if x>1
        Tag(1:x-1,y)=Tag(1:x-1,y)+1;
    end
    if y<dim_y
        Tag(x,y+1:dim_y)=Tag(x,y+1:dim_y)+1;
    end
    if y>1
        Tag(x,1:y-1)=Tag(x,1:y-1)+1;
    end
    Tag(Tag==depth+1)=1 ;
end

效果如下:

3.其他
当然也可以设计成以上两种规则的混合,还可以设计成对角相加,或者其他一些更奇怪的规则。胜利条件也可以被设置得更苛刻,比如只有都为1才算时胜利。未来会加入更多的修改。

问题和改进

  • 对于新规则,打乱可能存在问题,没有严格的证明其他任意阶数的任意状态和任意规则都可以复原
  • 规则部分没有模块化,不便于修改规则,可以设置一个函数句柄,来对规则进行选择
  • 缺少一个GUI界面以便选择更多模式,记录最好成绩等
  • 可以考虑增加一个计时模式
  • 对于某一规则可以写一个通用解法
  • 欢迎提出建议或意见

后记

算是写出的第一个真正完善的程序,程序语句可能也不够优雅。游戏解法方面,除了最原始的规则和阶数,目前对于更高阶数和新设计的解法也没什么好的解法,运气好可能能解出来。

欢迎交流解法和游戏相关规则设计的问题。


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
Delphi资源文件(.res)发布时间:2022-07-18
下一篇:
Delphi调用IE打开网页发布时间:2022-07-18
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap