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