public interface Grid<E>
{
    /**
     * Returns the number of rows in this grid.
     * @return the number of rows, or -1 if this grid is unbounded
     */
    int getNumRows();

    /**
     * Returns the number of columns in this grid.
     * @return the number of columns, or -1 if this grid is unbounded
     */
    int getNumCols();

    /**
     * Checks whether a location is valid in this grid. <br />
     * Precondition: <code>loc</code> is not <code>null</code>
     * @param loc the location to check
     * @return <code>true</code> if <code>loc</code> is valid in this grid,
     * <code>false</code> otherwise
     */
    boolean isValid(Location loc);

    /**
     * Puts an object at a given location in this grid. <br />
     * Precondition: (1) <code>loc</code> is valid in this grid (2)
     * <code>obj</code> is not <code>null</code>
     * @param loc the location at which to put the object
     * @param obj the new object to be added
     * @return the object previously at <code>loc</code> (or <code>null</code>
     * if the location was previously unoccupied)
     */
    E put(Location loc, E obj);

    /**
     * Removes the object at a given location from this grid. <br />
     * Precondition: <code>loc</code> is valid in this grid
     * @param loc the location of the object that is to be removed
     * @return the object that was removed (or <code>null<code> if the location
     *  is unoccupied)
     */
    E remove(Location loc);

    /**
     * Returns the object at a given location in this grid. <br />
     * Precondition: <code>loc</code> is valid in this grid
     * @param loc a location in this grid
     * @return the object at location <code>loc</code> (or <code>null<code> 
     *  if the location is unoccupied)
     */
    E get(Location loc);

    /**
     * Gets the locations in this grid that contain objects.
     * @return an array list of all occupied locations in this grid
     */
    ArrayList<Location> getOccupiedLocations();

    /**
     * Gets the valid locations adjacent to a given location in all eight
     * compass directions (north, northeast, east, southeast, south, southwest,
     * west, and northwest). <br />
     * Precondition: <code>loc</code> is valid in this grid
     * @param loc a location in this grid
     * @return an array list of the valid locations adjacent to <code>loc</code>
     * in this grid
     */
    ArrayList<Location> getValidAdjacentLocations(Location loc);

    /**
     * Gets the valid empty locations adjacent to a given location in all eight
     * compass directions (north, northeast, east, southeast, south, southwest,
     * west, and northwest). <br />
     * Precondition: <code>loc</code> is valid in this grid
     * @param loc a location in this grid
     * @return an array list of the valid empty locations adjacent to
     * <code>loc</code> in this grid
     */
    ArrayList<Location> getEmptyAdjacentLocations(Location loc);

    /**
     * Gets the valid occupied locations adjacent to a given location in all
     * eight compass directions (north, northeast, east, southeast, south,
     * southwest, west, and northwest). <br />
     * Precondition: <code>loc</code> is valid in this grid
     * @param loc a location in this grid
     * @return an array list of the valid occupied locations adjacent to
     * <code>loc</code> in this grid
     */
    ArrayList<Location> getOccupiedAdjacentLocations(Location loc);

