C# - 递归函数问题

发布于 2024-09-24 00:42:58 字数 2144 浏览 2 评论 0原文

这是我的功能:

static Map AddFormation(Map _map, Tile tile, int x, int y, int length, 
    Random rand, Tile endTile = (Tile)Int32.MaxValue)
{
    //so a call to AddFormation without the endTile will work, if I don't want a border.
    if ((int)endTile == Int32.MaxValue) endTile = tile;

    if (x >= 0 && x < _map.Data.GetLength(0) && y >= 0 && y < _map.Data.GetLength(1))
    {
        if (_map.Data[x, y].Tile != tile)
        {
            if (length > 0)
            {
                _map.Data[x, y].Tile = tile;
                int newlength = length - 1;
                AddFormation(_map, tile, x, y - 1, newlength, rand, endTile); // ^
                AddFormation(_map, tile, x, y + 1, newlength, rand, endTile); // v
                AddFormation(_map, tile, x - 1, y, newlength, rand, endTile); // <-
                AddFormation(_map, tile, x + 1, y, newlength, rand, endTile); // ->

            }
            else
            {
                _map.Data[x, y].Tile = endTile;
            }
        }
    }

    return _map;
}

我有一个 Tile 枚举,它可以让我在使用 Tile 时变得更轻松。 我有一个 Cell 类,其中包含一个名为“Tile”的 Tile 枚举和其他信息(对此不重要) Map 类包含一个称为 Data 的 Cell[,] 组。

我想要实现的是在特定点创建特定图块的块,稍后我将把随机化纳入其中(这样它就不仅仅是一颗钻石),但我把它拿出来看看这是否是原因我的问题。

问题是对该函数的调用总是产生比宽度高的块,我一生都无法明白为什么。

会发生什么:

    public static int[,] Add(int[,] grid, int x, int y, int length, int value)
    {

        if (x >= 0 && y >= 0 && x < grid.GetLength(0) && y < grid.GetLength(1))
        {
            if(grid[x,y] != value)
            {
                if(length > 0)
                {
                    grid[x, y] = value;

                    Add(grid, x - 1, y, length - 1, value);
                    Add(grid, x + 1, y, length - 1, value);
                    Add(grid, x, y - 1, length - 1, value);
                    Add(grid, x, y + 1, length - 1, value);
                }
            }
        }

        return grid;
    }

我创建了一个测试函数来看看如果我使用类似的东西 如果你的尺寸足够大,也会出现同样的问题(5 会产生完美的钻石,6 会产生奇怪的形状,11 会产生更奇怪的形状)

Here's my function:

static Map AddFormation(Map _map, Tile tile, int x, int y, int length, 
    Random rand, Tile endTile = (Tile)Int32.MaxValue)
{
    //so a call to AddFormation without the endTile will work, if I don't want a border.
    if ((int)endTile == Int32.MaxValue) endTile = tile;

    if (x >= 0 && x < _map.Data.GetLength(0) && y >= 0 && y < _map.Data.GetLength(1))
    {
        if (_map.Data[x, y].Tile != tile)
        {
            if (length > 0)
            {
                _map.Data[x, y].Tile = tile;
                int newlength = length - 1;
                AddFormation(_map, tile, x, y - 1, newlength, rand, endTile); // ^
                AddFormation(_map, tile, x, y + 1, newlength, rand, endTile); // v
                AddFormation(_map, tile, x - 1, y, newlength, rand, endTile); // <-
                AddFormation(_map, tile, x + 1, y, newlength, rand, endTile); // ->

            }
            else
            {
                _map.Data[x, y].Tile = endTile;
            }
        }
    }

    return _map;
}

I have a Tile enum which is to make my life easier when working with the tiles.
I have a Cell class which contains a Tile enum called "Tile" and other info (unimportant to this)
The Map class contains a Cell[,] group called Data.

