All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ch.sahits.game.openpatrician.engine.sea.LocationTracker Maven / Gradle / Ivy

package ch.sahits.game.openpatrician.engine.sea;

import ch.sahits.game.openpatrician.event.EGameStatusChange;
import ch.sahits.game.openpatrician.event.GameStateChange;
import ch.sahits.game.openpatrician.event.data.ShipPositionUpdateEvent;
import ch.sahits.game.openpatrician.model.AIPlayerList;
import ch.sahits.game.openpatrician.model.IAIPlayer;
import ch.sahits.game.openpatrician.model.IPlayer;
import ch.sahits.game.openpatrician.model.PlayerList;
import ch.sahits.game.openpatrician.model.map.IMap;
import ch.sahits.game.openpatrician.model.sea.ILocationTracker;
import ch.sahits.game.openpatrician.model.ship.INavigableVessel;
import ch.sahits.game.openpatrician.utilities.annotation.ClassCategory;
import ch.sahits.game.openpatrician.utilities.annotation.EClassCategory;
import ch.sahits.game.openpatrician.utilities.annotation.WeakMatrixListType;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Interner;
import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import com.thoughtworks.xstream.annotations.XStreamOmitField;
import javafx.geometry.Dimension2D;
import javafx.geometry.Point2D;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Component that helps split up the amount of ships into segements of the navigable map.
 * This reduces the omount of ships that need to be checked if a ship nearby is searched.
 *
 * @author Andi Hotz, (c) Sahits GmbH, 2015
 *         Created on Dec 13, 2015
 */
@Component
@Lazy
@ClassCategory({EClassCategory.SINGLETON_BEAN, EClassCategory.SERIALIZABLE_BEAN})
public class LocationTracker implements ILocationTracker {
    @XStreamOmitField
    private static final Logger LOGGER = LogManager.getLogger(LocationTracker.class);
    @Autowired
    private IMap map;
    @Autowired
    @Qualifier("serverClientEventBus")
    @XStreamOmitField
    private AsyncEventBus clientServerEventBus;
    @Autowired
    @Qualifier("syncServerClientEventBus")
    @XStreamOmitField
    private EventBus syncServerClientEventBus;
    @Autowired
    @XStreamOmitField
    protected Interner pointInterner;
    @Autowired
    private AIPlayerList aiPlayers;
    @Autowired
    private PlayerList players;

    private Dimension2D mapDimension;

    private double segmentEdgeWidth;
    private double segmentEdgeHeigth;

    // First index is columns, second is rows
    @WeakMatrixListType(INavigableVessel.class)
    private List>[][] segments;

    @PostConstruct
    void initialize() {
        mapDimension = map.getDimension();
        updateSegmentSize(20);
        clientServerEventBus.register(this);
        syncServerClientEventBus.register(this);
    }

    @PreDestroy
    private void unregister() {
        clientServerEventBus.unregister(this);
        syncServerClientEventBus.unregister(this);
    }

    public void updateSegmentSize(int nbShips) {
        int[] nbSegments = calculateRowsAndColumnsNeeded(nbShips/2, mapDimension);
        segmentEdgeWidth = (mapDimension.getWidth() + 1) / nbSegments[1];
        segmentEdgeHeigth = (mapDimension.getHeight() + 1) / nbSegments[0];
        List registeredVessels = new ArrayList<>();
        if (segments != null) {
            for (List>[] outer : segments) {
                for (List> inner : outer) {
                    for (WeakReference vessel : inner) {
                        if (vessel.get() != null) {
                            registeredVessels.add(vessel.get());
                        }
                    }
                }
            }
        }
        segments = new List[nbSegments[1]][nbSegments[0]];
        for (int i = 0; i < nbSegments[1]; i++) {
            for (int j = 0; j < nbSegments[0]; j++) {
                segments[i][j] = new ArrayList<>();
            }
        }
        for (INavigableVessel registeredVessel : registeredVessels) {
            add(registeredVessel);
        }
    }
    @VisibleForTesting
    int[] calculateIndices(Point2D location) {
        int indexX = (int) Math.max(Math.floor(location.getX() / segmentEdgeWidth), 0);
        int indexY = (int) Math.max(Math.floor(location.getY() / segmentEdgeHeigth), 0);
        return new int[]{indexX, indexY};
    }

    public void add(INavigableVessel ship) {
        int[] index = calculateIndices(ship.getLocation());
        segments[index[0]][index[1]].add(new WeakReference(ship));
    }

    public void remove(INavigableVessel ship) {
        int[] index = calculateIndices(ship.getLocation());
        if (!segments[index[0]][index[1]].removeIf(wr -> ship.equals(wr.get()))) {
            LOGGER.warn("The ship {} could not be found and removed in segment {}", ship, index);
        }
    }

    public List getShipsInSegment(Point2D location) {
        int[] index = calculateIndices(location);
        List> references = segments[index[0]][index[1]];
        ArrayList copyList = new ArrayList<>();
        for (WeakReference reference : references) {
            if (reference.get() != null) {
                copyList.add(reference.get());
            }
        }
        return copyList;
    }

