【问题标题】:Nice way to do this in modern OO C-like language?用现代类似 OO C 的语言来做这件事的好方法?
【发布时间】:2009-05-26 11:54:23
【问题描述】:

我有Tiles 代表游戏二维世界中的图块。瓷砖可以在任意数量的 4 个面上有墙。我现在有这样的事情:

interface Tile {
    boolean isWallAtTop();
    boolean isWallAtRight();
    boolean isWallAtLeft();
    boolean isWallAtBottom();
}

在其他地方,我也有 16 张图片,每个可能的瓷砖墙配置一张。像这样的:

static final Image WALLS_ALL_AROUND = ...
static final Image WALL_ON_TOP_AND_RIGHT = ...
/* etc etc all 16 possibilities */

我想写一篇

static Image getWallImage(Tile tile)

我想避免是经历像

这样的可能性的折磨
if (tile.isWallTop && tile.isWallRight 
    && !tile.isWallBottom && !tile.isWallLeft) {
    return WALL_ON_TOP_AND_RIGHT;
}

有人知道更可爱的方法吗?

【问题讨论】:

    标签: c# java c++ data-structures


    【解决方案1】:

    Go go 小工具位掩码。为每个图块使用 4 位掩码,说明哪一面有墙。

    A B C D
    

    位 A 表示顶部的墙壁,B 表示右侧,C 表示底部,D 表示左侧。定义常量以帮助您在逻辑上与掩码相交,即

    if (tile.Walls & (WALL_LEFT | WALL_RIGHT))
      // Do stuff
    

    为了找到图像,这个 4 位掩码产生 16 种可能性。将其用作图像“数组”的索引,因此您可以毫不费力地直接找到正确的图像。

    【讨论】:

    • 天啊!我强烈建议使用方法(或 C# 中的属性),而不是像“if( tile.Walls & (WALL_LEFT | WALL_RIGHT) ) 这样的手动测试。手动方法会在代码中添加不必要的细节。必须始终抽象出细节。当然,这个评论并不反对4位方法。
    • 位掩码在这里是 IMO 错误的解决方案(通常是丑陋的 hack)。 OP 的代码纯粹是程序性的,他要求提供 OO 解决方案。
    • 位掩码是一个非常可爱的 hack。使用它们,但将所需的谓词编写为静态方法(JIT 愿意,将内联)。
    • ie, public static bool IsTopLeft(Wall wall) { return (wall == (WALL_TOP | WALL_LEFT); } @Adam - 你的代码内联有两个错误 - 一,你需要测试相等,如果你想要一个独占测试,两个 java/C# 想要一个 if 子句中的布尔值,而不是“C 非零为真”。
    • 16 个具有 4 个布尔字段(共 64 个字段)的常量对象。这比简单的位掩码更好?以什么可能的方式?您捕获了哪些通用的、可重用的功能?这个基础设施支持什么可扩展的情况?此外,这如何避免他想避免的情况,即需要 4 个布尔测试来检查是否只设置了一面墙(相对于简单的逻辑运算)?
    【解决方案2】:

    平铺对象是否有任何其他属性?如果不是(或者如果您可以排除这些因素),您可以使用 Tile.getImage() 方法将 tile 对象本身变成 16 个常量的枚举,该方法返回传递给构造函数的固定图像。这被称为Flyweight pattern

    class Tile {
        public final boolean isWallAtTop;
        public final boolean isWallAtRight;
        public final boolean isWallAtLeft;
        public final boolean isWallAtBottom;
        public final Image image;
    
        private Tile(boolean top, boolean right, boolean left, 
                     boolean bottom, Image image)
        {
            this.isWallAtTop = top;
            this.isWallAtRight = right;
            this.isWallAtLeft = left;
            this.isWallAtBottom = bottom;
            this.image = image;
        }
    
        public static final Tile WALLS_ALL_AROUND = 
            new Tile(true, true, true, true, new Image("allWalls.png"))
        // more constants here, plus other methods that work with
        // only the wall data
    }
    

    在 Java 中,您甚至可以将其实现为“真正的”枚举。

    对于由瓦片组成的地图,您可以拥有一个简单的瓦片引用二维数组,或者如果您需要单个瓦片的其他数据,请拥有另一个包含“其他数据”的 SpecificTile 类和引用上面的 Tile 对象之一。

    【讨论】:

      【解决方案3】:

      我建议创建一个位标志枚举,如下所示。

      [Flags]
      public enum WallLocations
      {
          None = 0,
          Left = 1,
          Right = 2,
          Top = 4,
          Bottom = 8
      }
      

      然后您可以使用字典将墙壁位置映射到图像。

      Dictionary<WallLocations, Image> map = new Dictionary<WallLocations, Image>();
      
      map.Add(WallLocations.None, image0000);
      map.Add(WallLocations.Left, image1000);
      map.Add(WallLocations.Right, image0100);
      map.Add(WallLocations.Top, image0010);
      map.Add(WallLocations.Bottom, image0001);
      map.Add(WallLocations.Left | WallLocations.Right, image1100);
      // ....
      

      至少在 C# 中,您还可以将枚举定义扩展为所有 16 种情况。

      [Flags]
      public enum WallLocations
      {
          None = 0,
      
          Left = 1,
          Right = 2,
          Top = 4,
          Bottom = 8,
      
          LeftAndRight = Left | Right,
          LeftAndTop = Left | Top,
          LeftAndBottom = Left | Bottom,
          RightAndTop = Right | Top,
          RightAndBottom = Left | Bottom,
          TopAndBottom = Top | Bottom,
      
          AllExceptLeft = Right | Top | Bottom,
          AllExceptRight = Left | Top | Bottom,
          AllExceptTop = Left | Right | Bottom,
          AllExceptBottom = Left | Right | Top,
      
          All = Left | Right | Top | Bottom
      }
      

      【讨论】:

        【解决方案4】:

        你不能用标志枚举做点什么吗?

        【讨论】:

          【解决方案5】:

          您应该将墙壁配置放入单个变量中,并构建此变量到瓷砖图像的映射。

          【讨论】:

            【解决方案6】:

            不要将WALLS_ALL_AROUND 制作为图像,而是将其制作为包含图像的对象,以及处理允许的移动等所需的任何其他逻辑。在Location 对象中,您只需将walls 属性设置为WALLS_ALL_AROUND,即可查询图像并处理其他与墙相关的逻辑。

            由于每组图像只需要这些(不可变)对象的 16 个实例(如果您只有一组图像,每个实例都可能是 Tile 的单例子类),因此您还可以节省内存。

            【讨论】:

              【解决方案7】:

              一个体面的 OO 解决方案可能涉及Strategy Pattern。一种可能的实现是拥有一个 WallConfiguration 类和一个 WallFactory 来创建它们。然后,一个 Tile 将包含一个 WallConfiguration。实现(C++ 风格)看起来像这样:

              class Tile
              {
              private:
                  WallConfiguration walls;
                  // Other Tile data
              
              public:
                  enum Walls
                  { 
                      TOP,
                      BOTTOM,
                      LEFT,
                      RIGHT,
                      TOP_RIGHT,
                      TOP_LEFT
                      // Fill in the rest
                  }; //Enums have a place!
                  Tile(Walls wall)
                  {
                      walls = WallFactory.getConfiguration(wall);
                  }
              

              其中一个有趣的优势是,现在,当您添加炸毁墙壁的能力时(来吧……您知道您会这样做,因为我们都喜欢炸毁东​​西),您可以将该职责添加到 WallConfiguration 类,例如,当您炸毁左墙时,该类将知道如何获取自身的正确实例。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2012-08-08
                • 2014-01-27
                • 2023-04-04
                • 1970-01-01
                • 1970-01-01
                • 2013-02-16
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多