c# MineSweeper项目:任何更简单的方式来显示邻近的地雷



我做了一个扫雷舰WPF项目作为学习面向对象编程的实践。然而,我显示按钮"看到"多少地雷的方法有点冗长和混乱。当前的网格是6x6,正如您所看到的,它是一个很长的行列表。主要的问题是,如果我想扩展网格,例如到100x100,它将需要更多的行。

我想知道是否有任何解决方法/方法使它更干净或/和更短的机会?

这是显示一个按钮看到多少地雷的方法:

 private int MineInfo(int index)
        //shows how many mines one button can "see".
        {
           // n = mines
            int n = 0;
         // Edges:
            if (index == 0)
            {
                if (mines.Contains(index + 1))
                {
                    n++;
                }
                if (mines.Contains(index + 6))
                {
                    n++;
                }
                if (mines.Contains(index + 7))
                {
                    n++;
                }
            }
            if (index == 5)
            {
                if (mines.Contains(index - 1))
                {
                    n++;
                }
                if (mines.Contains(index + 6))
                {
                    n++;
                }
                if (mines.Contains(index + 5))
                {
                    n++;
                }
            }
            if (index == 30)
            {
                if (mines.Contains(index + 1))
                {
                    n++;
                }
                if (mines.Contains(index - 6))
                {
                    n++;
                }
                if (mines.Contains(index - 5))
                {
                    n++;
                }
            }
            if (index == 35)
            {
                if (mines.Contains(index - 1))
                {
                    n++;
                }
                if (mines.Contains(index - 6))
                {
                    n++;
                }
                if (mines.Contains(index - 7))
                {
                    n++;
                }
            }
         // Top Row
            if (index > 0 && index < 5)
            {
                if (mines.Contains(index - 1))
                {
                    n++;
                }
                if (mines.Contains(index + 1))
                {
                    n++;
                }
                if (mines.Contains(index + 6))
                {
                    n++;
                }
                if (mines.Contains(index + 5))
                {
                    n++;
                }
                if (mines.Contains(index + 7))
                {
                    n++;
                }
            }
            // Bottom row
            if (index > 30 && index < 35)
            {
                if (mines.Contains(index - 1))
                {
                    n++;
                }
                if (mines.Contains(index + 1))
                {
                    n++;
                }
                if (mines.Contains(index - 6))
                {
                    n++;
                }
                if (mines.Contains(index - 5))
                {
                    n++;
                }
                if (mines.Contains(index - 7))
                {
                    n++;
                }
            }
           // left side
            if ((index == 6) || (index == 12) || (index == 18) || (index == 24))
            {
                if (mines.Contains(index - 6))
                {
                    n++;
                }
                if (mines.Contains(index + 6))
                {
                    n++;
                }
                if (mines.Contains(index + 1))
                {
                    n++;
                }
                if (mines.Contains(index - 5))
                {
                    n++;
                }
                if (mines.Contains(index + 7))
                {
                    n++;
                }
            }
            // Right side
            if ((index == 11) || (index == 17) || (index == 23) || (index == 29))
            {
                if (mines.Contains(index - 6))
                {
                    n++;
                }
                if (mines.Contains(index + 6))
                {
                    n++;
                }
                if (mines.Contains(index - 1))
                {
                   n++;
                }
                if (mines.Contains(index - 7))
                {
                    n++;
                }
                if (mines.Contains(index + 5))
                {
                    n++;
                }
            }

            // Middle buttons
            if ((index > 6 && index < 11) || (index > 12 && index < 17) || (index > 18 && index < 23) || (index > 24 && index < 29))
            {
                if (mines.Contains(index - 1))
                {
                    n++;
                }
                if (mines.Contains(index + 1))
                {
                    n++;
                }
                if (mines.Contains(index - 6))
                {
                    n++;
                }
                if (mines.Contains(index + 6))
                {
                    n++;
                }
                if (mines.Contains(index - 7)) 
                {
                    n++;
                }
                if (mines.Contains(index + 7)) 
                {
                    n++;
                }
                if (mines.Contains(index - 5)) // Right top
                {
                    n++;
                }
                if (mines.Contains(index + 5)) // right bottom
                {
                    n++;
                }
            }
            return n;
        }

这就是循环的作用,这是所有编程语言,OOP和其他语言中都存在的特性。

每当你遇到一个问题,你可以用"我有一个算法在N种情况下都能类似地工作"来表达,你通常在谈论一个循环。在这个例子中,你有两个概括:

  1. 每个相邻的单元被相同地处理。例如,如果它的地址在您的mines集合中找到,则将总数增加1。
  2. 无论您开始使用的具体索引是什么,您都可以检查相同模式的相邻单元格。

一个问题是,当你不在边缘时,上面的方法效果很好,但是当你在边缘时,你需要忽略一些通常会找到相邻单元格的地方。

让你的场景更难以立即找到解决方案的部分原因是,你选择将单元格定位为单维索引,尽管实际的游戏板是二维的。作为一般规则,最好让您的数据结构尽可能地与数据结构打算建模的对象匹配。代码将更容易编写,特别是它将更容易获得关于如何解决特定问题的见解,因为您可以根据原始问题(例如,这里的二维搜索)来考虑它们,而不必在您的数据结构和原始问题之间进行心理映射。

随着时间的推移和练习,你可以更好地做这种心理映射,但即使是一个非常有经验的程序员,最好避免这种情况。对于没有经验的程序员来说,这是一个很好的时间来关注始终确保您的数据结构尽可能接近原始问题。

下面是一个与原始代码保持一致的解决方案。但是,它仍然内化了一个抽象,以便在单维数据结构和原始二维问题空间之间进行转换。这在代码中引入了一点低效率,但与使算法更容易编写和理解相比,这是一个非常小的成本:

int MineInfo(int index, int rows, int columns)
{
    int centerRow = index / rows, centerColumn = index % columns,
        result = 0;
    for (int i = -1; i <= 1; i++)
        for (int j = -1; j <= 1; j++)
        {
            // Ignore the center cell
            if (i == 0 && j == 0)
            {
                continue;
            }
            int checkRow = centerRow + i, checkColumn = centerColumn + j;
            // Ignore cells not within the game board
            if (checkRow < 0 || checkRow >= rows ||
                checkColumn < 0 || checkColumn >= columns)
            {
                continue;
            }
            int checkIndex = checkRow * columns + checkColumn;
            if (mines.Contains(checkIndex))
            {
                result++;
            }
        }
    return result;
}

上面的代码将所有的逻辑封装到一个方法中。但是,即使在有令人信服的理由将数据存储在与原始问题空间不匹配的数据结构中的情况下,在一些帮助器方法甚至包装器类中抽象这种差异也是有用的。也就是说,游戏板的单维索引和二维坐标之间的转换可以在MineInfo()方法和代码的类似区域使用的方法中实现。

我把这个练习留给读者。:)

当然,还有其他方法来解决这个问题。如果我可以改变什么,我要做的第一件事就是停止使用单维数据结构来存储数据。不使用索引集合,而是创建一个二维数组来跟踪单元的状态,包括是否存在地雷、单元是否暴露、标记或其他。这样实现就简单多了。

走更难的路,当然也有可能在单维空间中严格执行上述所有操作。但是处理边缘情况变得复杂得多,而且在代码中阅读和编写也困难得多。恕我直言,这不值得。:)

相关内容

  • 没有找到相关文章

最新更新