    public List getShipsInSegments(Point2D location, int radius) {
        int[] index = calculateIndices(location);
        final Point2D locationBefore = pointInterner.intern(getPointOnMap(location.getX() - radius, location.getY()));
        int[] indexBefore = calculateIndices(locationBefore);
        final Point2D locationAfter = pointInterner.intern(getPointOnMap(location.getX() + radius, location.getY()));
        int[] indexAfter = calculateIndices(locationAfter);
        Set set = new HashSet<>();
        if (indexBefore != index) {
            set.addAll(getShipsInSegment(locationBefore));
        }
        set.addAll(getShipsInSegment(location));
        if (indexAfter != index) {
            set.addAll(getShipsInSegment(locationAfter));
        }
        return new ArrayList<>(set);
    }

    /**
     * Ensure that the point is on the map.
     * @param x coordinate on the map
     * @param y coordinate on the map
     * @return Point representing the point on the map.
     */
    private Point2D getPointOnMap(double x, double y) {
         double xx = Math.min(Math.max(0, x), mapDimension.getWidth());
        double yy = Math.min(Math.max(0, y), mapDimension.getHeight());
        return new Point2D(xx, yy);
    }

    @Subscribe
    public void handleShipMove(ShipPositionUpdateEvent event) {
        if (event.getFromLocation().getX() != event.getToLocation().getX()
                || event.getFromLocation().getY() != event.getToLocation().getY()) {
            int[] oldIndex = calculateIndices(event.getFromLocation());
            int[] newIndex = calculateIndices(event.getToLocation());
            if (!Arrays.equals(oldIndex, newIndex)) {
                segments[oldIndex[0]][oldIndex[1]].removeIf(wr -> event.getShip().equals(wr.get()));
                segments[newIndex[0]][newIndex[1]].add(new WeakReference<>(event.getShip()));
            }
        }
    }

    /**
     * Calculate the number of segments the image is partitioned into
     * @param numberOfImages target number of segments
     * @param containerSize dimension of the image
     * @return array of length: index 0: nbRows, index 1: nbCols
     */
    @VisibleForTesting
    int[] calculateRowsAndColumnsNeeded(int numberOfImages, Dimension2D containerSize) {
        int colsAttempt;
        int rowsAttempt;
        // Calculate the length of one side from a single cell
        int containerArea = (int) Math.rint(containerSize.getHeight() * containerSize.getWidth());
        float singleCellArea = containerArea / numberOfImages;
        double cellSideLength = Math.sqrt(singleCellArea);

        colsAttempt = (int) Math.floor(containerSize.getWidth() / cellSideLength);
        rowsAttempt = (int) Math.floor(containerSize.getHeight() / cellSideLength);

        if (colsAttempt * rowsAttempt >= numberOfImages) {

            return new int[]{rowsAttempt, colsAttempt};

        }
        // If the container is a square or bigger horizontally than vertically
        else if (containerSize.getHeight() <= containerSize.getWidth()) {

            colsAttempt = (int) Math.ceil(containerSize.getWidth() / cellSideLength);
            rowsAttempt = (int) Math.floor(containerSize.getHeight() / cellSideLength);

            if (colsAttempt * rowsAttempt >= numberOfImages) {
                //
                return new int[]{rowsAttempt, colsAttempt};

            } else {

                colsAttempt = (int) Math.floor(containerSize.getWidth() / cellSideLength);
                rowsAttempt = (int) Math.ceil(containerSize.getHeight() / cellSideLength);

                if (colsAttempt * rowsAttempt >= numberOfImages) {
                    return new int[]{rowsAttempt, colsAttempt};
                } else {
                    colsAttempt = (int) Math.ceil(containerSize.getWidth() / cellSideLength);
                    rowsAttempt = (int) Math.ceil(containerSize.getHeight() / cellSideLength);

                    if (colsAttempt * rowsAttempt >= numberOfImages) {
                        return new int[]{rowsAttempt, colsAttempt};
                    } else {
                        throw new IllegalStateException("Could not calculate enough rows and columns");
                    }
                }
            }
        }
        // If the container is bigger vertically than horizontally
        else {

            colsAttempt = (int) Math.floor(containerSize.getWidth() / cellSideLength);
            rowsAttempt = (int) Math.ceil(containerSize.getHeight() / cellSideLength);

            if (colsAttempt * rowsAttempt >= numberOfImages) {
                //
                return new int[]{rowsAttempt, colsAttempt};

            } else {

                colsAttempt = (int) Math.ceil(containerSize.getWidth() / cellSideLength);
                rowsAttempt = (int) Math.floor(containerSize.getHeight() / cellSideLength);

                if (colsAttempt * rowsAttempt >= numberOfImages) {
                    return new int[]{rowsAttempt, colsAttempt};
                } else {
                    colsAttempt = (int) Math.ceil(containerSize.getWidth() / cellSideLength);
                    rowsAttempt = (int) Math.ceil(containerSize.getHeight() / cellSideLength);

                    if (colsAttempt * rowsAttempt >= numberOfImages) {
                        return new int[]{rowsAttempt, colsAttempt};
                    } else {
                        throw new IllegalStateException("Could not calculate enough rows and columns");
                    }
                }
            }
        }
    }
    @Subscribe
    public void handleGameLoad(GameStateChange gameStateChange) {
        if (gameStateChange.getStatusChange() == EGameStatusChange.GAME_LOADED) {
            int nbShips = 0;
            for (IAIPlayer player : aiPlayers) {
                nbShips +=  player.getFleet().size();
            }
            for (IPlayer player : players) {
                nbShips +=  player.getFleet().size();
            }
            updateSegmentSize(nbShips);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy