views:

138

answers:

2

I have a game consisting of ships that fly around on a 2d grid. I am writing a function that takes a location, and figures out if the (predefined) target can be hit from there. This simply requires checking all the grid square's in the potential aggressor's line of fire.

In most cases, this is a cross, formed like so:

(currX +/- SHOT_RANGE, currY) and (currX, currY +/- SHOT_RANGE)

Where SHOT_RANGE is the maximum distance a shot can travel, and the firing ship is currently located at (currX, currY).

The code to check these two lines is fairly straightforward:

        for (int i = x - SHOT_RANGE; i < x + SHOT_RANGE; i++) {
            if (target.TileX == i && target.TileY == y) {
                return true;
            }
        }

        for (int j = y - SHOT_RANGE; j < y + SHOT_RANGE; j++) {
            if (target.TileX == x && target.TileY == j) {
                return true;
            }
        }

However, on certain "power tiles" the ship can also fire diagonally. All these squares must be checked, too. This is where the repetition comes in. Can you see a way to do this with less code?

    /// <param name="x">Current x-coord of the potential ship</param>
    /// <param name="y">Current y-coord of the potential ship</param>
            private bool CanShootTargetFrom(int x, int y) {

        if ((target.TileX == x && Math.Abs(target.TileY - y) <= SHOT_RANGE) || (target.TileY == y && Math.Abs(target.TileX - x) <= SHOT_RANGE)) {
            return true;
        }

        if (board.IsPowerTileAt(x, y)) {
            int i = x - SHOT_RANGE;
            int j = y - SHOT_RANGE;
            while (i != x + SHOT_RANGE && j != y + SHOT_RANGE) {
                if (target.TileX == i && target.TileY == j) {
                    return true;
                }
                i++;
                j++;
            }
            i = x - SHOT_RANGE;
            j = y + SHOT_RANGE;
            while (i != x + SHOT_RANGE && j != y - SHOT_RANGE) {
                if (target.TileX == i && target.TileY == j) {
                    return true;
                }
                i++;
                j--;
            }
        }

        return false;
    }

UPDATED to use Carra's suggestion, and I realized I could eliminate two of the loops checking diagonals by increasing the upper bounds.

+1  A: 

I think it can be done easier without looping, at least for the horizontal and vertical scan:

class Ship{int x;int y}

Ship s;//your ship
Ship t;//target

if(
  (s.y == t.y && abs(s.x-t.x) <= SHOT_RANGE) 
  ||
  (s.x == t.x && abs(s.y-t.y) <= SHOT_RANGE)
  )
   return true;

As for diagonals, they're a triangle with a 90 degrees angle: (a² + b² < c²)

int a = abs(s.x - t.x)
int b = abs(s.y - t.y)
if(a == b && a * a + b * b <= shot_range * shot_range)
  return true;

I hope it's something like this you're looking for?

Carra
but for the diagonals, wouldn't this return true if the firer is at `(3, 3)` and the target is at `(1, 2)`? This would not be correct; the ship only fires along the diagonals. (So from `(3, 3)`, a ship could hit `(2, 2)`, `(1, 1)`, etc.)
Rosarch
Good point. But it's a 90 degrees angle so a == b.
Carra
A: 

I think that a better design would be to change your function to have a signature similar to:
public bool CanShoot(IShootable potentialTarget)

This method could be a member of a Ship class. Each Ship object would know its own firing capabilities. Anything that you wanted to be able to be shot at could implement IShootable.

In regards to your question, there seems to be a much easier way. Similar to what Carra said, you can just check if the absolute value of the difference of each coordinate is less than your firing range. If not, return false. Otherwise, if they are on a power tile, return true. If they are not on a power tile, check to make sure that either the x coordinate or y-coordinate match, if not return false, otherwise return true.

This is under the assumption that a ship with firing range 2 at (0,0) can fire at a ship at (2,2). I assume this because the code you posted would allow this.

Brett Widmeier