views:

205

answers:

5

I'm using C++ to recursively make a hexagonal grid (using a multiply linked list style). I've got it set up to create neighboring tiles easily, but because I'm doing it recursively, I can only really create all 6 neighbors for a given tile. Obviously, this is causing duplicate tiles to be created and I'm trying to get rid of them in some way. Because I'm using a class, checking for null pointers doesn't seem to work. It's either failing to convert from my Tile class to and int, or somehow converting it but not doing it properly. I'm explicitly setting all pointers to NULL upon creation, and when I check to see if it still is, it says it's not even though I never touched it since initialization. Is there a specific way I'm supposed to do this? I can't even traverse the grid without NULLs of some kind

Here's some of my relevant code. Yes, I know it's embarassing.

Tile class header:

class Tile
{
public:
    Tile(void);
    Tile(char *Filename);
    ~Tile(void);

    void show(void);
    bool LoadGLTextures();
    void makeDisplayList();
    void BindTexture();
    void setFilename(char *newName);

    char Filename[100];
    GLuint texture[2];
    GLuint displayList;
    Tile *neighbor[6];
    float xPos, yPos,zPos;
};`

Tile Initialization:

Tile::Tile(void)
{
    xPos=0.0f;
    yPos=0.0f;
    zPos=0.0f;
    glEnable(GL_DEPTH_TEST);
    strcpy(Filename, strcpy(Filename, "Data/BlueTile.bmp"));
    if(!BuildTexture(Filename, texture[0]))
        MessageBox(NULL,"Texture failed to load!","Crap!",MB_OK|MB_ICONASTERISK);

    for(int x=0;x<6;x++)
    {
        neighbor[x]=NULL;
    }
}

Creation of neighboring tiles:

void MakeNeighbors(Tile *InputTile, int stacks)
{
    for(int x=0;x<6;x++)
    {
        InputTile->neighbor[x]=new Tile();
        InputTile->neighbor[x]->xPos=0.0f;
        InputTile->neighbor[x]->yPos=0.0f;
        InputTile->zPos=float(stacks);
    }
    if(stacks)
    {
        for(int x=0;x<6;x++)
            MakeNeighbors(InputTile->neighbor[x],stacks-1);
    }
}

And finally, traversing the grid:

void TraverseGrid(Tile *inputTile)
{
    Tile *temp;
    for(int x=0;x<6;x++)
        if(inputTile->neighbor[x])
        {
            temp=inputTile->neighbor[x];
            temp->xPos=0.0f;
            TraverseGrid(temp);
            //MessageBox(NULL,"Not Null!","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
        }

}

The key line is "if(inputTile->neighbor[x])" and whether I make it "if(inputTile->neighbor[x]==NULL)" or whatever I do, it just isn't handling it properly. Oh and I'm also aware that I haven't set up the list fully. It's only one direction now.

+9  A: 

If you want to create a hexagonal grid you should remember that it easily can be simulated using a normal grid!

    __    __    __
\__/2 \__/4 \__/6 \
/1 \__/3 \__/5 \__/
\__/8 \__/10\__/12\
/7 \__/9 \__/11\__/
\__/  \__/  \__/  \

This will make life MUCH simpler :)

Hence the easiest way would be

  1. set up a temporary square grid m*n
  2. fill it with tiles
  3. traverse the grid and connect properly

Now the connections, based on the diagram above:

A) Connect to previous and next [x-1,y], [x+1,y]
B) Connect to row above and row below [x,y-1], [x,y+1]
C) Connect to row above previous and next [x-1,y-1], [x+1,y-1]

... and you have all desired connections (just remember to check bounds to decide if the tile isn't on the edge) -- if you hold the tiles in another way, you can even remove the grid :).

Kornel Kisielewicz
It can, but that diagram doesn't look helpful to me. For a 2-dimensional array where each node (x,y) neighbors (x-1,y),(x+1,y),(x,y-1),(x,y+1), a honeycomb pattern is analogous to additionally linking to (x+1,y-1),(x+1,y+1) on even rows and (x-1,y-1),(x-1,y+1) on odd rows.
Potatoswatter
The trick is, first squish the hexagons into staggered bricks, and then see how the bricks relate to a regular grid.
Potatoswatter
@Potatoswatter, here, ph34r my l33t ascii skillz ;>
Kornel Kisielewicz
+1 for the artwork ;)
Georg Fritzsche
I've already made a way to use a coordinate system, and I have all of the code for creating proper neighbors. By "not handling properly" I mean that when I set the pointers to NULL, and then check to see if they're NULL; I either get an error "Cannot convert from Tile to int" or it simply says that it's not NULL even though I haven't changed it. I noted that I was aware that they weren't linked properly yet. I just haven't implemented it yet. The way I want to do it also needs to be able to check for NULL pointers, so this comes first. Thanks for the responses
Jon Brant
This is actually a pretty difficult solution. You have to know weather you are on an even row or an odd row to determine "NE". For example, NE of 9 is 10, but NE of 10 is 5. How the hell does that work? But +8 for those awesome graphics is pretty worth-while.
Bill K
+1  A: 

I'm only guessing at what MakeNeighbors() does, but instead of blindly doing InputTile->neighbor[x]=new Tile();, you could check to see if neighbor[x] is non-NULL before creating a new one and initializing it. E.g. if its parent creates it and sets all of its neighbor information, then it shouldn't go and create its parent.

When the parent creates the children, it should also define the children's other neighbors appropriately, as far as it knows them. So, it should make sure that child[i] also is neighbors with child[i-1] and child[i+1].

dash-tom-bang
A: 

I actually did the same thing but my pattern was more like this:

00   01   02   03   04

   10    11   12   13   14

      20    21   22   23   24

         30   31   32   33   34

This makes it pretty easy to figure out what can be reached, but forces a strange offset pattern. I just got rid of (in the above example) 00,01,10 and 20 to make it more of a hex pattern like this:

            02   03   04   05   06

         11   12   13   14   15

            21   22   23   24   25

         30   31   32   33   34

So if you look at the pattern above, reachable is always the same:

from 23 (call 2 "a" and 3 "b") you can get to:

NW(a-1, b), NE(a-1, b+1), W(a, b-1), E(a, b+1), SW(a+1, b-1), SE(a+1,b)

This pattern should hold correct for the entire grid.

EDIT:

I was going to do this in a comment but it got too long. I can see two approaches.

1) Allocate the array of nodes (null, don't allocate them). Whenever you need to allocate a node, just do so, but use the array address whenever you need to reference a node, if it's null populate it, if it has a value use that value. Huge empty arrays shouldn't take up that much memory, but if they do...

2) Create a HashSet to hold your nodes where the Hash value of the node class is calculated like this: (a << 32 || b). In this way you can instantly look up to see if a previous node existed or not. Remember to overload "equals" as well (it should return true only if the compared object is the same type and a and b are equal).

In a mostly populated system where bounds are known, 1 will save memory, but if your system is sparse (as you claim) then #2 could save a LOT of memory with no cost to efficiency.

Bill K
Yeah, I've got all of that. The particular problem I'm looking into has an agent walking tile to tile with a specific set of rules (I'm actually making an analog of "Langton's Ant" with this. And because of its behavior, it will quickly get to an edge. Rather than creating a MASSIVE array for it to travel in, I want to be able to create new tiles only when necessary.
Jon Brant
Actually, looking at your way of setting it up, it's a bit more intuitive than how I set it up
Jon Brant
If allocating the entire data structure is not desirable, perhaps you should consider modeling it as an implicit graph -- a graph where nodes (tiles) and edges (junctions) are only discovered/allocated when needed. The Boost Graph Library is a very robust (albeit complex) framework to guide you if you go down that route. You can then perform traversals and other path finding algorithms on top of the implicit graph.
BennyG
@Jon Brant This comment is just to tell you to look at my edit in response to your comment above.
Bill K
A: 

In the class declaration there is a second constructor Tile(char *Filename);. Maybe this constructor is used to create the main node, but doesn't initialize neighbor properly? (Its implementation isn't shown.)

And on an unrelated node, you have a duplicate strcpy() in the constructor that doesn't serves any purpose and might only lead to problems:

strcpy(Filename, strcpy(Filename, "Data/BlueTile.bmp"));
sth
Oh, haha, yeah. I was doing something different with it before. Thanks for pointing that out.
Jon Brant
I use the other constructor for specifying a non-default texture
Jon Brant
+1  A: 

Creation. Recursion is a neat and elegant way to solve some problems, but it isn't perfect for every problem. I suspect that a purely recursive solution to creating the nodes would be much more complicated (i.e. less elegant) than Kornel Kisielewicz's straightforward iterative solution. That's because the Tile constructor needs to know the layout of all tiles in its immediate vicinity, in order to avoid recreating nodes that are already there.

Traversal. The main problem in your node-traversal code is similar in that you will wind up with an infinite loop and blow the stack because every node will eventually "traverse" back to its parent, beginning the cycle again. I presume you're trying to visit every tile exactly once, right? In that case TraverseGrid() needs to have a parameter telling it which direction we are entering the node from, so that we avoid traversing back that way.

But that's not enough -- you also need more discipline in deciding which directions to go. Simply spreading out in all directions except the direction we entered from will still wind up in an infinite loop and stack overflow, since any three adjacent tiles will cycle endlessly. In order to do this recursively you need to really think about which strategies will wind up visiting each node once and only once.

One possibility would be changing the signature of TraverseGrid() to TraverseGrid(Tile *inputTile, int fromDir, bool leftmost) and then using the following rules:

  • If we entered from above-left, traverse only to above-right, passing leftmost = false.
  • If we entered from below-left or above-right, traverse only to below-right, passing leftmost = false.
  • If leftmost, and there is a node to our lower left, then also traverse to that node, passing leftmost = true.

Of course fromDir and leftmost could be combined into a single parameter, but this gives the general idea.

Another alternative would be keeping a visited flag in each tile which is checked before traversing to that tile. Then your traversal will be a flood fill. But again, a simple iterative traversal is likely to be much simpler and easier to understand, and has the additional benefit of using constant stack space.

j_random_hacker
Thanks for that. Very helpful
Jon Brant
+1: for a more verbose description of the solution
Kornel Kisielewicz