【连载爽文 Part.1】4*4Puzzle ——基于MATLAB App Designer 的一例游戏。

1.引言

上个月简直闲到离谱,于是乎打开王者,追了一个活动,如图。puzzle 本体是一个4*4的棋盘。

通关条件:翻出4个相连通的正确答案,成一条水平线、垂直线或者主副对角线即可通关。

我们将水平线的解称为横向通路,垂直线的解称为纵向通路,对角线的解则称作主副对角线通路。

最后puzzle做出来的样子如下图(抱歉忘记截图了,凑合看一看!)

可以看到,正确答案一共10个,错误答案一共6个,且puzzle 的解只有一条(第一横向通路)。

我是MATLAB 的初学者 ,觉得MATLAB 很适合处理这种矩阵数据,矩阵元素调用以及行项列项求和等操作很是方便。于是尝试用MATLAB 进行该puzzle 的制作。

2.建模分析

制作该puzzle 最重要的部分是生成一个随机的、10个正确答案、6个错误答案、且只有一条通路的4*4 棋盘。

因为在Matlab 中,可以对方阵进行上下翻转或左右旋转的操作(后面会提到),所以我们只需要模拟出:Z1唯一解为第一列通路、Z2唯一解为第二列通路以及Z3唯一解为主对角线通路这三种情况的puzzle,即可完成puzzle 建模。

判断一条道路的‘‘通断’’,我们可以在该方向求和,当哪一条道路上的4个元素的和为4时,这条路即是通路。(这个求和很重要,其思路取自16宫格,即通路和相等。共有48解。在验证puzzle是否正确生成,以及编写app designer ,定位答案的时候会用)

结果输出:符合条件的4*4 方阵,正确答案标记为1(数值),错误答案标记为0(数值)。

3.1.Matlab 建模——Z1

这是我建立Z1模型的思路及过程:(Z1.mlx)Z

通过a=ones(4) 建立一个4*4 的全一矩阵,此后只需将6个错误答案填入a 中即可。

将非第一列的主、副对角线断路,即在(2,2)、(3,3)、(4,4)和(3,2)、(2,3)、(1,4)中各挑选一个位置填入0。代码如下;

p=0;
a=ones(4);
b1=randperm(3,1)+1;b2=randperm(3,1)+1;  %随机主副对角线列数
s1=5*b1-4;s2=3*b2+1;
a(s1)=0;a(s2)=0;
%a                                      % 2 off 6

通过求和判断列向通路并定位。保留第一列a(:,1),便于定位,使sum(a(:,1))==4;

sum1=sum(a);sum1(1)=0;          %列向求和
%disp('cols sum')               %列项和向量
%disp(sum1)
no1=find(sum1==4);              %断路列的定位
len1=size(no1);len1=len1(2);    %确定定位向量的长度

纵向断路,为a补齐纵向的0;

for n=1:len1
    ch=randperm(4,1);           %行定位
    a(ch,no1(n))=0;             %0的位置
end
%a                              % 2+len1 off 6

通过求和判断行向通路并定位。同样保留a(:,1);

sum2=sum(a,2)';                 %行向求和
%disp('rows sum')               %行项和向量
%disp(sum2)
no2=find(sum2==4);              %断路行的定位
len2=size(no2);len2=len2(2);    %确定定位向量的长度

横向断路,为a补齐横向的0;

if sum(no2)~=0                  %判断是否需要进行断路
    for n=1:len2
        ch=randperm(3,1)+1;     %列定位
        a(no2(n),ch)=0;         %0的位置
    end    
end
%a                              % 2+len1+len2 off 6

至此,通过对行向和列向元素的求和,我们分3步,分别完成了对【主副对角线通路】、【2、3、4】路纵向通路以及【1、2、3、4】路横向通路的断开。可以思考出,此时0的个数在闭区间[4,6]之内,此时只需要判断0的个数,将剩余的0(如果存在的话)补齐至方阵元素为1的位置中即可(当然第一列除外)。

count【0】的个数,并补齐至0*6;