    /**
     * Gets the neighboring occupants in all eight compass directions (north,
     * northeast, east, southeast, south, southwest, west, and northwest).
     * <br />
     * Precondition: <code>loc</code> is valid in this grid
     * @param loc a location in this grid
     * @return returns an array list of the objects in the occupied locations
     * adjacent to <code>loc</code> in this grid
     */
    ArrayList<E> getNeighbors(Location loc);
} public class Location implements Comparable
{
    private int row; // row location in grid
    private int col; // column location in grid

    /**
     * The turn angle for turning 90 degrees to the left.
     */
    public static final int LEFT = -90;
    /**
     * The turn angle for turning 90 degrees to the right.
     */
    public static final int RIGHT = 90;
    /**
     * The turn angle for turning 45 degrees to the left.
     */
    public static final int HALF_LEFT = -45;
    /**
     * The turn angle for turning 45 degrees to the right.
     */
    public static final int HALF_RIGHT = 45;
    /**
     * The turn angle for turning a full circle.
     */
    public static final int FULL_CIRCLE = 360;
    /**
     * The turn angle for turning a half circle.
     */
    public static final int HALF_CIRCLE = 180;
    /**
     * The turn angle for making no turn.
     */
    public static final int AHEAD = 0;

    /**
     * The compass direction for north.
     */
    public static final int NORTH = 0;
    /**
     * The compass direction for northeast.
     */
    public static final int NORTHEAST = 45;
    /**
     * The compass direction for east.
     */
    public static final int EAST = 90;
    /**
     * The compass direction for southeast.
     */
    public static final int SOUTHEAST = 135;
    /**
     * The compass direction for south.
     */
    public static final int SOUTH = 180;
    /**
     * The compass direction for southwest.
     */
    public static final int SOUTHWEST = 225;
    /**
     * The compass direction for west.
     */
    public static final int WEST = 270;
    /**
     * The compass direction for northwest.
     */
    public static final int NORTHWEST = 315;

    /**
     * Constructs a location with given row and column coordinates.
     * @param r the row
     * @param c the column
     */
    public Location(int r, int c)
    {
        row = r;
        col = c;
    }

    /**
     * Gets the row coordinate.
     * @return the row of this location
     */
    public int getRow()
    {
        return row;
    }

    /**
     * Gets the column coordinate.
     * @return the column of this location
     */
    public int getCol()
    {
        return col;
    }

    /**
     * Gets the adjacent location in any one of the eight compass directions.
     * @param direction the direction in which to find a neighbor location
     * @return the adjacent location in the direction that is closest to
     * <tt>direction</tt>
     */
    public Location getAdjacentLocation(int direction)
    {
        // reduce mod 360 and round to closest multiple of 45
        int adjustedDirection = (direction + HALF_RIGHT / 2) % FULL_CIRCLE;
        if (adjustedDirection < 0)
            adjustedDirection += FULL_CIRCLE;

        adjustedDirection = (adjustedDirection / HALF_RIGHT) * HALF_RIGHT;
        int dc = 0;
        int dr = 0;
        if (adjustedDirection == EAST)
            dc = 1;
        else if (adjustedDirection == SOUTHEAST)
        {
            dc = 1;
            dr = 1;
        }
        else if (adjustedDirection == SOUTH)
            dr = 1;
        else if (adjustedDirection == SOUTHWEST)
        {
            dc = -1;
            dr = 1;
        }
        else if (adjustedDirection == WEST)
            dc = -1;
        else if (adjustedDirection == NORTHWEST)
        {
            dc = -1;
            dr = -1;
        }
        else if (adjustedDirection == NORTH)
            dr = -1;
        else if (adjustedDirection == NORTHEAST)
        {
            dc = 1;
            dr = -1;
        }
        return new Location(getRow() + dr, getCol() + dc);
    }

    /**
     * Returns the direction from this location toward another location. The
     * direction is rounded to the nearest compass direction.
     * @param target a location that is different from this location
     * @return the closest compass direction from this location toward
     * <code>target</code>
     */
    public int getDirectionToward(Location target)
    {
        int dx = target.getCol() - getCol();
        int dy = target.getRow() - getRow();
        // y axis points opposite to mathematical orientation
        int angle = (int) Math.toDegrees(Math.atan2(-dy, dx));

        // mathematical angle is counterclockwise from x-axis,
        // compass angle is clockwise from y-axis
        int compassAngle = RIGHT - angle;
        // prepare for truncating division by 45 degrees
        compassAngle += HALF_RIGHT / 2;
        // wrap negative angles
        if (compassAngle < 0)
            compassAngle += FULL_CIRCLE;
        // round to nearest multiple of 45
        return (compassAngle / HALF_RIGHT) * HALF_RIGHT;
    }

    /**
     * Indicates whether some other <code>Location</code> object is "equal to"
     * this one.
     * @param other the other location to test
     * @return <code>true</code> if <code>other</code> is a
     * <code>Location</code> with the same row and column as this location;
     * <code>false</code> otherwise
     */
    public boolean equals(Object other)
    {
        if (!(other instanceof Location))
            return false;

        Location otherLoc = (Location) other;
        return getRow() == otherLoc.getRow() && getCol() == otherLoc.getCol();
    }

    /**
     * Generates a hash code.
     * @return a hash code for this location
     */
    public int hashCode()
    {
        return getRow() * 3737 + getCol();
    }

    /**
     * Compares this location to <code>other</code> for ordering. Returns a
     * negative integer, zero, or a positive integer as this location is less
     * than, equal to, or greater than <code>other</code>. Locations are
     * ordered in row-major order. <br />
     * (Precondition: <code>other</code> is a <code>Location</code> object.)
     * @param other the other location to test
     * @return a negative integer if this location is less than
     * <code>other</code>, zero if the two locations are equal, or a positive
     * integer if this location is greater than <code>other</code>
     */
    public int compareTo(Object other)
    {
        Location otherLoc = (Location) other;
        if (getRow() < otherLoc.getRow())
            return -1;
        if (getRow() > otherLoc.getRow())
            return 1;
        if (getCol() < otherLoc.getCol())
            return -1;
        if (getCol() > otherLoc.getCol())
            return 1;
        return 0;
    }

    /**
     * Creates a string that describes this location.
     * @return a string with the row and column of this location, in the format
     * (row, col)
     */
    public String toString()
    {
        return "(" + getRow() + ", " + getCol() + ")";
    }
} public class Player {

    public static int numberOfPlayers;
    public final CO CO;
    public final int id;
    private ArrayList<Unit> unitsControlled;
    private int money;
    private ArrayList<Property> propertiesOwned;
    private int commTowers;
    private Color teamColor;

    /**
     * Constructs a Player
     *
     * @param startingMoney starting amount of money
     * @param teamColor     team color to be assigned to this Player
     */
    public Player(CO commandingOfficer, int startingMoney, Color teamColor) {
        //if error when setting starting funds, set to 0
        if (setMoney(startingMoney) == -1) {
            setMoney(0);
        } else {
            setMoney(startingMoney);
        }
        this.id = ++Player.numberOfPlayers;
        this.CO = commandingOfficer;
        this.teamColor = teamColor;
        this.unitsControlled = new ArrayList<>();
        this.propertiesOwned = new ArrayList<>();
    }

    /**
     * @return the player's team color
     */
    public Color getTeamColor() {
        return this.teamColor;
    }

    /**
     * @return the amount of money this player has
     */
    public int getMoney() {
        return this.money;
    }

    /**
     * @param money a positive integer to set money to
     * @return the amount of money that this player has after execution
     * or  -1 if the amount was invalid
     */
    public int setMoney(int money) {
        if (money >= 0) {
            this.money = money;
            return this.money;
        } else {
            return -1;
        }
    }

    /**
     * @return a list of all of the units that this player controls
     */
    public ArrayList<Unit> getUnitsControlled() {
        return unitsControlled;
    }

    /**
     * @return the number of Units that this player controls
     */
    public int getNumUnitControlled() {
        return getUnitsControlled().size();
    }

    /**
     * @return a list of all of the Properties that this player owns
     */
    public ArrayList<Property> getPropertiesOwned() {
        return this.propertiesOwned;
    }

    /**
     * @return the number of Properties that this player controls
     */
    public int getNumPropertiesOwned() {
        return getPropertiesOwned().size();
    }

    public int getCommTowers() {
        return commTowers;
    }

    public void setCommTowers(int commTowers) {
        this.commTowers = commTowers;
    }

    public boolean hasHQ() {
        for (Property prop : getPropertiesOwned()) {
            if (prop instanceof HQ) {
                return true;
            }
        }
        return false;
    }
} public class Runner {

    private static final boolean __DEBUG_MODE = false;
    public static Player[] players;
    public static final int externalFPS = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice()
            .getDisplayMode().getRefreshRate();
    private static int fpsTarget;

    public static int getFpsTarget() {
        return fpsTarget;
    }

    /**
     * precondition: 0 < fpsTarget < 1001
     * Foreground always runs at system refresh rate
     *
     * @param fpsTarget target # of frames per second to update background at
     */
    public static void setFpsTarget(int fpsTarget) {
        if (fpsTarget > 1000 || fpsTarget < 1) {
            throw new IllegalArgumentException("fps target out of range");
        } else if (fpsTarget > Runner.externalFPS) {
            JOptionPane.showMessageDialog(null, "Error: your monitor's current resolution is running at"
                    + Runner.externalFPS + " hz , which is <" + fpsTarget + " hz", "Bad FPS target", 0, null);
        } else {
            Runner.fpsTarget = fpsTarget;
            Runner.msDelay = (int) Math.ceil(1000.0 / fpsTarget);
        }
    }

    public static int getMsDelay() {
        return msDelay;
    }

    private static int msDelay;
    public final static int MONEY_PER_PROPERTY = 200;
    public static String pathToMapPic;


    public static void main(String[] args) {

        setFpsTarget(20);
        AVWorld avw = new AVWorld();
        Player p1 = new Player(new TestCO(), 0, new Color(255, 80, 45));
        Player p2 = new Player(new TestCO(), 0, new Color(75, 150, 255));
        setUpPlayers(p1, p2);
        TerrainGrid<Actor> g = null;
        String[] options = {"map 1", "map 2", "Exit"};
        int selection;
        if (__DEBUG_MODE == false) {
            selection = JOptionPane.showOptionDialog(null,
                    "which map?\n\tMap 1: small map (10x10)\n\tMap 2: large map (24x24)", "Map Selection", 0, 0,
                    getMainIcon(), options, 2);
        } else {
            selection = -1;
        }
        if (selection == 1) {
            g = new TerrainGrid<Actor>(24, 24);
            fillTerrainGrid(g, "maps/map2.dat");
            pathToMapPic = "maps/Map_2.png";
            minFill2(g, p1, p2);
            renderAVW(avw, g);
            avw.getWorldFrame().control.display.zoomIn();
        } else if (selection == 0) {
            pathToMapPic = "maps/Map_1.gif";
            g = new TerrainGrid<Actor>(10, 10);
            fillTerrainGrid(g, "maps/map1.dat");
            minFill1(g, p1, p2);
            renderAVW(avw, g);
        } else if (selection == -1) {
            System.out.println("ENTERING DEBUG MAP");
            //TODO add debug map
        } else {
            System.exit(0);
        }
        cycleTurnPlayer();
        avw.getWorldFrame().showDirectionsPopup();
        while (allPlayersCompeting(getCompetitivePlayers())) {
            avw.setMessage("Currently selected: none.\n\nUse your units to move. Click your factories to build. "
                    + "P1 money: " + players[1].getMoney() + "(+"
                    + players[1].getNumPropertiesOwned() * MONEY_PER_PROPERTY + ")  P2 money: " + players[2].getMoney()
                    + "(+" + players[2].getNumPropertiesOwned() * MONEY_PER_PROPERTY + ")");
            avw.go();
        }
        avw.getWorldFrame().control.turnCycleButton.setEnabled(false);
        String victoryDiag = victoryDiag();
        avw.setMessage(victoryDiag);
        JOptionPane.showMessageDialog(avw.getWorldFrame(), (victoryDiag), "Game Over", 0,
                new ImageIcon(Runner.class.getClassLoader().getResource("victory.png")));
    }

    public static void renderAVW(AVWorld avw, TerrainGrid<Actor> g) {
        avw.setGrid(g);
        avw.show();
    }

    private static void setUpPlayers(Player p1, Player p2) {
        players = new Player[3];
        players[p1.id] = p1;
        players[p2.id] = p2;
        turnPlayer = players[2];
    }

    private static ImageIcon getMainIcon() {
        URL icoLoc = Runner.class.getClassLoader().getResource("icon.png");
        return new ImageIcon(icoLoc);
    }

    private static void customFillTest1(TerrainGrid<Actor> g, Player p1, Player p2) {
        Infantry inf1 = new Infantry(players[1]);
        inf1.putSelfInGrid(g, g.getLocationArray()[1][1]);
        Infantry inf2 = new Infantry(players[1]);
        inf2.putSelfInGrid(g, g.getLocationArray()[9][6]);
        Mech mech1 = new Mech(players[2]);
        mech1.putSelfInGrid(g, g.getLocationArray()[2][8]);
        MedTank medTank1 = new MedTank(players[1]);
        medTank1.putSelfInGrid(g, g.getLocationArray()[1][8]);
        APC apc1 = new APC(players[1]);
        apc1.putSelfInGrid(g, g.getLocationArray()[2][1]);
        Carrier carrier1 = new Carrier(players[1]);
        carrier1.putSelfInGrid(g, g.getLocationArray()[3][6]);
        BCopter copter1 = new BCopter(players[1]);
        copter1.putSelfInGrid(g, g.getLocationArray()[7][7]);
        TCopter copter2 = new TCopter(players[1]);
        copter2.putSelfInGrid(g, g.getLocationArray()[7][6]);
        AdvFighter avf1 = new AdvFighter(players[1]);
        avf1.setFuel(1.0);
        avf1.putSelfInGrid(g, g.getLocationArray()[5][5]);
    }

    private static void minFill1(TerrainGrid<Actor> g, Player p1, Player p2) {
        Infantry inf1 = new Infantry(players[2]);
        inf1.putSelfInGrid(g, g.getLocationArray()[6][0]);
        Infantry inf3 = new Infantry(players[2]);
        inf3.putSelfInGrid(g, g.getLocationArray()[7][1]);
        Infantry inf2 = new Infantry(players[1]);
        inf2.putSelfInGrid(g, g.getLocationArray()[6][9]);
    }

    private static void minFill2(TerrainGrid<Actor> g, Player p1, Player p2) {
        Infantry inf1 = new Infantry(players[2]);
        inf1.putSelfInGrid(g, g.getLocationArray()[2][19]);
        Infantry inf3 = new Infantry(players[2]);
        inf3.putSelfInGrid(g, g.getLocationArray()[2][17]);
        Infantry inf2 = new Infantry(players[1]);
        inf2.putSelfInGrid(g, g.getLocationArray()[21][4]);
    }

    /**
     * A player is competitve if he/she controls Units and his/her HQ To knock
     * out another Player, destroy all of their Units or capture his/her HQ
     *
     * @return whether or not all players are competitive
     */
    private static boolean allPlayersCompeting(ArrayList<Player> competitivePlayers) {
        // TODO more than 2 player support
        return competitivePlayers.size() == 2;
    }

    private static ArrayList<Player> getCompetitivePlayers() {
        ArrayList<Player> competitivePlayers = new ArrayList<>();
        for (Player p : players) {
            if (null != p && p.getUnitsControlled().size() > 0) {
                boolean hasHQ = p.hasHQ();
                if (hasHQ) {
                    competitivePlayers.add(p);
                }
            }
        }
        return competitivePlayers;
    }

    /**
     * Precondition: a Player has won
     *
     * @return a description of the Player's victory
     */
    private static String victoryDiag() {
        ArrayList<Player> allPlayers = new ArrayList<Player>(
                Arrays.asList(players).stream().filter(p -> null != p).collect(Collectors.toList())),
                compPlayers = getCompetitivePlayers();
        StringBuilder victoryMessage = new StringBuilder(
                "Congratulations to Player " + compPlayers.get(0).id + " for eliminating\n");
        allPlayers.remove(compPlayers.get(0));
        for (Player loser : allPlayers) {
            String loserName = "Player " + loser.id;
            victoryMessage.append("\t" + loserName + " by ");
            if (!loser.hasHQ()) {
                victoryMessage.append("capturing " + loserName + "'s HQ");
            }
            if (loser.getUnitsControlled().size() == 0) {
                victoryMessage.append("destroying all of " + loserName + "'s units");
            }
        }
        return victoryMessage.toString();
    }

    public static void fillTerrainGrid(TerrainGrid<Actor> g, String fileName) {

        // String fileName = JOptionPane.showInputDialog("file name");
        try {
            Scanner sc = new Scanner(Runner.class.getClassLoader().getResourceAsStream(fileName));
            if (!(g.getNumRows() == sc.nextInt() && g.getNumCols() == sc.nextInt())) {
                System.out.println("map size != grid size");
                throw new Exception("map size != grid size");
            }
            // hmm??? good
            sc.nextLine();
            for (int r = 0; r < g.getNumRows(); r++) {

                String[] rowStringForm = sc.nextLine().split(",");
                for (int c = 0; c < g.getNumCols(); c++) {
                    try {
                        g.getLocationArray()[r][c] = makeTerrain(r, c, g, rowStringForm[c]);
                    } catch (ArrayIndexOutOfBoundsException e) {
                        System.out.println("array out of bounds");
                        System.err.println("check map, row:" + r + "\tcol:" + c);
                    }
                }
            }
        } catch (Exception e) {
            System.out.println("error reading file");
            e.printStackTrace();
        }
    }

    private static Player turnPlayer;

    public static Player getTurnPlayer() {
        return turnPlayer;
    }

    public static Player getNextTurnPlayer() {
        Player old = getTurnPlayer(), next;
        try {
            if (players[old.id + 1] == null) {
                next = players[1];
            } else {
                next = players[old.id + 1];
            }
        } catch (ArrayIndexOutOfBoundsException e) {
            next = players[1];
        }
        return next;
    }

    public static Player cycleTurnPlayer() {
        Player old = turnPlayer;
        Player next;
        Iterator<Unit> i0 = old.getUnitsControlled().iterator();
        while (i0.hasNext()) {
            Unit u = i0.next();
            if (u.canMove()) {
                u.immobilize();
            }
            if (u instanceof Stealth && ((Stealth) u).isHidden()) {
                Stealth s = (Stealth) u;
                s.hideRender();
            } else if (u instanceof Stealth2 && ((Stealth2) u).isHidden()) {
                Stealth2 s = (Stealth2) u;
                s.hideRender();
            }
        }
        next = getNextTurnPlayer();
        // reset movement
        Iterator<Unit> i = next.getUnitsControlled().iterator();
        while (i.hasNext()) {
            Unit u = i.next();
            u.resetMovement();
            if (u instanceof HiddenUnit) {
                ((HiddenUnit) u).unBox();
            }
        }
        // supply from properties
        Iterator<Property> i2 = next.getPropertiesOwned().iterator();
        while (i2.hasNext()) {
            Property p = i2.next();
            p.tryResupply();
        }

        turnPlayer = next;
        turnPlayer.getPropertiesOwned().get(0).getHostGrid().hostWorld.getWorldFrame()
                .setTitle("AdvanceWars GridWorld (" + Runner.fpsTarget + "hz) : player " + turnPlayer.id);
        for (Property p : turnPlayer.getPropertiesOwned()) {
            turnPlayer.setMoney(turnPlayer.getMoney() + MONEY_PER_PROPERTY);
            if (p.getCapTimer() != p.FULL_CAP_TIMER
                    && (p.getHostGrid().get(p) == null || ((Unit) p.getHostGrid().get(p)).getOwner() == p.getOwner())) {
                p.resetCapTimer();
            }
        }
        return turnPlayer;

    }

    public static Terrain makeTerrain(int r, int c, TerrainGrid<Actor> hostGrid, String terrainType) throws Exception {
        switch (terrainType) {
            case "Beach":
                return new Beach(r, c, hostGrid);
            case "Plains":
                return new Plains(r, c, hostGrid);
            case "Bridge":
                return new Bridge(r, c, hostGrid);
            case "Forest":
                return new Forest(r, c, hostGrid);
            case "Mountain":
                return new Mountain(r, c, hostGrid);
            case "Ocean":
                return new Ocean(r, c, hostGrid);
            case "Reef":
                return new Reef(r, c, hostGrid);
            case "River":
                return new River(r, c, hostGrid);
            case "Road":
                return new Road(r, c, hostGrid);
            case "Test":
                return new TestTerrain(r, c, hostGrid);
            default:
                String[] propProp = terrainType.split("_");
                if (propProp.length != 2) {
                    throw new Exception("error reading file, bad property?");
                } else {
                    String ownerID = propProp[1];
                    Player ownerOfProp;
                    if (!("none".equals(ownerID) || "neutral".equals(ownerID))) {
                        ownerOfProp = players[Integer.parseInt(ownerID)];
                    } else {
                        ownerOfProp = null;
                    }
                    switch (propProp[0]) {
                        case "Property":
                            return new Property(r, c, hostGrid, ownerOfProp);
                        case "Barracks":
                            return new Barracks(r, c, hostGrid, ownerOfProp);
                        case "SeaPort":
                            return new SeaPort(r, c, hostGrid, ownerOfProp);
                        case "AirPort":
                            return new AirPort(r, c, hostGrid, ownerOfProp);
                        case "HQ":
                            return new HQ(r, c, hostGrid, ownerOfProp);
                        default:
                            System.err.println("r:" + r + "\tc:" + c);
                            throw new Exception("error reading file, bad property?");

                    }
                }
        }
    }
} public class TerrainGrid<E> extends AbstractGrid<E> {
    public AVWorld hostWorld;
    private Object[][] occupantArray; // the array storing the grid elements
    private Location[][] locationArray; // array containing map

    public Location[][] getLocationArray() {
        return locationArray;
    }

    public ArrayList<Location> getAllLocations() {
        ArrayList<Location> ans = new ArrayList<>();
        for (Location[] l0 : locationArray) {
            ans.addAll(new ArrayList<>(Arrays.asList(l0)));
        }
        return ans;
    }

    /**
     * Constructs an empty bounded grid with the given dimensions.
     * (Precondition: <code>rows > 0</code> and <code>cols > 0</code>.)
     *
     * @param rows number of rows in TerrainGrid
     * @param cols number of columns in TerrainGrid
     */
    public TerrainGrid(int rows, int cols) {
        if (rows <= 0)
            throw new IllegalArgumentException("rows <= 0");
        if (cols <= 0)
            throw new IllegalArgumentException("cols <= 0");
        occupantArray = new Object[rows][cols];
        locationArray = new Location[rows][cols];
    }

    public int getNumRows() {
        return occupantArray.length;
    }

    public int getNumCols() {
        // Note: according to the constructor precondition, numRows() > 0, so
        // theGrid[0] is non-null.
        return occupantArray[0].length;
    }

    public boolean isValid(Location loc) {
        if (null == loc) {
            return false;
        }
        return 0 <= loc.getRow() && loc.getRow() < getNumRows()
                && 0 <= loc.getCol() && loc.getCol() < getNumCols();
    }

    public ArrayList<Location> getOccupiedLocations() {
        ArrayList<Location> theLocations = new ArrayList<Location>();

        // Look at all grid locations.
        for (int r = 0; r < getNumRows(); r++) {
            for (int c = 0; c < getNumCols(); c++) {
                // If there's an object at this location, put it in the array.
                Location loc = locationArray[r][c];
                //FIXME something weird
                if (null != loc) {
                    if (get(loc) != null) {
                        theLocations.add(loc);
                    }
                } else {
                    System.out.println("grid[" + r + "][" + c + "] is null, moving on");
                }
            }
        }

        return theLocations;
    }

    public E get(Location loc) {
        //TODO
        if (!isValid(loc))
            throw new IllegalArgumentException("Location " + loc
                    + " is not valid");
        return (E) occupantArray[loc.getRow()][loc.getCol()]; // unavoidable warning
    }

    public E put(Location loc, E obj) {
        if (!isValid(loc))
            throw new IllegalArgumentException("Location " + loc
                    + " is not valid, cannot put in grid");
        if (obj == null)
            throw new NullPointerException("obj == null, cannot put in grid");

        // Add the object to the grid.
        E oldOccupant = get(loc);
        occupantArray[loc.getRow()][loc.getCol()] = obj;
        //System.out.println("success putting in unit. See line 106 of TerrainGrid");
        return oldOccupant;
    }

    public E remove(Location loc) {
        if (!isValid(loc))
            throw new IllegalArgumentException("Location " + loc
                    + " is not valid");

        // Remove the object from the grid.
        E r = get(loc);
        occupantArray[loc.getRow()][loc.getCol()] = null;
        return r;
    }
} public abstract class Terrain extends Location {

    protected TerrainGrid hostGrid;

    /**
     * Constructs a Terrain with given row and column coordinates.
     *
     * @param r the row
     * @param c the column
     */
    public Terrain(int r, int c, TerrainGrid<Actor> hostGrid) {
        super(r, c);
        this.hostGrid = hostGrid;
    }

    /**
     * @return the defense points of this Terrain
     * non-air units occupying the terrain gain a 10% defense bonus per defense point
     */
    public abstract int getDefense();

    /**
     * @return the cost of mobility points that a unit of movementType would take
     * to traverse this terrain
     */
    public double getMoveCost(MoveType movementType) {
        switch (movementType) {
            case AIR:
                return getMoveCostAir();
            case SEA:
                return getMoveCostBoat();
            case FOOT:
                return getMoveCostFoot();
            case TIRES:
                return getMoveCostTires();
            case TREADS:
                return getMoveCostTreads();
            case LANDER:
                return getMoveCostLander();
            default:
                throw new IllegalArgumentException();
        }
    }

    /**
     * @return the cost of mobility points that a unit of movementType.FOOT would take
     * to traverse this terrain -- override to set
     */
    protected abstract double getMoveCostFoot();

    /**
     * @return the cost of mobility points that a unit of movementType.TIRES would take
     * to traverse this terrain -- override to set
     */
    protected abstract double getMoveCostTires();

    /**
     * @return the cost of mobility points that a unit of movementType.TREADS would take
     * to traverse this terrain -- override to set
     */
    protected abstract double getMoveCostTreads();

    /**
     * @return the cost of mobility points that a unit of movementType.AIR would take
     * to traverse this terrain -- override to set
     */
    protected double getMoveCostAir() {
        return 1;
    }

    /**
     * @return the cost of mobility points that a unit of movementType.SEA would take
     * to traverse this terrain -- override to set
     */
    protected double getMoveCostBoat() {
        return 999;
    }

    /**
     * @return the cost of mobility points that a unit of movementType.LANDER would take
     * to traverse this terrain -- override to set
     */
    protected double getMoveCostLander() {
        return 999;
    }

    /**
     * @return Manhattan distance to other terrain t
     */
    public int getDistanceTo(Terrain t) {
        return Math.abs(this.getRow() - t.getRow()) + Math.abs(this.getCol() - t.getCol());
    }

    @Override
    public Location getAdjacentLocation(int direction) {
        int adjustedDirection = (direction + HALF_RIGHT / 2) % FULL_CIRCLE;
        if (adjustedDirection < 0)
            adjustedDirection += FULL_CIRCLE;

        adjustedDirection = (adjustedDirection / HALF_RIGHT) * HALF_RIGHT;
        int dc = 0;
        int dr = 0;
        if (adjustedDirection == EAST)
            dc = 1;
        else if (adjustedDirection == SOUTHEAST) {
            dc = 1;
            dr = 1;
        } else if (adjustedDirection == SOUTH)
            dr = 1;
        else if (adjustedDirection == SOUTHWEST) {
            dc = -1;
            dr = 1;
        } else if (adjustedDirection == WEST)
            dc = -1;
        else if (adjustedDirection == NORTHWEST) {
            dc = -1;
            dr = -1;
        } else if (adjustedDirection == NORTH)
            dr = -1;
        else if (adjustedDirection == NORTHEAST) {
            dc = 1;
            dr = -1;
        }
        try {
            return hostGrid.getLocationArray()[getRow() + dr][getCol() + dc];
        } catch (ArrayIndexOutOfBoundsException e) {
            return null;
        }
    }

    /**
     * @return list of Manhattan adjacent Terrains
     */
    public ArrayList<Terrain> getAllAdjacentTerrains() {
        ArrayList<Terrain> adjacent = new ArrayList<Terrain>();
        //45 deg for all, 90 for Manhattan
        for (int dir = 0; dir < 360; dir += 90) {
            Terrain t = (Terrain) getAdjacentLocation(dir);
            if (t != null) {
                adjacent.add(t);
            }
        }
        return adjacent;
    }

    public String getUIName() {
        switch (this.getClass().getSimpleName()) {
            case "Property":
                return "City";
            default:
                return this.getClass().getSimpleName();
        }
    }

    public String getDefenseStarsAsString() {
        Object a;
        if ((a = hostGrid.get(this)) != null && (a instanceof Air)) {
            return "☆☆☆☆☆";
        }
        int def = getDefense();
        return "★★★★★".substring(0, def) + "☆☆☆☆☆".substring(def, 5);
    }

    public TerrainGrid getHostGrid() {
        return hostGrid;
    }
} public abstract class Factory extends Property {

    protected ArrayList<Constructor<? extends Unit>> buildableUnits;

    /**
     * Constructs a Factory with given row and column coordinates.
     *
     * @param r the row
     * @param c the column
     */
    public Factory(int r, int c, TerrainGrid<Actor> hostGrid, Player owner) {
        super(r, c, hostGrid, owner);
    }

    /**
     * @return a set of constructors for units that
     * can be built on this factory
     */
    public ArrayList<Constructor<? extends Unit>> getBuildableUnits() {
        return this.buildableUnits;
    }

    public void buildUnit(Constructor<? extends Unit> constructor) {
        try {
            Unit u = constructor.newInstance(getOwner());
            if (u.getBuildCost() > getOwner().getMoney()) {
                System.out.println("costs " + u.getBuildCost());
                System.out.println("when money = " + getOwner().getMoney());
                JOptionPane.showMessageDialog(hostGrid.hostWorld.getWorldFrame(), "not enough money");
                return;
            } else {
                getOwner().setMoney(getOwner().getMoney() - u.getBuildCost());
                u.putSelfInGrid(hostGrid, this);
            }


        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
                | InvocationTargetException e) {
            e.printStackTrace();
        }
    }
} public class Property extends Terrain {

    // cap timer
    public static final int FULL_CAP_TIMER = 200;
    private int capTimer;
    // capture mechanics
    private Player owner;

    /**
     * Constructs a Property with given row and column coordinates.
     *
     * @param r     the row
     * @param c     the column
     * @param owner owner of property, null if neutral
     */
    public Property(int r, int c, TerrainGrid<Actor> hostGrid, Player owner) {
        super(r, c, hostGrid);
        this.setOwner(owner);
        this.capTimer = 200;
    }

    /**
     * @return the current capture timer
     */
    public int getCapTimer() {
        return this.capTimer;
    }

    /**
     * resets the capture timer to the FULL_CAP_TIMER
     */
    public void resetCapTimer() {
        this.capTimer = Property.FULL_CAP_TIMER;
    }

    /**
     * @return the current owner of the property If the property is neutral, it
     * returns null.
     */
    public Player getOwner() {
        return this.owner;
    }

    /**
     * Sets the owner of the property. Should only be invoked internally by
     * tickCapTimer() Precondition: Player p is not null
     */
    public void setOwner(Player p) {
        this.owner = p;
        if (null != owner) {
            p.getPropertiesOwned().add(this);
        }
    }

    /**
     * Decrements the capture timer by the health of the occupying infantry unit
     * If capTimer falls below or equal to 0, the property is captured.
     */
    public void tickCapTimer(Unit u) {
        // currently, only infantry can cap. CO powers will probably allow other
        // units to cap later.
        if (!(u instanceof Infantry)) {
            JOptionPane.showMessageDialog(null,
                    "not sure how you invoked this method without " + "an infantry unit...see line 39 of Property");
            System.exit(-1);
        }
        // actual code
        // TODO checks for extra capturing points, to be implemented in CO
        int tickBy = (int) u.getOwner().CO.passive(u.getHealth(), COFlag.CAPTURE, u.getUnitType());
        this.capTimer -= tickBy;
        System.out.println("capTimer = " + capTimer);
        if (capTimer <= 0) {
            if (null != getOwner()) {
                ArrayList<Property> oldOwnerProperties = getOwner().getPropertiesOwned();
                oldOwnerProperties.remove(this);
            }
            this.setOwner(u.getOwner());
            resetCapTimer();
            MenuMaker.noBugsPls(hostGrid.hostWorld.getWorldFrame().control.display, hostGrid);
            JOptionPane.showMessageDialog(hostGrid.hostWorld.getWorldFrame(),
                    this.getClass().getSimpleName() + " at " + this + " captured\n\tby: Player " + getOwner().id, "Poperty Captured!", 0,
                    new ImageIcon(Property.class.getClassLoader().getResource("32x/capture.png")));
        }
    }

    public void tryResupply() {
        Unit occ = (Unit) hostGrid.get(this);
        if (occ != null &&
                (occ.getAmmo() != occ.getWeapons()[0].getMaxAmmo() || occ.getFuel() != 99 || occ.getHealth() != 100)) {
            if (this.getOwner() == occ.getOwner()) {
                occ.setAmmo(occ.getWeapons()[0].getMaxAmmo());
                occ.setFuel(99);
                if (tryRepair(occ)) {
                    getOwner().setMoney((int) Math.max(0.0, getOwner().getMoney() - (0.2 * occ.getBuildCost())));
                }

                Set<Terrain> where = new HashSet<>();
                where.add(this);
                GridPanel display = hostGrid.hostWorld.getWorldFrame().control.display;
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        ImageIcon supplyIco = new ImageIcon(this.getClass().getClassLoader().getResource(
                                "32x/supply.png"));
                        for (int x = 0; x < 4; x++) {
                            display.showIconsOnSetOfLocations(supplyIco.getImage(), where);
                            try {
                                Thread.sleep(250);
                            } catch (InterruptedException e1) {
                                e1.printStackTrace();
                            }
                            display.paintImmediately(display.getBounds());
                        }

                    }
                }).start();
            }
        }
    }

    private boolean tryRepair(Unit occ) {
        if (getOwner() != occ.getOwner()) {
            return false;
        }
        if (occ.getHealth() == 100) {
            return false;
        } else if ((occ instanceof Air && this instanceof AirPort) ||
                (occ instanceof Sea && this instanceof SeaPort) ||
                (!(occ instanceof Sea || occ instanceof Air))) {
            occ.setHealth((int) Math.min(100.0, occ.getHealth() + 20));
            return true;
        }
        return false;

    }

    @Override
    protected double getMoveCostFoot() {
        return 1;
    }

    @Override
    protected double getMoveCostTreads() {
        return 1;
    }

    @Override
    protected double getMoveCostTires() {
        return 1;
    }

    @Override
    public int getDefense() {
        return 3;
    }
} public interface Carry {
    /**
     * @return whether or not this Carry can carry any more Units
     */
    default boolean isFull() {
        return getMaxCapacity() == getUnits().size();
    }

    /**
     * @return maximum number of Units this can carry
     */
    default public int getMaxCapacity() {
        return 1;
    }

    /**
     * @return a List of the units carried (can be empty)
     */
    public ArrayList<Unit> getUnits();

    /**
     * Refills {@link fuel} and {@link ammo} for adjacent allied {@link Unit}s
     */
    public void resupply();

    /**
     * @return whether or not this Carry has resupplying capabilities
     */
    public boolean canResupply();

    /**
     * Precondition: Carry is not full and Carry can carry the Unit
     * precondition is checked with canCarry() and isFull()
     * PostCondition: getUnits() returns ArrayList containing Unit u
     */
    public default void addUnit(Unit u) {
        this.getUnits().add(u);
    }

    /**
     * Drops off one carried Unit in valid Manhattan adjacent tile
     * can still move after this action; this can drop off multiple Units given space
     * Precondition: This Carry is carrying at least one Unit for which
     * it can be dropped in a Manhattan adjacent tile
     */
    public default void drop() {
        //TODO Implement me
    }

    /**
     * @return whether or not this Carry can carry the Unit
     */
    boolean canCarry(Unit u);

} public class HiddenUnit extends Unit {
    private Unit containedUnit;

    public Unit getContainedUnit() {
        return containedUnit;
    }

    public Unit unBox() {
        Grid<Actor> gr = getGrid();
        Terrain loc = (Terrain) getLocation();
        Player owner = getOwner();
        owner.getUnitsControlled().remove(this);
        this.removeSelfFromGrid();
        containedUnit.getOwner().getUnitsControlled().add(containedUnit);
        containedUnit.putSelfInGrid(gr, loc);
        containedUnit.resetMovement();
        return containedUnit;
    }

    public HiddenUnit(Player owner, Stealth s) {
        setOwner(owner);
        containedUnit = s;
    }

    public HiddenUnit(Player owner, Stealth2 s) {
        setOwner(owner);
        ;
        containedUnit = s;
    }

    @Override
    public int getBuildCost() {
        return 0;
    }

    @Override
    public double getBaseArmorResistance() {
        return 0;
    }

    @Override
    public void outOfFuel() {
        //do nothing?
    }

    @Override
    public MoveType getMovementType() {
        return MoveType.AIR;
    }

    @Override
    public String getInfo() {
        return "Currently selected: none.\n\nUse your units to move. Click your factories to build. " +
                //					 "DO NOT use arrow keys or Enter"+
                "P1 money: " + Runner.players[1].getMoney() + "  P2 money: " + Runner.players[2].getMoney();
    }

} public abstract class Stealth extends Air {
    /**
     * calls super(Player) from child classes
     * don't invoke this
     *
     * @param owner owner of unit
     */
    public Stealth(Player owner) {
        super(owner);
    }

    private boolean hidden;
    private Air hiddenUnit;

    /**
     * Hides the stealth unit -- consumes extra daily fuel.
     * hides sprite by replacing self in grid
     * by version casted to Stealth
     */
    public void hide() {

        this.hidden = true;
    }

    @SuppressWarnings("Duplicates")
    public void hideRender() {
        Grid<Actor> gr = getGrid();
        Terrain loc = (Terrain) getLocation();
        Player owner = getOwner();
        owner.getUnitsControlled().remove(this);
        this.removeSelfFromGrid();
        new HiddenUnit(owner, this).putSelfInGrid(gr, loc);
    }

    /**
     * reveals the hidden unit -- does opposite of hide()
     */
    public void unHide() {

        this.hidden = false;
    }

    /**
     * @return whether or not the unit is currently hidden
     */
    public boolean isHidden() {
        return this.hidden;
    }

    /**
     * @return extra amount of daily fuel the unit consumes when hidden
     */
    public abstract int getExtraDailyCost();

    @Override
    public void deductDailyCost() {
        if (this.isHidden()) {
            this.setFuel(getFuel() - getExtraDailyCost());
        }
        this.setFuel(getFuel() - this.getDailyCost());
        if (getFuel() <= 0) {
            setFuel(0);
        }
    }
} public abstract class Stealth2 extends Sea {
    /**
     * calls super(Player) from child classes
     * don't invoke this
     *
     * @param owner owner of unit
     */
    public Stealth2(Player owner) {
        super(owner);
    }

    private boolean hidden;
    private Sea hiddenUnit;

    /**
     * Hides the stealth unit -- consumes extra daily fuel.
     * hides sprite by replacing self in grid
     * by version casted to Stealth
     */
    public void hide() {
        this.hidden = true;
    }

    @SuppressWarnings("Duplicates")
    public void hideRender() {
        Grid<Actor> gr = getGrid();
        Terrain loc = (Terrain) getLocation();
        Player owner = getOwner();
        owner.getUnitsControlled().remove(this);
        this.removeSelfFromGrid();
        new HiddenUnit(owner, this).putSelfInGrid(gr, loc);
    }

    /**
     * reveals the hidden unit -- does opposite of hide()
     */
    public void unHide() {
        this.hidden = false;
    }

    /**
     * @return whether or not the unit is currently hidden
     */
    public boolean isHidden() {
        return this.hidden;
    }

    /**
     * @return extra amount of daily fuel the unit consumes when hidden
     */
    public abstract int getExtraDailyCost();

    @Override
    public void deductDailyCost() {
        if (this.isHidden()) {
            this.setFuel(getFuel() - getExtraDailyCost());
        }
        this.setFuel(getFuel() - this.getDailyCost());
        if (getFuel() <= 0) {
            setFuel(0);
        }
    }
} public abstract class Unit extends Actor {
    public boolean hasMoved;

    //constructor
    /**
     * Not a lazy boolean flag, as ability to move involves a myriad of
     * factors including  {@link fuel}, participation in
     * combat, transport, and other special circumstances like {@link CO} powers and electronic
     * warfare (EW)
     */
    protected boolean canMove;
    //ownership
    private Player owner;
    //combat defense
    private Suit suit;
    private int health = 100;
    private Weapon[] weapons = new Weapon[2];
    private int ammo;
    private double fuel;
    private int dailyCost;
    private double mobility;
    private double maxMobility;

    /**
     * Constructs a Unit owned by owner
     * also loads the values for daily fuel cost and max mobility
     */
    public Unit(Player owner) {
        if (null != owner) {
            setOwner(owner);
            this.setDailyCost(loadDailyCost());
            this.setMaxMobility(loadMaxMobility());
            //this.canMove = true;
            this.setFuel(99);
            this.resetMovement();
        } else {
            //FOR FACTORY MENU, FIXME in Factory class NOT VERY EFFICIENT
            //don't worry, it will get deleted soon
        }
    }

    /**
     * @deprecated debug purposes only
     */
    public Unit() {
    }

    private static Queue<Terrain> genPath(Terrain t, Map<Terrain, Terrain> parentMap) throws IllegalArgumentException {
        if (!parentMap.containsKey(t)) {
            throw new IllegalArgumentException();
        }
        Queue<Terrain> ans = new LinkedList<>();
        Terrain parent = parentMap.get(t);
        if (parent == null) {
            //we started here, no need to move
            return ans;
        } else {
            ans = genPath(parent, parentMap);
            ans.add(t);
            return ans;
        }
    }

    /**
     * @deprecated never call this
     */
    @Override
    public void act() {
    }

    public Player getOwner() {
        return this.owner;
    }

    public void setOwner(Player newOwner) {
        if (null != newOwner) {
            this.owner = newOwner;
            this.owner.getUnitsControlled().add(this);
            this.setColor(owner.getTeamColor());
        } else {
            throw new IllegalArgumentException("owner is null");
        }
    }

    //money
    public abstract int getBuildCost();

    public Suit getSuit() {
        return suit;
    }

    public void setSuit(Suit s) {
        this.suit = s;
    }

    /**
     * @return percent of damage taken generally as decimal
     */
    public abstract double getBaseArmorResistance();

    public int getHealth() {
        return health;
    }

    /**
     * only use this for true dmg aka ICBM or internally
     *
     * @param hp to set to
     */
    public void setHealth(int hp) {
        health = hp;
        if (health <= 0) {
            this.selfDestruct();
        }
    }

    public UnitType getUnitType() {
        ResourceBundle b = ResourceBundle.getBundle("unit_to_unit_type");
        return UnitType.valueOf(b.getString(getType()));
    }

    public String getType() {
        return this.getClass().getSimpleName();
    }

    //TODO override this to set resistances
    public double resist(double damage, String source) {
        return damage;
    }

    //taking damage and dying
    public void takeDamage(double damage, String source) {
        if (suit != null) {
            damage = suit.resist(damage, source);
            damage = this.resist(damage, source);
        }
        this.health -= Math.round(damage);
        if (this.health <= 0) {
            this.health = 0;
            this.selfDestruct();
        }
    }

    protected void selfDestruct(boolean... showNoFuelIcon) {
        //TODO animation

        URL fireIconLocation = this.getClass().getClassLoader().getResource("32x/fire.png");
        URL noFuelIconLocation = this.getClass().getClassLoader().getResource("32x/noFuel.png");
        Set<Terrain> where = new HashSet<Terrain>();
        where.add((Terrain) getLocation());
        try {
            TerrainGrid tg = (TerrainGrid) getGrid();
            GridPanel display = tg.hostWorld.getWorldFrame().control.display;
            AVWorld avw = tg.hostWorld;
            final Unit _this = this;
            new Thread(new Runnable() {

                @Override
                public void run() {
                    try {
                        if (showNoFuelIcon.length != 0 && showNoFuelIcon[0]) {
                            for (int x = 0; x < 3; x++) {
                                display.showIconsOnSetOfLocations(new ImageIcon(noFuelIconLocation).getImage(), where);
                                Thread.sleep(250);
                            }
                        }
                        display.repaint();
                        for (int x = 0; x < 3; x++) {
                            display.showIconsOnSetOfLocations(new ImageIcon(fireIconLocation).getImage(), where);
                            Thread.sleep(250);

                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        _this.removeSelfFromGrid();
                        _this.getOwner().getUnitsControlled().remove(_this);
                        display.repaint();
                    }

                }

            }).start();

        } catch (ClassCastException cce) {
            System.err.println("?? Host grid is not TerrainGrid. wat. PROBABLY FATAL ERROR Moving on...");
            this.removeSelfFromGrid();
            this.getOwner().getUnitsControlled().remove(this);
            return;
        }
    }

    //combat offense
    protected void setWeapon(int index, String weaponType) {
        this.weapons[index] = new Weapon(weaponType, this);
        if (index == 0) {
            this.setAmmo(weapons[0].getMaxAmmo());
        }
    }

    //movement

    public Weapon[] getWeapons() {
        return weapons;
    }

    public int getAmmo() {
        return ammo;
    }

    public void setAmmo(int x) {
        if (x >= 0) {
            ammo = x;
        } else {
            throw new IllegalArgumentException("bad attempt to set ammo to negative");
        }
    }

    /**
     * @return whether or not this unit can attack Unit u
     * Gets overridden for ranged units, indirect fire, etc
     */
    public boolean canTarget(Unit u) {
        if ((WeaponType.NONE.equals(getWeapons()[0].getWeaponType()) && null == getWeapons()[1]) || (getAmmo() == 0)) {
            return false;
        }
        return couldTarget(u, (Terrain) getLocation());
    }

    /**
     * @return whether or not this unit can attack Unit toCheck from Terrain hypothetical
     * Gets overridden for ranged units, indirect fire, etc
     */
    public boolean couldTarget(Unit toCheck, Terrain hypothetical) {
        if (null == toCheck || toCheck instanceof HiddenUnit) {
            return false; //can't target nothing or hidden things
        }
        return hypothetical.getDistanceTo((Terrain) toCheck.getLocation()) == 1;
    }

    /**
     * @return whether or not this unit can counterattack Unit u
     * Gets overridden for ranged units, indirect fire, etc
     */
    public boolean canCounter(Unit u) {
        return canTarget(u);
    }

    /**
     * * This Unit attacks the targeted Unit. If the target is still alive and
     * can counter this unit, as determined by canCounter(Unit), it counterattacks afterwards
     * Precondition: ammo > 0 or has secondary weapon, target is in range
     * as determined by canTarget(Unit)
     */
    public void fire(Unit target) {
        immobilize();
        //fire first
        //stronger weapon
        double wep1, wep2, damage;
        wep1 = this.weapons[0].damageCalc(target);
        if (null != this.weapons[1]) {
            wep2 = this.weapons[1].damageCalc(target);
        } else {
            wep2 = 0;
        }
        boolean usedPrimary = (wep1 >= wep2);
        if (usedPrimary) {
            this.setAmmo(getAmmo() - 1);
            damage = wep1;
        } else {
            damage = wep2;
        }
        //luck
        double luck = (double) Weapon.luck();
        luck = this.owner.CO.passive(luck, COFlag.LUCK, getUnitType());
        damage += luck;
        String source;
        if (usedPrimary) {
            source = this.weapons[0].getWeaponType();
        } else {
            source = this.weapons[1].getWeaponType();
        }
        damage = this.owner.CO.passive(damage, COFlag.ATTACK, this.getUnitType());
        target.takeDamage(damage, source);
        //attacking uncloaks
        if (this instanceof Stealth) {
            Stealth me = (Stealth) (this);
            if (me.isHidden()) {
                me.unHide();
            }
        } else if (this instanceof Stealth2) {
            Stealth2 me = (Stealth2) (this);
            if (me.isHidden()) {
                me.unHide();
            }
        }
        //counter attack if target is alive and direct fire
        if (target.getHealth() > 0 && target.canCounter(this)) {
            wep1 = target.weapons[0].damageCalc(this);
            if (null != target.weapons[1]) {
                wep2 = target.weapons[1].damageCalc(this);
            } else {
                wep2 = 0;
            }
            usedPrimary = wep1 >= wep2;
            if (usedPrimary) {
                target.ammo--;
                damage = wep1;
            } else {
                damage = wep2;
            }
            //luck
            luck = Math.random() * 10 + 0.1;
            //CO power affects luck
            luck = target.owner.CO.passive(luck, COFlag.LUCK, target.getUnitType());
            damage += luck;
            if (usedPrimary) {
                source = target.weapons[0].getWeaponType();
            } else {
                source = target.weapons[1].getWeaponType();
            }
            damage = target.owner.CO.passive(damage, COFlag.COUNTER, this.getUnitType());
            this.takeDamage(damage, source);
        }
    }

    public void deductDailyCost() {
        this.setFuel(getFuel() - getDailyCost());
    }

    /**
     * As you may have noticed, this has void return type.
     * This method is usually invoked upon fuel reaching or dropping below 0,
     * by the <i>{@link setFuel()}</i> method
     * This is public because it can also be invoked by other special
     * circumstances like {@link CO} powers and electronic warfare (EW)
     */
    public abstract void outOfFuel();

    public void setMobility(double x) {
        if (x >= 0) {
            this.mobility = x;
        } else {
            throw new IllegalArgumentException("bad attempt to set negative mobility");
        }
    }

    /**
     * @return Current {@link mobility} of the {@link Unit}
     */
    public double getCurrentMobility() {
        return this.mobility;
    }

    /**
     * @return Maximum {@link mobility} of the {@link Unit}
     * {@link mobility} is set to this value at the start of each turn
     * by <i>{@link resetMovement()}</i>  or by special circumstances like {@link CO} powers
     */
    public double getMaxMobility() {
        return this.maxMobility;
    }

    /**
     * sets the maximum mobility of the unit to maxMobility
     * should invoked from the constructor
     * Precondition: maxMobility is a positive integer
     * Postcondition: this.maxMobility is set to maxMobility
     */
    private void setMaxMobility(double maxMobility) {
        if (maxMobility >= 0) {
            this.maxMobility = maxMobility;
        } else {
            throw new IllegalArgumentException("bad attempt to max mobility (0 or negative)");
        }
    }

    /**
     * loads the maximum mobility for this unit from a resourcebundle
     * for use in constructor for setMaxMobility()
     *
     * @return maximum mobility value for the unit
     */
    private double loadMaxMobility() {
        ResourceBundle b = ResourceBundle.getBundle("unit_move");
        return (double) ((int) (Double.parseDouble(b.getString(getType())) + 0.5));
    }

    /**
     * loads the daily fuel cost for this unit from a resourcebundle
     * for use in constructor for setDailyCost()
     *
     * @return maximum mobility value for the unit
     */
    private int loadDailyCost() {
        if (this instanceof HiddenUnit) {
            HiddenUnit me = (HiddenUnit) this;
            Unit cont = (me).getContainedUnit();
            return (cont.loadDailyCost());
        }
        ResourceBundle b = ResourceBundle.getBundle("unit_daily_fuel");
        try {
            double ans = Double.parseDouble(b.getString(getType()));
            return (int) (this.owner.CO.passive(ans, COFlag.DAILY_COST, getUnitType()));
        } catch (NumberFormatException e) {
	    System.err.println(Arrays.toString(e.getStackTrace()));
	    System.err.println("Corrupt File?");
            System.err.println("Method: Unit.loadDailyCost()");
            throw e;
        }
    }

    /**
     * @return the daily fuel cost of the unit
     * override this method to "set" a different daily fuel cost of the unit
     * should be 0 for land units, different for air and sea units
     */
    public int getDailyCost() {
        return dailyCost;
    }

    /**
     * sets the daily fuel cost of the unit to cost
     * should invoked from the constructor
     * Precondition: fuelCost is a positive integer
     * Postcondition: this.dailyCost is set to fuelCost
     */
    private void setDailyCost(int x) {
        this.dailyCost = x;
    }

    /**
     * @return The {@link MoveType} of this {@link Unit}
     */
    public abstract MoveType getMovementType();

    /**
     * @return Amount of fuel remaining. Fuel is required to move,
     * and is consumed when moving. {@link fuel} is truncated to integer at the start of each turn by
     * the resetMovement() method.
     */
    public double getFuel() {
        return this.fuel;
    }

    /**
     * @return Amount of fuel remaining. Mobility is required to move, and is consumed when moving.
     * maxMobility fuel is converted into mobility at the start of each turn by resetMovement().
     * fuel is truncated to integer at the start of each turn by the resetMovement() method.
     */
    public void setFuel(double d) {
        this.fuel = d;
        if (fuel < 0) {
            outOfFuel();
        }
    }

    /**
     * @return Whether or not this unit can still move. A {@link Unit} can move
     * until they runs out of {@link mobility}, participates in combat,
     * or is dropped off from a carrying unit. A {@link Unit} currently carried cannot move,
     * but will move with the carrying unit.
     */
    public boolean canMove() {
        if (this.getCurrentMobility() == 0) {
            immobilize();
            return false;
        }
        return this.canMove;
    }

    /**
     * sets canMove to false, changes colors
     *
     * @return whether or not the unit was already UNABLE to move
     */
    public boolean immobilize() {
        if (!canMove) {
            return true;
        } else {
            canMove = false;
            if (getColor() != null) {
                setColor(getColor().darker());
            }
            return false;
        }
    }

    /**
     * Resets movement-related variables for new day or {@link CO} power, namely:
     * <p>{@link canMove}<p>{@link fuel}
     * Postcondition: The daily fuel cost of the unit is deducted from fuel.
     * up to maxMobility is deducted from fuel and the same amount is
     * added to mobility.
     */
    public void resetMovement() {
        //this.hasMoved = false;
        if (!canMove && getColor() != null) {
            setColor(getColor().brighter());
        }
        this.canMove = true;
        this.fuel = (int) (getFuel());
        if (null != getLocation()) {//If not carried
            this.setFuel(this.getFuel() - (this.maxMobility - mobility));
            this.deductDailyCost();
        }
        if (getFuel() > 0) {
            this.mobility = this.maxMobility;
        }
    }

    /**
     * Precondition: the unit has enough mobility to make it to the Terrain toMoveto
     * Postcondition: the unit moves to the Terrain toMoveTo, traversing each terrain in a good path
     *
     * @throws Exception
     */
    public void move(Terrain toMoveTo, boolean... realMove) throws Exception {
        Terrain t = (Terrain) getLocation();
        boolean moveSuccess = realMove.length == 0 ?
                move(findPathTo(toMoveTo)) : move(findPathTo(toMoveTo), realMove[0]);
        if (moveSuccess) {
            immobilize();
            t = (Terrain) getLocation();
            for (Terrain checkingForHidden : t.getAllAdjacentTerrains()) {
                Actor occ = getGrid().get(checkingForHidden);
                if (occ instanceof HiddenUnit && ((HiddenUnit) occ).getContainedUnit().getOwner() != this.getOwner()) {
                    HiddenUnit hu = (HiddenUnit) occ;
                    Unit aha = hu.unBox();
                    aha.immobilize();
                    if (aha instanceof Stealth) {
                        ((Stealth) aha).unHide();
                    } else if (aha instanceof Stealth2) {
                        ((Stealth2) aha).unHide();
                    }
                }
            }
        } else {
            System.out.println("move failed, see line 441 of Unit");
        }
    }

    /**
     * uses DFS
     *
     * @return a set of Terrains that the unit can move to, given constraints of the surrounding
     * terrain, units, and current mobility level
     */
    public Set<Terrain> getValidMoveSpaces() {
        Set<Terrain> ans = new HashSet<>();
        ans.add((Terrain) this.getLocation());
        if (!this.canMove()) {
            return ans;
        }
        Stack<Terrain> toCheck = new Stack<>();
        toCheck.push((Terrain) this.getLocation());
        Map<Terrain, Double> distances = new HashMap<>();
        distances.put((Terrain) this.getLocation(), 0.0);
        while (0 != toCheck.size()) {
            double distTo;
            Terrain current = toCheck.pop();
            ArrayList<Terrain> adjacent = current.getAllAdjacentTerrains();
            for (Terrain t : adjacent) {
                if ((distTo = distances.get(current) + t.getMoveCost(this.getMovementType())) <= mobility) {
                    if (null == getGrid().get(t)) {
                        if (!distances.containsKey(t) || distTo < distances.get(t)) {
                            distances.put(t, distTo);
                            toCheck.push(t);
                        }
                        ans.add(t);
                    } else {
                        Unit u = (Unit) getGrid().get(t);
                        if (u.getOwner() == this.getOwner()) {
                            if (distances.containsKey(t)) {
                                if (distTo < distances.get(t)) {
                                    distances.put(t, distTo);
                                    toCheck.push(t);
                                }
                            } else {
                                distances.put(t, distTo);
                                toCheck.push(t);
                            }
                            if (u instanceof Carry) {
                                Carry c = (Carry) u;
                                if (c.canCarry(this)) {
                                    ans.add(t);
                                }
                            }
                        }
                    }
                }
            }
        }
        return ans;

    }

    //	@SuppressWarnings({ "unchecked", "rawtypes" })
//	private Queue<Terrain> findpathTo_2(Terrain target) throws IllegalArgumentException{
//		if(null==target){
//			throw new IllegalArgumentException("target is null");
//		}
//		Map<Terrain, Integer> shortDistances = new HashMap<>();
//		Map<Terrain, Terrain> previousLink = new HashMap<>();
//		Terrain current = (Terrain) getLocation();
//		Comparator<Terrain> comp = new Comparator<Terrain>() {
//			/**
//			 * Approximates which has shorter path to get to
//			 */
//			@Override
//			public int compare(Terrain arg0, Terrain arg1) {
//				int x;
//				//heuristic will never overestimate total cost
//				Integer c0 = ((-1==(x = calculatePathCost(arg0))?Integer.MAX_VALUE:x))+arg0.getDistanceTo(target);
//				Integer c1 = ((-1==(x = calculatePathCost(arg1))?Integer.MAX_VALUE:x))+arg1.getDistanceTo(target);
//				return c0.compareTo(c1);
//			}
//			private int calculatePathCost(Terrain t){
//				if(!previousLink.containsKey(t)){
//					return -1;
//				}
//				Terrain parent = previousLink.get(t);
//				if(null==parent){
//					return 0;
//				}else{
//					return (int) (calculatePathCost(previousLink.get(parent))+parent.getMoveCost(getMovementType()));
//				}
//			}
//			
//		};
//		PriorityQueue<Terrain> toCheck = new PriorityQueue<>(comp);
//		//add all tiles in range
//		toCheck.addAll(getValidMoveSpaces());
//		
//		
//		
//		
//	}

    private int totalCost(Queue<Terrain> path) {
        if (null == path || path.isEmpty()) {
            return 0;
        } else {
            int ans = 0;
            for (Terrain t : path) {
                ans += t.getMoveCost(getMovementType());
            }
            return ans;
        }
    }

    private int totalCost(Terrain t, Map<Terrain, Terrain> previousLink) {
        if (!previousLink.containsKey(t)) {
            throw new IllegalStateException("terrain not mapped to parent — how did we get here?");
        }
        Terrain parent = previousLink.get(t);
        if (null == parent) {
//			return (int) t.getMoveCost(getMovementType());
            //we started here, 0
            return 0;
        } else {
            return (int) (t.getMoveCost(getMovementType()) + totalCost(parent, previousLink));
        }
    }

    /**
     * Precondition: the unit has enough mobility to reach the Terrain that the path is being found to.
     * The returned path will be optimal, uses A*
     *
     * @return a queue of Terrains that could be traversed to reach the destination
     * @throws Exception
     */
    private Queue<Terrain> findPathTo(Terrain target) throws Exception {
        Queue<Terrain> ans = new LinkedList<>();

        if (null == target) {
            return new LinkedList<>();
        }

        if (target == getLocation()) {
            //you are already here
            return ans;
        }
        Map<Terrain, Terrain> previousLink = new HashMap<>();
        previousLink.put((Terrain) getLocation(), null);
        @SuppressWarnings({"unchecked", "rawtypes"})
        Comparator<Terrain> comp = new Comparator() {
            @Override
            /**
             * Approximates which path is better
             */
            public int compare(Object o1, Object o2) {
                Terrain t1 = (Terrain) o1;
                Terrain t2 = (Terrain) o2;
//				Integer c1 = totalCost(savedPaths.get(t1))+t1.getDistanceTo(target);
//				Integer c2 = totalCost(savedPaths.get(t2))+t2.getDistanceTo(target);
                Integer c1 = totalCost(t1, previousLink) + t1.getDistanceTo(target);
                Integer c2 = totalCost(t2, previousLink) + t2.getDistanceTo(target);
                return c1.compareTo(c2);
            }

        };
        PriorityQueue<Terrain> toCheck = new PriorityQueue<>(comp);
        toCheck.add((Terrain) getLocation());
        while (!toCheck.isEmpty()) {
            Terrain current = toCheck.poll();
            for (Terrain t : current.getAllAdjacentTerrains()) {
                if (999 != t.getMoveCost(getMovementType()) &&
                        (null == getGrid().get(t) || ((Unit) (getGrid().get(t))).getOwner() == this.getOwner())) {

                    boolean updated = false;
                    if (!previousLink.containsKey(t)) {
                        previousLink.put(t, current);
                        updated = true;
                    }

                    if (t == target) {
                        return genPath(t, previousLink);
                    }

                    if (t.getMoveCost(getMovementType()) + totalCost(current, previousLink) < totalCost(t, previousLink)) {
                        previousLink.put(t, current);
                        updated = true;
                    }
                    if (totalCost(t, previousLink) <= getCurrentMobility() && updated) {
                        if (!toCheck.contains(t)) {
                            toCheck.add(t);
                        }

                    }
                }
            }
        }
        //if you got here, then there is no path
        //See Precondition: there is a path, checked by getValidMoveSpaces
        throw new Exception("no path, precondition not met");

    }

    /**
     * Should only be invoked by {@code move(Queue<Terrain> path)} method
     * Postcondition: this.getLocation returns t
     */
    private void traverse(Grid<Actor> gr, Terrain t) {
        double moveCost = t.getMoveCost(this.getMovementType());
        setMobility(getCurrentMobility() - moveCost);
    }

    /**
     * Moves the {@link Unit} along a path of {@link Terrain}s.
     *
     * @param path A {@link Queue} of adjacent {@link Terrain}s
     * @return Whether or not {@link Unit} was successful in moving all the way along the path.
     * For now, it should always be successful because fog of war/land mines etc are not implemented yet
     */
    public boolean move(Queue<Terrain> path, boolean... realMove) {
        if (0 == path.size()) {
            //already done
            return true;
        }
        Terrain t = path.poll();
        Grid<Actor> gr = this.getGrid();
        //reached end of path, success
        if (t == null) {
            return true;
        }
        //invalid Terrain, fail -- should never happen
        if (!(gr.isValid(t))) {
            System.err.println("invalid terrain, not sure what happened here");
            return false;
        }
        //not enough mobility -- should never happen
        if (t.getMoveCost((this.getMovementType())) > this.getCurrentMobility()) {
            System.err.println("not enough mobi -- check BFS algorithm???");
            return false;
        }
        //Terrain is occupied by something other than allied Unit, fail
        try {
            Unit u = (Unit) (gr.get(t));
            if (null != u && u.getOwner() != this.getOwner()) {
                System.err.println("Terrain is occupied by something other than allied Unit, fail");
                return false;
            }
        } catch (ClassCastException actorIsNotAUnit) {
            System.err.println(Arrays.toString(actorIsNotAUnit.getStackTrace()));
            System.err.println("why do you have non-units in the grid");
            return false;
        }
        //move on
        traverse(getGrid(), t);
        if (path.peek() == null && realMove.length != 0) {
            teleport(t);
        }
        return realMove.length == 0 ? move(path) : move(path, true);
    }

    private void teleport(Terrain t) {
        Grid gr = getGrid();
        this.removeSelfFromGrid();
        this.putSelfInGrid(gr, t);
    }


    /**
     * Precondition: Carry c canCarry(this) is true
     *
     * @param c the Carry that this Unit will be loaded into
     * @throws Exception if unit cannot be carried
     */
    public void load(Carry c) throws Exception {
        if (c.canCarry(this)) {
            c.addUnit(this);
        } else {
            throw new Exception("cannot be carried, precondition not met");
        }
        immobilize();
    }

    public String getWeaponInfo() {
        if (this instanceof HiddenUnit) {
            return null;
        }
        if (WeaponType.NONE.equals(getWeapons()[0].getWeaponType())) {
            if (getWeapons()[1] == null) {
                return "Unarmed";
            } else {
                return "∞ ammo: " + getWeapons()[1].getWeaponType();
            }
        } else {
            return getAmmo() + " ammo: " + getWeapons()[0].getWeaponType() +
                    ((null != getWeapons()[1]) ? ", ∞ ammo: " + getWeapons()[1].getWeaponType() : "");
        }
    }

    public String getInfo() {
        return getType() + ((null != getLocation() && getLocation() instanceof Terrain) ? " at " + ((Terrain) getLocation()).getUIName() + getLocation() : "")
                + " [" + getHealth() + " HP, " + ((int) getFuel()) + " fuel, " + getWeaponInfo() + " ]" +
                (((this instanceof Stealth && ((Stealth) this).isHidden()) || (this instanceof Stealth2 && ((Stealth2) this).isHidden())) ?
                        "[hidden]" : "");
    }

    public String getConciseInfo() {
        return getType() + " [" + getHealth() + " HP]";
    }

    /**
     * @return whether or not this unit is a jet aircraft
     */
    public boolean isJet() {
        return (this instanceof Fighter || this instanceof Bomber || this instanceof StealthBomber ||
                this instanceof AdvFighter || this instanceof JSF || this instanceof CAS || this instanceof DropShip);
    }
} public class Infantry extends Unit {

    /**
     * Constructs an Infantry owned by owner
     * also sets the correct weapons
     */
    public Infantry(Player p) {
        super(p);
        setWeapon(0, WeaponType.NONE); //no primary -- secondary has infinite ammo
        setWeapon(1, WeaponType.RIFLE);
    }

    public Infantry(Player p, Suit suit) {
        this(p);
        this.setSuit(suit);
    }

    @Override
    public int getBuildCost() {
        return 100;
    }

    @Override
    public double getBaseArmorResistance() {
        //0% resistance
        return 1;
    }

    @Override
    public void outOfFuel() {
        //do nothing
    }

    @Override
    public MoveType getMovementType() {
        return MoveType.FOOT;
    }

    /**
     * Precondition: Infantry is on an enemy
     * owned or neutral Property
     * Ticks the cap timer of the Property with tickCapTimer()
     */
    public void capture() {
        System.out.println("capturing city");
        Terrain t = (Terrain) this.getLocation();
        if (t instanceof Property) {
            Property p = (Property) t;
            System.out.println("capTimer = " + p.getCapTimer());
            p.tickCapTimer(this);
        } else {
            System.err.println("?? not on property, loc = " + getLocation().getClass().getSimpleName() + " " + getLocation());
        }
        this.immobilize();
    }

    @Override
    public boolean couldTarget(Unit toCheck, Terrain hypothetical) { //cannot target jet fighters, boats
        return super.couldTarget(toCheck, hypothetical) && !toCheck.isJet() && !MoveType.SEA.equals(toCheck.getMovementType());
    }

} public final class WeaponType {

	//direct fire
	public static final String NONE = "NONE";
	public static final String RIFLE = "RIFLE";
	public static final String ROCKET_LAUNCHER = "ROCKET_LAUNCHER";
	public static final String MG = "MG";
	public static final String HMG = "HMG";
	public static final String AP = "AP";
	public static final String HE = "HE";
	public static final String HEAT = "HEAT";
	public static final String ROCKET_POD = "ROCKET_POD";
	public static final String MISSILE = "MISSILE";
	public static final String FLAK = "FLAK";
	public static final String TORPEDO = "TORPEDO";
	public static final String DEPTH_CHARGE = "DEPTH_CHARGE";
	
	//indirect fire
	public static final String SHELL = "SHELL";
	public static final String MISSILES = "MISSILES";
	public static final String ROCKET = "ROCKET";
	public static final String ROTARY = "ROTARY";
	
	//special
	public static final String ICBM = "ICBM";
}
import info.gridworld.grid.Grid;
import info.gridworld.grid.Location;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.beans.PropertyChangeListener;
import java.beans.PropertyEditor;
import java.beans.PropertyEditorManager;
import java.beans.PropertyEditorSupport;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import javax.swing.Action;
import javax.swing.ImageIcon;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import cbskarmory.Player;
import cbskarmory.Runner;
import cbskarmory.TerrainGrid;
import cbskarmory.terrain.Terrain;
import cbskarmory.terrain.properties.Factory;
import cbskarmory.terrain.properties.Property;
import cbskarmory.units.Carry;
import cbskarmory.units.HiddenUnit;
import cbskarmory.units.Stealth;
import cbskarmory.units.Stealth2;
import cbskarmory.units.Unit;
import cbskarmory.units.land.Infantry;
import cbskarmory.weapons.WeaponType;
                                    for (int i = 0; i < 2; i++) {
                                        display.showIconsOnSetOfLocations(hidePic.getImage(), where);
                                        try {
                                            Thread.sleep(500);
                                        } catch (InterruptedException e) {
                                            e.printStackTrace();
                                        }
                                        tryRepaint(display);
                                    }
                                    noBugsPls(display, (TerrainGrid) u.getGrid());

                                }
                            }).start();
                            display.repaint();
                        }

                        @Override
                        public void addPropertyChangeListener(PropertyChangeListener arg0) {
                        }

                        @Override
                        public Object getValue(String arg0) {
                            return null;
                        }

                        @Override
                        public boolean isEnabled() {
                            return enabled;
                        }

                        @Override
                        public void putValue(String arg0, Object arg1) {
                        }

                        @Override
                        public void removePropertyChangeListener(PropertyChangeListener arg0) {
                        }

                        @Override
                        public void setEnabled(boolean arg0) {
                            enabled = arg0;
                        }
                    };
                    hidingOption.setAction(hidingAction);
                    hidingOption.setIcon(hideIco);
                    hidingOption.setText(s.isHidden() ? "unhide" : "hide");
                    ans.add(hidingOption);
                }
            } else if (u instanceof Stealth2) {
                Stealth2 s = (Stealth2) u;
                {
                    JMenuItem hidingOption = new JMenuItem();
                    ImageIcon hideIco = get16xIcon(MenuMaker.class.getClassLoader().getResource("32x/hide.png"));
                    ImageIcon hidePic = new ImageIcon(MenuMaker.class.getClassLoader().getResource("32x/poof.png"));
                    Action hidingAction = new Action() {
                        public boolean enabled = true;

                        @Override
                        public void actionPerformed(ActionEvent arg0) {
                            try {
                                u.move((Terrain) newLoc, true);
                            } catch (Exception e1) {
                                e1.printStackTrace();
                            }
                            if (!s.isHidden()) {
                                s.hide();
                            } else {
                                s.unHide();
                            }
                            new Thread(new Runnable() {

                                @Override
                                public void run() {
                                    Set<Terrain> where = new HashSet<>();
                                    where.add((Terrain) newLoc);
                                    noBugsPls(display, (TerrainGrid) u.getGrid());
                                    for (int i = 0; i < 2; i++) {
                                        display.showIconsOnSetOfLocations(hidePic.getImage(), where);
                                        try {
                                            Thread.sleep(500);
                                        } catch (InterruptedException e) {
                                            e.printStackTrace();
                                        }
                                        tryRepaint(display);
                                    }
                                    noBugsPls(display, (TerrainGrid) u.getGrid());
                                }
                            }).start();
                            display.repaint();
                        }

                        public void addPropertyChangeListener(PropertyChangeListener arg0) {
                        }

                        public Object getValue(String arg0) {
                            return null;
                        }

                        public boolean isEnabled() {
                            return enabled;
                        }

                        public void putValue(String arg0, Object arg1) {
                        }

                        public void removePropertyChangeListener(PropertyChangeListener arg0) {
                        }

                        public void setEnabled(boolean arg0) {
                            enabled = arg0;
                        }
                    };
                    hidingOption.setAction(hidingAction);
                    hidingOption.setIcon(hideIco);
                    hidingOption.setText(s.isHidden() ? "unhide" : "hide");
                    ans.add(hidingOption);
                }
            }

            // infantry can capture
            if (u instanceof Infantry) {