I am working on a game that uses a grid system (think chess board). Each tile on the board has a Location (row/column). There is also the concept of a Direction (N, E, S, W, NE, SE, NW, SW).
Given two Locations I am calculating 3 direction things:
- The direction (N, E, S, W, NE, SW, NW, SW, NONE, or null. Null is when the tiles do not line up exactly on one of the direction).
- The general direction (If it isn’t one of the specific directions then it falls into the NE, SE, NW, SW general direction).
- The closest direction (if there is a tie it is null).
For example, (0, 0) -> (4, 3) has:
- direction of null
- general direction of SE
- closest direction of SE
and (0, 0) -> (4, 1) has:
- direction of null
- general direction of SE
- closest direction of S
The code works, but it is terribly ugly. I am sure there must be a cleaner algorithm for setting the values, one with far less repetition. Any thoughts on ways to reduce the duplicate code?
public final class Dimension
{
private final int rowDistance;
private final int columnDistance;
private final Direction direction;
private final Direction generalDirection;
private final Direction closestDirection;
public Dimension(final Location locationA,
final Location locationB)
{
if(locationA == null)
{
throw new IllegalArgumentException("locationA cannot be null");
}
if(locationB == null)
{
throw new IllegalArgumentException("locationB cannot be null");
}
rowDistance = locationB.getRow() - locationA.getRow();
columnDistance = locationB.getColumn() - locationA.getColumn();
// not moving at all
if(rowDistance == 0 && columnDistance == 0)
{
direction = Direction.NONE;
generalDirection = Direction.NONE;
closestDirection = Direction.NONE;
}
else
{
final int absoluteDifference;
absoluteDifference = Math.abs(Math.abs(rowDistance) - Math.abs(columnDistance));
// North Westish
if(rowDistance <= 0 && columnDistance <= 0)
{
final int north;
final int west;
final int northWest;
if(absoluteDifference == 0)
{
direction = Direction.NORTH_WEST;
generalDirection = Direction.NORTH_WEST;
}
else if(rowDistance == 0)
{
direction = Direction.WEST;
generalDirection = Direction.WEST;
}
else if(columnDistance == 0)
{
direction = Direction.NORTH;
generalDirection = Direction.NORTH;
}
else
{
direction = null;
generalDirection = Direction.NORTH_WEST;
}
north = Math.abs(columnDistance);
west = Math.abs(rowDistance);
northWest = Math.abs(
Math.abs(Math.max(rowDistance, columnDistance)) -
Math.abs(Math.min(rowDistance, columnDistance)));
if(northWest < west && northWest < north)
{
closestDirection = Direction.NORTH_WEST;
}
else if(west < northWest && west < north)
{
closestDirection = Direction.WEST;
}
else if(north < northWest && north < west)
{
closestDirection = Direction.NORTH;
}
else
{
closestDirection = null;
}
}
// North Eastish
else if(rowDistance <= 0 && columnDistance >= 0)
{
final int north;
final int east;
final int northEast;
if(absoluteDifference == 0)
{
direction = Direction.NORTH_EAST;
generalDirection = Direction.NORTH_EAST;
}
else if(rowDistance == 0)
{
direction = Direction.EAST;
generalDirection = Direction.EAST;
}
else if(columnDistance == 0)
{
direction = Direction.NORTH;
generalDirection = Direction.NORTH;
}
else
{
direction = null;
generalDirection = Direction.NORTH_EAST;
}
north = Math.abs(columnDistance);
east = Math.abs(rowDistance);
northEast = Math.abs(
Math.abs(Math.max(rowDistance, columnDistance)) -
Math.abs(Math.min(rowDistance, columnDistance)));
if(northEast < east && northEast < north)
{
closestDirection = Direction.NORTH_EAST;
}
else if(east < northEast && east < north)
{
closestDirection = Direction.EAST;
}
else if(north < northEast && north < east)
{
closestDirection = Direction.NORTH;
}
else
{
closestDirection = null;
}
}
// South Westish
else if(rowDistance >= 0 && columnDistance <= 0)
{
final int south;
final int west;
final int southWest;
if(absoluteDifference == 0)
{
direction = Direction.SOUTH_WEST;
generalDirection = Direction.SOUTH_WEST;
}
else if(rowDistance == 0)
{
direction = Direction.WEST;
generalDirection = Direction.WEST;
}
else if(columnDistance == 0)
{
direction = Direction.SOUTH;
generalDirection = Direction.SOUTH;
}
else
{
direction = null;
generalDirection = Direction.SOUTH_WEST;
}
south = Math.abs(columnDistance);
west = Math.abs(rowDistance);
southWest = Math.abs(
Math.abs(Math.max(rowDistance, columnDistance)) -
Math.abs(Math.min(rowDistance, columnDistance)));
if(southWest < west && southWest < south)
{
closestDirection = Direction.SOUTH_WEST;
}
else if(west < southWest && west < south)
{
closestDirection = Direction.WEST;
}
else if(south < southWest && south < west)
{
closestDirection = Direction.SOUTH;
}
else
{
closestDirection = null;
}
}
// South Eastish
else
{
final int south;
final int east;
final int southEast;
if(absoluteDifference == 0)
{
direction = Direction.SOUTH_EAST;
generalDirection = Direction.SOUTH_EAST;
}
else if(rowDistance == 0)
{
direction = Direction.EAST;
generalDirection = Direction.EAST;
}
else if(columnDistance == 0)
{
direction = Direction.SOUTH;
generalDirection = Direction.SOUTH;
}
else
{
direction = null;
generalDirection = Direction.SOUTH_EAST;
}
south = Math.abs(columnDistance);
east = Math.abs(rowDistance);
southEast = Math.abs(
Math.abs(Math.max(rowDistance, columnDistance)) -
Math.abs(Math.min(rowDistance, columnDistance)));
if(southEast < east && southEast < south)
{
closestDirection = Direction.SOUTH_EAST;
}
else if(east < southEast && east < south)
{
closestDirection = Direction.EAST;
}
else if(south < southEast && south < east)
{
closestDirection = Direction.SOUTH;
}
else
{
closestDirection = null;
}
}
}
}
public int getRowDistance()
{
return (rowDistance);
}
public int getColumnDistance()
{
return (columnDistance);
}
public Direction getDirection()
{
return (direction);
}
public Direction getGeneralDirection()
{
return (generalDirection);
}
public Direction getClosestDirection()
{
return (closestDirection);
}
}
I would compute the distance much as you have, then I would pass the orthogonal distances to Math.Atan2 to compute a heading H that can be scaled (int)Math.Round(H * 4 / Math.PI) into the range 0-7 as a hash that counts clockwise through the compass points from N, and which you can directly cast into your enum assuming you enumerate clockwise.