What I am trying to achieve is to create a block of the specific tile at a specific point, I will later incorporate Randomisation into this (so it wouldn't be just a diamond) but I took it out to see if that was the cause of my issue.

The problem is a call to this function always produces blocks taller than they are wide and I can't for the life of me see why..

I created a test function to see what happens if I use something like:

    public static int[,] Add(int[,] grid, int x, int y, int length, int value)
    {

        if (x >= 0 && y >= 0 && x < grid.GetLength(0) && y < grid.GetLength(1))
        {
            if(grid[x,y] != value)
            {
                if(length > 0)
                {
                    grid[x, y] = value;

                    Add(grid, x - 1, y, length - 1, value);
                    Add(grid, x + 1, y, length - 1, value);
                    Add(grid, x, y - 1, length - 1, value);
                    Add(grid, x, y + 1, length - 1, value);
                }
            }
        }

        return grid;
    }

Which seems to suffer from the same problem if you go big enough (5 produces a perfect diamond, 6 produces a strange shape and something like 11 even stranger)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

百合的盛世恋 2024-10-01 00:42:58

好吧,在花了很长时间之后(我确实喜欢递归),这里是解决方案的一部分(可能很难解释):

问题是你允许“路径”沿着已经存在的单元格回溯被分配为endTile。如果您看一下第一个方法,您会发现搜索点在向上搜索后直接向下移动。您只需将其删除即可。

这是我正在使用的代码(请注意,它调用 AddFormationAlt 两次,一次用于上升,一次用于下降):

class Program
{
    static string left;
    static string right;

    static void Main(string[] args)
    {
        int size = 20;
        int sizem = size*2 + 1;
        Map m = new Map(new int[sizem,sizem]);
        AddFormationAlt(m, 1, size, size, size-1, 2);

        var l = left;
        var r = right;
    }

    private class Map
    {
        public int[,] Data { get; set; }

        public Map(int[,] data)
        {
            Data = data;
        }

        public string Print()
        {
            StringBuilder sb = new StringBuilder();

            for (int x = 0; x < Data.GetLength(0); x++)
            {
                for (int y = 0; y < Data.GetLength(1); y++)
                    sb.Append(Data[y, x] == 0 ? " " : Data[y,x] == 1 ? "." : "#");
                sb.AppendLine();
            }

            return sb.ToString();
        }
    }

    static void AddFormationAlt(Map _map, int tile, int x, int y, int length, int endTile)
    {
        // You may need to change the cloning method when you change the tiles from ints
        Map m1 = new Map((int[,])_map.Data.Clone());
        Map m2 = new Map((int[,])_map.Data.Clone());

        // Contains the left and right half of the Map you want, you need to join these together.
        Map aleft = AddFormationAlt(m1, true, tile, x, y, length, endTile);
        Map aright = AddFormationAlt(m2, false, tile, x, y + 1, length, endTile);

        left = aleft.Print();
        right = aright.Print();
    }

    static Map AddFormationAlt(Map _map, bool up, int tile, int x, int y, int length, int endTile)
    {
        if (x >= 0 && x < _map.Data.GetLength(0) && y >= 0 && y < _map.Data.GetLength(1))
        {
            if (_map.Data[y, x] != tile)
            {
                if (length > 0)
                {
                    _map.Data[y, x] = tile;
                    int newlength = length - 1;

                    // Either go 'up' or 'down'
                    if(up)
                        AddFormationAlt(_map, true, tile, x, y - 1, newlength, endTile); // ^
                    else
                        AddFormationAlt(_map, false, tile, x, y + 1, newlength, endTile); // v

                    AddFormationAlt(_map, up, tile, x - 1, y, newlength, endTile); // <-
                    AddFormationAlt(_map, up, tile, x + 1, y, newlength, endTile); // ->

                }
                else
                    _map.Data[y, x] = endTile;
            }
        }

        return _map;
    }
}

我更改了所有 Data[x, y]Data[y, x] 因为这就是我通常存储它们的方式,然后它就起作用了 xD。

aleftaright中,您想要的菱形的左半部分和右半部分位于单独的Map中,您需要将它们连接在一起不知何故(对于像你这样聪明的人来说应该不会太难:)。 leftright 显示 Map 的文本表示(注意中心的重叠):

left:

             #              
            #.
           #..
          #...
         #....
        #.....
       #......
      #.......
     #........
    #.........
   #..........
  #...........
 #............
#.............
 #............
  #...........
   #..........
    #.........
     #........
      #.......
       #......
        #.....
         #....
          #...
           #..
            #.
             #              

< code>right:

             #                      
             .#            
             ..#           
             ...#          
             ....#         
             .....#        
             ......#       
             .......#      
             ........#     
             .........#    
             ..........#   
             ...........#  
             ............# 
             .............#
             ............# 
             ...........#  
             ..........#   
             .........#    
             ........#     
             .......#      
             ......#       
             .....#        
             ....#         
             ...#          
             ..#           
             .#            
             #   

您需要清理它并将所有类更改回您自己的类。我希望这有帮助!

Ok, after spending a long time on this (I do like recursion), here is partway to the solution (it may be hard to explain):

The problem is that you are allowing the "path" to backtrack along the cells that have already been allocated as endTiles. If you take a look at your first method, you make the search point go down straight after it has searched up. You simply need to remove that.

This is the code I am using (notice that it calls AddFormationAlt twice, once for going up, once for going down):

class Program
{
    static string left;
    static string right;

    static void Main(string[] args)
    {
        int size = 20;
        int sizem = size*2 + 1;
        Map m = new Map(new int[sizem,sizem]);
        AddFormationAlt(m, 1, size, size, size-1, 2);

        var l = left;
        var r = right;
    }

    private class Map
    {
        public int[,] Data { get; set; }

        public Map(int[,] data)
        {
            Data = data;
        }

        public string Print()
        {
            StringBuilder sb = new StringBuilder();

            for (int x = 0; x < Data.GetLength(0); x++)
            {
                for (int y = 0; y < Data.GetLength(1); y++)
                    sb.Append(Data[y, x] == 0 ? " " : Data[y,x] == 1 ? "." : "#");
                sb.AppendLine();
            }

            return sb.ToString();
        }
    }

    static void AddFormationAlt(Map _map, int tile, int x, int y, int length, int endTile)
    {
        // You may need to change the cloning method when you change the tiles from ints
        Map m1 = new Map((int[,])_map.Data.Clone());
        Map m2 = new Map((int[,])_map.Data.Clone());

        // Contains the left and right half of the Map you want, you need to join these together.
        Map aleft = AddFormationAlt(m1, true, tile, x, y, length, endTile);
        Map aright = AddFormationAlt(m2, false, tile, x, y + 1, length, endTile);

        left = aleft.Print();
        right = aright.Print();
    }

    static Map AddFormationAlt(Map _map, bool up, int tile, int x, int y, int length, int endTile)
    {
        if (x >= 0 && x < _map.Data.GetLength(0) && y >= 0 && y < _map.Data.GetLength(1))
        {
            if (_map.Data[y, x] != tile)
            {
                if (length > 0)
                {
                    _map.Data[y, x] = tile;
                    int newlength = length - 1;

                    // Either go 'up' or 'down'
                    if(up)
                        AddFormationAlt(_map, true, tile, x, y - 1, newlength, endTile); // ^
                    else
                        AddFormationAlt(_map, false, tile, x, y + 1, newlength, endTile); // v

                    AddFormationAlt(_map, up, tile, x - 1, y, newlength, endTile); // <-
                    AddFormationAlt(_map, up, tile, x + 1, y, newlength, endTile); // ->

                }
                else
                    _map.Data[y, x] = endTile;
            }
        }

        return _map;
    }
}

I changed all your Data[x, y] to Data[y, x] because that's how I usually store them and then it worked xD.

In aleft and aright you have the left half and the right half of the diamond you want in separate Maps, you need to join them together somehow (shouldn't be too hard for a clever guy like you :). left and right show the textual representation of Maps (note the overlap in the centre):

left:

             #              
            #.
           #..
          #...
         #....
        #.....
       #......
      #.......
     #........
    #.........
   #..........
  #...........
 #............
#.............
 #............
  #...........
   #..........
    #.........
     #........
      #.......
       #......
        #.....
         #....
          #...
           #..
            #.
             #              

right:

             #                      
             .#            
             ..#           
             ...#          
             ....#         
             .....#        
             ......#       
             .......#      
             ........#     
             .........#    
             ..........#   
             ...........#  
             ............# 
             .............#
             ............# 
             ...........#  
             ..........#   
             .........#    
             ........#     
             .......#      
             ......#       
             .....#        
             ....#         
             ...#          
             ..#           
             .#            
             #   

You need to clean this up and change all the classes back to your own ones. I hope this helps!

只是我以为 2024-10-01 00:42:58

当你说:

if(grid[x,y] != value)

如果你没有遇到任何已经设置为该值的块,你就告诉它只继续沿着这条“腿”走。问题是,一旦获得足够长的长度,从起点顶部伸出的“腿”就会向左和向右“螺旋”,因此当递归最终回到它开始尝试的点时向左或向右出去,那里已经有一个广场,你立即返回。

看起来您想要将 if(length > 0) 放在 if(grid[x,y] != value)之后 /code> 块,而不是其内部。这样,您只需“设置”尚未设置的值,但您将继续,直到达到适当的长度。

当然,由于“分支”(即 if 语句)比“赋值”(即在数组中设置值)需要更长的时间,因此您不妨删除 if(grid[x ,y] != value) 完全如此,并且有多次将点设置为相同值的风险,因为它比比较当前值更便宜。

if (x >= 0 && y >= 0 && x < grid.GetLength(0) && y < grid.GetLength(1))
{
    grid[x, y] = value;
    if(length > 0)
    {

        Add(grid, x - 1, y, length - 1, value);
        Add(grid, x + 1, y, length - 1, value);
        Add(grid, x, y - 1, length - 1, value);
        Add(grid, x, y + 1, length - 1, value);
    }
}

return grid;

When you say:

if(grid[x,y] != value)

You're telling it to only continue down this "leg" if you don't run into any blocks that have already been set to this value. The problem is that once you get a long enough length, the "leg" going out the top of the starting point "spirals around" to the left and right, and so when the recursion finally comes back to the point where it starts trying to go out the left or right, there is already a square there and you return immediately.

It looks like you want to take the if(length > 0) and put it after the if(grid[x,y] != value) block, rather than inside of it. That way, you only "set" the value if it hasn't already been set, but you will continue until you reach the appropriate length.

Of course, since "branches" (i.e. if statements) take longer than "assignments" (i.e. setting a value in an array), you might as well just remove the if(grid[x,y] != value) entirely, and risk setting spots to the same value multiple times, because it's cheaper than comparing the current value.

if (x >= 0 && y >= 0 && x < grid.GetLength(0) && y < grid.GetLength(1))
{
    grid[x, y] = value;
    if(length > 0)
    {

        Add(grid, x - 1, y, length - 1, value);
        Add(grid, x + 1, y, length - 1, value);
        Add(grid, x, y - 1, length - 1, value);
        Add(grid, x, y + 1, length - 1, value);
    }
}

return grid;
吻安 2024-10-01 00:42:58

难道你不想要类似的东西吗

 if(grid[x,y] != 0) // or whatever the initial value is

?否则

 if(grid[x,y] != value)

,当你长出来时,它会长回种子点。

Don't you want something like

 if(grid[x,y] != 0) // or whatever the initial value is

instead of

 if(grid[x,y] != value)

Otherwise, when you grow out, it will grow back to the seed point.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文