![266081a3508917f20e3a318817ea1b5e.png](https://i-blog.csdnimg.cn/blog_migrate/ce02da2d2bc1c81d1ee868a9d75ceeb6.jpeg)
注1:本文仅讲述十滴水小游戏算法在matlab中的递归实现,求解算法和图像识别代码将分别讲述.游戏地址
注2:本文仅作留档
注3:写文章好几把累
Part Ⅰ :分析
1.为何使用递归?
一拍脑门想到的(不是
主要是之前写过一个递归走迷宫代码,它和十滴水有异曲同工之妙。
![328d89b15f99a61a810df4e493a347d4.png](https://i-blog.csdnimg.cn/blog_migrate/5705e624af6f84e5a5809221abdd59e9.jpeg)
![da7772f0886b2575bb110f0e0ddd902b.png](https://i-blog.csdnimg.cn/blog_migrate/e0c24732547d6337fdb6bbe9118916f8.jpeg)
![c6ccc5f511a75d341a612ed1564f2e68.png](https://i-blog.csdnimg.cn/blog_migrate/796f2d8361f8399ebc6f76cf833e9cf5.jpeg)
![638d968b704cd997d4dbca6a3d7627dc.png](https://i-blog.csdnimg.cn/blog_migrate/956111be1af4d112c3def23a55035f00.jpeg)
书中的迷宫用矩阵图表示为这样:
![34308553d90c1de01f80cf02b2ffda3b.png](https://i-blog.csdnimg.cn/blog_migrate/3d07af3dc0c45c2548be65e7ed980ade.png)
我们定义十滴水的状态为:
1.初始状态:即最开始没有点击时的状态
![e0fb5eabc4837cc74dcb4e380f32c77c.png](https://i-blog.csdnimg.cn/blog_migrate/b754f5d57c05daf41b0d106f1709566d.jpeg)
2.一般状态:可点击水滴时的状态(你可以发现在空中有水飞行时,水滴是点击不了的)
![c2ee515f0f731f1fc35f9bde4f49bf33.png](https://i-blog.csdnimg.cn/blog_migrate/3a29a5a750e0c244e1528bf06b7a1afe.jpeg)
定义funfun为十滴水在各个状态间转移的函数。(即运行一次会使十滴水从本状态跃迁到下一个状态)
共同之处在于:对funfun进行一次输入,此次输入会使整个盘面发生更新,使之跃迁到下一个状态(按照十滴水的游戏规则,点击一次后,会爆炸的爆炸,炸出水;不会爆炸的,加水;空白的,飞过;直到达到下一个状态)。走迷宫也是,运行一次会使整个盘面进行更新,使之跃迁到迷宫被走出的状态(所有按照规则可走的道路均被打上脚印)。
可以认为迷宫是十滴水的一种特殊情况。只有两个状态,初态,走出态(可走道路布满脚印)。
因此可以用递归模拟。
Part Ⅱ :代码及思路
- 任务1:状态如何表示?
- 任务2:点击操作如何模拟?
- 任务3:炸出的水如何模拟?
①对于任务1,很简单,只需用矩阵刻画即可。
不妨设为map。
matlab提供了丰富的矩阵操作函数。可视化也十分方便。下为一局十滴水的初始状态。
![704f4e54a26af6c6c9b0a409cabf8973.png](https://i-blog.csdnimg.cn/blog_migrate/6cdfb200e755f963fbf6fb274ef90ae3.jpeg)
global map
map设为全局变量。其大小为6*6。每个位置只有5种状态,例map(1,1)只能为0,1,2,3,4中的任意一个。
设map(1,1)初始值为0,其状态转移用图表示为
![5a10bf4294adb541b4167749336c8e86.png](https://i-blog.csdnimg.cn/blog_migrate/04e0ea319ce148e52bbf16c3de150db4.png)
可以看出map(1,1)==0是终态(吸收态),任意初始值都会在有限步之后到达0
②对于任务Ⅱ
令funfun函数具有一个输入dro
funfun(dro)
dro为为一个二维向量表示坐标,形如[1,3],他表示点击(drop加水)的坐标。
加水即向盘面上的水滴加水(不考虑向无水的格子中加水,太憨了)
满足以下规则(注map(dro)表示map(dro(1),dro(2))):
- 如果map(dro)在1到3之间那么直接加一即可
- 如果map(dro)==4那么置0
if map(pos(1),pos(2))<=3&&map(pos(1),pos(2))>=1
map(pos(1),pos(2))=map(pos(1),pos(2))+1;
elseif map(pos(1),pos(2))==4
map(pos(1),pos(2))=0;
end
③对于任务Ⅲ:
这个解释起来比较麻烦
首先考察飞行的水,某一时刻他有位置、运动的方向和速度等性质,显然面向对象是描述它的最佳方案,我们定义一个public class shui{}(不是
害,递归递归。
速度其实可以不用管,因为同一时刻,同一个位置处比如[1,2]上不可能有两个运动方向相同的水。所以对于飞行的水必然是可以任意的空间顺序来处理的,仅需在递归时指定一个空间操作的步长为1即可。(此处可能有点难以理解,我也很难解释)
所以仅需考虑运动方向。可以借用单位向量的概念来描述。再定义另一个位置向量pos(position)作为funfun的输入。即:
funfun(pos,dro)
其作用为:
- 保存水滴炸裂的位置,即飞行水的源头。
此时dro的作用就改变了,它表示的是飞行水的当前位置。
运动方向为
sign(dro-pos)
飞行水的规则为
- 当飞行水遇到不为零的map(dro)时执行任务Ⅱ的操作(operate),即map(dro)不为0时,使用任务Ⅱ的规则
- 当飞行水遇到为0的map(dro)时执行fun(pos,dro+sign(dro-pos)),即向运动方向运动再一个步长。
那么任务Ⅱ的逻辑需作出一些改变:具体来说就是何时向盘面上的水滴加水。
我们重新定义任务Ⅱ
- 仅当pos==dro时执行原任务Ⅱ
- 对于飞行的水来说就是当它在飞行过程中遇到map(dro)不为零时执行原任务Ⅱ,否则继续飞行。
- 关于水滴爆裂,也需对任务Ⅱ逻辑进行一些添加,添加爆裂产生飞行水的操作。
改动后的任务Ⅱ:
if
任务Ⅲ:
if
检查递归是否有出口(注上方代码第二个if就是飞行水的位置限制):
当它既不属于点击操作(pos==dro),飞行水也全部消失时,递归就达到了出口。
应该没问题。
试运行。
![252615a5a4fbeb0bc2b77ed1bcafedfa.gif](https://i-blog.csdnimg.cn/blog_migrate/f02838f009f82a22ec9de5b3cf9ae5b3.gif)
没问题。
实现完整代码:
function
总代码:
%%
%点击运行
%直接运行
%预存若干局面以及随机局面
%取消14行,15行注释为随机局面
%取消16行为图像识别
%取消其他矩阵的注释为已有局面
%97行pause()为单步运行,pause(0.5)为间隔半秒运行
function testtenwater
global map%图
% rng(s)
% p=randi(5,6)-1;
% map=p;
%map=csvread('a.csv');
%map=cell2mat(map)
%map=
% map=[
% 0 4 3 0 0 3
% 4 1 3 3 2 3
% 3 1 2 3 2 4
% 4 1 2 4 4 2
% 0 2 1 2 3 0
% 0 2 3 2 2 0
% ]
map=[
0 3 3 1 1 4
4 2 2 1 1 4
3 2 0 4 0 3
0 4 2 3 3 4
1 2 0 2 3 3
0 2 2 1 0 2
]
% map=[
% 1 4 2 4 2 0
% 2 0 0 2 2 3
% 0 3 2 4 1 1
% 3 3 1 0 4 0
% 3 0 3 2 3 3
% 2 4 4 2 2 1
%
% ]
% map=[
% 3 4 2 2 2 0
% 2 1 3 0 1 2
% 3 0 3 3 3 0
% 2 4 4 1 0 0
% 1 3 2 4 4 2
% 3 3 1 0 4 1
% ]
% map=[
% 3 0 3 3 2 0
% 3 1 1 4 4 2
% 2 3 0 4 1 2
% 4 2 3 4 3 1
% 1 0 2 2 0 4
% 1 2 0 0 3 0
%
% ]
h=6;
imagesc(map)%作图
%%
%test
% dx
% dy
% mm=[dx ;dy' ;y 0 0 0 0 0;x 0 0 0 0 0];
% flag=[-1,1];
% for ii=1:2
% for jj=1:2
% [aa(ii,jj),fl(ii,jj)]=mysumUAD(mm(ii,:),mm(ii+2,1),flag(jj));
% end
% end
%%
%求解算法
pic_num = 1;
for ii=100:-1:1
b=jisuan2();
b(map==0)=0;
[i,j]=ind2sub([6,6],find(b==max(max(b)))); %单下标转换
cc=randi(length(i),1); %随机选取周围水最多的点
figure(1);
imagesc( map)
colorbar
drawnow;
if sum(map>0)==0
disp(ii)
break
end
pos=[i(cc),j(cc)];
disp(pos)
text(pos(2),pos(1),'click')
pause()%去掉其中的数字可以单步运行
funfun(pos,pos);
F=getframe(gcf);
I=frame2im(F);
[I,ch]=rgb2ind(I,256);
if pic_num == 1
imwrite(I,ch,'test.gif','gif', 'Loopcount',inf,'DelayTime',0.5);
else
imwrite(I,ch,'test.gif','gif','WriteMode','append','DelayTime',0.5);
end
pic_num = pic_num + 1;
end
end
%%
%计算map中所有的周围水
function b=jisuan2()
for ii=1:6
for jj=1:6
b(ii,jj)=jisuan1([ii,jj]);
end
end
end
%%
%周围水计算程序
%按照水最多来点
function a=jisuan1(pos)
global map
x=pos(1);
y=pos(2);
dx=map(x,:);
dy=map(:,y);
mm=[dx ;dy' ;y 0 0 0 0 0;x 0 0 0 0 0];
flag=[-1,1];
for ii=1:2
for jj=1:2
[aa(ii,jj),fl(ii,jj)]=mysumUAD(mm(ii,:),mm(ii+2,1),flag(jj));
end
end
temp=sum(sum(fl));%统计周围有几个方向有水滴
a=sum(sum(aa))+4*map(x,y);%统计总的水滴数(中间水滴数权重为4,其余方向为1)
switch temp
%周围四个方向有水滴的优先
case 4
a=a*4;
case 3
a=a*3;
case 2
a=a*2;
case 1
a=a;
end
end
%%
function [c,fl]=mysumUAD(dx,y,flag)
c=0;
%flag=1up
%flag=-1down
%fl返回up或down方向是否有水滴
fl=1;
mm=y+flag;
switch flag
case 1
pp=6;
case -1
pp=1;
end
for yy=mm:flag:pp
c=c+dx(yy);
if c>0
break;
end
end
if (mm>pp&&flag>0)||(mm<pp&&flag<0)
fl=0;
elseif (c==0)&&(yy==pp)
fl=0;
end
return
end
%%
%矩阵实现十滴水
%待
%%
%递归实现十滴水
function funfun(pos,dro)
global map
if sum(pos==dro)==2
if map(pos(1),pos(2))<=3&&map(pos(1),pos(2))>=1
map(pos(1),pos(2))=map(pos(1),pos(2))+1;
elseif map(pos(1),pos(2))==4
map(pos(1),pos(2))=0;
funfun(pos,dro-[0,1]);
funfun(pos,dro-[1,0]);
funfun(pos,dro+[0,1]);
funfun(pos,dro+[1,0]);
end
end
if sum(sum(pos==dro))<2
if dro(1)>=1&&dro(1)<=6&&dro(2)>=1&&dro(2)<=6
if map(dro(1),dro(2))==0
funfun(pos,dro+sign(dro-pos))
else
funfun(dro,dro);
end
end
end
end