自制的MATLAB拼图游戏GUI界面版详解(下篇)
摘要:这篇博文在早前本人写的介绍拼图游戏的基础上推出带有GUI用户界面的增强版,这里将通过上、中、下三篇博文详细介绍利用MATLAB GUI设计的拼图游戏完整实现过程,每篇都会附上相应代码及解释。下篇主要讲解拼图游戏中开始游戏并点击拼图块最终完成拼图的内部逻辑。下篇的要点如下:
1. 前言
这篇博文紧接着前面一篇继续介绍MATLAB GUI拼图游戏的详细过程,这一篇终于到了介绍最后的拼图逻辑功能实现的部分了。本篇博文中拼图逻辑的代码与早前的基础版本相似,在其基础上做了一些改进使其能适应新加入的功能。因为GUI界面版内容较多,这里限于篇幅分成了上、中、下连续三篇依次讲解,可点击跳转查看拼图游戏完整代码。希望本文能给对MATLAB GUI界面设计或小游戏感兴趣的朋友有所启发。
2. 开始并完成拼图
简单来说,我们这一节实现的功能是:点击“开始拼图”按钮后,在拼图区点击拼图块能够移动拼图并最终能够完成游戏,得到完整的拼好的拼图。效果如下:
这里自上而下逐个函数讲解,首先和上篇的方法一样,我们打开之前设计好的包含GUI界面的fig文件,选中开始拼图的按钮,右击选择“查看回调”,点击“Callback”即可跳转至该函数的定义编辑文件中,在该函数中添加如下代码:
% --- 按钮pushbutton_run被点击时执行.
function pushbutton_run_Callback(hObject, eventdata, handles)
% 获取选中的图片绝对路径
file_name=get(handles.edit_path,'String');
if exist(file_name,'file')==0 % 不存在则读取默认图片
pic_data=imread('jigsawImage.jpeg');
else
pic_data=imread(file_name);
end
n=get(handles.popupmenu_rank,'value'); % 获取下拉选择框的值
rank_Tag=n+2;% 计算选择的拼图阶数
% 确定每块拼图长宽
len=min([size(pic_data,1),size(pic_data,2)]);
len_col=round(len/rank_Tag);
len_row=round(len/rank_Tag);
% 转换图片为正方形
pic_data=imresize(pic_data,[rank_Tag*len_col rank_Tag*len_row]);
jigsaw(handles,rank_Tag, pic_data);% 调用拼图游戏
以上代码中,第3-18行其目的是为了获得当前初始的图片数据pic_data、拼图的阶数rank_Tag及所有控件的句柄handles,第19行调用拼图函数jigsaw,这个函数之前没有创建,这里新建一个m文件命名为jigsaw,在里面添加实现拼图逻辑的代码。
3. 拼图主函数介绍
3.1 分割拼图并显示
在编写拼图游戏主函数前,先写几个主函数中要用到的子函数,这部分先在jigsaw.m文件中添加两个用于分割拼图并显示的函数。这节代码与前面的基础版本基本类似,与前面的思路相同,将整幅图像按阶数分成对应份数并用数字表示。不过这里因选择的图片尺寸不一,因此需要先计算每块拼图的宽高,可由图片的整体尺寸除以拼图阶数求得;同时因为不是默认的单一坐标系,在显示拼图的时候为避免混乱需要指定显示拼图的坐标轴。将图片分割并显示的函数的代码如下:
function x = choose(image,index,pic_data,rank_Tag)
%% 根据索引选择对应位置上的拼图块
len_row=size(pic_data,1)/rank_Tag; % 每块拼图的宽度
len_col=size(pic_data,2)/rank_Tag; % 每块拼图的高度
if index>0 %标记为1,2,3,4,5,6,7,8的拼图块
% 计算出行数row以及列数column
row=fix((index-1)/rank_Tag);
column=mod(index-1,rank_Tag);
% 分割出对应拼图块数据
x=image(1+row*len_row:len_row*(row+1),1+column*len_col:len_col*(column+1),:);
else
x=uint8(255*ones(len_col,len_row,3));% 拼图块0矩阵数据
end
function drawmap(A,handle,pic_data,rank_Tag)
% A:当前标签矩阵
% handle:图形句柄
% pic_data:图片数据
% rankTag:拼图阶数
%% 将运算数字与对应拼图对应显示图片
origin=pic_data;
len_row=size(pic_data,1)/rank_Tag; % 每块拼图的宽度
len_col=size(pic_data,2)/rank_Tag; % 每块拼图的高度
% 对要显示的拼图进行赋值
for row=1:rank_Tag
for col=1:rank_Tag
pic_data(1+(row-1)*len_row:len_row*row,1+(col-1)*len_col:len_col*col,:)=choose(origin,A(row,col),pic_data,rank_Tag);
end
end
axes(handle) % 选定坐标轴
image(pic_data) % 显示拼图
set(handle,'Visible','off');% 隐藏坐标轴
在以上代码中,choose函数的输入参数除了image, index还加入了pic_data, rank_Tag,分别是用于拼图的图像数据以及当前拼图阶数。在对图片进行分割时应考虑到拼图的阶数,因此在代码第10行按照拼图块标记选择原图中相应数据进行赋值。drawmap函数中加入了handle表示要显示拼图的坐标轴句柄,以便在第31行指定坐标系。
3.2 移动拼图
为了实现拼图的移动,需要知道鼠标点击的位置,这里鼠标位置所在的拼图块的行列号由row, col传入。判断是否移动拼图的标准是点击位置是否在空白拼图块(标号为0)的4邻域内(行号或列号相差1),因此我们只需比较row, col加或减1的标号矩阵的值是否为0,如为0表示位置相邻则交换两个位置上的值。
值得注意的是为保证程序不会出错,需要先判断row, col的值是否在可点击范围内,同时为了实时显示游戏过程中的移动步数,有必要声明一个变量用于计数。移动拼图块的代码如下:
function tag=movejig(tag,row,col,handle_step)
global steps; % 计步
%% 4个if分4种情况对不同位置处的点坐标与矩阵行列式统一
last_tag=tag;
num = tag(row,col);%鼠标位置与号码牌一致
[max_row,max_col]=size(tag);
% 检测点击位置是否处于0号临域
if (row-1)<=max_row && (row-1)>0 % 点击在范围内
if tag(row-1,col)==0 % 空白块在点击位置的上一行
tag(row-1,col) = num; % 交换两个位置上的值
tag(row,col) = 0;
end
end
if (row+1)<=max_row && (row+1)>0
if tag(row+1,col)==0 % 空白块在点击位置的下一行
tag(row+1,col) = num;
tag(row,col) = 0;
end
end
if (col-1)<=max_col && (col-1)>0
if tag(row,col-1)==0 % 空白块在点击位置的左边一列
tag(row,col-1) = num;
tag(row,col) = 0;
end
end
if (col+1)<=max_col && (col+1)>0
if tag(row,col+1)==0 % 空白块在点击位置的右边一列
tag(row,col+1) = num;
tag(row,col) = 0;
end
end
zt = abs(tag-last_tag); % 比较两个矩阵
if handle_step~=0 && sum(zt(:))~=0 % 矩阵已改变表示,拼图发生移动
if exist('steps','var')
steps=steps+1; % 步数加1
set(handle_step,'String',num2str(steps))% 实时显示步数
end
end
代码通过改变标记矩阵tag后根据tag的值显示拼图对应拼图块达到逻辑和显示上的拼图移动,第33-39行判断拼图是否发生移动并据此记录移动拼图块的步数,并显示在显示步数的文本标签上。
3.3 打乱拼图
打乱拼图采用的是模拟手动打乱拼图的方式,不断随机产生点击位置坐标,利用上面编写的移动拼图函数,不断随机打乱拼图。随机动作的次数应随拼图的阶数而增加,重复一定次数可将拼图完全打乱,返回一个打乱之后的标记矩阵。打乱拼图的代码如下:
function y = Disrupt(rank_Tag)
%% 按人工打乱方式,随机打乱原拼图排列顺序
% y初始为顺序矩阵,用于打乱拼图
y=[1:1:rank_Tag^2-1,0];
y=reshape(y,rank_Tag,rank_Tag);
y=y';
for i = 1:300*rank_Tag % 打乱次数应随阶数增加而增加
row=randi([1,rank_Tag]);% 产生一个范围在1到rank的整数
col=randi([1,rank_Tag]);
y=movejig(y,row,col,0);% 按随机产生的动作打乱拼图
end
3.4 拼图主函数
主函数的设计思路是首先将标记矩阵打乱,并按照标记矩阵中的排列显示拼图块,然后需要获得鼠标点击处的位置坐标以移动拼图,每次移动后判断拼图顺序是否已经正确,顺序正确后结束游戏。获取鼠标点击位置坐标的方法是利用figure的WindowButtonDownFcn属性定义一个坐标获取的回调函数。当在图上按下鼠标的时候,就会自动执行回调函数来获取坐标值。主函数的代码如下:
function jigsaw(handles, rank_Tag, pic_data)
% handles:图形句柄
% rank_Tag:拼图阶数
% pic_data:读入图片的全路径
%% 拼图主函数
Tag_A= Disrupt(rank_Tag);% 将标记矩阵的排列顺序打乱
drawmap(Tag_A,handles.axes_jigsaw,pic_data,rank_Tag);% 按照标记矩阵显示拼图
global flag;% flag决定在拼图区点击是否移动拼图
global Tag; % Tag是标记矩阵,定义成全局变量,方便传递参数
global steps;% steps用于计数,累计移动拼图的步数
% 初始化变量
flag=true; % 开始游戏后,可以点击移动拼图
steps=0;
Tag=Tag_A;
len_row=size(pic_data,1)/rank_Tag; % 每块拼图的宽度
len_col=size(pic_data,2)/rank_Tag; % 每块拼图的高度
mask_number(Tag,handles,len_row,len_col) % 改进选择决定是否显示数字标记
set(handles.text_steps,'String',num2str(steps)) % 显示初始步数
set(gcf,'windowButtonDownFcn',{@ButtonDownFcn,handles,pic_data,rank_Tag}); % 点击鼠标时调用ButtonDownFcn函数
function mask_number(tag,handles,len_row,len_col)
%% 判断是否需要显示数字提示的函数
ismask=get(handles.checkbox_num,'Value');% 获取CheckBox的值(是否勾选)
rank_Tag=size(tag,1);% 拼图阶数
axes(handles.axes_jigsaw);% 确定坐标轴
% 根据是否勾选决定是否显示text标识
for i=1:size(tag,1)
for j=1:size(tag,2)
if ismask
text(len_col/2*(2*j-1)-10,len_row/2*(2*i-1),num2str(tag(i,j)),'FontSize',55-rank_Tag*5,'Color','c')
else
% 未勾选,在该位置设置text为空,实现不显示
text(len_col/2*(2*j-1)-10,len_row/2*(2*i-1),'','FontSize',55-rank_Tag*5,'Color','c')
end
end
end
主函数中调用了mask_number其作用在于根据控件CheckBox的勾选状态决定是否在拼图块上显示数字提示,这个在前面的代码中已多次提及了。第25行是设置windowButtonDownFcn属性的回调函数,gcf表示当前图形窗口句柄,ButtonDownFcn是回调函数名,@ButtonDownFcn表示其函数句柄,后面handles, pic_data, rank_Tag是回调函数的输入参数,整条代码就是设置当在当前图形窗口中点击鼠标时就会转而执行ButtonDownFcn函数,该函数的代码在下节介绍。
4. 鼠标事件回调函数
每次点击鼠标时就会执行一次回调函数,因此可以在回调函数中编写程序获取当前鼠标位置并据此移动一次拼图,然后判断拼图是否完成。定义回调函数ButtonDownFcn( ),输入参数src、event为系统约定变量,handles, pic_data, rank_Tag分别为需要用到的控件句柄、拼图的图片数据、拼图阶数,函数代码如下:
function ButtonDownFcn(src,event,handles,pic_data,rank_Tag)
%% 回调函数,鼠标点击事件发生时调用
global flag;% flag声明,共用全局变量
global Tag; % 全局变量声明
if flag % 若flag为true,允许移动拼图
pt=get(gca,'CurrentPoint'); % 获取当前鼠标点击位置坐标
xpos=pt(1,1); % 鼠标点击处的横坐标实际值
ypos=pt(1,2); % 鼠标点击处的纵坐标实际值
len_row=size(pic_data,1)/rank_Tag; % 每块拼图的宽度
len_col=size(pic_data,2)/rank_Tag; % 每块拼图的高度
col = ceil(xpos/len_row); % 将横坐标值转换为列数
row = ceil(ypos/len_col); % 将纵坐标值转换为行数
% 判断鼠标点击位置是否在有效范围内
if(col<=rank_Tag && col>0)&&(row<=rank_Tag && row>0)
Tag=movejig(Tag,row,col,handles.text_steps); % 按点击位置移动拼图
drawmap(Tag,handles.axes_jigsaw,pic_data,rank_Tag) % 显示拼图
mask_number(Tag,handles,len_row,len_col) % 是否显示数字提示
% order为顺序矩阵,以此判断拼图是否完成
order=[1:1:rank_Tag^2-1,0];
order=reshape(order,rank_Tag,rank_Tag);
order=order';
zt = abs(Tag-order); % 比较两个矩阵
if sum(zt(:))==0 % 顺序已经完全吻合
axes(handles.axes_jigsaw)
% 游戏完成,补全拼图
image(pic_data) % 显示全图
set(handles.axes_jigsaw,'Visible','off');% 隐藏坐标轴
msgbox('You did a good job ,恭喜完成!!!') % 提示完成信息
flag=false;% 游戏已经完成,设置flag为false
end
else % 点击在外部区域
return % 直接返回,不作处理
end
end
【代码解释】
代码第6行先判断全局变量flag的值,决定是否执行下面的操作(是否相应鼠标点击),由前面的代码可知当只有在重新选择图片、选择难度(阶数)的时候flag的值会被置为false来防止误操作,而当点击运行游戏按钮时该值会被重新置为true使程序运行。第7-9行获取鼠标点击处的坐标实际值,我们需要转换成拼图块的行列数,所以第11-15行获得拼图块的长宽,并由此计算行列数。第18行判断鼠标点击位置是否在拼图的显示区域内,第19-20行调用上面的函数按点击位置移动拼图并显示。第25-29行产生一个顺序的矩阵,并将当前的标号矩阵与该矩阵进行比较,以判断拼图是否已拼好。第31-36行在拼图拼好后显示完整图像并提示完成。第37行重新将flag的值置为false在重新开始游戏前不能再移动拼图。
【下载链接】
若您想获得博文中涉及的实现完整拼图功能的全部程序文件(包括图片、fig, m文件等),这里已打包上传至博主的CSDN下载资源中,下载后运行jigsawGUI.m文件即可运行(实现完整功能)。同时已将文件打包编程成exe的可执行文件,可在我的百度网盘中下载后直接运行。文件下载链接如下:
下载链接1:博文中涉及的完整程序文件
下载链接2:拼图游戏打包的可执行EXE文件(已装MATLAB的用户下载)
链接:https://pan.baidu.com/s/1m0qLu3Un4jDT-NyQwFZs1g
提取码:j906
下载链接3:拼图游戏打包的可执行EXE文件(未装MATLAB的用户下载)
链接:https://pan.baidu.com/s/1uCMJI5O0FsnJKyoK4Lcvmw
提取码:a1kb
【公众号获取】
本人微信公众号已创建,扫描以下二维码并关注公众号“AI技术研究与分享”,后台回复“JG20190508”即可获取全部资源文件。
5. 结束语
下篇的讲述就到这里,拼图的全部功能的实现终于全部分享完成,大家也可以参考博主前面的一篇博文:基于MATLAB的拼图游戏设计。由于博主能力有限,博文中提及的方法与代码即使经过测试,也难免会有疏漏之处。希望您能热心指出其中的错误,以便下次修改时能以一个更完美更严谨的样子,呈现在大家面前。同时如果有更好的实现方法也请您不吝赐教。
大家的点赞和关注是博主最大的动力,如果您想要获取博文中的完整代码文件,可通过C币或积分下载,没有C币或积分的朋友可在关注、点赞博文后在评论区留下邮箱,我会在第一时间发送给您。
【完整代码】
这一版本的代码有点多,最后在这里贴上代码供大家参考,下面是完整的MATLAB代码:
jigsawGUI.m:
function varargout = jigsawGUI(varargin)
% JIGSAWGUI MATLAB code for jigsawGUI.fig
% JIGSAWGUI, by itself, creates a new JIGSAWGUI or raises the existing
% singleton*.
%
% H = JIGSAWGUI returns the handle to a new JIGSAWGUI or the handle to
% the existing singleton*.
%
% JIGSAWGUI('CALLBACK',hObject,eventData,handles,...) calls the local
% function named CALLBACK in JIGSAWGUI.M with the given input arguments.
%
% JIGSAWGUI('Property','Value',...) creates a new JIGSAWGUI or raises the
% existing singleton*. Starting from the left, property value pairs are
% applied to the GUI before jigsawGUI_OpeningFcn gets called. An
% unrecognized property name or invalid value makes property application
% stop. All inputs are passed to jigsawGUI_OpeningFcn via varargin.
%
% *See GUI Options on GUIDE's Tools menu. Choose "GUI allows only one
% instance to run (singleton)".
%
% See also: GUIDE, GUIDATA, GUIHANDLES
% Edit the above text to modify the response to help jigsawGUI
% Last Modified by GUIDE v2.5 06-May-2019 15:17:29
% Begin initialization code - DO NOT EDIT
gui_Singleton = 1;
gui_State = struct('gui_Name', mfilename, ...
'gui_Singleton', gui_Singleton, ...
'gui_OpeningFcn', @jigsawGUI_OpeningFcn, ...
'gui_OutputFcn', @jigsawGUI_OutputFcn, ...
'gui_LayoutFcn', [] , ...
'gui_Callback', []);
if nargin && ischar(varargin{1})
gui_State.gui_Callback = str2func(varargin{1});
end
if nargout
[varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
gui_mainfcn(gui_State, varargin{:});
end
% End initialization code - DO NOT EDIT
% --- Executes just before jigsawGUI is made visible.
function jigsawGUI_OpeningFcn(hObject, eventdata, handles, varargin)
% This function has no output args, see OutputFcn.
% hObject handle to figure
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
% varargin command line arguments to jigsawGUI (see VARARGIN)
% Choose default command line output for jigsawGUI
handles.output = hObject;
% Update handles structure
guidata(hObject, handles);
% javaFrame=get(hObject,'javaFrame');
% set(javaFrame,'FigureIcon',javax.swing.ImageIcon('Puzzle_icon.png')) %icon.jpg为指定的图标
% warning off all;
% UIWAIT makes jigsawGUI wait for user response (see UIRESUME)
% uiwait(handles.figure1);
% --- Outputs from this function are returned to the command line.
function varargout = jigsawGUI_OutputFcn(hObject, eventdata, handles)
% varargout cell array for returning output args (see VARARGOUT);
% hObject handle to figure
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
% Get default command line output from handles structure
varargout{1} = handles.output;
function edit_path_Callback(hObject, eventdata, handles)
% hObject handle to edit_path (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
% Hints: get(hObject,'String') returns contents of edit_path as text
% str2double(get(hObject,'String')) returns contents of edit_path as a double
% --- Executes during object creation, after setting all properties.
function edit_path_CreateFcn(hObject, eventdata, handles)
% hObject handle to edit_path (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles empty - handles not created until after all CreateFcns called
% Hint: edit controls usually have a white background on Windows.
% See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
set(hObject,'BackgroundColor','white');
end
% --- 点击图片文件选择按钮(pushbutton_path)时执行.
function pushbutton_path_Callback(hObject, eventdata, handles)
global flag; % 全局变量声明,flag决定是否能点击拼图
% 弹出文件选择框,选择一张图片
[file,path] = uigetfile({'*.jpg;*.jpeg;*.png;*.bmp;*.tif',...
'图片文件 (*.jpg,*.jpeg,*.png,*.bmp,*.tif)'},'选择一张图片');
if isequal(file,0) % 若文件不存在
set(handles.edit_path,'String','请选择一张图片');
else
fileName= fullfile(path,file); % 选择的图片绝对路径
set(handles.edit_path,'String',fileName); % 显示选择的图片路径
pic_data=imread(fileName);
% 判断拼图的阶数
n=get(handles.popupmenu_rank,'value');
rank_Tag=n+2;
% 选取最短那条边作为边长
len=min([size(pic_data,1),size(pic_data,2)]);
len_col=round(len/rank_Tag);% 每个拼图块长度
len_row=round(len/rank_Tag);% 每个拼图块宽度
% 将图片调整为正方形
pic_data=imresize(pic_data,[rank_Tag*len_col rank_Tag*len_row]);
axes(handles.axes_original) % 确定要显示的坐标轴
image(pic_data);% 显示图片
axis off; % 关闭坐标显示
% 显示拼图
axes(handles.axes_jigsaw)
image(pic_data);
axis off;
% 拼图块数字标识
Tag=[1:1:rank_Tag^2-1,0];
Tag=reshape(Tag,rank_Tag,rank_Tag);
Tag=Tag';
% 根据checkbox的是否勾选决定是否显示拼图块数字提示
ismask=get(handles.checkbox_num,'Value');
axes(handles.axes_jigsaw);
for i=1:size(Tag,1)
for j=1:size(Tag,2)
text(len_col/2*(2*j-1)-10,len_row/2*(2*i-1),num2str(Tag(i,j)),'FontSize',55-rank_Tag*5,'Color','c')
end
end
if ~ismask % 若未勾选状态,删除所有text图形
h=findall(gca,'type','text');
delete(h);
end
flag=false;% 重选文件后置flag防止误点击
end
% --- 按钮pushbutton_run被点击时执行.
function pushbutton_run_Callback(hObject, eventdata, handles)
% 获取选中的图片绝对路径
file_name=get(handles.edit_path,'String');
if exist(file_name,'file')==0 % 不存在则读取默认图片
pic_data=imread('jigsawImage.jpeg');
else
pic_data=imread(file_name);
end
n=get(handles.popupmenu_rank,'value'); % 获取下拉选择框的值
rank_Tag=n+2;% 计算选择的拼图阶数
% 确定每块拼图长宽
len=min([size(pic_data,1),size(pic_data,2)]);
len_col=round(len/rank_Tag);
len_row=round(len/rank_Tag);
% 转换图片为正方形
pic_data=imresize(pic_data,[rank_Tag*len_col rank_Tag*len_row]);
jigsaw(handles,rank_Tag, pic_data);% 调用拼图游戏
% --- Executes on slider movement.
function slider1_Callback(hObject, eventdata, handles)
% hObject handle to slider1 (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
% Hints: get(hObject,'Value') returns position of slider
% get(hObject,'Min') and get(hObject,'Max') to determine range of slider
% --- Executes during object creation, after setting all properties.
function slider1_CreateFcn(hObject, eventdata, handles)
% hObject handle to slider1 (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles empty - handles not created until after all CreateFcns called
% Hint: slider controls usually have a light gray background.
if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
set(hObject,'BackgroundColor',[.9 .9 .9]);
end
% --- 下拉选择框popupmenu_rank被点击时执行.
function popupmenu_rank_Callback(hObject, eventdata, handles)
global flag; % 是否可点击拼图块标识
flag=false; % 下拉框改变,点击开始前不能点击
file_name=get(handles.edit_path,'String');% 文件名
% 读取图片
if exist(file_name,'file')==0
pic_data=imread('jigsawImage.jpeg');
else
pic_data=imread(file_name);
end
% 获取并计算拼图阶数
n=get(handles.popupmenu_rank,'value');
rank_Tag=n+2;
% 计算拼图块长宽
len=min([size(pic_data,1),size(pic_data,2)]);
len_col=round(len/rank_Tag);
len_row=round(len/rank_Tag);
% 统一拼图尺寸
pic_data=imresize(pic_data,[rank_Tag*len_col rank_Tag*len_row]);
% 数字标识
Tag=[1:1:rank_Tag^2-1,0];
Tag=reshape(Tag,rank_Tag,rank_Tag);
Tag=Tag';
% 显示拼图
axes(handles.axes_jigsaw)
image(pic_data);
axis off;
% 根据选择情况决定是否显示数字标识
ismask=get(handles.checkbox_num,'Value');
axes(handles.axes_jigsaw);
for i=1:size(Tag,1)
for j=1:size(Tag,2)
text(len_col/2*(2*j-1)-10,len_row/2*(2*i-1),num2str(Tag(i,j)),'FontSize',55-rank_Tag*5,'Color','c')
end
end
if ~ismask
h=findall(gca,'type','text');
delete(h);
end
% --- Executes during object creation, after setting all properties.
function popupmenu_rank_CreateFcn(hObject, eventdata, handles)
% hObject handle to popupmenu_rank (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles empty - handles not created until after all CreateFcns called
% Hint: popupmenu controls usually have a white background on Windows.
% See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
set(hObject,'BackgroundColor','white');
end
% --- Executes on key press with focus on popupmenu_rank and none of its controls.
function popupmenu_rank_KeyPressFcn(hObject, eventdata, handles)
% hObject handle to popupmenu_rank (see GCBO)
% eventdata structure with the following fields (see UICONTROL)
% Key: name of the key that was pressed, in lower case
% Character: character interpretation of the key(s) that was pressed
% Modifier: name(s) of the modifier key(s) (i.e., control, shift) pressed
% handles structure with handles and user data (see GUIDATA)
% --- Executes on key press with focus on pushbutton_path and none of its controls.
function pushbutton_path_KeyPressFcn(hObject, eventdata, handles)
% hObject handle to pushbutton_path (see GCBO)
% eventdata structure with the following fields (see UICONTROL)
% Key: name of the key that was pressed, in lower case
% Character: character interpretation of the key(s) that was pressed
% Modifier: name(s) of the modifier key(s) (i.e., control, shift) pressed
% handles structure with handles and user data (see GUIDATA)
% --- If Enable == 'on', executes on mouse press in 5 pixel border.
% --- Otherwise, executes on mouse press in 5 pixel border or over pushbutton_path.
function pushbutton_path_ButtonDownFcn(hObject, eventdata, handles)
% hObject handle to pushbutton_path (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
% --- Executes during object creation, after setting all properties.
function axes_original_CreateFcn(hObject, eventdata, handles)
% 创建图形时执行,设置原始图片显示
image(imread('jigsawImage.jpeg'));
set(hObject,'Visible','off','Tag','axes_original');
% --- Executes during object creation, after setting all properties.
function axes_jigsaw_CreateFcn(hObject, eventdata, handles)
% 创建图形时执行,设置拼图区坐标显示拼图原始图片
image(imread('jigsawImage.jpeg'));
set(hObject,'Visible','off','Tag','axes_jigsaw')% 关闭坐标轴显示
% --- checkbox_num按钮被点击时执行.
function checkbox_num_Callback(hObject, eventdata, handles)
global Tag;% 拼图块的数字标识
% 读取图片文件
file_name=get(handles.edit_path,'String');
if exist(file_name,'file')==0
pic_data=imread('jigsawImage.jpeg');
else
pic_data=imread(file_name);
end
% 获取并计算拼图阶数
n=get(handles.popupmenu_rank,'value');
rank_Tag=n+2;
% 如果拼图块标识Tag与当前的拼图阶数不符,根据当前阶数重置
if size(Tag,1)~=rank_Tag||size(Tag,2)~=rank_Tag
Tag=[1:1:rank_Tag^2-1,0];
Tag=reshape(Tag,rank_Tag,rank_Tag);
Tag=Tag';
end
% 计算每个拼图块的长宽
len=min([size(pic_data,1),size(pic_data,2)]);
len_col=round(len/rank_Tag);
len_row=round(len/rank_Tag);
% 根据选择情况决定是否显示数字标识
ismask=get(handles.checkbox_num,'Value');
axes(handles.axes_jigsaw);
for i=1:size(Tag,1)
for j=1:size(Tag,2)
% 在每块拼图的中心位置添加对应数字显示
text(len_col/2*(2*j-1)-10,len_row/2*(2*i-1),num2str(Tag(i,j)),'FontSize',55-rank_Tag*5,'Color','c')
end
end
if ~ismask % 选择不显示,则删除所有text图形
h=findall(gca,'type','text');
delete(h);
end
% --- 关闭窗口figure1时执行.
function figure1_CloseRequestFcn(hObject, eventdata, handles)
clear global;% 清除所有全局变量
delete(hObject);
% --- Executes during object creation, after setting all properties.
function figure1_CreateFcn(hObject, eventdata, handles)
javaFrame=get(hObject,'javaFrame'); % 获取图形句柄
% 为窗口设置图标
set(javaFrame,'FigureIcon',javax.swing.ImageIcon('Puzzle_icon.png')) % Puzzle_icon.png为指定的图标
warning off all; % 忽略警告
jigsaw.m:
%% 制作人:吴限
% 2019年5月6日
function jigsaw(handles, rank_Tag, pic_data)
% handles:图形句柄
% rank_Tag:拼图阶数
% pic_data:读入图片的全路径
%% 拼图主函数
Tag_A= Disrupt(rank_Tag);% 将标记矩阵的排列顺序打乱
drawmap(Tag_A,handles.axes_jigsaw,pic_data,rank_Tag);% 按照标记矩阵显示拼图
global flag;% flag决定在拼图区点击是否移动拼图
global Tag; % Tag是标记矩阵,定义成全局变量,方便传递参数
global steps;% steps用于计数,累计移动拼图的步数
% 初始化变量
flag=true; % 开始游戏后,可以点击移动拼图
steps=0;
Tag=Tag_A;
len_row=size(pic_data,1)/rank_Tag; % 每块拼图的宽度
len_col=size(pic_data,2)/rank_Tag; % 每块拼图的高度
mask_number(Tag,handles,len_row,len_col) % 改进选择决定是否显示数字标记
set(handles.text_steps,'String',num2str(steps)) % 显示初始步数
set(gcf,'windowButtonDownFcn',{@ButtonDownFcn,handles,pic_data,rank_Tag}); % 点击鼠标时调用ButtonDownFcn函数
function ButtonDownFcn(src,event,handles,pic_data,rank_Tag)
%% 回调函数,鼠标点击事件发生时调用
global flag;% flag声明,共用全局变量
global Tag; % 全局变量声明
if flag % 若flag为true,允许移动拼图
pt=get(gca,'CurrentPoint'); % 获取当前鼠标点击位置坐标
xpos=pt(1,1); % 鼠标点击处的横坐标实际值
ypos=pt(1,2); % 鼠标点击处的纵坐标实际值
len_row=size(pic_data,1)/rank_Tag; % 每块拼图的宽度
len_col=size(pic_data,2)/rank_Tag; % 每块拼图的高度
col = ceil(xpos/len_row); % 将横坐标值转换为列数
row = ceil(ypos/len_col); % 将纵坐标值转换为行数
% 判断鼠标点击位置是否在有效范围内
if(col<=rank_Tag && col>0)&&(row<=rank_Tag && row>0)
Tag=movejig(Tag,row,col,handles.text_steps); % 按点击位置移动拼图
drawmap(Tag,handles.axes_jigsaw,pic_data,rank_Tag) % 显示拼图
mask_number(Tag,handles,len_row,len_col) % 是否显示数字提示
% order为顺序矩阵,以此判断拼图是否完成
order=[1:1:rank_Tag^2-1,0];
order=reshape(order,rank_Tag,rank_Tag);
order=order';
zt = abs(Tag-order); % 比较两个矩阵
if sum(zt(:))==0 % 顺序已经完全吻合
axes(handles.axes_jigsaw)
% 游戏完成,补全拼图
image(pic_data) % 显示全图
set(handles.axes_jigsaw,'Visible','off');% 隐藏坐标轴
msgbox('You did a good job ,恭喜完成!!!') % 提示完成信息
flag=false;% 游戏已经完成,设置flag为false
end
else % 点击在外部区域
return % 直接返回,不作处理
end
end
function mask_number(tag,handles,len_row,len_col)
%% 判断是否需要显示数字提示的函数
ismask=get(handles.checkbox_num,'Value');% 获取CheckBox的值(是否勾选)
rank_Tag=size(tag,1);% 拼图阶数
axes(handles.axes_jigsaw);% 确定坐标轴
% 根据是否勾选决定是否显示text标识
for i=1:size(tag,1)
for j=1:size(tag,2)
if ismask
text(len_col/2*(2*j-1)-10,len_row/2*(2*i-1),num2str(tag(i,j)),'FontSize',55-rank_Tag*5,'Color','c')
else
% 未勾选,在该位置设置text为空,实现不显示
text(len_col/2*(2*j-1)-10,len_row/2*(2*i-1),'','FontSize',55-rank_Tag*5,'Color','c')
end
end
end
function tag=movejig(tag,row,col,handle_step)
global steps; % 计步
%% 4个if分4种情况对不同位置处的点坐标与矩阵行列式统一
last_tag=tag;
num = tag(row,col);%鼠标位置与号码牌一致
[max_row,max_col]=size(tag);
% 检测点击位置是否处于0号临域
if (row-1)<=max_row && (row-1)>0 % 点击在范围内
if tag(row-1,col)==0 % 空白块在点击位置的上一行
tag(row-1,col) = num; % 交换两个位置上的值
tag(row,col) = 0;
end
end
if (row+1)<=max_row && (row+1)>0
if tag(row+1,col)==0 % 空白块在点击位置的下一行
tag(row+1,col) = num;
tag(row,col) = 0;
end
end
if (col-1)<=max_col && (col-1)>0
if tag(row,col-1)==0 % 空白块在点击位置的左边一列
tag(row,col-1) = num;
tag(row,col) = 0;
end
end
if (col+1)<=max_col && (col+1)>0
if tag(row,col+1)==0 % 空白块在点击位置的右边一列
tag(row,col+1) = num;
tag(row,col) = 0;
end
end
zt = abs(tag-last_tag); % 比较两个矩阵
if handle_step~=0 && sum(zt(:))~=0 % 矩阵已改变表示,拼图发生移动
if exist('steps','var')
steps=steps+1; % 步数加1
set(handle_step,'String',num2str(steps))% 实时显示步数
end
end
function y = Disrupt(rank_Tag)
%% 按人工打乱方式,随机打乱原拼图排列顺序
% y初始为顺序矩阵,用于打乱拼图
y=[1:1:rank_Tag^2-1,0];
y=reshape(y,rank_Tag,rank_Tag);
y=y';
for i = 1:300*rank_Tag % 打乱次数应随阶数增加而增加
% for i = 1:30 % 打乱次数应随阶数增加而增加
row=randi([1,rank_Tag]);% 产生一个范围在1到rank的整数
col=randi([1,rank_Tag]);
y=movejig(y,row,col,0);% 按随机产生的动作打乱拼图
end
function x = choose(image,index,pic_data,rank_Tag)
%% 根据索引选择对应位置上的拼图块
len_row=size(pic_data,1)/rank_Tag; % 每块拼图的宽度
len_col=size(pic_data,2)/rank_Tag; % 每块拼图的高度
if index>0 %标记为1,2,3,4,5,6,7,8的拼图块
% 计算出行数row以及列数column
row=fix((index-1)/rank_Tag);
column=mod(index-1,rank_Tag);
% 分割出对应拼图块数据
x=image(1+row*len_row:len_row*(row+1),1+column*len_col:len_col*(column+1),:);
else
x=uint8(255*ones(len_col,len_row,3));%拼图块0矩阵数据
end
function drawmap(A,handle,pic_data,rank_Tag)
% A:当前标签矩阵
% handle:图形句柄
% pic_data:图片数据
% rankTag:拼图阶数
%% 将运算数字与对应拼图对应显示图片
origin=pic_data;
len_row=size(pic_data,1)/rank_Tag; % 每块拼图的宽度
len_col=size(pic_data,2)/rank_Tag; % 每块拼图的高度
% 对要显示的拼图进行赋值
for row=1:rank_Tag
for col=1:rank_Tag
pic_data(1+(row-1)*len_row:len_row*row,1+(col-1)*len_col:len_col*col,:)=choose(origin,A(row,col),pic_data,rank_Tag);
end
end
axes(handle) % 选定坐标轴
image(pic_data) % 显示拼图
set(handle,'Visible','off');% 隐藏坐标轴