num0=size(find(a==0)');num0=num0(2);%提取a中0的个数
zeroin=a(5:16);                     %需要在a中插入0的位置
st=find(zeroin==1)+4;               %定位1
lenst=size(st);lenst=(lenst(2));    %定位向量的长度
if num0~=6                          %判断已存在0的个数
    rand=randperm(lenst,6-num0);    %随机位置
    k=st(rand);                     %定位至a中的元素位置
    a(k)=0;                         %补齐
end
%a                                  % 6 off 6

最后再小小的验证一下,验证方式和上面提到的求和一致,直接上代码:

cross=[sum(diag(a)),sum(diag(flipud(a)))];
col=sum(a);             % only col(1)==4
row=sum(a,2)';
mix=[cross,col,row];inmix=find(mix==4);
%disp('unique ans test')
if inmix==3             %答案唯一性验证
    %disp('True')
else
    %disp('False')
end

其中的cross 的两个元素为【主对角线和;副对角线和】

col 与 row 即为方阵a的列求和与行求和,各四个元素。

揉合到一起的mix 就是上文提到的10条通路所组成的向量。

因为是第一列向通路,4的定位位置就是col 的第1元素,在mix 里为第3元素。

判断成功,得到Z1型puzzle 。

3.2.Matlab 建模——Z2

第二模型选第二列向通路为唯一答案。

在建立Z2 模型的时候,一直在思考能不能用上Z1 的大部分代码。于是乎我想在补齐6个0之前,将生成的a方阵第一列与第二列位置互换:(下图第38行起)

经过实践及思考,发现不可行。因为Z1 的第一部分代码就断开了主副对角线通路,如果盲目互换1、2列元素,可能会导致主副对角线复通。(上图即为主对角线复通的情况)。

于是乎偷懒失败了,只能重新按照Z1 模型重写,不在话下。

重写之后

同样进行通路验证,理想的mix 验证位置为:7;验证部分代码同Z1.

3.3.Matlab 建模——Z3

建立Z3 的时候,相较Z1、Z2 更为复杂的就是元素位置的确定。

于是我思考:能不能经过Z1 的一系列操作,最后再补齐对角线上出现的0不就可以了吗!!?

(如下图组)

我们观看上两图,图一出现了(主对角线通路和第一列向通路);图二出现了(主对角线通路和第四列向通路)。实践发现,错误通路出现在纵向,但是此时已经补齐0了,那这个思路还能够继续进行吗。可以讨论。(不知是样本数量不足还是我的右手食指被施加以魔法...)。确实会出现这种错解情况,于是乎放弃,寻找新的思路。

本人还尝试过将全一矩阵的主对角线变为0,(因为觉得如此操作会便于定位1(要补齐0的位置),且将求和检测定位sum()==3即可。但想法最终未能完善。可以讨论。下图是一个(百年难遇——估计概率为:1/81)的崩溃情况,以此纪念,还是蛮快乐的。

于是只能按照老套路:第一部分只断开副对角线,然后横向纵向断开,最后补齐0...(因为不能投机取巧而无奈打出的省略)

p=0;
a=ones(4);
b1=randperm(4,1);  %随机主副对角线列数
a(b1,5-b1)=0;
a;                                      % 2 off 6

分别对列向、行向补充非主对角线上的0;

sum1=sum(a);                    %列向求和
%disp('cols sum')               %列项和向量
%disp(sum1)
no1=find(sum1==4);              %断路列的定位
len1=size(no1);len1=len1(2);    %确定定位向量的长度sum2=sum(a,2)';

if len1~=0
    for n=1:len1
        add=randperm(3,1)+no1(n);   % 0的横向定位
        if add>4
            add=add-4;
        end
        a(add,no1(n))=0;
    end
end
a;                                  % num0=1+len1

sum2=sum(a,2)';                 %行向求和
%disp('rows sum')               %行项和向量
%disp(sum2)
no2=find(sum2==4);              %断路行的定位
len2=size(no2);len2=len2(2);    %确定定位向量的长度

if len2~=0
    for n=1:len2
        add=randperm(3,1)+no2(n);   % 0的纵向定位
        if add>4
            add=add-4;
        end
        a(no2(n),add)=0;
    end
end
a;                                  % num0=1+len1+len2

计算0的个数,并补全;

num0=1+len1+len2;                   %补齐前0的个数
if num0~=6
    b=a-eye(4);
    fin=find(b==1)';
    leth=size(fin);
    leth=leth(2);
    op=randperm(leth,6-num0);
    a(fin(op))=0;
end
a;                                  % num0=6; 10 off 10

同样进行通路验证,理想的mix 验证位置为:1;验证部分代码同Z1.

4.为生成的3型puzzle 添加随机性

该部分对3型进行统一,并将3型puzzle 进行旋转以及翻转。此部分代码写入(F.mlx) 中。

因为3个类型的puzzle,最终获得的通路比例为4:4:2,即221。所以并没有按照1:1:1均等随机该3型模型,而是2:2:1。这个决定我也不知是错是对。可以讨论。

随机挑选3型puzzle;(下方代码很傻,有没有更好的方法?可以讨论。)

p=randperm(5,1);
if p==1
    Z1;
end
if p==2
    Z1;
end

if p==3
    Z2
end
if p==4
    Z2
end

if p==5
    Z3
end
a;

再对a进行随机打乱:

spin=randperm(4,1);     %随机确定次数
plr=randperm(2,1);
pud=randperm(2,1);
a=rot90(a,spin);        %逆时针旋转
if plr==1
    a=fliplr(a);        %左右翻转
end
if plr==1
    a=flipud(a);        %上下翻转
end
a;

运存清理大师:只保留了生成的矩阵a。

clearvars -except a

至此,puzzle 的棋盘a 建模已经完成了。

5.写在第一部分的最后

蓝字为讨论部分啦,虽然不知有多少人可以看到。但是写这篇blog 的时候还是激情澎湃的。仅作纪念

作者纯属萌新,(特别萌??),触及到的Matlab 部分简直是皮毛中的皮毛,且我自己深知这一点。所以写这种简单的东西让我感到自己在亵渎这个软件。

我知道自己的代码有很多很多可以优化的部分,希望向大佬虚心求教,和萌新一起学习。

  • 17
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
可以通过以下步骤来编写4*4数字华容道游戏: 1. 创建一个4*4的方格,用数字(1-15)填充其中15个小方格,留一个空白方格,用0表示。 2. 随机选择两个位置(i,j)和(k,l)并交换其对应的数字,重复多次以打乱方格的顺序。 3. 使用Pygame创建游戏界面,并将方格显示在窗口中。 4. 处理玩家的键盘事件,例如按上、下、左、右箭头键时,移动数字方格来重新排列数字。 5. 检查数字方格的顺序是否与目标状态匹配,即数字按顺序排列,最后一个方格为空。如果是,则游戏胜利。 以下是一个基本的代码框架: ``` import pygame import random # initialize pygame pygame.init() # set colors WHITE = (255, 255, 255) BLACK = (0, 0, 0) # set window size WINDOW_SIZE = (400, 400) # set block size BLOCK_SIZE = 100 # create font object font = pygame.font.Font(None, 36) # create grid grid = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 0]] # shuffle grid for i in range(100): row1 = random.randint(0, 3) col1 = random.randint(0, 3) row2 = random.randint(0, 3) col2 = random.randint(0, 3) grid[row1][col1], grid[row2][col2] = grid[row2][col2], grid[row1][col1] # create window object screen = pygame.display.set_mode(WINDOW_SIZE) # set window title pygame.display.set_caption("4x4 Number Puzzle Game") # set clock object clock = pygame.time.Clock() # main game loop while True: # handle events for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() quit() # draw grid for row in range(4): for col in range(4): rect = pygame.Rect(col * BLOCK_SIZE, row * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE) pygame.draw.rect(screen, WHITE, rect) text = font.render(str(grid[row][col]), True, BLACK) text_rect = text.get_rect(center=rect.center) screen.blit(text, text_rect) # update screen pygame.display.update() # set game FPS clock.tick(60) ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值