Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is Hashing a suitable solution? Am I over-complicating it?

I write a 2D platformer game where I need rooms with (maximum 4) doors. I write it in Java, but the language is irrelevant.

Each room can have 4 doors, on the top, bottom and sides. I call them NORTH, SOUTH, EAST and WEST. When I'm building a room, I only give it an integer, where each bit in the integer is representing a door.

For example if I want a room with 3 doors (one on the North, one on the East, and on on the west) i give the room the number: 11(1011 in binary).

For this reason each door has an integer, identifying it.

NORTH = 8;//1000
SOUTH = 4;//0100
EAST =  2;//0010
WEST =  1;//0001

If I generate a room, I give it the combination of these identifiers.

For example: the previously mentioned room would get the identifier

doorStock = NORTH | EAST | WEST;

I store these doors in a simple array:

Door doors[] = new Door[4];

My problem is: I need a function which can map the identifiers to the correct index in the array. I don't always need 4 doors.

What I did at first seems the simplest: the doors array would always have 4 elements, and the indices I would not use would simply remain nulls.

public Door getDoor(int doorID){
    switch(doorID){
        case NORTH:{
            return doors[0];
        }
        case SOUTH:{
            return doors[1];
        }
        case EAST:{
            return doors[2];
        }
        case WEST:{
            return doors[3];
        }
    }
    return null;
}

In order to be safe I needed to to determine if the door I'm requesting actually exists in the room.

private boolean doorExists(int doorID){
    return (doorID & doorStock) != 0
}

So with this, the query function looked like this:

public Door getDoor(int doorID){
    switch(doorID){
        case NORTH:{
            if(doorExists(NORTH))return doors[0];
            else return null;
        }
        case SOUTH:{
            if(doorExists(NORTH))return doors[1];
            else return null;
        }
        case EAST:{
            if(doorExists(NORTH))return doors[2];
            else return null;
        }
        case WEST:{
            if(doorExists(NORTH))return doors[3];
            else return null;
        }
    }
    return null;
}

Which was working. BUT! This way the array could waste space with the unused elements. Plus the class Door could potentially be any size, increasing the memory waste.

Not to mention I could need more "slots" for doors( for example, If I try to implement this in 3D), so I decided to try and make the doors array's size depending on the identifier for the doors:

Door doors = new Door[Integer.bitCount(doorStock)];

Which gave an IndexOutOfBounds error real quick. I wasn't surprised, because the doors array could be any size from 0 to 4, so I needed a new hashing method.

What I came up with are two hash tables, one for the array indices:

private final int[][] doorhash = {
    /* NORTH  SOUTH   EAST    WEST doorStock*/
    { -1,     -1,     -1,     -1} /*0000*/,
    { -1,     -1,     -1,      0} /*0001*/,
    { -1,     -1,      0,     -1} /*0010*/,
    { -1,     -1,      0,      1} /*0011*/,
    { -1,      0,     -1,     -1} /*0100*/,
    { -1,      0,     -1,      1} /*0101*/,
    { -1,      0,      1,     -1} /*0110*/,
    { -1,      0,      1,      2} /*0111*/,
    {  0,     -1,     -1,     -1} /*1000*/,
    {  0,     -1,     -1,      1} /*1001*/,
    {  0,     -1,      1,     -1} /*1010*/,
    {  0,     -1,      1,      2} /*1011*/,
    {  0,      1,     -1,     -1} /*1100*/,
    {  0,      1,     -1,      2} /*1101*/,
    {  0,      1,      2,     -1} /*1110*/,
    {  0,      1,      2,      3} /*1111*/
};

and one, which helps helps in the mapping of the previous table:

private final int[] directionHash = {
    -1, /*0000*/
     3, /*0001 - WEST*/
     2, /*0010 - EAST*/
    -1, /*0011*/
     1, /*0100 - SOUTH*/
    -1, /*0101*/
    -1, /*0110*/
    -1, /*0111*/
     0, /*1000 - NORTH*/
};

so my current mapping function looks like this:

public Door getDoor(int doorID){
    switch(doorID){
        case NORTH:{
            if(doorExists(NORTH))return doors[doorhash[doorStock][directionHash[NORTH]]];
            else return null;
        }
        case SOUTH:{
            if(doorExists(NORTH))return doors[doorhash[doorStock][directionHash[SOUTH]]];
            else return null;
        }
        case EAST:{
            if(doorExists(NORTH))return doors[doorhash[doorStock][directionHash[EAST]]];
            else return null;
        }
        case WEST:{
            if(doorExists(NORTH))return doors[doorhash[doorStock][directionHash[WEST]]];
            else return null;
        }
    }
    return null;
}

Which also seems to work fine, but I feel there's a simpler solution to this problem, or one with less wasteful hash tables. I feel this isn't as asymptotically flexible as it should be, or I am over-complicating things. What would be a better method?

Thank you for your time!

like image 269
Dávid Tóth Avatar asked Oct 08 '13 09:10

Dávid Tóth


1 Answers

Enums are your friend:

// Each possible direction - with Up/Down added to show extendability.
public enum Dir {
  North,
  South,
  East,
  West,
  Up,
  Down;
}

class Room {
  // The doors.
  EnumSet doors = EnumSet.noneOf(Dir.class);

  // Many other possible constructors.
  public Room ( Dir ... doors) {
    this.doors.addAll(Arrays.asList(doors));
  }

  public boolean doorExists (Dir dir) {
    return doors.contains(dir);
  }
}

Letting enums do the heavy lifting for you is a natural here. They also provide a ready-made EnumSet that is extremely efficient.

like image 84
OldCurmudgeon Avatar answered Sep 24 '22 03:09

OldCurmudgeon