更新:
没有素材或者.mat文件看着博客也比较难实现,下面是完整的游戏文件
链接:https://pan.baidu.com/s/1CH_vFQQ_m2rIXde-VtkPWg
提取码:uo2x
游戏画面:
注:文章开始写的时候还没有考虑到高阶的扩展的问题,高阶扩展部分的代码更具有普遍性,可以直接跳过前面的代码设计看高阶扩展部分。
游戏的玩法规则并非原创,最早看到是多年前百度魔方吧有人制作了可以在网页上玩的版本,同时还制作了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);随机生成值在1到9之间的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()即可调整设置不同维度
经过修改回调函数可以实现别的规则,新规则同样适用于不同的阶数
1.注释掉回调函数的第10行
%Tag(x,y)=Tag(x,y)+1;
规则就改为被点击的方块数值不发生变化,相邻方块数值加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才算时胜利。未来会加入更多的修改。
算是写出的第一个真正完善的程序,程序语句可能也不够优雅。游戏解法方面,除了最原始的规则和阶数,目前对于更高阶数和新设计的解法也没什么好的解法,运气好可能能解出来。
欢迎交流解法和游戏相关规则设计的问题。