views:

353

answers:

7

I have Tiles which represent the tiles in a game's 2-dimensional world. The tiles can have walls on any number of their 4 sides. I have something like this at the moment:

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

Somewhere else I also have 16 images, one for each possible tile wall configuration. Something like this:

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

I want to write a

static Image getWallImage(Tile tile)

What I'd like to avoid is the torture of going through the possibilities like

if (tile.isWallTop && tile.isWallRight 
    && !tile.isWallBottom && !tile.isWallLeft) {
    return WALL_ON_TOP_AND_RIGHT;
}

Does anyone know a cuter way to do this?

A: 

Can't you do something with flag enums ?

Frederik Gheysels
+15  A: 

Go go gadget bitmasks. Use a 4 bit mask for each tile, stating which side has a wall.

A B C D

Bit A indicates a wall on the top, B the right, C the bottom, D the left. Define constants to help you that you can just logically intersect with the mask, i.e.

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

For finding the image, this 4 bit mask produces the 16 possibilities. Use it as an index into an images "array", so you can directly find the correct image without any effort.

Adam Wright
Here Be Wolves
Bit masks are IMO the wrong solution here (and generally an ugly hack). The OP's code is purely procedural and he asked for an OO solution.
Michael Borgwardt
Bitmasks are quite a lovely hack. Use them, but write the predicates that you want as static methods (which JIT willing, will inline).
plinth
ie, public static bool IsTopLeft(Wall wall) { return (wall == (WALL_TOP | WALL_LEFT); }@Adam - your code inline has two bugs in it - one, you need to test for equality if you want an exclusive test, two java/C# want booleans in an if-clause, not the "C non-zero is true".
plinth
It's hard to have bugs when I've not said what the code does - it's just a small example. It's also not a specific language example, and I assume people can handle that.One can (over) engineer as much "OO"ness as it desired around this basic concept; the point being that there are 16 different states each tile can be in - 16 methods/properties is not a useful or sane solution.
Adam Wright
16 constant objects, each with 4 booleans and one matching Image, however, are a useful, sane and not "overengineerd" solution that is actually much better than this bitmask tomfoolery.
Michael Borgwardt
*16* constant objects with 4 boolean fields (64 fields in total). This is *better* than a simple bitmask? In what possible way? What generic, reusable functionality have you captured? What extensible situation does this infrastructure support? Moreover, how does this avoid the situation he wanted to avoid, where 4 boolean tests are required to check if only one wall is set (versus a simple logical operation)?
Adam Wright
Note that I'm not complaining against wrapping the logical operations in a more abstract manner, so you could have a method taking a varargs Wall enum that will return true iff each flag is set alongside none of the others.
Adam Wright
Yes, much better. You actually only need ONE boolean test to check whether one specific wall exists on a given Tile object. You don't need ANY logic for looking up images or other wall-related data - the Tile object contains it as a constant. There may even be wall-related logic you can add to the Tile class. It takes less memory and is faster than having a separate bitmask and looking up the image separately for each tile on the map.
Michael Borgwardt
The whole point of OO is keeping related data and functionality together. The wall booleans and the image are obviously related, so it's anti-OO to keep them separate and have a getWallImage() method that looks up the image.
Michael Borgwardt
But that's not true - it's not "Wall exists" he's concerned about, it's "Wall exists, and other walls do not". It also can't take less memory - each tile must be associated with it's "WallInfo" constant class, which at least will take sizeof(ptr) on the system; bitmasks take 4 bits here.I don't see how this approach does not keep the data and functionality together; I would have the images available from a resource handling class anyway, the instance of which can be associated with each tile and indexed via the mask.
Adam Wright
"Wall exists, and other walls do not" is only relevant to the OP to look up the correct image, which becomes unnecessary in my solution - along with the resource handling class. You have the image available right there, rather than keeping a reference for each tile (more memory) or look it up whenever you need it (slower), or both.
Michael Borgwardt
BTW, what I dislike about bitmasks in general is the counterintuitive use of operators they require - to test whether a tile has a top AND a right wall, you have to combined them using the OR operator...
Michael Borgwardt
Accepting this answer as the most helpful given the limited information and scope I provided. I'm in Java world, so in fact, having read around, I shall be trying out something with enums and EnumSets which probably won't end up resembling this solution.
jjujuma
@jjujuma: You're still a long way from a real OO solution here. As I pointed out in my answer, there are design patterns for these kind of situations. The bitmasks might very well show up in the implementation of the design, but they are not, in and of themselves, a good OO solution. You're likely to end up writing *lots* of conditional code to test them if you don't design the overall solution in a better OO manner.
Harper Shelby
A: 

You should put the wall configuration into a single variable and build a mapping of this variable to the tile images.

Svante
+4  A: 

Do the tile objects have any other properties? If not (or if you can factor out those), you could make the tile objects themselves into an enumeration of 16 constants with a Tile.getImage() method that returns a fixed image passed to the constructor. This is known as the 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
}

In Java, you could even implement this as a "real" enum.

For a map that consists of tiles, you could either have a simple 2-dimensional array of Tile references, or if you need other data for individual tiles, have another SpecificTile class that contains the "other data" and a reference to one of the Tile objects above.

Michael Borgwardt
Even better after the edit. +2 from me if it was possible :)
willcodejavaforfood
+2  A: 

I suggest creating a bit flag enum like the following.

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

Then you can use a dictionary to map from the wall locations to the images.

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);
// ....

At least in C# you could also extend the enum definition with all 16 cases.

[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
}
Daniel Brückner
A: 

Instead of making WALLS_ALL_AROUND an image, make it an object that includes the image, and also any other logic necessary for dealing with allowed movements and whatnot. The in a Location object you can just set the walls property to that WALLS_ALL_AROUND, which you can query for the image and have deal with your other wall-related logic.

Since you only need sixteen instances of these (immutable) objects for each set of images (each one is probably a singleton sublcass of Tile if you have only one set of images), you also save memory.

Curt Sampson
A: 

A decent OO solution would probably involve the Strategy Pattern. One possible implementation would be to have a WallConfiguration class, and a WallFactory to create them. A Tile would then contain a WallConfiguration. Implementation (C++ style) would look something like this:

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);
    }

One of the interesting advantages to this is that now, when you add the ability to blow up walls (and c'mon...you know you're going to, 'cuz we all like blowing things up), you can add that responsibility to the WallConfiguration class, which will know how to get the proper instance of itself when you blow up the left wall, for example.

Harper Shelby